通过TIM对内部时钟Update溢出更新事件进行计数在预期的目标数值进行内部更新。通过TIM对外围输入设备的时钟监听外部时钟CNT计数变换进行计数在预期的目标数值进行触发中断或PWM波形输出OC输出比较。一、配套必懂单词缩写TIM Timer 定时器作用实现定时中断、PWM 波形输出、外部脉冲计数、输入捕获测信号等功能。PSC Prescaler 预分频器作用对输入的高频时钟做分频降频把快速时钟放慢调整最小计时刻度。注意分频倍数 PSC 1PSC 写 0 就是不分频CK_PSC Clock of Prescaler 预分频器输入时钟作用送入预分频器的原始时钟信号是定时器最上游的时钟源。CK_CNT Clock of Counter 计数器工作时钟作用经过 PSC 分频之后、专门给计数器 CNT 使用的时钟每来一个脉冲 CNT 数值 1。CNT Counter Register 计数器寄存器作用存放当前计数值从 0 开始随 CK_CNT 脉冲不断累加。读取TIMx当前CNT计数值 num TIM_GetCounter(TIMx);不管 UIF 标志是 1 还是 0、有没有清标志、开没开中断CNT 都会一直循环计数。ARR Auto-Reload Register 自动重装载寄存器自动重装载值作用设定计数器计数上限CNT 计数到 ARR 数值后立刻清零重新从头计数同时可触发定时中断 / 更新事件。注意CNT 从 0 数到 ARR一共走 ARR1 个时基单位基本功能概念UPD Update Event 更新事件作用CNT 计满 ARR 归零的瞬间产生的信号用来触发定时中断。PWM Pulse Width Modulation 脉冲宽度调制作用通过控制高电平时间占比输出可调平均电压控制电机、LED 亮度。CCR Capture/Compare Register 捕获比较寄存器作用PWM、输入捕获核心寄存器存放对比计数值。APB Advanced Peripheral Bus 高级外设总线作用STM32 挂载定时器、串口等外设的系统总线提供 CK_PSC 时钟来源。IRQ Interrupt Request 中断请求作用定时器计时完成后向 CPU 发送的中断信号。ITStatus IT Interrupt 中断Status 状态作用中断状态库函数返回值类型TIM_GetITStatus代码用TIM_GetITStatus返回 ITStatus读取标志判断定时完成。不开 TIM_IT_Update只产生 Update 事件、只置 UIF 标志不会进中断服务函数开启 TIM_IT_Update 中断每次 Update 发生UIF 置 1向 CPU 发起中断请求进入中断函数。TIM_GetITStatus(TIMx, TIM_IT_Update) SET→发生溢出TIM_GetITStatus(TIMx, TIM_IT_Update) RESET→未溢出UIF Update Interrupt Flag 更新中断标志位更新事件标志硬件溢出只负责把标志拉成 SET你的清除代码只负责把标志拉回 RESET 如果不写清除函数一旦溢出置 1 后永远保持 SET不会变回 RESETIRQHandler Interrupt Request Handler 中断请求处理高级功能概念OC Output Compare 输出比较CNT 计数器按时基不断往上数硬件实时对比CNT和CCR里存的数值 两者相等时通道引脚电平自动翻转 / 置高 / 置低用来输出方波、可调占空比 PWM控制 LED、电机。 它只是借用定时器的时基计数时钟但不改变时基计算公式。CCRCapture/Compare Register 捕获比较寄存器CHChannel 通道TIM1_CH1、TIM2_CH2 等PWMPulse Width Modulation 脉冲宽度调制OC 最常用场景IC Input Capture 输入捕获外部引脚来了上升 / 下降沿信号立刻锁存当前 CNT 计数值到 CCR 通过前后两次捕获的计数值差 × 时基单位算出外部信号周期、高电平时长、频率。二、细节概念1.时基单位时基单位 定时器最小的计时刻度 / 最小时间格子定时器计时就像尺子尺子最小一格是 1mm → 1mm 就是尺子的基单位STM32 定时器最小一格是多少 ns/μs/ms → 这个值就是时基单位时间单位缩写s秒ms毫秒 1/1000 秒 一个负千位μs微秒 1/1000000 秒两个负千位ns纳秒 1/1000000000 秒1kHz → 周期 0.001 s1 ms毫秒 一个负千位1MHz → 周期 0.000001 s1 μs微秒两个负千位1GHz → 周期 1×10−9 s1 ns纳秒三个负千位定时周期 多格加起来完整一轮计时总时长。2.PWM频率Frequency一个周期T信号从高电平上拉→低电平下拉→回到高电平上拉。工程单位1。在1分钟内能执行几个周期则为频率 f 1 / T。如果20ms执行一个周期一分钟可执行50次PWM周期。3.占空比DutyCycle在一个PWM脉冲周期通过高电平的时间与整个周期时间的比例。对输出脉冲的宽度时间高电平时间进行调制可以控制电压变化如0~3.3V。应用于电机转速和转矩、LED亮度、音频信号、电源信号、温度控制如果周期是10ms脉宽是8ms低电平2ms则占空比是80%三参数设置时基定时总时间TIMx_CNT计数器寄存器、TIMx_PSC预分频器寄存器、TIMx_ARR自动装载寄存器举个实例最常见 F103APB172MHz时基单位计算公式时基单位 预分频数值PSC 1÷ 定时器输入原始时钟CK_PSCCK_PSC 72MHz设置 PSC71 分频系数 PSC172CK_CNT 72MHz ÷ 72 1MHz计数频率CK_CNT CK_PSC / ( PSC 1 )时基单位 1/1MHz 1μs → 此时定时器最小一格就是 1 微秒时基单位 1μs定时总时间计算公式定时总时间 时基单位 × ARR 1。定时总时间时基单位沿用上面例子时基 1μsARR999 总定时 1μs × 1000 1000μs 1ms每 1ms 进一次中断结论时基 1μs更小→ 频率 1MHz更高 时基 10μs更大→ 频率 0.1MHz更低计数时钟频率 fCK_PSC ÷ (PSC 1)时基单位 T (PSC 1) ÷ fCK_PSC定时周期 T 总 T × (ARR 1)1. 时钟流程外部时钟 → APB 总线 →CK_PSC定时器原始时钟→ PSC 预分频 →CK_CNT计数时钟CK_CNT 每跳 1 次代表 1 个时基单位2. 更新事件完整逻辑CNT 不断按时基单位累加 → CNT ARR → 产生更新事件 UPDATE→ 硬件自动把UIF 标志置 13. 新手混淆区分更新事件与中断真正的 “中断执行” 是什么开启中断 配置 NVIC 写TIMx_IRQHandler中断服务函数CNT一溢出硬件自动跳转到 IRQHandler 里执行代码不用 while 循环一直查询这才是中断。Update 更新事件程序要主动调用CNT 数到 ARR → 自动溢出、CNT 清零、硬件置 UIF 标志只要定时器在计数必然产生和开不开中断无关。 这是 “溢出” 的本体。你不用中断服务函数只是在 while (1) 里循环读取这个标志属于轮询查询。触发根源Update 溢出事件运行机制主循环不停查询CPU 空转等待不属于中断运行while(1) { // 读取更新中断标志 if(TIM_GetITStatus(TIM2, TIM_IT_Update) SET) { my_custom_func(); // 自定义函数 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清中断标志 } }IRQ 中断请求底层代码自动调用使用TIM_GetITStatus(..., TIM_IT_Update)必须提前开启更新中断使能TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE)硬件溢出后置起中断挂起标志这个标志专门给中断体系用触发根源同样是Update 溢出事件运行机制硬件自动打断主循环优先执行这才是中断。/** * 函 数TIM2中断函数 * 参 数无 * 返 回 值无 * 注意事项此函数为中断函数无需调用中断触发后自动执行 * 函数名为预留的指定名称可以从启动文件复制 * 请确保函数名正确不能有任何差异否则中断函数将不能进入 */ void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET) //判断是否是TIM2的更新事件触发的中断 { Num ; //Num变量自增用于测试定时中断 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位 //中断标志位必须清除 //否则中断将连续不断地触发导致主程序卡死 } }中断向量表 启动文件 (.s)1启动文件 startup_stm32f10x.s汇编文件里面有一张巨大的表格叫中断向量表 Vector table。 表格每一行格式DCD 函数名含义某个中断触发时CPU 要跳转到这个函数的内存地址执行。当你写了同名函数编译器会把这个函数的内存地址填充到向量表里对应的位置。弱定义 WEAK机制如果你 C 语言里重写同名函数优先用你写的函数地址规则表格里写死了固定函数名名字不能随便改必须严格对应TIM2_IRQHandler。节选定时器中断示例DCD TIM2_IRQHandler ; TIM2全局中断入口地址 DCD TIM3_IRQHandler ; TIM3全局中断入口地址 DCD TIM4_IRQHandler ; TIM4全局中断入口地址四、定时器类型所拥有功能数量从上到下兼容高级定时器TIM1 | TIM8总线APB2、通用定时器TIM2~5总线APB1、基本定时器TIM6~7总线APB1外设支持部分定时器如STM32F103C8T6定时器资源TIM1高级、TIM2~4通用五、底层代码初始化多触发一次的更新TIM_TimeBaseInit函数末尾手动产生了更新事件一定要设置中断输出配置清除定时器更新标志位TIM_ClearFlag(TIM2, TIM_FLAG_Update);解决OLED计数的NUM从1开始清除之后从0开始。六、模块化#include stm32f10x.h // Device header /** * 函 数定时中断初始化 * 参 数无 * 返 回 值无 */ void Timer_Init(void) { /*开启时钟*/ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟 /*配置时钟源*/ TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟若不调用此函数TIM默认也为内部时钟 /*时基单元初始化*/ TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量 TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; //时钟分频选择不分频此参数用于配置滤波器时钟不影响时基单元功能 TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; //计数器模式选择向上计数 TIM_TimeBaseInitStructure.TIM_Period 10000 - 1; //计数周期即ARR的值 TIM_TimeBaseInitStructure.TIM_Prescaler 7200 - 1; //预分频器即PSC的值 TIM_TimeBaseInitStructure.TIM_RepetitionCounter 0; //重复计数器高级定时器才会用到 TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit配置TIM2的时基单元 /*中断输出配置*/ TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位 //TIM_TimeBaseInit函数末尾手动产生了更新事件 //若不清除此标志位则开启中断后会立刻进入一次中断 //如果不介意此问题则不清除此标志位也可 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断 /*NVIC中断分组*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2 //即抢占优先级范围0~3响应优先级范围0~3 //此分组配置在整个工程中仅需调用一次 //若有多个中断可以把此代码放在main函数内while循环之前 //若调用多次配置分组的代码则后执行的配置会覆盖先执行的配置 /*NVIC配置*/ NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量 NVIC_InitStructure.NVIC_IRQChannel TIM2_IRQn; //选择配置NVIC的TIM2线 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; //指定NVIC线路使能 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; //指定NVIC线路的抢占优先级为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; //指定NVIC线路的响应优先级为1 NVIC_Init(NVIC_InitStructure); //将结构体变量交给NVIC_Init配置NVIC外设 /*TIM使能*/ TIM_Cmd(TIM2, ENABLE); //使能TIM2定时器开始运行 } /* 定时器中断函数可以复制到使用它的地方 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } */#ifndef __TIMER_H #define __TIMER_H void Timer_Init(void); #endif1、独立输出模式完整周期由 (PSC1)*(ARR1) 共同决定ARR自动重装在值固定100所以公式要除以100。720*1000是1Mhz720是1Khz2、从模式ARR设满量程65535计数器由外部脉冲强制清零周期不由ARR决定因此计算频率不用除以ARR。TIM是16位计数器最大计数上限65535设置满量程最大化可测量脉冲周期范围。最大可测周期对应最小可测频率72是1Mhz普通模式CNT加到ARR自动清零Reset从模式外部TIx触发立刻清零ARR仅作为计数上限保护外部脉冲 → PA6(GPIO) → 滤波器 → 边沿检测 → TI1FP1通道1滤波触发信号工作流程1. 第一个脉冲上升沿​- TI1FP1触发输入捕获把当前CNT值存入CCR1​- 同时从模式Reset立刻将计数器CNT清零​2. 第二个脉冲上升沿​- 再次捕获CNT此时CNT是两个脉冲间隔的计数值​- 再次清零CNT核心逻辑- CCR寄存器存储两个相邻脉冲之间的计数值N​- 脉冲周期​- 脉冲频率①输入捕获模式测频率GetFreq②PWMI模式测频率占空比GetDuty