关联:STM32总结超全笔记【秋招自用】
四.STM32定时器(重中之重中之重)
【前言】
我觉得在冗长的理论之前,我们首先要对定时器的输出比较功能做一个理解。
【问】输出比较用来做什么?应用场景是什么??
输出比较,就是可以控制定时器,去比较 CNT 和 CCR 寄存器值的关系,来对输出的电平进⾏置1、置0或翻转的操作,⽤于输出⼀定频率和占空比的 PWM 波形。
【问】什么是PWM波?
PWM全称为脉冲宽度调制。
PWM波输出原理:
当 CNT<CCRx 时, IO 输出低电平(逻辑 0 );当 CNT>=CCRx 时, IO 输出高电平(逻辑 1 );当 CNT=ARR 时,定时器溢出, CNT 的值被清零,然后继续递增,依次循环;
占空比:高电平所占时间 / 周期时间
占空比 = CCR / (ARR + 1)频率 = CK_PSC / (PSC + 1) / (ARR + 1)
【PWM的应用场景】
【问】我们已经知道了输出比较,就是通过cnt和arr的比较,输出高低电平,输出PWM波,那么PWM波有什么用???
1.PWM输出呼吸灯
之前学完了最简单的基本定时器,我们来学习通用定时器。
首先人眼对80Hz以上的刷新频率是感受不到闪烁的。
我们来分析一下比如说:
- 频率100Hz的场景:10毫秒内,5毫秒打开,5毫秒关闭,(占空比50%)此时常量。
- 频率1Hz的场景:1秒内,0.5秒打开,0.5秒关闭,此时很明显的闪烁。
- 频率100Hz的场景:10毫秒内,9毫秒打开,1毫秒关闭,(占空比90%)此时亮度比第一个例子亮。
所以:频率相同,占空比越大,灯越亮。
改变占空比,即可改变灯的亮度,实现呼吸灯。
2.PWM控制电机转速
直流电机中,电机转速是周期内输出的平均电压值,那么占空比越大,输出电压越大,速度越大。
改变占空比,即可改变电机转速。
3.PWM控制舵机
舵机频率一般为50Hz,相当于20毫秒的脉冲,脉冲的高电平部分范围是0.5ms到2.5ms之间。如果是180度的舵机,那么:
0.5ms对应0度
1ms对应45度
以此类推,2.5ms对应180度
---------------------------------------------------------------------------------------------------------------------------------
有了这些知识铺垫后,我们开始定时器输出比较的学习。
之前学完了最简单的基本定时器,我们来学习通用定时器。
【通用定时器】
我们着重看一下输出比较这部分框图:
【输出比较】
说白了就是先初始化定时器模块和输出比较模块,然后通过控制CCR的值,改变灯的亮度或者是电机的速度。
那么例程就写一个控制直流电机转速,为后面的STM32小车做铺垫。
【例程6】输出PWM波控制电机转速
首先看一下硬件连接:
因为直流电机属于大功率器件,单片机引脚输出电压太小无法直接驱动,所以需要一个电机驱动模块,这里用TB6612。
【PWM初始化】
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
【问】为什么PA2被设置为了复用推挽输出?
因为PA2接的是PWMA,由内部外设控制的时候,需要设置为复用推挽输出。
后面都是比较基础的设置了。
【电机驱动模块的GPIO初始化】
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PWM_Init(); //初始化直流电机的底层PWM
}
【设置电机速度的函数】
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}
这里是对TIM_SetCompare3做一个封装。
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0) //如果设置正转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转
PWM_SetCompare3(Speed); //PWM设置为速度值
}
else //否则,即设置反转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA5置高电平,设置方向为反转
PWM_SetCompare3(-Speed); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}
此函数就是根据TB6612的数据手册编写的(所以查Datasheet非常重要)
此时在主函数中利用封装好的Motor_SetSpeed函数即可控制电机速度了。