特別說明
當前博客平臺賬號已廢棄,如果有使用細節(jié)問題請前往我新博客平臺進行討論交流。
個人博客平臺 HuRuWo的技術(shù)小站
文章首發(fā)于個人博客HuRuWo的技術(shù)小站,如果本文非vip用戶無法完全瀏覽或者圖片無法打開,可前往個人博客文章地址查看文章并留言討論。
個人博客文章地址上半年最好的Android串口開發(fā)入門指南)
更多技術(shù)文章訪問本人博客HuRuWo的技術(shù)小站,包括 Electron從零開發(fā) Android 逆向 app 微信數(shù)據(jù)抓取 抖音數(shù)據(jù)抓取 閑魚數(shù)據(jù)抓取 小紅書數(shù)據(jù)抓取 其他軟件爬蟲 等技術(shù)文章
一些預(yù)備知識
物聯(lián)網(wǎng)開發(fā)開發(fā)是時下熱門的行業(yè)。Android系統(tǒng)自然也能進行物聯(lián)網(wǎng)開發(fā)。除開Android本身自帶的模塊還有一類通過外部鏈接的設(shè)備需要通過串口來進行通信。本人在做完兩個相關(guān)的抓娃娃和寄存柜項目之后覺得需要總結(jié)一點東西給大家。
關(guān)于串口
串口通信指串口按位(bit)發(fā)送和接收字節(jié)。盡管比按字節(jié)(byte)的并行通信慢,但是串口可以在使用一根線發(fā)送數(shù)據(jù)的同時用另一根線接收數(shù)據(jù)。
在串口通信中,常用的協(xié)議包括RS-232、RS-422和RS-485。
當然具體是那種協(xié)議和你選擇的硬件有關(guān),將你的硬件插到對應(yīng)協(xié)議的串口口即可。
開發(fā)前的準備
1.檢查你的開發(fā)板設(shè)備,包括開發(fā)板信息,開發(fā)板上面包含的模塊信息。是否有Wifi模塊 藍牙模塊 指定接口等。還有一方面就是關(guān)于開發(fā)板系統(tǒng)的信息,開發(fā)板的系統(tǒng)版本。如果需要特別定制,可以和廠商商量。
關(guān)于系統(tǒng)定制
某些特殊的板塊需要隱藏狀態(tài)欄不能被下拉,否則會被退出應(yīng)用。還有一方面就是可以定制取消掉下導(dǎo)航欄。
2.檢查你的硬件裝備
正確連接你的設(shè)備,向你的硬件提供商索要開發(fā)資料?;镜馁Y料包括硬件的通訊命令格式。當然更好的是如果能要到開發(fā)程序資料。比如android程序或者源碼那就更好了。
3.正確的連接,測試你的硬件與系統(tǒng)
Android串口助手
下載一個串口調(diào)試助手,按照資料輸入命令。測試是否能夠成功的啟動設(shè)備。并且收到對應(yīng)的返回數(shù)據(jù)。
開發(fā)階段
需要一點點的JNI知識和一點點Android多線程開發(fā)經(jīng)驗
整體的開發(fā)流程如下:打開指定串口-->開啟接收數(shù)據(jù)線程(readthead)-->發(fā)送串口數(shù)據(jù)-->接收數(shù)據(jù)處理返回信息-->關(guān)閉接收數(shù)據(jù)線程(readthead)-->關(guān)閉串口。
導(dǎo)入so庫
里面封裝了c層代碼調(diào)用底層代碼的通信方式,如果你們喜歡改東西的話??梢宰约焊闹?,不過我覺得沒有必要,因為這些代碼已經(jīng)封裝的很好了。直接使用即可。
至于通過c代碼如何生成相應(yīng)的so文件,以及如何java層調(diào)用c層代碼都是很基礎(chǔ)的東西啦。
我不想在這里展開大篇幅的講JNI,因為串口通信其實用的JNI知識不多。
首先把JNI相關(guān)代碼導(dǎo)入到自己的工程里面:
先看下目錄結(jié)構(gòu)吧:
jni目錄
[圖片上傳失敗...(image-76edb2-1606101414169)]
java 目錄
[圖片上傳失敗...(image-df6e96-1606101414169)]
SerialPort.java
了解JNI的同學(xué)都知道的,這個SerialPort.h對應(yīng)的就是SerialPort.java層的native 方法。
這里用兩個方法
private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();
很顯然一個是打開串口 一個是 關(guān)閉串口 方法
打開串口之前,程序需要獲得最高權(quán)限,SerialPort.java的構(gòu)造函數(shù)里面需要獲得設(shè)備的超級root權(quán)限,也是通過輸入su命令完成。
if (!device.canRead() || !device.canWrite()) {
try {
/* Missing read/write permission, trying to chmod the file */
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
+ "exit\n";
su.getOutputStream().write(cmd.getBytes());
if ((su.waitFor() != 0) || !device.canRead()
|| !device.canWrite()) {
throw new SecurityException();
}
} catch (Exception e) {
e.printStackTrace();
throw new SecurityException();
}
}
最后記得調(diào)用生成的.so文件
static {
System.loadLibrary("serial_port");
}
SerialPortFinder
這個類很簡單,能用于獲取設(shè)備的串口信息。通常一個開發(fā)板會有幾個到十幾個的串口。
兩個public方法:
-
public String[] getAllDevices()獲取所有串口名稱 -
public String[] getAllDevicesPath()獲取所有串口地址
開始通信
[圖片上傳失敗...(image-3ca0db-1606101414169)]
整個信息發(fā)送接收步驟如下:
1.初始化SerialPort 獲得權(quán)限打開指定串口
2.打開ReadThread監(jiān)聽數(shù)據(jù)返回
3.使用SendThread發(fā)送數(shù)據(jù)
4.繼續(xù)發(fā)送或者關(guān)閉
為此我們需要寫一個SerialHelper來簡化代碼,以下是核心代碼:
構(gòu)造函數(shù)
public SerialHelper(String sPort, int iBaudRate) {
this.sPort = "/dev/ttyS3";
this.iBaudRate = 9600;
this._isOpen = false;
this._bLoopData = new byte[]{48};
this.iDelay = 500;
this.sPort = sPort;
this.iBaudRate = iBaudRate;
}
打開 關(guān)閉 串口
//打開時打開監(jiān)聽線程
public void open() throws SecurityException, IOException, InvalidParameterException {
this.mSerialPort = new SerialPort(new File(this.sPort), this.iBaudRate, 0);
this.mOutputStream = this.mSerialPort.getOutputStream();
this.mInputStream = this.mSerialPort.getInputStream();
this.mReadThread = new SerialHelper.ReadThread();
this.mReadThread.start();
this.mSendThread = new SerialHelper.SendThread();
this.mSendThread.setSuspendFlag();
this.mSendThread.start();
this._isOpen = true;
}
// 關(guān)閉線程 釋放函數(shù)
public void close() {
if (this.mReadThread != null) {
this.mReadThread.interrupt();
}
if (this.mSerialPort != null) {
this.mSerialPort.close();
this.mSerialPort = null;
}
this._isOpen = false;
}
兩個線程 發(fā)送線程:
private class SendThread extends Thread {
public boolean suspendFlag;
private SendThread() {
this.suspendFlag = true;
}
public void run() {
super.run();
while(!this.isInterrupted()) {
synchronized(this) {
while(this.suspendFlag) {
try {
this.wait();
} catch (InterruptedException var5) {
var5.printStackTrace();
}
}
}
SerialHelper.this.send(SerialHelper.this.getbLoopData());
try {
Thread.sleep((long)SerialHelper.this.iDelay);
} catch (InterruptedException var4) {
var4.printStackTrace();
}
}
}
public void setSuspendFlag() {
this.suspendFlag = true;
}
public synchronized void setResume() {
this.suspendFlag = false;
this.notify();
}
}
讀取線程
private class ReadThread extends Thread {
private ReadThread() {
}
public void run() {
super.run();
while(!this.isInterrupted()) {
try {
if (SerialHelper.this.mInputStream == null) {
return;
}
byte[] buffer = new byte[512];
int size = SerialHelper.this.mInputStream.read(buffer);
if (size > 0) {
ComBean ComRecData = new ComBean(SerialHelper.this.sPort, buffer, size);
SerialHelper.this.onDataReceived(ComRecData);
}
} catch (Throwable var4) {
Log.e("error", var4.getMessage());
return;
}
}
}
}
其他函數(shù)見代碼,包括數(shù)值和文本發(fā)送 波特率的設(shè)置等等。
實戰(zhàn)一個串口數(shù)據(jù)調(diào)試助手
下面使用封裝的SerialHelper來完成整個數(shù)據(jù)發(fā)送接收:
界面隨便搞一下:
[圖片上傳失敗...(image-be31d6-1606101414169)]
然后開始邏輯代碼:
首先實例化SerialPortFinder 實現(xiàn)數(shù)據(jù)接收 寫入列表
serialPortFinder = new SerialPortFinder();
serialHelper = new SerialHelper() {
@Override
protected void onDataReceived(final ComBean comBean) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getBaseContext(), FuncUtil.ByteArrToHex(comBean.bRec), Toast.LENGTH_SHORT).show();
logListAdapter.addData(comBean.sRecTime+": "+FuncUtil.ByteArrToHex(comBean.bRec));
recy.smoothScrollToPosition(logListAdapter.getData().size());
}
});
}
};
然后利用SerialPortFinder找到所有的串口號,列出來所有的波特率 ,都傳給spinner
final String[] ports = serialPortFinder.getAllDevicesPath();
final String[] botes = new String[]{"0", "50", "75", "110", "134", "150", "200", "300", "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "576000", "921600", "1000000", "1152000", "1500000", "2000000", "2500000", "3000000", "3500000", "4000000"};
SpAdapter spAdapter = new SpAdapter(this);
spAdapter.setDatas(ports);
spSerial.setAdapter(spAdapter);
SpAdapter spAdapter2 = new SpAdapter(this);
spAdapter2.setDatas(botes);
spBote.setAdapter(spAdapter2)
打開串口:
btOpen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
serialHelper.open();
btOpen.setEnabled(false);
} catch (IOException e) {
e.printStackTrace();
}
}
});
數(shù)據(jù)發(fā)送(兩種數(shù)據(jù)發(fā)送格式):
文本類型
if (serialHelper.isOpen()) {
serialHelper.sendTxt(edInput.getText().toString());
} else {
Toast.makeText(getBaseContext(), "搞毛啊,串口都沒打開", Toast.LENGTH_SHORT).show();
}
Hex類型
if (serialHelper.isOpen()) {
serialHelper.sendHex(edInput.getText().toString());
} else {
Toast.makeText(getBaseContext(), "搞毛啊,串口都沒打開", Toast.LENGTH_SHORT).show();
}
最后記得關(guān)閉一下串口咯:
@Override
protected void onDestroy() {
super.onDestroy();
serialHelper.close();
}
好的 ,完事了。 測試一下
連線開機:
[圖片上傳失敗...(image-82778a-1606101414169)]
發(fā)串口信息:
[圖片上傳失敗...(image-b8ebf1-1606101414169)]
同時設(shè)備也滴塌滴塌的響了,完美。
代碼我也放上去把Android串口助手
一些要說的
雖然整個JNI移植過程非常簡單,但是問題出現(xiàn)了。如果大家使用的3.0版本的AS、會發(fā)現(xiàn)默認的JNI使用Cmake而不是.mk文件配置的。
所以又增加了一個難度,為了方便大家。我把所有關(guān)于串口的資源打包成aar 文件,大家直接使用即可。
使用過程:
aar文件導(dǎo)入lib文件夾
gradle文件
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation(name: 'serialport-1.0.1', ext: 'aar')
}
完成。
總結(jié)一下
基本的串口通信到此結(jié)束。到了實際生產(chǎn),更多的要解決多線程上的邏輯問題。設(shè)備的各種狀態(tài)以及突發(fā)狀況的處理等等。所以串口通信成功只是一個小小的開始,更多的問題還在后面。
再放一次本篇文章的代碼 Android串口助手
再次編輯一下:
!this.isInterrupted() 這個方法是谷歌官方demo里面的代碼,我沒有做修改。當然實際生產(chǎn)的時候你會碰到多個發(fā)送界面問題 需要結(jié)合 available 方法判斷是否有數(shù)據(jù)
try {
while (inputStream.available() > 0) {
int numBytes = inputStream.read(readBuffer);
}
System.out.print(new String(readBuffer));
} catch (IOException e) {
}