目录
介绍
PWM占空比
框图
输出比较
通道 1 输出比较功能为例
PWM 输出模式
PWM 边沿对齐模式
hal库代码
标准库代码
介绍
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微
处理器的数字输出来对模拟电路进行控制的的技术。
高级控制定时器 (TIM1 和 TIM8) 和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。高级控制定时器比通用定时器增加了可编程死区互补输出、重复计
数器、带刹车 (断路) 功能,这些功能都是针对工业电机控制方面。主要常用的输入捕获和输出比较功能。
如果把灯亮看作100%,灯灭看作0%,要实现50%的亮度,可以在某个单位时间里亮灯50%时间、灭灯50%时间,只要这个单位时间够小,由于人眼具有视觉暂留效应,就会感觉整个灯是一直亮着,且亮度只要原来的一半。PWM实质就是GPIO不断翻转输出高、低电平,这个效果可以写代码控制GPIO产生,但这样就会占用CPU, CPU就不方便做其它事情。此时可以利用定时器,设置好翻转时间,让其自动控制GPIO翻转,无需CPU再参与。
PWM占空比
首先定时器从0开始计数,在0~t1时间段, TIMx_CNT<TIMx_CCR1,输出低电平;在t1~t2时间段, TIMx_CNT>TIMx_CCR1,输出高电平; t2时, TIMx_CNT=TIMx_ARR计数器溢出,重新从0开始,如此循环
在一个周期内,高电平占整个信号周期的百分比,称之为占空比
框图
输出比较
输出比较就是通过定时器的外部引脚对外输出控制信号,有冻结、将通道 X(x=1,2,3,4)设置为匹配时输出有效电平、将通道 X 设置为匹配时输出无效电平、翻转、强制变为无效电平、强制变为有效电平、 PWM1 和 PWM2 这八种模式,具体使用哪种模式由寄存器 CCMRx 的位 OCxM[2:0]配置。其中 PWM 模式是输出比较中使用的最多。
通道 1 输出比较功能为例
灰色阴影部分是输入捕获功能部分,右边没有阴影部分就是输出比较功能部分。首先程序员写 CCR1 寄存器,即写入比较值。这个比较值需要转移到对应的捕获/比较影子寄存器后才会真正生效。什么条件下才能转移?图阴影中可以看到 compare_transfer 旁边的与门,需要满足三个条件: CCR1 不在写入操作期间、 CC1S[1:0] = 0 配置为输出、 OC1PE 位置0(或者 OC1PE 位置 1,并且需要发生更新事件,这个更新事件可以软件产生或者硬件产生)。
当 CCR1 寄存器的值转移到其影子寄存器后,新的值就会和计数器的值进行比较,它们的
比较结果将会通过输出控制部分影响定时器的输出。
PWM 输出模式
PWM 输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定
PWM 边沿对齐模式
在递增计数模式下,计数器从 0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0开始计数并生成计数器上溢事件
在边沿对齐模式下,计数器 CNT 只工作在一种模式,递增或者递减模式。这里我们以 CNT 工作在递增模式为例,在中, ARR=8, CCR=4, CNT 从 0 开始计数,当 CNT<CCR 的值时, OCxREF为有效的高电平,于此同时,比较中断寄存器 CCxIF 置位。当 CCR<=CNT<=ARR 时, OCxREF为无效的低电平。然后 CNT 又从 0 开始计数并生成计数器上溢事件,以此循环往复。
hal库代码
uint8_t led = 0;
TIM_HandleTypeDef g_pwm;
/*计数器CNT从0到CCR, 输出低电平,LED灯亮
*计数器CNT从CCR到ARR,输出高电平, LED灯灭
*CCR值越小, 占空比越大,灯越暗, CCR值越大,占空比越小,灯越亮, CCR值与亮度成正比
*定时器时钟频率为72MHz / 360 = 200kHz。
*计算得到每次计数的时间为1 / 200kHz = 5us。
*计算定时器的溢出时间可以使用下面的公式:
*Tout = ((ARR + 1) / TIM_CLK)= ((2000) / 200000) = 0.01秒 = 10毫秒
*/
/*工作参数*/
void tim_pwm_init(uint32_t prescaler, uint32_t period)
{
TIM_OC_InitTypeDef pwm_tim;
TIM_ClockConfigTypeDef tim_clk_conf;
/*72MHz 经过 360 分频后,定时器时钟为 200KHz,即计数器每间隔 5us 计数一次,从 0 计数到 ARR,经历 10ms*/
g_pwm.Instance = TIM2;
g_pwm.Init.Prescaler = prescaler;/*分频系数 360-1*/
g_pwm.Init.Period = period;/*自动重装载值 ARR=2000-1*/
g_pwm.Init.CounterMode = TIM_COUNTERMODE_UP;/*向上计数*/
g_pwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;/*定时器时钟不从 HCLK 分频*/
g_pwm.Init.AutoReloadPreload = TIM_AUTOMATICOUTPUT_DISABLE;/*不自动重新装载*/
HAL_TIM_PWM_Init(&g_pwm);/*初始化pwm*/
tim_clk_conf.ClockSource = TIM_CLOCKSOURCE_INTERNAL;/*选用内部时钟作为定时器时钟源*/
HAL_TIM_ConfigClockSource(&g_pwm ,&tim_clk_conf);
/*pwm配置*/
pwm_tim.OCMode = TIM_OCMODE_PWM1;/*模式1*/
pwm_tim.Pulse = led; /*比较值.占空比一半,后面代码再修改*/
pwm_tim.OCPolarity = TIM_OCPOLARITY_LOW;/*极性,LED灯对应低电平有效*/
pwm_tim.OCFastMode = TIM_OCFAST_DISABLE;/*输出比较快速使能禁止(仅在 PWM1 和 PWM2 可设置*/
HAL_TIM_PWM_ConfigChannel(&g_pwm, &pwm_tim, TIM_CHANNEL_2);/*通道2*/
HAL_TIM_PWM_Start(&g_pwm, TIM_CHANNEL_2);
/*启动中断*/
HAL_TIM_PWM_Start_IT(&g_pwm ,TIM_CHANNEL_2 );
}
/*msp初始化*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
GPIO_InitTypeDef g_led;
__HAL_RCC_GPIOA_CLK_ENABLE();/*使能gpio时钟*/
__HAL_RCC_TIM2_CLK_ENABLE();/*使能tim2时钟*/
g_led.Pin = GPIO_PIN_1;
g_led.Mode = GPIO_MODE_AF_PP;/*复用推挽*/
g_led.Pull = GPIO_PULLUP;
g_led.Speed = GPIO_SPEED_FREQ_HIGH;
/*初始化灯*/
HAL_GPIO_Init(GPIOA,&g_led);
HAL_NVIC_SetPriority(TIM2_IRQn,0,0);/*配置定时器中断优先级*/
HAL_NVIC_EnableIRQ(TIM2_IRQn);/*使能 TIM2 中断*/
}
}
/*中断函数*/
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_pwm);
}
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
/*修改对应通道的比较寄存器 CCR 的值(占空比)*/
__HAL_TIM_SET_COMPARE(&g_pwm , TIM_CHANNEL_2 , led*2000/255);
}
}
/*led呼吸灯*/
void pwm_led(void)
{
uint8_t flag = 0;
/*波形0到255递增,255到0递减*/
if(!flag)
{
led=led+1;/*递增*/
if(led == 255)
flag =1;
}
else{
led=led-1;/*递减*/
if(led == 0)
flag =0;
}
HAL_Delay(4);
}
标准库代码
void pwm_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;/*复用推挽*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
/*使用内部时钟*/
TIM_InternalClockConfig(TIM2);
/*初始化定时器*/
/*PWM频率: frq = CK_PSC/(PSC + 1)/(ARR+1)= 72MHZ/ = 70Mhz/(720-1+1)/(100-1+1) = 1000
*PWM占空比:duty = CCR/(ARR+1) = 50/(100-1+1) = 50%
*PWM分辨率:reso = 1/(ARR + 1) = 1/(100-1+1) = 1%
*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;/*向上计数*/
TIM_TimeBaseInitStruct.TIM_Period = 100-1;/*arr自动重装值*/
TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1;/*psc预分频值*/
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;/*重复计数器,高级定时器才有,这里给0*/
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);/*初始化TIM_OCInitStruct*/
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; /*输出比较模式*/
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCPolarity_High; /*输出比较的极性*/
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;/*使能输出*/
TIM_OCInitStruct.TIM_Pulse = 0;/*ccr捕获比较寄存器值,后面设置*/
TIM_OC2Init(TIM2, &TIM_OCInitStruct);/*使用通道*/
/*开启Tim*/
TIM_Cmd(TIM2,ENABLE);
}
void pwm_set_ccr(void)
{
uint8_t i;
for(i=0;i<=100;i++)/*从0到100呼吸变亮*/
{
TIM_SetCompare2(TIM2,i);/*设置定时器通道的捕获比较寄存器(CCR1)的值*/
Delay_ms(10);
}
for(i=0;i<=100;i++)/*从0到100呼吸变暗*/
{
TIM_SetCompare2(TIM2,100-i);/*设置ccr的值*/
Delay_ms(10);
}
}