上一篇:Android 串口通信筆記2 調(diào)試工具分析 工具類實(shí)現(xiàn)分析、項(xiàng)目實(shí)現(xiàn)
Android串口開發(fā) 延伸和擴(kuò)展,
1.使用JNI Cmake 自己編譯串口通信 的so庫(kù):Android Studio 3.0 實(shí)現(xiàn)方式。
2.CRC校驗(yàn) 以及擴(kuò)展設(shè)計(jì):
a.一(串口)對(duì)多(硬件通信);
b.多(串口)對(duì)多(硬件)的實(shí)現(xiàn)。
1.以串口調(diào)試工具為例,使用其原本的源代碼使用JNI Cmake Android Studio 3.0 實(shí)現(xiàn)方式。

勾選 include C++ support 沒有下載ndk 的要下載。
①.延續(xù)使用jni 的方式

把相關(guān)的 been 和實(shí)現(xiàn)方法 都復(fù)制過(guò)來(lái)如圖。
創(chuàng)建.h 文件 注:一定要現(xiàn)進(jìn)入到app/main/java/ 目錄下
然后 javah -classpath -jni +完整路徑到類名

在main目錄下創(chuàng)建jni 文件夾,把生成的.h 文件復(fù)制進(jìn)去 ,新建同名的.c文件,把實(shí)現(xiàn)代碼拷進(jìn)去--注意需要修改 open 和close方法的名字 和.h 文件里改為一致。

這是.h 文件的

修改 cmakelist.txt 中 add_library 的so文件名 和路徑
add_library( # Sets the name of the library.
# 設(shè)置so文件名稱.
serial_port
# Sets the library as a shared library.
SHARED
# 設(shè)置這個(gè)so文件為共享.
# Provides a relative path to your source file(s).
# 設(shè)置這個(gè)so文件為共享.
src/main/jni/com_silencefun_comtest_serialport_SerialPort.c)
// .......省略注釋部分
target_link_libraries( # Specifies the target library.
# 制定目標(biāo)庫(kù).
serial_port
# Links the target library to the log library
# included in the NDK.
${log-lib} )
注: “serial_port” 這個(gè) so庫(kù)名稱要和你要加載的要保持一致
在SerialPortJava類中,

在app的build.gradle中 defaultConfig中配置生成平臺(tái)so包
defaultConfig {
applicationId "com.silencefun.comtest"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
//生成多個(gè)版本的so文件
abiFilters 'arm64-v8a','armeabi-v7a','x86','x86_64'
}
}
}
然后 同步,build-make project 執(zhí)行完畢之后切換視圖
如圖已經(jīng)生成了so:

②直接使用cmake方式 不得不說(shuō)新支持方式的簡(jiǎn)單了好多。
創(chuàng)建完支持C的項(xiàng)目后,
在需要調(diào)用java 的Native 類中聲明方法,其實(shí)還是直接復(fù)制SerialPort把加載的so 文件名字改一下:

直接在自動(dòng)生成的Native-lib.c中完善實(shí)現(xiàn) (其他都不用改,方便快捷)
把 原來(lái).c文件中的實(shí)現(xiàn)方法 直接拷過(guò)去然后修改方法名:
注意 要對(duì)應(yīng)好路徑

