• 【STM32H7教程】第32章 STM32H7的TIM定时器基础知识和HAL库API


    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

    第32章       STM32H7的TIM定时器基础知识和HAL库API

    本章节为大家讲解TIM1 – TIM17(STM32H7没有TIM9,TIM10和TIM11)共计14个定时器的基础知识和对应的HAL库API。

    32.1 初学者重要提示

    32.2 定时器基础知识

    32.3 定时器的HAL库用户

    32.4 源文件stm32h7xx_hal_tim.c

    32.5 总结

    32.1 初学者重要提示

    1.   学习定时器外设推荐从硬件框图开始了解基本的功能特性,然后逐步深入了解各种特性,这种方式方便记忆和以后查阅。
    2.   STM32H7的定时器输出100MHz方波是完全没问题。http://www.armbbs.cn/forum.php?mod=viewthread&tid=86434
    3.   STM32H7定时器进出中断的速度能跑到12.5MHz,所有程序在TCM和Flash运行没差别。http://www.armbbs.cn/forum.php?mod=viewthread&tid=90836
    4.   STM32H7的定时器输入捕获可以实现12MHz方波的双边沿捕获,单边沿可以做到24MHz。http://www.armbbs.cn/forum.php?mod=viewthread&tid=91283
    5.   特别注意STM32H7的TIM1,8,15,16,17才有RCR重复计数器,其它都没用的。
    6.   STM32H7的单个定时器中不同通道可以配置不同频率PWM。http://www.armbbs.cn/forum.php?mod=viewthread&tid=89008
    7.   STM32H7的TIM1-TIM17中断入口函数名使用时要注意,别搞错了:
    TIM1_BRK_IRQHandler             
    TIM1_UP_IRQHandler              
    TIM1_TRG_COM_IRQHandler        
    TIM1_CC_IRQHandler                                                    
    TIM2_IRQHandler                                           
    TIM3_IRQHandler                                                 
    TIM4_IRQHandler                 
    TIM5_IRQHandler            
    TIM6_DAC_IRQHandler           <------------------要注意            
    TIM7_IRQHandler 
    TIM8_BRK_TIM12_IRQHandler      <------------------要注意,定时器12也是用的这个
    TIM8_UP_TIM13_IRQHandler       <------------------要注意,定时器13也是用的这个
    TIM8_TRG_COM_TIM14_IRQHandler  <------------------要注意,定时器14也是用的这个
    TIM8_CC_IRQHandler          
    TIM15_IRQHandler 
    TIM16_IRQHandler 
    TIM17_IRQHandler

    32.2 定时器基础知识

    注,不同定时支持的功能略有区别,基础定时器功能较少,TIM1和TIM8高级定时器功能多些。

    •   TIM2和TIM5是32位定时器,其它定时器都是16位定时器。16位和32位的区别是CNT计数器范围不同,32位的范围是0 到2^32 – 1,而16位的是0到65535;它们支持的分频是范围是一样的,都是1到65535。
    •   计数器支持递增、递减和递增/递减二合一。
    •   多个独立通道,可用于:

    – 输入捕获。

    – 输出比较。

    – PWM 生成(边沿和中心对齐模式)。

    – 单脉冲模式输出。

    •   发生如下事件时生成中断/DMA 请求:

    – 更新:计数器上溢/下溢、计数器初始化(通过软件或内部/外部触发)

    – 触发事件(计数器启动、停止、初始化或通过内部/外部触发计数)

    – 输入捕获

    – 输出比较

    •   支持增量式编码器和霍尔传感器。

    32.2.1 定时器TIM1-TIM17的区别

    STM32H7支持的定时器有点多,要简单的区分下。STM32H7支持TIM1-TIM8,TIM12-TIM17共14个定时器,而中间的TIM9,TIM10,TIM11是不存在的,这点要注意。

    粗略的比较如下:

     

     

    通过上面的表格,至少要了解到以下两点:

    1.   STM32H7的定时器主要分为高级定时器,通用定时器,基础定时器和低功耗定时器。
    2.   TIM2和TIM5是32位定时器,其它都是16位定时器。

    32.2.2 定时器的硬件框图

    认识一个外设,最好的方式就是看他的框图,方便我们快速的了解定时器的基本功能,然后再看手册了解细节。

    下面我们直接看最复杂的高级定时器TIM1&TIM8框图:

     

    通过这个框图,我们可以得到如下信息:

    •   TIMx_ETR接口

    外部触发输入接口。ETR支持多种输入源:输入引脚(默认配置)、比较器输出和模拟看门狗。

     

    •   截图左侧的TIMx_CH1,TIMx_CH2,TIMx_CH3和TIMx_CH4接口

    这四个通道主要用于输入捕获,可以计算波形频率和脉宽。                                                                                                                             

    •   TIMx_BKIN和TIMx_BKIN2接口

    断路功能,主要用于保护由 TIM1 和 TIM8 定时器产生的 PWM 信号所驱动的功率开关

    •   TRGO内部输出通道

    主要用于定时器级联,ADC和DAC的定时器触发。

    •   6组输出比较单元OC1到OC6

    OC1到OC4有对应的输出引脚,而OC5和OC6没有对应的输出引脚,主要用于内部控制。

    •   截图右侧的输出比较通道TIMx_CH1,TIMx_CH1N,TIMx_CH2,TIMx_CH2N,TIMx_CH3,TIMx_CH3N和TIMx_CH4

    主要用于PWM输出,注意CH1到CH3有互补输出,而CH4没有互补输出。

    •   其它框图里面未展示出来功能

    定时器TIM1&TIM8还支持的其它功能在用到的时候再做说明。

    32.2.3 定时器的时基单元

    定时器要工作就需要一个基本时基单元,而基本的时基单元是由下面几个寄存器组成的:

    •   预分频器寄存器 (TIMx_PSC)

    用于设置定时器的分频,比如定时器的主频是200MHz,通过此寄存器可以将其设置为100MHz,50MHz,25MHz等分频值。

    注:预分频器有个缓冲功能,可以让用户实时更改,新的预分频值将在下一个更新事件发生时被采用(以递增计数模式为例,就是CNT计数值达到ARR自动重装寄存器的数值时会产生更新事件)。

    •   计数器寄存器 (TIMx_CNT)

    计数器是最基本的计数单元,计数值是建立在分频的基础上面,比如通过TIMx_PSC设置分频后的频率为100MHz,那么计数寄存器计一次数就是10ns。

    •   自动重载寄存器 (TIMx_ARR)

    自动重装寄存器是CNT计数寄存器能达到的最大计数值,以递增计数模式为例,就是CNT计数器达到ARR寄存器数值时,重新从0开始计数。

    注,自动重载寄存器是预装载的。对自动重载寄存器执行写入或读取操作时会访问预装载寄存器。预装载寄存器的内容既可以立即传送到影子寄存器(让设置立即起到效果的寄存器),也可以在每次发生更新事件时传送到影子寄存器。简单的说就是让ARR寄存器的数值立即更新还是更新事件发送的时候更新。

    •   重复计数器寄存器 (TIMx_RCR)

    以递增计数模式为例,当CNT计数器数值达到ARR自动重载数值时,重复计数器的数值加1,重复次数达到TIMx_RCR+ 1后就,将生成更新事件。

    注,只有TIM1,TIM8,TIM15,TIM16,TIM17有此寄存器。

    比如我们要配置定时器实现周期性的中断,主要使用这几个寄存器即可。

    32.2.4 定时器输出比较(PWM)

    使用定时器时基单元的那几个寄存器仅仅能设置周期,还不能设置占空比。针对这个问题,还需要比较捕获寄存CCR的参与,这样就可以设置占空比了。

    为了方便大家理解,以PWM 边沿对齐模式,递增计数配置为例:

    •   当计数器TIMx_CNT < 比较捕获寄存器TIMx_CCRx期间,PWM参考信号OCxREF输出高电平。
    •   当计数器TIMx_CNT >= 比较捕获寄存器TIMx_CCRx期间, PWM参考信号OCxREF输出低电平。
    •   当比较捕获寄存器TIMx_CCRx > 自动重载寄存器TIMx_ARR,OCxREF保持为1。
    •   当比较捕获寄存器TIMx_CCRx = 0,则OCxRef保持为0。

    下面是TIMx_ARR=8的波形效果:

     

    32.2.5 定时器输入捕获

    与PWM一样,使用定时器实现输入捕获,仅靠时基单元的那几个寄存器是不行的,我们需要一个寄存器来记录发生捕获时的具体时间,这个寄存器依然由比较捕获寄存器TIMx_CCRx来实现。

    比如我们要测量一路方波的周期:

    •   配置定时器为输入捕获模式,上升沿触发,设置分频,自动重装等寄存器,比如设置的CNT计数器计数1次是1微秒。
    •   当有上升沿触发的时候,TIMx_CCRx寄存器就会自动记录当前的CNT数值,然后用户就可以通过CC中断,在中断复位程序里面保存当前的TIMx_CCRx寄存器数值。等下次再检测到上升沿触发,两次时间求差就可以得到方波的周期。

    不过这里要特别注意一点,如果CNT发生溢出(比如16位定时器,计数到65535就溢出了)就需要特别处理下,将CNT计数溢出考虑进来。

    32.3 定时器的HAL库用法

    定时器的HAL库用法其实就是几个结构体变量成员的配置和使用,然后配置GPIO、时钟,并根据需要配置NVIC、中断和DMA。下面我们逐一展开为大家做个说明。

    32.3.1 定时器寄存器结构体TIM_TypeDef

    定时器相关的寄存器是通过HAL库中的结构体TIM_TypeDef定义的,在stm32h743xx.h中可以找到这个类型定义:

    typedef struct
    {
      __IO uint16_t CR1;         /*!< TIM control register 1,                   Address offset: 0x00 */
      uint16_t      RESERVED0;   /*!< Reserved, 0x02                                                 */
      __IO uint32_t CR2;         /*!< TIM control register 2,                   Address offset: 0x04 */
      __IO uint32_t SMCR;        /*!< TIM slave mode control register,          Address offset: 0x08 */
      __IO uint32_t DIER;        /*!< TIM DMA/interrupt enable register,        Address offset: 0x0C */
      __IO uint32_t SR;          /*!< TIM status register,                      Address offset: 0x10 */
      __IO uint32_t EGR;         /*!< TIM event generation register,            Address offset: 0x14 */
      __IO uint32_t CCMR1;       /*!< TIM capture/compare mode register 1,      Address offset: 0x18 */
      __IO uint32_t CCMR2;       /*!< TIM capture/compare mode register 2,      Address offset: 0x1C */
      __IO uint32_t CCER;        /*!< TIM capture/compare enable register,      Address offset: 0x20 */
      __IO uint32_t CNT;         /*!< TIM counter register,                     Address offset: 0x24 */
      __IO uint16_t PSC;         /*!< TIM prescaler,                            Address offset: 0x28 */
      uint16_t      RESERVED9;   /*!< Reserved, 0x2A                                                 */
      __IO uint32_t ARR;         /*!< TIM auto-reload register,                 Address offset: 0x2C */
      __IO uint16_t RCR;         /*!< TIM repetition counter register,          Address offset: 0x30 */
      uint16_t      RESERVED10;  /*!< Reserved, 0x32                                                 */
      __IO uint32_t CCR1;        /*!< TIM capture/compare register 1,           Address offset: 0x34 */
      __IO uint32_t CCR2;        /*!< TIM capture/compare register 2,           Address offset: 0x38 */
      __IO uint32_t CCR3;        /*!< TIM capture/compare register 3,           Address offset: 0x3C */
      __IO uint32_t CCR4;        /*!< TIM capture/compare register 4,           Address offset: 0x40 */
      __IO uint32_t BDTR;        /*!< TIM break and dead-time register,         Address offset: 0x44 */
      __IO uint16_t DCR;         /*!< TIM DMA control register,                 Address offset: 0x48 */
      uint16_t      RESERVED12;  /*!< Reserved, 0x4A                                                 */
      __IO uint16_t DMAR;        /*!< TIM DMA address for full transfer,        Address offset: 0x4C */
      uint16_t      RESERVED13;  /*!< Reserved, 0x4E                                                 */
      uint16_t      RESERVED14;  /*!< Reserved, 0x50                                                 */
      __IO uint32_t CCMR3;       /*!< TIM capture/compare mode register 3,      Address offset: 0x54 */
      __IO uint32_t CCR5;        /*!< TIM capture/compare register5,            Address offset: 0x58 */
      __IO uint32_t CCR6;        /*!< TIM capture/compare register6,            Address offset: 0x5C */
      __IO uint32_t AF1;         /*!< TIM alternate function option register 1, Address offset: 0x60 */
      __IO uint32_t AF2;         /*!< TIM alternate function option register 2, Address offset: 0x64 */
      __IO uint32_t TISEL;       /*!< TIM Input Selection register,             Address offset: 0x68 */
    } TIM_TypeDef;

    这个结构体的成员名称和排列次序和CPU的定时器寄存器是一 一对应的。

    __IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m7.h 文件定义了这个宏:

    #define     __O     volatile             /*!< Defines 'write only' permissions */
    #define     __IO    volatile             /*!< Defines 'read / write' permissions */

    下面我们看下定时器的定义,在stm32h743xx.h文件。

    #define PERIPH_BASE         ((uint32_t)0x40000000)
    #define D2_APB1PERIPH_BASE   PERIPH_BASE
    #define D2_APB2PERIPH_BASE   (PERIPH_BASE + 0x00010000)
    
    /*!< D2_APB1PERIPH 外设 */
    #define TIM2_BASE             (D2_APB1PERIPH_BASE + 0x0000) <----- 展开这个宏,(TIM_TypeDef *) 0x40000000
    #define TIM3_BASE             (D2_APB1PERIPH_BASE + 0x0400)
    #define TIM4_BASE             (D2_APB1PERIPH_BASE + 0x0800)
    #define TIM5_BASE             (D2_APB1PERIPH_BASE + 0x0C00)
    #define TIM6_BASE             (D2_APB1PERIPH_BASE + 0x1000)
    #define TIM7_BASE             (D2_APB1PERIPH_BASE + 0x1400)
    #define TIM12_BASE            (D2_APB1PERIPH_BASE + 0x1800)
    #define TIM13_BASE            (D2_APB1PERIPH_BASE + 0x1C00)
    #define TIM14_BASE            (D2_APB1PERIPH_BASE + 0x2000)
    
    /*!< D2_APB1PERIPH 外设 */
    #define TIM1_BASE             (D2_APB2PERIPH_BASE + 0x0000)
    #define TIM8_BASE             (D2_APB2PERIPH_BASE + 0x0400)
    #define TIM15_BASE            (D2_APB2PERIPH_BASE + 0x4000)
    #define TIM16_BASE            (D2_APB2PERIPH_BASE + 0x4400)
    #define TIM17_BASE            (D2_APB2PERIPH_BASE + 0x4800)
    
    #define TIM1                ((TIM_TypeDef *) TIM1_BASE)
    #define TIM2                ((TIM_TypeDef *) TIM2_BASE)
    #define TIM3                ((TIM_TypeDef *) TIM3_BASE)
    #define TIM4                ((TIM_TypeDef *) TIM4_BASE)
    #define TIM5                ((TIM_TypeDef *) TIM5_BASE)
    #define TIM6                ((TIM_TypeDef *) TIM6_BASE)
    #define TIM7                ((TIM_TypeDef *) TIM7_BASE)
    #define TIM8                ((TIM_TypeDef *) TIM8_BASE)
    #define TIM12               ((TIM_TypeDef *) TIM12_BASE)
    #define TIM13               ((TIM_TypeDef *) TIM13_BASE)
    #define TIM14               ((TIM_TypeDef *) TIM14_BASE)
    #define TIM15               ((TIM_TypeDef *) TIM15_BASE)
    #define TIM16               ((TIM_TypeDef *) TIM16_BASE)
    #define TIM17               ((TIM_TypeDef *) TIM17_BASE)

    我们访问TIM2的CR1寄存器可以采用这种形式:TIM2->CR1 = 0;

    32.3.2 定时器句柄结构体TIM_HandleTypeDef

    HAL库在TIM_TypeDef的基础上封装了一个结构体TIM_HandleTypeDef,定义如下:

    typedef struct
    {
      TIM_TypeDef              *Instance;     /*!< Register base address             */ 
      TIM_Base_InitTypeDef     Init;          /*!< TIM Time Base required parameters */
      HAL_TIM_ActiveChannel    Channel;       /*!< Active channel                    */ 
    
    /*!< DMA Handlers array This array is accessed by a @ref DMA_Handle_index */
      DMA_HandleTypeDef        *hdma[7];      
      HAL_LockTypeDef          Lock;          /*!< Locking object                    */
      __IO HAL_TIM_StateTypeDef   State;      /*!< TIM operation state               */  
    }TIM_HandleTypeDef;

    这里重点介绍前四个参数,其它参数主要是HAL库内部使用的。

      TIM_TypeDef  *Instance

    这个参数是寄存器的例化,方便操作寄存器,比如使能定时器的计数器。

    SET_BIT(huart->Instance->CR1,  TIM_CR1_CEN)。

      TIM_Base_InitTypeDef  Init

    这个参数是用户接触最多的,用于配置定时器的基本参数。

    TIM_Base_InitTypeDef结构体的定义如下:

    typedef struct
    {
      uint32_t Prescaler;      
      uint32_t CounterMode;    
      uint32_t Period;         
      uint32_t ClockDivision;    
      uint32_t RepetitionCounter; 
      uint32_t AutoReloadPreload;  
    } TIM_Base_InitTypeDef;
    •   成员Prescaler

    用于设置定时器分频,对于32位的TIM2和TIM5范围是0到0xFFFFFFFF,其它定时器是0到0xFFFF。

    •   成员CounterMode

    用于设置计数模式,向上计数模式、向下计数模式和中心对齐模式。

    #define TIM_COUNTERMODE_UP                ((uint32_t)0x0000U)   /*!< Up counting mode */
    #define TIM_COUNTERMODE_DOWN               TIM_CR1_DIR          /*!< Down counting mode */
    #define TIM_COUNTERMODE_CENTERALIGNED1     TIM_CR1_CMS_0        /*!< Center-aligned counting mode 1 */
    #define TIM_COUNTERMODE_CENTERALIGNED2     TIM_CR1_CMS_1        /*!< Center-aligned counting mode 2 */
    #define TIM_COUNTERMODE_CENTERALIGNED3     TIM_CR1_CMS          /*!< Center-aligned counting mode 3 */
    •   成员Period

    用于设置定时器周期,对于32位的TIM2和TIM5范围是0到0xFFFFFFFF,其它定时器是0到0xFFFF。

    •   成员ClockDivision

    用于指示定时器时钟 (CK_INT) 频率与死区发生器以及数字滤波器(ETR、TIx)所使用的死区及采样时钟 (tDTS) 之间的分频比。

    #define TIM_CLOCKDIVISION_DIV1       ((uint32_t)0x0000U)        /*!< Clock Division DIV1 */
    #define TIM_CLOCKDIVISION_DIV2       (TIM_CR1_CKD_0)            /*!< Clock Division DIV2 */
    #define TIM_CLOCKDIVISION_DIV4       (TIM_CR1_CKD_1)             /*!< Clock Division DIV4 */
    •   成员RepetitionCounter

    用于设置重复计数器,仅TIM1和TIM8有,其它定时器没有。作用是每当计数器上溢/下溢时,重复计数器减1,当减到零时,才会生成更新事件,这个在生成PWM时比较有用。

    •   成员AutoReloadPreload

    用于设置定时器的ARR自动重装寄存器是更新事件产生时写入有效还是立即写入有效。如果使能了表示更新事件产生时写入有效,否则反之。

    #define TIM_AUTORELOAD_PRELOAD_DISABLE   ((uint32_t)0x0000U)   /*!< TIMx_ARR register is not buffered */
    #define TIM_AUTORELOAD_PRELOAD_ENABLE    (TIM_CR1_ARPE)        /*!< TIMx_ARR register is buffered */

      HAL_TIM_ActiveChannel    Channel;

    用于设置定时器通道,比如TIM1和TIM8都是6个通道。

    typedef enum
    {
      HAL_TIM_ACTIVE_CHANNEL_1        = 0x01U,    /*!< The active channel is 1     */
      HAL_TIM_ACTIVE_CHANNEL_2        = 0x02U,    /*!< The active channel is 2     */
      HAL_TIM_ACTIVE_CHANNEL_3        = 0x04U,    /*!< The active channel is 3     */   
      HAL_TIM_ACTIVE_CHANNEL_4        = 0x08U,    /*!< The active channel is 4     */
      HAL_TIM_ACTIVE_CHANNEL_5        = 0x10U,    /*!< The active channel is 5     */
      HAL_TIM_ACTIVE_CHANNEL_6        = 0x20U,    /*!< The active channel is 6     */
      HAL_TIM_ACTIVE_CHANNEL_CLEARED  = 0x00U     /*!< All active channels cleared */    
    }HAL_TIM_ActiveChannel;

      DMA_HandleTypeDef        *hdma[7];

    用于关联DMA。

    配置定时器参数,其实就是配置结构体TIM_HandleTypeDef的成员。

    TIM_HandleTypeDef   TimHandle = {0};
    
    /* 
        定时器中断更新周期 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)
    */
    TimHandle.Instance = TIMx;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;    
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    32.3.3 定时器输出比较结构体TIM_OC_InitTypeDef

    此结构体主要用于定时器的输出比较,定义如下:

    typedef struct
    {                                 
      uint32_t OCMode;       
      uint32_t Pulse;       
      uint32_t OCPolarity;    
      uint32_t OCNPolarity;   
      uint32_t OCFastMode;  
      uint32_t OCIdleState;   
      uint32_t OCNIdleState;  
    } TIM_OC_InitTypeDef; 

    下面将这几个参数一 一做个说明。

    •   OCMode

    用于配置输出比较模式,支持的模式较多:

    /*!< TIM Output timing mode */
    #define TIM_OCMODE_TIMING                ((uint32_t)0x0000U)       
    
    /*!< TIM Output Active mode */                          
    #define TIM_OCMODE_ACTIVE                ((uint32_t)TIM_CCMR1_OC1M_0) 
    
     /*!< TIM Output Inactive mode */                       
    #define TIM_OCMODE_INACTIVE              ((uint32_t)TIM_CCMR1_OC1M_1)           
                  
     /*!< TIM Output Toggle mode */               
    #define TIM_OCMODE_TOGGLE                 ((uint32_t)TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0)         
    
     /*!< TIM PWM mode 1 */
    #define TIM_OCMODE_PWM1                   ((uint32_t)TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1) 
    
    /*!< TIM PWM mode 2 */                    
    #define TIM_OCMODE_PWM2                    ((uint32_t)TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0)   
    
     /*!< TIM Forced Active mode */   
    #define TIM_OCMODE_FORCED_ACTIVE           ((uint32_t)TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_0)         
    
     /*!< TIM Forced Inactive mode */             
    #define TIM_OCMODE_FORCED_INACTIVE         ((uint32_t)TIM_CCMR1_OC1M_2)                                        
    
    /*!< TIM Rettrigerrable OPM mode 1 */  
    #define TIM_OCMODE_RETRIGERRABLE_OPM1      ((uint32_t)TIM_CCMR1_OC1M_3)   
    
    /*!< TIM Rettrigerrable OPM mode 2 */                                        
    #define TIM_OCMODE_RETRIGERRABLE_OPM2      ((uint32_t)TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_0)    
    
    /*!< TIM Combined PWM mode 1 */                     
    #define TIM_OCMODE_COMBINED_PWM1           ((uint32_t)TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_2)     
    
    /*!< TIM Combined PWM mode 2 */                  
    #define TIM_OCMODE_COMBINED_PWM2           ((uint32_t)TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_2)   
    
    /*!< TIM Asymetruc PWM mode 1 */  
    #define TIM_OCMODE_ASSYMETRIC_PWM1         ((uint32_t)TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2)  
    
     /*!< TIM Asymetruc PWM mode 2 */    
    #define TIM_OCMODE_ASSYMETRIC_PWM2         ((uint32_t)TIM_CCMR1_OC1M_3 | TIM_CCMR1_OC1M)  
    •   Pulse

    可用于设置占空比,对应定时器的CCR寄存器,32位的TIM2和TIM5范围是0到0xFFFFFFFF。

    •   OCPolarity

    设置输出极性,可选高电平或低电平有效。

    #define TIM_OCPOLARITY_HIGH                ((uint32_t)0x0000U)
    #define TIM_OCPOLARITY_LOW                 (TIM_CCER_CC1P)
    •   OCNPolarity

    互补输出极性设置,可选高电平或者低电平有效。

    #define TIM_OCNPOLARITY_HIGH               ((uint32_t)0x0000U)
    #define TIM_OCNPOLARITY_LOW                (TIM_CCER_CC1NP)
    •   OCFastMode

    快速输出模式使能,仅OCMode配置为PWM1或者PWM2模式时才有意义。

    #define TIM_OCFAST_DISABLE                ((uint32_t)0x0000U)
    #define TIM_OCFAST_ENABLE                 (TIM_CCMR1_OC1FE)
    •   OCIdleState

    空闲状态时,设置输出比较引脚的电平状态。

    #define TIM_OCIDLESTATE_SET                (TIM_CR2_OIS1)
    #define TIM_OCIDLESTATE_RESET              ((uint32_t)0x0000U)
    •   OCNIdleState

    空闲状态时,设置互补输出引脚的电平状态。

    #define TIM_OCNIDLESTATE_SET               (TIM_CR2_OIS1N)
    #define TIM_OCNIDLESTATE_RESET             ((uint32_t)0x0000U)

    32.3.4 定时器输入捕获结构体TIM_IC_InitTypeDef

    此结构体主要用于定时器的输入捕获,定义如下:

    typedef struct
    {                                  
      uint32_t ICPolarity;   
      uint32_t ICSelection; 
      uint32_t ICPrescaler; 
      uint32_t ICFilter;    
    } TIM_IC_InitTypeDef;

    下面将这几个参数一 一做个说明。

    •   ICPolarity

    输入触发极性,可以选择上升沿,下降沿或者双沿触发。

    #define  TIM_ICPOLARITY_RISING             TIM_INPUTCHANNELPOLARITY_RISING
    #define  TIM_ICPOLARITY_FALLING            TIM_INPUTCHANNELPOLARITY_FALLING
    #define  TIM_ICPOLARITY_BOTHEDGE           TIM_INPUTCHANNELPOLARITY_BOTHEDGE
    •   ICSelection

    输入捕获通道选择,可以选择直接输入(即CC1选择TI1,CC2选择TI2等),间接输入(CC1选择TI2,CC3选择TI4等)或者TRC。

    #define TIM_ICSELECTION_DIRECTTI       (TIM_CCMR1_CC1S_0)   
    #define TIM_ICSELECTION_INDIRECTTI     (TIM_CCMR1_CC1S_1)  
    #define TIM_ICSELECTION_TRC            (TIM_CCMR1_CC1S)  
    •   ICPrescaler

    输入捕获分频,表示每捕获1,2,4或8个事件后表示一次捕获。

    #define TIM_ICPSC_DIV1       ((uint32_t)0x0000U)                 
    #define TIM_ICPSC_DIV2       (TIM_CCMR1_IC1PSC_0)    
    #define TIM_ICPSC_DIV4       (TIM_CCMR1_IC1PSC_1)    
    #define TIM_ICPSC_DIV8       (TIM_CCMR1_IC1PSC)    
    •   ICFilter

    输入捕获滤波器,可以定义采样频率和多少个连续事件才视为有效的触发,参数范围0到15。具体定义如下,其中fCK_INT表示定时器时钟,fDTS表示死区时间采样率,N表示这么多个事件代表一次有效边沿。

    0000:无滤波器,按 fDTS 频率进行采样
    0001: fSAMPLING=fCK_INT, N=20010: fSAMPLING=fCK_INT, N=40011: fSAMPLING=fCK_INT, N=80100: fSAMPLING=fDTS/2, N=60101: fSAMPLING=fDTS/2, N=80110: fSAMPLING=fDTS/4, N=60111: fSAMPLING=fDTS/4, N=8
    1000: fSAMPLING=fDTS/8, N=6
    1001: fSAMPLING=fDTS/8, N=8
    1010: fSAMPLING=fDTS/16, N=5
    1011: fSAMPLING=fDTS/16, N=6
    1100: fSAMPLING=fDTS/16, N=8
    1101: fSAMPLING=fDTS/32, N=5
    1110: fSAMPLING=fDTS/32, N=6

    32.3.5 定时器的底层配置(GPIO,时钟,中断等)

    HAL库有个自己的底层初始化回调函数,比如调用函数HAL_TIM_Base_Init就会调用HAL_TIM_Base_MspInit,此函数是弱定义的。

    __weak void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
    {
      /* Prevent unused argument(s) compilation warning */
      UNUSED(htim);
      /* NOTE : This function Should not be modified, when the callback is needed,
                the HAL_TIM_Base_MspDeInit could be implemented in the user file
       */
    }

    用户可以在其它的C文件重定向,并将相对的底层初始化在里面实现。对应的底层复位函数HAL_TIM_Base_DeInit是在函数HAL_TIM_Base_MspDeInit里面被调用的,也是弱定义的。

    当然,用户也可以自己初始化,不限制必须在两个函数里面实现。

    定时器外设的基本参数配置完毕后还不能使用,还需要配置GPIO、时钟、中断等参数,比如下面配置TIM1使用PA8做PWM输出。

    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
      GPIO_InitTypeDef   GPIO_InitStruct;
    
      /* 使能TIM1时钟 */
      __HAL_RCC_TIM1_CLK_ENABLE ();
    
      /* 使能GPIOA时钟 */
      __HAL_RCC_GPIOA_CLK_ENABLE ();
    
      /* 设置TIM1使用PA8做PWM输出引脚,将其配置为输出,推挽,复用模式 */
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
      GPIO_InitStruct.Pull = GPIO_PULLUP;
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    
      GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
      GPIO_InitStruct.Pin = GPIO_PIN_8;
      HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    }

    总结下来就是以下几点:

    •   配置TIM时钟。
    •   配置TIM所用到引脚和对应的GPIO时钟。
    •   如果用到定时器中断,还需要通过NVIC配置中断。
    •   如果用到DMA,还要配置DMA。

    关于这个底层配置有以下几点要着重说明下:

    •   定时器所使用引脚的复用模式选择已经被HAL库定义好,放在了stm32h7xx_hal_gpio_ex.h文件里面。比如TIM1有一个复用,
    #define GPIO_AF1_TIM1      ((uint8_t)0x01)  /* TIM1 Alternate Function mapping */

    但是却有4个输出通道,每个通道都有几个支持的输出引脚:

    TIM1_CH1,  PA8   PE9    PK1
    TIM1_CH2,  PA9   PE11
    TIM1_CH3,  PA10  PE13   PJ9
    TIM1_CH4,  PA11  PE14   PJ11

    具体使用哪个,配置对应引脚的复用即可:

     

    32.3.6 定时器的状态标志清除问题

    下面我们介绍__HAL_TIM_GET_FLAG函数。这个函数用来检查定时器标志位是否被设置。

    /** @brief  Check whether the specified TIM interrupt flag is set or not.
      * @param  __HANDLE__: specifies the TIM Handle.
      * @param  __FLAG__: specifies the TIM interrupt flag to check.
      *        This parameter can be one of the following values:
      *            @arg TIM_FLAG_UPDATE: Update interrupt flag
      *            @arg TIM_FLAG_CC1: Capture/Compare 1 interrupt flag
      *            @arg TIM_FLAG_CC2: Capture/Compare 2 interrupt flag
      *            @arg TIM_FLAG_CC3: Capture/Compare 3 interrupt flag
      *            @arg TIM_FLAG_CC4: Capture/Compare 4 interrupt flag
      *            @arg TIM_FLAG_CC5: Compare 5 interrupt flag
      *            @arg TIM_FLAG_CC6: Compare 6 interrupt flag
      *            @arg TIM_FLAG_COM:  Commutation interrupt flag
      *            @arg TIM_FLAG_TRIGGER: Trigger interrupt flag
      *            @arg TIM_FLAG_BREAK: Break interrupt flag   
      *            @arg TIM_FLAG_BREAK2: Break 2 interrupt flag                     
      *            @arg TIM_FLAG_SYSTEM_BREAK: System Break interrupt flag
      *            @arg TIM_FLAG_CC1OF: Capture/Compare 1 overcapture flag
      *            @arg TIM_FLAG_CC2OF: Capture/Compare 2 overcapture flag
      *            @arg TIM_FLAG_CC3OF: Capture/Compare 3 overcapture flag
      *            @arg TIM_FLAG_CC4OF: Capture/Compare 4 overcapture flag
      * @retval The new state of __FLAG__ (TRUE or FALSE).
      */
    #define __HAL_TIM_GET_FLAG(__HANDLE__, __FLAG__)   (((__HANDLE__)->Instance->SR &(__FLAG__)) == (__FLAG__))

    前5个是比较常用的中断标志。

    •   TIM_FLAG_UPDATE

    定时器更新标准,配置一个周期性的定时器中断要用到。

    •      TIM_FLAG_CC1

    TIM_FLAG_CC2

    TIM_FLAG_CC3

    TIM_FLAG_CC4

    捕获/比较标志,配置了捕获/比较中断要用到。

    与标志获取函数__HAL_TIM_GET_FLAG对应的清除函数是__HAL_TIM_CLEAR_FLAG:

    /** @brief  Clear the specified TIM interrupt flag.
      * @param  __HANDLE__: specifies the TIM Handle.
      * @param  __FLAG__: specifies the TIM interrupt flag to clear.
      *        This parameter can be one of the following values:
      *            @arg TIM_FLAG_UPDATE: Update interrupt flag
      *            @arg TIM_FLAG_CC1: Capture/Compare 1 interrupt flag
      *            @arg TIM_FLAG_CC2: Capture/Compare 2 interrupt flag
      *            @arg TIM_FLAG_CC3: Capture/Compare 3 interrupt flag
      *            @arg TIM_FLAG_CC4: Capture/Compare 4 interrupt flag
      *            @arg TIM_FLAG_CC5: Compare 5 interrupt flag
      *            @arg TIM_FLAG_CC6: Compare 6 interrupt flag
      *            @arg TIM_FLAG_COM:  Commutation interrupt flag
      *            @arg TIM_FLAG_TRIGGER: Trigger interrupt flag
      *            @arg TIM_FLAG_BREAK: Break interrupt flag   
      *            @arg TIM_FLAG_BREAK2: Break 2 interrupt flag                     
      *            @arg TIM_FLAG_SYSTEM_BREAK: System Break interrupt flag
      *            @arg TIM_FLAG_CC1OF: Capture/Compare 1 overcapture flag
      *            @arg TIM_FLAG_CC2OF: Capture/Compare 2 overcapture flag
      *            @arg TIM_FLAG_CC3OF: Capture/Compare 3 overcapture flag
      *            @arg TIM_FLAG_CC4OF: Capture/Compare 4 overcapture flag
      * @retval The new state of __FLAG__ (TRUE or FALSE).
      */
    #define __HAL_TIM_CLEAR_FLAG(__HANDLE__, __FLAG__)       ((__HANDLE__)->Instance->SR = ~(__FLAG__))

    清除标志函数所支持的参数跟获取函数是一 一对应的。除了这两个函数,还是定时器的中断开启和中断关闭函数用的也比较多。

    /** @brief  Enable the specified TIM interrupt.
    * @param  __HANDLE__: specifies the TIM Handle.
    * @param  __INTERRUPT__: specifies the TIM interrupt source to enable.
    *          This parameter can be one of the following values:
    *            @arg TIM_IT_UPDATE: Update interrupt
    *            @arg TIM_IT_CC1:   Capture/Compare 1 interrupt
    *            @arg TIM_IT_CC2:  Capture/Compare 2 interrupt
    *            @arg TIM_IT_CC3:  Capture/Compare 3 interrupt
    *            @arg TIM_IT_CC4:  Capture/Compare 4 interrupt
    *            @arg TIM_IT_COM:   Commutation interrupt
    *            @arg TIM_IT_TRIGGER: Trigger interrupt
    *            @arg TIM_IT_BREAK: Break interrupt
    * @retval None
    */
    #define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__)  ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))
    
      /** @brief  Disable the specified TIM interrupt.
      * @param  __HANDLE__: specifies the TIM Handle.
      * @param  __INTERRUPT__: specifies the TIM interrupt source to disable.
      *          This parameter can be one of the following values:
      *            @arg TIM_IT_UPDATE: Update interrupt
      *            @arg TIM_IT_CC1:   Capture/Compare 1 interrupt
      *            @arg TIM_IT_CC2:  Capture/Compare 2 interrupt
      *            @arg TIM_IT_CC3:  Capture/Compare 3 interrupt
      *            @arg TIM_IT_CC4:  Capture/Compare 4 interrupt
      *            @arg TIM_IT_COM:   Commutation interrupt
      *            @arg TIM_IT_TRIGGER: Trigger interrupt
      *            @arg TIM_IT_BREAK: Break interrupt
      * @retval None
      */
    #define __HAL_TIM_DISABLE_IT(__HANDLE__, __INTERRUPT__)   ((__HANDLE__)->Instance->DIER &= ~(__INTERRUPT__))

    常用的也是前五个参数,1个定时器更新中断以及4个CC中断 。

    注意:操作定时器的寄存器不限制必须要用HAL库提供的API,比如要操作寄存器CR1,直接调用TIM1->CR1操作即可。

    32.3.7 定时器初始化流程总结

    使用方法由HAL库提供:

      第1步:通过下面几个函数配置定时器工作在相应的模式

    •   HAL_TIM_Base_Init

    简单的定时器时基础功能

    •   HAL_TIM_OC_Init 和 HAL_TIM_OC_ConfigChannel

    配置定时器产生输出比较信号

    •   HAL_TIM_PWM_Init 和 HAL_TIM_PWM_ConfigChannel

    配置定时器产生PWM信号

    •   HAL_TIM_IC_Init 和 HAL_TIM_IC_ConfigChannel

    配置定时器测量外部信号

    •   HAL_TIM_OnePulse_Init 和 HAL_TIM_OnePulse_ConfigChannel

    配置定时器工作在单脉冲模式

    •   HAL_TIM_Encoder_Init

    配置定时器使用编码器接口

      第2步:定时器几个常用功能的底层初始化API,这个里面需要用户自己填

    第1步里面的几个函数会调用下面的API。

    •   定时器基本功能 : HAL_TIM_Base_MspInit()
    •   输入捕获 : HAL_TIM_IC_MspInit()
    •   输出比较 : HAL_TIM_OC_MspInit()
    •   PWM输出 : HAL_TIM_PWM_MspInit()
    •   单脉冲输出模式: HAL_TIM_OnePulse_MspInit()
    •   编码器模式 : HAL_TIM_Encoder_MspInit()

      第3步:底层初始化具体实现

    第2步中函数的具体实现。

    •   使用函数__HAL_RCC_TIMx_CLK_ENABLE()使能定时器时钟。
    •   使用函数__HAL_RCC_GPIOx_CLK_ENABLE()使能定时器使用到的引脚时钟。
    •   使用函数HAL_GPIO_Init()配置GPIO的复用功能。
    •   如果使能了定时器中断,调用函数HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ配置。
    •   如果使能了DMA,还需要做DMA的配置。
    •   定时器默认使用APB时钟,如果使用外部时钟,调用函数HAL_TIM_ConfigClockSource可以配置。

      第4步:启动定时器外设

    •   定时器基础功能:

    HAL_TIM_Base_Start()

    HAL_TIM_Base_Start_DMA()

    HAL_TIM_Base_Start_IT()

    •   输入捕获 :

    HAL_TIM_IC_Start()

    HAL_TIM_IC_Start_DMA()

    HAL_TIM_IC_Start_IT()

    •   输出比较 :

    HAL_TIM_OC_Start()

    HAL_TIM_OC_Start_DMA()

    HAL_TIM_OC_Start_IT()

    •   PWM输出:

    HAL_TIM_PWM_Start()

    HAL_TIM_PWM_Start_DMA()

    HAL_TIM_PWM_Start_IT()

    •   单脉冲模式:

    HAL_TIM_OnePulse_Start()

    HAL_TIM_OnePulse_Start_IT().

    •   编码器模式:

    HAL_TIM_Encoder_Start()

    HAL_TIM_Encoder_Start_DMA()

    HAL_TIM_Encoder_Start_IT().

      第5步:定时器的DMA突发使用下面两个函数

    •   HAL_TIM_DMABurst_WriteStart()
    •   HAL_TIM_DMABurst_ReadStart()

    定时器常用的功能,通过上面这几步即可实现。

    32.4 源文件stm32h7xx_hal_tim.c

    此文件涉及到的函数非常多,这里把几个常用的函数做个说明:

    •   HAL_TIM_Base_Init
    •   HAL_TIM_Base_Start
    •   HAL_TIM_PWM_Init
    •   HAL_TIM_PWM_ConfigChannel
    •   HAL_TIM_PWM_Start
    •   HAL_TIM_IC_Init
    •   HAL_TIM_IC_ConfigChannel
    •   HAL_TIM_IC_Start_IT
    •   HAL_TIM_OC_Init
    •   HAL_TIM_OC_ConfigChannel
    •   HAL_TIM_OC_Start

    32.4.1 函数HAL_TIM_Base_Init

    函数原型:

    HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
    { 
      /* 检测是否是有效句柄 */
      if(htim == NULL)
      {
        return HAL_ERROR;
      }
      
      /* 检测参数 */
      assert_param(IS_TIM_INSTANCE(htim->Instance)); 
      assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
      assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
      
      if(htim->State == HAL_TIM_STATE_RESET)
      { 
        /* 默认取消锁 */
        htim->Lock = HAL_UNLOCKED;
        /* 初始化底层硬件 : GPIO, CLOCK, NVIC */
        HAL_TIM_Base_MspInit(htim);
      }
      
      /* 设置TIM状态 */
      htim->State= HAL_TIM_STATE_BUSY;
      
      /* 基本参数配置 */
      TIM_Base_SetConfig(htim->Instance, &htim->Init); 
      
      /* 设置TIM就绪 */
      htim->State= HAL_TIM_STATE_READY;
      
      return HAL_OK;
    }

    函数描述:

    此函数用于初始化定时器用于PWM。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    注意事项:

    1. 函数HAL_TIM_Base_MspInit用于初始化定时器的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能,由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
    2. 如果形参htim的结构体成员gState没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量TIM_HandleTypeDef TimHandle。

    对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个 HAL_TIM_STATE_RESET  = 0x00U。

    解决办法有三:

    方法1:用户自己初始定时器和涉及到的GPIO等。

    方法2:定义TIM_HandleTypeDef TimHandle为全局变量。

    方法3:下面的方法

    if(HAL_TIM_Base_DeInit(&TimHandle) != HAL_OK)
    {
        Error_Handler();
    }  
    if(HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler();
    }

    使用举例:

    TIM_HandleTypeDef   TimHandle = {0};
    
    /* 
        定时器中断更新周期 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)
    */
    TimHandle.Instance = TIMx;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;    
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    32.4.2 函数HAL_TIM_Base_Start

    函数原型:

    HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
    {
      /* 检测参数状态 */
      assert_param(IS_TIM_INSTANCE(htim->Instance));
      
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_BUSY;
      
      /* 使能定时器 */
      __HAL_TIM_ENABLE(htim);
      
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_READY;
      
      /* 返回HAL_OK */
      return HAL_OK;
    }

    函数描述:

    此函数比较简单,调用函数HAL_TIM_Base_Init配置了基础功能后,启动定时器。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量。
    •   返回值,固定返回HAL_OK,表示初始化成功。

    使用举例:

    TIM_HandleTypeDef   TimHandle = {0};
    
    /* 
        定时器中断更新周期 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)
    */
    TimHandle.Instance = TIMx;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;    
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 启动定时器 */
    if (HAL_TIM_Base_Start(&TimHandle) != HAL_OK)
    {
    Error_Handler(__FILE__, __LINE__);
    }

    32.4.3 函数HAL_TIM_PWM_Init

    函数原型:

    HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
    {
      /* 检查句柄是否有效 */
      if(htim == NULL)
      {
        return HAL_ERROR;
      }
    
      /* 检测参数 */
      assert_param(IS_TIM_INSTANCE(htim->Instance));
      assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
      assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
    
      if(htim->State == HAL_TIM_STATE_RESET)
      {
        /* 默认取消锁 */
        htim->Lock = HAL_UNLOCKED;
    
        /* 初始底层 : GPIO, CLOCK, NVIC 和 DMA */
        HAL_TIM_PWM_MspInit(htim);
      }
    
      /* 设置定时状态 */
      htim->State= HAL_TIM_STATE_BUSY;
     
      /* 配置定时器用于PWM */  
      TIM_Base_SetConfig(htim->Instance, &htim->Init); 
       
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_READY;
      
      return HAL_OK;
    }

    函数描述:

    此函数用于初始化定时为PWM方式。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    注意事项:

    1. 函数HAL_TIM_PWM_MspInit用于初始化定时器的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能,由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
    2. 如果形参htim的结构体成员gState没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量TIM_HandleTypeDef TimHandle。

    对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个 HAL_TIM_STATE_RESET  = 0x00U。

    解决办法有三:

    方法1:用户自己初始定时器和涉及到的GPIO等。

    方法2:定义TIM_HandleTypeDef TimHandle为全局变量。

    方法3:下面的方法

    if(HAL_TIM_PWM_DeInit(&TimHandle) != HAL_OK)
    {
        Error_Handler();
    }  
    if(HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler();
    }

    使用举例:

    TIM_HandleTypeDef  TimHandle = {0};
    
    /*  PWM频率 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)*/
    TimHandle.Instance = TIM1;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = 0;
    if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    32.4.4 函数HAL_TIM_PWM_ConfigChannel

    函数原型:

    HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
                                                TIM_OC_InitTypeDef* sConfig,
                                                uint32_t Channel)
    {
       /* 省略 */
    
      /* 开锁 */
      __HAL_LOCK(htim);
    
      htim->State = HAL_TIM_STATE_BUSY;
    
      switch (Channel)
      {
        case TIM_CHANNEL_1:
        {
          /* 检查参数 */
          assert_param(IS_TIM_CC1_INSTANCE(htim->Instance));
    
          /* 配置通道1的PWM模式 */
          TIM_OC1_SetConfig(htim->Instance, sConfig);
    
          /*预装载使能,更新事件产生时写入有效 */
          htim->Instance->CCMR1 |= TIM_CCMR1_OC1PE;
    
          /* 配置是快速输出模式 */
          htim->Instance->CCMR1 &= ~TIM_CCMR1_OC1FE;
          htim->Instance->CCMR1 |= sConfig->OCFastMode;
        }
        break;
    
        case TIM_CHANNEL_2:
        {
           /* 省略 */
        }
        break;
    
        case TIM_CHANNEL_3:
        {
           /* 省略 */
        }
        break;
    
        case TIM_CHANNEL_4:
        {
          /* 省略 */
        }
        break;
    
        case TIM_CHANNEL_5:
        {
           /* 省略 */
        }
        break;
    
        case TIM_CHANNEL_6:
        {
            /* 省略 */
        }
        break;
    
        default:
        break;
      }
    
      htim->State = HAL_TIM_STATE_READY;
    
      /* 关锁 */
      __HAL_UNLOCK(htim);
    
      return HAL_OK;
    }

    函数描述:

    此函数用于配置定时器的PWM通道。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于定时器基本参数配置。
    •   第2个参数是TIM_OC_InitTypeDef类型结构体指定变量,用于定时器输出比较参数配置。
    •   第3个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    TIM_CHANNEL_5

    TIM_CHANNEL_6

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    使用举例:

    TIM_HandleTypeDef  TimHandle = {0};
    
    /*  PWM频率 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)*/
    TimHandle.Instance = TIM1;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = 0;
    if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 配置定时器PWM输出通道 */
    sConfig.OCMode       = TIM_OCMODE_PWM1;
    sConfig.OCPolarity   = TIM_OCPOLARITY_HIGH;
    sConfig.OCFastMode   = TIM_OCFAST_DISABLE;
    sConfig.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
    sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    sConfig.OCIdleState  = TIM_OCIDLESTATE_RESET;
    
    /* 占空比 */
    sConfig.Pulse = pulse;
    if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TimChannel[_ucChannel]) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    32.4.5 函数HAL_TIM_PWM_Start

    函数原型:

    HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
    {
      /* 检测参数 */
      assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
      
      /* 使能捕获比较通道 */
      TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
      
      if(IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)  
      {
        /* 使能主输出 */
        __HAL_TIM_MOE_ENABLE(htim);
      }
        
      /* 使能定时器 */
      __HAL_TIM_ENABLE(htim);
      
      /* 返回状态*/
      return HAL_OK;
    }

    函数描述:

    此函数用于启动PWM。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   第2个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    使用举例:

    TIM_HandleTypeDef  TimHandle = {0};
    
    /*  PWM频率 = TIMxCLK / usPrescaler + 1)/usPeriod + 1)*/
    TimHandle.Instance = TIM1;
    TimHandle.Init.Prescaler         = usPrescaler;
    TimHandle.Init.Period            = usPeriod;
    TimHandle.Init.ClockDivision     = 0;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0;
    TimHandle.Init.AutoReloadPreload = 0;
    if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 配置定时器PWM输出通道 */
    sConfig.OCMode       = TIM_OCMODE_PWM1;
    sConfig.OCPolarity   = TIM_OCPOLARITY_HIGH;
    sConfig.OCFastMode   = TIM_OCFAST_DISABLE;
    sConfig.OCNPolarity  = TIM_OCNPOLARITY_HIGH;
    sConfig.OCNIdleState = TIM_OCNIDLESTATE_RESET;
    sConfig.OCIdleState  = TIM_OCIDLESTATE_RESET;
    
    /* 占空比 */
    sConfig.Pulse = pulse;
    if (HAL_TIM_PWM_ConfigChannel(&TimHandle, &sConfig, TimChannel[_ucChannel]) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 启动PWM输出 */
    if (HAL_TIM_PWM_Start(&TimHandle, TimChannel[_ucChannel]) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    32.4.6 函数HAL_TIM_IC_Init

    函数原型:

    HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim)
    {
      /* 检测形参是否有效 */
      if(htim == NULL)
      {
        return HAL_ERROR;
      }
    
      /* 检测参数 */
      assert_param(IS_TIM_INSTANCE(htim->Instance));
      assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
      assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision)); 
    
      if(htim->State == HAL_TIM_STATE_RESET)
      {
    /* 默认取消锁 */
        htim->Lock = HAL_UNLOCKED;
        
         /* 初始底层 : GPIO, CLOCK, NVIC 和 DMA */
        HAL_TIM_IC_MspInit(htim);
      }
      
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_BUSY; 
      
      /* 配置定时器为输入捕获模式 */  
      TIM_Base_SetConfig(htim->Instance, &htim->Init); 
       
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_READY;
      
      return HAL_OK;
    }

    函数描述:

    此函数用于定时器输入捕获初始化。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    注意事项:

    1. 函数HAL_TIM_IC_MspInit用于初始化定时器的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能,由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
    2. 如果形参htim的结构体成员gState没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量TIM_HandleTypeDef TimHandle。

    对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个HAL_TIM_STATE_RESET  = 0x00U。

    解决办法有三:

    方法1:用户自己初始定时器和涉及到的GPIO等。

    方法2:定义TIM_HandleTypeDef TimHandle为全局变量。

    方法3;下面的方法

    if(HAL_TIM_IC_DeInit(&UartHandle) != HAL_OK)
    {
        Error_Handler();
    }  
    if(HAL_TIM_IC_Init(&UartHandle) != HAL_OK)
    {
        Error_Handler();
    }

    32.4.7 函数HAL_TIM_IC_ConfigChannel

    函数原型:

    HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef* sConfig, uint32_t Channel)
    {
      /* 检查参数 */
      assert_param(IS_TIM_CC1_INSTANCE(htim->Instance));
      assert_param(IS_TIM_IC_POLARITY(sConfig->ICPolarity));
      assert_param(IS_TIM_IC_SELECTION(sConfig->ICSelection));
      assert_param(IS_TIM_IC_PRESCALER(sConfig->ICPrescaler));
      assert_param(IS_TIM_IC_FILTER(sConfig->ICFilter));
    
      /* 开锁 */  
      __HAL_LOCK(htim);
      
      htim->State = HAL_TIM_STATE_BUSY;
      
      if (Channel == TIM_CHANNEL_1)
      {
        /* 配置输入通道1 */
        TIM_TI1_SetConfig(htim->Instance,
                   sConfig->ICPolarity,
                   sConfig->ICSelection,
                   sConfig->ICFilter);
                   
        /* 清零IC1PSC位 */
        htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC;
    
        /* 根据用户配置,设置分频 */
        htim->Instance->CCMR1 |= sConfig->ICPrescaler;
      }
      else if (Channel == TIM_CHANNEL_2)
      {
    /* 省略 */
      }
      else if (Channel == TIM_CHANNEL_3)
      {
         /* 省略 */
      }
      else
      {
         /* 省略 */
      }
      
      htim->State = HAL_TIM_STATE_READY;
        
      __HAL_UNLOCK(htim);
      
      return HAL_OK; 
    }

    函数描述:

    此函数用于配置定时器的输入捕获通道。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于定时器基本参数配置
    •   第2个参数是TIM_IC_InitTypeDef类型结构体指定变量,用于定时器输出比较参数配置。
    •   第3个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    32.4.8 函数HAL_TIM_IC_Start_IT

    函数原型:

    HAL_StatusTypeDef HAL_TIM_IC_Start_IT (TIM_HandleTypeDef *htim, uint32_t Channel)
    {
      /* 检查参数 */
      assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
      
      switch (Channel)
      {
        case TIM_CHANNEL_1:
        {       
          /* 使能CC1(Capture/Compare 1)中断 */
          __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
        }
        break;
        
        case TIM_CHANNEL_2:
        {
       /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_3:
        {
      /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_4:
        {
      /* 省略 */
        }
        break;
        
        default:
        break;
      }  
      /* 使能输入捕获通道 */
      TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
        
      /* 使能定时器 */
      __HAL_TIM_ENABLE(htim);  
    
      /* 返回状态 */
      return HAL_OK;  
    }

    函数描述:

    此函数用于启动定时器输入捕获模式,采用定时器方式。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   第2个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    32.4.9 函数HAL_TIM_OC_Init

    函数原型:

    HAL_StatusTypeDef HAL_TIM_OC_Init(TIM_HandleTypeDef* htim)
    {
      /* 检测形参是否有效 Check */
      if(htim == NULL)
      {
        return HAL_ERROR;
      }
    
      /* 检查参数 */
      assert_param(IS_TIM_INSTANCE(htim->Instance));
      assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
      assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
     
      if(htim->State == HAL_TIM_STATE_RESET)
      {
    /* 默认取消锁 */
        htim->Lock = HAL_UNLOCKED;
        
    /* 初始底层 : GPIO, CLOCK, NVIC 和 DMA */
        HAL_TIM_OC_MspInit(htim);
      }
      
      /* 设置定时器状态Set the TIM state */
      htim->State= HAL_TIM_STATE_BUSY;
    
      /* 配置定时器为输出比较模式Init the base time for the Output Compare */  
      TIM_Base_SetConfig(htim->Instance,  &htim->Init); 
      
      /* 设置定时器状态 */
      htim->State= HAL_TIM_STATE_READY;
      
      return HAL_OK;
    }

    函数描述:

    此函数用于定时器输入捕获初始化。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    注意事项:

    1. 函数HAL_TIM_OC_MspInit用于初始化定时器的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能,由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
    2. 如果形参htim的结构体成员gState没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量TIM_HandleTypeDef TimHandle。

    对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个HAL_TIM_STATE_RESET  = 0x00U。

    解决办法有三:

    方法1:用户自己初始定时器和涉及到的GPIO等。

    方法2:定义TIM_HandleTypeDef TimHandle为全局变量。

    方法3;下面的方法

    if(HAL_TIM_OC_DeInit(&UartHandle) != HAL_OK)
    {
        Error_Handler();
    }  
    if(HAL_TIM_OC_Init(&UartHandle) != HAL_OK)
    {
        Error_Handler();
    }

    32.4.10   函数HAL_TIM_OC_ConfigChannel

    函数原型:

    HAL_StatusTypeDef HAL_TIM_OC_ConfigChannel(TIM_HandleTypeDef *htim,
                                               TIM_OC_InitTypeDef* sConfig,
                                               uint32_t Channel)
    {  
      /* Check the parameters */
      assert_param(IS_TIM_CHANNELS(Channel)); 
      assert_param(IS_TIM_OC_MODE(sConfig->OCMode));
      assert_param(IS_TIM_OC_POLARITY(sConfig->OCPolarity));
      
      /* Process Locked */
      __HAL_LOCK(htim); 
      
      htim->State = HAL_TIM_STATE_BUSY;
      
      switch (Channel)
      {
        case TIM_CHANNEL_1:
        {
          /* 检测参数Check the parameters */
          assert_param(IS_TIM_CC1_INSTANCE(htim->Instance)); 
          
         /* 配置定时器输出比较通道1 */
          TIM_OC1_SetConfig(htim->Instance, sConfig);
        }
        break;
        
        case TIM_CHANNEL_2:
        {
          /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_3:
        {
          /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_4:
        {
          /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_5:
        {
          /* 省略 */
        }
        break;
        
        case TIM_CHANNEL_6:
        {
          /* 省略 */
        }
        break;
            
        default:
        break;    
      }
      
      htim->State = HAL_TIM_STATE_READY;
      
      __HAL_UNLOCK(htim); 
      
      return HAL_OK;
    }

    函数描述:

    此函数用于初始化串口的基础特性和高级特性。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于定时器基本参数配置。
    •   第2个参数是TIM_OC_InitTypeDef类型结构体指定变量,用于定时器输出比较参数配置。
    •   第3个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    TIM_CHANNEL_5

    TIM_CHANNEL_6

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    32.4.11   函数HAL_TIM_OC_Start

    函数原型:

    HAL_StatusTypeDef HAL_TIM_OC_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
    {
      /* 检查参数 */
      assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
      
      /* 使能输出比较通道 */
      TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
      
      if(IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET)  
      {
        /* 使能主输出 */
        __HAL_TIM_MOE_ENABLE(htim);
      }
      
      /* 使能定时器 */
      __HAL_TIM_ENABLE(htim); 
      
      /* 返回状态 */
      return HAL_OK;
    }

    函数描述:

    此函数用于启动定时器输出比较模式。

    函数参数:

    •   第1个参数是TIM_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
    •   第2个参数是通道设置,支持以下参数:

    TIM_CHANNEL_1

    TIM_CHANNEL_2

    TIM_CHANNEL_3

    TIM_CHANNEL_4

    •   返回值,返回HAL_ERROR表示配置失败,HAL_OK表示配置成功,HAL_BUSY表示忙(操作中),HAL_TIMEOUT表示时间溢出。

    32.5 总结

    本章节就为大家讲解这么多,建议大家将GPIO的驱动源码结合参考手册中的寄存器通读一遍,对于我们后面章节的学习大有裨益。

  • 相关阅读:
    Windows Server 2019 Core 或 hyper-v server 2019图形工具安装
    添加进站允许规则和出站规则命令 netsh advfirewall firewall
    ID 1196 请确保与从属 IP 地址相关联的网络适配器已配置为至少可以访问一个 DNS 服务器
    通过注册表的方式禁用IPV6
    MS-SQL SERVER服务器占用CPU高,用语句先排查。
    hyper-v server 卸载软件(MD-DOS卸载软件)
    除getopenFilename以外。打开文件的另一种方式FileDialog
    返回当前工作簿的目录ThisWorkbook.path & ""
    筛选
    自动填充
  • 原文地址:https://www.cnblogs.com/armfly/p/11248450.html
Copyright © 2020-2023  润新知