因为公司的安卓项目一直无法达到单片机与串口通信的效率, 因此本人自定义了一个新的jni库,这篇文章用于记录过去一个月的努力, 从最开始的android_serial_port_api到自定义jni串口读写库的过程.这个过程也使我进一步熟悉了c++的应用.项目地址(https://github.com/flykule/MserialPort)欢迎大家使用, 注意项目demo是配合特定机器的, 需要自己修改demo进行测试!
AndoridSerialPortApi的使用与缺陷
首先来总结一下这个上古项目(https://code.google.com/archive/p/android-serialport-api/)的优点和缺点:
优点: 使用起来简单,快速集成
缺点: 不能自定义串口参数,如停止位,奇偶校验等这个其实还能接受,因为实际需要调整场景不多
缺点二: 速度不够理想, 实践与串口屏连接需要以30ms的间隔发一条命令, 不能接受
缺点三: 与扫码头适配不理想, 当二维码较长时会分成好几次返回(这其实是逻辑问题)
Termios参数设置
首先查看linux wiki:https://en.wikibooks.org/wiki/Serial_Programming/termios,得知这东西有60多个参数... 没关系, 先调重要部分即可,参考项目(https://github.com/gbmhunter/CppLinuxSerial),几个重要参数:
波特率, 必须
阻塞读写与最小读取字符数(重要!)
关闭 echo与设置raw
另外原项目的open函数只增加了读写标记,根据wiki可以添加了几个flag,最后一个flag根据wiki会被忽略,O_NONBLOCK与O_NDELAY是一样的,而且最好通过vtime来设置非阻塞:
库框架设计:
一开头考虑的是单线程读写, 但是这种模式与旧的库没有本质区别, 而且在java层需要大量管理代码,本着简洁可靠的原则, 决定在native层使用多线程,一个串口对应一个读线程, 一个写线程, 一个loop线程,各线程间通过condition_variable和std::promise进行消息通信下面来说明各个线程 的作用与流程:
读线程 -> 顾名思义, 负责读取串口数据, 回调java方法
写线程 -> 平时等待锁, 当有数据进入queue时往串口写数据
可能有些人有疑问, 为什么会多一个loop线程呢?这个线程 的主要角色, 就是一个poll循环,利用linux的poll命令循环判断串口是否有数据,如果有数据那么就唤醒读线程进行读数据
但是这里有一个问题,如果数据还没写完你就读取, 就会造成需要读多次才能读完的景象,因此这里通过ioctl进一步做处理和保险:
那么如何中止这些线程呢? 很简单, 通过std::promise进行
决定了与单个串口通信的方式后, 就是需要一个管理类来管理打开的串口, 这块比较简单, 有兴趣的参考源码即可, 核心功能还是上面的几个线程.
JNI接口层与使用
打开串口:
直接在主线程发送与接受即可, 不需要增加额外的线程管理逻辑.需要注意的是在接受数据处理的地方不能直接发新的指令,最好是有单独的线程池专门用于发送指令, 拿rxjava举例:
总结
大体就是这样,目前已经集成到生产环境,效率可以说有较大提高, 有问题和建议的话欢迎大家给我留言!