Files
stc32g128k/Drivers/timer.c
edisondeng 767aa14086 fix(timer): compensate Timer0_GetTimestamp for pending TF0 overflow
TH0/TL0 torn-read protection didn't cover the case where the hardware
counter already wrapped but Timer0_Isr hasn't run yet to bump
timer0_ovf_count, making the timestamp appear to jump backward by one
overflow period (~32.768ms). Read TF0 under a brief EA guard and treat
a pending-but-unserviced overflow as ovf+1 without clearing it.
2026-07-31 15:52:50 +08:00

112 lines
4.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "timer.h"
#include "../App/clock.h"
// 外部引用的全局滴答/标志变量 (在 main.c 中定义)
extern volatile u16 ms_tick;
extern volatile bit alarm_flash_flag;
extern volatile u16 key_scan_timer;
/* =========================================================================
* Timer0自由运行硬件时间戳基准
* ========================================================================= */
// Timer0 溢出次数,扩展 16 位硬件计数为 32 位软件时间戳的高位部分
static volatile u16 timer0_ovf_count = 0;
void Timer0_Init(void)
{
TMOD &= 0xF0; // Timer0 模式位清零 -> Mode 0 (STC 上为 16 位不停表计数)
TL0 = 0;
TH0 = 0;
ET0 = 1; // 开启 Timer0 溢出中断允许
TR0 = 1; // 启动 Timer0 计数,此后永不停止 (全局 EA 由 Timer1_Init() 统一开启)
}
/**
* @brief Timer0 溢出中断服务程序 (每约 32.768ms 触发一次)
* @details 只做计数扩展,不做任何耗时操作,保持中断尽量短。
*/
void Timer0_Isr(void) interrupt 1
{
timer0_ovf_count++;
}
u32 Timer0_GetTimestamp(void)
{
u16 ovf1, ovf2;
u8 h1, h2, l;
bit tf, ea_bak;
do {
ovf1 = timer0_ovf_count;
h1 = TH0;
l = TL0;
h2 = TH0;
ovf2 = timer0_ovf_count;
} while (h1 != h2 || ovf1 != ovf2);
// 上面的撕裂读保护只处理了 TH0/TL0 两字节配对读取时的撕裂,但还有另一种更隐蔽的竞态:
// 硬件计数器已经物理溢出归零 (TH0/TL0 已经是溢出后的小数值),但 Timer0_Isr 因为中断延迟
// 还没来得及把 timer0_ovf_count 加一,导致算出来的时间戳比真实值凭空小了一整个溢出周期
// (65536 tick ≈ 32.768ms),看起来像是"时间倒流"。这里读一次硬件溢出标志位 TF0 做补偿:
// 若 TF0 已经置位但还未被服务,本次读数临时按 ovf+1 计算,不清 TF0留给真正的
// Timer0_Isr 之后正常执行、更新持久计数,不影响后续调用。
ea_bak = EA;
EA = 0;
tf = TF0;
EA = ea_bak;
if (tf) {
ovf1++;
}
return ((u32)ovf1 << 16) | ((u16)h1 << 8) | l;
}
/* =========================================================================
* Timer1系统 1ms 基准滴答
* ========================================================================= */
// 主循环按键扫描定时标志 (由 1ms 中断置 1主循环响应清除),定义详见 event.h 的 extern 声明
volatile bit key_scan_flag = 0;
void Timer1_Init(void)
{
AUXR |= 0x40; // 设定定时器 1 为 1T 模式 (无分频,主频直接输入)
TMOD &= 0x0F; // 设置定时器 1 模式寄存器,保留低四位 (定时器0配置)
{
// 按照系统主频 (MAIN_Fosc) 计算 1ms 对应的重装载值
u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL));
TL1 = (u8)reload; // 载入低 8 位
TH1 = (u8)(reload >> 8);// 载入高 8 位
}
ET1 = 1; // 开启定时器 1 中断允许
TR1 = 1; // 启动定时器 1 计数
EA = 1; // 开启全局中断允许
}
/**
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
* @details 承担系统软时钟计算,并每 10ms 置位主循环按键扫描标志,不直接在中断中进行 ADC 多次采样。
*/
void Timer1_Isr(void) interrupt 3
{
// 1ms 递增全局滴答计数器 (0~999 循环),供各 App 的呼吸灯/闪烁/超时等周期性效果使用
ms_tick++;
if (ms_tick >= 1000) {
ms_tick = 0;
}
// 1ms 定时更新软时钟
Clock_IncMS();
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
alarm_flash_flag = (ms_tick / 100) % 2;
/* 物理按键定时计数 (每 10ms 置位一次标志位) */
key_scan_timer++;
if (key_scan_timer >= 10) {
key_scan_timer = 0; // 重置扫描分频计数器
key_scan_flag = 1; // 仅在中断里置起标志位,留待主循环中执行实际读取与消抖
}
}