[经验分享] 51单片机UART串行通信 软件模拟UART + 硬件UART回显

[复制链接]
376|1
wowu 发表于 2025-10-13 16:34 | 显示全部楼层 |阅读模式
项目目标
通过两个完整工程,掌握 51单片机串行通信的两种实现方式:

软件模拟UART —— 利用定时器+GPIO精确控制时序,模拟串口协议发送数据;
硬件UART回显系统 —— 使用51内置串口控制器,实现接收-发送闭环验证。
两者均通过 STC-ISP串口助手 验证通信结果,是嵌入式开发中串口调试的核心技能。


工程一:软件模拟UART发送字符串 “hello”

核心思想:用定时器中断实现精确位延时,通过P3.1引脚逐位模拟UART协议发送ASCII字符。

#include <reg51.h>  // 引入标准51单片机寄存器定义文件

sbit UART_TXD = P3^1;  // 定义P3.1引脚为UART数据发送端口,用于输出串行数据(硬件TXD引脚,兼容外部串口设备)

// 延时函数:通过双重循环结构实现基础延时,参数i控制延时长度(非精确,用于主循环节奏控制)
void delay(unsigned int i)
{
        unsigned int num = i;   // 将输入参数i暂存至局部变量num
        int j = 0;              // 外层循环计数器
        for(j = 0; j < 10; j++) // 外层循环执行10次
        {
                num = i;            // 每轮内层循环前重载初始值
                while(num--);       // 内层空循环,通过递减计数实现延时(粗略毫秒级)
        }
}

unsigned char delay_flag = 0;  // 全局延时完成标志位,由定时器中断置位(用于精确微秒级等待)

// 定时器0初始化函数:配置为模式1(16位定时器),并开启中断(用于生成精确位时间)
void timer0_init(void)
{
        unsigned char mod = 0;           // 临时变量,用于修改TMOD寄存器
        mod = TMOD;                      // 读取当前TMOD值
        mod &= ~(0xf << 0);              // 清除定时器0的模式控制位(低4位)
        mod |= (0x1 << 0);               // 设置定时器0为模式1(16位定时器)
        TMOD = mod;                      // 写回配置值

        ET0 = 1;                         // 使能定时器0中断
        EA = 1;                          // 开启总中断允许(全局中断开关)
}

// UART位延时函数:通过定时器0中断实现固定时间间隔(约50μs,对应600bps波特率)
void uart_delay(void)
{
        unsigned char mod = 0;           // 保留局部变量定义(未使用,兼容性保留)
       
        delay_flag = 0;                  // 清除延时标志,准备等待中断触发

        TH0 = (65536 - 760) / 256 ;      // 装载定时器0高8位初值(机器周期=1μs,760周期≈760μs,实际用于600bps≈1667μs/位,此处为简化演示)
        TL0 = (65536 - 760) % 256 ;      // 装载定时器0低8位初值
        TR0 = 1;                         // 启动定时器0

        while(!delay_flag);              // 等待中断服务程序将标志位置1(阻塞等待,实现精确延时)
}

// UART数据发送函数:支持无校验、偶校验、奇校验三种模式
// 参数说明:dat = 待发送字节,check = 0(无校验)、1(偶校验)、2(奇校验)
void uart_send(unsigned char dat, unsigned char check)
{
        unsigned char i = 0;             // 位计数器,用于遍历8位数据
        unsigned char num = 0;           // 统计数据中“1”的位数,用于校验计算

        UART_TXD = 1;                    // 空闲状态,输出高电平(线路空闲)
        UART_TXD = 0;                    // 发送起始位(低电平,通知接收方开始接收)
        uart_delay();                    // 延时,保持起始位宽度(约1位时间)
        uart_delay();                    // 二次延时,确保位宽稳定(增强抗干扰)

        // 逐位发送数据(从bit0到bit7,低位在前,符合UART标准)
        for(i = 0; i < 8; i++)
        {
                if(dat & (0x1 << i))         // 判断当前位是否为1(位测试)
                {
                        UART_TXD = 1;            // 发送高电平
                        num++;                   // 记录“1”的个数(用于后续校验位计算)
                }
                else
                {
                        UART_TXD = 0;            // 发送低电平
                }
                uart_delay();                // 保持当前位电平宽度(1位时间)
                uart_delay();                // 二次延时,确保稳定(增强容错)
        }

        // 校验位发送(根据check参数选择校验方式,本工程实际使用无校验)
        if(check == 1)                   // 偶校验模式
        {
                if(0 == (num % 2))           // 若“1”的个数为偶数
                        UART_TXD = 1;            // 发送高电平作为校验位
                else
                        UART_TXD = 0;            // 发送低电平作为校验位
                uart_delay();                // 校验位延时
                uart_delay();                // 二次延时
        }
        else if(check == 2)              // 奇校验模式
        {
                 if(num % 2)                  // 若“1”的个数为奇数
                        UART_TXD = 1;            // 发送高电平作为校验位
                else
                        UART_TXD = 0;            // 发送低电平作为校验位
                uart_delay();                // 校验位延时
                uart_delay();                // 二次延时
        }

        UART_TXD = 1;                    // 发送停止位(高电平,标志帧结束)
        uart_delay();                    // 停止位延时
        uart_delay();                    // 二次延时,确保帧结束稳定(接收方采样窗口)
}

