说实话单片机串口玩到一定阶段都会撞上485这道坎。前面写STM32学习笔记已经写到第三十一篇这一篇专门把“485通信原理及实验”拆开聊清楚。很多朋友第一次用485模块会觉得它不过就是“串口加个转换芯片”结果一接工业设备就翻车——不是收不到数据就是乱码要么一台上位机只能带一个从机。这篇内容会从底层电气原理讲起再落到实验接线、代码实现和故障排查完整走一遍485从理论到项目的路径。适合正准备把两个板子串起来、或者要对接现场PLC仪表的朋友照着做能少踩很多坑。1. 为什么工业现场离不开485从RS-232的痛点看差分传输1.1 RS-232那几个“天生局限”要理解485最好先看它替代掉的是谁。RS-232在早期PC和单片机调试里非常常见一根TX、一根RX、一根GND逻辑电平用正负电压表示电路简单。但它的问题很致命点对点一台设备只能和一路对端通信距离短一般十几米就开始信号衰减抗干扰弱单端信号的地线是薄弱环节只要两端设备的地电位有差异共模干扰就会直接叠加进数据里。工业现场压根不是这种环境。几十米的电缆从控制柜拉到设备端中间还可能穿过变频器、电机这类强干扰源一个总线里往往要挂仪表、传感器、执行器好几台设备还要保证至少一台主机能轮询访问所有节点。拿RS-232硬扛成本高、可靠性差。于是RS-485这类差分信号方案被大量采用专治“长距离、多节点、强干扰”这三大难题。1.2 差分信号的核心看两根线之间的电压差RS-485最核心的思想是差分传输。它不用“某根线对地是高还是低”来表示逻辑而是用A、B两根线之间的电压差来编码逻辑1也叫空闲态A线电压比B线高典型的差分电压大于 200 mV逻辑0起始位或数据0A线电压比B线低差分电压小于 -200 mV。接收端只看UARTA-B的差值并不关心A或B各自对地是多少伏。这就带来了两个直接好处。第一抗共模干扰。如果外部电磁噪声同时耦合到A和B线上两根线上的干扰在大小和方向上基本一致相减之后干扰被抵消。这也是为什么485规范推荐用双绞线——两根线拧在一起接收到的高频干扰尽量保持一致差分结构天然能压掉一大部分共模噪声。第二允许地电位有差异。RS-232收发双方必须共用一个参考地距离一长两地之间往往存在几伏甚至几十伏的电位差信号判断就会出错。RS-485接收器的输入共模范围通常能做到-7V到12V在工业环境里相当于给了很大的容差空间。这也解释了为什么485能在1200米的线路上稳定通信而RS-232通常只能在15米左右活动。1.3 RS-232、RS-422和RS-485的横向对比接触过工业串口的人还常听到RS-422它和485很像但有所区别。把三者放在一张表里最直观对比项RS-232RS-422RS-485信号方式单端差分差分通信模式全双工点对点全双工一对多半双工为主也可四线全双工最大节点数1对11发多收常见32个单位负载典型距离约15米约1200米约1200米逻辑判定对地电平A-B差分A-B差分硬件线数至少3根4根2根半双工工程中最常见的场景是“一台主机带多台从机共用两根线分时收发”这就是半双工RS-485。它只需要A、B两根线加一根信号地远距离还涉及屏蔽层接地成本低、布线简单所以在工业总线里占据了绝对主流。我把这些原理放在最前面是因为后面所有接线和代码问题追根溯源都能回到“差分”和“半双工”这两个词上。搞不清这两点调多少天都容易在同样的地方反复打转。2. 485物理层设计接线拓扑、终端电阻与收发器工作逻辑2.1 菊花链总线拓扑而不是星型接法RS-485的总线结构应该是菊花链所有节点的A线串在一条主干上B线串在另一条主干上每个节点用很短的“支线”接入主干。这里最容易犯的错是把485接成星型也就是主机在中间每个从机分别拉一对长线到主机。星型接法在短距离低速时或许能凑合但在稍长距离上会非常不稳定。原因出在信号反射。高速信号在传输线末端或分支处遇到阻抗突变会产生反射波反射回来的信号叠加到原始波形上轻则让波形边沿畸变重则直接造成误码。分支线越长、分支点越多反射越明显。所以从物理结构上就要尽量避免长支线理想状态就是一根总线从头穿到尾每个节点只出很短一段线到总线。实验室里用杜邦线短距离测试感觉不到差别一到现场几十上百米拓扑问题就会集中爆发。2.2 120欧姆终端电阻为什么是120而不是其他值一篇485文章如果不提终端电阻等于没写。在很多入门者看来电阻就是限流的终端电阻却另有用处它在总线两端吸收信号能量阻止反射。双绞线的特性阻抗通常在100到120欧姆左右信号到达导线末端如果遇到高阻或开路能量没地方去就会被反射回来。如果在末端并接一个与传输线特性阻抗相等的电阻信号能量就会被电阻吸收不再形成反射。这个值大约就是120欧姆因此485规范推荐在总线两端、且只能是两端各接一只120欧电阻。中间节点不需要接接多了会拉低信号的差分幅度。实验桌面上短线传输时不接终端电阻往往也能跑通很多初学者因此忽略它。但总线一旦拉长或者波特率提高终端电阻就是必须品。正确的做法是先确定哪两个节点在物理最两端在这两个节点的A-B之间分别并接120欧其余节点一律不接。买现成模块时很多485模块板上会有一个跳线帽或焊盘来切换终端电阻方便做实验时按需开启。2.3 MAX485类收发器芯片的工作真相STM32的UART引脚本身输出的是TTL电平没法直接驱动485总线因此中间必加一颗收发器芯片市面上最常见的是MAX485及其各种兼容型号。这类芯片的内部结构可以看成两半一半是发送驱动器负责把DI引脚输入的TTL电平转换成A、B差分信号另一半是接收器负责把A、B差分信号还原成TTL电平从RO引脚输出。芯片有两大控制引脚DEDriver Enable置高时发送驱动器使能芯片向总线输出信号REReceiver Enable置低时接收器使能芯片从总线读信号。很多模块把DE和RE连在一起再用一个GPIO控制GPIO输出高电平 DE为高、RE为高进入发送模式GPIO输出低电平 DE为低、RE为低进入接收模式。这样一根控制线就能切换方向。MAX485这种芯片管脚很少8个脚就能干活常规排列是RO、RE、DE、DI、GND、A、B、VCC。还有个容易误解的点总线空闲时A和B之间应该处于逻辑1状态也就是A略高于B。因为UART协议规定空闲位是高电平起始位是低电平所以总线的“无信号”状态必须维持在逻辑1否则接收端会不断触发起始位检测。如果总线上没有任何设备发送、又没有偏置电阻固定电平A-B差分电压可能漂在正负200mV阈值附近接收端就会乱输出表现为“莫名收到一堆0xFF”或持续噪声。后面顺带提一句硬件问题就是给A加轻微上拉、B加轻微下拉让总线在空闲时稳定停在逻辑1。2.4 STM32与485模块的接线速查以最常见的STM32F103用USART1为例与485模块的对应关系非常固定STM32引脚485模块引脚说明PA9USART1_TXDI数据发送进收发器PA10USART1_RXRO数据从收发器读出任意GPIO例如PB0DE/RE控制方向高发送、低接收GNDGND信号地3.3V或5VVCC根据模块供电需求选择很多现成的485模块带稳压和偏置电路VCC可以直接接5V或3.3V但要注意ST官方的UART引脚是TTL电平匹配电压范围。如果是MAX485这类裸芯片VCC可以接3.3V也可以接5V但要确保DI和RO的电平范围和STM32兼容——3.3V供电的裸芯片一般能和STM32直连5V供电时最好查一下引脚是否容忍3.3V输入。接线方向也要强调一句485没有“TX接RX”的交叉概念两端都是A对A、B对B。这跟RS-232的交叉接法完全不同初次实验最容易在这步翻车。如果两个设备都定义好了A、B实际连上去却不通先怀疑是不是某个模块把A、B标反了把A、B两根线对调再试往往瞬间就通。3. 实验台搭建与硬件踩坑实际接线的关键细节3.1 模块选型与面包板搭建学习阶段最省事的方案是买成品485转TTL模块几块钱一个上面有MAX485芯片、120欧终端电阻焊盘、还有A/B接线端子。这种模块拿来验证原理非常合适但我不建议一直停留在“插模块点灯”的层面。如果想把原理吃透可以买几片裸MAX485配合面包板自己搭电路芯片的DI接STM32的TXRO接STM32的RXDE和RE合并后接一个GPIOA和B之间必要时焊一个120欧电阻。自己搭一遍的好处是下次看到成品模块的丝印时心里有底不会连芯片引脚做什么用都搞不清。实验台的环境往往比现场“宽容”得多线短、干扰小、供电干净所以某些错误在现场才会暴露。反过来如果实验台上就调不通那问题多半出在基本接线或代码逻辑上不是玄学。3.2 多节点链路怎么搭如果要做“一主多从”实验最少也得三块板子一块当主机两块当从机。接线还是老规矩三块板的A并联在一根线上B并联在一根线上GND连在一起。注意这里GND必须连就算485是差分传输收发器芯片本身的工作参考地还是要共的只是它对两地电位差有了更高的容忍度。不共地时A/B上的共模电压可能超出芯片承受范围轻则误码重则烧芯片。多节点实验还要注意模块供电。如果每块板都独立用USB供电三个USB口的GND之间可能有电位差建议实验时从同一个电源轨供电或者把几个节点的GND明确连起来。有些初学者图省事每块板都用自己电脑的USB口结果两片模块之间A、B、GND全连上测试时发现通信时好时坏多半就是多个USB地造成的问题。3.3 供电、共地与隔离的取舍实验室里做485共地是底线。到了工程现场情况又不一样设备之间的地电位差可能很大甚至存在电力系统接地故障时的瞬态电压。这时候靠共地硬扛是不现实的专业做法是使用带隔离的RS-485收发器比如芯片内部集成磁耦隔离的方案或外部加DC-DC隔离电源再配合光耦/数字隔离器让A、B总线与单片机系统之间没有电气连接。隔离缓冲了共模电压也切断了干扰电流流入控制板的路径。这个话题在学习阶段不用展开太多但要去现场做项目时必须知道长线485并不是“线接对就好”它涉及系统接地、等电位连接、隔离设计。项目前期的成本大头往往不是芯片而是把隔离和防护做好。4. 软件实现UART收发与方向切换的时机把控4.1 半双工方向控制的本质硬件解决了电平问题软件只做一件事在正确的时间点上切换DE方向。因为485半双工总线是共享的同一时刻只能有一个节点在发送。主机平时处于接收状态DE保持低电平当要发数据时先把DE拉高进入发送数据发送完毕后必须再等最后一个停止位真正从引脚上“走完”才能把DE拉低切回接收。听起来简单踩坑的人却不少。不少人直接在HAL_UART_Transmit返回后就拉低DE结果发现每次发的最后一两个字节丢失或者对端收到的是错误数据。原因在于HAL库的发送函数返回时机和你以为的“发送完成”不是一回事。调用返回时数据可能已经从UART数据寄存器交给了移位寄存器但移位寄存器里的最后几位还没有完全移位输出。此刻立刻拉低DE收发器被禁用最后的停止位还没发完就被硬生生掐掉了。4.2 发送函数与TC标志正确的做法是发送结束后等待UART的TCTransmission Complete标志置位这个标志表示移位寄存器已经彻底空掉所有数据位和停止位都已发送完。之后再拉低DE才安全。参考实现#define RS485_DE_PORT GPIOB #define RS485_DE_PIN GPIO_PIN_0 static void RS485_DE_Write(uint8_t level) { HAL_GPIO_WritePin(RS485_DE_PORT, RS485_DE_PIN, level ? GPIO_PIN_SET : GPIO_PIN_RESET); } void RS485_Send(uint8_t *buf, uint16_t len) { RS485_DE_Write(1); HAL_UART_Transmit(huart1, buf, len, 200); while (__HAL_UART_GET_FLAG(huart1, UART_FLAG_TC) RESET) { } RS485_DE_Write(0); }如果坚持在某些场合用延时方案延时必须大于一个字节的传输时间。以9600波特率、10位算一帧起始位8数据位停止位为例一个字节约1.04毫秒115200波特率下约87微秒。给它两到三个字节的余量基本可靠但最稳的还是等TC标志。再补充一个细节DE切换本身需要时间收发器芯片的数据手册会给出从DE使能到输出稳定的传播延迟。如果总线上的第一字节出现异常往往需要在这个位置加几十微秒的准备时间。合适的处理是先拉高DE、稍微延时一个字节时间的几分之一再调用HAL_UART_Transmit。4.3 接收单字节中断与DMA空闲中断接收方向通常有两种做法。做法一单字节接收中断。每次只接收一个字节在回调里把字节存进缓冲区。逻辑简单、容易理解缺点是每个字节都触发一次中断波特率高或数据量大时占用CPU比较多。uint8_t rx_byte; uint8_t rx_buf[256]; volatile uint16_t rx_pos 0; volatile uint8_t frame_ready 0; int main(void) { // ... HAL_UART_Receive_IT(huart1, rx_byte, 1); while (1) { if (frame_ready) { frame_ready 0; // 处理rx_buf里的完整帧 } } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { rx_buf[rx_pos] rx_byte; if (rx_byte 0x0A) { // 示例以换行符作为一帧结束 frame_ready 1; rx_pos 0; } else if (rx_pos sizeof(rx_buf)) { rx_pos 0; } HAL_UART_Receive_IT(huart1, rx_byte, 1); } }做法二DMA 空闲中断。让DMA自动把收到的所有数据搬进一个环形数组当芯片检测到总线上出现一段空闲即一帧数据结束时触发IDLE中断在中断里计算DMA还差多少没填从而得到本次帧长度。这种方式中断频率低、CPU开销小是串口项目里比较成熟的方案。#define RX_BUF_SIZE 128 uint8_t dma_rx_buf[RX_BUF_SIZE]; volatile uint16_t dma_rx_len 0; volatile uint8_t dma_frame_ok 0; void RS485_RxStart(void) { HAL_UART_Receive_DMA(huart1, dma_rx_buf, RX_BUF_SIZE); __HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE); } void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE) ! RESET) { __HAL_UART_CLEAR_IDLEFLAG(huart1); HAL_UART_DMAStop(huart1); dma_rx_len RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1-hdmarx); dma_frame_ok 1; } HAL_UART_IRQHandler(huart1); }收到完整帧后主程序判断dma_frame_ok标志解析数据然后调用RS485_RxStart重新开启下一次接收。这里有个细节如果一帧恰好把DMA缓冲区占满IDLE中断不会在缓冲区末尾出现这时候需要另加DMA传输完成中断做保护。实验阶段可以先把RX_BUF_SIZE设成大于最大帧长度问题就规避了。4.4 部分STM32片内RS-485硬件方向控制最后提一个“进阶选项”。部分较新的STM32系列比如F0、G0、L4等USART外设内置了驱动器使能控制功能配置USART_CR3中的DEM位并设定DEP极性再通过DEAT/DEN这些预分频寄存器设置提前量和释放时间芯片就能自动在发送前拉高RTS引脚、发送结束后自动拉低完全由硬件完成485方向切换。这个功能的好处是发送方向切换和波特率、帧长度严格同步不受中断响应延迟影响。如果你用的芯片支持强烈建议翻一下参考手册按寄存器说明配置一次。对F103这种老型号没有该功能老老实实GPIO控制也不会有什么问题只是要更注意时机。5. 实验现象分析与排查从自发自收到多机通信5.1 第一步先做自发自收在连接任何其他设备之前先做回环测试。最简单的做法是把一块485模块的A和B短接或者模块在发送时同时把自己的收发通道接到一起。单片机上电后让函数不断发送一串固定数据同时开启接收如果USART收到的内容和发送内容完全一致说明GPIO方向控制、收发器芯片、UART链路都正常。回环测试是分治排查的第一步。如果回环都收不到问题基本集中在单片机本身或模块接线如果回环正常、但接上另一块板就收不到问题多半出在双端接线、A/B顺序、终端电阻或共地上。这个排查顺序能节省大量时间。5.2 双机与多机通信体验双机实验时把两块板的A连A、B连B、GND连GND一主一从。主机发送查询指令从机收到后延时几十毫秒再回复。这段回复前的延时是必须的主机发完数据要切回接收状态从机收到后要解析、拼装响应帧这些都需要时间如果从机响应太快主机可能还没完成方向切换。我在实验里常用的从机回复前延时是10到20毫秒既保证了时序稳定又不会让总线上空闲太久。多机实验时给每个从机分配不同地址主机按地址轮询。观察到的典型现象是只接一块从机时通信稳定同时接两块从机后偶尔会出现应答乱码。原因往往不是软件而是总线末端没有接120欧终端电阻或者某根支线太长。加终端电阻、缩短支线后问题一般会消失。5.3 常见故障排查表调试过程中积累的一些典型问题整理成表故障现象可能原因排查方向完全无通信A/B接反、模块供电不对、DE控制错误查接线和GPIO电平用万用表量A/B电压收到乱码波特率不一致、未共地、干扰过大核对波特率确认GND连接检查终端电阻收尾几个字节丢失DE切换过早停止位被截断等TC标志后再拉低DE第一字节异常DE拉高后准备时间不足发送前增加短延时从机间互相干扰总线冲突、多个节点同时发送检查轮询时序从机必须错峰响应空闲噪声总线无偏置A/B电平漂移给A加轻上拉、B加轻下拉或并终端电阻距离稍长就出错支线过长、终端电阻缺失改菊花链拓扑仅在两端接120欧6. 从实验到项目波特率、协议与工程注意点6.1 波特率与线路长度的取舍实验桌上9600和115200看不出差别但项目里必须取舍。485距离越长可用的波特率越低这是信号在电缆上衰减和分布电容共同决定的。一个比较保守的经验值1200米左右用9600波特率比较稳妥几百米内可以用38400或57600几十米的短距离才建议上115200。具体能跑多高还取决于线缆质量和现场干扰所以项目选型时不要拍脑袋最好在最长的实际线缆上实测误码率。此外数据帧格式建议统一用8位数据、无校验、1位停止位这也是Modbus RTU最常用的组合。如果数据对可靠性要求高可以考虑9位数据加校验位但很多现成设备不支持通用性不如8N1。6.2 应用层协议给数据一个“框”RS-485只规定电气层不管数据怎么组织。一个完整帧至少要包含设备地址、功能码、数据长度、数据内容、校验码。主机先发地址只有地址匹配的从机才响应这就是485总线最简单的多机寻址方式。校验用CRC16比较普遍很多人在做Modbus RTU时直接用它的CRC算法因为它已经是事实上的工业标准格式。Modbus RTU是我强烈建议学习的协议。它定义了帧格式、功能码如03读保持寄存器、06写单个寄存器、CRC16校验方法各种触摸屏、PLC、组态软件原生支持上位机直接配置就能通信。实验时先在STM32上实现一个只支持03和06的Modbus从机再用电脑串口助手发Modbus帧能通整个485链路就算真正打通了。如果做产品第一步就是“通信能按统一协议互相理解”这比“能收到字节”高出整整一个层级。6.3 项目级经验与个人体会做485相关的实际项目有几个经验值得提前分享。第一485收发“安静”也很重要。主机轮询完一圈没有应答时不能一直让总线处于负载状态。设计上要有超时机制比如等待应答超过50毫秒就判定失败换下一台从机避免卡死在某个掉线设备上。第二接线要有“可维护性”。每块板子的A、B、GND端子要标注清晰DE控制引脚旁预留测试点。调试时用示波器同时看A-B差分波形和DE电平一眼就能判断是软件没切换方向还是硬件没上总线。这比反复改代码猜原因高效得多。第三隔离和保护放到架构里考虑。实验台用共地足够但产品要到现场线缆可能经过强电区域、甚至户外隔离收发器和防护器件不是可选项是必选项。所有保护措施都应在原理图阶段留出位置。把485通信原理和实验完整走一遍后你会发现它本身并不神秘差分信号解决干扰半双工靠GPIO切方向协议自己定注意时序和拓扑就能把链路调稳。学到这后面再接触CAN、以太网这些更复杂总线时很多底层逻辑其实是相通的。