29. 定时器输出指定个数的PWM

一、重复计数器

  计数器每次上溢或下溢都能使重复计数器减 1,减到 0 时,再发生一次溢出就会产生更新事件。如果设置 RCR 为 N,更新事件将在 N+1 次溢出时发生。

重复计数器

二、常用的寄存器

2.1、TIM1和TIM8控制寄存器

TIM1和TIM8控制寄存器1

  TIMx_CR1 寄存器 CMS[9:8] 位指示定时器时钟(CK_INT)频率与死区发生器以及数字滤波器(ETR、TIx)所使用的死区及采样时钟(tDTS)之间的分频比。

  TIMx_CR1 寄存器位 7(ARPE)用于控制自动重载寄存器是否进行缓冲,如果 ARPE 位置 1,ARR 起缓冲作用,即只有在更新事件发生时才会把 ARR 的值写入其影子寄存器里;如果 ARPE 位置 0,那么修改自动重载寄存器的值时,该值会马上被写入其影子寄存器中,从而立即生效。

  TIMx_CR1 寄存器 CMS[6:5] 位,用于设置边沿对齐模式还是中心对齐模式。当 CMS[1:0] 位设置为 00 时,为边沿对齐模式,其它值为中心对齐模式。

  TIMx_CR1 寄存器位 4 DIR 位,用于控制定时器的计数方向。设置 DIR 位为 0 时,为递增计数。设置 DIR 位为 1 时,为递减计数。当定时器配置为中心对齐模式或编码器模式时,该位为只读状态。

  TIMx_CR1 寄存器位 0 CEN 位,用于使能计数器的工作,必须要设置该位为 1,计数器才会开始计数。

TIM1和TIM8控制寄存器2

2.2、TIM1和TIM8从模式控制寄存器

TIM1和TIM8从模式控制寄存器

  该寄存器的 SMS[2:0]位,用于从模式选择,其实就是选择计数器输入时钟的来源。比如通用定时器中断例程中我们设置 SMS[2:0]=000,禁止从模式,这样 PSC 预分频器的时钟就直接来自内部时钟(CK_INT),按照我们例程 System_Clock_Init() 函数的配置,频率为 84Mhz(APB1 总线时钟频率的 2 倍)。

2.3、TIM1和TIM8捕获/比较模式寄存器

TIM1和TIM8捕获/比较模式寄存器1

  该寄存器的有些位在不同模式下,功能不一样,我们前面已经说过。比如我们要让 TIM1 的 CH1 输出 PWM 波为例,该寄存器的模式设置位 OC1M[2:0]就是对应着通道 1 的模式设置,此部分由 3 位组成,总共可以配置成 8 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1 和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反,这里我们设置为 PWM 模式 1。位 3 OC1PE 是输出比较通道 1 的预装使能,该位需要置 1,另外 CC1S[1:0]用于设置通道 1 的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。

2.4、TIM1和TIM8捕获/比较使能寄存器

TIM1和TIM8捕获/比较使能寄存器

  该寄存器控制着各个输入输出通道的开关和极性。如果我们想让要让 TIM1 的 CH1 输出 PWM 波,这里我们要使能 CC1E 位,该位是通道 1 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。如果我们想要通道 1 的互补通道输出相反的 PWM 波,我们需要把对应的捕获/比较 1 输出使能位CC1E 置 1。CC1P 和 CC1NP 分别是通道 1 输出和通道 1 互补输出的极性设置位。

2.5、TIM1和TIM8计数器

TIM1和TIM8计数器

  TIM1/TIM8 的计数寄存器都是 16 位有效的,计数模式可以是递增计数模式、递减计数模式和中心对齐计数模式。和基本定时器一样,可以直接写该寄存器设置计数的初始值,也可以读取该寄存器获取计数器值。

2.6、TIM1和TIM8预分频器

TIM1和TIM8预分频器

  定时器的预分频寄存器都是 16 位的,即写入该寄存器的数值范围是 0 到 65535,表示 1 到 65536 分频。比如我们要 8400 分频,就往该寄存器写入 8399。

