当前位置: 首页 > news >正文

Day45 51单片机UART串口通信与数码管时钟系统

day45 51单片机UART串口通信与数码管时钟系统

本项目整合了 UART异步串口通信、定时器中断计时、数码管动态扫描显示、BCD码转换、中断与查询混合编程 等核心嵌入式开发技术,构建了一个可通过串口设置时间、自动走时并动态显示的数字时钟系统。


一、UART串口通信基础与寄存器详解

1. UART核心概念

  • 异步通信:无共享时钟,依赖预设波特率同步。
  • 串行传输:数据逐位在单线上传输(对比并行P0/P1口)。
  • 全双工TXD(发送)、RXD(接收)可同时工作。
  • 标准数据帧(本代码配置):
    • 起始位:1位(低电平)
    • 数据位:8位
    • 校验位:无
    • 停止位:1位(高电平)

2. 关键SFR寄存器配置

SCON 串口控制寄存器(地址 98H,可位寻址)
符号功能说明代码操作
7SM0串口方式位0SM0 = 0;
6SM1串口方式位1SM1 = 1;方式1
5SM2多机通信控制位SM2 = 0;(标准模式)
4REN允许接收位REN = 1;(允许接收)
1TI发送中断标志(软件清零TI = 0;
0RI接收中断标志(软件清零RI = 0;

方式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计数+1if(num_ms == 20)                           // 20 × 50ms = 1000ms = 1秒{num_ms = 0;                            // 清零毫秒计数sec++;                                 // 秒+1if(sec == 60)                          // 秒满60{min++;                             // 分+1sec = 0;                           // 秒归零if(min == 60)                      // 分满60{hour++;                        // 时+1min = 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译码器驱动共阴极数码管。

五、系统运行理想效果

  1. 上电初始化

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

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

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

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

✅知识点:
UART异步通信协议、SCON/SBUF/PCON/TMOD寄存器操作、波特率计算、定时器中断应用、数码管动态扫描、BCD码转换、中断与查询混合编程、嵌入式系统主循环设计。

http://www.dtcms.com/a/388845.html

相关文章:

  • 企业级图像AIGC技术观察:Seedream 4.0 模型能力与应用场景分析
  • Kurt-Blender零基础教程:第2章:建模篇——第2节:什么是修改器与建模马拉松
  • fbx 导入到 blender 出现很多黑色虚线的解决方法
  • 记力扣.2779 数组的最大美丽值 练习理解
  • Day26_【深度学习(6)—神经网络NN(2)前向传播的搭建案例】
  • 古老的游戏之竞技体育
  • CURSOR平替(deepseek+VScode)方案实现自动化编程
  • java对电子发票是否原件的快速检查
  • 贪心算法应用:顶点覆盖问题详解
  • Odoo中非库存商品的高级自动化采购工作流程
  • 缺少自动化测试会对 DevOps 带来哪些风险
  • 深入解析 Python 中的 __pycache__与字节码编译机制
  • SEO 优化:元数据 (Metadata) API 和站点地图 (Sitemap) 生成
  • postman+Jenkins进行API automation集成
  • 【算法磨剑:用 C++ 思考的艺术・单源最短路收官】BF/SPFA 负环判断模板 + 四大算法全总结
  • Flink的介绍及应用
  • 微信小程序插屏广告(InterstitialAd)全解析与实战应用案例
  • 格雷希尔G70R系列快速密封连接器+GT系列软管组件的配套组合方案,在新能源汽车老化测试的应用
  • 【Debug日志| 随机下降】
  • 滑动窗口法的优化与实战——力扣209.长度最小的子数组
  • 【Spring Boot 报错已解决】org.yaml.snakeyaml.scanner.ScannerException 报错原因与解决方案
  • 国家统计局数据读取——数据读取——清洗数据06
  • 基于 scratch 构建简单镜像
  • Web安全的暗角:10大易忽略逻辑漏洞解析!
  • 矩阵奇异值分解算法(SVD)详解
  • 【FreeRTOS】 二值信号量与互斥量(CMSIS-RTOS v2 版本)
  • Qt C++ :Qt全局定义<QtGlobal>
  • 【STL源码剖析】从源码看 list:从迭代器到算法
  • MySQL 专题(三):事务与锁机制深度解析
  • 使用BLIP训练自己的数据集(图文描述)