Android串口編程入門
標簽: Android智能設備開發 智能硬件 android java kotlin 串口編程
基本常識
串口通信:指串口按位(bit)發送和接收字節。盡管比按字節(byte)的并行通信慢,但是串口可以使用一根線發送數據的同時接收數據。在串口通信中,常用的協議包括RS232、RS-422和RS-485。
在Android開發中,對串口數據的讀取和寫入,實際上是是通過I/O流讀取、寫入文件數據。
串口用完記得關閉(文件關閉)。 串口關閉,即是文件流關閉。
一、準備so庫以及相關SDK
用到開源庫serialPort-api
下載其中相關so庫資源導入到項目中
其中libs文件夾中的armeabi、armeabi-v7a代表不同的cpu架構,可以根據自己app運行環境自動加載,通常只需要armeabi就可以了。android_serialport_api包下的兩個文件則是用來加載so庫以及提供相關接口的封裝SDK;
注意:這里的包名不可以修改,必須跟so庫中的保持一致(這同樣在以后對接其他外設SDK時,在加載so庫方法經常會遇到方法名加載不到的原因)
在拷貝完相關so庫之后我們仍然需要在gradle中指定我們支持的cpu架構,保持libs包中一致即可:
defaultConfig {
ndk {
abiFilters "armeabi","armeabi-v7a" // "armeabi", "x86", "arm64-v8a"
}
}
SerialPort類:
/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android_serialport_api;
import android.util.Log;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Scanner;
public class SerialPort {
private static final String TAG = "SerialPort";
/*
* Do not remove or rename the field mFd: it is used by native method close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
public SerialPort(File device, int baudrate, int flags, int parity) throws SecurityException, IOException {
/* Check access permission */
if (!device.canRead() || !device.canWrite()) {
try {
/* Missing read/write permission, trying to chmod the file */
//Runtime.getRuntime().exec 會提示需要ACCESS_SUPERUSER權限
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
// su = Runtime.getRuntime().exec("/system/xbin/su");
String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";
su.getOutputStream().write(cmd.getBytes());
WatchThread wt = new WatchThread(su);
wt.start();
if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
ArrayList<String> commandStream = wt.getStream();
wt.setOver(true);
throw new SecurityException();
}
} catch (Exception e) {
e.printStackTrace();
throw new SecurityException();
}
}
mFd = open(device.getAbsolutePath(), baudrate, flags, parity);
if (mFd == null) {
Log.e(TAG, "native open returns null");
throw new IOException();
}
mFileInputStream = new FileInputStream(mFd);
mFileOutputStream = new FileOutputStream(mFd);
}
// Getters and setters
public InputStream getInputStream() {
return mFileInputStream;
}
public OutputStream getOutputStream() {
return mFileOutputStream;
}
// JNI
private native static FileDescriptor open(String path, int baudrate, int flags, int parity);
public native void close();
static {
System.loadLibrary("serial_port");
}
class WatchThread extends Thread {
Process p;
boolean over;
ArrayList<String> stream;
public WatchThread(Process p) {
this.p = p;
over = false;
stream = new ArrayList<String>();
}
public void run() {
try {
if(p == null)return;
Scanner br = new Scanner(p.getInputStream());
while (true) {
if (p==null || over) break;
while(br.hasNextLine()){
String tempStream = br.nextLine();
if(tempStream.trim()==null||tempStream.trim().equals(""))continue;
stream.add(tempStream);
}
}
} catch(Exception e){e.printStackTrace();}
}
public void setOver(boolean over) {
this.over = over;
}
public ArrayList<String> getStream() {
return stream;
}
}
}
從這個類中我們可以看到,通過System.loadLibrary來加載我們對應的so庫,“serial_port”名稱則對應libs文件夾下面的armeabi包中的so庫名稱,如果不是一致的則會拋出異常提示加載so庫失敗。
static {
System.loadLibrary("serial_port");
}
當然我們有些也可以將so庫放置在jni文件夾或者jnilib文件夾下面,如果我們so庫防止的路徑沒有問題,但是在加載so庫的時候仍然拋出異常提示找不到對應的so庫,那么可能是我們程序并沒有識別到相應的路徑,這個時候我們就需要在gradle里面進行配置:
sourceSets.main {
jniLibs.srcDir "libs"
}
同時可以看到該類中封裝的打開和關閉串口的方法:
private native static FileDescriptor open(String path, int baudrate, int flags, int parity);
public native void close();
如果沒有權限的也需要配置相關的系統權限,包括我們進行串口初始化時需要的root權限也是必須要的,如果沒有root權限則無法進行串口初始化。到此為止我們串口編程的基本準備工作已經全部準備就緒。
二、編寫串口通訊工具類
首先我們需要編寫的是串口通訊需要的工具類,該工具類主要解決一下幾個問題:串口的打開、關閉、輸入監聽、輸出等;同時該工具類作為全局唯一的操作類來進行串口通訊的統一管理:
package com.xiye.reservesamplecabinet.manager
import android_serialport_api.SerialPort
import com.xiye.reservesamplecabinet.utils.L
import com.xiye.reservesamplecabinet.utils.T
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.security.InvalidParameterException
/**
* Created by Administrator on 2018/8/3.
*/
class BoxActionManager private constructor() {
companion object {
val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { BoxActionManager() }
private var mSerialPort: SerialPort? = null
private var mOutputStream: OutputStream? = null
private var mInputStream: InputStream? = null
private var mReadDataThread: Thread? = null
/**
* 初始化鎖控串口
*/
@Throws(IOException::class)
private fun initSerialPort() {
if (mSerialPort == null) {
mSerialPort = getSerialPort(0, "dev/ttyS0", 9600)
if (mSerialPort == null) {
T.instance.showToast("串口初始化失敗!")
return
}
mOutputStream = mSerialPort?.outputStream
mInputStream = mSerialPort?.inputStream
}
}
/**
* 初始化串口
*/
private fun getSerialPort(parity: Int, path: String, baudrate: Int): SerialPort? {
var mSerialPort: SerialPort? = null
if (path.isEmpty() || baudrate == -1) {
throw InvalidParameterException()
}
try {
mSerialPort = SerialPort(File(path), baudrate, 0, 0)
} catch (e: IOException) {
e.printStackTrace()
} catch (e: SecurityException) {
e.printStackTrace()
}
return mSerialPort
}
}
init {
if (mSerialPort == null) {
try {
initSerialPort()
} catch (e: IOException) {
e.printStackTrace()
}
}
initThread()
}
/**
* 初始化串口讀取線程
*/
private fun initThread() {
if (mReadDataThread == null) {
mReadDataThread = Thread {
while (true) {
try {
if (mInputStream != null) {
val count = mInputStream!!.available()
val buffer = ByteArray(count)
val size = mInputStream!!.read(buffer)
val hexStr = TransUtils.bytes2hex(buffer)
if (size > 0) {
onDataReceiver(buffer)
}
try {
Thread.sleep(50L)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
mReadDataThread?.start()
}
}
private fun onDataReceiver(buffer: ByteArray) {
}
@Synchronized
fun openDoor(boardNo: Int, lockNo: Int) {
if (checkSerialPort()) {
return
}
val cmd = byteArrayOf(
boardNo.toByte(), 0xF2.toByte(), 0x55, lockNo.toByte()
)
try {
mOutputStream?.write(TransUtils.getSendDatas(cmd))
mOutputStream?.flush()
} catch (e: IOException) {
T.instance.showToast(e.message)
}
}
@Synchronized
fun queryDoor(boardNo: Int, lockNo: Int) {
if (checkSerialPort()) {
return
}
val cmd = byteArrayOf(
boardNo.toByte(), 0xF1.toByte(), 0x55, lockNo.toByte()
)
try {
mOutputStream?.write(TransUtils.getSendDatas(cmd))
mOutputStream?.flush()
} catch (e: IOException) {
T.instance.showToast(e.message)
}
}
}
在初始化方法中可以看到mSerialPort = getSerialPort(0, “dev/ttyS0”, 9600),是指定了一個固定的串口進行初始化,我們也可以通過SerialPortFinder類中提供的方法進行一個動態的搜索串口并進行相關初始化,需要根據項目不同的需求來靈活使用。
其中onDataReceiver方法則是監聽到的輸入信息,我們可以通過協議相關的約定進行一個解析來判斷監聽到的輸入數據是否合法,同時解析出我們需要的數據幀。
同樣我們是通過**mOutputStream?.write()**方法來對數據輸出,輸出的數據幀同樣是根據協議中的約定來進行組幀。
感興趣的同學可以通過NDK開發中jni配置及調用GPIO了解如何自己一步步編譯屬于自己的so庫文件并運用到項目中
智能推薦
PCI串口編程
一、硬件環境 硬件開發環境是PCI9054+FPGA,16串口或8串口。 軟件開發平臺則是linux、VxWorks。 主要是運用在工控領域,在一般商用平臺可能用不到這玩意。 二、PCI相關知識 PCI相關知識主要是參考了網上一些前輩寫的博文,這里將其貼出來,以免后來人繼續收集。 PCI 總線學習筆記-PCI9054 http://blog.csdn.net/lg2lh/ar...
Linux串口編程
一、串口通信介紹 串口是計算機上的串行通信的物理接口。首先先介紹一下串行通信,串行通信的分類: 1、按照數據傳送方向,分為: 單工:數據傳輸只支持數據在一個方向上傳輸;就像路上的單行線。 半雙工:允許數據在兩個方向上傳輸。但是,在某一時刻,只允許數據在一個方向上傳輸;半雙工就像分時段的單行線,上午時段通行這邊,下午時段通行另一邊,而單工就是全天單行線。 全雙工:允許數據同時在兩個方向上傳輸。因此,...
Linux 串口編程
串行接口 串行接口 (Serial Interface) 是指數據一位一位地順序傳送,其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信,從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。一條信息的各位數據被逐位按順序傳送的通訊方式稱為串行通訊。 串行通訊的特點是:數據位的傳送,按位順序進行,最少只需一根傳輸線即可完成;成本低但傳送速度慢。串行通訊的距離可以從幾米到幾千米...
串口編程案例
前提準備 虛擬串口 串口調試助手 其他資源 模擬COM1串口發送信息到COM2接收 首先、下載虛擬串口進行安裝,安裝完后打開軟件(漢化過程忽略) 右鍵打開計算機–>管理–>設備管理器–>端口 打開VS,新建項目(Windows 應用程序) 下載串口調試助手,打開 用串口調試助手發送,效果如圖: 簡單串口編程結束!...
比特說串口編程
要說一種植物,從種子發芽開始.要說計算機,從源頭開始. 在1.X系列的文章中,我們從最開始的燈泡的例子開始,一步步深入,知道了CPU是怎么工作的,配合內存可以自動工作。 前面的內容基本上是CPU工作原理的完整輪廓了。 我們知道現在使用的各種形態的計算機除了CPU還有很多別的設備,這些所有的設備都不在CPU里面,都是外部設備,簡稱:外設。常見外設有:鍵盤,鼠...
猜你喜歡
七、Linux串口編程
Linux下的串口編程過程如下(我就不給大家再講串口是什么了,不懂得朋友自行補上串口相關知識): 目錄 一、打開串口 二、初始化串口 1、 串口的初始化結構介紹 2、串口的初始化常用函數介紹 函數 tcgetattr 波特率相關的函數 cfsetispeed 和 cfsetospeed、cfgetispeed 、cfgetospeed 函數 tcflush 函數 tcseta...
樹莓派串口編程
串口通信通常用在多機通信中 比如樹莓派和語言模塊的通信 數據在T和R之間通信,這時全雙工的 全雙工:兩個人互相罵 半雙工: 講話的時候另一個人閉嘴 通信要求:1、語言相通 ——》數據格式 2、語速正常 ——》波特率 比如用串口使用樹莓派時,選擇了115200。 數據位,停止位,奇偶校驗位就是數據格式 如果不寫驅動,就要用到wiringPi 參考如下 ...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...