TIMTIMTimer定时器可以对输入的时钟进行计数并在计数值达到设定值时触发中断。16位计数器、预分频器、自动重装寄存器的时基单元在72MHz技术时钟下可以实现最大59.65s的定时。不仅具备基本的定时中断功能而且包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。根据复杂度和应用场景分为高级定时器、通用定时器、基本定时器三种类型。类型编号总线功能高级定时器TIM1、TIM8APB2拥有通用定时器的全部功能并额外具有重复计数器、死区生成、互补输出、刹车输入等功能通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能并额外由内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能STM32F103C8T6定时器资源TIM1、TIM2、TIM3、TIM4基本定时器预分频器、自动重装载寄存器、CNT计数器共同组成时基单元。基本定时器使用内部时钟CK_INT作为工作时钟STM32中一般为72MHz。PSC预分频器的作用是把输入时钟分频得到真正送给计数器CNT的计数时钟。例如TIM_Prescaler 7200 - 1如果输入时钟为72MHz经过PSC后频率变为72MHz / 7200 10kHz也就是说CNT每1 / 10k 0.1ms计数加一。ARR自动重装载寄存器决定CNT数到多少后溢出例如ARR 10000 - 1计数器的计数范围为0, 1, ... 9999。PSC和ARR共同决定计数时间定时时间 1 / (输入频率 / (PSC * ARR)) (PSC * ARR) / 输入频率。CNT是Counter计数器本体基本定时器一般只支持向上计数即计数值0, 1, 2 ...每来一个上升沿计数加一。“根据控制位的设定在 U 事件时传送预装载寄存器至实际寄存器”在STM32中有些寄存器不是一些入就立刻生效而是先写入预装载寄存器等待更新事件U发生后再转移到真正工作的寄存器。这样做可以防止计数过程中参数突然变化导致定时周期异常。更新事件UUpdate Event通常在以下情况产生CNT计数溢出、软件手动产生更新事件、定时器重新初始化时产生更新事件。更新事件发生后通常可以进行CNT重新从0开始计数 PSC、ARR新值装载生效 可以产生中断 可以产生DMA请求 可以产生TRGO输出中断和DMA输出。当更新事件发生后如果使能了更新中断就会进入定时器中断函数。例如TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);当CNT溢出时会触发TIM6_IRQHandler(void)如果使能DMA请求则更新事件也可以触发DMA传输。所以基本定时器常用于周期性中断 周期性执行任务 周期性触发DMA 周期性触发DAC触发控制器TRGOTrigger Output触发输出可以用于触发DAC转换典型用途TIM6 定时产生 TRGO - DAC 接收 TRGO - DAC 按固定频率输出波形基本定时器整体工作流程可以概括为RCC 提供 TIMxCLK ↓ PSC 对时钟进行分频 ↓ CNT 按分频后的时钟递增计数 ↓ CNT 计到 ARR 后溢出 ↓ 产生更新事件 U ↓ CNT 清零重新计数 ↓ 可触发中断、DMA 或 TRGO总结基本定时器就是一个由内部时钟驱动的向上计数器。通过PSC分频、ARR设定周期、CNT计满产生更新事件从而触发中断、DMA或DAC。通用定时器与基本定时器相比通用定时器在可以实现基本定时器所有功能的基础上还能实现定时中断 PWM 输出 输入捕获 输出比较 编码器计数 外部脉冲计数 主从定时器同步 触发 ADC / DAC DMA 传输时钟源选择部分通用定时器的时钟源有来自 RCC 的 TIMxCLK TIMx_ETR ITR0 ~ ITR3 TI1F_ED TI1FP1 / TI2FP2内部时钟CK_INT与基本定时器相同STM32中为72MHz。外部触发输入ETRTIMx_ETR → ETR → 极性选择、边沿检测和预分频器 → 输入滤波ETR是External Trigger Input它可以让外部引脚上的信号参与定时器控制例如外部脉冲作为计数时钟 外部信号启动定时器 外部信号复位定时器 外部信号触发一次定时操作极性选择、边沿检测、预分频、输入滤波等一些列操作可以提升抗干扰能力。使用ETR做工作时钟的模式叫“外部时钟模式2”内部触发输入ITR0 - ITR3ITR是Internal Trigger内部触发信号用于实现定时器的主从模式即多个定时器的联动。TI1F_ED、TI1FP1、TI1FP2这些信号来自定时器通道输入例如CH1、CH2可以作为定时器触发源或计数源。触发控制器TRGOTrigger Output与基本定时器相似可以用于硬件触发DAC、ADC避免CPU被频繁打断。从模式控制器从模式控制器用于控制定时器如何响应外部或内部触发信号它可以让定时器工作在不同的从模式下例如复位模式外部触发信号一来CNT被清零门控模式只有外部信号有效时定时器才计数外部时钟模式外部时钟作为CNT的计数时钟来一次计数器加1编码器模式CH1和CH2接旋转编码器A、B相定时器自动判定方向并计数时基单元PSCARRCNT、更新事件U、中断和DMA输出与基本计时器相同CNT支持向上计数、向下计数、中央对齐模式。输入通道部分CH1 - CH4CH1 - CH4四个通道可以作为输入也可以作为输出。作为输入时可以用于输入捕获、测频率、测周期、测脉宽、测占空比…每个通道后有输入滤波器和边沿检测器用于处理外部输入信号。滤波器用于去除毛刺信号边沿检测器用于选择检测上升沿/下降沿/双边沿。经过处理得到TIxFPx信号送往捕获/比较模块用于输入捕获。捕获/比较寄存器CCR1 - CCR4CCR是通用定时器中非常重要的寄存器主要有两种用途输入捕获时保存CNT的值输出比较时与CNT比较。输入捕获模式输入捕获时外部信号边沿到来当前CNT的值会被保存到CCRx中例如CH1 检测到上升沿 当前 CNT 3560 CCR1 3560通过两次捕获值的差可以计算输入信号的频率或周期第一次捕获 CCR1 1000 第二次捕获 CCR1 3000 差值 2000如果CNT计数频率为1MHz则输入信号周期为2000 / 1 MHz 2 ms频率为1 / 2 ms 500 Hz。输出比较模式输出比较模式下CNT会不断与CCRx比较当CNT CCRx时就产生比较匹配事件可以用来翻转输出电平、置高/低电平、产生PWM、产生中断、产生DMA请求输出控制部分右侧输出控制 OC1 OC2 OC3 OC4 TIMx_CH1 TIMx_CH2 TIMx_CH3 TIMx_CH4当通道作为输出时CCR和CNT的比较结果会送到输出控制模块决定引脚输出什么波形PWM输出PWM指的是Pulse Width Modulation脉冲宽度调制。本质是“用一串高低电平快速切换的方波通过改变高电平持续时间的比例来控制等效输出效果”。PWM输出是通用定时器最常用的功能之一。PWM的周期由ARR决定PWM 频率 TIMxCLK / ((PSC 1) × (ARR 1))占空比有CCRx决定占空比 CCRx / (ARR 1)其中ARR决定PWM周期CCR决定PWM的占空比。编码器接口输入CH1、CH2的信号可以进入编码器接口相关逻辑。常用的是TIMx_CH1 接编码器 A 相 TIMx_CH2 接编码器 B 相定时器根据A、B相的相位关系判断方向例如A 相领先 B 相 → 正转 → CNT 加 B 相领先 A 相 → 反转 → CNT 减使用编码器时软件不需要每次中断都去判断方向定时器硬件会自动完成计数。通用定时器整体工作流程作为普通定时器使用RCC 提供 TIMxCLK ↓ PSC 预分频 ↓ CNT 计数 ↓ CNT 计到 ARR ↓ 产生更新事件 U ↓ 触发中断 / DMA / TRGO作为PWM输出使用RCC 提供 TIMxCLK ↓ PSC 分频 ↓ CNT 从 0 计到 ARR ↓ CNT 与 CCRx 比较 ↓ 输出控制模块产生 PWM ↓ 从 TIMx_CHx 引脚输出作为输入捕获使用外部信号进入 TIMx_CHx ↓ 输入滤波和边沿检测 ↓ 检测到指定边沿 ↓ 把当前 CNT 值保存到 CCRx ↓ 产生捕获中断或 DMA 请求作为编码器使用编码器 A/B 相输入 CH1 / CH2 ↓ 输入滤波和边沿检测 ↓ 编码器接口判断方向 ↓ CNT 自动加减 ↓ 读取 CNT 得到位置变化定时中断基本结构预分频器时序CK_PSC是预分频器的输入时钟CNT_EN是计数器使能信号高电平时计数器正常工作低电平时计数器停止。CK_CNT是预分频器输出的时钟也是计数器的输入时钟。当CNT_EN变为高电平后经过一段延时分频器才会产生第一个CK_CNT脉冲计数器才开始工作。这里的预分频器的参数指的是预分频器系数与PSC的关系为预分频系数 PSC 1因此预分频器的参数从1变到2说明PSC从0变到1从预分频控制寄存器的值也可以看出。图99中起初PSC 0输出频率与输入频率相同中间向PSC写入新值1但是由于UEV更新事件没发生此时的PSC新值还在预装载寄存器即图中的预分频控制寄存器Prescaler control register所以输出频率不变。之后UEV发生新值写入PSC即预分频缓冲器Prescaler buffer输出频率变为输入频率的一半。计数器时序图CK_INT是内部时钟72MHzCNT_EN时钟使能信号时钟使能信号变为高电平后一段时间后PSC发出第一个脉冲计数器开始工作每个周期计数加1达到ARR后溢出产生更新事件并将更新中断标志UIF置为1申请中断因此需要在中断函数中将UIF置0。计数器溢出的频率叫溢出频率溢出频率 PSC输入频率 / (PSC 1)(ARR 1)。自动重装载寄存器ARR自动重装载寄存器有两种模式预装载和不预装载。不预装载模式下修改ARR的值计数范围会在当前计数周期改变如下图在计数时将ARR由FF修改为36则计数最大值立刻变为36。预装载模式下修改ARR的值计数范围不会立刻改变此时计数范围由自动重装载影子寄存器Auto-reload preload register决定只有在计数溢出后更新事件UEV发生才将ARPR(Auto-reload preload register)的值写入ARSR(Auto-reload preload register)自动重装载影子寄存器计数范围才变。时钟树时钟源部分产生原始时钟HSI高速内部时钟HSIHigh-speed Interal clock signal8MHz不需要外部晶振上电后默认使用HSI是系统时钟SYSCLK的来源之一。HSE高速外部时钟HSEHigh-speed External clock signal4 - 16 MHz精度高是SYSCLK的来源之一。LSE低速外部时钟LSELow-speed External clock signal32.768kHz常用于RTC实时时钟频率低功耗低精度比LSI好。LSI低俗内部时钟LSILow-speed Internal clock signal40kHz可用于独立看门狗也是RTC时钟源之一。PLL部分倍频产生高速时钟PLL锁相环倍频器可以把低频率的时钟变成高频率时钟。来源有两个HSI和HSE。HSI可直接作为系统时钟SYSCLK也可经过PLL经过PLL之前要先将频率将为一半即4MHz。HSE可直接进入PLL也可先降频一半进入PLL。PLLMUL是PLL倍频系数可以是x2, x3,..., x16当HSE为8MHzPLLMUL为9时产生频率为8MHz * 9 72MHz这是STM32F1常见系统时钟。SYSCLK部分选择系统主时钟SW选择器实现系统主时钟SYSCLK来源的选择可以是HSIHSEPLLCLK。SYSCLK时整个芯片的的主时钟后面的AHB、APB1、APB2都是经过SYSCLK分配得到的。时钟分配电路SYSCLK先经过AHB Prescaler进行分频这里预设分频系数为1即不进行降频仍输出72MHz。给APB1总线设置时钟时APB1 Prescaler预设分频系数为2即APB1频率为36MHz且最大值为36MHz。TIM2 -7通用定时器基本定时器连在APB1上APB1在给TIM2 - 7设置频率时由于APB1 Prescaler分频系数为2所以要先将频率翻倍再输出给定时器所以TIM2 - 7频率为72MHz。TIM1和TIM8高级定时器连在APB2上APB2 Prescaler预设分频系数为1所以APB2频率为72MHz直接输出给TIM1和TIM8因此高级定时器的频率为72MHz。综上所有定时器的频率都为72MHz。定时中断由于定时器位于STM32内部不涉及外部硬件因此将定时器模块代码存放在System/下。整体思路使能内部时钟 - 选择时钟源 - 配置时基单元包括PSC、ARR、CNT - 配置中断输出控制使能更新中断 - 配置NVIC - 使能定时器运行控制需要使用的函数// 时基单元初始化函数voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);// 使能计数器运行控制部分voidTIM_Cmd(TIM_TypeDef*TIMx,FunctionalState NewState);// 使能中断输出信号voidTIM_ITConfig(TIM_TypeDef*TIMx,uint16_tTIM_IT,FunctionalState NewState);// 以下是时钟源选择函数voidTIM_InternalClockConfig(TIM_TypeDef*TIMx);// 选择内部时钟voidTIM_ITRxExternalClockConfig(TIM_TypeDef*TIMx,uint16_tTIM_InputTriggerSource);// 用另一个定时器的触发信号计数实现级联// 使用 CH1/CH2 引脚输入脉冲计数voidTIM_TIxExternalClockConfig(TIM_TypeDef*TIMx,uint16_tTIM_TIxExternalCLKSource,uint16_tTIM_ICPolarity,uint16_tICFilter);// 用 ETR 引脚输入脉冲计数外部时钟模式1voidTIM_ETRClockMode1Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 用 ETR 引脚输入脉冲计数外部时钟模式2voidTIM_ETRClockMode2Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 只配置 ETR 引脚滤波、极性、分频本身不启动计数voidTIM_ETRConfig(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 检查某个定时器标志位是否被置为1FlagStatusTIM_GetFlagStatus(TIM_TypeDef*TIMx,uint16_tTIM_FLAG);// 清除某个定时器标志位voidTIM_ClearFlag(TIM_TypeDef*TIMx,uint16_tTIM_FLAG);// 检查某个定时器中断是否真正产生ITStatusTIM_GetITStatus(TIM_TypeDef*TIMx,uint16_tTIM_IT);// 清除某个定时器中断挂起位voidTIM_ClearITPendingBit(TIM_TypeDef*TIMx,uint16_tTIM_IT);完成定时器驱动代码// Timer.c#includestm32f10x.h// Device headerexternint16_tnum;voidTimer_Init(void){// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);// 选择时钟源TIM_InternalClockConfig(TIM2);// 配置时基单元主要是计数模式PSC和ARR的值CNT未设置可通过其他函数设置CNT起始值TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;// 决定输入滤波等模块用多快的采样时钟影响不大TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;// 向上计数TIM_TimeBaseInitStructure.TIM_Period10000-1;// 设置ARRTIM_TimeBaseInitStructure.TIM_Prescaler7200-1;// 设置PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;// 高级定时器才有的功能TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);// 使能更新中断连接更新中断到NVICTIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn;// 选择中断通道NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority1;// 子优先级响应优先级NVIC_Init(NVIC_InitStructure);// 使能定时器运行控制TIM_Cmd(TIM2,ENABLE);}// 重写中断函数voidTIM2_IRQHandler(void){// 检查标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update)SET){num;// 清除标志位TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}// Timer.h#ifndef__TIMER_H#define__TIMER_HvoidTimer_Init(void);#endif完成main.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeTimer.huint16_tnum;intmain(void){OLED_Init();Timer_Init();OLED_ShowString(1,1,Count:);while(1){OLED_ShowNum(1,7,num,6);}}当需要在其他.c文件里操作当前.c文件中的变量时可以在其他.c中使用extern关键字。本项目还可以通过将中断函数写在main.c中来解决变量作用域不同的问题。以上代码实现的定时器计数在实验过程中可以发现是从1开始计数的说明中断函数在初始化之后就立刻执行了一次问题出现在时基单元初始化函数TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure);中这个函数在结束时实行了voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct){.../* Generate an update event to reload the Prescaler and the Repetition counter values immediately */TIMx-EGRTIM_PSCReloadMode_Immediate;}产生了一个更新事件来重置PSC和计数器的值这导致定时器初始化完成后立刻触发中断函数计数值加一。解决方法是定时器初始化完成后使用TIM_ClearFlag()清除时间更新标志位避免产生更新事件。定时器外部中断ETRExternal Trigger外部触发输入配置函数// 只配置ETR输入本身的参数不会让定时器进入外部时钟模式voidTIM_ETRClockMode1Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 配置ETR输入参数设置定时器为外部时钟模式1voidTIM_ETRClockMode2Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 配置ETR输入参数设置定时器为外部时钟模式2voidTIM_ETRConfig(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);定时器驱动代码// timer.c#includestm32f10x.h// Device headerexternuint16_tnum;voidTimer_Init(void){// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);// 选择时钟源选择外部时钟// 由于TIM2的ETR引脚PA0接触不亮所以使用TIM_TIxExternalClockConfig替代// TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x10);TIM_TIxExternalClockConfig(TIM2,TIM_TIxExternalCLK1Source_TI2,TIM_ICPolarity_Rising,0x0F);// 配置时基单元主要是计数模式PSC和ARR的值CNT未设置可通过其他函数设置CNT起始值TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;// 决定输入滤波等模块用多快的采样时钟影响不大TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;// 向上计数TIM_TimeBaseInitStructure.TIM_Period10-1;// 设置ARRTIM_TimeBaseInitStructure.TIM_Prescaler1-1;// 设置PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;// 高级定时器才有的功能TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);// 使能更新中断连接更新中断到NVICTIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn;// 选择中断通道NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority1;// 子优先级响应优先级NVIC_Init(NVIC_InitStructure);// 使能定时器运行控制TIM_Cmd(TIM2,ENABLE);}uint16_tTimer_GetNum(void){returnTIM_GetCounter(TIM2);}// 重写中断函数voidTIM2_IRQHandler(void){// 检查标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update)SET){num;// 清除标志位TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}// Timer.h#ifndef__TIMER_H#define__TIMER_HvoidTimer_Init(void);uint16_tTimer_GetNum(void);#endif对射式红外传感器驱动代码#includestm32f10x.h// Device headervoidInfraredSensor_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU;GPIO_InitStructure.GPIO_PinGPIO_Pin_1;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);}#ifndef__InfraredSensor_H#define__InfraredSensor_HvoidInfraredSensor_Init(void);uint16_tInfraredSensor_GetNum(void);#endifmain.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeTimer.h#includeInfraredSensor.huint16_tnum;intmain(void){OLED_Init();Timer_Init();InfraredSensor_Init();OLED_ShowString(1,1,Num:);OLED_ShowString(2,1,Couter:);while(1){OLED_ShowNum(1,5,num,3);OLED_ShowNum(2,8,Timer_GetNum(),3);}}IO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);}#ifndef __InfraredSensor_H#define __InfraredSensor_Hvoid InfraredSensor_Init(void);uint16_t InfraredSensor_GetNum(void);#endifmain.c c #include stm32f10x.h // Device header #include OLED_Software.h #include Timer.h #include InfraredSensor.h uint16_t num; int main(void) { OLED_Init(); Timer_Init(); InfraredSensor_Init(); OLED_ShowString(1, 1, Num:); OLED_ShowString(2, 1, Couter:); while(1) { OLED_ShowNum(1, 5, num, 3); OLED_ShowNum(2, 8, Timer_GetNum(), 3); } }
[STM32]Day6-Part1定时计数+定时器外部中断
发布时间:2026/6/15 6:26:15
TIMTIMTimer定时器可以对输入的时钟进行计数并在计数值达到设定值时触发中断。16位计数器、预分频器、自动重装寄存器的时基单元在72MHz技术时钟下可以实现最大59.65s的定时。不仅具备基本的定时中断功能而且包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。根据复杂度和应用场景分为高级定时器、通用定时器、基本定时器三种类型。类型编号总线功能高级定时器TIM1、TIM8APB2拥有通用定时器的全部功能并额外具有重复计数器、死区生成、互补输出、刹车输入等功能通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能并额外由内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能STM32F103C8T6定时器资源TIM1、TIM2、TIM3、TIM4基本定时器预分频器、自动重装载寄存器、CNT计数器共同组成时基单元。基本定时器使用内部时钟CK_INT作为工作时钟STM32中一般为72MHz。PSC预分频器的作用是把输入时钟分频得到真正送给计数器CNT的计数时钟。例如TIM_Prescaler 7200 - 1如果输入时钟为72MHz经过PSC后频率变为72MHz / 7200 10kHz也就是说CNT每1 / 10k 0.1ms计数加一。ARR自动重装载寄存器决定CNT数到多少后溢出例如ARR 10000 - 1计数器的计数范围为0, 1, ... 9999。PSC和ARR共同决定计数时间定时时间 1 / (输入频率 / (PSC * ARR)) (PSC * ARR) / 输入频率。CNT是Counter计数器本体基本定时器一般只支持向上计数即计数值0, 1, 2 ...每来一个上升沿计数加一。“根据控制位的设定在 U 事件时传送预装载寄存器至实际寄存器”在STM32中有些寄存器不是一些入就立刻生效而是先写入预装载寄存器等待更新事件U发生后再转移到真正工作的寄存器。这样做可以防止计数过程中参数突然变化导致定时周期异常。更新事件UUpdate Event通常在以下情况产生CNT计数溢出、软件手动产生更新事件、定时器重新初始化时产生更新事件。更新事件发生后通常可以进行CNT重新从0开始计数 PSC、ARR新值装载生效 可以产生中断 可以产生DMA请求 可以产生TRGO输出中断和DMA输出。当更新事件发生后如果使能了更新中断就会进入定时器中断函数。例如TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);当CNT溢出时会触发TIM6_IRQHandler(void)如果使能DMA请求则更新事件也可以触发DMA传输。所以基本定时器常用于周期性中断 周期性执行任务 周期性触发DMA 周期性触发DAC触发控制器TRGOTrigger Output触发输出可以用于触发DAC转换典型用途TIM6 定时产生 TRGO - DAC 接收 TRGO - DAC 按固定频率输出波形基本定时器整体工作流程可以概括为RCC 提供 TIMxCLK ↓ PSC 对时钟进行分频 ↓ CNT 按分频后的时钟递增计数 ↓ CNT 计到 ARR 后溢出 ↓ 产生更新事件 U ↓ CNT 清零重新计数 ↓ 可触发中断、DMA 或 TRGO总结基本定时器就是一个由内部时钟驱动的向上计数器。通过PSC分频、ARR设定周期、CNT计满产生更新事件从而触发中断、DMA或DAC。通用定时器与基本定时器相比通用定时器在可以实现基本定时器所有功能的基础上还能实现定时中断 PWM 输出 输入捕获 输出比较 编码器计数 外部脉冲计数 主从定时器同步 触发 ADC / DAC DMA 传输时钟源选择部分通用定时器的时钟源有来自 RCC 的 TIMxCLK TIMx_ETR ITR0 ~ ITR3 TI1F_ED TI1FP1 / TI2FP2内部时钟CK_INT与基本定时器相同STM32中为72MHz。外部触发输入ETRTIMx_ETR → ETR → 极性选择、边沿检测和预分频器 → 输入滤波ETR是External Trigger Input它可以让外部引脚上的信号参与定时器控制例如外部脉冲作为计数时钟 外部信号启动定时器 外部信号复位定时器 外部信号触发一次定时操作极性选择、边沿检测、预分频、输入滤波等一些列操作可以提升抗干扰能力。使用ETR做工作时钟的模式叫“外部时钟模式2”内部触发输入ITR0 - ITR3ITR是Internal Trigger内部触发信号用于实现定时器的主从模式即多个定时器的联动。TI1F_ED、TI1FP1、TI1FP2这些信号来自定时器通道输入例如CH1、CH2可以作为定时器触发源或计数源。触发控制器TRGOTrigger Output与基本定时器相似可以用于硬件触发DAC、ADC避免CPU被频繁打断。从模式控制器从模式控制器用于控制定时器如何响应外部或内部触发信号它可以让定时器工作在不同的从模式下例如复位模式外部触发信号一来CNT被清零门控模式只有外部信号有效时定时器才计数外部时钟模式外部时钟作为CNT的计数时钟来一次计数器加1编码器模式CH1和CH2接旋转编码器A、B相定时器自动判定方向并计数时基单元PSCARRCNT、更新事件U、中断和DMA输出与基本计时器相同CNT支持向上计数、向下计数、中央对齐模式。输入通道部分CH1 - CH4CH1 - CH4四个通道可以作为输入也可以作为输出。作为输入时可以用于输入捕获、测频率、测周期、测脉宽、测占空比…每个通道后有输入滤波器和边沿检测器用于处理外部输入信号。滤波器用于去除毛刺信号边沿检测器用于选择检测上升沿/下降沿/双边沿。经过处理得到TIxFPx信号送往捕获/比较模块用于输入捕获。捕获/比较寄存器CCR1 - CCR4CCR是通用定时器中非常重要的寄存器主要有两种用途输入捕获时保存CNT的值输出比较时与CNT比较。输入捕获模式输入捕获时外部信号边沿到来当前CNT的值会被保存到CCRx中例如CH1 检测到上升沿 当前 CNT 3560 CCR1 3560通过两次捕获值的差可以计算输入信号的频率或周期第一次捕获 CCR1 1000 第二次捕获 CCR1 3000 差值 2000如果CNT计数频率为1MHz则输入信号周期为2000 / 1 MHz 2 ms频率为1 / 2 ms 500 Hz。输出比较模式输出比较模式下CNT会不断与CCRx比较当CNT CCRx时就产生比较匹配事件可以用来翻转输出电平、置高/低电平、产生PWM、产生中断、产生DMA请求输出控制部分右侧输出控制 OC1 OC2 OC3 OC4 TIMx_CH1 TIMx_CH2 TIMx_CH3 TIMx_CH4当通道作为输出时CCR和CNT的比较结果会送到输出控制模块决定引脚输出什么波形PWM输出PWM指的是Pulse Width Modulation脉冲宽度调制。本质是“用一串高低电平快速切换的方波通过改变高电平持续时间的比例来控制等效输出效果”。PWM输出是通用定时器最常用的功能之一。PWM的周期由ARR决定PWM 频率 TIMxCLK / ((PSC 1) × (ARR 1))占空比有CCRx决定占空比 CCRx / (ARR 1)其中ARR决定PWM周期CCR决定PWM的占空比。编码器接口输入CH1、CH2的信号可以进入编码器接口相关逻辑。常用的是TIMx_CH1 接编码器 A 相 TIMx_CH2 接编码器 B 相定时器根据A、B相的相位关系判断方向例如A 相领先 B 相 → 正转 → CNT 加 B 相领先 A 相 → 反转 → CNT 减使用编码器时软件不需要每次中断都去判断方向定时器硬件会自动完成计数。通用定时器整体工作流程作为普通定时器使用RCC 提供 TIMxCLK ↓ PSC 预分频 ↓ CNT 计数 ↓ CNT 计到 ARR ↓ 产生更新事件 U ↓ 触发中断 / DMA / TRGO作为PWM输出使用RCC 提供 TIMxCLK ↓ PSC 分频 ↓ CNT 从 0 计到 ARR ↓ CNT 与 CCRx 比较 ↓ 输出控制模块产生 PWM ↓ 从 TIMx_CHx 引脚输出作为输入捕获使用外部信号进入 TIMx_CHx ↓ 输入滤波和边沿检测 ↓ 检测到指定边沿 ↓ 把当前 CNT 值保存到 CCRx ↓ 产生捕获中断或 DMA 请求作为编码器使用编码器 A/B 相输入 CH1 / CH2 ↓ 输入滤波和边沿检测 ↓ 编码器接口判断方向 ↓ CNT 自动加减 ↓ 读取 CNT 得到位置变化定时中断基本结构预分频器时序CK_PSC是预分频器的输入时钟CNT_EN是计数器使能信号高电平时计数器正常工作低电平时计数器停止。CK_CNT是预分频器输出的时钟也是计数器的输入时钟。当CNT_EN变为高电平后经过一段延时分频器才会产生第一个CK_CNT脉冲计数器才开始工作。这里的预分频器的参数指的是预分频器系数与PSC的关系为预分频系数 PSC 1因此预分频器的参数从1变到2说明PSC从0变到1从预分频控制寄存器的值也可以看出。图99中起初PSC 0输出频率与输入频率相同中间向PSC写入新值1但是由于UEV更新事件没发生此时的PSC新值还在预装载寄存器即图中的预分频控制寄存器Prescaler control register所以输出频率不变。之后UEV发生新值写入PSC即预分频缓冲器Prescaler buffer输出频率变为输入频率的一半。计数器时序图CK_INT是内部时钟72MHzCNT_EN时钟使能信号时钟使能信号变为高电平后一段时间后PSC发出第一个脉冲计数器开始工作每个周期计数加1达到ARR后溢出产生更新事件并将更新中断标志UIF置为1申请中断因此需要在中断函数中将UIF置0。计数器溢出的频率叫溢出频率溢出频率 PSC输入频率 / (PSC 1)(ARR 1)。自动重装载寄存器ARR自动重装载寄存器有两种模式预装载和不预装载。不预装载模式下修改ARR的值计数范围会在当前计数周期改变如下图在计数时将ARR由FF修改为36则计数最大值立刻变为36。预装载模式下修改ARR的值计数范围不会立刻改变此时计数范围由自动重装载影子寄存器Auto-reload preload register决定只有在计数溢出后更新事件UEV发生才将ARPR(Auto-reload preload register)的值写入ARSR(Auto-reload preload register)自动重装载影子寄存器计数范围才变。时钟树时钟源部分产生原始时钟HSI高速内部时钟HSIHigh-speed Interal clock signal8MHz不需要外部晶振上电后默认使用HSI是系统时钟SYSCLK的来源之一。HSE高速外部时钟HSEHigh-speed External clock signal4 - 16 MHz精度高是SYSCLK的来源之一。LSE低速外部时钟LSELow-speed External clock signal32.768kHz常用于RTC实时时钟频率低功耗低精度比LSI好。LSI低俗内部时钟LSILow-speed Internal clock signal40kHz可用于独立看门狗也是RTC时钟源之一。PLL部分倍频产生高速时钟PLL锁相环倍频器可以把低频率的时钟变成高频率时钟。来源有两个HSI和HSE。HSI可直接作为系统时钟SYSCLK也可经过PLL经过PLL之前要先将频率将为一半即4MHz。HSE可直接进入PLL也可先降频一半进入PLL。PLLMUL是PLL倍频系数可以是x2, x3,..., x16当HSE为8MHzPLLMUL为9时产生频率为8MHz * 9 72MHz这是STM32F1常见系统时钟。SYSCLK部分选择系统主时钟SW选择器实现系统主时钟SYSCLK来源的选择可以是HSIHSEPLLCLK。SYSCLK时整个芯片的的主时钟后面的AHB、APB1、APB2都是经过SYSCLK分配得到的。时钟分配电路SYSCLK先经过AHB Prescaler进行分频这里预设分频系数为1即不进行降频仍输出72MHz。给APB1总线设置时钟时APB1 Prescaler预设分频系数为2即APB1频率为36MHz且最大值为36MHz。TIM2 -7通用定时器基本定时器连在APB1上APB1在给TIM2 - 7设置频率时由于APB1 Prescaler分频系数为2所以要先将频率翻倍再输出给定时器所以TIM2 - 7频率为72MHz。TIM1和TIM8高级定时器连在APB2上APB2 Prescaler预设分频系数为1所以APB2频率为72MHz直接输出给TIM1和TIM8因此高级定时器的频率为72MHz。综上所有定时器的频率都为72MHz。定时中断由于定时器位于STM32内部不涉及外部硬件因此将定时器模块代码存放在System/下。整体思路使能内部时钟 - 选择时钟源 - 配置时基单元包括PSC、ARR、CNT - 配置中断输出控制使能更新中断 - 配置NVIC - 使能定时器运行控制需要使用的函数// 时基单元初始化函数voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);// 使能计数器运行控制部分voidTIM_Cmd(TIM_TypeDef*TIMx,FunctionalState NewState);// 使能中断输出信号voidTIM_ITConfig(TIM_TypeDef*TIMx,uint16_tTIM_IT,FunctionalState NewState);// 以下是时钟源选择函数voidTIM_InternalClockConfig(TIM_TypeDef*TIMx);// 选择内部时钟voidTIM_ITRxExternalClockConfig(TIM_TypeDef*TIMx,uint16_tTIM_InputTriggerSource);// 用另一个定时器的触发信号计数实现级联// 使用 CH1/CH2 引脚输入脉冲计数voidTIM_TIxExternalClockConfig(TIM_TypeDef*TIMx,uint16_tTIM_TIxExternalCLKSource,uint16_tTIM_ICPolarity,uint16_tICFilter);// 用 ETR 引脚输入脉冲计数外部时钟模式1voidTIM_ETRClockMode1Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 用 ETR 引脚输入脉冲计数外部时钟模式2voidTIM_ETRClockMode2Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 只配置 ETR 引脚滤波、极性、分频本身不启动计数voidTIM_ETRConfig(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 检查某个定时器标志位是否被置为1FlagStatusTIM_GetFlagStatus(TIM_TypeDef*TIMx,uint16_tTIM_FLAG);// 清除某个定时器标志位voidTIM_ClearFlag(TIM_TypeDef*TIMx,uint16_tTIM_FLAG);// 检查某个定时器中断是否真正产生ITStatusTIM_GetITStatus(TIM_TypeDef*TIMx,uint16_tTIM_IT);// 清除某个定时器中断挂起位voidTIM_ClearITPendingBit(TIM_TypeDef*TIMx,uint16_tTIM_IT);完成定时器驱动代码// Timer.c#includestm32f10x.h// Device headerexternint16_tnum;voidTimer_Init(void){// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);// 选择时钟源TIM_InternalClockConfig(TIM2);// 配置时基单元主要是计数模式PSC和ARR的值CNT未设置可通过其他函数设置CNT起始值TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;// 决定输入滤波等模块用多快的采样时钟影响不大TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;// 向上计数TIM_TimeBaseInitStructure.TIM_Period10000-1;// 设置ARRTIM_TimeBaseInitStructure.TIM_Prescaler7200-1;// 设置PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;// 高级定时器才有的功能TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);// 使能更新中断连接更新中断到NVICTIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn;// 选择中断通道NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority1;// 子优先级响应优先级NVIC_Init(NVIC_InitStructure);// 使能定时器运行控制TIM_Cmd(TIM2,ENABLE);}// 重写中断函数voidTIM2_IRQHandler(void){// 检查标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update)SET){num;// 清除标志位TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}// Timer.h#ifndef__TIMER_H#define__TIMER_HvoidTimer_Init(void);#endif完成main.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeTimer.huint16_tnum;intmain(void){OLED_Init();Timer_Init();OLED_ShowString(1,1,Count:);while(1){OLED_ShowNum(1,7,num,6);}}当需要在其他.c文件里操作当前.c文件中的变量时可以在其他.c中使用extern关键字。本项目还可以通过将中断函数写在main.c中来解决变量作用域不同的问题。以上代码实现的定时器计数在实验过程中可以发现是从1开始计数的说明中断函数在初始化之后就立刻执行了一次问题出现在时基单元初始化函数TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure);中这个函数在结束时实行了voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct){.../* Generate an update event to reload the Prescaler and the Repetition counter values immediately */TIMx-EGRTIM_PSCReloadMode_Immediate;}产生了一个更新事件来重置PSC和计数器的值这导致定时器初始化完成后立刻触发中断函数计数值加一。解决方法是定时器初始化完成后使用TIM_ClearFlag()清除时间更新标志位避免产生更新事件。定时器外部中断ETRExternal Trigger外部触发输入配置函数// 只配置ETR输入本身的参数不会让定时器进入外部时钟模式voidTIM_ETRClockMode1Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 配置ETR输入参数设置定时器为外部时钟模式1voidTIM_ETRClockMode2Config(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);// 配置ETR输入参数设置定时器为外部时钟模式2voidTIM_ETRConfig(TIM_TypeDef*TIMx,uint16_tTIM_ExtTRGPrescaler,uint16_tTIM_ExtTRGPolarity,uint16_tExtTRGFilter);定时器驱动代码// timer.c#includestm32f10x.h// Device headerexternuint16_tnum;voidTimer_Init(void){// 使能时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);// 选择时钟源选择外部时钟// 由于TIM2的ETR引脚PA0接触不亮所以使用TIM_TIxExternalClockConfig替代// TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x10);TIM_TIxExternalClockConfig(TIM2,TIM_TIxExternalCLK1Source_TI2,TIM_ICPolarity_Rising,0x0F);// 配置时基单元主要是计数模式PSC和ARR的值CNT未设置可通过其他函数设置CNT起始值TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivisionTIM_CKD_DIV1;// 决定输入滤波等模块用多快的采样时钟影响不大TIM_TimeBaseInitStructure.TIM_CounterModeTIM_CounterMode_Up;// 向上计数TIM_TimeBaseInitStructure.TIM_Period10-1;// 设置ARRTIM_TimeBaseInitStructure.TIM_Prescaler1-1;// 设置PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter0;// 高级定时器才有的功能TIM_TimeBaseInit(TIM2,TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2,TIM_FLAG_Update);// 使能更新中断连接更新中断到NVICTIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn;// 选择中断通道NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority1;// 子优先级响应优先级NVIC_Init(NVIC_InitStructure);// 使能定时器运行控制TIM_Cmd(TIM2,ENABLE);}uint16_tTimer_GetNum(void){returnTIM_GetCounter(TIM2);}// 重写中断函数voidTIM2_IRQHandler(void){// 检查标志位if(TIM_GetITStatus(TIM2,TIM_IT_Update)SET){num;// 清除标志位TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}// Timer.h#ifndef__TIMER_H#define__TIMER_HvoidTimer_Init(void);uint16_tTimer_GetNum(void);#endif对射式红外传感器驱动代码#includestm32f10x.h// Device headervoidInfraredSensor_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU;GPIO_InitStructure.GPIO_PinGPIO_Pin_1;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);}#ifndef__InfraredSensor_H#define__InfraredSensor_HvoidInfraredSensor_Init(void);uint16_tInfraredSensor_GetNum(void);#endifmain.c#includestm32f10x.h// Device header#includeOLED_Software.h#includeTimer.h#includeInfraredSensor.huint16_tnum;intmain(void){OLED_Init();Timer_Init();InfraredSensor_Init();OLED_ShowString(1,1,Num:);OLED_ShowString(2,1,Couter:);while(1){OLED_ShowNum(1,5,num,3);OLED_ShowNum(2,8,Timer_GetNum(),3);}}IO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);}#ifndef __InfraredSensor_H#define __InfraredSensor_Hvoid InfraredSensor_Init(void);uint16_t InfraredSensor_GetNum(void);#endifmain.c c #include stm32f10x.h // Device header #include OLED_Software.h #include Timer.h #include InfraredSensor.h uint16_t num; int main(void) { OLED_Init(); Timer_Init(); InfraredSensor_Init(); OLED_ShowString(1, 1, Num:); OLED_ShowString(2, 1, Couter:); while(1) { OLED_ShowNum(1, 5, num, 3); OLED_ShowNum(2, 8, Timer_GetNum(), 3); } }