2.7、TIM1和TIM8自动重载寄存器

TIM1和TIM8自动重载寄存器

  在 F4 系列中,TIM2 和 TIM5 的自动重装载寄存器是 32 位的,其他通用定时器自动重载寄存器是低 16 位有效。该寄存器可以由 APRE 位设置是否进行缓冲。计数器的值会和自动重装寄存器影子寄存器进行比较,当两者相等,定时器就会溢出,从而发生更新事件,如果打开了更新中断,还会发生更新中断。

2.8、TIM1和TIM8重复计数器寄存器

TIM1和TIM8重复计数器寄存器

  重复计数器寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。当更新事件发生时,该寄存器的值会转移到其影子寄存器中,从而真正起作用。

  该寄存器的 REP[7:0] 位是低 8 位有效,即最大值 255。因为这个寄存器只是起缓冲作用,如果大家对该寄存器写入值后,想要立即生效,可以通过对 UG 位写 1,产生软件更新事件。

2.9、TIM1和TIM8捕获/比较寄存器

TIM1和TIM8捕获/比较寄存器1

  捕获/比较寄存器(TIMx_CCR1),该寄存器只有 1 个,对应通道 CH1。我们使用的是通道 1。在输出模式下,捕获/比较寄存器影子寄存器的值与 CNT 的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的占空比了。

2.10、TIM1和TIM8断路和死区寄存器

TIM1和TIM8断路和死区寄存器

  DTG[7:0] 位,用于设置死区时间。

  位 12 BKP 位是选择断路输入信号有效电平。

  位 13 BKE 位是断路输入功能,如果我们想要使用断路输入功能,需要将位 12 BKE 位置 1 即可。

  位 14 AOE 位是自动输出使能位,如果使能 AOE 位,那么在我们输入刹车信号后再断开了刹车信号,互补的 PWM 会自动恢复输出,如果失能 AOE 位,那么在输入刹车信号后再断开了刹车信号,互补的 PWM 就不会恢复输出,而是一直保持刹车信号输入时的状态。为了方便观察,我们使能该位,即置 1。

  位 15 MOE 位是使能主输出,想要高级定时器的通道正常输出,则必须设置 MOE 位为 1。

三、高级定时器对应通道引脚

【1】、TIM1 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚 互补通道名 互补通道引脚 互补重映射通道引脚
Channel 1 PA8 PE9 Channel 1N PA7 PE8/PB13
Channel 2 PA9 PE11 Channel 2N PB0 PE10/PB14
Channel 3 PA10 PE13 Channel 3N PB1 PE12/PB15
Channel 4 PA11 PE14 Channel 4N

【2】、TIM8 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚 互补通道名 互补通道引脚 互补重映射通道引脚
Channel 1 PC6 Channel 1N PA5 PA7
Channel 2 PC7 Channel 2N PB0 PB14
Channel 3 PC8 Channel 3N PB1 PB15
Channel 4 PC9 Channel 4N

四、互补输出配置步骤

4.1、使能定时器时钟和对应通道的GPIO时钟

  使能高级定时器的时钟。

#define __HAL_RCC_TIM1_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM1EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM1EN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_TIM8_CLK_ENABLE()   do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM8EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM8EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)

  使能定时器通道引脚对应的 GPIO 的时钟。

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE()  do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_GPIOD_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIODEN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)

4.2、配置定时器基本工作参数

  HAL 库提供定时器输出 PWM 的初始化函数,它的说明如下:

HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);

  其中,htimTIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄),结构体定义如下:

typedef struct
{
    TIM_TypeDef *Instance;                              // 寄存器基地址
    TIM_Base_InitTypeDef Init;                          // 定时器初始化结构体
    HAL_TIM_ActiveChannel Channel;                      // 定时器通道
    DMA_HandleTypeDef *hdma[7];                         // DMA管理结构体

    HAL_LockTypeDef Lock;                               // 锁对象
    __IO HAL_TIM_StateTypeDef State;                    // 定时器状态
    __IO HAL_TIM_ChannelStateTypeDef ChannelState[4];   // 定时器通道状态
    __IO HAL_TIM_ChannelStateTypeDef ChannelNState[4];  // 定时器互补通道状态
    __IO HAL_TIM_DMABurstStateTypeDef DMABurstState;    // DMA溢出状态
} TIM_HandleTypeDef;

  Instance指向定时器寄存器基地址,可选值如下:

