定时器是单片机开发中最核心的外设之一,广泛应用于定时控制、脉冲宽度调制(PWM)、外部脉冲计数等场景。本文基于STM32F103系列单片机,结合Keil MDK-ARM开发环境,从底层配置到实战调试,手把手讲解定时器定时中断、PWM输出、脉冲计数三大核心功能的编程方法,同时详细说明如何通过Keil调试功能观察变量、验证程序逻辑,零基础也能快速上手。
一、前期准备
1. 软硬件环境
- 硬件:STM32F103C8T6最小系统板、USB-TTL模块、LED灯、蜂鸣器、示波器(可选,观察PWM波形)、杜邦线;
- 软件:Keil MDK-ARM V5.36(需安装STM32F103器件库)、STM32CubeMX(辅助配置寄存器,可选);
- 基础知识:熟悉STM32定时器基本结构(计数器、预分频器、自动重装寄存器)、Keil工程创建流程、C语言基础。
2. 核心原理梳理
STM32F103的通用定时器(TIM2~TIM4)具备定时中断、PWM输出、脉冲计数功能,核心参数:
- 预分频器(PSC):将系统时钟分频,降低计数器计数频率;
- 自动重装寄存器(ARR):计数器计数到ARR值时触发中断/更新;
- 计数器模式:向上计数(默认)、向下计数、中心对齐计数;
- PWM模式:通过比较寄存器(CCR)与计数器值比较,输出高低电平;
- 脉冲计数:通过定时器外部时钟模式,检测外部引脚脉冲并计数。
二、实战1:定时中断(以1秒中断为例)
定时中断是定时器最基础的应用,例如实现1秒触发一次中断,控制LED灯闪烁。
1. 寄存器配置思路
系统时钟为72MHz(STM32F103常用配置),要实现1秒中断:
- 预分频器(PSC)设置为7199,分频后计数频率=72MHz/(7199+1)=10kHz;
- 自动重装寄存器(ARR)设置为9999,计数次数=9999+1=10000次,总定时时间=10000/10kHz=1秒;
- 开启更新中断(UIE),使能定时器中断,配置NVIC中断优先级。
2. Keil工程代码实现
#include"stm32f10x.h"// 全局变量:记录中断次数u32 timer_count=0;// 定时器2初始化函数voidTIM2_Init(void){TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;// 使能TIM2时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);// 定时器基础配置TIM_TimeBaseStructure.TIM_Period=9999;// ARR值TIM_TimeBaseStructure.TIM_Prescaler=7199;// PSC值TIM_TimeBaseStructure.TIM_ClockDivision=0;// 时钟分频因子TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;// 向上计数TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);// 开启定时器更新中断TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);// NVIC配置:中断优先级NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);// 使能定时器2TIM_Cmd(TIM2,ENABLE);}// LED初始化(PA0)voidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_0);// 初始熄灭}// TIM2中断服务函数voidTIM2_IRQHandler(void){if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET){timer_count++;// 中断次数+1if(timer_count%1==0)// 1秒触发{GPIO_WriteBit(GPIOA,GPIO_Pin_0,!GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_0));// LED翻转}TIM_ClearITPendingBit(TIM2,TIM_IT_Update);// 清除中断标志位}}intmain(void){LED_Init();TIM2_Init();while(1){// 主循环无需操作,中断中处理LED}}3. Keil调试观察变量
步骤1:进入调试模式
- 编译工程后,点击Keil工具栏“Debug”按钮(放大镜图标),进入调试界面;
- 确认“View”->“Watch & Call Stack Window”->“Watch 1”打开观察窗口。
步骤2:添加观察变量
- 在Watch 1窗口的“Name”列输入
timer_count,按回车; - 启动调试(点击“Run”按钮),可看到
timer_count每1秒增加1,LED同步闪烁; - 暂停调试(“Stop”按钮),可查看变量实时值,验证中断触发频率是否正确。
三、实战2:PWM输出(控制LED亮度)
PWM通过调节高低电平占空比控制外设,例如调节LED亮度、电机转速。本文以TIM3_CH1(PA6)输出PWM为例。
1. 配置思路
- 定时器3时钟:APB1总线时钟72MHz,预分频器PSC=71,分频后计数频率=1MHz;
- ARR=999,PWM周期=1000/1MHz=1ms,频率=1kHz;
- 比较寄存器CCR1值决定占空比(占空比=CCR1/(ARR+1)*100%);
- 配置TIM3_CH1为PWM模式1,输出比较使能,GPIO配置为复用推挽输出。
2. 代码实现
#include"stm32f10x.h"// TIM3 PWM初始化voidTIM3_PWM_Init(u16 arr,u16 psc){GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);// GPIO配置(PA6=TIM3_CH1)GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;// 复用推挽输出GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);// 定时器基础配置TIM_TimeBaseStructure.TIM_Period=arr;TIM_TimeBaseStructure.TIM_Prescaler=psc;TIM_TimeBaseStructure.TIM_ClockDivision=0;TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);// PWM模式配置TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;// PWM模式1TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;// 输出使能TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;// 高电平有效TIM_OC1Init(TIM3,&TIM_OCInitStructure);// 配置CH1TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);// 使能预装载寄存器TIM_ARRPreloadConfig(TIM3,ENABLE);// 使能ARR预装载TIM_Cmd(TIM3,ENABLE);// 使能TIM3}intmain(void){u16 pwm_val=0;u8 dir=1;// 占空比增减方向// 初始化:ARR=999,PSC=71 → 1kHz PWMTIM3_PWM_Init(999,71);while(1){// 模拟占空比渐变if(dir)pwm_val++;elsepwm_val--;if(pwm_val>999)dir=0;if(pwm_val==0)dir=1;TIM_SetCompare1(TIM3,pwm_val);// 设置CCR1值,调整占空比// 延时for(u32 i=0;i<10000;i++);}}3. Keil调试与验证
步骤1:观察PWM占空比变量
- 进入调试模式,在Watch窗口添加
pwm_val,启动调试; - 可看到
pwm_val从0递增到999,再递减到0,循环变化; - 结合示波器:将探头接PA6,可观察到1kHz PWM波形,占空比随
pwm_val渐变。
步骤2:验证PWM周期
- 在调试界面打开“Peripherals”->“Timer”->“TIM3”,查看PSC=71、ARR=999,确认计数频率和周期配置正确;
- 观察“Counter”值,验证计数器从0到999循环计数,与配置一致。
四、实战3:脉冲计数(统计外部脉冲个数)
定时器支持外部时钟模式,可检测外部引脚的脉冲信号并计数,例如统计按键按下次数、编码器脉冲数。本文以TIM2_CH1(PA0)检测外部脉冲为例。
1. 配置思路
- 将TIM2_CH1配置为外部时钟模式2,脉冲信号从PA0输入;
- 预分频器PSC=0(不分频),计数器直接对外部脉冲计数;
- 开启计数器,通过读取TIM2->CNT值获取脉冲个数。
2. 代码实现
#include"stm32f10x.h"// 脉冲计数初始化voidTIM2_Count_Init(void){GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// GPIO配置(PA0=TIM2_CH1,上拉输入)GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;// 上拉输入GPIO_Init(GPIOA,&GPIO_InitStructure);// 定时器基础配置TIM_TimeBaseStructure.TIM_Period=0xFFFF;// 最大计数范围TIM_TimeBaseStructure.TIM_Prescaler=0;// 不分频TIM_TimeBaseStructure.TIM_ClockDivision=0;TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);// 外部时钟模式配置TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;// 上升沿计数TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;// 不分频TIM_ICInitStructure.TIM_ICFilter=0x00;// 无滤波TIM_ICInit(TIM2,&TIM_ICInitStructure);// 选择外部时钟模式2:TI1FP1作为时钟源TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0);TIM_SetCounter(TIM2,0);// 计数器清零TIM_Cmd(TIM2,ENABLE);// 使能定时器}intmain(void){u16 pulse_count=0;TIM2_Count_Init();while(1){pulse_count=TIM_GetCounter(TIM2);// 读取脉冲计数// 每计数10个脉冲,做一次标记(可替换为业务逻辑)if(pulse_count%10==0&&pulse_count!=0){pulse_count=0;TIM_SetCounter(TIM2,0);// 清零重新计数}}}3. Keil调试观察计数结果
- 进入调试模式,在Watch窗口添加
pulse_count; - 用杜邦线将PA0接脉冲信号源(如信号发生器),或手动短接PA0到GND产生脉冲;
- 启动调试,可看到
pulse_count随外部脉冲数递增,每累计10个脉冲自动清零,验证计数功能正常; - 调试界面打开“Peripherals”->“Timer”->“TIM2”,查看“Counter”值,与
pulse_count一致,确认计数准确。
五、Keil调试核心技巧
- 变量观察:除Watch窗口,可通过“Memory Window”查看变量内存地址,验证数据存储是否正确;
- 寄存器查看:通过“Peripherals”菜单查看定时器寄存器(如PSC、ARR、CNT、CCR),快速定位配置错误;
- 断点调试:在中断服务函数、主循环关键位置添加断点(F9),暂停程序后查看变量,分析执行流程;
- 实时刷新:调试时勾选“View”->“Periodic Window Update”,实现变量实时刷新,无需手动暂停;
- 错误排查:若定时器功能异常,优先检查时钟使能、GPIO复用配置、中断优先级、寄存器初始化顺序。