尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

51单片机RS-485通讯教程:半双工控制与调试技巧

发布时间:2026/9/19 21:46:26

资讯中心
01
ARTICLE

51单片机RS-485通讯教程:半双工控制与调试技巧

51单片机RS-485通讯教程:半双工控制与调试技巧
简介《51单片机实现的485通讯程序》是一份面向嵌入式初学者的PDF技术文档讲解如何基于8051内核单片机串口实现RS-485半双工通信适用于工业控制、多点远程通信等场景。文档以完整C代码为载体逐段说明常量定义、定时器T1波特率配置、SCON串口初始化、外部中断0处理以及数据帧发送与接收流程重点剖析了地址匹配、转义字符、校验字节和帧错误检测等RS-485协议实现细节并给出M_DE/M_RE驱动器控制逻辑可直接迁移到实际项目中。文档还对__ACTIVE_、_GETDATA、_OK、__STATUS_等命令的交互流程做了说明方便理解主机与从机之间的应答机制。资源为单个PDF文件压缩包大小约698KB当前已有389人学习。这份资料既能帮助初学者理解串口通信与中断编程也能为调试多节点485网络提供排错思路和代码参考适合作为单片机课程设计或工程开发的速查手册。1. 51单片机做485通讯真正的门槛不在串口而在半双工很多人的第一反应是485通讯不就是把串口调通、然后发数据吗实际在51单片机上跑起来你会发现最折磨人的不是波特率也不是中断而是半双工这根“独木桥”谁来控制、什么时候切换。RS-485 只用一对差分线同一时刻只能有一个节点发送其它节点全部接收。收发器芯片如 MAX485 有独立的发送使能端 DE 和接收使能端 RE程序里哪怕把方向切换的时机算错一个字节总线就会立刻“堵死”——发完自己的数据还没释放总线想收别人的帧就只能等到超时。这篇内容按最常见的 STC89C52 / AT89C52 MAX485 方案把物理层接线、寄存器配置、方向切换状态机、可运行的 C 代码和排错手段一条线讲完适合做工业采集、仪表通讯、多机分机控制的工程师直接套用或改参数。2. 从MAX485到UART寄存器51单片机485通讯的硬件链路怎么搭2.1 差分信号与收发器选型为什么485能传1200米RS-232 的信号是相对于地线的单端电平共地一破、干扰一大通讯距离超过十几米就开始错包。485 改成 A、B 两线之间的差分电压来传数据A 高于 B 表示逻辑 1反过来表示逻辑 0。外界干扰同时作用在两条线上差模电压基本不受影响所以工业现场用 485 拉到几百米甚至 1200 米都很常见。51 单片机的 UART 引脚输出的是 TTL 电平不能直接挂到 A/B 线上必须经过一个收发器完成 TTL 与差分电平的互换。最常见的收发器是 MAX485还有 SP485、SN65HVD72 这类兼容型号。MAX485 的管教从左边数RO接收输出、RE接收使能低有效、DE发送使能高有效、DI发送输入、VCC、A、B、GND。RE 和 DE 在多数电路里直接接在一根控制脚上控制脚拉高接收关闭、发送开启拉低接收开启、发送关闭。这样一根 I/O 口就能完成方向切换非常省引脚。注意 51 单片机是 5V 逻辑MAX485 也工作在 5V直接连没问题如果你用的是 3.3V 的 STC15 系列就要选 3.3V 版本的收发器否则 TTL 高低电平阈值可能不匹配。2.2 51单片机串口寄存器配置波特率与中断使能51 单片机串口工作在方式 18 位 UART可变波特率波特率由定时器 1 的溢出率决定。常用公式是波特率 (2^SMOD / 32) × (定时器1溢出率)。工程上最省心的办法是使用 11.0592MHz 晶振因为它能精确分频出 9600、19200、115200 等常用波特率。若用 12MHz 晶振算 9600误差会到百分之几长时间大数据通讯就会出现偶发乱码。配置串口的关键寄存器有 3 个SCON、TMOD、TH1/TL1。SCON 的 SM00、SM11 表示方式 1REN 置 1 允许接收TI 是发送完成标志RI 是接收完成标志。TMOD 把定时器 1 设为方式 28 位自动重装重装值 TH1 由波特率反推。以 11.0592MHz、9600bps、SMOD0 为例TH1 256 - 11059200 / (32 × 12 × 9600) 0xFD。下面是完整的初始化函数// 51单片机串口0初始化使用定时器1产生波特率 // 晶振11.0592MHz波特率9600bps void UART_Init(void) { SCON 0x50; // 0101 0000: 方式1(SM00,SM11), REN1允许接收 TMOD 0x0F; // 清空定时器1控制位保留定时器0的设置 TMOD | 0x20; // 定时器1工作方式28位自动重装 TH1 0xFD; // 重装值256 - 时钟/(32*12*波特率) TL1 0xFD; // 初始值与重装值相同 ES 1; // 打开串口中断 EA 1; // 打开总中断 TR1 1; // 启动定时器1 }代码逻辑很简单SCON 0x50 一步做完方式选择和接收使能TMOD 先清 0 再置 1 是为了不破坏定时器 0 的配置很多工程里定时器 0 还承担着别的计时任务。TH1 和 TL1 相同是因为方式 2 下 TL1 计数溢出后自动装入 TH1不需要中断函数里手工重装。波特率如果想用 4800重装值改为 0xFA如果代码里用 SMOD1PCON0x80重装值需要相应调整具体值一般查表更稳妥。需要提醒的是ES1 开后如果有接收数据会自动跳中断中断函数里必须处理 RI 和 TI 标志否则会一直重复进中断。2.3 最小原理图与PCB接法RO、DI、RE、DE四根线不能错51 单片机与 MAX485 的连接是整个通讯链路里最容易画错的部分。RO 接单片机的 RXD因为 RO 是 485 芯片把总线上的差分发回 TTL 给单片机接收DI 接单片机的 TXD单片机发出去的 TTL 通过 DI 进入 485 芯片转发到总线。RE 和 DE 合并接一个 I/O 口比如 P1.0。发送时把 P1.0 拉高接收时拉低。A、B 是总线端A 接总线的 A 或同名端B 接 B正反接反会导致通讯完全不通。A 和 B 之间要跨接一个 120Ω 终端电阻而且只在总线物理末端的两台设备上各放一个。如果只有两台设备点对点通信两端各接 120Ω也就是总线上等效 60Ω这是标准做法如果多机级联只在最远的两个节点接电阻中间节点不要接。还有一个细节是电源去耦。485 通讯常出现“能发不能收”或“一堆节点同时发”的问题很多时候不是程序问题而是 MAX485 的 VCC 旁路电容没有紧贴芯片引脚放。建议 VCC 与 GND 之间接一个 100nF 陶瓷电容位置越近越好。如果现场干扰大A、B 线上还可以加 TVS 管做保护但这个属于后期抗扰设计第一版调通可以先不加。下面是一个最小连接的表格51引脚MAX485引脚方向说明P3.0/RXDRO485芯片接收输出给51低电平有效P3.1/TXDDI51发送数据到485芯片P1.0REDE共用方向控制高发低收VCC 5VVCC供电旁路电容100nF接地GNDGND共地不能悬空不接A、B接总线末端并联120Ω电阻注意 RE 的硬件逻辑是低电平使能接收DE 是高电平使能发送把两个脚并在一起用是为了用一根 I/O 线在高和低之间切换。这种接法下程序绝对不能把方向引脚输出高阻态或悬空必须始终驱动为高或低否则 RE、DE 浮动会导致收发同时半开总线状态不确定。初始化串口前先把方向引脚置低让 485 处于接收态再打开串口中断这是避免一上电就抢总线的基本习惯。3. 半双工状态机51单片机485通讯的方向切换与总线释放3.1 发送完成检测用TI标志还是用发送完中断51 单片机的串口发送过程是数据写入 SBUF 后硬件一位一位往外移全部移完之后硬件把 TI 置 1。如果用查询方式在 TXD 写入 SBUF 后只要判断 TI 是否等于 1就知道这一帧最后一个停止位已经移出去了。但这里有个关键点TI 置 1 时只是数据已从串口移出并不代表 MAX485 的差分电平已经稳定停止驱动。实际工程里发送完最后一个字节后DE 仍然维持高电平总线还被当前节点“占着”。如果你在检测到 TI 后立刻把方向引脚拉低有可能会截断最后一个停止位的尾巴导致对端收到的帧校验错误。一个必要的延时通常是“一个字节的传输时间”。9600bps 下 1 位时间约 104.17μs一个字节加起始位和停止位共 10 位所以发送一个字节约 1.04ms。稳妥的做法是在发送完最后一字节后等至少 2 倍位时间再关 DE。用定时器做精确延时或者简单地在循环里吃几个 NOP但坏处是不同晶振和波特率下 NOP 次数要重新调。更严谨的方案是把等待放进发送完成中断在中断里用定时器启动一个一次性超时超时后再关 DE这样主程序不用阻塞。不过对多数做数据采集的场景采用“TI 标志 半位延迟”已经足够关键是知道这个尾巴存在。3.2 软件状态机的三个状态发送、等待、接收485 通讯程序在逻辑上是一个三态循环空闲接收、发送中、释放等待。空闲态下 RE 有效总线来数据就能进串口中断需要发数据时先把方向引脚拉高再写 SBUF数据发完后不马上回接收而是保持发送态几十微秒到几毫秒让最后一个位完整发送出去等待结束后拉低方向引脚回到接收态。下面是一个用 switch 实现的简化状态机// 485方向控制与主状态机IDLE/WAIT_TX/OVER_TX三种状态 #define TX_IDLE 0 #define TX_WAIT 1 #define TX_OVER 2 sbit RS485_DIR P1^0; // 方向控制1发0收 void RS485_SendByte(uint8_t dat) { RS485_DIR 1; // 切到发送模式 SBUF dat; // 写入串口数据寄存器 while (!TI); // 等待该字节发送完成 TI 0; // 清标志 RS485_DIR 0; // 立即回到接收模式——单字节场景可行 }这段代码适合单字节发送因为每个字节之间方向已经复位下一个字节再拉高两次切换之间的空闲时间足够总线稳定。但如果你用这个函数连续发送一个长度超过 4 字节的报文每发一个字节就把 DE 拉低一次总线上会出现多次短脉冲翻转部分从机接收端会把同一个字节重复识别因为它们在 DE 低电平期间总线不稳。所以工程做法是整包发送期间 DE 保持高等全部字节发完、最后一位稳定后再拉低。正确的整包发送状态机需要引入发送长度计数和等待标志// 整帧发送期间DE保持有效发完最后字节后延时释放总线 void RS485_SendFrame(uint8_t *buf, uint8_t len) { uint8_t i; RS485_DIR 1; // 整帧期间保持发送态 for (i 0; i len; i) { SBUF buf[i]; while (!TI); // 等待每个字节移出 TI 0; } // 等待1.5个字符时间确保最后一个停止位完整到达对端 Delay_us(16 * 100); // 9600bps下约1.5ms根据波特率调整 RS485_DIR 0; // 释放总线切回接收 }延迟时间不宜过长。如果主程序循环周期很紧这个阻塞等待会拖慢响应更合理的做法是把这个延时放到发送完成中断里用定时器做异步释放。那样状态机会多一层“正在释放”状态代码复杂度上升但好处是主循环不用等接收响应更快。针对大多数 51 单片机项目一个 485 通讯节点不会是唯一任务所以异步状态机更贴近真实需求。3.3 方向引脚切换的时序参数与常见误用方向切换的时序包含两个重要参数发送前准备时间和发送后释放时间。发送前准备时间指 DE 拉高之后到写第一个字节到 SBUF 之间的间隔。这个时间不需要太长几十微秒足够 MAX485 内部驱动电路稳定。发送后释放时间至少要覆盖最后一个字节的停止位宽度建议留 1.5 个字符时间。9600bps 下 1.5 个字符约 1.56ms19200bps 下约 0.78ms。如果波特率提高到 115200释放时间可以缩到 150μs 以内。常见的误用有两种。第一种发送完后立刻把 DE 拉低导致对端收到的帧最后一位出错表现是能收到大部分数据但最后一字节经常错为 0xFF 或 0x00。第二种发送后等待时间固定为很大的值比如 10ms主循环一忙报文间隔拉长整个通讯周期变慢。面对这种问题我一般会用示波器抓 DE 引脚和 A/B 差分波形对比 DE 下降沿与 A/B 最后一个位结束之间的时间差把延迟调到刚好吃掉尾巴即可不需要留多大余量。另一个容易忽略的是接收态下 DI 和 RO 的状态。在接收模式下DI 悬空没关系因为 DI 只在发送时有效但 RO 连接到单片机 RXD如果总线上没有设备发送RO 默认输出高电平RXD 一直收到空闲电平不会触发接收中断。但如果 MAX485 的 A/B 悬空、没有接终端电阻RO 可能因为输入悬空而抖动导致单片机频繁进接收中断接收缓冲区被垃圾数据填满。所以在接收模式下同样要保证总线有确定的电平A/B 之间的 120Ω 电阻不仅是信号完整性需要也承担着让差分输入不悬空的作用。4. 可抄作业的51单片机485通讯程序轮询版与中断版4.1 轮询发送查询接收的最小程序如果你只是临时验证通讯、不需要处理复杂协议可以直接用轮询方式把要发的数据从串口发出去然后反复查询 RI 标志等到接收完成再读 SBUF。这种写法简单粗暴适合点对点的一问一答。下面的代码演示了主循环里先发一个 0xAA然后等待从机返回一个字节// 轮询式485收发发送0xAA查询RI接收一个字节 #include reg52.h sbit RS485_DIR P1^0; void UART_Init(void) { SCON 0x50; TMOD 0x0F; TMOD | 0x20; TH1 0xFD; TL1 0xFD; TR1 1; // 不开启串口中断使用查询方式 } void Delay_Ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 120; j); } void main(void) { unsigned char rx; UART_Init(); RS485_DIR 0; // 默认接收 while (1) { RS485_DIR 1; SBUF 0xAA; while (!TI); // 等发送完成 TI 0; Delay_Ms(2); // 保证停止位完整 RS485_DIR 0; while (!RI); // 阻塞等待接收 rx SBUF; RI 0; // 此时可对rx做处理这里作为简单演示 Delay_Ms(500); } }这段代码的优点是没有任何中断逻辑一目了然缺点是主程序在执行 while(!RI) 时完全卡住如果从机不回帧或者总线故障程序会永远停在那里。实际应用中至少要加一个接收超时判断比如用定时器 0 计数超过 20ms 没等到 RI 就跳出循环。轮询模式的另一个问题是串口发送期间不能响应其它外部事件实时性差。所以它只适合做极简验证不能用在多任务场景。4.2 中断接收串口状态机发送的工程版进入工程化阶段我会把接收完全放到中断里发送用标志位驱动主循环只负责组帧和判断接收结果。这样主程序在等待应答时可以继续做別的事比如采集 AD 或刷新数码管。中断接收最常用的结构是环形缓冲区// 中断接收环形缓冲区256字节取模 volatile unsigned char rxbuf[256]; volatile unsigned char rx_head 0, rx_tail 0; void UART_ISR(void) interrupt 4 { unsigned char ch; if (RI) { ch SBUF; rxbuf[rx_tail] ch; rx_tail (rx_tail 1) % 256; // 环形指针进位 RI 0; } if (TI) { // TI标志用于发送状态机这里清标志 TI 0; } }环形缓冲区解决了串口接收不定长数据时来不及处理的问题但要注意缓冲区大小必须足够存放一帧最大数据否则接收尾巴会覆盖还没取走的头部出现丢帧。如果项目只需要固定长度帧可以不缓存整帧在中断里直接就收到哪一字节然后把帧号相加。中断函数必须越短越好不要在中断里做 CRC 校验也不要在中断里调用延时函数。发送端也建议用状态机主程序把待发数据拷到一个发送缓冲区设置发送长度和发送序号然后置 DIR1 并允许串口中断在中断函数里检测 TI发送下一字节全部发完后关闭发送中断并释放总线。这个流程不会阻塞主循环适合带调度器的系统。如果不想写复杂状态机也可以用一种更省事的办法主程序用轮询发整帧发完后再打开接收中断。因为绝大多数 485 通讯是主从问答主机发送时从机不会回数据所以发送期间关接收、接收期间禁发送逻辑上完全没问题。4.3 多机通讯地址帧与广播辨识一台主机挂多台从机是 485 最常见的组网方式。物理层只有一对线所有从机都能收到主机发的每一帧所以必须在帧格式里加上设备地址。最简单的帧格式是地址字节 功能码 数据 校验。从机收到一帧后先判断地址是不是自己不是自己的直接丢弃是自己的就校验 CRC校验通过再执行命令。为了让从机知道一帧从哪开始有时会在帧头加 0xFE 或 0xAA 作为同步字符但这种做法在数据段里出现相同字符时会误判所以更推荐固定帧长 CRC 校验的协议。51 单片机做多机通讯还可以利用串口的方式 2 或方式 3 的第九位。方式 3 下波特率可变而 SM2 位可以在接收时置 1使只有地址帧第九位为 1才能触发 RI 中断数据帧第九位为 0自动忽略。主机发送地址时用附加第九位为 1之后发数据帧第九位为 0这样从机在 SM21 时只会在地址帧时唤醒数据帧不打扰 CPU。这个特性对多机通讯省电和减轻中断负担很有效但实际工程中因为要维护第九位状态代码复杂度略高。我一般宁可所有从机都接收并靠地址过滤因为 51 单片机的串口中断负载本来就不高9600 波特率下一秒钟最多几十帧每帧都中断一次都能轻松扛住没必要为一丁点性能牺牲协议灵活性。多机通讯的一个重要陷阱是总线竞争。所有节点在发送之前必须先检查当前总线是否空闲。51 单片机没有专门的 RS-485 控制器总线状态要靠软件去“听”——在接收态下如果一段时间内没有收到任何数据就认为总线空闲。常见的做法是把上一帧接收完成时间记录下来主程序在发送前判断当前时间与上次接收时间差大于 3.5 个字符时间才允许发送。这个逻辑在 Modbus RTU 协议里同样适用直接拿来用即可。5. 调通51单片机485通讯的3个验证技巧与故障定位5.1 回环法验证收发通路先把 MAX485 的 A、B 输出端不接总线直接用杜邦线把单片机板子的 A 接到自己的 B 吗不是。真实要检查的是 TTL 侧把单片机的 TXD 直接短路到 RXD发一个字节看能不能自己收回来这样能验证串口本身。然后接上 MAX485把 A 和 B 间不接电阻但短接相当于让 A、B 电压一样此时差分电压为 0接收端读到的是不确定状态不能用这种方式验证。正确做法是拿两个做好的板子A 对 A、B 对 B 接起来然后把板子1的 DI 引脚人为接地或接高看板子2的 RO 是否输出对应电平。如果两块板子对通不了用示波器分别看 A-B 差分波形和 RO 输出基本马上能定位是方向切换时序还是硬件焊错。5.2 用波特率误差表排查乱码乱码最常见的原因是晶振频率与波特率不匹配。12MHz 晶振想得到 9600 波特率会产生较大误差长时间跑数据会偶尔错一 bit 导致整个字节错误。用 11.0592MHz 晶振则基本没有误差。如果已经换了晶振还乱码查一下 SCON 里的 SM0/SM1 是否误设成方式 0 或方式 2。方式 0 是同步移位寄存器波特率固定为时钟的 1/12出来的波形完全不是 UART对端当然收不对。遇到乱码时先用逻辑分析仪抓单片机 TXD 引脚的实际波形测量一下 1 位的高电平时间是不是和理论值接近偏差超过 3% 就要改重装值。5.3 总线终端电阻与冲突排查如果两个板子通讯时好时坏测一测 A、B 之间的电阻。正常工作状态下两端挂 120Ω 时总线上等效电阻约 60Ω用万用表量 A-B 在断电时就可以看到 60Ω 左右。如果测到接近 0Ω说明 A/B 短路如果测到几百 kΩ 甚至开路说明末端电阻没接长线反射会让波形振铃数据率一高就出错。还有一类冲突问题是多个从机的 DE 引脚同时被拉高多半是程序里的方向控制没有在初始化时置低或者上电瞬间某个 I/O 口为高电平。解决方法是所有从机初始化代码的第一条指令就执行 RS485_DIR0并且把 MAX485 的 RE/DE 引脚通过 10kΩ 下拉电阻接地确保单片机上电复位瞬间总线处于接收态。这一个小电阻能省掉很多现场排查里“找不到原因但重启就好”的尴尬。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。