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);
        }
    }
}

编译与运行

  1. 创建STM32标准外设库工程,将上述代码放入main.c。
  2. 编译生成HEX文件,通过ST-Link或串口ISP下载至STM32F103C8T6。
  3. 使用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是否调用。