#define TIM1                ((TIM_TypeDef *) TIM1_BASE)
#define TIM8                ((TIM_TypeDef *) TIM8_BASE)

  Init定时器初始化结构体,用于配置定时器的相关参数,它的定义如下:

typedef struct
{
    uint32_t Prescaler;             // 预分频系数
    uint32_t CounterMode;           // 计数模式
    uint32_t Period;                // 自动重装载值
    uint32_t ClockDivision;         // 时钟分频因子
    uint32_t RepetitionCounter;     // 重复计数器值
    uint32_t AutoReloadPreload;     // 自动重装载值预载入功能
} TIM_Base_InitTypeDef;

  Prescaler预分频系数,即写入预分频寄存器的值,范围 0 到 65535。

  CounterMode计数器计数模式,。

#define TIM_COUNTERMODE_UP                 0x00000000U                          /*!< Counter used as up-counter   */
#define TIM_COUNTERMODE_DOWN               TIM_CR1_DIR                          /*!< Counter used as down-counter */
#define TIM_COUNTERMODE_CENTERALIGNED1     TIM_CR1_CMS_0                        /*!< Center-aligned mode 1        */
#define TIM_COUNTERMODE_CENTERALIGNED2     TIM_CR1_CMS_1                        /*!< Center-aligned mode 2        */
#define TIM_COUNTERMODE_CENTERALIGNED3     TIM_CR1_CMS                          /*!< Center-aligned mode 3        */

  Period自动重载值,即写入自动重载寄存器的值,范围 0 到 65535。

  AutoReloadPreload自动重载预装载使能,即控制寄存器 1(TIMx_CR1)的 ARPE 位。

#define TIM_AUTORELOAD_PRELOAD_DISABLE                0x00000000U               /*!< TIMx_ARR register is not buffered */
#define TIM_AUTORELOAD_PRELOAD_ENABLE                 TIM_CR1_ARPE              /*!< TIMx_ARR register is buffered */

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

typedef enum 
{
    HAL_OK = 0x00U,             // 成功
    HAL_ERROR = 0x01U,          // 错误
    HAL_BUSY = 0x02U,           // 忙碌
    HAL_TIMEOUT = 0x03U         // 超时
} HAL_StatusTypeDef;

4.3、设置对应通道引脚的工作模式

  HAL 库中,提供 HAL_GPIO_Init() 函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);

  该函数的第一个形参 GPIOx 用来 指定端口号,可选值如下:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)

  第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来 设置 GPIO 的工作模式,其定义如下:

typedef struct
{
  uint32_t Pin;         // 引脚号
  uint32_t Mode;        // 模式设置
  uint32_t Pull;        // 上下拉设置
  uint32_t Speed;       // 速度设置
  uint32_t Alternate;   // 复用功能设置
}GPIO_InitTypeDef;

  成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。

#define GPIO_PIN_0                 ((uint16_t)0x0001)  /* Pin 0 selected    */
#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */
#define GPIO_PIN_2                 ((uint16_t)0x0004)  /* Pin 2 selected    */
#define GPIO_PIN_3                 ((uint16_t)0x0008)  /* Pin 3 selected    */
#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */
#define GPIO_PIN_6                 ((uint16_t)0x0040)  /* Pin 6 selected    */
#define GPIO_PIN_7                 ((uint16_t)0x0080)  /* Pin 7 selected    */
#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
#define GPIO_PIN_9                 ((uint16_t)0x0200)  /* Pin 9 selected    */
#define GPIO_PIN_10                ((uint16_t)0x0400)  /* Pin 10 selected   */
#define GPIO_PIN_11                ((uint16_t)0x0800)  /* Pin 11 selected   */
#define GPIO_PIN_12                ((uint16_t)0x1000)  /* Pin 12 selected   */
#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */
#define GPIO_PIN_14                ((uint16_t)0x4000)  /* Pin 14 selected   */
#define GPIO_PIN_15                ((uint16_t)0x8000)  /* Pin 15 selected   */

  成员 Mode 是 GPIO 的 模式选择,有以下选择项:

