ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Android车载串口通信实战:从UART到RS485的完整链路解析

Android车载串口通信实战:从UART到RS485的完整链路解析 1. 车载项目里的串口选型思路与整体架构1.1 为什么车载场景绕不开串口车载项目里串口通信承担了大量不起眼但关键的工作。车机要和OBD诊断仪交换车辆状态数据要和多功能按键面板做事件交互要跟外接的雷达、温控、姿态传感器做数据采集还要和底层MCU最常见的搭档是STM32保持双向通信。这些设备里几乎每一个都带UART口因为UART是老牌传输协议几乎所有MCU原生支持不像USB需要协议栈也不像CAN需要专用控制器。我见过不少刚转车载开发的安卓工程师第一反应是“用USB不就行了”。真去对接的时候才发现车载外设很多还是老一代设计物理接口就是DB9或者端子排协议就是裸的串口报文根本没有现成的USB协议实现。更麻烦的是车规级的Android平台Qualcomm、NXP、Rockchip这些方案本身对外设端口的暴露往往就是把UART映射成tty节点想绕都绕不开。所以搞懂串口通信是车载安卓开发的基本功。这篇笔记覆盖的是一条完整链路硬件上怎么选UART、RS232、RS485电平怎么转换信号怎么保护软件上Android怎么配置串口节点、怎么用NDK打开和读写、怎么设计可靠的数据帧最后把乱码、烧写失败、权限拒绝、丢包这类高频问题一并讲清楚。适合正在做Android车载、智能座舱、车联网外设对接的开发者也适合刚入行嵌入式的同学快速建立一条串口知识主线。1.2 先选物理层TTL UART、RS232、RS485怎么定项目启动时我一般先问硬件工程师三个问题通信距离多长总线挂几个设备现场干扰大不大这三个问题的答案直接决定选UART、RS232还是RS485。不是越贵的越好而是匹配场景的才好。TTL UART是板级通信的最佳选择设备间距离不超过一米直接拿MCU的TXD、RXD两根线对接电平是3.3V或5V的逻辑电平不需要额外芯片成本可以忽略。RS232适合十几米内的点对点通信比如车机和诊断仪之间的短距对接它把TTL电平转换成±3V到±15V的电压信号抗干扰能力比TTL强不少但只有两个设备之间能通。RS485则是真正的总线级方案差分信号能让它跑到一千米以上而且支持一主多从一条总线上挂几十个设备都没问题抗共模干扰能力很强工业现场和车载多设备场景基本都选它。我在一个商用车项目里车机需要同时采集八个胎压传感器数据传感器分布在车架两侧走线长度接近十米还挨着电机和液压管路。这种工况RS232和TTL UART想都别想只能在RS485总线上挂节点再给每端加上终端匹配电阻。如果当初偷懒全走TTL电平光电磁干扰就能让数据错得没法看。项目TTL UARTRS232RS485信号方式单端电平0VVCC单端电平±3V±15V差分信号A-B电压差典型距离1米以内15米左右1200米左右节点数量1对11对1一主多从标准32节点工作模式全双工全双工半双工为主抗干扰能力弱中等强典型应用板级MCU调试、短距直连诊断仪、短距串口设备传感器组网、工业总线选型逻辑其实不复杂板内通信用TTL UART车外短距设备用RS232多设备、远距离、干扰大的环境直接上RS485。1.3 整车通信链路长什么样确定物理层之后第二步是梳理整条链路。车载Android串口通信从上到下是这样的结构最上层是Android应用程序Java/Kotlin负责协议解析、UI展示和数据业务中间层是JNI串口库NDK编译的so负责调用Linux系统调用打开串口设备、设置参数再往下是Linux内核里的tty驱动对应硬件上的UART控制器最底层是电平转换芯片RS232收发器或RS485收发器它把UART控制器的TTL电平转换成对应物理层标准最后才接到外部设备。很多人写代码时只盯着应用层一旦通信异常就疯狂怀疑Android代码其实问题往往出在驱动配置、电平转换芯片或者外部设备本身的协议上。我的排查顺序永远是先用示波器或者逻辑分析仪在硬件侧看波形确认电平转换后的信号对不对再往上排查Android层。链路里每一层都可能出错跳层排查是串口开发的大忌。2. UART、RS232、RS485核心技术点逐一拆解2.1 UART数据帧、波特率以及8N1到底是什么UART是异步通信协议本质是“按约定好的节奏一比特一比特地传”。它没有时钟线双方必须预先约定波特率每秒传输的符号数发送端按这个节奏把数据逐个发出去接收端靠每个数据帧的起始位来对齐节奏。因为都是异步的哪怕双方时钟有微小偏差只要在一个字符帧内累积误差不太大就能正确采样。一帧UART数据长这样空闲状态下总线保持高电平发送开始时先来一个低电平起始位然后在LSB优先的顺序下依次送数据位数据位数通常是8位但也可以配成5、6、7位数据位之后如果启用了校验就有一个校验位最后是高电平的停止位停止位可以是1位、1.5位或2位。车载项目里最常见的配置是8个数据位、无校验、1个停止位简写为8N1这也是我默认推荐的配置232和485标准里大量现有设备默认就是8N1。波特率的选择直接影响通信稳定性。很多人以为波特率越高越好真不是。波特率翻倍每个符号的宽度减半抗干扰能力跟着下降同样一根线材9600能稳定跑30米切到115200可能十米就开始随机出错。车载项目里我的习惯是短距离设备用115200总线级和多设备长线场景降到9600或者19200宁可多一点传输时间也不要天天处理丢包重传。2.2 RS232电平转换电路与典型接线RS232不是一种协议是物理层电气标准。它把TTL的“高电平3.3V/5V表示1、低电平表示0”翻转成“负电压表示1、正电压表示0”典型范围是±3V到±15V。因为电平逻辑是反的而且电压范围不同TTL UART不能直接接RS232接口必须经过电平转换芯片。常见的转换芯片是MAX232和MAX3232前者需要5V供电后者支持3.3V供电车载板子上的MCU基本都用3.3V所以MAX3232出场率更高。这类芯片的典型外围电路很简单电源引脚接上供电配四个0.1uF到1uF的电荷泵电容然后把MCU的TXD接到芯片的T1INRXD接到R1OUT芯片的T1OUT和R1IN再引出去接DB9头的第2脚RX和第3脚TXDB9的第5脚接地。很多人搜“RS232串口通信原理图”时想找的就是这套经典应用。我踩过一个坑某次调试时串口能通但数据时好时坏用万用表一量芯片T1OUT对地电压只有±4V明显低于正常的±6V以上最后发现是电荷泵电容虚焊。直接换掉电容重新上电电压恢复正常。这种问题没法靠软件查出来只能靠示波器或万用表一步步量。所以做串口硬件对接时示波器或者至少一个万用表是必须的不要全指望调试助手。2.3 RS485自动收发原理、终端电阻与组网方式RS485用差分信号传输数据在A、B两根线上分别拉出相反的电平接收端比较两根线的电压差来还原数据而不是像RS232那样测量单根线对地电压。这种差分结构让RS485对共模干扰有天然免疫力也是它能跑远距离、抗干扰强的根本原因。2线制RS485是半双工工作同一时刻要么发要么收不能同时进行。标准收发器比如MAX485有DE和RE两个方向控制引脚DE拉高时进入发送模式RE拉低时进入接收模式通过软件切换方向。但Android应用层不是实时系统线程调度时机不确定手动切换方向容易在一个字符还没发完时就被切到接收导致帧头被截断。所以我建议直接用支持自动收发切换的芯片比如MAX13487芯片内部根据TXD电平自动控制方向外部只需要接TXD、RXD两根信号线省掉了软件控制逻辑也避免方向切换时序带来的数据丢失。RS485组网有三个硬性要求第一总线两端必须各接一个120Ω终端匹配电阻用来吸收反射信号不接电阻的话长距离传输时信号会在末端反弹形成波形畸变第二布线要走手拉手的菊花链不能星形分支分支越短越好第三A/B双绞线必须用屏蔽双绞线屏蔽层单端接地这能显著减少电磁干扰耦合。另外主从设备之间最好共地虽然RS485是差分信号不共地也能通信但两个设备的通信地出现较大压差时长期运行容易损坏收发器。2.4 调试图上的USB转串口芯片多花几块钱能少踩很多坑开发阶段免不了用PC端的串口调试助手这时候USB转串口模块就是必备工具。热搜里ch340、ft232r、ft231x这些关键词高频出现说明大家在驱动和芯片选型上都踩过不少坑。市面主流芯片我基本都用过简单总结一下CH340价格最便宜几块钱就能买到但驱动在某些系统版本下不够稳Windows下偶尔出现识别成未知设备或者打开串口后通信一段时间掉线的问题。CP2102兼容性不错很多官方开发板也用它。FT232R和FT231X是FTDI家的老牌产品驱动成熟稳定性和兼容性都是第一梯队就是价格贵一些。还有PL2303老版本芯片在Win10/11下驱动签名有问题新驱动又不支持旧芯片买模块时尽量避开。调试阶段我愿意多花几块钱选FTDI或者CP2102的方案原因很简单当通信链路出问题时一个稳定的串口工具可以帮你排除“上游调试工具不稳定”这个变量。如果还在用便宜的PL2303做串口烧写烧写失败时先别怀疑固件把波特率降下来重置一下模块再试很多时候问题就出在工具本身。3. Android串口配置与数据通信实战3.1 设备节点长什么样权限怎么打通Android底层就是Linux串口设备在Linux里表现为字符设备节点。不同SoC平台节点名不一样高通平台常见/dev/ttyHSL或/dev/ttyMSMMTK平台常见/dev/ttyMT通用x86平台则是/dev/ttyS0、/dev/ttyS1这种形式。定位具体节点的方法是查看系统串口配置dts或者直接看/sys/class/tty下面的设备列表。串口开发绕不开的大坑是权限。普通App默认根本打不开/dev/ttyS*这类节点因为设备节点属主是root或system权限是crw-rw----第三方App连读的资格都没有。技术上常见三种解决办法一是定制ROM的做法在init.rc里对目标节点执行chmod 666或者在SELinux policy里给特定应用放行tty_device访问二是把应用做成系统应用用系统签名参与签名这样就能拿到system权限三是设备root后通过su提权执行chmod 666再打开节点。车载Android基本是定制系统所以我更推荐在系统侧直接处理权限不要依赖root。具体做法是找平台工程师确认目标串口节点名然后在init.rc或对应的.rc文件中追加一句对节点权限的修改同时检查SELinux策略。否则应用层即使调用了open函数也会返回Permission denied。3.2 NDK串口库集成与环境配置Android应用层不能直接打开串口必须通过JNI调用native层封装好的C代码再经过系统调用open、ioctl、read、write来操作设备节点。社区里应用最广泛的是Google的android-serialport-api核心是两个文件SerialPort.java和serial_port.c。它的思路是把打开串口、设置termios参数这些逻辑放在C层Java层只负责持有文件描述符并包装出InputStream和OutputStream。在Android Studio里集成这个库推荐用CMake方式。先在app/build.gradle里配置externalNativeBuild指向CMakeLists.txt然后在CMakeLists.txt里加入serial_port.c源文件最后在主工程的native方法声明处System.loadLibrary(serial_port)。编译时需要注意NDK版本和ABI的选择车机系统一般是arm64-v8a我习惯在abiFilters里只保留arm64-v8a和armeabi-v7a避免多ABI编译导致的代码膨胀。有一个经常被忽略的点Android Studio默认对NDK的构建支持已经很完善但很多人卡在了NDK和SDK版本不匹配的问题上。最好统一使用SDK Manager里推荐的NDK版本不要去下载乱七八糟的独立NDK否则CMake会给你报一堆莫名其妙找不到编译器的错。3.3 打开串口、设置波特率与数据位串口配置的核心是termios结构体。open函数拿到文件描述符之后通过tcgetattr读取当前终端参数再修改波特率、数据位、校验位、停止位最后用tcsetattr写回。下面给出一份可以直接改用的C代码片段int open_serial(const char *path, int baudrate) { int fd open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) return -1; struct termios options; tcgetattr(fd, options); speed_t speed; switch (baudrate) { case 9600: speed B9600; break; case 19200: speed B19200; break; case 38400: speed B38400; break; case 57600: speed B57600; break; case 115200: speed B115200; break; default: speed B115200; } cfsetispeed(options, speed); cfsetospeed(options, speed); options.c_cflag ~CSIZE; options.c_cflag | CS8; // 8个数据位 options.c_cflag ~PARENB; // 无校验 options.c_cflag ~CSTOPB; // 1个停止位 options.c_cflag | (CLOCAL | CREAD); options.c_iflag IGNPAR; options.c_oflag 0; options.c_lflag 0; tcsetattr(fd, TCSANOW, options); return fd; }这里几个flag的用意得说清楚。CLOCAL表示不关心调制解调器的控制线避免open时因为DTR/DSR状态不对而阻塞CREAD使能接收IGNPAR让驱动忽略奇偶校验错误防止出错时丢掉后续所有数据。c_oflag和c_lflag都设为0是禁用输出处理和行处理模式防止内核把收到的数据做换行转换或者回显这些默认行为在串口通信里只会添乱。Java层的封装很简单声明一个native open方法传入设备路径和波特率返回FileDescriptor然后用FileInputStream和FileOutputStream包一层就能读写。public class SerialPort { static { System.loadLibrary(serial_port); } private native FileDescriptor open(String path, int baudrate, int flags); public SerialPort(File device, int baudrate) throws IOException { mFd open(device.getAbsolutePath(), baudrate, 0); if (mFd null) throw new IOException(native open returns null); mFileInputStream new FileInputStream(mFd); mFileOutputStream new FileOutputStream(mFd); } }这段代码最早是Google官方demo里的写法到现在依然是社区里所有Android串口库的基础。如果你不想自己写JNI也可以用现成的开源库但本质上它还是重新包装了这份代码。我的建议是哪怕用库也要理解这套open/ioctl流程因为排查问题的时间点往往就在这些底层细节里。3.4 数据收发线程与帧校验处理串口读是阻塞的所以绝对不能在主线程里直接read否则卡死UI是必然的。Android串口数据收发的标准做法是一个后台线程专门读InputStream并发把收到的字节交给数据解析器另一个写操作呢用线程安全的队列包装需要发送数据时往队列里放发送线程从队列取数据再写入OutputStream。读取逻辑我一般这样写private fun startReading() { readerThread thread(start true) { val buffer ByteArray(1024) while (isRunning) { val size mInput.read(buffer) if (size 0) { val data buffer.copyOf(size) handler.post { protocolParser.parse(data) } } } } }解析器是整个通信稳定性的关键。我强烈建议不要在读取线程里做复杂的协议解析直接把原始字节抛给一个独立的解析模块这样一是可以保证读线程不被占住二是方便对粘包和半包做统一处理。车载串口设备的常见问题是设备动作频繁时一包数据可能被拆成多次read返回或者多包数据挤在同一个read里。所以协议解析必须带缓冲区把收到的字节累积起来按帧头、长度字段逐步拆帧。拆帧逻辑建议设计成状态机遍历缓冲区找帧头读到完整一帧再处理处理完移除已消费的字节。每次解析结束后如果缓冲区剩余长度小于最小帧长就保留等下次read再拼接。这个“等下一次”的时间可能只有几毫秒但能彻底解决半包问题。写侧也要注意线程安全。多个业务模块同时向串口发数据时如果并发write底层的字节会交错混在一起接收方根本没法解析。所以我用一个LinkedBlockingQueue做发送队列一个独立的发送线程串行消费队列。这样即使多个模块同时上报发送端也能保证数据帧的完整性。3.5 串口稳定性、热插拔与异常恢复车载工况和桌面开发完全不是一个量级车机在行驶中面临的震动、温度变化、电源波动都对串口链路有影响。设备运行几天后突然串口无响应、fd异常、read返回-1这类问题出现的概率不低。所以代码层面必须做好异常恢复。我的做法是应用层做一层“串口状态机”保持四个状态已关闭、打开中、已打开、异常。读取线程如果捕获到IOException立即把状态切到异常通知主线程尝试关闭旧fd并重新打开设备。打开失败则按指数退避策略重试第一次等1秒第二次等2秒第四次以后固定等10秒避免在设备还没准备好的时候疯狂重试刷屏日志。如果设备走的是USB转串口方案还需要监听UsbManager的ACTION_USB_DEVICE_ATTACHED/DETACHED广播在设备拔插时自动恢复连接。纯原生的tty设备没有插拔概念但底层驱动可能因为休眠或电源管理导致串口假死这时候重开fd是最简单有效的恢复手段。总之车载串口开发要有心理准备链路中任何一环都可能掉链子应用层必须兜底。4. 车载串口协议设计与报文解析4.1 常用帧格式与地址分配物理层通了之后真正的难点在协议层。车载串口设备五花八门协议格式也各有各的规矩但万变不离其宗常见帧结构是帧头 长度 命令/地址 数据域 校验。帧头用来定位起点长度字段告诉接收方这帧数据要收多少字节命令字区分功能类型地址在RS485一主多从场景里用于路由校验保证数据完整。设计帧格式时有个原则长度字段和校验字段不是可选项是必选项。没有长度字段接收方就得分包解析没有校验字段数据错一比特根本发现不了等业务层用错了数据再去追溯代价就大了。我在项目里常用的一种紧凑帧格式如下字节偏移字段说明0-1帧头0xAA 0x552长度从命令字到校验前的总字节数3地址从机地址或广播地址0xFF4命令功能码5~N4数据域实际业务数据N5校验异或或CRC帧头选两个不同的字节比如0xAA和0x55可以有效降低数据域里恰好像帧头导致误判的概率。如果数据域本身可能包含整段任意字节建议在编码时做转义处理如Modbus RTU里0x7E转成0x7D 0x5E或者干脆靠长度字段来严格约束不匹配就跳过一字节重新查找帧头。4.2 校验方式如何选校验算法直接决定通信的可靠性。最轻量的是异或校验把数据逐字节异或得到一字节校验码实现简单CPU开销几乎为零适合帧长较短、数据质量要求不高的场景。Modbus RTU的CRC16则是工业标准抗干扰能力强能检测出大多数突发错误而且有高效查表实现代码量不大车载总线场景我最推荐用它。计算CRC16时要注意字节序。CRC16的计算结果有高字节在前和低字节在前两种约定Modbus规定低字节在前。很多人自己做CRC16校验时对不上报文先检查是不是字节序反了。CRC32适合数据量比较大的日志类和文件传输场景普通命令交互用不上没必要为了“高大上”强行加。除了校验算法业务层还要有超时重发机制。发送请求后如果从机没有回应主站需要在一定时间后重发这个超时时间怎么设定很有讲究要按波特率来算。9600波特率下一字节约1.04ms一条10字节的响应帧在硬件上就有10ms的传输时间再算上从机处理时间超时设20ms都是不合理的。我一般按“帧长字节数 * 每字节耗时 * 2 从机处理余量”来粗略估算宁可偏大一点也不能因为超时太短误杀正常响应。重发次数通常不超过3次超过后该从站直接标记为离线由业务层决定是否告警。4.3 一主多从RS485总线的请求/响应对RS485一主多从的通信模型非常清晰总线上同一时刻只能有一个节点发送数据正常情况下由主站发起请求从站收到正确报文后回响应。从站绝不能主动“讲话”否则多个从站同时抢总线数据碰撞后整条总线就乱了。以Modbus RTU读寄存器为例主站发请求帧01 03 00 00 00 01 84 0A拆开看01是从站地址03是功能码读保持寄存器00 00是起始寄存器地址00 01是要读的寄存器个数84 0A是CRC16校验字节序低字节在前。从站如果正常响应帧长这样01 03 02 12 34 B6 4A01是回自己的地址03表示功能码原样返回02是后面数据域的字节数12 34是寄存器里的两个字节数据B6 4A是CRC16。如果地址不对或者寄存器不存在从站会返回一个异常码功能码的最高位置1后面跟着错误码比如01表示非法功能02表示非法数据地址。这些基础报文结构在排查“为什么没数据”时极其有用我建议读一遍Modbus协议原文很多厂商的自定义协议都是Modbus的变体或者简化版。Android端的报文解析代码可以按功能码和地址做一个分发器。收到完整帧后先校验地址是否匹配本机如果是主站则校验地址字段是不是要管理的设备再校验CRC最后按功能码分发到对应的处理器。这样新增一个设备功能只需要增加一条处理分支不需要改动底层拆帧逻辑维护起来非常舒服。5. 常见问题与排查技巧实录5.1 串口乱码先从波特率和电平下手串口乱码是出现频率最高的问题热搜词“rs232乱码”常年挂着我也解释过很多遍。乱码的出现只有几种原因波特率不一致、电平逻辑不对、共地不良、线材接触不良。排查顺序很重要不要一上来就怀疑Android代码。第一步确认两端波特率完全一致。很多人说“我明明都设置成115200了”结果仔细一看设备那边是115200Android这边配置的百分比是115200没错但termios的B115200宏定义在linux内核里对应的是实际的波特率如果底层驱动对时钟分频有特殊处理实际波特率可能和理论值有偏差。这种硬件层面的偏差很难靠肉眼看出最靠谱的做法是用示波器测一下空闲状态和起始位宽度倒推出实际波特率。第二步确认电平逻辑。TTL设备直接接RS232接口板逻辑电平反了数据全乱。我之前接过一个第三方传感器对方标称RS232接口但实际内部信号是TTL电平结果不管怎么调波特率都是乱码。后来用逻辑分析仪抓波形才发现这个“RS232”根本就是TTL直出中间的MAX232是后加的加反了。所以硬件链路初始对接时先拿一个已知OK的USB转串口模块在PC端验证排除电平问题再回来查Android。5.2 串口烧写失败多数不是代码问题串口烧写失败是嵌入式开发最常见的“玄学问题”之一。报错可能是“无法连接设备”“同步失败”“擦除失败”但背后的真实原因经常出乎意料。一是目标板的串口被占用PC端调试助手还没关掉烧录工具肯定抢不到串口二是USB转串口模块供电不足烧录时需要瞬间拉高电流给目标板供电便宜的模块电压直接跌落导致通信中断三是波特率设置过高烧写工具和Bootloader之间的时序容错变差。我自己的处理套路先关掉所有串口调试工具单独打开烧录工具然后给目标板独立供电不要依赖USB转串口模块的3.3V/5V输出接着把波特率从默认的115200降到57600甚至38400重试最后换一根短一点的USB线或者换一个FTDI芯片的模块。这样四步走下来绝大多数烧写失败都能解决如果还不行才考虑Boot引脚没拉对或者芯片损坏这类硬件问题。5.3 打不开串口、权限拒绝区分SELinux和文件权限应用层调用open时抛出Permission denied先别急着改代码。用adb shell登进去手动执行ls -l查看设备节点确认节点是否存在以及属主权限。如果节点本身是crw-rw----属主是root:root而你的应用不是root或system那就是节点权限不够需要在系统侧改权限或者用系统签名。如果节点权限没问题但应用还是打不开大概率是SELinux拦住了。查看方法adb shell dmesg | grep avc如果看到avc: denied这类日志说明是SELinux策略拦截。车载定制的Android一般不允许直接setenforce 0需要联系系统工程师在sepolicy里给对应域添加tty_device或者node的allow规则。这两种原因的区别一定要能分清楚不然越改越乱。5.4 数据丢包、粘包、错位协议层来兜底物理层通了之后数据层面还会遇到丢包、粘包、错位三类问题而且经常同时出现。粘包好理解就是多个帧被合并到一次read返回里半包则是帧被拆成多次返回错位则往往是波特率偏差或接收线程处理不及时导致采样点偏移某些字节被错误读取。针对这三类问题唯一的解法是协议层做完整的状态机拆帧加上CRC校验再辅助业务层的重发机制来兜底。排查丢包时我习惯在每一帧里加一个递增序号字段。收到帧后先看序号是否连续如果不连续说明中间确实丢了数据此时可以统计丢失序号的范围和间隔帮助判断是在哪一段链路上丢的。如果没有序号设计丢包根本没法量化和定位。这也是我一直强调自定义协议里要带序号的原因。5.5 常见问题速查表症状优先排查项解决方向一直乱码波特率、电平示波器测波特率确认RS232/RS485/TTL无数据TX/RX顺序交换收发线再试时通时断接地、线材共地换双绞屏蔽线烧写失败端口占用、供电关调试工具独立供电降波特率Permission denied节点权限、SELinuxchmod或改sepolicyRS485丢包终端匹配电阻总线两端各加120Ω粘包/半包解析逻辑拆帧状态机加缓冲拼接读数据为负数/错位驱动分频查dts时钟和UART驱动配置5.6 调试过程中的几个实操技巧调试串口这么多年有几个习惯帮我省了大量时间。第一PC端的串口调试助手同时只能开一个COM口是独占的多个工具抢同一个串口后开的那个往往打不开先开的也会变得不稳定。第二Android侧打印串口数据时永远用HEX格式不要用String直接打印因为设备返回的字节可能是非UTF-8编码直接转String会变成一堆乱码干扰排查。第三logcat里给串口读和写分别打不同的TAG比如SerialPortRead和SerialPortWrite这样一眼就能看出数据是收不到还是发不出不用在几十行日志里来回翻。还有一些和文件路径相关的坑。Android 7.0以上分区存储和FileProvider机制改了外部存储访问方式如果串口工具需要导出日志文件或者读取配置文件路径处理不当就会遇到类似content://com.xxx.fileprovider这种由SAF和FileProvider引发的问题。我的建议是串口调试过程中需要落盘的数据统一写到应用私有目录比如getExternalFilesDir再通过FileProvider分享出去不要直接往公共存储目录写否则高版本系统上会因为权限问题白屏。这个经验虽然听起来和串口无关但真到现场调日志时能少踩一个坑。关于驱动安装Windows下CH340和FTDI的驱动安装完一定要去设备管理器里确认端口号常见问题是驱动装好了但端口被识别成其他设备或者端口号一直在变。调试时建议把端口号固定下来省得每次插拔USB都要重新选一次端口。6. 一些做车载串口项目的个人体会踩过这么多坑之后我的体会是串口开发本身不复杂复杂的是硬件链路里任何一环都可能出问题而Android层往往不是出问题的那一环。所以不管三七二十一拿到设备先别写Android代码先在PC端用调试助手把链路打通确认物理层、电平、波特率、协议都OK再动Android侧这样能省掉一半的调试时间。最后再分享一个经验车载项目里协议设计早点定帧头和CRC校验字段千万别省地址空间也尽量留充足。尤其是RS485多设备场景后期要新增设备时如果地址分配和校验机制一开始设计得太随意出现地址冲突和乱码时排查起来真的能让人怀疑人生。串口通信的链路就这么长每一层都稳一点整条链路才能真的稳。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进