STM32F103 RS485串口通信实战:自动收发电路、组网与调试要点
2026/9/16 11:20:32 网站建设 项目流程

简介:面向STM32F103嵌入式开发者的RS485串口通信工程资源,包含完整的串口驱动、RS485方向控制及收发示例代码,适合工业现场远距离、抗干扰通信等应用场景的开发者参考,可用于入门学习或工程复用。压缩包共213个文件,以C语言源文件和头文件、启动文件、Keil工程文件为主,也包含编译生成的中间文件,整体约3.27兆字节,目录结构清晰,内含库函数、中断处理及系统配置文件,可直接在Keil中加载。已有1011人学习下载。内容以MAX485、SP3485等转换芯片为例,详细展示硬件接线、串口初始化(含波特率、数据位、停止位配置)、发送接收时的方向切换流程,并讨论循环冗余校验、握手协议、主从冲突避免、电气隔离等工程要点,可帮助入门者从原理到代码完整走通RS485通信链路,同时也能作为调试排错的参考模板。

1. STM32(F103)RS485串口通信:为什么实际项目中离不开它

在自动化设备现场,最常看到的不是 USB 转 TTL,而是两根绞在一起的 A、B 线。STM32F103C8T6 这类 Cortex-M3 主控要接入 RS485 总线,不能把 USART 引脚直接怼上去,因为 USART 输出的是 3.3V TTL 电平,而 RS485 需要差分信号,线缆动辄几十米。RS485 串口通信解决的就是远距离、多节点和抗干扰这三个问题,典型应用是变频器参数读写、电表采集、Modbus 传感器接入,以及带 RS485 接口的工业触摸屏。

如果把这个主题做成压缩包,里面通常是一个最小工程:串口初始化、收发例程和参考电路。但拿到例程之前,至少需要弄清自动收发电路怎么选、方向引脚为什么要在发送完成后释放、总线空闲时 A/B 电压为什么有要求。下面从硬件到代码,最后落到调试技巧,适合需要自己调板子的工程师,也适合把 RS485 作为毕业设计基础层的同学。

2. RS485 串口通信物理层与自动收发电路设计

RS485 标准规定 A、B 两线之间的差分电压:大于等于 +1.5V 时表示逻辑 1,小于等于 -1.5V 时表示逻辑 0。STM32F103 的 USART 是单端 TTL 电平,必须经过 MAX485、SP3485 这类收发器才能挂到总线上。收发器内部可以简单看成两部分:发送侧把 DI 的 TTL 转换成 A/B 差分,接收侧把 A/B 差分转换成 RO 上的 TTL。因此硬件设计的核心就是处理好“方向使能”。

2.1 TTL 转 RS485 的引脚对应与最小接线

以 MAX485 为例,推荐引脚对应关系如下:

MAX485 引脚功能说明接到 STM32F103
RO接收输出,A/B 差分转成 TTLUSART1_RX(PA10)
RE接收使能,低电平有效与 DE 短接后接方向控制脚
DE发送使能,高电平有效同上
DI发送输入,TTL 转差分USART1_TX(PA9)
A差分正确总线 A 线
B差分负端总线 B 线

RE 和 DE 如果分开接,可以做成收发独立,但 RS485 是半双工总线,绝大多数场景都是用同一个 GPIO 控制。也可以直接用 TXD 信号通过三极管或电容自动切换方向,这就是热词里常说的 RS485 自动收发电路。自动收发的好处是软件不用管方向,缺点是波特率较高、线缆较长时,方向切换的毛刺可能造成第一字节丢帧。我一般只在低速和短距离场景下用自动收发,距离超过 100 米就改成 GPIO 手动控制。

2.2 用 GPIO 控制 STM32F103 的 RS485 方向切换

初始化时把方向脚配成推挽输出,默认拉低让芯片处于接收状态。发送数据时先拉高 DE,再调用 USART 发送;发送完成后必须等 TC(Transmission Complete)标志置位,再拉低方向。如果只等 TXE(发送数据寄存器空),最后一个字节可能还没从移位寄存器发完,停止位会被切断,从机收到就是一帧乱码。