#define  GPIO_MODE_AF_PP                        0x00000002U     // 推挽式复用

  成员 Pull 用于 配置上下拉电阻,有以下选择项:

#define  GPIO_NOPULL        0x00000000U     // 无上下拉
#define  GPIO_PULLUP        0x00000001U     // 上拉
#define  GPIO_PULLDOWN      0x00000002U     // 下拉

  成员 Speed 用于 配置 GPIO 的速度,有以下选择项:

#define  GPIO_SPEED_FREQ_LOW         0x00000000U    // 低速
#define  GPIO_SPEED_FREQ_MEDIUM      0x00000001U    // 中速
#define  GPIO_SPEED_FREQ_HIGH        0x00000002U    // 高速
#define  GPIO_SPEED_FREQ_VERY_HIGH   0x00000003U    // 极速

  成员 Alternate 用于 配置具体的复用功能,不同的 GPIO 口可以复用的功能不同,具体可参考数据手册。

#define GPIO_AF1_TIM1          ((uint8_t)0x01)  /* TIM1 Alternate Function mapping */

#define GPIO_AF3_TIM8          ((uint8_t)0x03)  /* TIM8 Alternate Function mapping  */

4.4、配置PWM模式和比较值

  定时器的 PWM 通道设置初始化函数。其声明如下:

HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfig, uint32_t Channel);

  其中,htimTIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。

  sConfigTIM_OC_InitTypeDef 结构体类型指针变量,用于配置定时器的输出比较参数。

typedef struct
{
    uint32_t OCMode;            // 输出比较模式选择
    uint32_t Pulse;             // 设置比较值
    uint32_t OCPolarity;        // 设置输出比较极性
    uint32_t OCNPolarity;       // 设置互补输出比较极性
    uint32_t OCFastMode;        // 使能或失能输出比较快速模式
    uint32_t OCIdleState;       // 空闲状态OC1输出
    uint32_t OCNIdleState;      // 空闲状态OC1N输出
} TIM_OC_InitTypeDef;

  成员变量 OCMode 用来 设置模式,可选值如下:

#define TIM_OCMODE_PWM1                     (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1)                    /*!< PWM mode 1                             */
#define TIM_OCMODE_PWM2                     (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0) /*!< PWM mode 2                             */

  成员变量 Pulse 用来 设置捕获比较值

  成员变量 TIM_OCPolarity 用来 设置输出极性

#define TIM_OCPOLARITY_HIGH                0x00000000U                          /*!< Capture/Compare output polarity  */
#define TIM_OCPOLARITY_LOW                 TIM_CCER_CC1P                        /*!< Capture/Compare output polarity  */

  Channel 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。

#define TIM_CHANNEL_1                      0x00000000U                          /*!< Capture/compare channel 1 identifier      */
#define TIM_CHANNEL_2                      0x00000004U                          /*!< Capture/compare channel 2 identifier      */
#define TIM_CHANNEL_3                      0x00000008U                          /*!< Capture/compare channel 3 identifier      */
#define TIM_CHANNEL_4                      0x0000000CU                          /*!< Capture/compare channel 4 identifier      */
#define TIM_CHANNEL_ALL                    0x0000003CU                          /*!< Global Capture/compare channel identifier  */

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

4.5、使能中断

4.5.1、设置中断优先级分组

  HAL_NVIC_SetPriorityGrouping() 函数是设置中断优先级分组函数。其声明如下:

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

  其中,参数 PriorityGroup中断优先级分组号,可以选择范围如下:

