【51单片机】深度解析 51 串口 UART:原理、配置、收发实现与工程化应用全总结

🔥小龙报:个人主页

🎬作者简介:C++研发,嵌入式,机器人方向学习者

❄️个人专栏:《工科必装软件安装教程》《嵌入式的开端 ---- 51单片机》
永远相信美好的事情即将发生

文章目录


前言

串口通信是 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 重定向完成高级输出,层层递进拆解实操要点。掌握串口通信,不仅能实现单片机与电脑的数据交互,更为后续传感器数据采集、多设备通信打下坚实基础。
嵌入式学习本就是一个循序渐进、积少成多的过程,每一个寄存器的理解、每一次代码的调试,都是在为技术之路添砖加瓦。不必畏惧初期的晦涩,坚持敲代码、做实操,那些看似复杂的知识点终会迎刃而解。✨保持热爱,脚踏实地,你的嵌入式之路,终将繁花似锦!

相关推荐
承渊政道1 小时前
C++学习之旅【C++中模板进阶内容介绍】
c语言·c++·笔记·学习·visual studio
qq_532453532 小时前
使用 Three.js 构建沉浸式全景图AR
开发语言·javascript·ar
浅念-2 小时前
C语言——动态内存管理
c语言·开发语言·c++·笔记·学习
Vect__5 小时前
基于线程池从零实现TCP计算器网络服务
c++·网络协议·tcp/ip
草履虫建模8 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
Lester_11019 小时前
STM32 高级定时器PWM互补输出模式--如果没有死区,突然关闭PWM有产生瞬间导通的可能吗
stm32·单片机·嵌入式硬件·嵌入式软件
naruto_lnq10 小时前
分布式系统安全通信
开发语言·c++·算法
小李独爱秋10 小时前
“bootmgr is compressed”错误:根源、笔记本与台式机差异化解决方案深度指南
运维·stm32·单片机·嵌入式硬件·文件系统·电脑故障
学嵌入式的小杨同学10 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux