#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; // 仅在中断里置起标志位,留待主循环中执行实际读取与消抖 } }