news 2026/4/15 16:16:02

Keil定时器编程:定时中断、PWM输出、脉冲计数(附Keil调试实战)教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Keil定时器编程:定时中断、PWM输出、脉冲计数(附Keil调试实战)教程

定时器是单片机开发中最核心的外设之一,广泛应用于定时控制、脉冲宽度调制(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调试核心技巧

  1. 变量观察:除Watch窗口,可通过“Memory Window”查看变量内存地址,验证数据存储是否正确;
  2. 寄存器查看:通过“Peripherals”菜单查看定时器寄存器(如PSC、ARR、CNT、CCR),快速定位配置错误;
  3. 断点调试:在中断服务函数、主循环关键位置添加断点(F9),暂停程序后查看变量,分析执行流程;
  4. 实时刷新:调试时勾选“View”->“Periodic Window Update”,实现变量实时刷新,无需手动暂停;
  5. 错误排查:若定时器功能异常,优先检查时钟使能、GPIO复用配置、中断优先级、寄存器初始化顺序。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 16:16:01

Rust扩展编译失败频发?立即掌握这6个关键诊断步骤

第一章&#xff1a;Rust扩展编译失败的常见现象与影响在开发基于 Rust 的项目时&#xff0c;尤其是涉及 FFI&#xff08;外部函数接口&#xff09;或使用 rustc 编译器插件、自定义构建脚本扩展时&#xff0c;开发者常会遭遇扩展编译失败的问题。这类问题不仅中断构建流程&…

作者头像 李华
网站建设 2026/4/15 6:39:48

【Laravel 13缓存清理终极指南】:掌握多模态缓存清除的5大核心策略

第一章&#xff1a;Laravel 13多模态缓存清理概述在现代Web应用开发中&#xff0c;缓存机制是提升系统性能的关键组件。Laravel 13引入了多模态缓存清理机制&#xff0c;支持开发者针对不同缓存驱动&#xff08;如Redis、Memcached、文件系统等&#xff09;执行精细化的清除策略…

作者头像 李华
网站建设 2026/4/12 8:22:09

TI德州仪器 SN65C3221EPWR芯片的概述

SN65C3221EPWR 是德州仪器生产的一款双通道、15kV ESD 保护、3.0V 至 5.5V、低功耗 RS-232 线路收发器。 SN65C3221EPWR 该器件包括一个线路驱动器、一个线路接收器和一个双电荷泵电路&#xff0c;用于异步通信控制器和串行端口连接器之间的电气接口。它支持高达1 Mbit/s的数据…

作者头像 李华
网站建设 2026/4/10 20:03:18

别再用普通insert了!农业传感器PHP数据入库的6种高效模式对比分析

第一章&#xff1a;农业传感器数据入库的挑战与现状随着智慧农业的发展&#xff0c;大量部署在田间的传感器持续采集土壤湿度、气温、光照强度等关键环境参数。这些数据是实现精准农业决策的基础&#xff0c;但如何高效、可靠地将海量、异构的传感器数据写入数据库&#xff0c;…

作者头像 李华
网站建设 2026/4/15 7:47:14

【Laravel开发者必看】:5步实现Laravel 13多模态权限控制

第一章&#xff1a;Laravel 13 多模态权限控制概述 Laravel 13 引入了全新的多模态权限控制系统&#xff0c;旨在应对现代 Web 应用中日益复杂的访问控制需求。该系统不仅支持传统的基于角色的权限管理&#xff08;RBAC&#xff09;&#xff0c;还融合了基于属性的访问控制&am…

作者头像 李华