上一篇:Android 串口通信笔记2 调试工具分析 工具类实现分析、项目实现
Android串口开发 延伸和扩展,
1.使用JNI Cmake 自己编译串口通信 的so库:Android Studio 3.0 实现方式。
2.CRC校验 以及扩展设计:
a.一(串口)对多(硬件通信);
b.多(串口)对多(硬件)的实现。
1.以串口调试工具为例,使用其原本的源代码使用JNI Cmake Android Studio 3.0 实现方式。
creat project勾选 include C++ support 没有下载ndk 的要下载。
①.延续使用jni 的方式
image.png
把相关的 been 和实现方法 都复制过来如图。
创建.h 文件 注:一定要现进入到app/main/java/ 目录下
然后 javah -classpath -jni +完整路径到类名
image.png在main目录下创建jni 文件夹,把生成的.h 文件复制进去 ,新建同名的.c文件,把实现代码拷进去--注意需要修改 open 和close方法的名字 和.h 文件里改为一致。
.c.png这是.h 文件的
image.png修改 cmakelist.txt 中 add_library 的so文件名 和路径
add_library( # Sets the name of the library.
# 设置so文件名称.
serial_port
# Sets the library as a shared library.
SHARED
# 设置这个so文件为共享.
# Provides a relative path to your source file(s).
# 设置这个so文件为共享.
src/main/jni/com_silencefun_comtest_serialport_SerialPort.c)
// .......省略注释部分
target_link_libraries( # Specifies the target library.
# 制定目标库.
serial_port
# Links the target library to the log library
# included in the NDK.
${log-lib} )
注: “serial_port” 这个 so库名称要和你要加载的要保持一致
在SerialPortJava类中,
在app的build.gradle中 defaultConfig中配置生成平台so包
defaultConfig {
applicationId "com.silencefun.comtest"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
//生成多个版本的so文件
abiFilters 'arm64-v8a','armeabi-v7a','x86','x86_64'
}
}
}
然后 同步,build-make project 执行完毕之后切换视图
如图已经生成了so:
image.png②直接使用cmake方式 不得不说新支持方式的简单了好多。
创建完支持C的项目后,
在需要调用java 的Native 类中声明方法,其实还是直接复制SerialPort把加载的so 文件名字改一下:
image.png
直接在自动生成的Native-lib.c中完善实现 (其他都不用改,方便快捷)
把 原来.c文件中的实现方法 直接拷过去然后修改方法名:
注意 要对应好路径
image.png
出现问题反思:采
用jni方式在Android 5.1的板子没有问题 换到4.4结果就不行 总是 LOGE("tcgetattr() failed");
后来 更新ndk 、cmake、 LLDB到最新,完全解决问题。
github 地址欢迎 star❤
https://github.com/silencefun/ComTest/tree/master/AndroidStuido_3.0_COMTEST
2.扩展设计:一(串口)对多(硬件通信)、多(串口)对多(硬件)的实现。
因为 有些智能终端硬件限制有些可能只开放一个通信口来接多个硬件模块数据,当然前提是 挂在这一个通信口上的 硬件模块 是在同一波特率,因为打开初始化的时候已经设定好了波特率(尝试动态更改,不太理解底层实现过程代码,屡败屡试,最终放弃)。
相似的某些硬件模块要采集的数据可能是多条数据(多个命令下返回多条数据 最后在封装)所以设定一个【命令组】的概念:把硬件模块对应的命令放进一个数组或者list中。
命令组,即 该硬件所需要的数据是需要连续发送一组 若干个命令,根据接收到的多条数据来解析--- 此处只是解析方式不同,可以根据每次传递标志位来区分,待所有所需所有数值都有,即执行一轮次之后 完全解析再更新数据。
同理多串口情况下就是多个通信串口,每一个口上边都挂了N(N>=1)个硬件模块(当然这么残暴的情形是有的:比如波特率不同必须多个串口)。
在之前一篇笔记中 Android 串口通信笔记2 的SerialHelper,即控制类---每个硬件串口对象的管理控制实例 中添加 相应的 成员变量 来区分 目前 具体是哪一个 硬件模块 的哪一个命令 响应的 值。
所以对应的 封装 读取到的 信息 也要添加上 当前 对应的 命令 和硬件 模块标志。
Combean 添加字段
private String scmd = "";
private String sflag = "";
对应的 硬件模块 数据结构大概可以:
MeterInfo
private String name;//名称
private String modenname;//型号
private String modertype;//类型
private List<String> cmdlist;//发送命令
private String cleandatacmd;//清除命令
private String decimal;//小数位置 (可能解析能用到)
private String portname;//port路径 类似 /dev/ttys1
private String sFlag;//flag 可取 模块name
SerialHelper 类要在原有基础之上 添加 部分字段
....
private String scmd = "";
private String sflag = "";
private List<MeterInfo> meterlist = new ArrayList<>(); //一个串口肯能要和多个 硬件通信
.....
所以初始化串口控制类SerialHelper的实例时候,要先set List<MeterInfo> ,
在send 命令线程中,两层循环:
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));
//设定两次的时间间隔
setTimespace(meterlist .get(i));
send(getbLoopData());//发送命令
try {
Thread.sleep(iDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
对应的在 发送线程发送后 发送线程sleep 的时候 ,读的线程一直在跑,读取到数据 封装Combean 对象:
int size = mInputStream.read(buffer);
if (size > 0) {
ComBean ComRecData = new ComBean(sPort, buffer, size);
ComRecData.setScmd(scmd);
ComRecData.setSflag(sflag);
onDataReceived(ComRecData); //调用抽象方法传递
}
这样在业务处理部分实现 onDataReceived 方法时候就能判断是哪个模块哪个命令对应的值。
关于CRC校验:
/**
* 获取 crc校验码
*
* @param hextext 16进制
* @return 低位高位顺序
*/
public static String getCrc16(String hextext) {
byte[] arr_buff = SerialFunc.HexToByteArr(hextext);
int len = arr_buff.length;
// 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
int crc = 0xFFFF;
int i, j;
for (i = 0; i < len; i++) {
// 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
for (j = 0; j < 8; j++) {
// 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
if ((crc & 0x0001) > 0) {
// 如果移出位为 1, CRC寄存器与多项式A001进行异或
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();
}
网友评论