23. 定时器的PWM输出模式

一、什么是PWM

  脉冲宽度调制(PWM),是英文 “Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。我们可以让定时器产生PWM,在计数器频率固定时,PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定。

二、PWM生成原理

  定时器产生 PWM 的方式有许多种,下面我们以边沿对齐模式(即递增计数模式/递减计数模式)为例,PWM 模式 1 或者 PWM 模式 2 产生 PWM 的示意图,如下图所示:

产生PWM示意图

  上图中,定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时。当 CNT<CCRx 时,IO 输出低电平(逻辑 0);当 CNT>=CCRx 时,IO 输出高电平(逻辑 1);当 CNT=ARR 时,定时器溢出,CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就是 PWM 输出的原理。

产生PWM示意图

  STM32F407 的定时器除了 TIM6 和 TIM7,其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出!

三、捕获/比较通道的输出部分

捕获比较通道1主电路之输出比较功能部分

  首先程序员写 CCR1 寄存器,即写入比较值。这个比较值需要转移到对应的捕获/比较影子寄存器后才会真正生效。什么条件下才能转移?图中可以看到 compare_transfer 旁边的与门,需要满足三个条件:CCR1 不在写入操作期间、CC1S[1:0] = 0 配置为输出、OC1PE 位置 0(或者 OC1PE 位置 1,并且需要发生更新事件,这个更新事件可以软件产生或者硬件产生)。当 CCR1 寄存器的值转移到其影子寄存器后,新的值就会和计数器的值进行比较,它们的比较结果将会通过第⑥部分影响定时器的输出。

通道1输出阶段

  上图中,可以看到输出模式控制器,由 OC1M[2:0] 位配置输出比较模式。F4 系列有 8 种输出比较模式之多。oc1ref 是输出参考信号,高电平有效,为高电平时称之为有效电平,为低电平时称之为无效电平。它的高低电平受到三个方面的影响:OC1M[3:0]位配置的输出比较模式、第 ⑤ 部分比较器的比较结果、还有就是 OC1CE 位配置的 ETRF 信号。ETRF 信号可以将 oc1ref 电平强制清零,该信号来自 IO 外部。

  一般来说,当计数器的值和捕获/比较寄存器的值相等时,输出参考信号 oc1ref 的极性就会根据我们选择的输出比较模式而改变。如果开启了比较中断,还会发生比较中断。

  • CC1P 位用于选择通道输出极性。
  • CC1E 位置 1 使能通道输出。
  • OC1 信号就会从 TIMx_CH1 输出到 IO 端口,再到 IO 外部。

四、常用寄存器

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

4.2、TIMx从模式控制寄存器

TIMx从模式控制寄存器

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

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

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

  该寄存器的有些位在不同模式下,功能不一样。比如我们要让 TIM14 的 CH1 输出 PWM 波为例进行介绍,该寄存器的模式设置位 OC1M[2:0] 就是对应着通道 1 的模式设置,此部分由 3 位组成。总共可以配置成 8 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1 和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反。

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

TIMx捕获比较使能寄存器

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

4.5、TIMx计数器

TIMx计数器

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

4.6、TIMx预分频器

TIMx预分频器

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

4.7、TIMx自动重载寄存器

TIMx自动重载寄存器

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

4.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配置步骤

6.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)

6.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)

#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;

6.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 */

6.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 表示 超时

6.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))

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

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 表示 超时

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

七、原理图

LED灯模块

LED灯模块引脚接线图

  通过原理图分析可以得到,LED 1 的阴极接到 TIM14 的通道 1 引脚。因为 LED0 是低电平有效,因此,我们需要将定时器 14 的通道 1 的输出比较极性设置为低电平。

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

\[T_{out} = \frac{(arr + 1) * (psc + 1)}{F_{clk}} = \frac{(499 + 1) * (83 + 1)}{84000000} = 0.0005s \]

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

  因为 LED1 是低电平有效,所以我们在定时器初始化函数中设置了输出比较极性为低,那么当比较值固定为 200 时,\(占空比 = \frac{((arr + 1) - CCR1)}{(arr + 1)} = \frac{((499 + 1) - 200)}{499 + 1} = 60\%\)。其中 arr 是写入自动重载寄存器(TIMx_ARR)的值,CCR1 就是写入捕获/比较寄存器 1(TIMx_CCR1)的值。占空比是指在一个周期内,高电平时间相对于总时间所占的比例。

八、程序源码

  定时器的 PWM 功能初始化函数,内容如下:

TIM_HandleTypeDef g_tim14_handle;

/**
 * @brief 定时器PWM功能初始化函数
 * 
 * @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_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 == TIM14)
    {
        __HAL_RCC_TIM14_CLK_ENABLE();                                           // 使能TIM14的时钟
        __HAL_RCC_GPIOF_CLK_ENABLE();                                           // 使能TIM14的Channel 1对应的GPIO时钟

        GPIO_InitStruct.Pin = GPIO_PIN_9;                                       // TIM14的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_TIM14;                             // 复用功能选择
        HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
    } 
}

  main() 函数,内容如下:

int main(void)
{
    uint8_t direction = 1;
    uint16_t value = 0;

    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    TIM_PWM_Init(&g_tim14_handle, TIM14, 83, 499, TIM_CHANNEL_1, TIM_OCPOLARITY_LOW, 300);

    HAL_TIM_PWM_Start(&g_tim14_handle, TIM_CHANNEL_1);                          // 使能输出并启动计数器

    while (1)
    {
        HAL_Delay(10);
        direction ? ++value : --value;
        direction = (value >= 300) ? 0 : direction;
        direction = (value <= 0) ? 1 : direction;

        __HAL_TIM_SET_COMPARE(&g_tim14_handle, TIM_CHANNEL_1, value);           // 修改占空比
    }
  
    return 0;
}
posted @ 2023-12-04 18:20  星光映梦  阅读(580)  评论(0编辑  收藏  举报