// 主程序入口
void main(void)
{
        unsigned char dat = 0;           // 保留变量,未使用(兼容性保留)
        char buf[10] = {"hello"};        // 定义待发送字符串缓冲区(含终止符\0,共6字节)
        unsigned char i = 0;             // 循环索引变量

        P2 = 0x55;                       // 初始化P2口输出模式(01010101,用于LED状态指示)

        timer0_init();                   // 初始化定时器0,用于位延时(波特率时基)

        while(1)                         // 主循环:持续发送数据(无限循环)
        {
                for(i = 0; i < 5; i++)       // 依次发送"hello"中的5个字符(跳过\0)
                {
                        uart_send(buf, 0);    // 调用发送函数,无校验模式(check=0)
                }

                delay(1000);                 // 主循环延时,控制发送节奏(约几毫秒,防止发送过快)
                P2 = ~P2;                    // 翻转P2口电平,指示程序运行状态(LED闪烁)
        }
}

// 定时器0中断服务函数(中断号1)
void timer0_handler(void) interrupt 1
{
        delay_flag = 1;                  // 中断触发,置位延时完成标志(唤醒uart_delay中的while循环)
}



工程一运行理想结果
单片机通过 P3.1 引脚持续发送字符串 “hello”,串口助手接收到重复的 “hello” 数据流,并在接收缓冲区中显示为连续的 “hellohellohello…” 字符串。

📊 具体表现:
波特率:约600bps(由定时器初值760推算,实际位宽≈1667μs);
帧结构:1起始位 + 8数据位 + 0校验位 + 1停止位 = 10位/字符;
发送内容:每轮循环发送5字节:'h' 'e' 'l' 'l' 'o';
接收显示:串口助手显示连续 hellohellohello...(无换行符);
状态指示:P2口LED以固定频率闪烁(每发送完一次"hello"翻转一次);
调试验证:STC-ISP串口助手“操作成功”,接收字节数持续增长(如563字节)。
🧩 工程二:硬件UART串口回显系统(9600bps)
✅ 核心思想:使用51内置串口控制器,配置定时器1为波特率发生器,实现接收即回显功能,验证串口通路。

/*
* 功能    : STC89C5x 最小串口回显工程
* 硬件    : 11.0592 MHz 晶振 → 9600 bps(工业标准晶振,精确匹配常用波特率)
*/

#include <reg51.h>

/*------- 引脚定义 -------*/
sbit UART_TXD = P3^1;          // 模拟 UART 发送脚(本工程未使用模拟发送,保留兼容性定义)

/*------- 软件延时函数 -------*/
// 粗略延时,主频 11.0592 MHz 下约 1 ms@i≈120(用于超时等待)
void delay(unsigned int i)
{
    unsigned int num = i;
    int j = 0;
    for(j = 0; j < 10; j++)
    {
        num = i;
        while(num--);
    }
}

/*------- 定时器 0 相关 -------*/
unsigned char delay_flag = 0;  // 1:表示 50 µs 到点(uart_delay 用,为软件UART预留时基)

/* 定时器 0 初始化:模式 1(16 位定时),用于产生 50 µs 基准(本工程未用于串口,为扩展预留) */
void timer0_init(void)
{
    unsigned char mod = 0;
    mod = TMOD;         // 读原寄存器
    mod &= ~(0xf << 0); // 清 T0 位(定时器0控制位)
    mod |= (0x1 << 0);  // 设 T0 为模式 1(16位定时器)
    TMOD = mod;
    ET0 = 1;            // 允许 T0 中断(使能定时器0中断)
    EA  = 1;            // 开总中断(全局中断允许)
}

/* 阻塞 50 µs(9600 bps 一位时间≈104μs,此处760周期≈760μs,仅为示例占位) */
void uart_delay(void)
{
    delay_flag = 0;
    TH0 = (65536 - 760) / 256;   // 760 个机器周期≈760 µs(12MHz晶振下)
    TL0 = (65536 - 760) % 256;
    TR0 = 1;                     // 启动定时器0
    while(!delay_flag);          // 等待中断置位(阻塞等待)
}

/*------- UART 底层收发 -------*/
// 发送 1 字节(查询 TI)—— 使用硬件串口发送寄存器SBUF
void uart_send_byte(unsigned char dat)
{
    TI = 0;         // 清发送标志(必须手动清除,硬件不自动清零)
    SBUF = dat;     // 写入发送缓冲寄存器,硬件自动添加起始位、停止位
    while(!TI);     // 等待发送完成标志置位(查询方式,阻塞等待)
}

