4. 定时器中断实现秒表
任务目标
使用STM32定时器中断实现精确计时秒表,通过串口周期性输出计时读数(时:分:秒.毫秒)。计时精度由定时器硬件保证,不依赖主循环软件延时。
硬件与依赖
- STM32F103系列开发板(本文以STM32F103C8T6为例)
- USART1:PA9(TX)、PA10(RX),波特率115200
- TIM2:72MHz时钟,配置为1ms中断周期
- 使用标准外设库(SPL)或HAL库均可,本文给出寄存器级实现以展示核心原理
核心实现
定时器配置为向上计数模式,自动重装载值设为7199(72MHz/7200=10kHz),再通过软件分频每10次中断产生1ms基准。秒表变量在中断服务函数中累加,主循环中通过串口打印当前读数。
// 秒表数据结构
typedef struct {
uint32_t ms; // 毫秒计数
uint8_t second;
uint8_t minute;
uint8_t hour;
} Stopwatch_t;
volatile Stopwatch_t sw = {0, 0, 0, 0};
volatile uint8_t ms_tick = 0; // 1ms标志
// TIM2中断服务函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
static uint8_t sub_count = 0;
sub_count++;
if (sub_count >= 10) { // 10次100us中断 = 1ms
sub_count = 0;
ms_tick = 1;
}
}
}
// 在main中调用,每1ms更新一次秒表
void Stopwatch_Update(void) {
if (ms_tick) {
ms_tick = 0;
sw.ms++;
if (sw.ms >= 1000) {
sw.ms = 0;
sw.second++;
if (sw.second >= 60) {
sw.second = 0;
sw.minute++;
if (sw.minute >= 60) {
sw.minute = 0;
sw.hour++;
if (sw.hour >= 24) sw.hour = 0;
}
}
}
}
}
// 串口打印秒表读数
void Stopwatch_Print(void) {
char buf[32];
sprintf(buf, "%02d:%02d:%02d.%03lu\r\n",
sw.hour, sw.minute, sw.second, (unsigned long)sw.ms);
USART_SendString(USART1, buf);
}
// 主函数
int main(void) {
// 系统时钟72MHz、GPIO、USART1初始化(略,标准配置)
USART1_Init(115200);
// TIM2初始化:72MHz/7200 = 10kHz,即100us中断一次
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 71; // 72MHz/72 = 1MHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 99; // 1MHz/100 = 10kHz
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
uint32_t last_print = 0;
while (1) {
Stopwatch_Update();
// 每500ms打印一次
if (sw.ms - last_print >= 500 || sw.ms < last_print) {
last_print = sw.ms;
Stopwatch_Print();
}
}
}
运行与验证
- 将代码编译下载至开发板,连接串口至PC(PA9→RX,PA10→TX,共地)
- 打开串口调试助手,设置115200-8-N-1
- 复位开发板,观察串口输出
预期输出示例(每500ms更新一次):
00:00:00.000
00:00:00.500
00:00:01.000
00:00:01.500
...
通过判定标准:
- 串口持续输出递增的秒表读数,格式正确
- 读数间隔均匀,用秒表对照1分钟内误差不超过±0.5秒
- 毫秒位按0、500、000、500规律交替(因500ms打印间隔)
关键说明
- 定时器分频参数:72MHz主频经71分频得1MHz,再计数100次得10kHz中断频率(100μs周期),软件10次累加得1ms基准
- 若使用HAL库,将TIM2配置为1ms周期中断,在回调中直接递增sw.ms即可,无需软件分频
- 打印间隔500ms仅为便于观察,可修改条件实现更频繁或更稀疏的输出
- 秒表最大计时24小时后自动归零,如需更长计时可扩展hour为uint32_t