# 串行之路:UART通信原理、参数体系与51单片机实战精要
串口是嵌入式系统中最基础、最顽强的通信接口。其硬件简单,两根信号线即可实现双向通信;其协议透明,从51单片机到ARM、PC串口助手均可直接互联。然而许多开发者调通串口后便不再深究:起始位为何是低电平?波特率误差容忍范围多少?奇偶校验如何在中断中处理?本文从异步串行通信的帧结构出发,逐层解析波特率发生器、寄存器配置、中断架构,最终落地为可复用的工程代码。
---
## 一、异步串行通信的帧解剖
UART通信以**字符帧**为基本单元。帧结构由起始位、数据位、可选校验位、停止位拼接而成:
**起始位**:1位,低电平。总线空闲时为高电平,下降沿标志接收开始。
**数据位**:5~8位,低位先行。51单片机模式1固定为8位数据。
**校验位**:奇/偶/无。奇校验使“数据位+校验位”中1的个数为奇数。
**停止位**:1或2位,高电平。为下一帧起始位的下降沿提供准备时间。
**时序关系**:每帧时长 = (1起始 + 数据位 + 校验位 + 停止位) × 位时间。9600bps下8N1格式每帧约1.04ms。
**理解帧结构是诊断串口问题的前提**。乱码常因波特率对位误差累计;帧错位常因停止位丢失或干扰被误认为起始位。
---
## 二、波特率发生器:从公式到初值
51单片机UART的波特率源自**定时器1的溢出率**。模式1下:
```
波特率 = (2^SMOD / 32) × 定时器1溢出率
```
定时器1方式2(8位自动重装)时:
```
溢出率 = 晶振频率 / (12 × (256 - TH1))
```
**工程公式**:
```
TH1 = 256 - 晶振频率 / (波特率 × 12 × (32 / 2^SMOD))
```
常用配置(11.0592MHz):
| 波特率 | SMOD | TH1初值 | 误差 |
|--------|------|---------|-------|
| 4800 | 0 | 0xFA | 0.00% |
| 9600 | 0 | 0xFD | 0.00% |
| 19200 | 1 | 0xFD | 0.00% |
| 115200 | 1 | 0xFF | 8.5% |
**误差容忍**:串口异步通信要求波特率误差 ≤ ±2.5%。115200在11.0592MHz下误差8.5%已不可靠,需换用22.1184MHz晶振或硬件串口独立波特率发生器。
---
## 三、寄存器配置:SCON与PCON
**SCON**:串口控制寄存器,地址0x98,可位寻址。
```c
SCON = 0x50; // 0101 0000
// SM0 SM1 SM2 REN TB8 RB8 TI RI
// 0 1 0 1 0 0 0 0
```
- SM0/SM1 = 01:模式1,8位UART,波特率可变
- REN = 1:允许接收
- TI/RI:发送/接收中断标志,硬件置位,**必须软件清零**
<"y3.a8k1.org.cn"><"e7.a8k1.org.cn"><"u9.a8k1.org.cn">
**PCON**:电源控制寄存器,SMOD位(PCON.7)控制波特率倍频。
```c
PCON &= 0x7F; // SMOD = 0,波特率正常
PCON |= 0x80; // SMOD = 1,波特率加倍
```
**SBUF**:串口缓冲寄存器。物理上为**两个独立寄存器**——写SBUF启动发送,读SBUF获取接收数据。
---
## 四、收发架构:查询发送 + 中断接收
**查询发送**:TI标志轮询,实现简单。
```c
void UART_SendByte(unsigned char dat) {
SBUF = dat;
while (!TI); // 等待发送完成
TI = 0; // 软件清零
}
```
**中断接收**:响应及时,不阻塞主循环。
```c
void UART_ISR(void) interrupt 4 {
if (RI) {
RI = 0;
unsigned char rx = SBUF;
process_rx_byte(rx); // 仅状态机流转,绝不阻塞
}
// TI中断通常不在ISR中处理,或仅置标志位
}
```
**收发互锁**:发送操作在中断开启时也会触发TI中断。若中断函数内未判断TI/RI且无TI处理,将产生**冗余中断**。规范做法是ISR中仅处理RI,TI由查询方式管理。
---
## 五、协议层:帧边界与状态机
字节收发是物理层能力,**协议层需解决帧同步问题**。两种工程模式:
**模式A:固定帧长 + 定时超时**
接收固定字节数后认定一帧完整。适用于指令长度恒定的场景。
```c
#define FRAME_LEN 8
unsigned char rx_buffer[FRAME_LEN];
unsigned char rx_index = 0;
void process_rx_byte(unsigned char dat) {
rx_buffer[rx_index++] = dat;
if (rx_index >= FRAME_LEN) {
rx_index = 0;
frame_ready = 1;
}
}
```
**模式B:特殊字符定界 + 状态机**
以帧头帧尾标识边界,兼容变长帧。常用格式:`AA CMD LEN DATA ... SUM 55`
```c
typedef enum { IDLE, HEAD1, HEAD2, CMD, LEN, DATA, CH K } RxState;
RxState rx_state = IDLE;
unsigned char rx_sum, rx_len, rx_cnt, rx_cmd;
unsigned char rx_payload[16];
void process_rx_byte(unsigned char dat) {
switch (rx_state) {
case IDLE: if (dat == 0xAA) rx_state = HEAD1; break;
case HEAD1: rx_state = (dat == 0x55) ? HEAD2 : IDLE; break;
case HEAD2: rx_cmd = dat; rx_sum = dat; rx_state = CMD; break;
case CMD: rx_len = dat; rx_sum += dat; rx_cnt = 0;
rx_state = (rx_len > 0) ? DATA : CH K; break;
case DATA: rx_payload[rx_cnt++] = dat; rx_sum += dat;
if (rx_cnt >= rx_len) rx_state = CH K; break;
case CH K: if (dat == rx_sum) { /* 帧有效,执行命令 */ }
rx_state = IDLE; break;
default: rx_state = IDLE; break;
}
}
```
**状态机优势**:无需帧级缓冲区,每字节处理时间O(1),**与帧长度无关**。
---
## 六、工程优化与常见陷阱
**晶振选择**:11.0592MHz是串口通信的黄金频率——其分频可精确产生多种标准波特率。12MHz虽为整数,但9600bps误差达8.5%,高波特率下不可用。
**波特率自适应**:某些系统需检测主机波特率。原理:主机发送0x55(二进制01010101),从机测量相邻下降沿间隔计算位时间。
**发送阻塞优化**:大批量数据发送时`while(!TI)`占用CPU。可改为**发送中断驱动环形队列**:主循环或定时器填充缓冲区,ISR取下一字节发送。
```c
// 简化示意:TI中断中发送缓冲区下一字节
if (TI) {
TI = 0;
if (tx_head != tx_tail) {
SBUF = tx_buffer[tx_tail++];
}
}
```
<"b2.a8k1.org.cn"><"x5.a8k1.org.cn"><"r1.a8k1.org.cn">
**电气噪声**:长线通信需加0.1μF电容对地,或使用MAX232转换RS-232电平。TTL电平直连超过50cm易产生误码。
---
## 七、从配置到框架
掌握SCON、TMOD、TH1是串口入门的起点,但**工程级串口驱动应是可配置、可移植、低耦合的框架**。将波特率计算、中断处理、环形缓冲区、协议解析封装为独立模块,上层业务无需关注SBUF何时写入、TI何时清零。
串口是嵌入式的“标准外设”,其原理三十余年未变。理解帧结构才能诊断乱码,掌握波特率生成才能自由移植,封装协议层才能使串口真正服务于业务。**从调通到精通,正是将寄存器操作抽象为通信服务的过程**。