出現(xiàn)問(wèn)題反思:采
用jni方式在Android 5.1的板子沒有問(wèn)題 換到4.4結(jié)果就不行 總是 LOGE("tcgetattr() failed");
后來(lái) 更新ndk 、cmake、 LLDB到最新,完全解決問(wèn)題。
github 地址歡迎 star?
https://github.com/silencefun/ComTest/tree/master/AndroidStuido_3.0_COMTEST
2.擴(kuò)展設(shè)計(jì):一(串口)對(duì)多(硬件通信)、多(串口)對(duì)多(硬件)的實(shí)現(xiàn)。
因?yàn)?有些智能終端硬件限制有些可能只開放一個(gè)通信口來(lái)接多個(gè)硬件模塊數(shù)據(jù),當(dāng)然前提是 掛在這一個(gè)通信口上的 硬件模塊 是在同一波特率,因?yàn)榇蜷_初始化的時(shí)候已經(jīng)設(shè)定好了波特率(嘗試動(dòng)態(tài)更改,不太理解底層實(shí)現(xiàn)過(guò)程代碼,屢敗屢試,最終放棄)。
相似的某些硬件模塊要采集的數(shù)據(jù)可能是多條數(shù)據(jù)(多個(gè)命令下返回多條數(shù)據(jù) 最后在封裝)所以設(shè)定一個(gè)【命令組】的概念:把硬件模塊對(duì)應(yīng)的命令放進(jìn)一個(gè)數(shù)組或者list中。
命令組,即 該硬件所需要的數(shù)據(jù)是需要連續(xù)發(fā)送一組 若干個(gè)命令,根據(jù)接收到的多條數(shù)據(jù)來(lái)解析--- 此處只是解析方式不同,可以根據(jù)每次傳遞標(biāo)志位來(lái)區(qū)分,待所有所需所有數(shù)值都有,即執(zhí)行一輪次之后 完全解析再更新數(shù)據(jù)。
同理多串口情況下就是多個(gè)通信串口,每一個(gè)口上邊都掛了N(N>=1)個(gè)硬件模塊(當(dāng)然這么殘暴的情形是有的:比如波特率不同必須多個(gè)串口)。
在之前一篇筆記中 Android 串口通信筆記2 的SerialHelper,即控制類---每個(gè)硬件串口對(duì)象的管理控制實(shí)例 中添加 相應(yīng)的 成員變量 來(lái)區(qū)分 目前 具體是哪一個(gè) 硬件模塊 的哪一個(gè)命令 響應(yīng)的 值。
所以對(duì)應(yīng)的 封裝 讀取到的 信息 也要添加上 當(dāng)前 對(duì)應(yīng)的 命令 和硬件 模塊標(biāo)志。
Combean 添加字段
private String scmd = "";
private String sflag = "";
對(duì)應(yīng)的 硬件模塊 數(shù)據(jù)結(jié)構(gòu)大概可以:
MeterInfo
private String name;//名稱
private String modenname;//型號(hào)
private String modertype;//類型
private List<String> cmdlist;//發(fā)送命令
private String cleandatacmd;//清除命令
private String decimal;//小數(shù)位置 (可能解析能用到)
private String portname;//port路徑 類似 /dev/ttys1
private String sFlag;//flag 可取 模塊name
SerialHelper 類要在原有基礎(chǔ)之上 添加 部分字段
....
private String scmd = "";
private String sflag = "";
private List<MeterInfo> meterlist = new ArrayList<>(); //一個(gè)串口肯能要和多個(gè) 硬件通信
.....
所以初始化串口控制類SerialHelper的實(shí)例時(shí)候,要先set List<MeterInfo> ,
在send 命令線程中,兩層循環(huán):
for (int i = 0; i < meterlist .size(); i++) {
List<String> cmdlist = meterlist .get(i).getGetdatacmdlist();
for (int j = 0; j < cmdlist.size(); j++) {
setHexLoopData(cmdlist.get(j));
setSflag(meterlist .get(i).getsFlag());
setScmd(cmdlist.get(j));
//設(shè)定兩次的時(shí)間間隔
setTimespace(meterlist .get(i));
send(getbLoopData());//發(fā)送命令
try {
Thread.sleep(iDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
對(duì)應(yīng)的在 發(fā)送線程發(fā)送后 發(fā)送線程sleep 的時(shí)候 ,讀的線程一直在跑,讀取到數(shù)據(jù) 封裝Combean 對(duì)象:
int size = mInputStream.read(buffer);
if (size > 0) {
ComBean ComRecData = new ComBean(sPort, buffer, size);
ComRecData.setScmd(scmd);
ComRecData.setSflag(sflag);
onDataReceived(ComRecData); //調(diào)用抽象方法傳遞
}
這樣在業(yè)務(wù)處理部分實(shí)現(xiàn) onDataReceived 方法時(shí)候就能判斷是哪個(gè)模塊哪個(gè)命令對(duì)應(yīng)的值。
關(guān)于CRC校驗(yàn):
/**
* 獲取 crc校驗(yàn)碼
*
* @param hextext 16進(jìn)制
* @return 低位高位順序
*/
public static String getCrc16(String hextext) {
byte[] arr_buff = SerialFunc.HexToByteArr(hextext);
int len = arr_buff.length;
// 預(yù)置 1 個(gè) 16 位的寄存器為十六進(jìn)制FFFF, 稱此寄存器為 CRC寄存器。
int crc = 0xFFFF;
int i, j;
for (i = 0; i < len; i++) {
// 把第一個(gè) 8 位二進(jìn)制數(shù)據(jù) 與 16 位的 CRC寄存器的低 8 位相異或, 把結(jié)果放于 CRC寄存器
crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
for (j = 0; j < 8; j++) {
// 把 CRC 寄存器的內(nèi)容右移一位( 朝低位)用 0 填補(bǔ)最高位, 并檢查右移后的移出位
if ((crc & 0x0001) > 0) {
// 如果移出位為 1, CRC寄存器與多項(xiàng)式A001進(jìn)行異或
crc = crc >> 1;
crc = crc ^ 0xA001;
} else
// 如果移出位為 0,再次右移一位
crc = crc >> 1;
}
}
String c = Integer.toHexString(crc);
if (c.length() == 4) {
c = c.substring(2, 4) + c.substring(0, 2);
} else if (c.length() == 3) {
c = "0" + c;
c = c.substring(2, 4) + c.substring(0, 2);
} else if (c.length() == 2) {
c = "0" + c.substring(1, 2) + "0" + c.substring(0, 1);
}
return c.toUpperCase();
}
Android 串口通信筆記2 調(diào)試工具分析 工具類實(shí)現(xiàn)分析、項(xiàng)目實(shí)現(xiàn)
Android 串口通信開發(fā)筆記3:CMake 方式實(shí)現(xiàn)和 多對(duì)多的實(shí)現(xiàn)邏輯
Android 串口開發(fā) 支持N-8-1(數(shù)據(jù)位停止位校驗(yàn)方式) 設(shè)定