4. 定时器中断实现秒表
任务目标
使用STM32定时器产生1ms中断,在中断服务函数中累计计数,实现秒表计时功能,并通过串口打印当前时间(时:分:秒.毫秒)。
硬件与依赖
- MCU:STM32F103系列(本文以STM32F103C8T6为例)
- 定时器:TIM2(32位向上计数定时器)
- 串口:USART1(PA9=TX, PA10=RX),波特率115200
- 时钟:系统主频72MHz,APB1定时器时钟72MHz
- 开发方式:标准外设库(STM32F10x_StdPeriph_Lib)
实现原理
TIM2时钟源为72MHz,设置预分频器PSC=71,得到1MHz计数频率;设置自动重装载值ARR=999,则计数溢出周期为1ms。在TIM2中断服务函数中递增毫秒计数器,当计数到1000时进位为1秒,并更新时分秒字段。主循环中每秒通过串口输出一次当前时间。
关键完整代码
#include "stm32f10x.h"
#include <stdio.h>
volatile uint32_t ms_counter = 0; // 毫秒累计
volatile uint8_t sec = 0, min = 0, hour = 0;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
ms_counter++;
if (ms_counter >= 1000)
{
ms_counter = 0;
sec++;
if (sec >= 60) { sec = 0; min++; }
if (min >= 60) { min = 0; hour++; }
if (hour >= 24) { hour = 0; }
}
}
}
void USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void TIM2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InitStructure.TIM_Prescaler = 71; // 72MHz/72 = 1MHz
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStructure.TIM_Period = 999; // 1MHz/1000 = 1kHz → 1ms
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
void USART_SendString(char *str)
{
while (*str)
{
USART_SendData(USART1, (uint8_t)(*str++));
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
}
int main(void)
{
uint8_t last_sec = 0xFF;
USART1_Init();
TIM2_Init();
while (1)
{
if (sec != last_sec) // 每秒打印一次
{
char buf[32];
last_sec = sec;
sprintf(buf, "\r\n%02d:%02d:%02d.%03d", hour, min, sec, ms_counter);
USART_SendString(buf);
}
}
}
编译与运行
- 创建STM32标准外设库工程,将上述代码放入main.c。
- 编译生成HEX文件,通过ST-Link或串口ISP下载至STM32F103C8T6。
- 使用USB转TTL模块连接PA9(TX)至PC串口RX,GND共地。
验证方法
- 打开串口调试助手,波特率设为115200,8位数据、1位停止位、无校验。
- 复位开发板,观察串口输出。预期每秒输出一行时间,格式如
00:00:01.000、00:00:02.000,数值递增且间隔约1秒。 - 用秒表对照,连续观察1分钟,串口时间与真实时间误差应小于0.5秒(晶振精度范围内)。
- 若输出乱码,检查波特率设置与USB转TTL接线;若无输出,检查PA9是否正确连接及USART1时钟是否使能。
常见问题
- 时间不准确:检查系统时钟是否为72MHz。若使用外部晶振,需在SystemInit中正确配置PLL。
- 串口无输出:确认USART1_TX引脚为PA9,且已配置为复用推挽输出。
- 中断不触发:检查NVIC是否使能TIM2中断,以及TIM_ITConfig是否调用。