串口通信是单片机与外部设备交互最常用的方式之一。无论是调试打印、固件烧录,还是控制继电器、连接蓝牙模块,都离不开它。本文基于STC89C52单片机,结合多个实战代码和硬件图解,带你彻底搞懂51单片机的串口通信------包括发送、接收、数据回环以及通过串口命令控制外设。
一、串口通信基础:UART是什么?
UART (Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种双向、串行、异步的通信总线,仅用一根数据接收线(RX)和一根数据发送线(TX)就能实现全双工通信------即同时收发数据。
如图"串口介绍"所示,一帧数据由起始位(低电平0) 、数据位(5~8位) 、奇偶校验位(可选) 、停止位(高电平1) 组成,之后跟随空闲位(高电平1)。收发双方必须约定相同的波特率、数据位、停止位和校验位才能正确通信。


1.1 常见电平标准
-
TTL电平:单片机直接输出。逻辑0为0V,逻辑1为5V(或3.3V)。通信距离短(几十米内),但无需转换芯片,如蓝牙模块、GPS模块。
-
RS-232电平:逻辑1为-5V~-15V,逻辑0为+5V~+15V。抗干扰强,传输距离可达30米。电脑的DB9串口就是RS-232,需要MAX232芯片转换。
-
RS-485:差分信号,传输距离上千米,适合工业现场。
如图"TTL电平"和"RS-232"所示,两者不能直连,必须通过电平转换。



二、51单片机串口硬件及引脚
STC89C52单片机有一个全双工串口(UART),通过P3.0(RXD)和P3.1(TXD)引脚收发数据。内部结构如"数据是怎么发送的?"图片所示:
-
发送缓冲器SBUF(写操作) → 移位寄存器 → TXD引脚发送。
-
接收移位寄存器 → 接收缓冲器SBUF(读操作) ← RXD引脚接收。
-
两个SBUF地址相同(0x99),但物理上是独立的。


三、核心寄存器详解(代码中的配置)
初始化串口必须配置以下寄存器,对照图片"串行控制寄存器SCON"、"PCON"、"TMOD"。
3.1 SCON ------ 串行控制寄存器(可位寻址,地址0x98)
| 位 | B7(SM0/FE) | B6(SM1) | B5(SM2) | B4(REN) | B3(TB8) | B2(RB8) | B1(TI) | B0(RI) |
|---|---|---|---|---|---|---|---|---|
| 值 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
-
SM0=0,SM1=1 :选择工作方式1(8位UART,波特率可变)。这也是最常用的方式。
-
REN=1:允许接收数据。
-
TI:发送中断标志。发送完一帧数据后硬件自动置1,必须软件清零。
-
RI:接收中断标志。接收完一帧数据后硬件自动置1,必须软件清零。
代码中的初始化:SCON = 0x50; → 二进制0101 0000,正是方式1 + 允许接收。

3.2 PCON ------ 电源控制寄存器(不可位寻址,地址0x87)
| 位 | B7(SMOD) | B6(SMOD0) | ... |
|---|---|---|---|
| 值 | 0 | 0 |
- SMOD :波特率加倍控制位。
PCON &= 0x7F;将SMOD清零,使用不加倍模式。

3.3 TMOD ------ 定时器工作模式寄存器(地址0x89)
我们使用定时器1(T1)产生波特率时钟。
| 位 | 7(GATE) | 6(C/T) | 5(M1) | 4(M0) | 3~0(定时器0) |
|---|---|---|---|---|---|
| 值 | 0 | 0 | 1 | 0 | 无关 |
-
M1=1,M0=0 :定时器1工作在8位自动重装模式(模式2)。
-
C/T=0:定时模式(计数内部时钟)。
代码:
cpp
TMOD &= 0x0F; // 清空高4位(定时器1部分)
TMOD |= 0x20; // 设置M1=1, M0=0

3.4 TH1 / TL1 ------ 波特率初值
波特率计算公式(方式1,不加倍,12T模式):
cpp
波特率 = (2^SMOD / 32) × (定时器1溢出率)
定时器1溢出率 = 系统时钟 / 12 / (256 - TH1)
对于11.0592MHz晶振,要得到9600bps,计算:
cpp
9600 = (1/32) × (11.0592e6 / 12 / (256 - TH1))
=> 256 - TH1 = 11.0592e6 / 12 / 32 / 9600 ≈ 3
=> TH1 = 253 = 0xFD
代码:TH1 = 0xFD; TL1 = 0xFD; TL1初值任意,因为自动重装时TH1会重新加载到TL1。
对照图片"常用波特率与定时器/计数器1各参数关系",11.0592MHz、9600bps、SMOD=0时重装值0xFD。

四、串口初始化完整步骤(代码逐行解析)
cpp
void main() {
SCON = 0x50; // 方式1,允许接收
PCON &= 0x7F; // 波特率不加倍
TMOD &= 0x0F; // 清空定时器1部分
TMOD |= 0x20; // 定时器1工作模式2(8位自动重装)
TH1 = 0xFD; // 初值高位
TL1 = 0xFD; // 初值低位(自动重装,写入任意值)
TR1 = 1; // 启动定时器1
ES = 1; // 使能串口中断
EA = 1; // 总中断使能
// 然后进入主循环...
}
补充知识:如果不使用中断而是查询方式,可以不开ES和EA,但在接收数据时需不断检测RI标志。
五、发送数据:字节与字符串
5.1 发送一个字节
cpp
void UART_Send_Byte(uchar send_byte) {
SBUF = send_byte; // 数据写入发送缓冲器,立即启动发送
while(!TI); // 等待TI变1(发送完成)
TI = 0; // 软件清标志
}
原理:写SBUF后,硬件自动将数据移位到TXD引脚,按波特率逐位发送。发送完一帧(包括停止位)后,硬件置位TI。
5.2 发送字符串
cpp
void UART_Send_Str(uchar *send_str) {
while(*send_str != '\0') {
UART_Send_Byte(*send_str++);
}
}
补充知识 :字符串以空字符\0结尾,例如"I am god!\r\n"中的\r\n是回车换行,使串口助手自动换行。
5.3 主循环中循环发送
cpp
while(1) {
UART_Send_Str("I am god!\r\n");
Delay_xms(1000);
}
效果:串口助手每秒收到一次"I am god!"。
六、接收数据:中断方式详解
串口接收使用中断4(interrupt 4)。当RXD引脚检测到完整的一帧后,RI被置1,触发中断。
cpp
void UART_Routine(void) interrupt 4 {
if(RI) { // 判断是否为接收中断
RI = 0; // 软件清零
recv = SBUF; // 读取接收缓冲器中的字节
// 根据 recv 执行相应操作(LED、蜂鸣器等)
}
}
关键点:
-
必须软件清零RI,否则会不断进入中断。
-
读取SBUF的动作会清除接收缓冲器,但不会影响后续接收(硬件有移位寄存器并行装入)。
-
中断内处理要尽量简短,否则可能丢失后续数据。
6.1 命令控制外设(代码中的switch-case)
接收到的字节(十六进制0x01~0x09)控制不同外设:
| 命令 | 功能 | 代码 |
|---|---|---|
| 0x01 | LED1点亮 | LED1 = 0; |
| 0x02 | LED2点亮 | LED2 = 0; |
| 0x03 | LED3点亮 | LED3 = 0; |
| 0x04 | LED4点亮 | LED4 = 0; |
| 0x05 | 熄灭所有LED | P1 |= 0x0F; |
| 0x06 | 蜂鸣器响(低电平触发) | BEEP = 0; |
| 0x07 | 蜂鸣器停 | BEEP = 1; |
| 0x08 | 继电器吸合(低电平触发) | JDQ1 = 0; |
| 0x09 | 继电器断开 | JDQ1 = 1; |
补充知识 :为什么用P1 |= 0x0F熄灭LED?因为LED共阳极(低电平亮),所以置高电平熄灭。P1 |= 0x0F将P1的低4位置1,不影响高4位(蜂鸣器在P1.6)。
七、数据回环实验:接收即发送
最简单的回环代码:
cpp
void UART_Routine(void) interrupt 4 {
if(RI) {
RI = 0;
recv = SBUF;
SBUF = recv; // 立即发回
while(!TI);
TI = 0;
}
}
用途:测试串口线路是否正常,或者作为简单的串口调试助手。实际项目中可用来验证数据是否被正确接收。
八、硬件接线与实验平台
8.1 单片机与电脑通信(USB转TTL)
需要一根USB转TTL模块(如CH340芯片),接线如下:
| USB转TTL模块 | 单片机 |
|---|---|
| TXD | P3.0 (RXD) |
| RXD | P3.1 (TXD) |
| GND | GND |
| 5V | VCC (可选,若模块供电) |
注意 :TX接RX,RX接TX------这就是交叉连接。如果模块上有黄色跳帽(用于切换电平),直接拔掉即可(如"实验三接线方式"图片所示)。


8.2 继电器模块控制220V设备
继电器模块(低电平触发)接线:
-
VCC → 单片机5V
-
GND → 单片机GND
-
IN → 单片机P2.0(JDQ1)
当P2.0输出低电平时,继电器吸合,常开端(NO)与公共端(COM)导通,可控制大电流设备。状态指示灯也会亮起(见"继电器"图片)。


8.3 蓝牙模块(JDY-31-SPP)
用于手机无线控制,接线如下:
-
VCC → 5V(必须5V供电)
-
GND → GND
-
TXD → P3.0(RXD)
-
RXD → P3.1(TXD)
同样交叉连接。手机需先配对(密码1234),然后打开蓝牙SPP助手,选择HEX发送命令(如01),单片机响应。注意:蓝牙模块连接时会占用串口,烧录程序时需拔掉蓝牙。
如图"蓝牙模块接线方式"所示。


九、常见问题与调试技巧
9.1 串口打开失败(设备打开失败)
-
检查USB转TTL模块是否插好,驱动是否安装(设备管理器查看COM口)。
-
关闭所有占用该COM口的软件(如多个串口助手、烧录软件)。
-
检查接线是否正确,特别是GND必须共地。
9.2 收发乱码
-
波特率不一致:确认晶振是11.0592MHz,代码中TH1=0xFD对应9600。若用12MHz晶振,9600bps误差较大,建议改用2400bps(TH1=0xF3)。
-
电平不匹配:TTL与RS-232直连会烧芯片,必须转换。
-
停止位/数据位设置错误:一般为1停止位、8数据位、无校验。
9.3 接收不到数据
-
检查REN是否置1。
-
中断是否开启(ES、EA)。
-
发送方是否正确发送(比如蓝牙模块需配对成功)。
9.4 发送字符串时主循环卡死
如果发送函数中没有等待TI清零而连续发送,会丢失数据。必须等待while(!TI);。
十、进阶知识补充(代码中没有但很重要)
10.1 ASCII与HEX的本质
-
ASCII :每个字符对应一个字节值,例如
'a'=0x61。发送字符串时逐个发送字符的ASCII码。 -
HEX (十六进制):直接发送字节的原始值,例如发送
0x01,对方收到的是二进制00000001,而不是字符'1'(0x31)。
在串口助手中,如果发送"01"(ASCII),实际发送0x30和0x31;如果选择HEX发送"01",则发送0x01。我们的命令代码使用HEX模式,所以串口助手必须选HEX发送,而接收区选ASCII才能看到"I am god!"字符串。
10.2 多机通信与SM2位
SCON的SM2位用于多机通信。当SM2=1时,只有接收到第9位数据为1(地址帧)时才置RI中断。这样一主多从系统中,从机可以只响应地址呼叫,节省CPU资源。我们的实验未使用此功能,SM2保持0。
10.3 中断优先级
串口中断的优先级默认较低(由IP寄存器设定)。如果在串口中断中处理复杂任务(如长延时),可能错过后续数据。一般做法:中断只读取数据存入缓冲区,主循环中处理。
10.4 数据发送的物理过程
如"数据是怎么发送的?"图片所示:
-
写SBUF后,数据进入发送移位寄存器。
-
TXD引脚按波特率时钟逐位输出:起始位(0) → 数据位D0~D7(LSB先发) → 可选校验位 → 停止位(1)。
-
接收方在波特率时钟的特定时刻采样RXD引脚,重建数据。

十一、总结与实验建议
通过本文,你应掌握了:
-
51单片机串口初始化的完整配置(SCON、PCON、TMOD、TH1/TL1)。
-
字节发送与字符串发送函数实现。
-
中断接收及命令解析控制LED、蜂鸣器、继电器。
-
数据回环测试方法。
-
与USB转TTL、蓝牙模块、继电器模块的硬件接线。
-
常见问题的排查思路。
实验建议:
-
先使用回环程序验证串口硬件和驱动程序正常。
-
再尝试发送字符串"Hello 51"。
-
最后用手机蓝牙APP发送HEX命令控制单片机,实现无线遥控。
串口通信就像单片机的"嘴巴"和"耳朵",掌握了它,你的单片机就能与外部世界无障碍对话!
所有图片素材均来自实验过程截图,包括寄存器说明、接线图、串口助手界面、蓝牙模块、继电器等。实际操作时请对照图片反复检查接线,避免接反烧毁设备。
参考资料:STC89C52数据手册,实验过程等图片。