[经验分享] 51单片机UART串口通信与数码管时钟系统

[复制链接]
216|0
八层楼 发表于 2025-10-13 20:31 | 显示全部楼层 |阅读模式
本项目整合了 UART异步串口通信、定时器中断计时、数码管动态扫描显示、BCD码转换、中断与查询混合编程 等核心嵌入式开发技术,构建了一个可通过串口设置时间、自动走时并动态显示的数字时钟系统。

一、UART串口通信基础与寄存器详解
1. UART核心概念
异步通信:无共享时钟,依赖预设波特率同步。
串行传输:数据逐位在单线上传输(对比并行P0/P1口)。
全双工:TXD(发送)、RXD(接收)可同时工作。
标准数据帧(本代码配置):
起始位:1位(低电平)
数据位:8位
校验位:无
停止位:1位(高电平)
2. 关键SFR寄存器配置

SCON 串口控制寄存器(地址 98H,可位寻址)



方式1说明:8位UART,波特率由定时器1决定 —— 本项目采用模式。


SBUF 串口数据缓冲寄存器(地址 99H)
物理上是两个独立寄存器(发送缓冲器 + 接收缓冲器),共用同一地址。
写操作 → 写入发送缓冲器 → UART自动发送:
SBUF = dat; // 将数据写入发送缓冲区,硬件自动串行发送


读操作 → 从接收缓冲器读取刚接收完成的数据:
uart_buf[uart_num++] = SBUF; // 读取接收缓冲器中的数据



PCON 电源控制寄存器(SMOD波特率倍增位)
PCON &= ~(1 << 7); // SMOD = 0,波特率不加倍


波特率公式(方式1 + 定时器1方式2 + SMOD=0):
波特率 = (1/32) × (fosc / 12 / (256 - TH1))



TMOD & TCON 定时器控制
mod &= ~(0xf << 4); // 清除定时器1模式位
mod |= (0x2 << 4);  // 设置定时器1为模式2(8位自动重装)
TMOD = mod;
TR1 = 1;            // 启动定时器1,开始产生波特率


为何用模式2? 自动重装避免软件干预误差,确保波特率精确稳定。
二、波特率计算与晶振选择
1. 波特率计算(11.0592MHz晶振)
TH1 = 0xfd; // 十进制253
TL1 = 0xfd;


代入公式:

波特率 = (1/32) × (11059200 / 12 / (256 - 253))
       = (1/32) × (11059200 / 36)
       = (1/32) × 307200
       = 9600 bps


结论:配置波特率为标准 9600 bps。

为何用11.0592MHz?
该频率可被整除,产生精确标准波特率(9600/19200/38400等)。12MHz会有误差,长期通信易出错。

三、程序模块详解(含注释与运行结果)
1. 头文件与全局变量定义
#include <reg51.h>

// 数码管段码表(共阴极,0-9 和 "-" 符号)
// 索引0~9对应数字0~9,索引10对应"-"
unsigned char digit_data[11] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40};

// 全局变量:用于计时(时、分、秒)
unsigned char sec = 0;   // 秒
unsigned char min = 0;   // 分
unsigned char hour = 0;  // 时

// 全局变量:UART接收缓冲区
unsigned char uart_buf[20] = {0};  // 接收数据缓冲数组
unsigned char uart_num = 0;        // 接收字节数计数器



2. 延时函数(软件延时)
// 延时函数:通过循环实现延时
// 参数 i:延时基数(数值越大延时越长)
void delay(unsigned int i)
{
        unsigned int num = i;      // 保存延时参数
        int j = 0;
        for(j = 0; j < 10; j++)   // 外层循环10次,扩大延时范围
        {
                num = i;               // 每次内层循环前重置计数器
                while(num--);          // 内层空循环,消耗机器周期
        }
}
//  理想结果:调用 delay(1000) 约产生数毫秒级延时,用于数码管消隐与扫描间隔




3. 数码管动态扫描显示函数
// 数码管显示函数:动态扫描显示8位数字
// 参数 num:指向8位数字数组的指针(数组内容为digit_data索引值)
void digit_show(unsigned char * num)
{
          char i = 0;
          for(i = 0; i < 8; i++)           // 依次扫描8个数码管
        {
                P2 = i << 2;                 // 位选:P2[7:2] 控制哪一位亮(假设使用3-8译码器)
                P0 = digit_data[num[8 - i - 1]]; // 段码:P0输出对应数字的段码(倒序显示)
                delay(1);                    // 短暂延时维持显示
                P0 = 0;                      // 消隐:关闭段码,避免残影
        }
}
// 理想结果:循环快速扫描,在人眼视觉暂留效应下稳定显示 "HH-MM-SS" 格式时间


