26. 定时器的输出比较模式

一、输出比较原理

  输出比较模式下翻转功能作用是:当计数器的值等于捕获/比较寄存器影子寄存器的值时,OC1REF 发生翻转,进而控制通道输出(OCx)翻转。通过翻转功能实现输出 PWM 的具体原理如下:PWM 频率由自动重载寄存器(TIMx_ARR)的值决定,在这个过程中,只要自动重载寄存器的值不变,那么 PWM 占空比就固定为 50%。我们可以通过捕获/比较寄存器(TIMx_CCRx)的值改变 PWM 的相位。

输出比较原理

  翻转功能输出的 PWM 周期,这里用 T 表示,其计算公式如下:

\[T = \frac{2*(arr+1)*((psc+1)}{F_{clk}} \]

  其中:T:翻转功能输出的 PWM 周期(单位为 s)。\(T_{clk}\):定时器的时钟源频率(单位为 MHz)。arr:自动重装寄存器(TIMx_ARR)的值。psc:预分频器寄存器(TIMx_PSC)的值。

PWM 波周期或频率由 ARR 决定,占空比固定 50%,相位由 CCRx 决定。

二、常用的寄存器

2.1、TIMx控制寄存器

TIMx控制寄存器1

  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,计数器才会开始计数。

2.2、TIMx从模式控制寄存器

TIMx从模式控制寄存器

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

2.3、TIMx捕获/比较模式寄存器

TIMx捕获/比较模式寄存器1

  模式设置位 OC1M[6:4] 就是对应着通道 1 的模式设置,此部分由 3 位组成,总共可以配置成 8 种模式,这里使用的是翻转功能,所以这 3 位必须设置为 011。通道 2 也是如此,将位 OC2M[14:12] 设置为 011。通道 3 和通道 4 就要设置 TIM1_CCMR2 寄存器的位 OC3M[6:4] 和位 OC4M[14:12]。除此之外,我们还要设置输出比较的预装载使能位,如通道 1 对应输出比较的预装载使能位 3 OC1PE 位置 1,其他通道也要把相应位置 1。

2.4、TIMx捕获/比较使能寄存器

TIMx捕获比较使能寄存器

  该寄存器控制着各个输入输出通道的开关和极性。如果我们想让要让 TIM14 的 CH1 输出 PWM 波,这里我们要使能 CC1E 位,该位是通道 1 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。CC1P 位是设置通道 1 的输出极性。

2.5、TIMx计数器

TIMx计数器

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

2.6、TIMx预分频器

TIMx预分频器

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

2.7、TIMx自动重载寄存器

TIMx自动重载寄存器

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

2.8、TIMx捕获比较寄存器

TIMx捕获比较寄存器1

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

三、定时器对应通道引脚

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

通道名 通道引脚 重映射通道引脚
Channel 1 PA0 PA5/PA15
Channel 2 PA1 PB3
Channel 3 PA2 PB10
Channel 4 PA3 PB11

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

通道名 通道引脚 重映射通道引脚
Channel 1 PA6 PC6/PB4
Channel 2 PA7 PC7/PB5
Channel 3 PB0 PC8
Channel 4 PB1 PC9

【3】、TIM4 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PD12 PB6
Channel 2 PD13 PB7
Channel 3 PD14 PB8
Channel 4 PD15 PB9

【4】、TIM5 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PA0
Channel 2 PA1
Channel 3 PA2
Channel 4 PA3

【5】、TIM9 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PE5 PA2
Channel 2 PE6 PA3

【6】、TIM10 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PF6 PB8

【7】、TIM11 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PF7 PB9

【8】、TIM12 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PB14
Channel 2 PB15

【9】、TIM13 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PF8 PA6

【10】、TIM14 对应通道引脚及其重映射

通道名 通道引脚 重映射通道引脚
Channel 1 PF9 PA7

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

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

【12】、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

四、输出PWM配置步骤

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)

  使能通用定时器的时钟。

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

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

#define __HAL_RCC_TIM11_CLK_ENABLE()    do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM11EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM11EN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)
#define __HAL_RCC_TIM12_CLK_ENABLE()  do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM12EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM12EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_TIM13_CLK_ENABLE()  do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM13EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM13EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_TIM14_CLK_ENABLE()  do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM14EN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM14EN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_TIM2_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM2EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM2EN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_TIM3_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM3EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM3EN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_TIM4_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM4EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM4EN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)

#define __HAL_RCC_TIM10_CLK_ENABLE()    do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM10EN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_TIM10EN);\
                                        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)
#define __HAL_RCC_GPIOE_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOEEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOEEN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)
#define __HAL_RCC_GPIOF_CLK_ENABLE()   do { \
                                       __IO uint32_t tmpreg = 0x00U; \
                                       SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
                                       /* Delay after an RCC peripheral clock enabling */ \
                                       tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
                                       UNUSED(tmpreg); \
                                       } while(0U)

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

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

HAL_StatusTypeDef HAL_TIM_OC_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)

#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 TIM9                ((TIM_TypeDef *) TIM9_BASE)
#define TIM10               ((TIM_TypeDef *) TIM10_BASE)
#define TIM11               ((TIM_TypeDef *) TIM11_BASE)
#define TIM12               ((TIM_TypeDef *) TIM12_BASE)
#define TIM13               ((TIM_TypeDef *) TIM13_BASE)
#define TIM14               ((TIM_TypeDef *) TIM14_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 GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_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_AF1_TIM2          ((uint8_t)0x01)  /* TIM2 Alternate Function mapping */