#define NVIC_PRIORITYGROUP_0         0x00000007U /*!< 0 bits for pre-emption priority
                                                      4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1         0x00000006U /*!< 1 bits for pre-emption priority
                                                      3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2         0x00000005U /*!< 2 bits for pre-emption priority
                                                      2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3         0x00000004U /*!< 3 bits for pre-emption priority
                                                      1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4         0x00000003U /*!< 4 bits for pre-emption priority
                                                      0 bits for subpriority */

  这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。如果调用了多次,则以最后一次为准。

4.5.2、设置中断优先级

  HAL_NVIC_SetPriority() 函数是设置中断优先级函数。其声明如下:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

typedef enum
{
  TIM1_BRK_TIM9_IRQn          = 24,     /*!< TIM1 Break interrupt and TIM9 global interrupt                    */
  TIM1_UP_TIM10_IRQn          = 25,     /*!< TIM1 Update Interrupt and TIM10 global interrupt                  */
  TIM1_TRG_COM_TIM11_IRQn     = 26,     /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
  TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                                    */

  TIM8_BRK_TIM12_IRQn         = 43,     /*!< TIM8 Break Interrupt and TIM12 global interrupt                   */
  TIM8_UP_TIM13_IRQn          = 44,     /*!< TIM8 Update Interrupt and TIM13 global interrupt                  */
  TIM8_TRG_COM_TIM14_IRQn     = 45,     /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
  TIM8_CC_IRQn                = 46,     /*!< TIM8 Capture Compare global interrupt                             */
} IRQn_Type;

  参数 PreemptPriority抢占优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

  参数 SubPriority响应优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

4.5.3、使能中断

  HAL_NVIC_EnableIRQ() 函数是中断使能函数。其声明如下:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

4.6、使能定时器更新中断

#define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__)    ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))

4.7、使能输出并启动计数器

HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);

  其中,htimTIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。

  Channel定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

4.8、修改比较值控制占空比

#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))

  __HANDLE__TIM_HandleTypeDef 结构体类型指针变量,__CHANNEL__ 对应 PWM 的输出通道,__COMPARE__ 则是要写到捕获/比较寄存器(TIMx_CCR1/2/3/4)的值。实际上该宏定义最终还是往对应的捕获/比较寄存器写入比较值来控制 PWM 波的占空比。

五、程序源码

  这时,我们将定时器 8 设置为自增计数模式。定时器 8 的时钟源频率为 2 倍 APB2 总线时钟频率,即频率为 168MHz,当预分频寄存器的值为 167,自动重载寄存器的值为 999。根据定时器溢出公式由公式得:

\[T_{out} = \frac{(arr + 1) * (psc + 1)}{F_{clk}} = \frac{(9999 + 1) * (16799 + 1)}{168000000} = 1s \]

  由频率是周期的倒数关系得到 PWM 的频率为 1Hz。

  定时器初始化函数,内容如下:

TIM_HandleTypeDef g_timer8_handle;

/**
 * @brief 定时器PWM功能初始化函数
 * 
 * @param htim 定时器句柄
 * @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: [1, 8]
 * @param prescaler 预分频系数,可选值: 0 ~ 65535
 * @param period 自动重装载值,可选值: 0 ~ 65535
 * @param channel 输出PWM的通道,可选值: TIM_CHANNEL_x, x可选范围: 1 ~ 4
 * @param polarity 输出比较极性,可选值: [TIM_OCPOLARITY_LOW, TIM_OCPOLARITY_HIGH]
 * @param pluse 输出比较值,可选值: 0 ~ 65535
 */
void TIM_PWM_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period, uint32_t channel, uint32_t polarity, uint32_t pluse)
{
    TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};

    htim->Instance = TIMx;                                                      // 定时器寄存器基地址
    htim->Init.CounterMode = TIM_COUNTERMODE_UP;                                // 计数模式
    htim->Init.Prescaler = prescaler;                                           // 预分频系数
    htim->Init.Period = period;                                                 // 自动重装载值
    HAL_TIM_PWM_Init(htim);

    TIM_OC_InitStruct.OCMode = TIM_OCMODE_PWM1;                                 // PWM模式1
    TIM_OC_InitStruct.Pulse = pluse;                                            // 比较值
    TIM_OC_InitStruct.OCPolarity = polarity;                                    // 输出比较极性
    HAL_TIM_PWM_ConfigChannel(htim, &TIM_OC_InitStruct, channel);
}

  定时器 PWM 模式底层初始化函数,内容如下:

