4. 定时器中断实现秒表

任务目标

使用STM32定时器产生1ms中断,在中断服务函数中累加计数实现秒表计时,并通过串口打印当前时间。本案例以STM32F103系列为例,使用标准外设库完成定时器中断配置。

硬件环境

  • STM32F103C8T6最小系统板(或兼容开发板)
  • USB转TTL串口模块(连接PA9/PA10)
  • ST-Link或J-Link调试器(用于程序下载)

关键代码


#include "stm32f10x.h"
#include <stdio.h>

volatile uint32_t tick_count = 0;   // 1ms递增计数
volatile uint32_t seconds = 0;      // 秒
volatile uint32_t minutes = 0;      // 分

// 重定向fputc到串口1
int fputc(int ch, FILE *f) {
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        tick_count++;
        if (tick_count >= 1000) {   // 1秒到达
            tick_count = 0;
            seconds++;
            if (seconds >= 60) {
                seconds = 0;
                minutes++;
                if (minutes >= 60) minutes = 0;
            }
        }
    }
}

void TIM2_Init(void) {
    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_InitStruct.TIM_Prescaler = 7199;        // 72MHz/7200 = 10kHz
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStruct.TIM_Period = 9;              // 10kHz/10 = 1kHz (1ms)
    TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &TIM_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);
}

void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode = USART_Mode_RX | USART_Mode_TX;
    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);
}

int main(void) {
    USART1_Init();
    TIM2_Init();

    printf("秒表启动\r\n");

    while (1) {
        printf("%02d:%02d\r", minutes, seconds);
        delay_ms(100);  // 每100ms刷新一次显示
    }
}

// 简单延时函数(非精确,仅用于主循环刷新间隔)
void delay_ms(uint32_t ms) {
    volatile uint32_t i;
    for (; ms > 0; ms--)
        for (i = 0; i < 8000; i++);
}

编译与下载

  1. 使用Keil MDK5创建工程,选择芯片型号STM32F103C8
  2. 添加标准外设库文件:stm32f10x_gpio.c、stm32f10x_tim.c、stm32f10x_usart.c、stm32f10x_rcc.c、misc.c
  3. 在工程选项中定义USE_STDPERIPH_DRIVER
  4. 编译后将程序通过ST-Link下载到开发板

验证方法

将USB转TTL模块的RX连接到PA9(USART1_TX),GND共地。打开串口调试助手,设置波特率115200、8位数据、1位停止位、无校验。复位开发板后,串口助手应持续收到形如00:00、00:01的时间显示,每秒钟数值递增一次,分钟数每60秒递增一次。若观察到时间按预期规律变化,则说明定时器中断配置正确,秒表功能实现成功。

常见问题

  • 串口无输出:检查PA9是否正确连接串口模块RX,确认波特率设置一致,检查USART1时钟是否使能。
  • 时间跳动异常:确认系统主频为72MHz。若使用8MHz外部晶振,需在SystemInit中正确配置PLL倍频。
  • 编译报错:确认已添加全部所需库文件,并在C/C++选项卡中定义USE_STDPERIPH_DRIVER。