
🔥小龙报:个人主页
🎬作者简介:C++研发,嵌入式,机器人方向学习者
❄️个人专栏:《工科必装软件安装教程》《嵌入式的开端 ---- 51单片机》
✨ 永远相信美好的事情即将发生

文章目录
- 前言
- 一、硬件接线与串口相关寄存器
-
- [1.1 硬件接线](#1.1 硬件接线)
- [1.2 串口相关寄存器](#1.2 串口相关寄存器)
- 二、模式选择
-
- [2.1 模式选择寄存器](#2.1 模式选择寄存器)
- 三、发送数据相关寄存器配置
-
- 3.1时钟源选择,影响波特率
-
- [3.1.1 查表法](#3.1.1 查表法)
- [3.1.2 公式法](#3.1.2 公式法)
- 3.2校验位设置
- 3.3发送数据
-
- [3.3.1 发送字节函数](#3.3.1 发送字节函数)
- [3.3.2 发送字符串函数](#3.3.2 发送字符串函数)
- 四、串口接收数据相关寄存器配置
- 五、串口中断
- 六、Printf发送数据
- 总结与每日励志
前言
串口通信是 51 单片机与外界数据交互的核心基础,广泛应用于调试、数据传输与指令控制。本文从硬件接线、寄存器配置入手,详细讲解串口工作模式、发送与接收原理,结合查询方式、中断方式及 printf 重定向实战,从零搭建完整串口通信体系,帮助初学者快速掌握串口开发要点,夯实嵌入式入门基础。
一、硬件接线与串口相关寄存器
1.1 硬件接线


1.2 串口相关寄存器

注:一般常用的是SCON SBUF PCON IE,如果需要配置不同中断优先级的则需要配置IPH IP,SADEN、SADDR用于多机通讯,比较少用
二、模式选择
STC89C52 有 1 个 UART, 有四种工作模式

模式0:同步移位寄存器,主要用于扩展并行输入或输出口。
模式1:8位UART,波特率可变(常用)
模式2:9位UART,波特率固定(多出的1位为校验位)
模式3:9位UART,波特率可变(多出的1位为校验位)
注:这里简单说明下常用和其它的区别,常用的就是你使用串口功能时,一般使用模式1就能满足所有需求
2.1 模式选择寄存器

csharp
SM0 = 0;
SM1 = 1;
或
SCON = 0x40; //0100 0000 串口工作模式1
模式1说明:
三、发送数据相关寄存器配置
发送流程:

SBUF 串口数据缓冲寄存器:可写入需要发送的数据

可以就简化为:

3.1时钟源选择,影响波特率
Timer1 Overflow:定时器1溢出作为时钟源,用于串口通讯的波特率
3.1.1 查表法

csharp
方式一:
TMOD = 0X20; //设置定时器1工作模式2 8位自动重载
TH1 = 0xFD; //设定初值
TL1 = 0XFD; //设定装载值
TR1 = 1; //打开计数器
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2 8位自动重载,这里为什么使用|=而不使用=呢,为了避免清除定时器0配置
TH1 = 0xFD; //设定初值
TL1 = 0XFD; //设定装载值
TR1 = 1; //打开计数器
3.1.2 公式法

如果需要设置波特率为9600=(1/32)*(11059000/12/(256-TH1))
SMOD:波特率加倍选择位,复位为0,可以不设置

csharp
方式一:
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2 8位自动重载,这里为什么使用|=而不使用=呢,为了避免清除定时器0配置
TH1 = 0xFD; //设定初值
TL1 = 0XFD; //设定装载值
TR1 = 1; //打开计数器
PCON |= 0X00; //设置B7为0
3.2校验位设置
TB8:校验位,在串口模式2、3作为校验位,模式1是8bit的,无需校验位

3.3发送数据
3.3.1 发送字节函数
csharp
#include <reg52.h>
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0100 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
//主函数
void main()
{
UartInit();//调用串口初始化函数
SBUF=0x30;
while(1)
{
}
}
3.3.2 发送字符串函数
这里还需要注意一个点是发送完成最后一位后,会置TI=1,所以我们可以用TI寄存器来判断是否发送完
csharp
#include <reg52.h>
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0100 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
void send_string(unsigned char str[])
{
unsigned char i=0;
while(str[i]!='\0')//判断是否到字符串尾
{
SBUF = str[i];
while(TI==0); //等待发送完成,发送完成TI会置1
TI=0; //下次发送前,要手动将TI置0
i++; //下次发送
}
}
//主函数
void main()
{
UartInit();//调用串口初始化函数
send_string("hello world!!");
while(1)
{
}
}
运行结果:

四、串口接收数据相关寄存器配置


可以简化为:

4.1波特率设置
csharp
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x40; //0101 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
4.2允许接收寄存器设置
REN 禁止/允许串口接收控制位,为1时,才能接收。

csharp
SM0 = 0;
SM1 = 1;
REN = 1;//允许接收数据
或
SCON = 0x50; //0101 0000 串口工作模式1 允许接收
4.3接收有效数据标志位
RI:接收有效数据标志位

csharp
#include <reg52.h>
sbit led=P2^7;
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x50; //0101 0000 串口工作模式1 允许接收
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
}
void main()
{
char receive;//用于接收串口数据的变量
UartInit();
while(1)
{
if(RI==1) //RI=1说明串口接收到了数据
{
RI=0; //RI置0保证下次接收
receive=SBUF;//将从串口接收到的数据报存到变量中
//判断接收的数据,作出相应的操作
if(receive=='O')
led=0;
else if(receive=='C')
led=1;
}
}
}
五、串口中断
接收到有效数据时,会触发串口中断,这时候,我们开启串口中断相关的寄存器,就可以在中断服务函数中处理相关逻辑,不需要在大循环中一直判断啦。

csharp
#include <reg52.h>
sbit led=P2^7;
//串口初始化函数
void UartInit() //9600bps@11.0592MHz
{
SCON = 0x50; //0101 0000 串口工作模式1
TMOD &= 0x0F; //清空TMOD中定时器1相关
TMOD |= 0X20; //设置定时器1工作模式2:8位自动重载
TH1 = 0xFD; //设定定时初值
TL1 = 0XFD;
TR1 = 1; //启动定时器1
ES=1;//打开串行通信中断
EA=1;//打开总中断
}
//串行中断函数
void Uart_receive() interrupt 4
{
if(RI==1)//RI=1说明串口接收到了数据
{
char receive;
RI=0;//RI置0保证下次接收
receive=SBUF;//将从串口接收到的数据报存到变量中
//判断接收的数据,作出相应的操作
if(receive=='O')
led=0;
if(receive=='C')
led=1;
}
}
//主函数
void main()
{
UartInit();
while(1)
{
}
}
注: 开启中断(ES=1+EA=1)后,串口硬件一直独立监听 RXD 引脚,当接收到 1 字节数据(比如O),硬件会自动完成 3 件事(纯硬件操作,和中断函数无关)
六、Printf发送数据
csharp
#include <reg52.h>
#include <stdio.h>
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/*
**重写printf调用的putchar函数,重定向到串口输出
**需要引入头文件<stdio.h>
*****/
char putchar(char dat){
//输出重定向到串口
SBUF = dat; //写入发送缓冲寄存器
while(!TI); //等待发送完成,TI发送溢出标志位 置1
TI = 0; //对溢出标志位清零
return dat; //返回给函数的调用者printf
}
//主函数
void main()
{
UartInit();//调用串口初始化函数
printf("hello\r\n");
while(1)
{
}
}
注: 为什么要写一个putchar函数?
C 语言标准库的printf本身不认识单片机的串口,它默认把内容输出到 "电脑控制台 / 显示屏",而putchar是printf的 "底层输出接口"------ 重写它,就是给printf指定新的输出路径:从单片机串口发出去,这就是串口重定向
总结与每日励志
✨ 串口通信是 51 单片机嵌入式开发的必备技能,本文从硬件到软件、从基础到实战,完整讲解了串口通信的核心知识点。从硬件接线、寄存器配置到模式 1 的核心应用,再到发送 / 接收的查询与中断实现,最后结合 printf 重定向完成高级输出,层层递进拆解实操要点。掌握串口通信,不仅能实现单片机与电脑的数据交互,更为后续传感器数据采集、多设备通信打下坚实基础。
嵌入式学习本就是一个循序渐进、积少成多的过程,每一个寄存器的理解、每一次代码的调试,都是在为技术之路添砖加瓦。不必畏惧初期的晦涩,坚持敲代码、做实操,那些看似复杂的知识点终会迎刃而解。✨保持热爱,脚踏实地,你的嵌入式之路,终将繁花似锦!

