一、PWM
定时器产生PWM:在计数器频率固定时,PWM 频率由自动重载寄存器(TIMx_ARR)的值决定,其占空比由捕获/比较寄存器(TIMx_CCRx)的值决定
定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时。当 CNT=CCRx 时,IO 输出高电平(逻辑 1);当 CNT=ARR 时,定时器溢出,CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改 变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就 是 PWM 输出的原理。
此外根据定时器工作方式还有如下的pwm方式:
STM32F407 的定时器除了 TIM6 和 TIM7,其他的定时器都可以用来产生 PWM 输出。其 中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产 生多达 4 路的 PWM 输出。
图片来自:(3条消息) stm32定时器概述_stm32的tim3有几个同道_haha690的博客-CSDN博客
二、寄存器
除了上一小节介绍的寄存器外,还会用到 3 个寄存器,来控制 PWM。这三个寄存器分别是:捕获/比较模式寄存器 (TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。
2.1捕获/比较模式寄存器 1(TIMx_CCMR1)
TIM10/TIM11/TIM13/TIM14 的捕获/比较模式寄存器,该寄存器只有 1 个:TIMx_CCMR1, TIMx_CCMR1 控制 CH1 和 CH2。TIMx_CCMR1 寄存器描述如图 所示:
上面寄存器只有0-7位有用是因为TIM10/TIM11/TIM13/TIM14 的捕获/比较模式寄存器只有一个CH1通道,所以高位8-15没有用,看下面的寄存器:
这个寄存器是TIM9/12的寄存器,因为他们有两个通道,所以他们的寄存器会有16位。
对于拥有4个通道的寄存器,除了有TIMx_CCMR1寄存器还有TIMx_CCMR2寄存器,ccmr1控制通道1和2,ccmr2控制3与4通道。
寄存器的有些位在不同模式下,功能不一样,我们现在只用到输出比较。关于该寄存器的详细说明,请参考《STM32F4xx 参考手册_V4(中文版).pdf》 第 476 页,16.4.7 节。比如我们要让 TIM14 的 CH1 输出 PWM 波为例进行介绍,该寄存器的模 式设置位 OC1M[2:0]就是对应着通道 1 的模式设置,此部分由 3 位组成。总共可以配置成 8 种 模式,使用 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1 和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反。
2.2、捕获/比较使能寄存器(TIMx_CCER)
TIM10/TIM11/TIM13/TIM14 的捕获/比较使能寄存器,该寄存器控制着各个输入输出通道 的开关和极性。TIMx_CCER 寄存器描述如图所示:
TIMx_CCER 寄存器 该寄存器比较简单,要让 TIM14 的 CH1 输出 PWM 波,这里我们要使能 CC1E 位,该位 是通道 1 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。CC1P 位是设置通 道 1 的输出极性,默认设置 0。
2.3、捕获/比较寄存器 1(TIMx_CCR1)
捕获/比较寄存器(TIMx_CCR1),该寄存器只有 1 个,对应通道 CH1。我们使用的是通道 1,所以来看看 TIMx_CCR1 寄存器,描述如图所示:
在输出模式下,捕获/比较寄存器影子寄存器的值与 CNT 的值比较,根据比较结果产生相 应动作,利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的占空比了。
注:对于有更多通道的定时器,比如tim9,如果要用CH1通道就配置ccr1寄存器,如果用CH2通道就配置ccr2寄存器。
三、相关函数
3.1. HAL_TIM_PWM_Init 函数
定时器的 PWM 输出模式初始化函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
该函数实现的功能以及使用方法和前面配置寄存器的HAL_TIM_Base_Init基本一样,作用都是初始化定时器的 ARR 和 PSC 等参数。
但是 HAL_TIM_Base_Init 函数针对 PWM 输出定义了单独的 MSP 回调函数 HAL_TIM_PWM_MspInit,所以当我们调用 HAL_TIM_PWM_Init 进行 PWM 初始化之后,该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。
HAL_TIM_Base_Init 初始化定时器它内部调用的回调函数是 HAL_TIM_Base_MspInit,这里大家注意区分。
3.2、HAL_TIM_PWM_ConfigChannel 函数
定时器的 PWM 通道设置初始化函数。其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
TIM_OC_InitTypeDef *sConfig,
uint32_t Channel);
形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,用于配置定时器基本参数。
形参 2 是 TIM_OC_InitTypeDef 结构体类型指针变量,用于配置定时器的输出比较参数。
typedef struct
{
uint32_t OCMode; /* 输出比较模式选择,寄存器的时候说过了,共 8 种模式 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 选择空闲状态下的非工作状态(OC1 输出) */
uint32_t OCNIdleState; /* 设置空闲状态下的非工作状态(OC1N 输出) */
} TIM_OC_InitTypeDef;
成员变量 OCMode 用来设置模式,这里设置为 PWM 模式 1。
成员变量 Pulse 用来设置捕获比较值。
成员变量 TIM_OCPolarity 用来设置输出极性。
其他成员 TIM_OutputNState,TIM_OCNPolarity,TIM_OCIdleState 和 TIM_OCNIdleState 后面 用到再介绍。
形参 3 是定时器通道,范围:TIM_CHANNEL_1 到 TIM_CHANNEL_4。比如定时器 14 只 有 2 个通道,那选择范围就只有 TIM_CHANNEL_1~2,所以要根据具体情况选择。
3.3、HAL_TIM_PWM_Start 函数
定时器的 PWM 输出启动函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
对于单独使能定时器的方法,在上一章定时器实验我们已经讲解。实际上,HAL 库也同样 提供了单独使能定时器的输出通道函数,函数为:
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState);
HAL_TIM_PWM_Start 函数内部也调用了该函数。
3.4、HAL_TIM_ConfigClockSource 函数
配置定时器时钟源函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim,
TIM_ClockConfigTypeDef *sClockSourceConfig);
形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。
形参 2 是 TIM_ClockConfigTypeDef 结构体类型指针变量,用于配置定时器时钟源参数。 TIM_ClockConfigTypeDef 定义如下:
typedef struct
{
uint32_t ClockSource; /* 时钟源 */
uint32_t ClockPolarity; /* 时钟极性 */
uint32_t ClockPrescaler; /* 定时器预分频器 */
uint32_t ClockFilter; /* 时钟过滤器 */
} TIM_ClockConfigTypeDef;
注: 该函数主要配置 TIMx_SMCR 寄存器。默认情况下,定时器的时钟源是内部时钟。本次编程也是使用内部时钟的,所以我们不用对时钟源初始化,默认即可。
这里只是让大家知道有 这个函数可以设定时器的时钟源。比如用 HAL_TIM_ConfigClockSource 初始化选择内部时钟, 方法如下:
TIM_HandleTypeDef timx_handle; /* 定时器 x 句柄 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; /* 选择内部时钟 */
HAL_TIM_ConfigClockSource(&timx_handle, &sClockSourceConfig);
后面的定时器初始化凡是用到内部时钟都不需要去初始化,系统默认即可。
3.5、定时器 PWM 输出模式配置步骤
1) 开启 TIMx 和通道输出的 GPIO 时钟,配置该 IO 口的复用功能输出。
首先开启 TIMx 的时钟,然后配置 GPIO 为复用功能输出。本实验我们默认用到定时器 14 通道 1,对应 IO 是 PF9,它们的时钟开启方法如下:
__HAL_RCC_TIM14_CLK_ENABLE(); /* 使能定时器 14 */
__HAL_RCC_GPIOF_CLK_ENABLE(); /* 开启 GPIOF 时钟 */
IO 口复用功能是通过函数 HAL_GPIO_Init 来配置的。
2) 初始化 TIMx,设置 TIMx 的 ARR 和 PSC 等参数
使用定时器的 PWM 输出功能时,通过 HAL_TIM_PWM_Init 函数初始化定时器参数。 注意:该函数会调用:HAL_TIM_PWM_MspInit函数。
3) 设置 TIMx_CHy 的 PWM 模式,输出比较极性,比较值等参数
HAL_TIM_PWM_ConfigChannel 函数来设置定时器为 PWM1 模式或者 PWM2 模式,根据需求设置输出比较的极性,设置比较值(控制占空比)等。
4) 使能 TIMx,使能 TIMx 的 CHy 输出
在 HAL 库中,通过调用 HAL_TIM_PWM_Start 函数来使能 TIMx 的某个通道输出 PWM。
5) 修改 TIM14_CCR1 来控制占空比
在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我 们通过修改比较值来控制 PWM 的输出占空比。HAL 库中提供一个修改占空比的宏定义:
__HAL_TIM_SET_COMPARE (__HANDLE__, __CHANNEL__, __COMPARE__)
__HANDLE__是 TIM_HandleTypeDef 结构体类型指针变量,
__CHANNEL__对应 PWM 的输出通道,
__COMPARE__则是要写到捕获/比较寄存器(TIMx_CCR1/2/3/4)的值。通过修改这个值就可以实现不同pwm
实际上该宏定义最终还是往对应的捕获/比较寄存器写入比较值来控制 PWM 波的占空比,如下解析: 比如我们要修改定时器 14 通道 1 的输出比较值(控制占空比),寄存器操作方法:
TIM14->CCR1 = ledrpwmval; /* ledrpwmval 是比较值,并且动态变化的,
所以我们要周期性调用这条语句,以到达及时修改 PWM 的占空比 */
__HAL_TIM_SET_COMPARE 这个宏定义函数最终也是调用这个寄存器操作的,所以说使用 HAL 库的函数其实就是间接操作寄存器的。
四、实战
复用GPIOF_PIN_9的tim14功能,通过控制占空比来控制灯的亮度。
#include "stm32f4xx.h"
#include "core_cm4.h"
#include "stm32f4xx_hal.h"
#include "stdio.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
TIM_HandleTypeDef g_timx_pwm_chy_handle; /* PWM句柄 */
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ LED的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
void led_init(void) /* 对LED串口进行初始化*/
{
GPIO_InitTypeDef gpio_init_struct; /* 定义结构体 */
gpio_init_struct.Pin = GPIO_PIN_10; /* LED引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOF, &gpio_init_struct); /* 初始化LED引脚 */
}
/* 通用定时器TIM14 通道1 PWM输出 初始化函数(使用PWM模式1)
用的APB1,当PPRE1 ≥ 2分频的时候,通用定时器的时钟为APB1时钟的2倍, 而APB1为42M,
所以定时器时钟 = 84Mhz */
void gtim_tim14_pwm_chy_init(uint16_t psc,uint16_t arr)
{
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器输出句柄 */
g_timx_pwm_chy_handle.Instance = TIM14; /* 定时器x */
g_timx_pwm_chy_handle.Init.Prescaler = psc; /* 预分频系数 */
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
g_timx_pwm_chy_handle.Init.Period = arr; /* 自动重装载值 */
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle); /* 初始化PWM */
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1; /* 模式选择PWM1 */
timx_oc_pwm_chy.Pulse = arr / 2; /* 设置比较值,此值用来确定占空比 */
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; /* 输出比较极性为低 */
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_1); /* 配置TIM14通道1 */
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_1); /* 开启对应PWM通道 */
}
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓此函数会被HAL_TIM_PWM_Init()调用↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM14)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_TIM13_CLK_ENABLE(); /* 使能定时器时钟 */
__HAL_RCC_GPIOF_CLK_ENABLE(); /* 开启CPIO时钟 */
gpio_init_struct.Pin = GPIO_PIN_9; /* 通道y的CPIO口 */
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
gpio_init_struct.Alternate = GPIO_AF9_TIM14; /* IO口REMAP设置, 是否必要查看头文件配置的说明! */
HAL_GPIO_Init(GPIOF, &gpio_init_struct);
}
}
int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
gtim_tim14_pwm_chy_init(84-1, 500-1); /* 84 000 000 / 84 = 1 000 000 1Mhz的计数频率,2Khz的PWM */
while (1)
{
delay_ms(10);
if (dir)ledrpwmval++; /* dir==1 ledrpwmval递增 */
else ledrpwmval--; /* dir==0 ledrpwmval递减 */
if (ledrpwmval > 300)dir = 0; /* ledrpwmval到达300后,方向为递减 */
if (ledrpwmval == 0)dir = 1; /* ledrpwmval递减到0后,方向改为递增 */
/* 修改比较值控制占空比 */
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_1, ledrpwmval);
}
}
五、特别讲解
定时器会设计到很多参数,什么预分频,计时器频率,pwm频率,占空比,有时候会很懵,所以特此写此模块对一些问题进行讲解。
5.1、定时器的句柄
定时器的参数初始化句柄用的TIM_HandleTypeDef类型,其中有个参数为TIM_TypeDef 类型的参数:
当我们使用TIMx定时器时,就会直接设定Instance=TIMx来完成初始化,TIM_TypeDef 如下:
从定义可以看出,其实这个参数就是直接指向不同定时器的地址,在定义中可以看见定时器的各种寄存器。也就是说,其实我们可以直接对TIM_TypeDef *Instance参数进行各种参数配置,就可以对定时器完成各种初始化,但是这样会比较麻烦,所以在TIM_HandleTypeDef还有其他的参数来进行参数配置。
5.2、定时器的频率与定时
时钟频率的计算公式如下:
stm32f407的apb1为42MHZ,但是我们将APB1倍频了,所以CK_INT 为 84MHz,即上式中第一个fck_psc=84MHz。如果使用的是APB2,则APB2的频率为84MHz,后面的计算和apb1一模一样。
然后我们设定 预分频寄存器psc 也就是设定 PCS[15:0]+1 如果我们设定为8400,那么 fCK_CNT=84MHz/8400=10KHz。 这样就得到计数器的计数频率为 10KHz,即计数器 1 秒钟可以计 10000 个数。
如果我们需要 500ms 的周期,所以就让计数器计数 5000 个数就能满足要求,即需要设置自动重载寄存器arr的值 为 4999。
当定时器工作时,CNT即计数定时器就会以10KHz的频率去递增,当CNT=自动重载寄存器arr时就会发生更新事件,这时候CNT重新计时,然后以此往复。如果开启了中断,还会发生中断。
如果需要中途修改arr的值,需要利用函数
__HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__)
参数1为定时器句柄,参数2为要重新设定的arr值,arr值设定,最好是在中断里面更新
#define __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) \
do{ \
(__HANDLE__)->Instance->ARR = (__AUTORELOAD__); \
(__HANDLE__)->Init.Period = (__AUTORELOAD__); \
} while(0)
5.3、定时器频率、PWM频率与占空比。
定时器频率上小节已经解释清楚了,这里主要解释定时器频率、PWM频率与占空比它们之间的关系。
gtim_tim14_pwm_chy_init(84-1, 500-1);
如上,在实战节章里面,我们将psc设为了84-1,所以有84 000 000 / 84 = 1 000 000 Mhz的计数频率,后面将arr设为了500-1也就是pwm频率为1 000 000 /500=2khz,也就是一秒内可以发送2k次pwm信号。
我们知道,占空比是比较计数值 CNT与CCRx 之间的值进行改变的,CCRx是通过CCRx寄存器进行改变的。
例子:我们现在是将arr设为了500-1,也就是计数器cnt每计数500次就会清零然后重新计数。我们设定ccrx的参数就能实现不同的占空比。比如我想实现0.8的占空比,那我就应该将ccrx参数设定为100,这样(500-100)/500=0.8。
问:为什么要有pwm频率,pwm频率直接用定时器频率不就行了?
答:一些元器件有规定的pwm频率,比如舵机就是要需要接受特定频率的PWM才可以控制。
在对pwm参数设定时用的句柄也是 TIM_HandleTypeDef句柄,但是pwm在参数设定的时候多了一个TIM_OC_InitTypeDef句柄,这个句柄是用来设定定时器的模式、占空比和极性的。如下图:
timx_oc_pwm_chy.Pulse = arr / 2; 就是对pwm的占空比进行设定的。但是我们在5.1讲了,在TIM_TypeDef *Instance中可以直接对定时器的寄存器进行设定:
注:这里在定义timx_oc_pwm_chy时直接是timx_oc_pwm_chy = {0}; 因为在此句柄里面还有很多其他的参数,但是我们这里只用到了三个参数,其他参数需要进行一下初始化,所以直接让其等于0。
我们设定不同占空比就是要改变CCRx的值实现的,所以我们也可以直接修改Instance->CCRx。这也就是HAL提供的,修改pwm占空比函数的原理:
#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__)))
文章来源:https://www.toymoban.com/news/detail-721040.html
我们修改ledrpwmval这个值就是在修改其占空比。文章来源地址https://www.toymoban.com/news/detail-721040.html
到了这里,关于stm32f407关于通用定时器各种函数——PWM(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!