STM32定时器详解:定时器中断和PWM输出原理

STM32定时器使用

  • STM32定时器
  • 计数模式
  • 定时器工作原理
  • 基本定时器:TIM6、TIM7
  • 示例代码
  • 代码讲解
  • 通用定时器:TIM2、TIM3、TIM4、TIM5
  • 高级定时器:TIM1、TIM8
  • 定时器PWM输出原理
  • 通用定时器3控制通道1输出PWM脉冲
  • 代码
  • 总结
  • 参考资料
  • STM32定时器

    STM32F103系列芯片拥有多种定时器,包括基本定时器、通用定时器和高级定时器,每种定时器都具有一些特定的功能。

    计数模式

    向上计数:计数器从0计数到自动重装载值(ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
    向下计数:计数器从自动重装载值(ARR)开始向下计数到0,然后重新从自动重装载值(ARR)开始向下计数,并且产生一个计数器溢出事件。
    向上向下双向计数(中央对齐模式):计数器从0开始计数到自动重装载值-1,产生一个计数器溢出事件,然后计数器从ARR开始向下计数,计数到1再次产生一个计数器溢出事件,以此往复。

    定时器工作原理

    STM32F103系统构架知:TIM1、8在APB2上,TIM2、3、4、5、6、7在APB1上,APB1操作速度限于36MHz,APB2操作于全速72MHz。

    基本定时器:TIM6、TIM7

    TIM6、TIM7定时器主要功能:16位自动重装载累加计算器,16位可编程预分频器(1~65536),触发DAC的同步电路、产生DMA中断请求。

    基本定时器框图
    时钟源: STM32F103基本定时器的时钟源,可以使用内部时钟源或外部时钟源。当使用内部时钟源(CN_INT)时,计时器的时钟频率由STM32内部时钟提供。当使用外部时钟源时,计时器的时钟频率由外部时钟信号提供,可以使用外部晶体振荡器、RC振荡器等外部时钟源。

    预分频器:PSC预分频寄存器,用于设定定时器时钟的分频系数。

    CNT计数器:用于记录定时器的当前值,从0开始计数,当CNT到达ARR时计数器重新从0开始计数(向上计数模式)。

    自动重装载寄存器:ARR用于设定计时器的计时时长,当CNT到达该值时自动重新计数。

    如何定时呢?
    首先确定TIM6\TIM7时钟源(内部时钟CK_INT)经APB1预分频后分频提供,如果APB1预分频系数是1则频率不变,在库函数中APB1预分频的系数是2,即PCLK1=36MHz,所以定时器的时钟频率TIMxCLK为72Mhz(36*2)

    计数器计数的计算方式:

    CK_CNT = TIMxCLK/(PSC+1)     //CK_CNT最大65536
    

    PSC是预分频器值

    定时时间计算(计数器的中断周期*中断的次数):

    Tout = ((ARR+1)*(PSC+1))/TIMxCLK
    

    假设需要计时1s产生一次中断(已知TIMxCLk为72MHz):
    先设ARR值为9999(ARR+1就是10000)
    1 = (10000(PSC+1))/72000000*
    因此PSC=7199

    所以CK_CNT计算有什么用?

    假设PSC是7199 所以CN_CNK=1000(hz)的时钟  计数一次花费时间1/1000 = 0.0001s = 0.1ms
    所以Tout = (ARR+1)*(0.0001) 已知定时1s = Tout  所以ARR = 9999
    

    示例代码

    使用定时器TIM6中断方法控制LED灯闪烁,设置定时器1s中断一次,中断服务函数中控制LED实现LED闪烁功能。

    定时器头文件定义

    #ifndef __TIME_H
    #define __TIME_H
    
    #include "stm32f10x.h"
    
    #define TIM         TIM4    
    #define TIM_CLK     RCC_APB1Periph_TIM4     //RCC时钟
    #define TIM_ARR     10000-1                 //定义ARR值
    #define TIM_PSC     7200-1                  //定义PSC值
    #define TIM_IRQ     TIM4_IRQn               //定义中断编号
    #define TIM_IRQHandler TIM4_IRQHandler      //重定义中断函数
    
    void TIM_Init_Config(void);     
    
    #endif /* __TIME_H */
    

    定时器源文件

    #include "bsp_time.h"
    
    //中断优先级配置
    void TIM_NVIC_Config(void){
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);             //设置优先级分组
        NVIC_InitStructure.NVIC_IRQChannel = TIM_IRQ;               //指定需要配置的中断信号
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   //先占优先级 1
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;          //从优先级   1
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             //使能NVIC
        NVIC_Init(&NVIC_InitStructure);                             //初始化NVIC寄存器
    }
    
    
    //定时器配置
    void TIM_Init_Config(void){
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
        RCC_APB1PeriphClockCmd(TIM_CLK, ENABLE);                    //开启TIM4定时器所对应的APB1总线时钟
        TIM_TimeBaseInitStruct.TIM_Prescaler = TIM_PSC;             //设置TIM4的预分频系数
        TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//计数模式设置为递增模式
        TIM_TimeBaseInitStruct.TIM_Period = TIM_ARR;                //设置TIM4自动重载寄存器的值,用于设定定时器的定时时长
        TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;    //设置TIM4的时钟分割比,即分割细度
    //    TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
        TIM_TimeBaseInit(TIM, &TIM_TimeBaseInitStruct);
        
        TIM_ITConfig(TIM, TIM_IT_Update, ENABLE);//使能TIM4的溢出中断,将TIM_IT_Update设置为ENABLE即可。这一步非常重要,否则定时器无法产生中断信号,不会触发相应的中断服务程序。
        
        TIM_NVIC_Config();
        
        TIM_Cmd(TIM, ENABLE);   //使能定时器 -- 开启定时器
    }
    

    中断函数

    //"stm32f10x_it.c"源文件
    void TIM_IRQHandler(void){
        if(TIM_GetITStatus(TIM, TIM_IT_Update)!= RESET){
            LED_TOGGLE;
            TIM_ClearITPendingBit(TIM, TIM_IT_Update);
        }
    }
    

    代码讲解

    定时器结构体成员

    /**
      * @brief  TIM Time Base Init structure definition
      * @note   This structure is used with all TIMx except for TIM6 and TIM7.    
      */
    
    
    typedef struct
    {
      uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                           This parameter can be a number between 0x0000 and 0xFFFF */
    
    
      uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                           This parameter can be a value of @ref TIM_Counter_Mode */
    
    
      uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                           Auto-Reload Register at the next update event.
                                           This parameter must be a number between 0x0000 and 0xFFFF.  */
    
    
      uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                          This parameter can be a value of @ref TIM_Clock_Division_CKD */
    
    
      uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                           reaches zero, an update event is generated and counting restarts
                                           from the RCR value (N).
                                           This means in PWM mode that (N+1) corresponds to:
                                              - the number of PWM periods in edge-aligned mode
                                              - the number of half PWM period in center-aligned mode
                                           This parameter must be a number between 0x00 and 0xFF.
                                           @note This parameter is valid only for TIM1 and TIM8. */
    } TIM_TimeBaseInitTypeDef;
    

    TIM_Prescaler:指分配器的预分频值,一般为一定的预定值,大小范围为0到65536
    TIM_CounterMode:指定时器的计数模式,一般有递增和递减模式

    #define TIM_CounterMode_Up                 ((uint16_t)0x0000)
    #define TIM_CounterMode_Down               ((uint16_t)0x0010)
    #define TIM_CounterMode_CenterAligned1     ((uint16_t)0x0020)
    #define TIM_CounterMode_CenterAligned2     ((uint16_t)0x0040)
    #define TIM_CounterMode_CenterAligned3     ((uint16_t)0x0060)
    

    TIM_Period:定时器的自动重装载值,大小范围为0到65536
    TIM_ClockDivision:TIM的时钟分割比,即分割细度
    TIM_RepetitionCounter: 指定TIM1和TIM8定时器的重复计数器值。当重复计数器的值减少到0时,将会产生一个溢出事件,并将TIMx的计数器重新从重复计数器的值开始计数。

    定时器初始化函数

    void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)    //x从1到17
    

    TIMx:定时器选择,可从1到17

    使能/失能中断溢出函数

    void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)    //x从1到17
    

    开启/关闭定时器函数

    void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)    //x从1到17
    

    定时器中断标志位获取和清除

    FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);        //获取定时器中断标志状态,检查x定时器是否发生中断事件
    
                                                                               //TIM_FLAG是需要检测的中断标志
    void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);                  //清除定时器中断标志,将x定时器指定的中断标志清零
    
    ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);              //获取定时器中断状态,检查x定时器是否产生中断信号
                                                                               //TIM_IT用于检测需要的中断源
    
    void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);            //清除定时器的中断状态
    

    定时器中断标志状态函数一般先于定时器中断状态函数使用,并且在启用中断之前使用它们以确保不会丢失年中断信号;定时器中断状态函数则用于在ISR(中断服务程序)中检查中断状态。

    通用定时器:TIM2、TIM3、TIM4、TIM5

    定时器时钟来源:

  • 内部定时器(CK_INT)
  • 外部时钟模式1:外部输入脚(TIx)
  • 外部时钟模式2:外部触发输入(ETR)
  • 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器作为另一个定时器的预分频器。
  • 编程步骤:

  • 配置系统时钟
  • 配置NVIC
  • 配置定时器
  • 配置定时器中断服务函数
  • 高级定时器:TIM1、TIM8

    待补写

    定时器PWM输出原理

    PWM输出就是对外输出脉宽(占空比)可调的方波信号,信号频率由自动重装载寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。

    通用定时器3控制通道1输出PWM脉冲


    从上图通用定时器框图可以看出一个定时器只有一个计数器,所以同一个定时器输出的PWM频率是一样的,可以改变的是占空比大小(可以通过改变比较寄存器改变占空比大小)。

    查看定时器TIM3复用的引脚图,如下所示

    代码

    PWM头文件定义

    #ifndef __PWM_H
    #define __PWM_H
    #include "stm32f10x.h"
    
    #define PWM_TIM     TIM3
    #define PWM_TIM_CLK RCC_APB1Periph_TIM3
    
    #define PWM_TIM_GPIP_PORT   GPIOA
    #define PWM_TIM_GPIO_CLK    RCC_APB2Periph_GPIOA
    #define PWM_TIM_GPIO_Pin    GPIO_Pin_6
    
    void PWM_TIM_Mode_Init(uint16_t freq, uint16_t duty);
    
    #endif /* __PWM_H */
    

    PWM源文件

    #include "bsp_pwm.h"
    
    /**
    * @brief 初始化PWM的GPIO为复用模式
    *
    * @retval None
    */
    void PWM_TIM_GPIO_Init(void){
        GPIO_InitTypeDef GPIO_InitStruct;
        RCC_APB2PeriphClockCmd(PWM_TIM_GPIO_CLK, ENABLE);   //开启GPIOA时钟
        GPIO_InitStruct.GPIO_Pin = PWM_TIM_GPIO_Pin;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;        //设置引脚为复用推挽输出模式
        GPIO_Init(PWM_TIM_GPIP_PORT, &GPIO_InitStruct);     //初始化函数
    }
    
    /**
    * @brief 初始化TIM为PWM输出模式
    *
    * @param freq PWM的频率(kHz)
    * @param duty PWM的占空比(0~100)
    * @retval None
    */
    void PWM_TIM_Mode_Init(uint16_t freq, uint16_t duty){
        uint16_t arr = 36000/freq;      //freq是频率,单位是khz   //将频率
        PWM_TIM_GPIO_Init();
        //配置定时器
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
        TIM_OCInitTypeDef TIM_OCInitStruct;
        //开启定时器时钟
        RCC_APB1PeriphClockCmd(PWM_TIM_CLK, ENABLE);
        
        TIM_TimeBaseInitStruct.TIM_Period = arr-1;
        TIM_TimeBaseInitStruct.TIM_Prescaler =1; //时钟预分频数  设置频率f = 36mhz  因为 t = TIMx_CLK/(PSC+1) --> t = 1/(36Mhz) --> f = 1/t  --> f = 1/t = 36mhz
        TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;   //采样分频
        TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;    //计数模式
        TIM_TimeBaseInit(PWM_TIM, &TIM_TimeBaseInitStruct);
        
        TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;      //设置为PWM1模式
        TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;  //输出使能
        TIM_OCInitStruct.TIM_Pulse = (arr+1)*duty/100-1;    //设置占空比
        TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;      //输出极性
        //初始化PWM_TIM输出比较通道1
        TIM_OC1Init(PWM_TIM, &TIM_OCInitStruct);
        //使能OC1输出比较多预装载以及TIMx的配置寄存器
        TIM_OC1PreloadConfig(PWM_TIM, TIM_OCPreload_Enable);
        //开启定时器
        TIM_Cmd(PWM_TIM,ENABLE);
    }
    

    主程序

    PWM_TIM_Mode_Init(20, 80);    //初始化时调用一次即可
    

    TIM_OCInitTypeDef结构体讲解

    typedef struct
    {
      uint16_t TIM_OCMode;        /*!< 指定TIM输出比较的模式 */
    
    
      uint16_t TIM_OutputState;   /*!< 指定TIM输出比较通道的输出状态(开启或关闭) */
    
    
      uint16_t TIM_OutputNState;  /*!< 指定TIM互补输出比较通道的输出状态(开启或者关闭),只对定时器TIM1、TIM8有效 */
    
    
      uint16_t TIM_Pulse;         /*!< 指定TIM输出比较通道的占空比 */
    
    
      uint16_t TIM_OCPolarity;    /*!< 指定TIM输出比较通道的极性 */
    
    
      uint16_t TIM_OCNPolarity;   /*!< 指定TIM互补输出比较通道的极性 */
    
    
      uint16_t TIM_OCIdleState;   /*!< 指定TIM输出比较通道在闲置或者空档时的输出电平状态 */
    
    
      uint16_t TIM_OCNIdleState;  /*!< 指定TIM互补输出比较通道在空档状态下输出的电平状态 */
    } TIM_OCInitTypeDef;
    

    ARR决定PWM周期
    CCRx决定PWM占空比大小
    ARR计算:

    由Tout = ((ARR+1)*(PSC+1))/TIMxCLK           --   已知TIMxCLK = 72Mhz且PSC设置为1
    因此T=(arr+1)/36            -- 已知 t = 1/f
    所以1/f = (arr+1)/36  
    得 arr = 36000/f
    

    总结

    要多用,要多用!

    参考资料

    https://www.bilibili.com/video/BV1b54y1m71K/?spm_id_from=333.337.search-card.all.click&vd_source=101ae2594bcc6dbee11cc6670869bc2e
    
    https://blog.csdn.net/qq_44016222/article/details/123507270?spm=1001.2100.3001.7377&utm_medium=distribute.pc_feed_blog_category.none-task-blog-classify_tag-20-123507270-null-null.nonecase&depth_1-utm_source=distribute.pc_feed_blog_category.none-task-blog-classify_tag-20-123507270-null-null.nonecase
    
    https://www.guyuehome.com/39703#%E4%B8%89%E3%80%81%E5%AE%9A%E6%97%B6%E5%99%A8%E8%BE%93%E5%87%BAPWM
    
    https://blog.csdn.net/qq_38410730/article/details/79996222
    

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32定时器详解:定时器中断和PWM输出原理

    发表评论