简介:这是一个面向Android开发者的串口通信简化Demo,基于android-serialport-api精简改编,解决原版Demo常见不可用的问题,仅保留一个Activity,适合需要快速集成串口收发的项目或个人学习。资源包共43个文件、约78KB,包含5个Java源文件、1个C底层文件、5份XML配置、6张PNG图标、预编译SO库及可直接安装的APK,另有class、dex、project等工程中间文件与MK构建脚本,从源码到安装包形成完整闭环,目录结构清晰,便于导入和二次开发。目前已有1551人学习下载。借助该Demo可以直观理解Android串口通讯中Java层与JNI层的调用流程,开箱即用,省去NDK环境搭建和交叉编译的繁琐步骤;同时可参照原生C层与Activity代码修改串口路径、波特率、数据位、停止位与校验位等核心参数,对串口移植、硬件调试以及上层通讯功能开发都有直接参考价值。对于刚接触串口编程的开发者,这一份极简工程也能帮助看清串口打开、读写与关闭的完整实现链路,减少踩坑。 Android串口通信这个需求,最近找我咨询的人特别多。开发智能硬件、对接单片机、调试工控设备,都离不开这个基础能力。网上相关的帖子零散得很,很多文章看了也只是反复在讲如何引入一个第三方库,真上手做项目时遇到的问题一个都没说清楚。今天把这个demo从选型到落地的完整过程拆开来讲,包括哪些坑必须绕开、哪些参数必须留意。
先一句话说清楚这东西能干什么:Android设备通过串口协议和外部设备通信,比如STM32、PLC、传感器模块、RS485总线设备,或者带有UART接口的各种控制板。它解决的是Android系统和硬件设备之间建立物理链路、完成双向数据交互的问题。适合刚接触Android硬件开发的人,也适合那些已经被各种串口库绕晕了的老手。
1. 项目要解决什么问题,为什么这个通道不能省
1.1 Android设备对接硬件的三条路,串口的不可替代性
Android和外设通信,常见的物理通道有蓝牙、WiFi、串口三种。
蓝牙方便但协议栈复杂,配对流程、连接状态管理、传输稳定性都要自己兜底。WiFi则要搭网络环境,设备端得有TCP/IP协议栈的接入能力,成本直接拉高。串口的优势在于协议足够底层、物理连接简单、延迟可控,尤其对接工业设备和单片机的场景里,很多控制器压根没有网络协议栈,UART就是标配。
这也就解释了为什么STM32、PLC、各类传感器模块的调试接口大多数都是串口。对Android开发来说,串口虽然“原始”,却是一条绕不开的硬通道。
1.2 Android系统对串口的天然隔离,权限是第一步
Android基于Linux内核,串口设备在系统中对应/dev/ttyS*、/dev/ttyUSB*这种节点文件。理论上有读写权限就能打开,但真实情况是普通应用根本拿不到这些节点的访问权限。
没有root的设备,/dev/ttyS0这类节点的owner是root或系统用户,应用直接打开会抛Permission denied。业界常用方案是在初始化脚本里给串口节点设置666权限,例如修改/dev/ttyS0的权限为chmod 666 /dev/ttyS0,或者在系统启动脚本里加入权限修改逻辑。还有一个常规做法是让应用运行在具备系统权限的进程中。
开发demo时,在root过的设备上或定制系统中通过su执行chmod命令即可先跑通流程,后续上生产再考虑更稳妥的权限策略。
2. 串口通信的核心细节,参数不对神仙也调不通
2.1 波特率、数据位、停止位、校验位,一个都不能错
串口通信中,两端设备的四个参数必须完全一致,少了任何一步匹配都收不到正确数据。波特率是每秒钟传输的比特数,常见的有9600、115200。数据位通常选8,停止位选1。校验位常见为NONE。
这两个参数直接影响通信成败。等调试时发现自己收了一堆乱码,第一反应一定是查波特率两端是否一致。115200能跑到,但物理链路质量差时选9600更稳。工业现场往往有强电干扰,RS485总线场景中,更低的波特率意味着抗干扰能力更强,这个选择决定了通信质量的上限。
2.2 TTL、RS232、RS485三种电平,别接错接口烧了板子
Android的调试板和USB转串口模块引出的一般是TTL电平,3.3V或5V。RS232是正负电压逻辑,RS485则使用差分信号。三者物理电平不兼容,接错就是烧芯片。
做demo时最常用的USB转TTL模块是CH340和FTDI,操作实践上要先确认模块的工作电压和主板的串口电平一致。如果目标设备是RS485总线,需要加一个RS485转TTL的模块,不能直接把USB转TTL接到RS485总线上。
这里补充一句选型时的经验:CH340驱动安装简单,Windows和Linux下都方便,下载时注意去正规渠道获取,防止网上下到捆绑安装包的驱动。FTDI的驱动相对更全,但价格贵一些,兼容性也更好。CP2102也是常见选择,一般开发调试用CH340就足够了。
2.3 UART只是物理通道,协议才是通信的灵魂
串口只管把字节流从一个点搬到另一个点,至于这些字节怎么解析,由两端的协议约定。这就像快递只负责把包裹运到门口,包裹里装着什么东西、怎么开箱,是寄件人和收件人之间的约定。
写demo时,需要自定义一个简单的通信协议,比如一帧数据包含帧头、长度、数据、校验值。帧头常用0xAA或0x55,长度指明后面的数据字节数,校验用来检测数据在传输过程中是否损坏。基于这一层约定,才能判断收上来的数据是不是完整的一帧。
3. 完整实现过程,从零到能收发数据
3.1 硬件准备,用最小成本搭起调试环境
搭一套串口调试环境,需要的硬件无非这么几样:一台Android设备(最好是支持OTG且能root的,或者直接用开发板),一根OTG线,一块USB转TTL模块,和一个串口调试工具。总成本不过二三十块钱,就能把开发环境搭建起来。
调试目标如果手头没有STM32,可以先用USB转TTL模块的TX和RX短接,自己发给自己的数据,拿来验证串口收发逻辑是否正常。这个“自发自收”的办法非常实用,能快速排除硬件层面的问题,特别适合阶段性的功能验证。
3.2 Demo代码实现,核心逻辑其实就三步
先看打开串口的动作。Android中打开串口本质上就是打开一个文件描述符,再通过ioctl函数配置串口参数,之后就能读写这个文件描述符。核心调用逻辑简洁得很:
// 伪代码示意,实际实现建议直接基于JNI封装 val fd = FileInputStream(file).fd val serialPort = SerialPort(fd) // 封装类内部完成open和configureconfigure阶段需要设置波特率、数据位、停止位、校验位,背后用的是Linux的termios结构体。这些参数配置完成后,读写就是普通的流操作。
读写要放在独立线程里处理。串口通信数据是持续流入的,如果放到主线程,界面会被消息风暴卡死,体验直接归零。标准做法是开一个收数据线程,用while循环阻塞读,读到的数据回调到主线程更新UI。
关闭串口同样要严谨,不需要时把文件描述符释放掉,否则下次打开会失败。有一段时间我在demo里不停地打开关闭串口,出现过资源耗尽的情况,正是因为这个细节没处理好。
3.3 核心技术库选型,用成熟方案还是自己封装
串口库的常见方案有:google官方仓库里的android-serialport-api和民间活跃维护的licheedev/Android-SerialPort,以及基于Modbus协议的专用库。
android-serialport-api已经比较老,但胜在稳定简单,适合想深入了解原理的人去读源码。licheedev版本的封装更舒服,带自动重连、数据回调封装等,上手效率高。选型依据很简单:只是做工具类app,选封装好的直接跑;如果是要在嵌入式板卡上定制系统,最好自己基于JNI封装,能把控制权牢牢握在手里。
4. 与设备对接时的实战配置,从STM32到RS485
4.1 和STM32对接的调参套路
STM32串口调试时,建议用串口调试助手先把板子的收发调通,确认板子的输出数据格式,再让Android端去适配。否则两边同时排查,定位问题的时间直接翻倍。
这部分的关键是明确日志输出格式。MCU发上来的数据一般是Hex格式的字节数组,比如01 03 02 00 7F这种,调试助手里显示Hex、ASCII还是UTF-8字符串,直接影响报文判断。很多人在板子上发的是Hex,Android端却用字符串解析,结果收到一堆乱码,这个对齐工作值得做在前面。
4.2 RS485总线场景,Android设备的接入方式
RS485总线连接多个设备,Android设备要接入,得先过一个485转TTL模块。模块的A、B线并联到485总线上,另一端输出TTL再接USB转TTL或直接进开发板的UART引脚。
接好后还有件事容易忽略:RS485是半双工通信,数据发送和接收共用一对线,发的时候不能收,收的时候不能发。软件上需要控制收发切换。多数现成的485模块有自动收发切换电路,如果用的模块需要手动切换,Android端代码里得在发送完成后做个延时再切回接收模式,否则首个字节丢失是必然的。
4.3 Modbus RTU协议对接,最常见的高频场景
做工业项目绕不开Modbus RTU。它定义在RS485物理层之上,格式固定:从站地址、功能码、数据、CRC校验。Android端需要实现CRC16校验函数,根据功能码拼装数据帧和解析响应帧。
这里有个容易踩的坑:很多做Android的人对CRC不熟,建议直接搜成熟的CRC16-Modbus实现,不要自己造轮子。Modbus RTU的字节间超时要求是“一帧数据内部各字节间隔不超过1.5个字符时间”,这意味着接收端要及时处理收到的字节,不能等到攒够了才去读,否则帧已经断开了。
5. 常见问题与排查技巧实录
5.1 打开串口报Permission denied,怎么办
这一般就是权限问题。属于定制系统的,检查启动脚本中串口节点权限有没有设置;使用root设备的,用su -c chmod 666 /dev/ttyS0临时改权限再测试。如果是通过USB转串口接入,先看/dev/ttyUSB*节点有没有生成,没生成说明驱动没加载,另说。
权限问题排查完还有个冷门注意点:有些设备上有多个串口节点,/dev/ttyS0、/dev/ttyS1不代表物理位置顺序,对应的可能是不同的UART引脚或者蓝牙模块,盲目改权限没意义。建议去查板子的原理图或者串口节点对应的设备树配置,确定准确的节点名称。
5.2 数据收发乱码的排查思路
收发乱码,优先级最高的方向是波特率是否匹配。把两端波特率都设成一样的,重新测。其次看数据位、停止位、校验位。三者都一致仍乱码,再检查USB转TTL模块的TX/RX是否接反。
有一种情况容易忽略:Android端用的是字符串编码解析字节流,MCU发的却是二进制帧。这种场景不是乱码,而是解析方式不对,改成字节流方式逐帧解析即可。
5.3 串口数据丢失,首字节或者中间字节奇偶校验失败
丢首字常见于RS485半双工切换时序不对,发送完成立即切换接收,而总线上还在返回最后一个字节的尾巴。解决方法是发送后加一个延时,延时长度根据波特率计算,波特率越低延时越长。以9600波特率为例,一字节的时间大约是1.04ms,发送完后等3-5字节的时间再切接收,通常就能避开首字丢失。
中间丢数据则是接收线程读取不及时导致的。建议把串口读缓冲区开得大一些,并用单独线程持续读取,避免等到有数据了才去读。踩过一次掉数据的坑后,我现在写串口程序,第一件事永远是确认读线程循环是否可靠。
5.4 USB转串口模块不识别,驱动排查
手机OTG连接USB转TTL模块,没反应,优先级从模块本身开始排查。先用电脑测模块好坏,确认模块是好的,再排查手机是否支持OTG,是否已开启OTG功能。个别手机需要手动开启OTG。
驱动层面,CH340在Android上通常有内核驱动,如果手机内核没编译进去,就得换一个内核包含此驱动的设备或板卡,这个不是App层面能绕过的。
5.5 串口无法关闭,拔线后重连失败
这个问题的根源多半是文件描述符未释放。空闲时要及时关闭输入输出流和串口对象,尤其在做反复插拔测试时,不主动关闭会导致节点一直被占用,重连必然失败。
再补充一点小经验:打开串口前检查文件是否存在,抛异常时把异常信息记录到日志里,能省下很多不必要的排查时间。
6. 动手实践中的几条心得
6.1 调试串口,日志先行
做串口开发,日志设计会直接影响排障效率。建议加一个调试开关,把串口每次收发的原始字节都打到日志里。之前做过一个设备,通信偶发不稳定,靠日志连续打了两天,最后定位到是某一帧数据偶尔多了一个字节导致的解析错位,如果当时没有日志,这个问题排查难度会大很多。
日志格式也有讲究,Hex和ASCII都要有,平时看Hex,联调时看ASCII,两个视图并存能省很多事。
6.2 先做通断测试,再做协议解析
新手习惯直接写完整代码,然后一跑——“怎么数据不对”,然后陷入迷茫。老手的顺序是先做自发自收,验证串口链路通;再通过串口调试助手和另一头设备通信;最后再到Android代码里跑完整通信。一层一层排查,问题始终能在最小的范围内被锁定。
6.3 基于这个demo,后面还能怎么改
串口通信demo一旦跑通,往上叠加的方向很多——后续可以做一个自动识别波特率的功能,通过尝试常用波特率读取特征字节来猜测设备参数;可以对接数据库,把设备上报的数据落库展示趋势图;也可以封装成Service常驻后台,实现在App退到后台依然持续接收串口数据。这些方向都能让这个小demo变成一个真正可复用的工具。
开发串口项目,本质上是在和Linux的设备模型打交道。硬件的接线细节、驱动的存在与否、内核的权限配置、协议的字节定义,每一环都可能是问题点,而且调试手段有限,只能靠经验和日志逐步逼近。但正因为如此,把链路梳理清楚的方法论就变得特别值钱,这也是我每次写串口代码都保持敬畏心的原因。
本文还有配套的精品资源,点击获取