1. 51单片机串口通信基础概述
作为嵌入式开发中最基础也最重要的通信方式,串口通信在51单片机项目中扮演着关键角色。我第一次接触51串口是在大二的一个温度监测项目,当时为了把DS18B20采集的数据传到电脑显示,折腾了整整三天才调通。这种"痛并快乐着"的经历,相信每位嵌入式开发者都深有体会。
串口通信本质上是利用UART(Universal Asynchronous Receiver/Transmitter)实现的一种异步串行通信方式。其核心特点包括:
- 仅需两根信号线(TXD发送,RXD接收)即可实现全双工通信
- 不需要时钟信号,依靠预先约定的波特率实现同步
- 数据以帧为单位传输,每帧包含起始位、数据位、可选的校验位和停止位
在STC89C52这类典型51单片机中,UART相关的重要寄存器包括:
- SCON(串口控制寄存器):配置工作模式、使能接收等
- SBUF(串口数据缓冲器):实际存储发送/接收的数据
- PCON(电源控制寄存器):包含SMOD位用于波特率加倍
- IE(中断使能寄存器):控制串口中断的开关
实际开发中常见误区:很多初学者会忽略TMOD寄存器的配置,导致定时器1无法正常工作,进而使串口通信失败。正确的做法是使用"TMOD &= 0x0F; TMOD |= 0x20;"来确保只修改定时器1的配置而不影响定时器0。
2. 硬件连接与寄存器深度解析
2.1 硬件接线规范
正确的硬件连接是串口通信的基础。以STC89C52通过CH340芯片与PC通信为例:
- 单片机TXD(P3.1)接CH340的RXD
- 单片机RXD(P3.0)接CH340的TXD
- 共地连接必不可少(GND对接)
- 建议在信号线上串联100Ω电阻防止过冲

