29. 定时器输出指定个数的PWM
一、重复计数器
计数器每次上溢或下溢都能使重复计数器减 1,减到 0 时,再发生一次溢出就会产生更新事件。如果设置 RCR 为 N,更新事件将在 N+1 次溢出时发生。
二、常用的寄存器
2.1、TIM1和TIM8控制寄存器
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,计数器才会开始计数。
2.2、TIM1和TIM8从模式控制寄存器
该寄存器的 SMS[2:0]位,用于从模式选择,其实就是选择计数器输入时钟的来源。比如通用定时器中断例程中我们设置 SMS[2:0]=000,禁止从模式,这样 PSC 预分频器的时钟就直接来自内部时钟(CK_INT),按照我们例程 System_Clock_Init() 函数的配置,频率为 84Mhz(APB1 总线时钟频率的 2 倍)。
2.3、TIM1和TIM8捕获/比较模式寄存器
该寄存器的有些位在不同模式下,功能不一样,我们前面已经说过。比如我们要让 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 的 CH1 输出 PWM 波,这里我们要使能 CC1E 位,该位是通道 1 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。如果我们想要通道 1 的互补通道输出相反的 PWM 波,我们需要把对应的捕获/比较 1 输出使能位CC1E 置 1。CC1P 和 CC1NP 分别是通道 1 输出和通道 1 互补输出的极性设置位。
2.5、TIM1和TIM8计数器
TIM1/TIM8 的计数寄存器都是 16 位有效的,计数模式可以是递增计数模式、递减计数模式和中心对齐计数模式。和基本定时器一样,可以直接写该寄存器设置计数的初始值,也可以读取该寄存器获取计数器值。
2.6、TIM1和TIM8预分频器
定时器的预分频寄存器都是 16 位的,即写入该寄存器的数值范围是 0 到 65535,表示 1 到 65536 分频。比如我们要 8400 分频,就往该寄存器写入 8399。
2.7、TIM1和TIM8自动重载寄存器
在 F4 系列中,TIM2 和 TIM5 的自动重装载寄存器是 32 位的,其他通用定时器自动重载寄存器是低 16 位有效。该寄存器可以由 APRE 位设置是否进行缓冲。计数器的值会和自动重装寄存器影子寄存器进行比较,当两者相等,定时器就会溢出,从而发生更新事件,如果打开了更新中断,还会发生更新中断。
2.8、TIM1和TIM8重复计数器寄存器
重复计数器寄存器用于设置重复计数器值,因为它具有影子寄存器,所以它本身只是起缓冲作用。当更新事件发生时,该寄存器的值会转移到其影子寄存器中,从而真正起作用。
该寄存器的 REP[7:0] 位是低 8 位有效,即最大值 255。因为这个寄存器只是起缓冲作用,如果大家对该寄存器写入值后,想要立即生效,可以通过对 UG 位写 1,产生软件更新事件。
2.9、TIM1和TIM8捕获/比较寄存器
捕获/比较寄存器(TIMx_CCR1),该寄存器只有 1 个,对应通道 CH1。我们使用的是通道 1。在输出模式下,捕获/比较寄存器影子寄存器的值与 CNT 的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的占空比了。
2.10、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);
其中,htim 是 TIM_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);
其中,htim 是 TIM_HandleTypeDef 结构体类型指针变量(亦称定时器句柄)。
sConfig 是 TIM_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);
其中,htim 是 TIM_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。根据定时器溢出公式由公式得:
由频率是周期的倒数关系得到 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;
}