void RS485_SetDirection(uint8_t tx) { HAL_GPIO_WritePin(RS485_DIR_GPIO_Port, RS485_DIR_Pin, tx ? GPIO_PIN_SET : GPIO_PIN_RESET); } void RS485_SendBytes(uint8_t *buf, uint16_t len) { RS485_SetDirection(1); // 进入发送模式 HAL_UART_Transmit(&huart1, buf, len, 100); while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) ; // 等最后一个字节彻底发完 RS485_SetDirection(0); // 恢复接收模式 }

逻辑说明:前半段把方向脚拉高后,MAX485 的 DE 才有效,DI 上的 TTL 信号才能送到 A/B 总线。HAL_UART_Transmit 在数据寄存器为空时就返回,但移位寄存器可能还在发送停止位,所以必须用 TC 标志等待全部物理位流发完,再拉低方向回到接收态。参数说明里,RS485_DIR_Pin 是 CubeMX 生成的方向引脚宏;HAL_UART_Transmit 的超时写 100ms,在 9600 波特率下传 64 字节大约 60ms,100ms 够用。如果换成 DMA 发送,不能用 while 转圈等 TC,而要在 DMA 发送完成中断里拉低方向。

2.3 终端电阻和偏置电阻的参数调整

总线两端各并一个 120Ω 终端电阻,用于匹配特征阻抗,防止信号反射。短距离几米以内可以不加,但一主多从或者线缆较长时,缺少终端电阻会在 A/B 波形上看到明显的过冲和振铃。偏置电阻是另一个容易忽略的地方:当总线空闲、没有任何节点发送时,如果 A/B 之间没有压差,接收器输出不确定,串口会收到 0xFF 之类的乱码。常见做法是在 A 线上拉到 VCC,B 线拉到 GND,阻值取 620Ω 到 1kΩ,具体值需要根据设备数量重新计算并联等效电阻。

提示:用万用表测总线空闲时的 A-B 电压,应该落在 +0.2V 到 +5V 之间;如果接近 0V,就是偏置不够或没有偏置。

总线节点数偏置电阻参考值说明
2 ~ 161kΩ大部分 USB-RS485 模块内置
17 ~ 32620Ω降低并联等效阻抗
超过 32需要加中继器RS485 标准负载上限通常为 32 个

偏置电阻按“上拉电阻与下拉电阻串联后,再并联多个节点”来估。实际调试时只要保证 A-B 空闲电压稳定在 200mV 以上即可。后面要讲的组网和代码,都是基于上述电路能正常收发的前提。

3. 用 STM32F103 的 USART 实现 RS485 串口通信收发代码

物理层解决后,剩下的是 STM32F103 的 USART 配置。RS485 通信本身不要求特定协议,串口参数由收发双方约定。最常见的是 9600bps、8 数据位、1 停止位、无校验,写成串口助手里的“9600 8N1”。用 CubeMX 初始化时,重点不是把图形界面点完,而是理解每个参数和后续代码的互动。

3.1 STM32F103 串口初始化参数:波特率、停止位与校验

低速率下 8N1 最通用。下面这段初始化代码省略了 RCC 时钟配置,直接看关键项:

static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; // 与总线设备一致 huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } }

逻辑说明:USART1 时钟来自 APB2,最高 72MHz。高过采样 16 倍后,波特率计算是PCLK / (16 * div),所以 APB2 频率如果被改过,实际波特率会偏移。参数说明:WordLength 是 8 位数据,无校验时支持 8 位;Parity 一旦启用,数据位要配成 UART_WORDLENGTH_9B,因为校验位要占一位。很多“收发乱码”其实是 8E1/8O1 下配错 WordLength 造成的。

3.2 基于 HAL 库的方向控制与多字节发送

发送函数在第 2 章已经给出,这里补上接收侧处理。接收侧用中断或者 DMA,不能用阻塞轮询,因为总线上什么时候来数据不可控。开启接收中断的经典写法是每次收一个字节,再重新使能下一次接收:

uint8_t rs485_rx_buf[128]; volatile uint16_t rs485_rx_len = 0; void RS485_StartReceive(void) { RS485_SetDirection(0); HAL_UART_Receive_IT(&huart1, &rs485_rx_buf[rs485_rx_len], 1); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { rs485_rx_len++; HAL_UART_Receive_IT(&huart1, &rs485_rx_buf[rs485_rx_len], 1); } }

逻辑说明:HAL_UART_Receive_IT 每次只收一个字节,回调里继续使能下一次接收,这样每收到一字节都会触发一次中断。如果直接把长度设成 N,HAL 会在收满 N 字节后才回调,无法应对变长帧。参数说明:rs485_rx_len 是累计长度,需要在前一帧处理完后清零。这种逐字节方式在 9600 波特率下每字节约 1.04ms,中断开销可以接受,但主频紧张或者帧很长时更推荐 DMA。

3.3 用 DMA + 空闲中断接收不定长帧的改进方案

逐字节中断代码简单,但更稳的是 USART 空闲中断。空闲中断指总线在线路收到至少一个字节后出现一个字节时间的空闲电平,触发时机正好是 RS485 一帧数据结束后,天然适应变长报文。配合 DMA 接收,可以让硬件自动把数据搬进内存,IDLE 到来后检查 DMA 剩余计数即可得到帧长。

#define RX_BUF_SIZE 128 uint8_t dma_rx_buf[RX_BUF_SIZE]; void RS485_StartReceiveDMA(void) { HAL_UART_Receive_DMA(&huart1, dma_rx_buf, RX_BUF_SIZE); } void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); HAL_UART_DMAStop(&huart1); uint16_t len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); Process_RxFrame(dma_rx_buf, len); RS485_StartReceiveDMA(); } HAL_UART_IRQHandler(&huart1); }

逻辑说明:DMA 配置成普通模式,接收缓冲区长度为 RX_BUF_SIZE,每次启动后计数器从 RX_BUF_SIZE 往下减。空闲中断触发时,DMA 计数器剩余值就是未接收长度,用总长度减剩余值得到本次帧长度。HAL_UART_DMAStop 的作用是停止 DMA,防止下一次接收覆盖当前数据。参数说明:Process_RxFrame 是用户自己的协议处理函数,必须在清空缓冲区之前取走数据;hdma_usart1_rx 是 CubeMX 生成的 DMA 接收句柄,名称要和工程一致。

表格对比三种接收方式:

接收方式占用资源适合场景
逐字节中断每字节一次中断帧短、开发快
DMA 循环无中断,靠超时判断连续流数据
DMA + IDLE一帧一次中断RS485 不定长帧、Modbus

4. STM32F103 RS485 串口通信组网与一主多从实现

RS485 总线上可以挂多个节点,但同一时刻只能有一个节点发送,否则就是总线冲突。实际工程中用得最多的是“一主多从”轮询模式:只有主机在平时主动发命令,从机根据地址判断是否响应。这种模式比总线竞争简单,也适合 STM32F103 的资源。

4.1 一主多从的连接拓扑与地址分配

总线拓扑是一条主线,所有设备并联在同一对 A/B 线上。每个从机分配唯一地址,比如 1~247,地址 0 在 Modbus RTU 中属于广播地址,实际项目要慎用。轮询过程是主机先向从机地址 1 发请求,等从机回复,处理完后再问地址 2,直到所有从机被轮询一遍。从机不能主动往总线上发数据,否则会干扰正在进行的通信。从机端地址匹配可以写成:

#define RS485_ADDR_BROADCAST 0x00 #define RS485_ADDR_SLAVE 0x01 // 本机地址 uint8_t RS485_CheckAddress(uint8_t *buf, uint16_t len) { if (len < 4) return 0; if (buf[0] != RS485_ADDR_BROADCAST && buf[0] != RS485_ADDR_SLAVE) return 0; // 不是发给本机 return 1; }

逻辑说明:帧头第一个字节是地址。从机收到数据后先判断地址,不匹配就直接丢弃,不占用总线。参数说明:广播地址一般用于同时启动、同时复位,普通数据读写请求不要用,否则所有从机同时回包会造成总线冲突。

4.2 帧格式与 CRC16 校验

Modbus RTU 是最常见的 RS485 应用协议,帧格式为:地址(1) + 功能码(1) + 数据(N) + CRC16(2),CRC 低位在前。STM32F103 算 CRC16 很快,可以用按位算法省掉查表空间:

uint16_t RS485_CRC16(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *data++; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }

逻辑说明:多项式 0xA001 是 Modbus RTU 的 CRC-16 反写形式,逐位计算 8 个周期,最后得到的 crc 需要先发低字节再发高字节。参数说明:data 指向从地址开始的整帧,len 不含 CRC 本身。CRC 字节序写反是从机不响应的高频原因,调试时要先用串口工具对比标准计算器结果。

4.3 轮询调度与超时重传

多从机环境中,主机不能在发送请求后无限等下去。常见做法是设定响应超时,比如 100ms,超时重发 2 次,仍然失败就标记该节点离线,继续下一台。在 STM32F103 上可以用一个 SysTick 累计毫秒变量实现:

volatile uint32_t tick_ms; void RS485_PollSlave(uint8_t addr) { uint32_t start = tick_ms; uint8_t cmd[] = { addr, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 }; uint16_t crc = RS485_CRC16(cmd, 6); cmd[6] = crc & 0xFF; cmd[7] = crc >> 8; for (uint8_t retry = 0; retry < 3; retry++) { RS485_SendBytes(cmd, 8); rs485_rx_len = 0; while (tick_ms - start < 100) { if (rs485_rx_len >= 5) { /* 地址匹配且 CRC 校验正确后处理数据 */ return; } } } /* 标记从机离线 */ }

逻辑说明:超时判断用tick_ms - start,避免使用绝对时间导致回绕问题。rs485_rx_len 在空闲中断回调中被更新,主循环或任务里轮询。参数说明:100ms 超时在 9600 波特率下足够,但在 4800 波特率下要加大到 200ms 以上,因为每字节耗时从 1.04ms 变成 2.08ms,8 字节请求加 5 字节响应需要约 27ms,留出余量更安全。

波特率每字节约耗时100ms 能收字节数推荐响应超时
96001.04ms96100ms
48002.08ms48200ms ~ 300ms
1152000.087ms114920ms ~ 50ms

5. 调试 RS485 串口通信时的三个测量点与低波特率坑

5.1 用万用表和示波器验证自动收发电路

自动收发电路好不好,最直接的验证办法是让设备发送 0x55,用示波器同时测量 DI 和 A-B 差分波形。0x55 会产生 01010101 的码型,A-B 波形应能看到清晰的方波翻转。如果波形前几个位起点偏移,说明自动收发切换太慢。没有示波器时,用万用表测总线空闲时的 A-B 电压,稳定在 0.2V~5V 是及格线,低于 0.2V 优先补偏置电阻。

5.2 排查“9600 能通,4800 没数据”的问题

把波特率从 9600 改到 4800 后没有数据,多数不是参数写错,而是系统时钟源不准。如果 STM32F103 使用 HSI 内部时钟而不是外部晶振,9600 波特率误差可能刚好在容限内能通,降到 4800 后分频系数非整数,误差反而变大。解决办法是改用外部 8MHz 晶振并确认 APB2 时钟为 72MHz;若已经用外部晶振,则检查USART_BRR计算值是否接近整数。

5.3 丢帧时先测停止位波形

RS485 通信偶发丢帧,不要急着改协议。用示波器看 A 对 B 的差分波形,重点看停止位是否完整、空闲电平是否有毛刺。如果发送端用 GPIO 控制方向,检查是否等 TC 后再切方向;如果使用 DMA 发送,必须在 DMA 传输完成中断里释放方向,不能在 HAL_UART_Transmit 返回后立刻切。

提示:把方向释放绑定到 TC 或 DMA 完成中断,排查“最后一字节丢”比全协议抓包快得多。

最后一字节的停止位完整度,直接决定这个 RS485 网络在真实线缆长度下是否可靠。自动收发电路、偏置电阻、方向切换时机这三件事都做对了,STM32F103 的 RS485 串口通信才算真正能上线。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询