4. 定时器0初始化(50ms定时)
// 定时器0初始化函数:配置为50ms定时
void timer0_init(void)
{
        unsigned char mod = 0;
        TH0 = (65536 - 50000) / 256 ; // 设置定时器0初值高8位(50ms @12MHz)
        TL0 = (65536 - 50000) % 256 ; // 设置定时器0初值低8位
        mod = TMOD;                   // 读取当前TMOD值
        mod &= ~(0xf << 0);           // 清除定时器0的模式位(低4位)
        mod |= (0x1 << 0);            // 设置定时器0为模式1(16位定时器)
        TMOD = mod;                   // 写回TMOD寄存器
       
        ET0 = 1; // 允许定时器0中断
        EA = 1;  // 开启总中断
        TR0 = 1; // 启动定时器0
}
// 理想结果:每50ms触发一次中断,用于累计实现1秒定时

5. UART初始化(9600bps,方式1)
// UART初始化函数:配置为9600波特率,8位数据,无校验
void uart_init(void)
{
        unsigned char mod = 0;
        mod = TMOD;                   // 读取当前TMOD值
        SM0 = 0;                      // 设置UART为模式1(8位UART,可变波特率)
        SM1 = 1;
        SM2 = 0;                      // 禁止多机通信
        REN = 1;                      // 允许串行接收
        PCON &= ~(1 << 7);            // SMOD=0,波特率不加倍
       
        mod &= ~(0xf << 4);           // 清除定时器1的模式位(高4位)
        mod |= (0x2 << 4);            // 设置定时器1为模式2(8位自动重装)
        TMOD = mod;
       
        TH1 = 0xfd;                   // 设置定时器1初值,波特率9600(11.0592MHz晶振)
        TL1 = 0xfd;
        ET1 = 0;                      // 禁止定时器1中断(仅用作波特率发生器)

           ES = 1;                       // 允许串口中断
        EA = 1;                       // 开启总中断
        TR1 = 1;                      // 启动定时器1(开始产生波特率时钟)
}
//  理想结果:UART初始化完成,可接收外部设备发送的时间数据(格式:时、分、秒各1字节十六进制)


6. 十六进制转BCD码函数
// 十六进制转BCD函数:将1字节十六进制数转换为2位BCD码
// 参数 hex:输入的十六进制数(如0x15)
// 返回值:转换后的BCD码(如15)
unsigned char hextobcd(unsigned char hex)
{
        unsigned char num = 0;
        num = (hex >> 4) * 10 + (hex & 0x0f); // 高4位×10 + 低4位
        return num;
}
// 理想结果:输入0x12 → 返回12;输入0x59 → 返回59(用于将串口接收的十六进制时间转为十进制显示值)


7. 主函数(系统主循环)
// 主函数:系统入口
void main(void)
{
        unsigned char array[8] = {0,0,10,0,0,10,0,0}; // 显示缓冲区(10表示"-",初始显示"00-00-00")
        P2 = 0x55;                                    // 初始化P2口(测试或默认状态)
        timer0_init(); // 初始化定时器0(50ms中断,用于计时)
        uart_init();   // 初始化UART(9600bps,接收中断)
        uart_num = 10; // 【注意】人为设为10,仅为测试发送功能,实际应由中断递增

        while(1)       // 主循环
        {
                if(uart_num >= 3) // 接收到至少3字节数据(时、分、秒)
                {
                        hour = hextobcd(uart_buf[0]); // 第1字节转BCD → 小时
                        min = hextobcd(uart_buf[1]);  // 第2字节转BCD → 分钟
                        sec = hextobcd(uart_buf[2]);  // 第3字节转BCD → 秒
                       
                        uart_num = 0; // 清空接收计数器,等待下一次设置
                }
                // 分解当前时间到显示缓冲区 array[8]
                array[0] = hour / 10;   // 小时十位
                array[1] = hour % 10;   // 小时个位
                array[3] = min / 10;    // 分钟十位
                array[4] = min % 10;    // 分钟个位
                array[6] = sec / 10;    // 秒十位
                array[7] = sec % 10;    // 秒个位
                // array[2]和array[5]固定为10(显示"-"),已在初始化时设定

                digit_show(array); // 动态扫描显示当前时间
        }                  
}
//  理想结果:
// 1. 上电后显示 "00-00-00"
// 2. 通过串口发送3字节如 {0x12, 0x34, 0x56} → 显示 "12-34-56"
// 3. 定时器自动每秒递增,时间持续走动
// 4. 到59秒后分钟+1,到59分后小时+1,到23小时后归零