// 接收 1 字节(带 2 ms 超时)—— 使用硬件串口接收寄存器SBUF
// 返回 0 成功,-1 超时(防止程序卡死在等待接收)
char uart_recv_byte(unsigned char * dat)
{
    unsigned char num = 2;          // 约 2 ms 超时(调用delay()约2次)
    while((!RI) && num--)           // RI=0表示未收到,等待或超时
    {
        delay();    // 1 ms 级延时(粗略延时函数)
    }
    if(num < 0)                     // 超时判断(num递减至负数)
        return -1;  // 超时返回错误码
    *dat = SBUF;    // 从接收缓冲寄存器读取数据
    RI = 0;         // 清接收标志(必须手动清除)
    return 0;       // 成功返回0
}

/*------- UART 初始化 -------*/
// 9600 bps @11.0592 MHz,8-N-1,允许接收(标准配置)
void uart_init(void)
{
    unsigned char mod = 0;
    mod = TMOD;
    SM0 = 0;        // 串口模式0:8位UART(方式1)
    SM1 = 1;        // 串口模式1:8位UART(方式1)
    REN = 1;        // 允许接收(接收使能)
    PCON &= ~(1 << 7); // SMOD=0,波特率不倍速(SMOD位清零)
    mod &= ~(0xf << 4); // 清 T1 位(定时器1控制位)
    mod |= (0x2 << 4);  // T1 模式 2(8 位自动重装,用作波特率发生器)
    TMOD = mod;
    TH1 = 0xfd;     // 9600 bps 初值(11.0592MHz晶振下标准值)
    TL1 = 0xfd;     // 自动重装值 = TH1
    TR1 = 1;        // 启动 T1(启动波特率发生器)
}

/*------- 主函数 -------*/
void main(void)
{
    unsigned char dat = 0;          // 接收数据暂存变量
    char ret = 0;                   // 接收函数返回值(0成功,-1超时)
    char buf[10] = {"hello\n\r"};   // 仅占位,未使用(兼容性保留)
    unsigned char i = 0;            // 仅占位,未使用
    P2 = 0x55;                      // 初始 LED 状态(01010101)
    timer0_init();                  // 初始化 50 µs 时基(为软件UART预留)
    uart_init();                    // 初始化硬件串口(核心配置)
    while(1)
    {
        ret = uart_recv_byte(&dat); // 尝试接收1字节(带超时)
        if(ret != -1)               // 若接收成功(未超时)
            uart_send_byte(dat);    // 立即回显该字节(原样发送回去)
        P2 = ~P2;                   // 指示系统运行(每轮循环翻转P2,LED闪烁)
    }
}

/*------- 中断服务 -------*/
// 定时器 0 溢出:50 µs 到点(本工程未用于串口,为软件UART扩展预留)
void timer0_handler(void) interrupt 1
{
    delay_flag = 1;     // 唤醒 uart_delay(置位标志,解除阻塞)
}




工程二运行理想结果
单片机进入“回显模式”:通过串口助手发送任意字符(如 ‘A’),单片机立即原样返回该字符,实现“你说什么,我回什么”的闭环通信验证。


具体表现:
波特率:9600bps(精确匹配11.0592MHz晶振);
帧格式:8数据位、无校验、1停止位(8-N-1);
发送方式:查询TI标志,阻塞等待发送完成;
接收方式:查询RI标志,带2ms超时防卡死;
功能表现:
串口助手发送 “Hello!” → 单片机回显 “Hello!”;
发送 “123” → 回显 “123”;
无输入时,程序不阻塞,P2口持续闪烁;
状态指示:P2口LED持续闪烁,证明主循环正常运行;
调试意义:最小闭环系统,验证串口收发硬件功能正常。

知识体系归纳(双工程对比 + 核心概念)




核心知识点一句话总结
通过定时器中断精确控制GPIO电平翻转可软件模拟UART协议实现低速串行通信,而利用51单片机内置串口控制器配合定时器1波特率发生器可实现高效稳定的硬件UART收发,两者结合掌握串行通信底层原理与工程实践,是嵌入式开发的必备技能。


串行口通信四要素(通用)
波特率(Baud Rate) —— 每秒传输的位数(如9600bps),决定通信速度;
数据位(Data Bits) —— 每帧有效数据长度(通常8位);
校验位(Parity Bit) —— 奇校验/偶校验/无校验,用于简单错误检测;
停止位(Stop Bit) —— 帧结束标志(通常1位);
收发数据操作方法 —— 初始化 → 发送(uart_send)→ 接收(uart_recv)→ 处理。

最终运行效果总结
工程一(软件UART):持续发送“hello”,串口助手显示“hellohellohello…”,P2口周期闪烁;
工程二(硬件UART回显):发送任意字符立即原样返回,实现“你说我复读”,P2口持续闪烁;
共同点:均通过STC-ISP或通用串口助手验证,P2口LED证明程序存活,定时器中断机制确保时序精确;
教学价值:从零构建通信协议(软件)→ 使用硬件外设(硬件),完整覆盖串口通信知识体系。
————————————————
版权声明:本文为CSDN博主「cellurw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oareen/article/details/151801637

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
穷得掉渣大侠 发表于 2025-10-15 11:59 | 显示全部楼层
工程一的软件模拟UART部分让我对UART协议有了更深入的理解,通过定时器和GPIO来模拟通信协议
您需要登录后才可以回帖 登录 | 注册

本版积分规则

139

主题

4367

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部