/**
 * @brief 定时器PWM模式底层初始化函数
 * 
 * @param htim 定时器句柄
 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (htim->Instance == TIM8)
    {
        __HAL_RCC_TIM8_CLK_ENABLE();                                            // 使能TIM8的时钟
        __HAL_RCC_GPIOC_CLK_ENABLE();                                           // 使能TIM8的Channel 1对应的GPIO时钟

        GPIO_InitStruct.Pin = GPIO_PIN_6;                                       // TIM8的Channel 1
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                                 // 复用功能
        GPIO_InitStruct.Pull = GPIO_NOPULL;                                     // 不使用上下拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                           // GPIO输出速度
        GPIO_InitStruct.Alternate = GPIO_AF3_TIM8;                              // 复用功能选择
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

        HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 3, 0);
        HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
    }
}

  定时器设置 PWM 次数函数:

uint32_t g_timxchy_npwm_remain = 0;

/**
 * @brief 定时器8设置PWM输出次数函数
 * 
 * @param npwm 
 */
void TIM_NPWM_SetCount(TIM_HandleTypeDef *htim, uint32_t npwm)
{
    if (npwm <= 0)
    {
        return;
    }
  
    g_timxchy_npwm_remain = npwm;                                               // 保存脉冲个数
    HAL_TIM_GenerateEvent(htim, TIM_EVENTSOURCE_UPDATE);                        //  产生一次更新事件,在中断里面处理脉冲输出
    __HAL_TIM_ENABLE(htim);                                                     // 使能定时器
}

  定时器 8 更新中断服务函数:

/**
 * @brief 定时器8到13更新中断服务函数
 * 
 */
void TIM8_UP_TIM13_IRQHandler(void)
{
    uint16_t npwm = 0;

    if (__HAL_TIM_GET_FLAG(&g_tim8_handle, TIM_FLAG_UPDATE) != RESET)
    {
        if (g_timxchy_npwm_remain >= 256)                                       // 还有大于等于256个脉冲需要发送
        {
            g_timxchy_npwm_remain -= 256;
            npwm = 256;
        }
        else if (g_timxchy_npwm_remain % 256)                                   //  还有位数(不到 256)个脉冲要发送
        {
            npwm = g_timxchy_npwm_remain % 256;
            g_timxchy_npwm_remain = 0;                                          //  没有脉冲了
        }
  
        if (npwm)                                                               // 有脉冲要发送
        {
            TIM8->RCR = npwm - 1;                                               // 设置RCR值为npwm-1,即npwm个脉冲
            HAL_TIM_GenerateEvent(&g_tim8_handle, TIM_EVENTSOURCE_UPDATE);      // 产生一次更新事件,以更新RCR寄存器
            __HAL_TIM_ENABLE(&g_tim8_handle);                                   // 使能定时器
        }
        else
        {
            TIM8->CR1 &= ~(1 << 0);                                             // 关闭定时器 TIMX
        }
    }

    __HAL_TIM_CLEAR_IT(&g_tim8_handle, TIM_IT_UPDATE);                          // 清除定时器更新中断标志位
}

  main() 函数,内容如下:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);


    TIM_PWM_Init(&g_tim8_handle, TIM8, 16799, 9999, TIM_CHANNEL_1, TIM_OCPOLARITY_HIGH, 5000);
    TIM_NPWM_SetCount(&g_tim8_handle, 5);
    __HAL_TIM_ENABLE_IT(&g_tim8_handle, TIM_IT_UPDATE);                         // 使能更新中断
    HAL_TIM_PWM_Start(&g_tim8_handle, TIM_CHANNEL_1);                           // 使能输出并启动计数器

    while (1)
    {
  
    }
  
    return 0;
}
posted @ 2023-12-16 19:05  星光樱梦  阅读(77)  评论(0编辑  收藏  举报