
简介面向Android嵌入式开发、物联网设备联调及硬件调试场景的串口测试工具资源目标用户是希望快速实现Android串口收发与调试功能的开发人员。资源包以RAR压缩格式发布体积约2.62MB文件清单在下载页面中未完整展示但核心代码与说明均包含在内其中重点是可复用的CommManager串口管理类。当前已有765人学习下载适合用于学习串口通信原理或直接改造为项目基础工具。内容围绕Android串口通信完整实现路径展开先介绍权限配置与SerialPort库选型再说明波特率、数据位、停止位、校验位等参数设定覆盖打开/关闭串口、数据发送与监听接收线程、异常处理以及串口选择下拉框、波特率选择器、发送按钮和接收文本框等交互界面设计。掌握这些要点后结合CommManager类可快速搭建自己的Android串口测试工具减少从零编码的时间。 做嵌入式这么多年串口调试一直是最离不开的家伙事。以前我调试一块板子标配是笔记本加一根USB转串口线再开个桌面端的串口调试助手。但这类方案有个很现实的痛点现场环境往往没这么理想。上个月我在客户现场面前是一台老旧PLC和一个带Android系统的工控平板笔记本恰好没带。当时心里就想着如果手里这个Android平板能直接当串口调试工具用那就省大事了。回来之后我把Android串口调试这事好好捋了一遍从现成App到自研工具从UART底层原理到CH340、FTDI这类常见驱动芯片的适配再到RS485方向控制、串口DMA这些进阶话题都实际跑通了。这篇就把我踩过的坑、验证过的方案、能直接“抄作业”的代码一次说清楚。1. 为什么要在Android上做串口测试工具1.1 一个真实的调试场景先还原一下典型的现场。你面前是一台跑着Android系统的设备可能是工控平板、车载终端、POS机也可能是带屏幕的嵌入式网关。设备主板上有一路TTL电平的UART或者通过扩展口接出了一路RS232/RS485。你的任务很简单确认这路串口能不能正常收发、波特率和数据位对不对、和对面设备协议握手是否成功。以往的做法是拿笔记本但现在Android设备本身就是一个完整的计算平台有屏幕、有触摸、有USB Host能力通过OTG只要软件上打通了它就是一台小型的串口调试终端。而且对很多集成商来说在Android设备上做串口通信测试本身就是产品验收的一部分不是临时凑合而是正式的功能需求。1.2 工具选型现成App还是自研先给结论如果是临时用一下直接装一个现成的串口调试助手App就行如果是产品里要集成串口能力或者需要定制协议、自动测试场景就必须走自研的路。市面上现成的Android串口调试App有几个共性支持常见的波特率、支持HEX/ASCII切换、有定时发送功能。但它们的短板也很明显第一很多App对设备节点的处理很粗糙打开失败就提示没有权限不会告诉你具体是哪个节点、驱动是否加载第二不支持RS485方向控制的定制第三无法做自动化测试比如按某段时间间隔自动发送一帧Modbus RTU报文并校验返回。所以针对这些缺口自己写一个测试工具的性价比反而更高。选择在Android上开发而不是搬着一台Linux笔记本还有一层考虑很多嵌入式设备的远端维护没法接电脑但设备本身有Android系统把串口测试能力直接做成App后续运维和现场诊断可以少带一样设备。而且Android系统基于Linux内核串口访问的底层机制和Linux几乎一样理论上并不复杂。2. 串口访问的核心原理与权限模型2.1 Android串口库的工作机制Android本身在Java层没有暴露串口API所有串口操作都要走Linux内核的tty设备节点。目前业内最通用的方案是ceprce的开源android-serialport-api库它通过JNI封装了一个SerialPort类核心就是打开/dev/ttyS*、/dev/ttyMT*、/dev/ttyHSL*或/dev/ttyUSB*这类设备节点然后通过termios结构配置波特率、数据位、停止位和校验位。这个库的native层做的事情本质上就是Linux的open和fcntlopen(path, O_RDWR | O_NOCTTY | O_NONBLOCK)然后tcgetattr拿到原始配置再按需设置cfsetispeed和cfsetospeed最后用tcsetattr把参数写回。Java层拿到的就是一对FileInputStream和FileOutputStream读写字节数组即可。实际开发中我建议不要直接拿老版本代码因为它对Android 8.0以上的系统签名、SELinux上下文处理得不够好。可以在GitHub上找维护较活跃的分支或者自己把JNI部分编译一版。C代码看来看去就几百行重点理解这几个termios标志就够了CLOCAL | CREAD忽略调制解调器控制线启用接收器。如果不加串口可能收到不到数据。CS88位数据位PARENB启用校验CSTOPB2位停止位通常不需要。RAW模式一定要开启避免内核把回车换行、特殊字符做转换否则收到的二进制帧全乱套。2.2 权限串口工具的第一道坎这块是绝大多数人卡住的地方。Android上的串口设备节点通常在/dev/ttyS0、/dev/ttyMT0、/dev/ttyHSL0下普通App对这些节点的访问权限是受限的。如果应用没有root权限也没有系统签名打开节点的时候会直接抛Permission denied。解决路径有几条按推荐顺序排一下如果设备是你的开发机且有root直接在代码里把/dev/tty*的权限chmod成666一了百了。如果你是整机方案厂商在系统init脚本或ueventd.rc里给串口节点配上/dev/ttyS* 0666 system system保证所有App可读写。如果设备不能root也不能改系统但你的App可以拿到系统签名用系统签名打包也能绕过权限限制。最后的保底方案用su拉起一个root shell子进程把App要读写的节点映射成影子节点或者直接用root进程读写后经socket转发。这个方案稳定性一般不建议正式产品用。我在自研工具里做了一个很实用的功能启动时扫描/dev目录下列出所有tty*节点顺带检查当前App对这些节点的读权限和写权限用不同的颜色标识。这一个功能在排查权限问题时救了我好几次。2.3 物理链路USB转串口芯片与内核驱动Android设备如果要外接USB转串口模块常见芯片是CH340、CH341、CP2102和FTDI FT232。每个芯片在Linux内核里有不同驱动CH340/CH341对应ch341驱动CP2102对应cp210x驱动FTDI对应ftdi_sio驱动。问题来了——很多量产Android设备的系统镜像根本没有编译这些USB转串口驱动插上USB线后/dev下面不出现ttyUSB0。怎么确认你的设备支不支持插上模块后在adb shell里看dmesg | grep usb如果能看到“usb 1-1: ch341 converter now attached to ttyUSB0”之类的日志说明驱动OK如果只识别到USB设备但没有任何tty节点大概率是内核没编译对应驱动只能换系统镜像或换一款芯片。我平时常备一个CH340模块和一个CP2102模块这俩覆盖率最高。另外TTL电平转RS232和RS485需要额外有电平转换芯片比如MAX232、SP485这个和Android系统没有直接关系但如果是给老设备做调试电平不对会收一堆乱码甚至烧坏引脚接线前一定确认清楚。3. 从零搭建一个Android串口测试工具3.1 工程准备与依赖引入我用的开发环境是Android Studio建议直接创建一个空工程。SerialPort库部分按官方说法有两种引入方式一种是把JNI的so文件和Java类拷进工程另一种是通过Gradle依赖引入JitPack上的现成库。我更推荐第二种省去自己编译NDK的步骤。如果项目里已经用了KotlinJava版SerialPort类一样可以混编不冲突。一个典型的build.gradle依赖长这样dependencies { implementation com.github.ceprce:android-serialport-api:2.0 }如果你的gradle仓库没有配JitPack记得在settings.gradle的dependencyResolutionManagement里加maven { url https://jitpack.io }。这部分网上资料多不再展开。重点是引入后先在代码里试一下能不能加载so库static { System.loadLibrary(serial_port); }如果这里就崩了先检查CPU架构armeabi-v7a和arm64-v8a的so是否都打进APK了。很多设备是64位系统但App还是32位运行模式踩过这个坑的人不在少数。3.2 串口连接核心代码实现打开串口的核心代码不复杂但有几个细节必须处理好。我把常用的打开串口逻辑封装成一个SerialHelper类核心方法如下public SerialPort openSerialPort(File device, int baudrate, int flags) { if (!device.exists()) { log(设备节点不存在: device.getAbsolutePath()); return null; } try { SerialPort serialPort new SerialPort(device, baudrate, flags); mInputStream serialPort.getInputStream(); mOutputStream serialPort.getOutputStream(); return serialPort; } catch (Exception e) { log(打开串口失败: e.getMessage()); return null; } }注意几个细节flags参数传0就够用它对应的是打开文件时的O_RDWR等标志位暂时用不到其他值。打开成功后立刻检查输入输出流是否为null。有些设备节点只读不可写或者只写不可读提前发现问题比收发时报错更直观。串口打开后整个App期间只能有一个地方持有它关闭时要先停读写线程再关流最后调serialPort.close()。顺序反了有可能出现句柄泄漏导致下次无法重新打开。波特率这块单独说一下。SerialPort构造函数里的波特率参数native层直接映射到cfsetispeed和cfsetospeed。常见的9600、115200都没问题但如果是非标准波特率比如500000部分内核版本可能支持map。我实测过Android设备上500000能通但稳定性不如115200能用标准波特率尽量用标准。3.3 数据收发与UI交互设计一个串口测试工具UI上至少要覆盖这些功能串口节点选择、波特率/数据位/停止位/校验位配置、打开关闭按钮、接收区、发送区、HEX和ASCII切换、定时发送。我开发的这个工具还额外做了两个功能一个是发送后的时间戳打印方便分析协议时序另一个是统计接收字节数和错误计数排查硬件质量时很管用。接收数据用独立的读取线程这点务必不要在主线程里等数据。基本循环如下while (mIsReading mInputStream ! null) { int size mInputStream.read(buffer); if (size 0) { byte[] data new byte[size]; System.arraycopy(buffer, 0, data, 0, size); // 回调到UI线程刷新接收区 mHandler.post(() - onDataReceived(data)); } }有个容易被忽视的坑read在非阻塞模式下返回-1是正常情况不要把它当成错误直接退出线程否则你可能收到一帧数据后线程就悄悄挂了。SerialPort默认打开方式是O_NONBLOCK也就是说读不到数据时read会返回-1如果代码里对-1做了异常处理会导致后续所有数据都收不到。我一般把-1当成“没有数据继续循环”只有在size 0时才回调。发送端注意一点如果是定时发送建议用Handler.postDelayed而不是Thread.sleep前者在UI线程上更可控不至于因为系统休眠导致时间漂移太大。发送HEX时用户输入的“AA BB 01 03”先按空格拆分再逐字节转换。3.4 关键参数配置与实操验证实际串口测试时我一般先做环回测试把模块的TX和RX短接或者通过USB转串口把两端连起来。在波特率115200、8数据位、1停止位、无校验的参数下发送一串递增字节看接收区是否原样返回。如果返回内容完全一致说明链路是通的。如果环回都不通优先检查接线、波特率、打开权限这三个因素。注意串口调试里“收到乱码”大概率是波特率不一致或者两端电平不匹配完全不返回则优先怀疑TX/RX接反或者是模块供电不足。我这次在工控平板上测试时就是第一次把TX和RX接反了改了之后一切正常。一个实用参数经验表格供你参考场景波特率数据位停止位校验位备注标准环回测试11520081None最常用配置老式PLC/仪表960081None很多Modbus RTU设备默认参数Modbus RTU带校验960081Even部分仪表强制偶数校验高速采集设备92160081None注意CPU占用建议配合DMA4. 常见问题与排错速查4.1 打开串口失败Permission denied / No such device这类问题第一位原因永远是权限不是代码。有一次我在某款国产平板上测试/dev/ttyS3明明存在但打开就报Permission denied。后来在adb shell里执行ls -l /dev/ttyS3发现owner是root权限是crw-------App自然打不开。解决办法是临时执行chmod 666 /dev/ttyS3验证通过后再走系统方案。No such device则通常是节点路径不对。Android设备上串口节点不一定叫ttyS0高通平台叫ttyHSL0MTK平台叫ttyMT0展讯平台甚至可能是ttyS1。所以自研工具里做一个“扫描所有tty节点”的功能能大大减少这类问题。另外有些设备把串口放在了/dev/ttyGS0这类USB Gadget节点上别忽略。4.2 收不到数据、乱码、丢字节收不到数据先确认中断串口工具里有没有把“接收超时时间”设得很小有些工具默认5ms超时如果下层驱动在高负载下把数据分组提交应用层读出来的就是一帧被切碎的数据看起来像丢数据。我建议接收超时设置到20ms以上或者读取循环里用一次read尽量读完缓冲区所有字节。丢字节还有一个隐藏原因CPU负载过高导致读取线程被调度延迟。比如GNSS高精度定位、后台视频解码同时运行的时候串口的读取可能被抢占内核缓冲区溢出后丢数据。对策是提高读取线程优先级或者条件允许时在硬件层启用串口DMA。Linux串口DMA在某些平台上是默认开启的但在部分Android平板上需要通过内核配置打开会影响CPU占用率实测数据量不大的场景下普通轮询也够。乱码问题相对好查要么波特率不对要么电平不匹配要么是接线质量极差导致信号畸变。我遇到过一次很奇怪的现象每秒收到约20帧数据有将近一半是乱码排查半天发现是USB转串口模块的线太长又没有屏蔽工控现场电磁干扰严重换了一根带屏蔽的短line后问题消失。4.3 USB转串口设备无法识别如果插上USB转串口模块之后/dev下没有ttyUSB0最可能是内核没编译对应驱动。这里教大家一个通用的检查思路把USB设备VID/PID查出来然后看内核有没有对应的驱动。CH340的VID通常是1a86PID通常是7523在adb shell下执行cat /sys/kernel/debug/usb/devices或者lsusb有busybox的话能看到。如果识别到了设备但没驱动就只能在系统层解决这是App层面无能为力的。另外提一下有些Android设备的USB Host供电能力太弱接上部分功耗稍大的RS485转换模块后驱动加载不稳定表现为时好时坏。这时候可以外接一个带独立供电的USB Hub实测能解决不少顽固问题。4.4 常见问题速查表现象可能原因排查方向打开串口报Permission denied节点权限不足root下chmod / 系统签名 / ueventd.rc打开串口报No such device节点路径不对ls /dev/tty* 确认实际节点完全收不到数据TX/RX接反 / 波特率不对 / 驱动未加载环回测试 / 检查电平转换收到乱码波特率不一致 / 线路干扰 / 电平不匹配两端统一参数 / 换屏蔽线数据时断时续缓冲区溢出 / 线程调度被抢占提高线程优先级 / 加DMAUSB模块插上无反应内核缺驱动 / 供电不足查VID/PID / 换独立供电Hub关闭后再打开失败流关闭顺序错误先停线程再关流再close5. 进阶经验与项目扩展方向5.1 RS485通信的方向控制RS485和UART最大的不同在于它是半双工总线同一时刻要么发要么收方向控制通常由一个GPIO引脚控制收发芯片的DE/RE。很多调试工具只在UART层工作没有把RS485的方向控制做进去导致收发不同步。如果你的设备上有RS485收发器方向控制有两种实现路径一种是在底层直接操作GPIO发送前拉高DE、接收时拉低RE这个需要设备树或驱动支持另一种是用硬件自动方向控制的RS485芯片比如带auto-direction功能的模块软件不用管方向简单很多。我建议产品选型时就选带自动方向控制的省掉一堆麻烦。如果必须在软件层控制方向可以在串口发送前通过JNI写GPIO发送结束后再切换回接收。这里有个坑写GPIO调用本身有开销如果报文很短、发送频率很高方向切换的时间可能比发送时间还长导致线路时序不稳定。实测下来至少要在发送完成后留出500us以上的稳定时间再切到接收。5.2 大流量数据的接收优化如果你的串口测试工具要接收高速数据流比如每秒几兆比特App层面有几个优化点第一接收线程的byte[]缓冲区要足够大不要每次只读几十字节就回调第二尽量减少UI层的刷新次数可以用定时器和脏标记把数据分批刷新而不是每来一个包就刷新一次屏幕第三接收数据尽快从输入流读出让内核缓冲区保持空闲。实测在一个921600波特率、每帧约200字节的实时数据流场景下如果不做UI合并刷新App的接收线程CPU占用能跑到30%以上而且界面明显卡顿。合并到每100ms刷新一次后CPU占用直接降到5%以下数据也不丢。这个经验对做工业数据采集的同学非常有用。另外如果要和Modbus RTU设备通信串口工具最好内置简单的CRC16校验功能。你不用把完整的协议栈写进去但至少有“计算CRC并附加到发送报文尾部”的功能测试时能省很多手工算CRC的工夫。5.3 从测试工具到自动化协议测试测试工具做到后面完全可以升级成一套简单的自动化测试框架。比如把一系列测试指令写在配置文件里App按顺序发送并校验返回失败时截图、记录日志。这在产线测试场景里特别实用——Android设备作为测试终端配合一套脚本化的测试指令可以替代掉原来笨重的PC测试工位。我自己的做法是在串口工具里支持了简单的脚本模式每一行是一条指令支持注释和延时遇到错误码自动停止并导出日志。这个功能不到一天就写完了但实际节省的时间是长久的尤其是在重复验证某一帧报文的时候。个人建议你在做这类工具时一定把日志系统做扎实。串口通信的排查很多时候靠的不是猜而是回看收发记录。发出去的每一帧、收到的每一帧、什么时间、间隔多少全部落盘查找问题时一小时顶过去半天。这次在客户现场能快速定位乱码问题靠的就是日志里完整记录了每次接收的时间戳一对比就排除了软件层的时序嫌疑。Android串口调试这条路方法论并不复杂真正的坑都在底层细节里。把权限模型理清楚把读写线程的稳定性做好把日志系统搭扎实一套好用的Android串口测试工具就成型了。顺手再分享一个最后的小技巧做串口工具开发时电脑上尽量同时开一个桌面端串口助手做对照两边同时发同样数据哪边先出问题、出的什么问题一眼就能对比出来排查效率高非常多。本文还有配套的精品资源点击获取