【嵌入式STM32】TIM定时器总结

发布时间:2026/9/3 11:26:35
【嵌入式STM32】TIM定时器总结 0.基本情况介绍stm32F427定时器的划分如下参考官方数据手册stm32f103定时器1.定时器MX配置这里以TIM4通用定时器为例。先看MX配置这里的配置选项非常多下面整理一下。1.1 Slave Mode 从模式配置项可选的如下-Disable关闭从模式即定时器处于主模式可以向外部发送触发信号Trigger Output (TRGO)后面介绍 。其他下拉都为从模式定义本定时器如何响应外部触发。-External Clock Mode 1【外部时钟模式1很特殊】定时器把外部的触发信号当作时钟用。当设置该项时Clock Source变为灰色此时触发信号的来源从Trigger Source选择后面介绍-Reset Mode:收到触发时计数器清零同步复位计数从0重新开始。-Gated Mode: 收到触发信号后如果触发信号为高电平时计数低电平暂停门控测脉冲宽度使用。-Trigger Mode:收到触发信号启动计数计数完成后停止可用来对多个定时器进行同步启动。1.2 Trigger Source 触发源设置前提定时器 Slave Mode 下拉菜单中选择External Clock Mode时这个“触发信号”的来源取决于你在同一个配置界面中 Trigger Source触发源 下拉框里选择了什么。可以有如下选项- ITR0-ITR3 表示触发源来自内部其他定时器。所有TIMx定时器在内部相连用于定时器同步或链接。当一个定时器处于主模式时它可以通过在定时器溢出时输出TRGOTrigger Output事件来提供触发源从而对另一个处于从模式的定时器的计数器进行复位、启动、停止或提供时钟等操作。具体哪个 ITRx 对应哪个定时器由芯片的互联矩阵决定。下面是F103/F407的定时器主从关系图比如选择ITR0时主定时器TIM1驱动从定时器TIM2后面有案例- ETR1:外部引脚接入外部晶振/时钟源、高精度外部时基同步。在MX中搜索ETR可以看到所有的ETR引脚比如PE0端口对应可设置为TIM4的ETR引脚- Tl1_ED: 从下面的通道1Channel1作为输入触发源此时Channel1 必须配置为输入相关模式如 Input Capture Direct / Indirect不能配置为输出比如PWM等。根据输入触发源的边沿触发。- Tl1FP1: 和上面一样滤波后触发。- Tl2FP2:对应通道2滤波后触发。1.3 Clock Source 时钟源配置定时器的时钟来源当从模式配置为External Clock Mode 1时该项禁用mx的这种配置让人迷惑可选的有- Disable彻底关闭时钟源定时器不会计数或触发事件了相当于定时器废了。- Internal Clock使用内部时钟作为定时器的时钟源通常是来自系统时钟如 APB 时钟经过预分频后的频率。是最常用的时钟源。- ETR2 即External Clock Mode 2定时器从外部引脚ETR2接收时钟或触发信号适合外部事件驱动计数。这里的ETR2对应的还是上面的各个ETR引脚1.3.1 ETR2(External Clock Mode 2)和External Clock Mode 1区别对比维度External Clock Mode 1ETR (External Clock Mode 2)物理引脚复用 CH1 或 CH2 引脚专用 外部ETR 引脚不占用 CHx信号路径CHx → Input Filter → Edge Detector → Slave Mux → CNTETR → 独立 ETR Filter → 独立 ETR Prescaler → CNT最高输入频率较低受限于通用 IO 翻转速率和通道滤波器更高专用模拟/数字路径通常可达 f_APB/2 甚至更高分频能力❌ PSC 无效只能用 Channel Input Capture Prescaler (÷1/2/4/8)✅ 专属 ETR Prescaler (÷1/2/4/8)与 PSC 无关滤波能力通用 Input Filter采样时钟基于 f_DTS独立 ETR Filter采样时钟可选 f_DTS 或外部 ETRPS 分频后的时钟极性选择通过 Trigger Source 间接选择TI1FP1/TI2FP2/TI1_ED直接配置 ETR Polarity上升沿/下降沿/双沿CubeMX 配置位置Clock Source 下拉框 → External Clock Mode 1勾选 “ETR Pin” 复选框不在 Clock Source 下拉框里通道占用⚠️ 占用一个 CHx 通道该通道不能再做 PWM/IC✅ 不占用任何 CHx 通道1.4 Channel1配置选项说明Disable禁用该通道不进行任何输入捕获或输出比较操作。Input Capture direct mode直接输入捕获模式定时器在该通道捕获输入信号的定时器计数值用于测量输入信号的时间特性。Input Capture indirect mode间接输入捕获模式通常用于测量两个通道之间的时间差或配置组合模式。Input Capture triggered by TRC由触发控制器TRC触发的输入捕获模式较少用通常用于高级定时器的同步触发。Output Compare No Output输出比较模式但不产生任何输出信号仅用于软件事件或计数比较。Output Compare CH1输出比较模式并输出信号输出信号与通道1的比较结果相关联如用于同步输出。PWM Generation No Output产生 PWM 波形但不输出到引脚仅用于内部逻辑或触发其他事件。PWM Generation CH1产生 PWM 波形并输出到通道1关联的引脚。单脉冲模式启动后执行一次即停止。其他介绍略2. 关键寄存器可以通过keil如下方式查看运行时各个外设寄存器的数值2.1 TIMx_CR1 — 控制寄存器1CEN 计数器使能使能后计数即开始2.2 TIMx_SR — 状态寄存器UIFUpdate Interrupt Flag 更新中断或计数器溢出标记如果中断被使能会触发中断回调TIFTrigger Interrupt Flag 表示定时器触发事件发生比如TIM2被TIM1触发启动TIM2该标记设12.3 其他TIMx_CNT计数器自动递增/递减的数值。TIMx_ARR计数器达到该值时产生更新事件。3.定时器应用3.1 系统时基(Timebase Sourece)3.1.1 两个时基STM32的hal项目中存在两个“时基”HAL时基和操作系统OS时基。HAL时基为了给HAL_Delay()函数计算延时时间用OS时基给操作系统进行调度使用。MX配置中有个选项Timebase Source默认是SysTick它的意思是HAL时基选择的是SysTick中断。如果选择默认的SysTick会生成如下代码voidSysTick_Handler(void){HAL_IncTick();//驱动HAL时基//...省略xPortSysTickHandler();//驱动OS时基}SysTick_Handler是SysTick中断的回调函数其中驱动了两个时基MX推荐我们在有操作系统时Timebase Source选择某个TIM定时器比如这里选择TIM6在定时器中断响应函数中可以看到voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim-InstanceTIM6){HAL_IncTick();//驱动HAL时基}}那么此时OS时基在哪驱动呢搜一下xPortSysTickHandler发现在cmsis_os2.c中有如下定义#if(USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION0)voidSysTick_Handler(void){/* Clear overflow flag */SysTick-CTRL;if(xTaskGetSchedulerState()!taskSCHEDULER_NOT_STARTED){/* Call tick handler */xPortSysTickHandler();}}该USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 宏在MX时基选择SysTick时是定义为1的。也就是说OS时基还是靠systick驱动3.1.1 超时时间的配置方式以该TIM6为例在stm32f4xx_hal_timebase_tim.c中我们可以看到对TIM6的配置方式注意该函数在运行时会进入两遍第一遍Hal_Init()进入第二遍HAL_RCC_ClockConfig时进入第二遍时钟才是对的HAL_StatusTypeDefHAL_InitTick(uint32_tTickPriority){RCC_ClkInitTypeDef clkconfig;uint32_tuwTimclock,uwAPB1Prescaler0U;uint32_tuwPrescalerValue0U;uint32_tpFLatency;HAL_StatusTypeDef status;/* Enable TIM6 clock */__HAL_RCC_TIM6_CLK_ENABLE();/* Get clock configuration */HAL_RCC_GetClockConfig(clkconfig,pFLatency);/* Get APB1 prescaler */uwAPB1Prescalerclkconfig.APB1CLKDivider;/* Compute TIM6 clock */if(uwAPB1PrescalerRCC_HCLK_DIV1){uwTimclockHAL_RCC_GetPCLK1Freq();}else{uwTimclock2UL*HAL_RCC_GetPCLK1Freq();}/* Compute the prescaler value to have TIM6 counter clock equal to 1MHz */uwPrescalerValue(uint32_t)((uwTimclock/1000000U)-1U);/* Initialize TIM6 */htim6.InstanceTIM6;/* Initialize TIMx peripheral as follow: Period [(TIM6CLK/1000) - 1]. to have a (1/1000) s time base. Prescaler (uwTimclock/1000000 - 1) to have a 1MHz counter clock. ClockDivision 0 Counter direction Up */htim6.Init.Period(1000000U/1000U)-1U;htim6.Init.PrescaleruwPrescalerValue;htim6.Init.ClockDivision0;htim6.Init.CounterModeTIM_COUNTERMODE_UP;htim6.Init.AutoReloadPreloadTIM_AUTORELOAD_PRELOAD_DISABLE;statusHAL_TIM_Base_Init(htim6);if(statusHAL_OK){/* Start the TIM time Base generation in interrupt mode */statusHAL_TIM_Base_Start_IT(htim6);if(statusHAL_OK){/* Enable the TIM6 global Interrupt */HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);/* Configure the SysTick IRQ priority */if(TickPriority(1UL__NVIC_PRIO_BITS)){/* Configure the TIM IRQ priority */HAL_NVIC_SetPriority(TIM6_DAC_IRQn,TickPriority,0U);uwTickPrioTickPriority;}else{statusHAL_ERROR;}}}/* Return function status */returnstatus;}定时时间怎么计算的呢下面代码中Period 表示定时周期计数器计到该值时产生更新事件溢出中断。计数范围是从0计数到Period。这里表示1000个周期Prescaler 表示对时钟的预分频系数。定时器周期的最终公式ToutARR1*PSC1 / Tclk注意最终结果是秒因为Tclk单位是MHz。比如Tclk 84MHzPSC设置为83ARR设置为999计算为0.001s即1ms。要注意ARR和PSC都为16bit有的定时器是32bit最大为65535这样如果要定时周期为1s那么PSC设置为83ARR必须设置为1000000这样就超了范围了此时必须加大PSC的数量级比如PSC设置为83999不行也超了设置为41999ARR为2000-1最终即为1sCounterMode 计数方向 向上计数。AutoReloadPreload ARR寄存器预装载使能这里是关闭的ARR立即生效。TIM_AUTORELOAD_PRELOAD_DISABLE 关闭预装载写入 ARR 的值会立即生效。TIM_AUTORELOAD_PRELOAD_ENABLE 开启预装载新写入的 ARR 值暂存到预装载寄存器计数器溢出时更新 ARR。当你在定时器运行过程中修改 ARR 值关闭预装载时ARR 立即改变可能导致计数器行为异常提前溢出或延迟溢出。开启预装载时ARR 改变会在计数器溢出时统一更新保证计数器行为稳定不会产生瞬时异常。【应用场景】需要动态修改定时周期但保证计数器计数连续稳定时建议开启预装载。简单固定周期时关闭预装载即可。htim6.Init.Period(1000000U/1000U)-1U;htim6.Init.PrescaleruwPrescalerValue;htim6.Init.ClockDivision0;htim6.Init.CounterModeTIM_COUNTERMODE_UP;htim6.Init.AutoReloadPreloadTIM_AUTORELOAD_PRELOAD_DISABLE;3.1.3 定时器中断触发和DMA触发以该TIM6为例这里调用的是HAL_TIM_Base_Start_IT(htim6);使用的是中断触发的方式当定时器计数溢出时触发中断/* Start the TIM time Base generation in interrupt mode */HAL_TIM_Base_Start_IT(htim6);/* Enable the TIM6 global Interrupt */HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);//TIM6_DAC_IRQn为TIM6对应的中断向量对应的中断函数是TIM6_DAC_IRQHandler/* Configure the TIM IRQ priority */HAL_NVIC_SetPriority(TIM6_DAC_IRQn,TickPriority,0U);中断向量对应的函数voidTIM6_DAC_IRQHandler(void){HAL_TIM_IRQHandler(htim6);}最终会调用weak声明的可重定义的函数voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim-InstanceTIM6){HAL_IncTick();//驱动HAL时基}}中断对应的优先级为#define TICK_INT_PRIORITY 15U /*! tick interrupt priority */15为最低的优先级。DMA方式略。3.2 定时器同步(一个启动拉起另一个)3.2.1 配置方式比如从定时器为TIM2模式设置为Trigger mode设置触发源为ITR0对应TIM1观察TIM1溢出后TIM2的计数情况。预期效果TIM1溢出中断后会产生TRGO 触发信号TIM2 作为从定时器等待 TIM1 的 TGRO信号触发。TGRO信号触发后TIM2即启动了。TIM1的MX配置Master/Slave Mode (MSM bit) :主从模式使能设置为EnableTrigger Event Selection:触发事件源选择决定定时器产生触发输出TRGO的条件。Update Event更新事件计数器溢出;TIM2的配置代码变化voidMX_TIM1_Init(void){TIM_MasterConfigTypeDef sMasterConfig{0};//略...sMasterConfig.MasterOutputTriggerTIM_TRGO_UPDATE;sMasterConfig.MasterSlaveModeTIM_MASTERSLAVEMODE_ENABLE;HAL_TIMEx_MasterConfigSynchronization(htim1,sMasterConfig);}/* TIM2 init function */voidMX_TIM2_Init(void){TIM_SlaveConfigTypeDef sSlaveConfig{0};TIM_MasterConfigTypeDef sMasterConfig{0};//略...//从模式启用sSlaveConfig.SlaveModeTIM_SLAVEMODE_TRIGGER;sSlaveConfig.InputTriggerTIM_TS_ITR0;if(HAL_TIM_SlaveConfigSynchro(htim2,sSlaveConfig)!HAL_OK){Error_Handler();}//主模式禁用sMasterConfig.MasterOutputTriggerTIM_TRGO_RESET;sMasterConfig.MasterSlaveModeTIM_MASTERSLAVEMODE_DISABLE;if(HAL_TIMEx_MasterConfigSynchronization(htim2,sMasterConfig)!HAL_OK){Error_Handler();}}中断配置3.2.2 启动及踩坑main.c中启动代码如下错误代码MX_TIM2_Init();//!!!这两句MX生成的代码顺序导致的诡异问题MX_TIM1_Init();/* USER CODE BEGIN 2 */__HAL_TIM_CLEAR_FLAG(htim1,TIM_FLAG_UPDATE);HAL_TIM_Base_Start_IT(htim1);__HAL_TIM_ENABLE_IT(htim2,TIM_IT_UPDATE);3.2.2.1 坑1 - TIM2幽灵启动将USER CODE BEGIN 2之间的代码都 注释掉运行此时由于中断未使能HAL_TIM_Base_Start_IT调用或者__HAL_TIM_ENABLE_IT可以使能中断只能通过寄存器查看定时器的运行情况结果发现定时器TIM2初始化后就开始计数了如图这是为什么百思不得其解。TIM2到底是谁将CEN标记打上的呢查了下只有__HAL_TIM_ENABLE这个宏才能打上该标记关键是也没人调用啊。经过高人指点后才知道问题在于TIM1初始化后会立即产生一个触发更新事件即SR寄存器的UIF会被打上标记并产生一个TGRO信号此时如果TIM2初始化完了接着就启动了这是硬件特性导致的原因是产生该信号将影子寄存器的数值写入真正的寄存器下面框中的即为影子寄存器TIM1初始化后UIF被打上标记如何解决简单将下面两句倒换一下位置就行了MX_TIM1_Init();MX_TIM2_Init();//!!!这两句MX生成的代码顺序导致的诡异问题这样TIM1初始化后该事件并不会被TIM2接收到。但这里是MX生成的所以最好把MX_TIMx_Init()函数重新封装一下不要依赖MX生成的代码。3.2.2.1 坑2 - TIM2中断不进如果改为如下启动代码只启动TIM1的中断此时TIM2中断不会进入必须将__HAL_TIM_ENABLE_IT(htim2, TIM_IT_UPDATE);放开手动启动TIM2的溢出更新中断MX_TIM1_Init();MX_TIM2_Init();//!!!这两句MX生成的代码顺序导致的诡异问题/* USER CODE BEGIN 2 */// __HAL_TIM_CLEAR_FLAG(htim1, TIM_FLAG_UPDATE);HAL_TIM_Base_Start_IT(htim1);//__HAL_TIM_ENABLE_IT(htim2, TIM_IT_UPDATE);3.2.2.2 坑3 - TIM1中断立即进入将__HAL_TIM_CLEAR_FLAG(htim1, TIM_FLAG_UPDATE);注释掉TIM1的中断会在调用完HAL_TIM_Base_Start_IT(htim1);后立即进入这还是因为SR状态寄存器UIF标记导致中断会立即进入因此在启用之前请务必先清除该标记【这个方法适用于任何时候】最终正确的代码MX_TIM1_Init();MX_TIM2_Init();/* USER CODE BEGIN 2 */__HAL_TIM_CLEAR_FLAG(htim1,TIM_FLAG_UPDATE);HAL_TIM_Base_Start_IT(htim1);__HAL_TIM_ENABLE_IT(htim2,TIM_IT_UPDATE);3.3 测量频率测频法两个定时器一个接内部时钟源进行计时一个输入外部输入信号用进行计数测试一定时间段内的计数个数来计算。比如将上面例子中的TIM2改为如下配置时钟源设置为外部对应的IO端口为下图。这样可以从该端口输入信号源启动TIM1后会同步启动TIM2TIM1溢出后统计TIM2的计数__HAL_TIM_GET_COUNTER(htim);当然计数可能溢出因此需在TIM2的溢出中断中进行次数记录测频法适用于频率比较高的信号的测量。测周法对于频率比较低的信号比如10Hz、1Hz甚至更小可以采用测周期法。用输入捕获模式外接外部输入信号到定时器某个输入捕获通道此时定时器时钟源为内部时钟源捕获到上升沿后会跳中断此时记录计数器基于内部时钟源的计数该计数通过HAL_TIM_ReadCapturedValue()获得再跳下一个上升沿中断后再记录计数作差然后依据内部时钟源频率进行计算。下图是TIM2设置1通道为捕获模式上升沿捕获即外部信号上升沿时会产生该定时器的中断。if(htim-InstanceTIM2htim-ChannelHAL_TIM_ACTIVE_CHANNEL_1){uint32_t captured_valueHAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);// 定时器硬件自动将当前计数器 CNT 的值保存到对应的捕获比较寄存器 CCR比如 CCR1 对应通道1 // HAL_TIM_ReadCapturedValue()就是读取这个寄存器的值。}这个值也可以通过设置定时器中断存储计数器值的方式来获得这两种方式都是比较精确的。32.4 输出比较(输出PWM)待完善只能ETR引脚输入 纯计数3.5 DAC(定时器触发)https://doc.embedfire.com/mcu/stm32/f103zhinanzhe/std/zh/latest/book/DAC.html