简介:这份面向嵌入式初学者与STM32开发入门者的RS485通信文档,围绕STM32F103微控制器如何借助UART接口实现半双工RS485通信展开,重点解决DE方向控制、数据收发切换及传感器数据读取等常见问题。压缩包内共1个docx文件,约1.22MB,以图文说明配合C语言代码片段的形式组织,便于对照硬件连接与程序流程阅读。文档结合土壤温湿度传感器实例,给出RS485初始化、波特率设置、发送读取指令、接收响应数据并在LCD上显示温湿度的完整流程,同时涉及GPIO模式配置、中断优先级、接收缓冲区与发送/接收模式宏定义等关键知识点。已有2629人学习,适合需要快速理解RS485原理、掌握STM32串口编程并完成传感器采集实验的读者参考。
1. 从一根线开始:零基础也能看懂的 RS485 到底在干什么
很多人第一次看到「STM32 + RS485 + 传感器」这一串词,脑子里会立刻冒出一堆问号:RS485 是个芯片吗?是协议吗?STM32 又是什么?是不是得先学完单片机原理、电路、C 语言,才能碰这个东西?我先给一个让你不再发怵的答案:这套东西的本质,是让一块单片机能跟几十米外的一颗土壤探头「说话」,而且只需要两根线。
生活里最贴近的类比是课堂点名。老师喊「一号同学,报一下你的体温」,一号回答,其他同学安静听着,谁被叫到谁说话,这就是 RS485 常说的半双工——同一时刻只有一个人在讲话。而 STM32 就是那个老师,传感器是被点名的同学,两根线就是传递声音的空气。整份文档做的事情,就是教你怎么当这个老师:先把嗓子调好(初始化串口),再决定什么时候闭嘴听(切换收发方向),最后把同学报上来的数字翻译成人能看懂的温度和湿度,显示在屏上。适合谁?完全没碰过单片机、只学过一点点 C 语言、想做个能跑起来的小项目的新手,最合适。
2. 为什么值得先学它:从一个跑不通的串口说起
2.1 RS485 与普通串口的差别:多了一根「说话开关」
新手最容易混淆的一点是:RS485 不是全新的通信方式,它的数据格式和普通串口几乎一模一样,一样是波特率、8 位数据、1 位停止位、无校验。真正的差别只在硬件层面——普通串口是两根线各管一个方向,而 RS485 用一对差分线,发送和接收共用这条总线,所以必须有个开关来告诉你此刻是「说」还是「听」。
这份文档里,这个开关就是代码里的RS485_TX_EN,它接到了单片机的一个引脚上。你把引脚拉高,芯片就切到发送模式,把数据推到总线上;拉低就切回接收,安静地等别人说话。一开始我没意识到这个开关的重要性,只把数据往外发,屏幕上一个数都没有,后来才明白:发完不改回接收模式,传感器就算回答了,你也听不见。
为什么非得这么设计?因为 RS485 的强项是省线和远距离。一根总线可以挂几十个设备,布线成本低,抗干扰能力强,工业现场几十米上百米都在用。代价就是:所有人共用一条「话筒线」,必须轮流说话,靠地址区分谁被点名。文档里那句0x01就是地址,0x03是「读寄存器」的功能码,后面跟着要读的寄存器位置和数量——这串指令不是随便编的,是这颗土壤温湿度传感器手册里规定的格式。
2.2 为什么新手适合拿它练手:看得见摸得着
学嵌入式最怕的是什么?是代码跑完了,什么也没发生,你不知道是对是错。RS485 读传感器这个项目刚好反过来:数字变了就是通了,数字不变就是没通,屏幕就是你的裁判。这种即时反馈对新手极其重要。
而且它把嵌入式的几个核心动作全都串了一遍,但每一个都不深:时钟使能、GPIO 配置、串口初始化、中断、延时、数据解析、屏幕显示。你不需要先把每个模块啃透,先跟着跑通一遍,回头再看每个函数在干什么,理解速度会快很多。这也是我建议新手不要一上来就啃寄存器手册的原因——先有体感,再补原理,顺序反了容易劝退。
再补一句现实价值:土壤温湿度、光照、气体浓度这类工业传感器,大量走的就是 RS485 接口。学会读一个,换一个型号无非是改改指令和解析公式,套路是通用的。所以这个入门项目不是玩具,它是你后面接各种传感器的第一块垫脚石。
2.3 第一步该准备什么:硬件清单与接线思路
动手前先把东西备齐,别到写代码时发现少一根线。你需要:一块 STM32F103 的开发板(文档里用的是 ZET6 型号,引脚多,接屏幕方便)、一个 RS485 转接模块或者板载的 485 收发芯片、一个土壤温湿度传感器、一块 TFT 液晶屏、下载器和若干杜邦线。
接线记住三件事就够了。第一,传感器的 A 接模块的 A,B 接 B,接反了收不到数据,这是最常见的翻车点,别问我是怎么知道的。第二,模块和单片机之间是串口连接:单片机的 PA2 接模块的发送脚,PA3 接接收脚,这是文档里RS485_Init配置的那两个引脚。第三,收发方向控制脚要接到单片机的某个 IO 上,文档里用的是 PG3,实际接线时你要和自己代码里的定义对上,对不上就是发得出去收不回来。
提示:接线前先断电,接好再上电。RS485 的 A/B 线在带电插拔时容易打火,养成习惯比事后排查省事得多。
3. 拆开第一个例子:从初始化到屏幕出数,一步步跟着跑
3.1 先看懂那条指令:八个字节在说什么
在写任何代码之前,先搞明白你要发出去的东西是什么。文档里这一行是最关键的:
// 发给土壤温湿度传感器的读取指令,共 8 个字节 u8 TR_Read[8] = {0x01, 0x03, 0x00, 0x06, 0x00, 0x02, 0xC4, 0x38};逐字节拆开看:0x01是传感器地址,总线上如果挂了好几个设备,就靠它点名;0x03是功能码,意思是「我要读你的保持寄存器」;0x00 0x06是起始寄存器地址,告诉它从第 6 号位置开始给数据;0x00 0x02表示要读两个寄存器,一个存湿度一个存温度;最后0xC4 0x38是校验码,用来让传感器确认这条指令在传输中没有被干扰改坏。
这最后两个字节新手最容易忽略,觉得是凑数的。其实不是——它们是根据前面六个字节算出来的,算错了传感器会直接不理你,一个字节都不回。所以换传感器型号时,指令不能照抄,必须按新手册重新算一遍校验。这也是我建议第一次先用现成指令跑通、确认链路没问题,再去动它的原因。
3.2 初始化:把串口 2 调成能说话的状态
初始化函数看着长,其实就干了四件事。第一件是开时钟,不给外设供电它不会工作:
// 使能 GPIOA、GPIOG 和 USART2 的时钟,不开时钟后面全部无效 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG | RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);注意 USART2 挂在 APB1 上,GPIO 挂在 APB2 上,这两个函数调错了不会报错,但外设就是不动,属于典型的「玄学」现象——代码没错、编译通过、毫无反应。
第二件是配引脚。PA2 是发送,要配成复用推挽输出,因为它要由串口外设来驱动,不是普通 IO;PA3 是接收,配成浮空输入;PG3 是那个方向开关,配成普通推挽输出。第三件是配串口参数:波特率 9600、8 位数据、1 位停止、无校验、无硬件流控,同时打开接收中断。第四件是配中断优先级,文档里抢占优先级设 3、子优先级设 2,只要不和别的中断冲突就行。
USART_InitStructure.USART_BaudRate = bound; // 波特率,两边必须一致 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8 位数据 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1 位停止位 USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发都开最后一句RS485_TX_EN = 0别漏,它把方向默认设成接收。初始化完就应该是「听」的状态,等你要发的时候再切。
3.3 发送函数:先开开关,发完立刻关回去
发送逻辑本身不复杂,难的是时序。文档里的写法是:拉高方向脚进入发送模式,然后一个字节一个字节往外送,每送一个都等发送完成标志位置位,全部送完后把方向脚拉低,回到接收模式。
void RS485_Send_Data(u8 *buf, u8 len) { u8 t; RS485_TX_EN = 1; // 切到发送模式 for (t = 0; t < len; t++) { while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET); // 等上一个字节发完 USART_SendData(USART2, buf[t]); // 送入一个字节 } while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET); // 等最后一个字节彻底发完 RS485_RX_CNT = 0; // 清空接收计数,准备接新数据 RS485_TX_EN = 0; // 切回接收模式 }那个while等待是关键。如果你不等发送完成就切回接收,最后一个字节可能只发出去一半,传感器收到的就是残包,校验自然对不上,表现就是「有时候能读到,有时候读不到」,非常折磨人。我第一次调的时候就是这里少等了一步,折腾了大半天。
3.4 接收函数:靠「安静了」判断一帧结束
RS485 没有明确的帧结束标志,你只能靠时间来判断。文档里的思路是:先记下当前收到的字节数,等 10 毫秒再看一眼,如果数量没变而且不为零,就认为这一帧收完了,把缓冲区里的数据搬出来。
void RS485_Receive_Data(u8 *buf, u8 *len) { u8 rxlen = RS485_RX_CNT; // 当前已收到的字节数 u8 i = 0; *len = 0; delay_ms(10); // 等待 10ms,若期间没有新数据则认为帧结束 if (rxlen == RS485_RX_CNT && rxlen) // 数量没变且不为 0 { for (i = 0; i < rxlen; i++) buf[i] = RS485_RX_BUF[i]; *len = RS485_RX_CNT; RS485_RX_CNT = 0; // 清零,为下一帧做准备 } }这里 10 毫秒不是随便定的。波特率 9600 下,一个字节大约 1 毫秒,正常一帧数据之间的间隔远小于 10 毫秒,而两帧之间的空闲会超过它,所以用它当分隔线。如果传感器回得慢,或者你把波特率调得很低,这个值可能要加大,否则会把一帧拆成两半。
3.5 主函数:发、等、收、显示的循环
主函数的节奏很清楚:初始化一堆东西,然后进死循环,每轮发一次指令,等 300 毫秒让传感器准备答案,收一次数据,把字节拼成温湿度,刷到屏幕上,再等 3 秒开始下一轮。
while (1) { RS485_Send_Data(TR_Read, 8); // 发读取指令 delay_ms(300); // 给传感器时间处理 RS485_Receive_Data(rs485buf, &len); // 收返回数据 if (len) { if (len > 9) len = 9; // 防止越界,只取前 9 个字节 WD = (rs485buf[3] * 256 + rs485buf[4]) / 100; // 拼出湿度 SD = (rs485buf[5] * 256 + rs485buf[6]) / 10; // 拼出温度 LCD_ShowxNum(30, 190, WD, 4, 24, 0); LCD_ShowxNum(30, 230, SD, 4, 24, 0); } delay_ms(3000); // 3 秒采一次,没必要太频繁 }重点看那两行* 256 +。因为一个寄存器只有 8 位,装不下温湿度的实际数值,所以传感器把它拆成高字节和低字节两个寄存器存。高字节 * 256 + 低字节就是把两个 8 位拼回一个 16 位整数,这是所有这类传感器的通用套路。后面的/100和/10是小数点处理,因为传感器返回的是放大后的整数,比如返回 2530 实际是 25.30。这两个除数每个型号都不一样,一定要查手册,抄错了数字就是错的。
提示:
if (len > 9) len = 9;这行别删。中断接收是异步的,缓冲区有可能被写进多余的字节,不限制长度去取值,轻则显示乱码,重则读越界。
4. 新手最容易踩的坑:五个真实会卡住你的地方
第一个坑:程序烧进去,屏幕一直是 0。现象是显示区域有数字但永远不变,或者干脆没有。原因多半是方向控制脚接错了或者代码里的引脚定义和实际接线不一致,单片机以为自己在收,其实一直没切回来。解决办法很土但有效:用万用表量一下那个引脚在发送前后的电平有没有变化,没变化就是引脚定义错了,去改RS485_TX_EN对应的那行宏定义。
第二个坑:明明接了线,一个字节都收不到。现象是len始终为 0。原因最常见的是 A、B 两根线接反了,RS485 是差分信号,反了就是收不到;其次是波特率不匹配,单片机设 9600 但传感器出厂是 4800。解决顺序是先对调 A/B 试一次,再逐个波特率试,这两步能解决八成问题。
第三个坑:读到的数忽大忽小,或者偶尔跳成几万。现象是数据不稳定。原因是没等发送完成就切换方向,最后字节发送不完整,或者接收端没做帧长度限制。解决办法是把发送函数末尾那个等待标志位的while补上,同时加上长度上限判断,别让脏数据进解析。
第四个坑:传感器地址不是 1 却照抄指令。现象是发出去毫无回应。原因是总线上多个设备时地址必须唯一,你照抄的0x01可能和别的设备撞了,或者这颗传感器出厂就是别的地址。解决办法是先只接一个传感器,确保能通,再考虑多设备;多设备时逐个改地址,别一次全挂上去。
第五个坑:校验码照抄,换型号直接崩。现象是换了同类型传感器后完全读不到。原因是不同型号的寄存器地址和校验算法可能不同,最后两个字节不是通用的。解决办法是拿到新手册,用在线校验计算工具按新参数重算,或者写个小函数自己算,别靠猜。
5. 跑通之后学什么:三条不绕路的进阶方向
第一个例子能在屏幕上看到数字,说明你已经跨过了最难的那道坎。接下来别急着换项目,先把这一个吃透。第一件事是改参数做实验:把波特率从 9600 改成 4800,看会出什么现象;把采样间隔从 3 秒改成 500 毫秒,观察屏幕刷新和数据稳定性;把接收那 10 毫秒的等待改成 2 毫秒。每次只改一个,改完记录现象,这种「改动—观察—解释」的循环,比看十篇教程都管用。我第一次这么做的时候,把等待时间改小,立刻复现了数据被截断的问题,那一刻才真正理解帧间隔是什么意思。
第二件事是把发送和接收拆开来看。现在的接收是靠中断往缓冲区里塞,主循环去搬。你可以试着把中断里的逻辑加上一个帧头判断,或者干脆改成用空闲中断来判断一帧结束,这是实际项目里更常见的做法。再进一步,把温湿度解析那段从主函数里抽成一个独立函数,参数传缓冲区,返回结构体。别小看这种重构,它是从「能跑」走向「能维护」的分水岭。
第三件事才是扩展。同一个 RS485 总线上再挂一个光照传感器,学会用不同地址区分设备;或者把读到的数据通过另一个串口发给电脑,在电脑上画个曲线。这两步做完,你对「总线」和「多设备」的理解就落地了。
| 阶段 | 练什么 | 判断是否过关 |
|---|---|---|
| 跑通 | 原样烧录,屏幕出数 | 数字随环境变化而变 |
| 调参 | 改波特率、改间隔、改等待 | 能解释每次改动后的现象 |
| 重构 | 抽出解析函数、改帧判断方式 | 代码改动后功能仍正常 |
| 扩展 | 挂第二个传感器、上传数据 | 能靠地址区分设备 |
学习路径上,我建议按这个顺序补基础:先补 C 语言的指针和数组,因为所有缓冲区操作都靠它;再补串口通信的时序概念,理解波特率和帧结构;最后才去碰中断和优先级。顺序别反,一上来啃中断优先级分组,很容易卡在「为什么是 2 位抢占 2 位响应」这种细节上,对入门毫无帮助。
最后说一个我自己的习惯。从那以后我每次接一个新的 485 传感器,都强制走一遍固定流程:先只接一个设备、先把波特率对上、先发一条手册里的示例指令、先用串口助手看原始字节、确认无误再写解析代码。这套流程看着笨,但帮我省掉了无数次「到底是硬件问题还是代码问题」的自我怀疑。入门阶段最贵的不是知识,是信心,别让一次莫名其妙的失败把你劝退。希望帮到你。
本文还有配套的精品资源,点击获取