#define GPIO_AF2_TIM3          ((uint8_t)0x02)  /* TIM3 Alternate Function mapping */
#define GPIO_AF2_TIM4          ((uint8_t)0x02)  /* TIM4 Alternate Function mapping */
#define GPIO_AF2_TIM5          ((uint8_t)0x02)  /* TIM5 Alternate Function mapping */

#define GPIO_AF3_TIM8          ((uint8_t)0x03)  /* TIM8 Alternate Function mapping  */
#define GPIO_AF3_TIM9          ((uint8_t)0x03)  /* TIM9 Alternate Function mapping  */
#define GPIO_AF3_TIM10         ((uint8_t)0x03)  /* TIM10 Alternate Function mapping */
#define GPIO_AF3_TIM11         ((uint8_t)0x03)  /* TIM11 Alternate Function mapping */

#define GPIO_AF9_TIM12         ((uint8_t)0x09)  /* TIM12 Alternate Function mapping */
#define GPIO_AF9_TIM13         ((uint8_t)0x09)  /* TIM13 Alternate Function mapping */
#define GPIO_AF9_TIM14         ((uint8_t)0x09)  /* TIM14 Alternate Function mapping */

4.4、配置输出比较模式

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

HAL_StatusTypeDef HAL_TIM_OC_ConfigChannel(TIM_HandleTypeDef *htim, const 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_TOGGLE                   (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0)                    /*!< Toggle                                 */

  成员变量 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、使能通道预装载

#define __HAL_TIM_ENABLE_OCxPRELOAD(__HANDLE__, __CHANNEL__)    \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCMR1 |= TIM_CCMR1_OC1PE) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCMR1 |= TIM_CCMR1_OC2PE) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCMR2 |= TIM_CCMR2_OC3PE) :\
   ((__HANDLE__)->Instance->CCMR2 |= TIM_CCMR2_OC4PE))

  其中 __HANDLE__ 是定时器句柄,__CHANNEL__ 是定时器的通道。

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

HAL_StatusTypeDef HAL_TIM_OC_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.7、修改捕获比较寄存器的值

#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 波的占空比。

五、程序源码

  定时器输出比较功能初始化函数,内容如下:

TIM_HandleTypeDef g_tim13_handle;

/**
 * @brief 定时器输出比较功能初始化函数
 * 
 * @param htim 定时器句柄
 * @param TIMx 定时器寄存器基地址,可选值: TIMx, x可选范围: 1 ~ 5, 8 ~ 14
 * @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_OC_Init(TIM_HandleTypeDef *htim, TIM_TypeDef *TIMx, uint16_t prescaler, uint16_t period, uint32_t channel, uint32_t polarity, uint32_t pulse)
{
    TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};

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

    TIM_OC_InitStruct.OCMode = TIM_OCMODE_TOGGLE;                               // 输出比较模式翻转功能
    TIM_OC_InitStruct.OCPolarity = polarity;                                    // 输出比较极性
    TIM_OC_InitStruct.Pulse = pulse;                                            // 比较值
    HAL_TIM_OC_ConfigChannel(htim, &TIM_OC_InitStruct, channel);
}

  定时器输出比较模式底层初始化函数,内容如下:

/**
 * @brief 定时器输出比较底层初始化函数
 * 
 * @param htim 定时器句柄
 */
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    if (htim->Instance == TIM13)
    {
        __HAL_RCC_TIM13_CLK_ENABLE();                                           // 使能TIM13的时钟
        __HAL_RCC_GPIOF_CLK_ENABLE();                                           // 使能TIM13的Channel 1对应的GPIO时钟

        GPIO_InitStruct.Pin = GPIO_PIN_8;                                       // TIM13的Channel 1对应的GPIO引脚
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;                                 // 复用功能
        GPIO_InitStruct.Pull = GPIO_NOPULL;                                     // 不使用上下拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;                           // GPIO输出速度
        GPIO_InitStruct.Alternate = GPIO_AF9_TIM13;                             // 复用功能选择
        HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
    }
}

  通用定时器 TIM13 的时钟来自 APB1,当 PPRE1 ≥ 2 分频的时候,通用定时器的时钟为 APB1 时钟的 2 倍, 而 APB1 为 42M, 所以定时器时钟 = 84Mhz。如果我们往预分频寄存器的值写入 83,写入自动重载寄存器的值写入 999。带入 PWM 周期计算公式,可得:

\[T = \frac{2*(arr+1)*((psc+1)}{F_{clk}} = \frac{2 * (83 + 1) * (999 + 1)}{84000000} = 0.002s \]

  由上述式子得到 PWM 周期为 2ms,频率为 500Hz。ARR 值为固定为 1000,所以占空比则固定为 50%。

  main() 函数,内容如下:

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

    TIM_OC_Init(&g_tim13_handle, TIM13, 83, 999, TIM_CHANNEL_1, TIM_OCPOLARITY_HIGH, 500);

    __HAL_TIM_ENABLE_OCxPRELOAD(&g_tim13_handle, TIM_CHANNEL_1);                // 使能通道预装载
    HAL_TIM_OC_Start(&g_tim13_handle, TIM_CHANNEL_1);                           // 启动定时器
    __HAL_TIM_SET_COMPARE(&g_tim13_handle, TIM_CHANNEL_1, 249);                 // 设置捕获比较寄存器的值

    while (1)
    {
        HAL_Delay(100);
    }
  
    return 0;
}
posted @ 2023-12-10 17:20  星光樱梦  阅读(51)  评论(0编辑  收藏  举报