8. 定时器0中断服务函数(1秒累计)
// 定时器0中断服务函数:50ms定时,用于计时
void timer0_handler(void) interrupt 1
{
        static unsigned char num_ms = 0;           // 静态变量,记录50ms次数
        TH0 = (65536 - 50000) / 256 ;             // 重装定时器0高8位初值
        TL0 = (65536 - 50000) % 256 ;             // 重装定时器0低8位初值
        TR0 = 1;                                   // 确保定时器0继续运行(部分编译器需重置)
        num_ms++;                                  // 50ms计数+1
        if(num_ms == 20)                           // 20 × 50ms = 1000ms = 1秒
        {
                num_ms = 0;                            // 清零毫秒计数
                sec++;                                 // 秒+1
                if(sec == 60)                          // 秒满60
                {
                        min++;                             // 分+1
                        sec = 0;                           // 秒归零
                        if(min == 60)                      // 分满60
                        {
                                hour++;                        // 时+1
                                min = 0;                       // 分归零
                                if(hour == 24)                 // 时满24
                                {
                                        hour = 0;                  // 时归零(00:00:00)
                                }
                        }
                }
        }
}
// 理想结果:每1秒自动更新 sec/min/hour,实现精准走时,支持24小时制循环


9. UART中断服务函数(接收数据)
// UART中断服务函数:接收数据
void uart_handler(void) interrupt 4
{
        if(RI) // 接收中断标志置位 → 有数据接收完成
        {
                uart_buf[uart_num++] = SBUF; // 读取SBUF接收缓冲器数据,存入缓冲区
                RI = 0;                             // 必须软件清零RI标志,否则重复进入中断
        }
        else if(TI) // 发送中断标志置位(本程序发送使用查询方式,此分支仅做安全清除)
        {
                TI = 0;  // 清除发送中断标志(防止意外中断)
        }
}

// 理想结果:
// 1. 每接收到1字节数据,存入 uart_buf,uart_num 自增
// 2. 接收满3字节后,主循环中触发时间更新
// 3. 支持连续接收,缓冲区可扩展(当前最大20字节)


四、关键技术点总结
1. 查询 vs 中断 混合策略
发送:采用查询方式(while(!TI);),逻辑简单,适合教学示例。
接收:采用中断方式,高效不阻塞,适合实时接收。
实际项目推荐发送也改用中断,实现全非阻塞通信。

2. 时间数据格式设计
接收格式:3字节十六进制 → {Hour, Minute, Second}
内部存储:转换为BCD码便于显示分解。
显示格式:8位数码管 → "H H - M M - S S"
3. 定时器双重角色
定时器1:仅作波特率发生器(模式2自动重装)。
定时器0:作系统时钟源(50ms中断 → 累计成1秒)。
4. 动态扫描显示优化
使用 delay(1) + P0=0 消隐,避免鬼影。
位选 P2 = i << 2 假设使用3-8译码器驱动共阴极数码管。
五、系统运行理想效果
上电初始化:

显示 00-00-00
UART准备就绪,等待接收时间设置
串口设置时间:

电脑发送 {0x08, 0x30, 0x00} → 显示 08-30-00
发送 {0x17, 0x45, 0x30} → 显示 17-45-30
自动走时:

每秒秒数+1,自动进位分钟/小时
23-59-59 → 下一秒变为 00-00-00
稳定显示:

无闪烁、无重影,数字清晰

知识点:
UART异步通信协议、SCON/SBUF/PCON/TMOD寄存器操作、波特率计算、定时器中断应用、数码管动态扫描、BCD码转换、中断与查询混合编程、嵌入式系统主循环设计。
————————————————
版权声明:本文为CSDN博主「cellurw」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/oareen/article/details/151835097

本帖子中包含更多资源

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

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

131

主题

4396

帖子

2

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