我们之前在嵌入式项目里用串口通信,大多接触的是 TTL 电平的 UART 或者 RS232。一旦进入工业现场,设备之间的距离拉到几十米甚至上百米,再走普通串口就很容易出问题。这种情况下,RS485 几乎成了事实标准。本文基于 STM32 平台,完整拆解 RS485 总线搭建流程,内容包括通信原理、硬件电路设计、STM32 软件代码实现、工业布线规范和常见问题排查。如果你正在做毕业设计、设备联网改造或者工业控制项目,这篇文章可以作为一份可以直接参考的实操笔记。
1. RS485 是什么:从串口到工业总线
1.1 为什么需要 RS485
单片机与单片机之间最简单的通信方式就是 UART 串口,一根发送线、一根接收线,加上共地就可以完成数据交换。但这种方式有几个明显的限制:
- 传输距离短,一般 TTL 电平只能走几十厘米到几米;
- 抗干扰能力弱,噪声稍大就容易误码;
- 只能点对点通信,很难实现一主多从的组网结构;
- 电平标准不统一,设备之间可能因为电平不兼容导致通信失败。
RS485 就是为了解决这些问题而设计的。它采用差分信号传输,使用一对双绞线(A、B)传递数据,通过两条线之间的电压差表示逻辑 0 和逻辑 1。由于抗共模干扰能力强、传输距离远、支持多节点挂接,RS485 在工业自动化、楼宇自控、电力监控、环境监测等领域应用非常广泛。
1.2 RS485 的核心通信特性
RS485 标准规定了电气特性,但并没有规定具体的通信协议。也就是说,RS485 只负责物理层的信号传输,至于数据怎么封装、设备怎么寻址,需要靠上层协议来实现。
| 特性 | 说明 |
|---|---|
| 传输方式 | 差分信号,半双工 |
| 传输距离 | 理论最大 1200 米(实际受波特率和线缆质量影响) |
| 节点数量 | 标准收发器支持 32 个节点,使用高输入阻抗芯片可扩展更多 |
| 电平标准 | A-B 电压差大于 +200mV 表示逻辑 1,小于 -200mV 表示逻辑 0 |
| 接线方式 | A、B 两根差分线,通常还需要一根参考地 |
| 拓扑结构 | 手拉手总线型,避免星型连接 |
1.3 RS485 与 RS232、TTL 的区别
很多初学者容易把这三个概念混在一起。做一个简单的对比:
| 项目 | TTL 电平 | RS232 | RS485 |
|---|---|---|---|
| 电平标准 | 0~3.3V/5V | -15V~+15V 负逻辑 | 差分电压 |
| 传输距离 | 几米以内 | 约 15 米 | 可达 1200 米 |
| 通信方式 | 全双工 | 全双工 | 半双工 |
| 组网能力 | 点对点 | 点对点 | 多点总线 |
| 抗干扰能力 | 弱 | 一般 | 强 |
STM32 的 UART 外设输出的是 TTL 电平,必须通过 RS485 收发器芯片转换成差分信号才能接到总线上。这也是为什么我们做实验时需要外接一个 MAX485、SP3485 之类的芯片。
2. RS485 总线架构与关键概念
2.1 半双工通信与收发切换
RS485 是半双工通信,同一时刻只能有一个设备发送数据,其他设备处于接收状态。因此每个节点的收发器芯片都有发送和接收两个模式,通过 DE(Driver Enable)引脚控制。
- DE 为高电平时,芯片处于发送模式,A、B 输出差分信号;
- RE 为低电平时,芯片处于接收模式,A、B 上的差分信号被转换为 TTL 电平送到 STM32 的 RX 引脚。
常见做法是把 DE 和 RE 连在一起,由 STM32 的一个 GPIO 控制。发送数据前拉高该引脚,发送完毕后拉低,恢复接收状态。
2.2 终端电阻的作用
RS485 总线的特性阻抗一般为 120Ω。当信号传输到总线末端时,如果末端阻抗不匹配,信号会发生反射,导致通信波形畸变、误码率升高。解决方法是总线两端各接一个 120Ω 终端电阻。
注意两个要点:
- 终端电阻只需要在总线物理末端接,中间节点不需要接;
- 如果总线较短(比如实验桌上几米内),可以不接终端电阻也能正常工作,但长距离传输建议按标准接法来。
2.3 多点通信与设备寻址
RS485 支持一主多从架构。主设备发送带地址的指令帧,从设备收到后判断地址是否匹配。匹配则响应,不匹配则忽略。这意味着每个从设备必须有一个唯一的地址,通信协议中需要明确地址字节、功能码、数据和校验字段。
最简单的做法是参考 Modbus RTU 协议,帧格式通常包含:
从机地址 + 功能码 + 数据 + CRC 校验如果只是做两个设备之间的点对点通信,不涉及多机寻址,可以简化协议,直接收发原始数据即可。
3. STM32 RS485 通信硬件搭建
3.1 常用收发器芯片选型
STM32 系列开发板和工业板上最常见的 RS485 收发器是 MAX485 和 SP3485。
| 芯片 | 工作电压 | 说明 |
|---|---|---|
| MAX485 | 5V | 经典芯片,工业应用广泛 |
| SP3485 | 3.3V | 可直接由 STM32 供电,无需电平转换 |
| MAX3485 | 3.3V | 与 SP3485 类似,国产替代也很常见 |
如果你的 STM32 最小系统板是 3.3V 供电,建议优先选择 SP3485 或 MAX3485,省去电平转换的麻烦。如果使用 5V 供电的 MAX485,需要确认 STM32 引脚否能承受 5V 电平,或者增加电平转换电路。
3.2 经典 RS485 电路设计
一个典型的 STM32 + SP3485 电路包含以下几个部分:
- UART 的 TX 引脚连接到芯片的 DI 引脚;
- UART 的 RX 引脚连接到芯片的 RO 引脚;
- 一个 GPIO 控制 DE/RE 引脚;
- A、B 引脚接总线,通常还会加上拉/下拉电阻和终端电阻;
- 电源端加去耦电容。
下面是一个参考电路:
STM32 PA9 (USART1_TX) --> SP3485 DI STM32 PA10 (USART1_RX) --> SP3485 RO STM32 PA8 (GPIO) --> SP3485 DE/RE SP3485 A 引脚 --> 总线 A SP3485 B 引脚 --> 总线 B SP3485 VCC --> 3.3V SP3485 GND --> GND在 A、B 输出端,一般还会预留终端电阻焊盘,方便根据实际总线长度焊接 120Ω 电阻。
3.3 保护电路怎么加
工业现场环境复杂,雷击、静电、电机启停产生的浪涌都可能通过总线进入设备。保护电路至少要考虑以下几点:
- TVS 管:在 A、B 对地之间加 TVS 管,吸收瞬态过压;
- 气体放电管:用于更高能量的雷击防护,通常与 TVS 搭配使用;
- 光耦隔离:如果现场干扰严重,可以使用光耦将 STM32 与 RS485 芯片隔离,避免地电位差损坏单片机;
- 共模电感:抑制共模干扰。
对于实验性质的开发板,通常只在 A、B 端加两个 TVS 管就足够了。量产工业设备建议隔离方案搭配 TVS + 气体放电管。
3.4 自动收发电路说明
有些模块上集成了自动收发切换电路,通过 RC 延时电路自动判断发送和接收状态,不需要 GPIO 控制 DE/RE。这种电路的优势是软件简单,但劣势是波特率较低时可能出现切换不及时,导致帧尾数据丢失。本文后面的代码以 GPIO 控制 DE/RE 为例,这是最稳妥的工程做法。
4. STM32 RS485 通信软件实现
这一部分以 STM32F103 系列 + HAL 库为例,演示 RS485 的基本收发流程。用的开发环境是 STM32CubeMX + Keil MDK,不同芯片型号和库版本的配置思路是一样的,重点理解收发切换逻辑。
4.1 CubeMX 配置
打开 STM32CubeMX,新建工程选择你的芯片型号,然后按下面的方式配置:
- RCC 选择 HSE 外部晶振;
- SYS 选择 Serial Wire(如果使用 SWD 调试);
- USART1 设置为异步收发模式,波特率根据实际需求填写,常见值有 9600、19200、115200;
- 在 GPIO 设置里,将 PA8 配置为 Output Push Pull,作为 DE/RE 控制引脚;
- 时钟树按板载晶振配置,生成工程代码。
4.2 发送数据实现
RS485 发送数据前需要拉高 DE/RE 引脚,进入发送模式,然后调用 HAL_UART_Transmit 发送数据,发送完成后拉低引脚,恢复接收模式。
// 文件路径:Core/Src/rs485.c #include "rs485.h" #include "main.h" extern UART_HandleTypeDef huart1; #define RS485_DE_GPIO_Port GPIOA #define RS485_DE_Pin GPIO_PIN_8 void RS485_SetMode(uint8_t mode) { HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, mode ? GPIO_PIN_SET : GPIO_PIN_RESET); } uint8_t RS485_SendData(uint8_t *data, uint16_t len) { RS485_SetMode(1); // 进入发送模式 HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, data, len, 100); RS485_SetMode(0); // 恢复接收模式 return (status == HAL_OK) ? 0 : 1; }这里把 DE/RE 控制封装成了 RS485_SetMode 函数,发送前置高、发送后置低。注意发送完数据后让引脚保持一小段时间再切换回接收模式,实际项目中可以根据波特率计算微延时,确保最后一个字节完全发送出去。
4.3 接收数据实现
接收数据的写法有两种:
- 轮询方式:不断检查 RXNE 标志位,收到一字节就存入缓冲区;
- 中断方式:开启 UART 接收中断,数据到达后自动进入中断回调函数。
中断方式效率更高,下面给出一个基于 HAL 库中断接收的例子。
// 文件路径:Core/Src/main.c 中的关键片段 uint8_t rx_buffer[64]; uint8_t rx_index = 0; volatile uint8_t rx_complete_flag = 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { if (rx_index < 63) { rx_buffer[rx_index] = received_byte; rx_index++; HAL_UART_Receive_IT(&huart1, &received_byte, 1); } else { rx_complete_flag = 1; } } }在 main 函数中需要先启动一次中断接收:
uint8_t received_byte; HAL_UART_Receive_IT(&huart1, &received_byte, 1);每收到一个字节,HAL 库会自动调用回调函数。这种做法适合不定长数据的接收,可以根据帧头帧尾或超时时间判断一帧数据是否接收完毕。
需要注意的是,HAL 库的接收回调是每次收到指定数量字节后才触发。这里指定长度为 1,所以每来一个字节触发一次。对于比较长的数据帧,需要在回调函数里判断帧是否结束,例如通过空闲中断、定时器超时或者帧尾标志。
4.4 自定义通信协议示例
为了演示一个完整流程,假设我们做一个简单的协议:
帧头:0xAA 0x55 地址:1 字节 命令:1 字节 数据长度:1 字节 数据:N 字节 校验:1 字节,前面所有字节的累加和发送端代码示例:
// 文件路径:Core/Src/protocol.c #include "protocol.h" #include "rs485.h" uint8_t protocol_buf[128]; void Protocol_SendFrame(uint8_t addr, uint8_t cmd, uint8_t *data, uint8_t len) { uint8_t i; uint8_t checksum = 0; uint8_t index = 0; protocol_buf[index++] = 0xAA; protocol_buf[index++] = 0x55; protocol_buf[index++] = addr; protocol_buf[index++] = cmd; protocol_buf[index++] = len; for (i = 0; i < len; i++) { protocol_buf[index++] = data[i]; checksum += data[i]; } checksum += addr + cmd + len; protocol_buf[index++] = checksum; RS485_SendData(protocol_buf, index); }接收端解析代码示例:
// 文件路径:Core/Src/protocol.c uint8_t Protocol_ParseFrame(uint8_t *buffer, uint16_t size, uint8_t *addr, uint8_t *cmd, uint8_t *data) { uint16_t i; uint8_t checksum = 0; uint8_t data_len; if (size < 6) { return 0; } if (buffer[0] != 0xAA || buffer[1] != 0x55) { return 0; } *addr = buffer[2]; *cmd = buffer[3]; data_len = buffer[4]; for (i = 0; i < data_len; i++) { data[i] = buffer[5 + i]; checksum += data[i]; } checksum += buffer[2] + buffer[3] + data_len; if (checksum != buffer[5 + data_len]) { return 0; } return 1; }这个协议虽然简单,但已经把寻址、命令、长度和校验都包含了。工程中如果要挂多个从机,可以在此基础上扩展 Modbus RTU 协议,原理一致。
4.5 连接两个 STM32 进行实验
要验证 RS485 通信是否成功,最简单的方式是准备两块 STM32 开发板,分别接 RS485 模块,A 接 A、B 接 B,两块板子的 GND 也要相连。
实验流程:
- 主机发送一个固定字节串,例如
0x01 0x03 0x00 0x00; - 从机收到后原样返回;
- 主机收到回包后,通过串口打印到调试助手,确认通信正常。
如果没有两块开发板,也可以使用 USB 转 RS485 模块,配合电脑串口调试助手进行测试。PC 端发送数据给 STM32,STM32 收到后打印或回显。
5. 工业布线指南:RS485 总线工程的坑
5.1 总线拓扑结构
RS485 最推荐的总线拓扑是手拉手菊花链结构,也就是从主站到从站 1,再从从站 1 到从站 2,依次连接。这种结构信号反射最小,通信最稳定。
不推荐的接法:
- 星型拓扑:多个分支同时从主站引出,信号在分支节点反射严重;
- 树型拓扑:存在多条分支线缆,阻抗不连续;
- 环型拓扑:可能导致信号通路异常,一般不用于 RS485。
如果现场已经存在星型接线,尽量将分叉线缆缩短到最短。条件允许时,可以在每个分支末端加终端电阻,但这不是标准做法。
5.2 线缆选择
RS485 推荐使用特性阻抗 120Ω 的屏蔽双绞线。双绞线可以减少共模干扰,屏蔽层可以降低外部电磁干扰的影响。
选型参数建议:
- 线径:0.5mm² 以上,长距离布线建议 0.75mm² 或更粗;
- 屏蔽层:单端接地或通过电容接地,避免形成地环路;
- 颜色区分:A 线用一种颜色,B 线用另一种颜色,并在两端做好标签。
注意不要使用平行线代替双绞线,也不要将 RS485 信号线与动力线放在同一根线管中,否则干扰会非常明显。
5.3 接地问题
RS485 在短距离实验时经常不接 GND 也能工作,这是因为 A、B 差分信号本身有较强的共模抑制能力。但长距离通信时必须接一根信号地,目的是保证各个节点的参考地电位一致,避免共模电压超过收发器的允许范围。
正确的接地做法:
- 每个节点使用单点接地,避免形成地环路;
- 屏蔽层在主机侧单端接地;
- 如果现场存在较大的地电位差,建议使用带隔离的 RS485 模块。
5.4 波特率、线长与带载能力
RS485 的传输距离和波特率成反比:
- 9600bps 时,理论最远可达 1200 米;
- 115200bps 时,建议距离控制在 100 米以内;
- 更高波特率下,距离还要进一步缩短。
节点数量受收发器芯片的输入阻抗影响,标准负载是 32 个节点。如果使用 1/4 负载的芯片(如 MAX485 的某些型号),最多可挂 128 个节点。但实际项目中,节点越多,总线电容越大,波形越差,所以不建议把节点数用满。
5.5 布线时的其他建议
- 总线主干线尽量走直线,避免急弯和过长的支线;
- 所有节点的 A、B 不要接反,否则通信完全不通;
- 每个节点预留地址拨码开关,便于后期维护;
- 使用防雷器时,注意防雷器的接地端子要可靠接地,并尽量靠近设备安装。
6. 常见问题与排查思路
6.1 通信完全不通,示波器看不到波形
可能的原因:
- A、B 接反;
- 未共地;
- DE/RE 引脚没有正确切换;
- 波特率不一致;
- 芯片供电异常。
排查顺序:
- 检查电源电压是否在芯片允许范围内;
- 用万用表测量 A、B 之间的静态电压,正常值应在 0V 左右;
- 检查 STM32 GPIO 配置是否为推挽输出;
- 确认主机和从机的波特率、停止位、校验位一致;
- 用示波器观察 A、B 对 GND 的波形,看发送时是否有差分信号。
6.2 能收到数据但内容乱码
| 可能原因 | 解决思路 |
|---|---|
| 波特率不一致 | 重新确认两端波特率,包括上位机软件设置 |
| 校验位不一致 | 统一数据位、停止位、校验位 |
| 线缆过长或干扰大 | 降低波特率,检查屏蔽层接地 |
| 终端电阻未匹配 | 长距离传输时两端各接 120Ω 电阻 |
| 地电位差过大 | 检查各节点之间是否可靠共地 |
6.3 多机通信时数据互相干扰
当总线上挂了多个从机时,最常见的问题有两个:
- 从机地址冲突:检查每个从机的地址拨码是否唯一;
- 从机主动上报数据:从机不应该主动向总线发数据,只能被动响应主机请求。
在软件上,主机的发送帧必须有明确的地址字段,从机收到非本机地址的帧时直接丢弃。
6.4 使用 USB 转 RS485 工具时提示传输格式不正确
这种情况通常出现在使用串口调试助手时,软件要求设置数据格式。解决方法是:
- 确认波特率与 STM32 一致;
- 数据位选择 8;
- 停止位选择 1;
- 校验位选择 None。
如果工具支持 RTS/DTR 控制,检查是否误开了自动收发控制功能,导致模块一直处于发送或接收锁定状态。
6.5 总线上个别节点掉线
掉线原因通常是该节点的电源电压不稳、接触不良或者芯片损坏。可以优先检查该节点的供电,再测量其 A、B 端接线是否牢固,最后用替换法排查收发器芯片是否损坏。
7. 最佳实践与工程建议
7.1 硬件设计建议
- 在 STM32 与 RS485 芯片之间预留 0Ω 电阻或磁珠位置,方便调试时断开;
- 在 A、B 线上预留 TVS 管焊盘;
- DE/RE 控制引脚不要复用其他功能,避免误触发;
- 如果项目成本允许,优先使用带隔离的 RS485 模块。
7.2 软件设计建议
- 发送数据后等待 TXE 标志位置位,确认数据发送完成后再切换 DE/RE;
- 接收数据建议使用空闲中断或 DMA + 空闲中断,避免丢帧;
- 协议层必须加校验字段,不能只依赖物理层的差分信号;
- 对于多从机系统,主机应该设置超时重发机制;
- 日志中记录每次通信的收发明细,便于定位问题。
7.3 生产环境注意事项
- 现场布线时使用不同颜色的线缆区分 A/B,并在两端贴上标签;
- 所有设备的外壳应可靠接地;
- 电源和通信线尽量远离变频器等强干扰源;
- 设备升级固件时,先断开 RS485 总线,避免初始化期间的误码影响其他节点;
- 定期检查接线端子,防止因震动导致松动。
8. 总结
RS485 在工业通信中的地位非常稳固,STM32 通过外接收发器芯片就能轻松接入 RS485 总线。核心要点可以归纳为四条:一是理解差分信号和半双工切换机制,二是电路设计时重视保护与匹配,三是软件上做好收发切换和协议校验,四是现场布线遵循手拉手拓扑并重视接地与屏蔽。把这四点落实到位,基本可以覆盖大部分 RS485 项目的开发需求。
如果你正在做 RS485 通信实验,可以先从一块板子自发自收开始验证硬件,再用两块板子做点对点通信,最后再扩展到多机 Modbus 通信。这个路径走完了,再做工业项目心里就有底了。
如果本文对你有帮助,可以收藏备用。后续我也会整理 Modbus RTU 协议移植、多机轮询调度、DMA 接收等更深入的内容,欢迎继续关注。