Day45 51单片机UART串口通信与数码管时钟系统
day45 51单片机UART串口通信与数码管时钟系统
本项目整合了 UART异步串口通信、定时器中断计时、数码管动态扫描显示、BCD码转换、中断与查询混合编程 等核心嵌入式开发技术,构建了一个可通过串口设置时间、自动走时并动态显示的数字时钟系统。
一、UART串口通信基础与寄存器详解
1. UART核心概念
- 异步通信:无共享时钟,依赖预设波特率同步。
- 串行传输:数据逐位在单线上传输(对比并行P0/P1口)。
- 全双工:
TXD
(发送)、RXD
(接收)可同时工作。 - 标准数据帧(本代码配置):
- 起始位:1位(低电平)
- 数据位:8位
- 校验位:无
- 停止位:1位(高电平)
2. 关键SFR寄存器配置
▶ SCON
串口控制寄存器(地址 98H,可位寻址)
位 | 符号 | 功能说明 | 代码操作 |
---|---|---|---|
7 | SM0 | 串口方式位0 | SM0 = 0; |
6 | SM1 | 串口方式位1 | SM1 = 1; → 方式1 |
5 | SM2 | 多机通信控制位 | SM2 = 0; (标准模式) |
4 | REN | 允许接收位 | REN = 1; (允许接收) |
1 | TI | 发送中断标志(软件清零) | TI = 0; |
0 | RI | 接收中断标志(软件清零) | 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译码器驱动共阴极数码管。
五、系统运行理想效果
-
上电初始化:
- 显示
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码转换、中断与查询混合编程、嵌入式系统主循环设计。