串口通信作为51单片机最基础也最重要的外设功能之一,几乎出现在所有需要数据交互的应用场景中。从简单的调试信息输出到复杂的设备间通信,串口都扮演着关键角色。我使用51单片机开发已有八年时间,今天就来详细拆解这个看似简单却暗藏玄机的通信模块。
串口通信的本质是异步串行通信,数据以位为单位依次传输。51单片机内部集成的UART(通用异步收发传输器)模块,通过配置相关寄存器即可实现全双工通信。这里需要特别注意的是"全双工"特性——发送和接收可以同时进行,这在实际编程时往往会带来一些有趣的并发问题。
通信双方需要严格约定三个核心参数:波特率、数据位格式和停止位。其中波特率的选择尤为关键,常见的9600bps到115200bps各有适用场景。低速波特率抗干扰强但效率低,高速则反之。在我的项目经验中,室内环境推荐38400bps,工业环境则建议4800bps或9600bps。
正确的硬件连接是通信成功的前提。51单片机的串口引脚通常标记为TXD(P3.1)和RXD(P3.0)。连接时必须遵守"交叉互联"原则:
注意:很多初学者会犯的直接连接TXD-TXD的错误,这会导致通信完全失败。我曾在一个工业项目中花了三小时排查才发现是这个低级错误。
对于电平匹配,51单片机是TTL电平(0-5V),若连接RS232设备(±12V)必须经过MAX232等电平转换芯片。现在越来越多的设备采用3.3V电平,此时要注意51单片机的5V输出可能会损坏3.3V设备,建议添加电平转换电路或使用5V兼容的3.3V器件。
串口相关的核心寄存器有四个:SCON、PCON、TMOD和TCON。下面这段初始化代码值得逐行分析:
c复制void Uart_Init(void) {
TMOD = 0x20; // 定时器1模式2:8位自动重装
TH1 = 0xFD; // 9600bps@11.0592MHz
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
SCON = 0x50; // 模式1,允许接收
PCON = 0x00; // SMOD=0,波特率不加倍
}
定时器1用作波特率发生器,模式2(8位自动重装)是最常用的选择。初值计算公式为:
code复制初值 = 256 - (晶振频率)/(12×32×波特率)
以11.0592MHz晶振、9600bps为例:
code复制11059200 / 12 / 32 / 9600 = 3
256 - 3 = 253 = 0xFD
这个特殊频率(11.0592MHz)能产生精确的波特率,如果用12MHz晶振计算9600bps时会出现误差:
code复制12000000 / 12 / 32 / 9600 ≈ 3.2552
256 - 3.2552 ≈ 252.7448 → 取整253
实际波特率 = 12000000/12/32/(256-253) ≈ 10416bps (8.5%误差)
因此在实际项目中,我强烈建议使用11.0592MHz晶振,特别是需要精确波特率的场合。
先看最基本的发送函数实现:
c复制void Send_Byte(unsigned char dat) {
SBUF = dat; // 数据写入发送缓冲器
while (!TI); // 等待发送完成
TI = 0; // 清除发送中断标志
}
这个看似简单的函数有几个关键点:
接收函数同理:
c复制unsigned char Receive_Byte(void) {
while (!RI); // 等待接收完成
RI = 0; // 清除接收中断标志
return SBUF; // 读取接收数据
}
警示:我曾遇到过一个隐蔽的bug——在高温环境下RI标志偶尔会误触发。解决方案是读取SBUF前先检查RI,确认是真实数据而非干扰。
上述代码采用查询方式(轮询TI/RI标志),其特点是:
在115200bps高速通信时,每字节传输时间约87μs,查询方式可能错过数据。此时推荐改用中断方式:
c复制void Uart_ISR() interrupt 4 {
if (RI) {
RI = 0;
// 处理接收数据
}
if (TI) {
TI = 0;
// 发送完成处理
}
}
中断方式能及时响应通信事件,但要注意:
当不得不使用非标准晶振时,波特率误差需要特别关注。根据UART协议规范:
我曾用12MHz晶振实现4800bps通信(误差0.16%可行),但9600bps时误差达8.5%导致频繁出错。解决方案有:
当通信出现乱码时,建议按以下步骤排查:
常见故障现象与对策:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 接收全为0 | RXD未连接 | 检查线路 |
| 接收乱码 | 波特率不匹配 | 重新计算初值 |
| 偶发错误 | 线路干扰 | 缩短线距,加屏蔽 |
| 无法发送 | TXD损坏 | 更换单片机 |
在高速或大数据量传输时,简单的单字节收发会丢失数据。推荐采用环形缓冲区:
c复制#define BUF_SIZE 64
unsigned char rx_buf[BUF_SIZE];
unsigned char rx_head = 0, rx_tail = 0;
void Uart_ISR() interrupt 4 {
if (RI) {
RI = 0;
rx_buf[rx_head++] = SBUF;
rx_head %= BUF_SIZE;
}
// ...TI处理...
}
unsigned char Read_Byte() {
while (rx_head == rx_tail); // 等待数据
unsigned char c = rx_buf[rx_tail++];
rx_tail %= BUF_SIZE;
return c;
}
对于更高要求的应用,可以引入硬件流控制(RTS/CTS),但这需要额外的硬件线路支持。
通过USB转TTL模块连接电脑时,需要注意:
调试时可先发送固定字符串测试:
c复制void Send_String(char *s) {
while (*s) {
Send_Byte(*s++);
}
}
void main() {
Uart_Init();
Send_String("UART Test\r\n");
while(1);
}
51单片机支持多机通信模式(SCON的SM2位),典型应用包括:
配置要点:
示例代码片段:
c复制// 从机初始化
SCON = 0xF0; // 模式3,SM2=1,允许接收
// 主机发送地址
TB8 = 1; // 地址帧标志
SBUF = slave_addr;
while (!TI);
TI = 0;
TB8 = 0; // 数据帧标志
裸机串口通信建议设计简单协议,例如:
帧格式:
| 起始符 | 长度 | 数据 | 校验 | 结束符 |
|---|---|---|---|---|
| 0xAA | 1字节 | N字节 | 1字节 | 0x55 |
校验可采用累加和或CRC8。我在实际项目中总结的协议设计原则:
一个完整的通信程序往往需要结合定时器实现超时重发、错误重试等机制,这些都是在基础通信之上必须考虑的工程实践。