2.2 关键寄存器详解
SCON寄存器(地址:0x98)各位定义:
code复制SM0 SM1 SM2 REN TB8 RB8 TI RI
- SM0-SM1:工作模式选择(01对应模式1)
- REN:接收使能(1允许接收)
- TB8/RB8:模式2/3的校验位
- TI/RI:发送/接收中断标志
SBUF寄存器(地址:0x99)的特殊性:
- 物理上是两个独立寄存器(发送SBUF和接收SBUF)
- 共用同一地址,通过读写操作自动区分
- 写入数据触发发送,读取数据获取接收内容
配置示例:
c复制SCON = 0x50; // 模式1,允许接收
PCON |= 0x80; // SMOD=1,波特率加倍
3. 工作模式与波特率配置
3.1 四种工作模式对比
STC89C52的UART支持四种工作模式:
| 模式 | 数据位 | 波特率 | 典型应用 |
|---|---|---|---|
| 0 | 8位 | fosc/12 | 同步移位寄存器 |
| 1 | 8位 | 可变 | 标准异步通信 |
| 2 | 9位 | fosc/32或/64 | 多机通信 |
| 3 | 9位 | 可变 | 多机通信 |
模式1是最常用的8位UART模式,波特率由定时器1溢出率决定,灵活性最高。我在实际项目中90%的情况都使用模式1。
3.2 波特率精确计算
波特率计算公式(模式1):
code复制波特率 = (2^SMOD / 32) × (定时器1溢出率)
定时器1溢出率 = fosc / (12 × (256 - TH1))
以11.0592MHz晶振、9600波特率为例:
- 选择SMOD=0(PCON.7)
- 计算TH1 = 256 - 11059200/(12×32×9600) = 253 (0xFD)
初始化代码:
c复制void UART_Init() {
TMOD &= 0x0F; // 清零定时器1模式位
TMOD |= 0x20; // 定时器1模式2(8位自动重载)
TH1 = 0xFD; // 9600@11.0592MHz
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
SCON = 0x50; // 模式1,允许接收
}
4. 数据发送实现与优化
4.1 基础发送流程
- 将数据写入SBUF寄存器
- 硬件自动添加起始位和停止位并发送
- 发送完成后TI标志置1
- 软件清零TI后可以发送下一字节
单字节发送函数:
c复制void UART_SendByte(unsigned char dat) {
SBUF = dat;
while(!TI); // 等待发送完成
TI = 0; // 清除标志
}
4.2 字符串发送优化
直接调用单字节发送函数实现字符串发送:
c复制void UART_SendString(unsigned char *str) {
while(*str != '\0') {
UART_SendByte(*str++);
}
}
实际调试中发现的问题:当连续快速发送大量数据时,可能出现数据丢失。解决方案是加入流控或降低发送速率。我在一个工业传感器项目中就遇到过这个问题,最终通过增加50ms的发送间隔解决了数据丢失问题。
5. 数据接收的两种实现方式
5.1 查询方式接收
c复制unsigned char UART_ReceiveByte() {
while(!RI); // 等待接收完成
RI = 0;
return SBUF;
}
5.2 中断方式接收
初始化时开启中断:
c复制ES = 1; // 串口中断使能
EA = 1; // 全局中断使能
中断服务函数:
c复制void UART_ISR() interrupt 4 {
if(RI) {
RI = 0;
unsigned char received = SBUF;
// 处理接收到的数据
}
}
中断方式相比查询方式更高效,特别是在需要同时处理其他任务的系统中。但要注意中断服务函数应尽量简短,避免影响系统实时性。我曾在一个多任务系统中因为中断处理函数过于复杂导致系统响应迟缓,最终通过状态机重构解决了问题。
6. 工程实践中的高级应用
6.1 printf重定向实现
通过重写putchar函数实现printf到串口的输出:
c复制#include <stdio.h>
char putchar(char c) {
SBUF = c;
while(!TI);
TI = 0;
return c;
}
// 使用示例
printf("Temperature: %.1fC\r\n", temp);
6.2 数据帧协议设计
简单的数据帧格式示例:
code复制[头字节0xAA][长度][数据...][校验和]
校验和计算函数:
c复制unsigned char CheckSum(unsigned char *buf, unsigned char len) {
unsigned char sum = 0;
while(len--) {
sum += *buf++;
}
return sum;
}
6.3 波特率自适应尝试
通过测量起始位宽度自动检测波特率:
c复制unsigned long DetectBaudrate() {
unsigned long t;
while(RXD); // 等待低电平
TR1 = 1; // 启动定时器1
while(!RXD); // 等待高电平
TR1 = 0; // 停止定时器
t = TH1<<8 | TL1; // 获取计数值
return 11059200L / 12 / t;
}
7. 常见问题与调试技巧
7.1 典型问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无任何通信 | 接线错误 | 检查TXD/RXD交叉连接 |
| 收到乱码 | 波特率不匹配 | 检查双方波特率设置 |
| 只能收不能发 | TI未清零 | 发送后及时清除TI标志 |
| 数据丢失 | 发送过快 | 增加发送间隔或实现流控 |
7.2 示波器调试技巧
-
测量TXD引脚波形,确认:
- 起始位(低电平)是否完整
- 波特率是否准确(位宽度=1/波特率)
- 停止位(高电平)是否正常
-
常见异常波形分析:
- 电平不完整:检查上拉电阻
- 波形畸变:检查线路干扰
- 时序偏移:检查晶振精度
7.3 软件调试心得
-
使用串口调试助手时,注意:
- 选择正确的COM端口
- 设置匹配的波特率
- 勾选"十六进制显示"查看原始数据
-
在代码中添加调试输出:
c复制printf("Debug: value=%d\r\n", var);
- 利用LED指示状态:
c复制if(RI) {
LED = ~LED; // 接收指示灯
RI = 0;
}
通过系统性地掌握51单片机串口通信的原理和实现,开发者可以构建稳定可靠的嵌入式通信系统。从最初的寄存器配置到后期的协议设计,每个环节都需要严谨的态度和反复的实践验证。记住,在嵌入式开发中,没有"差不多"的概念——要么100%正确,要么完全不能用。这种追求精确的精神,正是嵌入式工程师的核心素养。