1. 从一台老设备的调试口说起串口为什么还在很多人第一次接触嵌入式都是从一根USB转串口线开始的。插上电脑打开串口调试助手选个COM口波特率115200然后屏幕上开始哗哗地滚日志。这个场景二十年没怎么变过但奇怪的是都2025年了以太网、WiFi、蓝牙、LoRa、CAN FD满天飞串口这个看起来老掉牙的东西为什么还牢牢占据着工业现场和开发板的调试口答案其实很朴素串口是异步通信里最便宜、最可靠、最容易实现的那一个。它不需要时钟线不需要复杂的协议栈不需要握手协商两根线TX/RX加一根地线就能跑。对于一颗几毛钱的MCU来说UART外设几乎是标配代码量小到可以塞进任何Bootloader里。你在STM32、GD32、全志V3S、Jetson TK1这些完全不同的平台上都能找到几乎一样的UART寄存器操作逻辑这种跨平台的通用语言属性是其他任何高速总线都替代不了的。更关键的是IIoT工业物联网场景里大量存量设备——PLC、变频器、电表、温控器、称重仪表——它们的通信接口就是RS485或者RS232。你不可能把一台用了十年的西门子PLC拆了换成以太网版本但你可以用一个几十块钱的串口转以太网模块把它接进MQTT网关。这就是串口不死的真实原因它不是最先进的技术但它是存量世界里连接成本最低的桥梁。这篇文章不打算写成UART协议教科书而是从实际工程角度把串口在IIoT底层里那些真正会踩坑的地方讲透TTL、RS232、RS485到底怎么选RS485组网的上下拉电阻和终端电阻怎么算DMA收发为什么会丢数据Linux下串口被占用怎么排查以及为什么你烧录固件失败十有八九是串口的问题。适合已经会点灯、会打印Hello World但一到真实组网和长距离传输就抓瞎的开发者。2. TTL、RS232、RS485三种串口的电气本质区别2.1 它们说的都是UART但电平完全不是一回事新手最容易混淆的概念就是UART、TTL串口、RS232、RS485到底什么关系。一句话说清楚——UART是协议TTL/RS232/RS485是物理层电气标准。UART定义了数据帧格式起始位、数据位、校验位、停止位而TTL、RS232、RS485定义了0和1用什么电压表示、能传多远、能不能多点组网。类型逻辑1电平逻辑0电平典型距离拓扑典型场景TTL UART3.3V/5V0V板内几十cm点对点MCU调试口、模块间通信RS232-3V~-15V3V~15V15m左右点对点老式PC串口、工控机RS485差分±1.5V~±6V差分反向1200m总线多点工业组网、PLC、电表TTL串口就是芯片引脚直接出来的电平3.3V代表10V代表0。它的问题是抗干扰能力极差一根杜邦线稍微长一点或者旁边有个电机在转数据就乱了。所以TTL串口基本只能在同一块板子或者相邻模块之间用。RS232用负逻辑而且电压摆幅大±12V左右抗干扰比TTL强不少但它依然是点对点的而且需要专门的电平转换芯片比如经典的MAX232。现在新设计里RS232越来越少主要出现在老设备的维护口上。RS485才是IIoT的主角。它用两根线A和B的电压差来表示信号而不是对地电压。这个设计的好处是共模干扰会同时叠加在A和B上相减之后就被抵消了。所以RS485能跑1200米能挂32个甚至128个节点工业现场几乎清一色用它。2.2 为什么你的TTL串口接RS485模块会烧我见过不止一个新手把MCU的TX直接接到RS485收发器的A/B上然后问为什么没数据。这里必须强调RS485收发器比如MAX485、SP3485的输入端是TTL电平的DI和DE/RE输出端才是A/B差分。你要接的是收发器的DI引脚不是A/B。正确的连接方式是MCU_TX → 收发器DIMCU_RX → 收发器ROMCU的一个GPIO → 收发器DE/RE收发方向控制。A接AB接B然后总线两端各加一个120Ω终端电阻。这个方向控制引脚是RS485半双工的核心发送时拉高接收时拉低切换时机不对就会丢首字节或者总线冲突。注意有些RS485模块标了自动收发内部用三极管或者专用芯片做了方向自动切换这种模块省了一个GPIO但在高波特率下切换延迟可能导致最后一个字节发不出去调试时如果发现数据偶尔缺尾字节优先怀疑这里。2.3 TTL通过光耦能传多远热词里有个问题很典型ttl uart通过光耦能传多远。光耦的作用是电气隔离不是延长距离。普通光耦如PC817的传输速度很慢波特率超过9600就开始失真高速光耦如6N137能到10Mbps但成本高。光耦隔离后的TTL串口传输距离还是取决于后面的驱动能力一般还是板级几十厘米。真正要长距离光耦后面还是要接RS485收发器。所以光耦TTL解决的是隔离问题不是距离问题这两个需求别搞混。3. RS485组网实战上下拉电阻和终端电阻到底怎么算3.1 终端电阻不是随便加的120ΩRS485总线两端各加120Ω终端电阻这个大家都知道。但为什么是120Ω因为标准RS485电缆的特性阻抗大约是120Ω终端电阻匹配特性阻抗才能吸收信号反射。如果你用的是普通的双绞线而不是标准RS485电缆特性阻抗可能是100Ω或者150Ω严格来说终端电阻应该匹配这个值但工程上120Ω足够用。问题在于很多现场根本不该加终端电阻的地方也加了。终端电阻只在总线的最远两端加中间节点绝对不能加。如果一条总线上挂了10个节点每个节点都焊了120Ω那并联起来就是12Ω收发器的驱动能力直接被拖垮通信距离反而大幅缩短。我遇到过一条总线只有30米却通信不稳最后发现是8个节点全带了终端电阻拆掉中间6个之后立刻正常。3.2 上下拉电阻的作用和取值计算RS485总线在空闲状态没有节点发送时A和B之间的差分电压是不确定的可能落在逻辑0和逻辑1之间的模糊区导致接收端收到随机跳变的噪声表现为总线空闲时收到乱码。解决办法是在总线上加偏置电阻A通过一个上拉电阻接VCCB通过一个下拉电阻接GND让空闲时A比B高总线稳定在逻辑1空闲态。取值怎么算假设总线两端各有一个120Ω终端电阻并联后等效60Ω。偏置电阻要在60Ω上产生至少200mV的差分电压RS485标准要求接收器能识别200mV以上的差分。设上拉Rup、下拉Rdn相等为R则Vab VCC × 60 / (R 60) 近似忽略另一侧 要求 Vab ≥ 0.2V 若 VCC 5V则 5 × 60 / (R 60) ≥ 0.2 解得 R ≤ 1440Ω所以常见的取值是560Ω到1kΩ之间。但注意偏置电阻只在一个节点上加就够了通常是主站或者总线的一端。如果每个节点都加等效阻值又会变小增加静态功耗。很多RS485收发器芯片如MAX3485、SP3485内部已经集成了失效安全偏置这种情况下外部就不用再加了查一下芯片手册的fail-safe特性。电阻类型位置典型值作用终端电阻总线最远两端120Ω阻抗匹配消除反射上拉电阻总线一端A到VCC560Ω~1kΩ空闲偏置到逻辑1下拉电阻总线一端B到GND560Ω~1kΩ空闲偏置到逻辑1隔离电阻可选串联在A/B10Ω~100Ω限流保护防浪涌3.3 组网拓扑和接地的坑RS485必须是手拉手菊花链拓扑不能星型不能树型。星型拓扑会让每个分支都产生反射通信距离和稳定性急剧下降。如果现场布线已经做成了星型补救办法是在每个分支末端加终端电阻但效果不如重新布线。接地问题更隐蔽。RS485是差分传输理论上不需要参考地但实际上如果两个节点的地电位差太大超过收发器的共模范围-7V~12V收发器会损坏或者误判。所以长距离RS485组网必须有一根地线把各节点的地连起来或者使用隔离型RS485收发器如ADM2483。我见过一个工厂车间两台设备分别接在不同的配电柜上地电位差有十几伏RS485芯片烧了好几片最后换成隔离模块才解决。4. 串口DMA收发为什么你的数据会丢4.1 中断收发的瓶颈在哪里用中断方式收串口数据每收到一个字节进一次中断。115200波特率下每个字节间隔约87微秒如果系统里还有别的中断比如定时器、ADC中断嵌套和响应延迟就可能导致字节丢失。更麻烦的是如果你在中断里做复杂处理比如解析协议下一个字节来了还没处理完就溢出ORE标志置位了。STM32的串口有个经典问题ORE标志置位后如果不及时清除接收会一直卡死。因为ORE是溢出错误它和RXNE共用一个中断入口很多人只清了RXNE没清ORE结果串口再也不收数据了。正确做法是在中断里先读SR寄存器再读DR寄存器这个顺序能同时清掉RXNE和ORE。4.2 DMA收发的正确姿势DMA直接内存访问让串口数据不经过CPU直接搬到内存是解决高速收发和丢数据的标准方案。但DMA不是配置好就万事大吉有几个坑必须注意。接收方向用DMA接收时通常配合空闲中断IDLE来判断一帧数据结束。因为DMA是循环搬运的你不知道对方什么时候发完。IDLE中断在总线空闲一个字节时间后触发此时读取DMA的剩余计数NDTR就能算出这一帧收了多少字节。这个组合是STM32/GD32上最常用的不定长接收方案。// STM32 HAL库空闲中断DMA接收示例 void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart1); // 停止DMA计算接收长度 HAL_UART_DMAStop(huart1); uint16_t recv_len BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); // 处理数据 recv_buffer[0..recv_len-1] process_data(recv_buffer, recv_len); // 重新启动DMA接收 HAL_UART_Receive_DMA(huart1, recv_buffer, BUFFER_SIZE); } }发送方向DMA发送的坑在于发送完成的判断。HAL库的HAL_UART_Transmit_DMA是异步的函数返回时数据还没发完。如果你紧接着就切换RS485方向引脚为接收最后几个字节就发不出去。正确做法是等TCTransmission Complete标志置位或者用DMA传输完成中断回调在回调里再切方向。提示RS485方向切换的时机建议在TC中断里做而不是TXE发送数据寄存器空。TXE只表示数据寄存器空了但移位寄存器里可能还有数据没发完此时切方向会截断最后一个字节。4.3 Linux下串口丢数据的排查Linux下串口丢数据八成是缓冲区设置的问题。默认的串口接收缓冲区可能只有4096字节高波特率下如果应用层读取不及时内核缓冲区满了就丢数据。可以用stty或者termios设置更大的缓冲区或者用setserial调整。另一个常见原因是VM虚拟机配置串口时的延迟。虚拟机把物理串口映射进来中间多了一层如果虚拟机设置里串口用了轮询模式而不是中断模式高波特率下必丢数据。VMware里要把串口设备的I/O模式改成轮询时主动放弃CPU或者直接用USB直通。排查Linux串口问题先看这几个命令# 查看所有串口设备 ls /dev/ttyS* /dev/ttyUSB* /dev/ttyACM* # 查看串口参数 stty -F /dev/ttyUSB0 -a # 查看串口被哪个进程占用 lsof /dev/ttyUSB0 fuser /dev/ttyUSB0 # 实时查看串口数据 cat /dev/ttyUSB0 | hexdump -Clsof和fuser是排查串口被占用的利器。Windows下没有lsof可以用资源监视器或者Process Explorer搜索句柄也可以用一个叫串口占用检测的小工具。热词里win7下怎么查看串口被哪个程序占用就是这个需求Win7下推荐用Process Explorer的Find Handle功能输入COM3就能找到占用进程。5. 从驱动到烧录那些让人抓狂的串口故障5.1 CH340、FT232R、CP2102USB转串口芯片的驱动坑USB转串口芯片是每个嵌入式工程师的必备工具但驱动问题能让人崩溃。常见的几款CH340/CH341国产便宜但Win7/XP下需要手动装驱动而且不同批次的芯片驱动可能不兼容。Win10以上一般免驱。FT232RFTDI的经典款稳定但市面上有大量山寨芯片FTDI的驱动会识别出山寨并变砖把PID改成0000。热词里ft232r usb uart驱动安装就是这个。正品FT232R在Windows下装VCP驱动即可如果遇到山寨变砖只能换芯片。CP2102Silicon Labs的稳定性好驱动安装简单推荐新手用。驱动装好之后设备管理器里应该出现USB-SERIAL CH340 (COMx)或者Silicon Labs CP210x USB to UART Bridge (COMx)。如果出现黄色感叹号右键更新驱动手动指向驱动目录。如果设备管理器里根本没有新设备换根USB线试试——很多便宜线只有充电功能没有数据功能这个坑我踩过不止一次。5.2 串口烧录失败先查这三件事串口烧写失败是热词里的高频问题。STM32、GD32、ESP8266这些用串口烧录的芯片失败原因通常就三个BOOT引脚状态不对。STM32要进BootloaderBOOT0必须拉高BOOT1拉低然后复位。很多人忘了复位或者BOOT0没接对芯片直接从Flash启动当然烧不进去。串口被占用。烧录软件打开串口失败是因为串口调试助手还开着。关掉所有可能占用串口的程序再试。波特率和流控。有些芯片的Bootloader对波特率敏感比如ESP8266烧录时波特率太高会失败降到115200甚至74880试试。流控RTS/CTS如果芯片不支持要在烧录软件里关掉。GD32F470VET6这类芯片串口烧录和STM32基本兼容但要注意GD32的Bootloader可能对某些USB转串口芯片的时序更敏感如果CH340烧录失败换FT232R试试成功率会高很多。5.3 串口打印乱码90%是波特率或时钟问题串口打印乱码第一反应是波特率不对。但如果你确认两边都是115200还是乱码那就要查时钟源。STM32的串口波特率是从系统时钟分频来的如果你改了系统时钟比如从72MHz超频到128MHz但没重新计算波特率分频系数实际波特率就偏了。用示波器量一下TX引脚的一个字节波形测出实际位宽反推波特率就能确认。还有一种乱码是电平不匹配。3.3V的MCU TX接到5V的USB转串口RX如果转串口芯片的输入阈值是2.0V3.3V还能识别但反过来5V TX接3.3V RX可能烧掉RX引脚。所以跨电压通信要么加电平转换要么选支持宽电压的转串口芯片。6. 串口在IIoT里的真实角色网关、协议和边缘计算6.1 串口转以太网/MQTT网关的架构IIoT里串口最常见的用法是串口转网关。一个典型的架构是现场设备PLC、电表通过RS485总线连到一个边缘网关网关上的MCU或者Linux核心把Modbus RTU协议解析出来转成MQTT或者HTTP上报到云平台。这个架构里串口侧的关键是Modbus RTU的帧间隔判断。Modbus RTU规定帧与帧之间至少3.5个字符时间的静默用来区分帧边界。在9600波特率下3.5个字符约4毫秒。网关如果用DMAIDLE接收IDLE中断的触发时间要配置得比3.5字符稍长否则会把一帧拆成两帧。很多Modbus通信不稳定的案例根源就在这个帧间隔判断上。6.2 串口封装成C模块的工程实践热词里串口封装c是个好话题。在嵌入式项目里串口驱动最好封装成独立的模块提供统一的接口初始化、发送、接收回调、错误处理。这样换平台从STM32换到GD32或者全志V3S时只需要改底层寄存器操作上层协议代码不用动。一个实用的封装思路是环形缓冲区状态机。接收中断只负责把字节塞进环形缓冲区主循环或者任务里从缓冲区取数据做协议解析。这样中断处理时间极短不会丢数据协议解析也不阻塞中断。发送方向用DMA发送完成回调避免阻塞。// 环形缓冲区接收的简化封装 typedef struct { uint8_t buffer[256]; volatile uint16_t head; volatile uint16_t tail; } ring_buffer_t; void uart_rx_isr(uint8_t byte) { uint16_t next (rb.head 1) % sizeof(rb.buffer); if (next ! rb.tail) { // 缓冲区未满 rb.buffer[rb.head] byte; rb.head next; } // 满了就丢弃或者置溢出标志 } int uart_read(uint8_t *data, uint16_t len) { int count 0; while (count len rb.tail ! rb.head) { data[count] rb.buffer[rb.tail]; rb.tail (rb.tail 1) % sizeof(rb.buffer); } return count; }6.3 边缘计算场景下的串口数据预处理在IIoT网关里串口收到的数据不一定直接上报往往要在边缘做预处理过滤无效数据、做单位换算、聚合多个设备的数据、异常检测。比如一个电表每秒钟通过RS485上报一次电压电流网关可以每10次取平均再上报减少云端流量。这个预处理逻辑放在哪里如果网关是Linux系统可以用Python或者C写个守护进程通过/dev/ttyUSB0读串口。如果是MCU网关就在主循环里做。关键是不要让串口读取阻塞预处理逻辑用非阻塞读或者独立任务。7. 一些实战中攒下来的经验串口这东西原理简单但工程细节极多。我做了这么多年总结下来几条最值钱的经验第一调试串口和通信串口要分开。很多项目用一个串口既打印日志又跑Modbus结果日志把协议帧冲乱了。如果MCU串口资源够一定留一个专门的调试口。第二RS485总线上的设备上电顺序有讲究。如果所有设备同时上电某些收发器在上电瞬间会输出不确定电平导致总线冲突。稳妥的做法是主站先上电从站逐个上电或者用带失效安全保护的收发器。第三长距离RS485一定要用双绞线而且A/B要配对。双绞线的一对线就是A和B不能随便拿两根线。如果现场已经布了非双绞线通信距离要打对折。第四串口调试助手要选支持时间戳和HEX显示的。分析Modbus协议时HEX显示能看清帧结构时间戳能判断帧间隔。我常用的是SSCOM和XCOM功能够用。第五遇到诡异问题先换线、换模块、换电脑。串口问题里硬件故障的比例远高于软件。一根劣质USB线、一个虚焊的DB9接头能让你查一整天代码。最后说个热词里提到的uart烧录路由器固件。这属于串口在Bootloader层面的应用原理和STM32串口烧录一样只是路由器的Bootloader通常是U-Boot通过串口进入命令行然后用TFTP或者YMODEM传固件。关键还是那三件事波特率、流控、进入Bootloader的时机。U-Boot启动时通常有1-2秒的倒计时按任意键进入命令行错过就得重启再来。串口不会死因为它是嵌入式和工业世界的普通话。你可能会用更快的总线做数据主干但调试、配置、救砖、连老设备最后还是得靠这根线。把串口的电气特性、组网规则、DMA收发和故障排查吃透比追任何新协议都实在。
阅读完成 · 觉得有帮助?