简介:一套基于51单片机与RS485协议的PC串口通信Proteus仿真设计包,以AT89S52为控制核心,结合MAX485电平转换芯片,帮助学习者在无硬件条件下掌握UART收发、方向控制及总线冲突规避等关键知识点。压缩包共56个文件,大小约29.75MB,核心包含Proteus仿真电路(dsn)、C语言源程序与hex固件,可直接运行和烧录;同时附有DS18B20、AT89S52等常用器件数据手册,51单片机烧录方法、元器件焊接技巧及毕设答辩参考等文档,兼顾设计、调试与文档撰写需求。目前已有163人学习下载,适合正在做单片机课程设计、毕业设计或自学RS485通信的工程师和学生。通过仿真文件可完整观察PC与单片机之间的数据收发过程,还能通过修改程序中的波特率、校验位等参数,深入理解串口初始化、中断接收和RS485收发切换的实现细节。
1. RS485 仿真做通了,实物联调才不玄学
很多人在做 51 单片机与 PC 通信时,第一步就画板子、焊 MAX485,程序一烧进去,示波器上看不到完整帧,串口助手收到一堆乱码,甚至还出现过两个节点的 AB 线互相把总线拉死的情况。这个以 RS485-PC 串口通信为核心的 Proteus 仿真工程,把 UART 初始化、DE/RE 方向切换和差分电平收发先放在仿真环境里跑通,再去碰实物,很容易就定位到是时序问题还是电平问题。工程里带了完整 Proteus 仿真文件和 C 源程序,外附 AT89S52、DS18B20、1602 等常用器件手册、烧录方法、焊接技巧和软件安装包下载地址,对正在做课程设计、毕业设计的电子信息方向学生很实用,对想快速复习 51 单片机和 RS485 组网的嵌入式工程师,也是一个可以直接抄作业的模板。
2. RS485 差分电平、MAX485 与收发方向控制
2.1 为什么不是 TTL,也不是 RS232
51 单片机的 UART 引脚直接输出的是 TTL 电平,高电平约 5V,低电平约 0V。PC 的 COM 口传统上是 RS232 电平,逻辑 1 是负电压,逻辑 0 是正电压,两者定义完全相反,直接连接不仅收不到数据,还可能损坏引脚。RS232 虽然简单,但传输距离一般只有 15 米左右,且是单端共地方式,现场有电机、变频器时共模干扰很容易让数据出错。
RS485 用 A、B 两根线之间的差分电压表达逻辑:A-B 大于 200mV 为逻辑 1,小于 -200mV 为逻辑 0。这种差分结构天然抑制共模干扰,标准 RS485 收发器在 100kbps 速率下通信距离可以到 1200 米,一条总线上最多挂 32 个标准节点。工业自动化、楼宇自控、仪表传感器网络里 RS485 几乎成了默认物理层,因为成本低、布线简单、可靠。表里对比了三者的关键差异。
| 项目 | TTL | RS232 | RS485 |
|---|---|---|---|
| 电平方式 | 单端 0~5V | 单端 ±12V | 差分 A/B |
| 典型距离 | 1 米以内 | 15 米左右 | 1200 米 |
| 节点数 | 点对点 | 点对点 | 标准 32 节点 |
| 工作模式 | 全双工 | 全双工 | 半双工常见 |
| 抗干扰能力 | 弱 | 一般 | 强 |
| 典型场景 | 板级调试 | 老旧外设 | 工业总线 |
2.2 MAX485 的 RE/DE 引脚与半双工切换
TTL 电平要转成 RS485 差分信号,常用芯片是 MAX485 或 SN75176。MAX485 有 8 个引脚:RO 接单片机 RXD,DI 接单片机 TXD,RE 低电平使能接收,DE 高电平使能发送。实际电路中通常把 RE 和 DE 短接,用一个 GPIO 控制,高电平发送、低电平接收,这就是半双工模式的核心。
发送一字节的时序是:先拉高 DE,让 MAX485 进入发送模式,然后把数据写入 SBUF 启动 UART 移位输出,等 TI 置位表示一字节已经全部从 TXD 移出,最后再拉低 DE 切回接收。这里最容易犯的错是发送完马上切回接收,看起来代码没问题,但最后一两位会被截断,PC 端收到帧错误或 0x00。下面是一字节发送的标准写法。
// 控制 RS485 方向:发送前拉高 DE/RE sbit DE = P1^0; // RE/DE 短接,由 P1.0 控制 void RS485_SendByte(unsigned char dat) { DE = 1; // 拉高,MAX485 进入发送模式 SBUF = dat; // 写入发送缓冲,UART 开始移位 while (TI == 0); // 等待硬件发送完成 TI = 0; // 清发送标志 DE = 0; // 切回接收模式 }这段代码的要点是while (TI == 0)必须放在DE = 0之前。TI 由硬件在停止位发送结束时自动置 1,只有确认这一位已经落到 A/B 线上,才能关闭发送器。如果改用自动收发电路,DE 的切换由三极管根据 TXD 起始位自动完成,则可以省掉这根 GPIO 控制线,但高波特率下起始位容易被截断,我一般更推荐 GPIO 控制方向,可预测性更强。
2.3 自动收发电路与常见误用
网上搜“rs485 自动收发电路”能找到很多方案,核心思路是用一个 NPN 三极管和一个 PNP 三极管组成逻辑,TXD 空闲为高电平时让 DE 保持低电平,TXD 出现起始位低电平时把 DE 拉高进入发送。优点是少占一个 IO,缺点是电路参数受波特率影响,9600 以下还能稳定,115200 时经常出现首字节丢失。
还有一种误用是把 RE/DE 直接接到单片机的 TXD,靠串口空闲时的高电平保持接收状态,发送时起始位低电平切入发送。这个做法在连续发送两个字节时会因为 DE 抖动把第二个字节的起始位吃掉。所以仿真里用 GPIO 方向切换来学原理,实物里如果追求稳定,也建议保留 GPIO 控制。工程里 MAX485 数据手册和原理图都在包里,对照着看引脚更直观。
3. Proteus 仿真电路搭建与 PC 串口联通
3.1 工程骨架:从 AT89C52 到 MAX485 再到 COMPIM
打开 Proteus 8 Professional,新建一个工程,先放核心器件:AT89C52、MAX485、COMPIM、虚拟终端、两个 120 欧终端电阻。晶振选 11.0592MHz,而不是常用的 12MHz,原因是这个频率可以被 9600、4800 波特率整除,串口误差接近 0,这点在后面的寄存器计算里会具体展开。包里的“软件安装包下载地址.txt”列了 Proteus 和串口助手下载方式,可以先装好。
典型连线是:单片机 P3.0/RXD 接第一片 MAX485 的 RO,P3.1/TXD 接 DI,P1.0 接 DE/RE 短接点。MAX485 的 A、B 作为差分总线,连接到第二片 MAX485(模拟 PC 侧的 RS485 转换器)的 A、B。第二片 MAX485 的 RO 接 COMPIM 的 RXD,DI 接 COMPIM 的 TXD。COMPIM 是 Proteus 里的串口模型,把它当成 PC 的虚拟 COM 口即可。首尾两端各接一个 120 欧电阻到 A-B 之间,匹配总线阻抗。
3.2 COMPIM 虚拟串口与 PC 端配置
COMPIM 默认使用物理串口,但仿真场景下最好用虚拟串口软件创建一对 COM 口,比如 COM3 和 COM4。将 COMPIM 的 Physical Port 设为 COM4,PC 上串口助手打开 COM3,Proteus 和 PC 之间就能通过虚拟串口桥接。串口参数统一设置为 9600、8 位数据、1 位停止位、无校验,对应 51 单片机 UART 方式 1 的标准配置。
如果不想依赖图形化串口助手,也可以直接用 Python 的 pyserial 库做 PC 端验证,这对后面做自动化测试和 Modbus 主站很方便。
import serial ser = serial.Serial('COM3', 9600, timeout=0.5) ser.write(b'PING\r\n') # 发送一帧给 Proteus 里的单片机 resp = ser.read(16) # 读取单片机返回的数据 print(resp.hex()) ser.close()这里timeout=0.5表示最多等 0.5 秒,read(16)最多读 16 字节,返回的十六进制字符串方便对照源代码里的发送内容。第一次跑如果出现SerialException,说明虚拟串口对没创建成功,或者 COMPIM 占用了同一个 COM 口。这种情况我一般先在设备管理器里确认 COM3、COM4 是否存在,再重启 Proteus 仿真。
3.3 跑仿真时看哪几个点
点击左下角 Play 按钮后,单片机程序开始运行,PC 串口助手应该能周期收到「RS485 OK」之类的字符串。这时候第一件事不是看收到多少数据,而是把示波器拖进工程,分别测量 TXD 引脚和 A-B 两端电压。TXD 上能看到正常的 UART 波形:空闲高电平、起始位低电平、8 个数据位、停止位高电平。
A-B 差分波形在空闲时应该稳定在高电平,即 A 比 B 高;发送数据时出现一组翻转脉冲。如果 A-B 一直不动,先看 DE 是否被拉高,再看 COMPIM 有没有选对物理端口。很多人在仿真里收不到数据,问题不是电路连错,而是虚拟串口被串口助手和 Proteus 同时打开,Windows 不允许独占复用。串口助手应该用免费版的虚拟串口工具,或者先关掉串口助手再启动仿真,运行时再打开。
4. 51 单片机 UART 初始化与 RS485 收发源程序解析
4.1 波特率由定时器 1 方式 2 决定
51 单片机的串口波特率由定时器 1 的溢出率决定,常用方式 2(8 位自动重装)来产生。波特率公式为:波特率 = (2^SMOD / 32) × 定时器溢出率,其中溢出率 = 晶振频率 / (12 × (256 - TH1))。SMOD 在 PCON 的最高位,取 0 或 1,相当于对波特率做 2 倍分频。
晶振选 11.0592MHz 的好处就在这里:当 SMOD=0、TH1=0xFD 时,溢出率 = 11.0592MHz / (12 × 3) = 307200Hz,再除以 32 得到 9600,误差为 0。如果换成 12MHz 晶振和同样的 TH1,实际波特率约 10416,误差 8.5%,PC 端就会出现偶发乱码。所以 Proteus 里的晶振属性一定要改成 11.0592MHz,不是默认的 1MHz 或 12MHz。
| 波特率 | SMOD | TH1 初值 | 理论误差 |
|---|---|---|---|
| 4800 | 0 | 0xFA | 0.00% |
| 9600 | 0 | 0xFD | 0.00% |
| 19200 | 1 | 0xFD | 0.00% |
从表里能看出一个规律:4800 和 9600 只差一个 TH1 初值,但很多人 9600 能通、4800 不通,多半是没注意 PCON 里 SMOD 是否残留为 1。SMOD=1 时 TH1=0xFD 对应 19200,不是 9600。所以切换波特率时,PCON = 0x00和TH1 = 0xFA要一起改。
4.2 SCON、PCON 与串口中断配置
串口工作方式由 SCON 寄存器控制,方式 1 是 10 位帧格式:1 个起始位、8 个数据位、1 个停止位。配置 SCON = 0x50,即 SM0=0、SM1=1 选择方式 1,REN=1 允许接收。PCON = 0x00 表示 SMOD=0,此时波特率为 9600。ES=1 开串口中断,EA=1 开总中断,RI 和 TI 在中断服务函数里必须由软件清零,否则会反复进入中断。
接收采用中断方式,每收到一字节,RI 自动置 1,进入interrupt 4的中断服务程序。发送采用查询方式,因为 RS485 是半双工,发送期间要一直占用总线,等待 TI 置位是最稳妥的做法。中断里尽量不要执行耗时操作,收到数据先放进全局缓冲区,主循环再做协议解析。
4.3 完整源程序结构与参数说明
下面是一段可以直接放进 Keil 工程的 RS485 收发示例,对应仿真里的 AT89C52。
#include <reg52.h> sbit DE = P1^0; // RE/DE 短接控制引脚 void UART_Init(void) { SCON = 0x50; // 方式 1:8 位 UART,允许接收 PCON = 0x00; // SMOD = 0,波特率不倍增 TMOD &= 0x0F; // 只修改定时器 1,保留定时器 0 配置 TMOD |= 0x20; // 定时器 1 方式 2:8 位自动重装 TH1 = 0xFD; // 9600bps @ 11.0592MHz TL1 = 0xFD; // 预装初值,与 TH1 一致 TR1 = 1; // 启动定时器 1 ES = 1; // 开启串口中断 EA = 1; // 开启总中断 } void RS485_SendString(unsigned char *str) { DE = 1; // 拉高 DE,进入发送模式 while (*str) { SBUF = *str++; // 写入待发字符 while (!TI); // 等待该字符发送完成 TI = 0; // 清除发送中断标志 } DE = 0; // 全部发送完成,切回接收模式 } void UART_ISR(void) interrupt 4 { unsigned char rx; if (RI) // 接收中断标志 { RI = 0; // 必须软件清零 rx = SBUF; // 读出收到的字节 P2 = rx; // 把数据送到 P2 口,方便仿真观察 if (rx == 'A') { RS485_SendString("RS485 OK\r\n"); } } }代码里TMOD &= 0x0F和TMOD |= 0x20两步是保持定时器 0 原配置不变,只把定时器 1 设成方式 2,这是工程化写法的习惯,避免直接赋值覆盖其他程序段对 T0 的设置。RS485_SendString里 DE 在整个字符串发送期间保持高电平,只在所有字节发完后拉低,目的是减少总线方向翻转次数,防止后续字节被截断。接收到字符A后原样返回RS485 OK\r\n,这个命令约定和串口助手下发测试命令是对应的。
中断函数里把rx送到 P2 口是调试技巧:仿真运行时观察 P2 口 LED 状态,就能直观看到收到的字符二进制值,不需要额外代码打印。
4.4 收发切换时隙与隐蔽问题
DE 拉高的时机必须在写入 SBUF 之前,因为 UART 空闲时 TXD 是高电平,MAX485 在发送模式下会把高电平放到 A-B 上,总线表现为正常的空闲状态。如果先写 SBUF 再拉高 DE,起始位已经过去一部分,PC 端会收到丢起始位的帧。发送结束后 DE 拉低也要在 TI 置位之后,因为 TI 表示整个停止位已经移出,严谨的做法是再延时半个位时间,约 52us(9600 波特率下 1 位约 104us),常见做法是加两个_nop_()或直接调用短延时函数。
仿真里经常遇到一种情况:单发一字节没问题,连续发送字符串时第一个字符丢了。原因通常是 DE 切换时总线驱动能力还没完全建立,MAX485 从接收态切到发送态需要一小段时间。手册里给出的是纳秒级,但实际布线电容会拉长,所以我的习惯是发送前 DE 拉高后加 1 个字节时间的延时,确保总线稳定再发数据。这一段在仿真里可能看不出差异,实物上却是稳定性的关键。
5. 一主多从组网、Modbus 帧接收与常见排错
5.1 一主多从组网与终端电阻
RS485 标准支持一条总线挂 32 个节点,实际工程中常用一主多从结构:PC 或者 PLC 做主站,多个 51 单片机做从站,所有节点的 A 接 A、B 接 B,接线采用手拉手菊花链,不要用星型拓扑,否则信号反射会非常明显。总线两端各并一个 120 欧终端电阻,中间节点不接。仿真里只挂一个节点时,终端电阻可以少接一端,但实物不加会看到长线末端波形回勾。
地址区分要在协议层做,硬件上所有节点的收发器都监听总线,只有地址匹配才响应。最简单的做法是每个从站用一个 8 位拨码开关存地址,主站发送地址帧,从站比对后再发数据帧。
5.2 Modbus 帧接收与粘包处理
RS485 只解决物理层传输,上层协议最常用的是 Modbus。Modbus RTU 以 3.5 个字符时间作为帧间隔,一帧结束后总线静默,从站据此判断一帧数据完整。9600 波特率下 1 个字符约 1.04ms,3.5 个字符约 3.65ms。用定时器 0 做超时计时,每收到一个字节清零重计,主循环里发现超时后解析缓冲区。
void UART_ISR(void) interrupt 4 { if (RI) { RI = 0; rx_buf[ptr++] = SBUF; // 存入接收缓冲区 timeout = 0; // 重置超时计数 } }主循环里每毫秒对timeout加 1,当timeout > 4且ptr > 0时认为一帧结束,清空缓冲区准备下一帧。参数 4 对应约 4ms,略大于 3.65ms,既不会把一帧拆开,也不会合并相邻两帧。注意如果波特率改成 4800,3.5 字符时间变成约 7.3ms,这个超时阈值也要相应翻倍。
5.3 波特率 4800 与 9600 切换的坑
“9600 能通信,4800 没有数据”是串口调试里常见的问题。先检查 TH1 初值,9600 用 0xFD,4800 用 0xFA,同时确认 PCON 的 SMOD 为 0。很多项目在初始化时为了把 9600 变成 19200 会把 PCON 设为 0x80,后来改波特率只改了 TH1 没改 PCON,实际速率变成 9600,和 PC 端的 4800 对不上。
还有一个隐蔽点是晶振频率。Proteus 工程里晶振必须显式设置为 11.0592MHz,如果用默认 12MHz,850 波特率的 TH1=0xFD 计算出的实际波特率与目标值偏差大,仿真偶尔也能通,但连续收发几个字节就会出错。最后分享一个调试手法:在 Proteus 里同时打开示波器测量 DE、TXD 和 A-B 电压,PC 串口助手按十六进制显示收到的数据,从低波特率到高波特率逐档切换,当某一档出现首字节 0x00 或帧错误时,重点检查 DE 拉低时机和终端电阻,而不是盲目改寄存器。
本文还有配套的精品资源,点击获取