STM32 RS-485通信实战:硬件配置、方向切换与DMA收发

发布时间:2026/10/16 10:22:52
STM32 RS-485通信实战:硬件配置、方向切换与DMA收发 简介这份资源面向嵌入式开发初学者与工业自动化方向的工程师围绕STM32F103平台实现RS485通信这一常见任务展开。内容涵盖RS485总线原理、UART接口配置、MAX485等驱动器的硬件连接以及DE/RE使能信号控制、收发中断处理、错误检测与串口助手调试等关键环节帮助读者理解多节点、长距离串行通信的完整实现思路。压缩包共122个文件约826KB以39个h头文件、38个c源文件、33个s汇编启动文件为主另含uvproj与uvopt工程文件、hex烧录文件、doc与pdf说明文档及png示意图构成可直接编译的Keil工程结构。目前已有5516人学习下载适合希望快速搭建485通信实验环境、对照源码理解底层驱动配置与排错方法的开发者参考。1. 从一块工控板说起STM32 上的 485 通信到底难在哪车间里一台老设备要接入新系统主控板用的是 STM32现场总线是 RS-485两根线拉出去一百多米中间还串着三台从机。调试的时候单机收发都正常一挂上总线就丢包示波器一打A/B 线上全是反射。这个场景几乎每个做工业通信的工程师都遇到过问题往往不在协议本身而在收发方向切换的时序、终端电阻的匹配、以及总线空闲时的偏置电压。STM32 实现 485 通信核心就是三件事USART 配置、DE/RE 方向控制、以及总线物理层的处理。它适合做 PLC 扩展、传感器采集、多机主从轮询这类场景不适合长距离高速率同时要求低延迟的场合。下面把我自己拆过的这套实现思路完整写出来从寄存器配置到中断收发再到现场排错能直接抄作业。2. 硬件层先立住收发器选型、终端电阻与偏置电压2.1 收发器芯片怎么选为什么不是随便一颗都行RS-485 是差分信号STM32 的 USART 出来的是 TTL 电平中间必须加一颗收发器。常见的有两类一类是半双工收发器比如 MAX485、SP3485、ADM2483 这类A/B 两根线收发共用另一类是隔离型比如 ADM2582、ADM2483 带磁隔离的版本适合现场有强干扰或者地电位差大的场合。选型的时候看三个参数最大速率、共模抑制范围、以及是否带隔离。速率方面普通 MAX485 标称 2.5Mbps但实际在 1200 米双绞线上跑 115200 已经勉强再高就得缩短距离或者换低电容线缆。共模范围 -7V 到 12V 是标准要求工业现场建议选 -15V 到 15V 的宽共模型号。隔离型的价格贵三到五倍但如果你的从机分布在不同配电柜、地线不共地不隔离就是等着烧芯片。我一般会按这个顺序判断先看通信距离和速率再看现场有没有变频器、伺服这类干扰源最后看从机之间地电位差。三项里有一项超标直接上隔离型别省这个钱。2.2 终端电阻和偏置电阻两个最容易被忽略的细节总线两端各接一个 120Ω 终端电阻这是常识但很多人只在一端接或者中间节点也接结果阻抗不匹配波形振铃。正确做法是只在物理总线的两个最远端各接一个中间节点一律不接。偏置电阻的作用是让总线在空闲时保持一个确定的差分电压防止接收器输出乱跳。一般在上拉 A 到 VCC 用 560Ω下拉 B 到 GND 用 560Ω或者用 680Ω 也行。偏置电阻只需要在总线的一端加一组不要每个节点都加否则并联后阻值变小驱动能力不够。下面这张表是我在不同场景下用过的参数组合可以直接参考场景线缆终端电阻偏置电阻收发器类型短距离 10 米内 115200普通双绞线两端各 120Ω一端 560Ω 上下拉非隔离中距离 100 米 9600屏蔽双绞线两端各 120Ω一端 680Ω 上下拉非隔离长距离 500 米 4800低电容屏蔽双绞线两端各 120Ω一端 560Ω 上下拉隔离型强干扰环境 50 米屏蔽双绞线两端各 120Ω一端 560Ω 上下拉隔离型注意偏置电阻和终端电阻是两回事终端电阻匹配阻抗偏置电阻确定空闲电平不要混用也不要省掉偏置。2.3 STM32 与收发器的连接DE/RE 怎么接以常见的 SP3485 为例它有四个关键引脚RO 接 STM32 的 RXDI 接 STM32 的 TXDE 和 RE 通常短接在一起由 STM32 的一个 GPIO 控制。DE 高电平使能发送RE 低电平使能接收短接后一个引脚就能切换收发方向。接线的时候注意TX 和 RX 不要接反RO 是接收输出接 STM32 的 RXDI 是发送输入接 STM32 的 TX。DE/RE 控制脚选一个普通 GPIO 就行比如 PA8推挽输出初始状态拉低让总线处于接收模式。如果用的是隔离型收发器比如 ADM2582它内部已经集成了隔离电源和信号隔离接线更简单但要注意它的逻辑侧供电和总线侧供电是分开的逻辑侧接 3.3V 或 5V总线侧需要单独供 5V别接错。3. 软件层核心USART 配置、方向切换时序与中断收发3.1 USART 初始化参数怎么设才不丢包STM32 的 USART 配置本身不复杂但 485 场景下有几个参数必须注意。波特率要和从机严格一致误差控制在 2% 以内否则长距离下累积误差会导致采样错位。数据位一般 8 位停止位 1 位校验位根据从机协议定很多 Modbus RTU 用无校验。下面是一段基于 HAL 库的初始化代码以 USART2 为例PA2 为 TXPA3 为 RXPA8 为 DE/RE 控制// 485 通信 USART2 初始化PA2 TX, PA3 RX, PA8 DE/RE void RS485_Init(uint32_t baudrate) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 使能 GPIOA 和 USART2 时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); // PA2 TX, PA3 RX 复用推挽 GPIO_InitStruct.Pin GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // PA8 作为 DE/RE 控制推挽输出初始低电平接收 GPIO_InitStruct.Pin GPIO_PIN_8; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); // USART2 参数波特率、8 数据位、1 停止位、无校验 huart2.Instance USART2; huart2.Init.BaudRate baudrate; huart2.Init.WordLength UART_WORDLENGTH_8B; huart2.Init.StopBits UART_STOPBITS_1; huart2.Init.Parity UART_PARITY_NONE; huart2.Init.Mode UART_MODE_TX_RX; huart2.Init.HwFlowCtl UART_HWCONTROL_NONE; huart2.Init.OverSampling UART_OVERSAMPLING_16; HAL_UART_Init(huart2); // 使能接收中断 __HAL_UART_ENABLE_IT(huart2, UART_IT_RXNE); HAL_NVIC_SetPriority(USART2_IRQn, 1, 0); HAL_NVIC_EnableIRQ(USART2_IRQn); }这段代码里波特率通过参数传入方便不同从机切换。PA8 初始拉低保证上电后总线处于接收状态不会误驱动。接收中断使能后每收到一个字节进一次中断适合不定长协议。参数说明UART_OVERSAMPLING_16是 16 倍过采样抗噪能力比 8 倍好长距离下建议保持 16。HwFlowCtl设为 NONE485 是半双工不需要硬件流控。中断优先级设 1不要设太高避免打断其他关键任务。3.2 方向切换的时序晚一步就丢最后一个字节485 半双工最核心的问题就是方向切换。发送的时候 DE 拉高发完最后一个字节后必须等 USART 的发送移位寄存器彻底空掉才能把 DE 拉低。如果拉早了最后一个字节还没移出去就被截断拉晚了总线多驱动一会儿可能和从机响应冲突。HAL 库提供了__HAL_UART_GET_FLAG(huart2, UART_FLAG_TC)来判断发送完成。TC 标志置位表示最后一个字节已经从移位寄存器发完。下面是一个可靠的发送函数// 485 发送函数先拉高 DE发完等 TC再拉低 DE void RS485_SendData(uint8_t *buf, uint16_t len) { // 拉高 DE进入发送模式 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // 逐字节发送 for (uint16_t i 0; i len; i) { while (!__HAL_UART_GET_FLAG(huart2, UART_FLAG_TXE)); huart2.Instance-DR buf[i]; } // 等待最后一个字节发送完成 while (!__HAL_UART_GET_FLAG(huart2, UART_FLAG_TC)); // 拉低 DE回到接收模式 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); }逻辑说明先拉高 DE然后逐字节写 DR每次写之前等 TXE 标志保证数据寄存器空。所有字节写完后等 TC 标志确保移位寄存器也空了再拉低 DE。这个顺序不能颠倒也不能用延时替代 TC 判断延时在低波特率下可能不够在高波特率下又浪费。参数说明UART_FLAG_TXE是发送数据寄存器空UART_FLAG_TC是发送完成。两者区别在于 TXE 只表示 DR 可以写下一个字节TC 表示最后一个字节的停止位都发完了。485 方向切换必须用 TC。3.3 中断接收与不定长帧处理空闲中断怎么用接收端如果每来一个字节就进中断对于不定长协议需要一个帧结束判断。常见做法是用 USART 的空闲中断IDLE总线空闲一个字节时间后触发表示一帧结束。STM32 的 IDLE 中断配合 DMA 或者纯中断都能用。下面是一个基于 IDLE 中断的接收框架// 接收缓冲区 uint8_t rx_buf[256]; uint16_t rx_len 0; volatile uint8_t frame_ready 0; // USART2 中断处理 void USART2_IRQHandler(void) { // 接收非空中断 if (__HAL_UART_GET_FLAG(huart2, UART_FLAG_RXNE)) { if (rx_len sizeof(rx_buf)) { rx_buf[rx_len] (uint8_t)(huart2.Instance-DR 0xFF); } else { // 缓冲区溢出丢弃 (void)huart2.Instance-DR; } } // 空闲中断一帧结束 if (__HAL_UART_GET_FLAG(huart2, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart2); frame_ready 1; } }逻辑说明RXNE 中断里把数据读进缓冲区同时判断是否溢出。IDLE 中断里清标志并置位 frame_ready主循环检测到 frame_ready 后处理整帧数据处理完把 rx_len 清零。参数说明缓冲区大小根据协议最大帧长定Modbus RTU 一般 256 字节够用。IDLE 标志清除要用__HAL_UART_CLEAR_IDLEFLAG先读 SR 再读 DR 也能清但 HAL 宏更直观。注意 IDLE 中断使能要在初始化时加上__HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE)。4. 避坑与排查现场最容易翻车的五个点4.1 现象单机正常一挂总线就丢包原因终端电阻只在一端接或者中间节点也接了导致阻抗不匹配信号反射。另外偏置电阻缺失总线空闲时差分电压在阈值附近接收器输出乱跳。解决确认只在物理总线两个最远端各接一个 120Ω中间节点全部断开终端电阻。偏置电阻只在一端加一组上拉 A 到 VCC 用 560Ω下拉 B 到 GND 用 560Ω。用示波器看 A/B 差分波形空闲时应该在 200mV 以上发送时幅值对称。4.2 现象发送正常接收不到从机响应原因DE/RE 切换时序不对发送完最后一个字节后 DE 拉低太晚总线还在被主控驱动从机的响应被顶掉。或者拉低太早最后一个字节没发完。解决严格用 TC 标志判断发送完成不要用延时。在 TC 置位后立即拉低 DE然后使能接收。如果从机响应很快可以在拉低 DE 后加一个极短的等待比如几个微秒再开接收中断。4.3 现象通信一段时间后死机或数据错乱原因接收缓冲区溢出没有处理或者中断优先级配置不当高优先级中断打断 USART 中断导致数据丢失。另外如果用了 DMA 接收DMA 和 IDLE 中断配合不当也会丢帧。解决接收中断里必须判断缓冲区边界溢出时丢弃并清标志。中断优先级不要设太高USART 中断优先级低于系统 tick 和关键任务。用 DMA 的话IDLE 中断里先关 DMA 再处理数据处理完重新配置。4.4 现象长距离通信误码率高短距离正常原因线缆电容过大波特率过高信号边沿变缓采样点错位。或者地电位差导致共模电压超出收发器范围。解决降低波特率或者换低电容屏蔽双绞线。检查从机之间地电位差超过收发器共模范围就加隔离型收发器。在 A/B 线上套磁环减少高频干扰。4.5 现象上电后总线被拉死所有从机无响应原因DE 控制脚初始状态不对上电瞬间被拉高主控一直驱动总线。或者多个节点同时驱动造成总线冲突。解决DE 控制脚初始化为低电平确保上电后处于接收模式。软件上电后先延时一段时间再初始化 USART等电源稳定。如果是多主结构需要协议层做仲裁硬件上不能同时驱动。5. 进阶技巧用 DMA 加 IDLE 中断做零拷贝收发前面用的是纯中断收发数据量大的时候 CPU 占用高。实际项目里我一般会换成 DMA 加 IDLE 中断的方案发送用 DMA 把缓冲区直接推到 USART接收用 DMA 循环模式加 IDLE 中断判断帧尾CPU 只在帧结束时处理一次。发送侧配置 DMA 的代码如下// 485 发送 DMA 配置USART2_TX 对应 DMA1_Stream6 void RS485_DMA_TxInit(void) { __HAL_RCC_DMA1_CLK_ENABLE(); hdma_usart2_tx.Instance DMA1_Stream6; hdma_usart2_tx.Init.Channel DMA_CHANNEL_4; hdma_usart2_tx.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_usart2_tx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode DMA_NORMAL; hdma_usart2_tx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_usart2_tx); __HAL_LINKDMA(huart2, hdmatx, hdma_usart2_tx); }发送的时候先拉高 DE启动 DMA等 DMA 传输完成中断里再等 TC 标志然后拉低 DE。接收侧用 DMA 循环模式IDLE 中断里计算收到的字节数处理完重新设置 DMA 计数器。这个方案的好处是 CPU 只在帧结束时介入收发过程中几乎不占 CPU。坑在于 DMA 传输完成中断和 TC 标志的先后顺序DMA 完成不代表 USART 移位寄存器空了还是要等 TC。另外接收 DMA 循环模式下IDLE 中断里要重新设置 NDTR 寄存器否则下一帧会覆盖。从那以后我每次做 485 通信都会先用示波器确认总线空闲电平和终端电阻再跑一遍 DMA 加 IDLE 的收发测试最后才上协议层。这个顺序能省掉很多玄学问题。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询