一、系统概述
基于STC89C52RC单片机(8位,11.0592MHz晶振),实现RS232全双工通信与RS485半双工通信双模式。系统通过UART串口连接MAX232(RS232电平转换)和MAX485(RS485差分转换),支持双机通信(点对点)与总线通信(多点),具备数据收发、帧格式自定义、收发切换控制功能,适用于工业控制、仪器仪表通信等场景。
二、硬件设计
2.1 核心组件选型
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| 主控 | STC89C52RC(8KB Flash,512B RAM) | UART通信控制、RS232/RS485模式切换 |
| RS232转换 | MAX232(电荷泵电平转换) | 将单片机TTL电平(0~5V)转换为RS232电平(±12V) |
| RS485转换 | MAX485(差分收发器) | 将TTL电平转换为RS485差分信号(A/B线) |
| 通信接口 | DB9(RS232)、凤凰端子(RS485) | 连接PC或工业总线设备 |
2.2 硬件连接图
+-------------------+ +-------------------+ +-------------------+
| STC89C52RC | | MAX232 | | PC/RS232设备 |
| P3.1(TXD) → MAX232|T1IN | T1OUT → DB9-2(RXD) | | DB9-3(TXD) → MAX232|R1IN |
| P3.0(RXD) ← MAX232|R1OUT | R1IN ← DB9-3(TXD) | | DB9-2(RXD) ← MAX232|T1OUT |
| P3.2(CTRL) → MAX485|DE/RE | | +-------------------+
| P3.1(TXD) → MAX485|DI | | +-------------------+
| P3.0(RXD) ← MAX485|RO | | | MAX485总线 |
+-------------------+ +-------------------+ | A/B线 → 其他节点 |
+-------------------+
关键引脚说明:
MAX232:T1IN(接单片机TXD)、R1OUT(接单片机RXD),实现RS232电平转换。
MAX485:DI(接单片机TXD,发送数据)、RO(接单片机RXD,接收数据)、DE/RE(方向控制,高电平发送,低电平接收),由单片机P3.2引脚控制。
三、软件设计(Keil C51)
3.1 开发环境
IDE:Keil μVision5(C51编译器)
晶振:11.0592MHz(确保波特率误差<1%)
通信协议:8位数据位、1位停止位、无校验位(8N1),波特率9600bps
3.2 核心原理
UART初始化:配置定时器1为模式2(8位自动重装),作为波特率发生器,计算TH1初值(11.0592MHz下,9600bps对应TH1=0xFD)。
RS232通信:全双工模式,直接通过TXD/RXD收发数据,无需方向控制。
RS485通信:半双工模式,发送前置DE/RE=1(发送模式),发送完毕后置DE/RE=0(接收模式),避免总线冲突。
3.3 核心代码实现
3.3.1 头文件与全局变量
#include <reg52.h>
#include <intrins.h>
// ==================== 引脚定义 ====================
#define RS485_DE_RE P3_2 // RS485方向控制引脚(高=发送,低=接收)
#define UART_RX_BUF_SIZE 16 // 接收缓冲区大小
#define UART_TX_BUF_SIZE 16 // 发送缓冲区大小
// ==================== 全局变量 ====================
unsigned char uart_rx_buf[UART_RX_BUF_SIZE]; // 接收缓冲区
unsigned char uart_tx_buf[UART_TX_BUF_SIZE]; // 发送缓冲区
unsigned char rx_cnt = 0; // 接收计数
unsigned char tx_cnt = 0; // 发送计数
bit rx_flag = 0; // 接收完成标志(1=接收一帧完成)
3.3.2 UART初始化(定时器1波特率发生器)
// UART初始化(9600bps,8N1)
void UART_Init() {
SCON = 0x50; // 模式1(8位UART),允许接收(REN=1)
TMOD |= 0x20; // 定时器1模式2(8位自动重装)
TH1 = 0xFD; // 波特率9600bps(11.0592MHz:(2^SMOD/32)*(fosc/(12*(256-TH1))) → SMOD=0时,TH1=253=0xFD)
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
ES = 1; // 允许串口中断
EA = 1; // 开总中断
}
3.3.3 RS232发送函数(查询模式)
// RS232发送一个字节
void RS232_SendByte(unsigned char dat) {
SBUF = dat; // 数据写入发送缓冲区
while (!TI); // 等待发送完成(TI=1)
TI = 0; // 清除发送中断标志
}
// RS232发送字符串
void RS232_SendString(unsigned char *str) {
while (*str != '\0') {
RS232_SendByte(*str++);
}
}
3.3.4 RS485发送函数(含方向控制)
// RS485发送一个字节
void RS485_SendByte(unsigned char dat) {
RS485_DE_RE = 1; // 置高电平,切换到发送模式
DelayUs(10); // 延时10μs,确保方向切换完成(关键!)
SBUF = dat; // 发送数据
while (!TI); // 等待发送完成
TI = 0;
DelayUs(10); // 延时10μs,确保最后一个字节发送完成
RS485_DE_RE = 0; // 置低电平,切换到接收模式
}
// RS485发送字符串
void RS485_SendString(unsigned char *str) {
RS485_DE_RE = 1; // 发送模式
DelayUs(10);
while (*str != '\0') {
SBUF = *str++;
while (!TI);
TI = 0;
}
DelayUs(10);
RS485_DE_RE = 0; // 接收模式
}
3.3.5 串口中断服务函数(接收数据)
// 串口中断(接收数据)
void UART_ISR() interrupt 4 {
if (RI) {
// 接收中断标志
RI = 0; // 清除接收标志
uart_rx_buf[rx_cnt++] = SBUF; // 存入接收缓冲区
if (rx_cnt >= UART_RX_BUF_SIZE) rx_cnt = 0; // 缓冲区溢出保护
if (SBUF == '\n') rx_flag = 1; // 检测到换行符,标记接收完成(可自定义帧尾)
}
}
3.3.6 延时函数(微秒级)
// 微秒延时(11.0592MHz晶振,1μs≈12个机器周期)
void DelayUs(unsigned int us) {
while (us--) {
_nop_(); _nop_(); _nop_(); _nop_(); // 4个_nop_≈1μs(误差±1μs)
_nop_(); _nop_(); _nop_(); _nop_();
}
}
3.3.7 主函数(测试逻辑)
void main() {
UART_Init(); // 初始化UART
RS485_DE_RE = 0; // 初始为接收模式(RS485)
while (1) {
// 示例1:RS232接收PC数据并回传(Echo)
if (rx_flag) {
rx_flag = 0;
RS232_SendString("RS232 Recv: ");
RS232_SendString(uart_rx_buf); // 回传接收数据
rx_cnt = 0; // 清空缓冲区
}
// 示例2:RS485定时发送数据(每2秒发送一次)
static unsigned int timer = 0;
if (++timer >= 2000) {
// 2秒(假设主循环1ms一次)
timer = 0;
RS485_SendString("RS485 Test: Hello!\r\n"); // 发送测试字符串
}
DelayMs(1); // 主循环延时1ms(需实现DelayMs函数)
}
}
// 毫秒延时(粗略)
void DelayMs(unsigned int ms) {
unsigned int i, j;
for (i=0; i<ms; i++)
for (j=0; j<110; j++); // 110次循环≈1ms(11.0592MHz)
}
参考代码 基于51单片机的串口(RS232+485)通信程序 www.youwenfan.com/contentalh/182671.html
四、关键问题与解决方案
4.1 波特率误差
问题:51单片机定时器1重载值计算错误导致波特率偏差(如12MHz晶振下9600bps误差大)。
解决:使用11.0592MHz晶振(可被1.8432MHz整除,波特率误差为0),计算公式:
$TH1=256−\frac{fosc}{12×32×波特率}$
(9600bps时,TH1=256 - 11059200/(12×32×9600)=256-3=253=0xFD)。
4.2 RS485收发冲突
问题:发送未完成时切换为接收模式,导致数据丢失。
解决:
发送前后添加10μs延时(确保MAX485方向切换完成);
总线末端并联120Ω终端电阻,减少信号反射。
4.3 接收数据溢出
问题:缓冲区过小或中断未及时响应导致数据覆盖。
解决:
增大缓冲区(如32字节);
使用环形缓冲区(头指针+尾指针),避免溢出覆盖。
五、测试与验证
5.1 RS232测试
硬件连接:单片机TXD/RXD→MAX232→DB9串口线→PC串口。
工具:PC端使用串口助手(如SSCOM),设置波特率9600、8N1。
预期结果:PC发送“Hello”,单片机回传“RS232 Recv: Hello”。
5.2 RS485测试
硬件连接:两台单片机通过MAX485模块连接(A-A相连,B-B相连),终端电阻120Ω。
测试步骤:主机定时发送“RS485 Test: Hello!”,从机接收后通过RS232转发至PC串口助手。
预期结果:从机串口助手显示主机发送的测试字符串,无丢包。
六、总结
基于51单片机实现了RS232/RS485双串口通信,核心是UART初始化、波特率精确控制与RS485方向切换时序。通过模块化设计(发送/接收分离、中断处理),可扩展为Modbus RTU协议(工业总线常用),适用于多节点数据采集与控制系统。