数码管时钟,已经修改了昨天走时不精准的问题 | 走时很精准
昨天帖子的数码管时钟,发现走时不准,误差很大,现在对定时器进行了修改,走时很精准,下载时选择12MHz频率。喜欢的朋友赶快复制拿走!!!/*================================================================
功能:STC32四位数码管时钟(带冒号2秒闪烁和简化按键设置)
硬件连接:
- MCU: STC32G12K128
- 数码管: 4401AS(四位共阴时钟数码管)
- 段码: P2.0-P2.7 -> 数码管a,b,c,d,e,f,g,DP
- 位码: P3.0-P3.3 -> 数码管位选1-4
- 按键: P0.0-P0.3 -> 功能/加/减/确认键
注意:冒号通过第2位数码管的DP段以2秒周期闪烁
================================================================*/
#include "STC32G.h" // STC32头文件
#include "intrins.h"
/*============ 引脚定义 ============*/
#define SEG_PORT P2 // 段码端口(P2.0-P2.7 -> a,b,c,d,e,f,g,DP)
#define BIT_PORT P3 // 位码端口(P3.0-P3.3 -> 位选1-4)
// 按键引脚定义
sbit KEY_MODE = P0^0; // 功能/模式键
sbit KEY_ADD= P0^1; // 加键
sbit KEY_SUB= P0^2; // 减键
sbit KEY_OK = P0^3; // 确认键
/*============ 模式定义 ============*/
#define MODE_NORMAL 0 // 正常显示模式
#define MODE_SET_HOUR 1 // 设置小时模式
#define MODE_SET_MINUTE2 // 设置分钟模式
/*============ 全局变量 ============*/
// 时间结构体
typedef struct {
unsigned char hour; // 小时 (0-23)
unsigned char minute; // 分钟 (0-59)
unsigned char second; // 秒钟 (0-59)
} TIME_TYPE;
TIME_TYPE time = {12, 0, 0};// 初始时间 12:00:00
// 显示相关变量
unsigned char disp_buf = {0}; // 显示缓冲区
bit colon_flag = 0; // 冒号显示标志
bit sec_led_on = 0; // 秒LED点亮标志
unsigned char display_mode = MODE_NORMAL;// 当前显示模式
bit blink_flag = 0; // 设置闪烁标志
unsigned int t1_counter = 0; // 定时器1计数器
unsigned int ms_counter = 0; // 毫秒计数器
// 按键相关
unsigned char key_state = 0; // 按键状态
unsigned int key_press_time = 0; // 按键按下时间
bit key_pressed = 0; // 按键按下标志
bit key_enable = 1; // 按键使能标志
unsigned int key_repeat_delay = 0; // 按键重复延时
/*============ 数码管编码表 ============*/
// 共阴数码管段码表 (0-9)
// 段码顺序:a b c d e f g DP (对应P2.0-P2.7)
const unsigned char code SEG_CODE[] = {
// 0 1 2 3 4 5 6 7 8 9
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,
// 不显示
0x00
};
// 位选表
// 共阴数码管,位选低电平有效
// 假设P3.0-3.3分别控制第1-4位数码管
const unsigned char code BIT_CODE[] = {
0xFE,// 11111110 - 第1位数码管 (P3.0=0)
0xFD,// 11111101 - 第2位数码管 (P3.1=0)
0xFB,// 11111011 - 第3位数码管 (P3.2=0)
0xF7 // 11110111 - 第4位数码管 (P3.3=0)
};
/*============ 函数声明 ============*/
void System_Init(void);
void GPIO_Init(void);
void Timer0_Init(void);
void Timer1_Init(void);
void Update_Display_Buffer(void);
void Key_Scan(void);
void Key_Process(unsigned char key_val);
void Delay_ms(unsigned int ms);
/*============ 主函数 ============*/
void main(void)
{
// 系统初始化
System_Init();
// 主循环
while(1)
{
Key_Scan(); // 按键扫描
Delay_ms(10); // 增加主循环延时
}
}
/*============ 系统初始化 ============*/
void System_Init(void)
{
GPIO_Init(); // GPIO初始化
Timer0_Init(); // 定时器0初始化
Timer1_Init(); // 定时器1初始化
// 开全局中断
EA = 1;
// 初始化显示缓冲区
Update_Display_Buffer();
}
/*============ GPIO初始化 ============*/
void GPIO_Init(void)
{
// P2设置为准双向口(段码输出)
P2M0 = 0x00;
P2M1 = 0x00;
P2 = 0x00; // 初始输出低电平
// P3设置为准双向口(位码输出)
P3M0 = 0x00;
P3M1 = 0x00;
P3 = 0xFF; // 初始输出高电平(关闭所有位)
// P0设置为高阻输入(按键输入),启用内部上拉
P0M0 = 0x00;
P0M1 = 0x00;
P0 = 0xFF; // 初始化为高电平
}
/*============ 定时器0初始化 ============*/
// 用于数码管动态扫描,1ms定时
void Timer0_Init(void)
{
// 设置定时器0为模式0(16位自动重装载)
AUXR |= 0x80; // 定时器0为1T模式
TMOD &= 0xF0; // 清除定时器0模式位
// 计算1ms定时初值(假设系统时钟为12MHz)
// 1T模式下,1个机器周期 = 1/12MHz ≈ 0.08333μs
// 1ms = 1000μs,需要计数次数 = 1000 / 0.08333 ≈ 12000
TH0 = (65536 - 12000) / 256;
TL0 = (65536 - 12000) % 256;
// 使能定时器0中断
ET0 = 1;
// 启动定时器0
TR0 = 1;
}
/*============ 定时器1初始化 ============*/
// 用于时钟计时,1ms定时
void Timer1_Init(void)
{
// 设置定时器1为模式0(16位自动重装载)
AUXR |= 0x40; // 定时器1为1T模式
TMOD &= 0x0F; // 清除定时器1模式位
// 计算1ms定时初值(假设系统时钟为12MHz)
// 1T模式下,1个机器周期 = 1/12MHz ≈ 0.08333μs
// 1ms = 1000μs,需要计数次数 = 1000 / 0.08333 ≈ 12000
TH1 = (65536 - 12000) / 256;
TL1 = (65536 - 12000) % 256;
// 使能定时器1中断
ET1 = 1;
// 启动定时器1
TR1 = 1;
}
/*============ 定时器0中断服务函数 ============*/
// 处理数码管动态扫描
void Timer0_ISR(void) interrupt 1
{
static unsigned char scan_index = 0;// 扫描索引
unsigned char seg_data;
// 重装定时器初值
TH0 = (65536 - 12000) / 256;
TL0 = (65536 - 12000) % 256;
// 关闭当前显示位(消隐)
BIT_PORT = 0xFF;
// 获取当前位的段码
seg_data = SEG_CODE];
// 处理第2位的冒号(DP段)
if(scan_index == 1)// 第2位数码管
{
if(colon_flag)// 需要显示冒号
{
seg_data |= 0x80;// 点亮DP段
}
}
// 输出段码
SEG_PORT = seg_data;
// 输出位码
BIT_PORT = BIT_CODE;
// 更新扫描索引
scan_index++;
if(scan_index >= 4)
{
scan_index = 0;
}
}
/*============ 定时器1中断服务函数 ============*/
// 处理时钟计时、冒号闪烁和设置闪烁
void Timer1_ISR(void) interrupt 3
{
static unsigned int colon_cnt = 0; // 冒号计数器
static unsigned int blink_cnt = 0; // 闪烁计数器
// 重装定时器初值
TH1 = (65536 - 12000) / 256;
TL1 = (65536 - 12000) % 256;
// 定时器1计数器 - 用于1秒计时
t1_counter++;
// 毫秒计数器 - 用于按键延时
ms_counter++;
// 1秒定时(1000 * 1ms = 1秒) - 修正为1000
if(t1_counter >= 1000)
{
t1_counter = 0;
// 仅在正常显示模式下更新时间
if(display_mode == MODE_NORMAL)
{
time.second++;
if(time.second >= 60)
{
time.second = 0;
time.minute++;
if(time.minute >= 60)
{
time.minute = 0;
time.hour++;
if(time.hour >= 24)
{
time.hour = 0;
}
}
}
}
// 更新显示缓冲区
Update_Display_Buffer();
}
// 冒号闪烁控制(2秒周期,亮1秒灭1秒)
// 1000ms = 1秒,所以计数到1000切换一次
colon_cnt++;
if(colon_cnt >= 1000)// 1000 * 1ms = 1秒
{
colon_cnt = 0;
sec_led_on = ~sec_led_on;// 切换冒号状态
// 在正常显示模式下更新冒号
if(display_mode == MODE_NORMAL)
{
colon_flag = sec_led_on;
}
}
// 设置时的数字闪烁控制(2Hz,亮0.25秒灭0.25秒)
// 250ms = 0.25秒
blink_cnt++;
if(blink_cnt >= 250)// 250 * 1ms = 250ms
{
blink_cnt = 0;
blink_flag = ~blink_flag;// 切换闪烁标志
// 更新显示缓冲区(当闪烁状态改变时)
Update_Display_Buffer();
}
// 按键延时控制
if(!key_enable)
{
key_repeat_delay++;
if(key_repeat_delay >= 300)// 300ms按键延时
{
key_repeat_delay = 0;
key_enable = 1;// 重新使能按键
}
}
}
/*============ 更新显示缓冲区 ============*/
void Update_Display_Buffer(void)
{
// 正常情况下显示小时和分钟
disp_buf = time.hour / 10; // 小时十位
disp_buf = time.hour % 10; // 小时个位
disp_buf = time.minute / 10;// 分钟十位
disp_buf = time.minute % 10;// 分钟个位
// 初始化冒号标志
colon_flag = 0;
// 根据当前模式处理显示
switch(display_mode)
{
case MODE_NORMAL:// 正常显示模式
// 正常显示冒号
colon_flag = sec_led_on;
// 处理小时十位为0的情况
if(disp_buf == 0)
{
disp_buf = 10;// 不显示
}
break;
case MODE_SET_HOUR:// 设置小时模式
// 设置模式下冒号常亮
colon_flag = 1;
if(blink_flag)// 闪烁时清除显示
{
// 整个小时一起闪烁
disp_buf = 10;// 小时十位不显示
disp_buf = 10;// 小时个位不显示
}
break;
case MODE_SET_MINUTE:// 设置分钟模式
// 设置模式下冒号常亮
colon_flag = 1;
if(blink_flag)// 闪烁时清除显示
{
// 整个分钟一起闪烁
disp_buf = 10;// 分钟十位不显示
disp_buf = 10;// 分钟个位不显示
}
break;
}
}
/*============ 按键扫描函数 ============*/
void Key_Scan(void)
{
unsigned char key_current = 0;
// 读取按键状态
if(KEY_MODE == 0) key_current |= 0x01;
if(KEY_ADD == 0)key_current |= 0x02;
if(KEY_SUB == 0)key_current |= 0x04;
if(KEY_OK == 0) key_current |= 0x08;
// 无按键按下
if(key_current == 0)
{
key_state = 0;
key_press_time = 0;
key_pressed = 0;
return;
}
// 有按键按下
switch(key_state)
{
case 0:// 第一次检测到按键
key_state = 1;
key_press_time = 0;
break;
case 1:// 按键消抖
key_press_time++;
if(key_press_time > 5)// 50ms消抖
{
key_state = 2;
key_press_time = 0;
}
break;
case 2:// 按键按下确认
if(key_enable)// 按键使能
{
Key_Process(key_current);
key_enable = 0;// 禁用按键
key_state = 3; // 等待释放
}
break;
case 3:// 等待释放
// 等待按键释放
break;
}
}
/*============ 按键处理函数 ============*/
void Key_Process(unsigned char key_val)
{
switch(key_val)
{
case 0x01:// 功能/模式键
if(display_mode == MODE_NORMAL)
{
display_mode = MODE_SET_HOUR;// 进入设置小时模式
}
else if(display_mode == MODE_SET_HOUR)
{
display_mode = MODE_SET_MINUTE;// 进入设置分钟模式
}
else if(display_mode == MODE_SET_MINUTE)
{
display_mode = MODE_NORMAL;// 返回正常显示模式
}
Update_Display_Buffer();// 更新显示
break;
case 0x02:// 加键
switch(display_mode)
{
case MODE_SET_HOUR:// 设置小时
time.hour++;
if(time.hour >= 24)
time.hour = 0;
break;
case MODE_SET_MINUTE:// 设置分钟
time.minute++;
if(time.minute >= 60)
time.minute = 0;
break;
default:
break;
}
Update_Display_Buffer();// 更新显示
break;
case 0x04:// 减键
switch(display_mode)
{
case MODE_SET_HOUR:// 设置小时
if(time.hour > 0)
time.hour--;
else
time.hour = 23;
break;
case MODE_SET_MINUTE:// 设置分钟
if(time.minute > 0)
time.minute--;
else
time.minute = 59;
break;
default:
break;
}
Update_Display_Buffer();// 更新显示
break;
case 0x08:// 确认键
// 确认键功能简化
if(display_mode == MODE_SET_HOUR)
{
display_mode = MODE_SET_MINUTE;// 从设置小时跳到设置分钟
}
else if(display_mode == MODE_SET_MINUTE)
{
display_mode = MODE_NORMAL;// 返回正常显示
}
Update_Display_Buffer();// 更新显示
break;
}
}
/*============ 延时函数 ============*/
void Delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 114; j++);
}
定时相关配置
需要考虑工作频率 定时器模式0是16位自动重装,就没必要在中断里手动重装了。 lcwswust 发表于 2026-4-8 09:30
定时器模式0是16位自动重装,就没必要在中断里手动重装了。
猜想自动重装更快:
自动重装靠底层电路硬件
手动重装靠CPU执行软件程序 狂热主宰 发表于 2026-4-8 09:53
猜想自动重装更快:
自动重装靠底层电路硬件
手动重装靠CPU执行软件程序 ...
最大的区别还是定时精度,手动重装依赖中断服务程序,会占用时钟周期,而且有被其他更高优先级中断截胡的风险,造成不确定性的误差增大。 用MCU定时器做时钟的时基要选高精度晶振,定时器中断最高优先级,周期尽可能短些,不要超过100us。选择自动重装模式,还要加入修正系数x。 if(t1_counter >= (10000+x))。采取这些措施后才能将日误差控制在1秒内。另外秒点闪烁周期1秒才比较合适。 wulin 发表于 2026-4-8 20:01
用MCU定时器做时钟的时基要选高精度晶振,定时器中断最高优先级,周期尽可能短些,不要超过100us。选择自动 ...
谢谢指导 晓飛飛 发表于 2026-4-8 16:36
最大的区别还是定时精度,手动重装依赖中断服务程序,会占用时钟周期,而且有被其他更高优先级中断截胡的 ...
谢谢 狂热主宰 发表于 2026-4-8 09:53
猜想自动重装更快:
自动重装靠底层电路硬件
手动重装靠CPU执行软件程序 ...
谢谢指导,刚学习单片机 wulin 发表于 2026-4-8 20:01
用MCU定时器做时钟的时基要选高精度晶振,定时器中断最高优先级,周期尽可能短些,不要超过100us。选择自动 ...
试过了,1秒闪烁太快了。没有2秒好。
页:
[1]