使用的单片机机型为STM32F103C8T6
文章目录
PWM
对于5V电路来说,输出只有高电平5V和低电平0V,控制LED灯就是点亮和熄灭,但如果想要控制其亮度呢?这就需要PWM
PWM
PWM (Pulse Width Modulation) 脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常用于电机控速等领域
拿LED灯举例
当输出高电平时,LED灯点亮;当输出低电平时,LED灯熄灭。但如果在一定周期内,比如1ms内,前100us高电平,后900us低电平,那么在人眼看来,LED灯就是很低的亮度。随着高电平所占时间增多,LED亮度就会增大
换成电机,由于惯性,从高电平转为低电平,电机并不会立刻停止,同样在较短时间内分配高低电平的比率就可以实现不同档位的电机
PWM参数
- Ts:周期,如1ms
- Ton:高电平时间,如400us
- Toff:低电平时间,如600us
频率 = 1 / Ts = 1KHz
占空比 :高电平时间占周期的比率。Ton / Ts = 40%
分辨率:1 / (计数器最大值 + 1)
输出比较
定时器输出比较模块如下
OC (Output Compare) 输出比较
通过比较 CNT 和 CCR 寄存器值的关系,对输出电平置1、置0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形
通用定时器和高级定时器都拥有4个输出比较通道,如上图的 TIMx_CH1 ~ TIMx_CH4
高级定时器的前3个通道额外拥有死去生成和互补输出的功能
输出部分电路如下:
通过 CNT 和 CCR比较控制输出使能电路
例如:当CNT < CRR 时,输出高电平,CNT >= CRR 时输出低电平。
配合时基单元 (参看【STM32】定时中断),即可输出PWM。
如定时器频率为1MHz,即每1微秒CNT计数加一,设置重装值ARR = 100,总周期为100us,比较器CRR = 40。
CNT计数前40us都输出高电平,后60us输出低电平。如此就输出了频率为10KHz,占空比为40%,分辨率为1%
输出比较模式
常用的为 PWM模式1 和 PWM模式2,二者效果相反,根据实际场景选择
编程实例
输出比较呼吸灯
- 程序目标:通过定时器输出比较实现呼吸灯(亮度周期变化)
- 程序原理:每20ms修改CRR值,输出不同PWM波形
接线图如下:
通过引脚定义得知,TIM2 的 CH1输出引脚为 A0
同时,将LED灯的正极接在输出引脚上,方便占空比的计算
PWM基本结构
PWM使用步骤:
- 开启相关外设时钟
c
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- 定时器使用内部时钟源72MHz
c
//时钟源使用内部时钟源,72MHz
TIM_InternalClockConfig(TIM2);
- 配置GPIO,注意引脚使用复用推挽输出
c
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //注意使用复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
- 配置时基单元,频率为1KHz,分辨率为1%
c
//时基单元
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_TimeBaseStructInit(&TIM_InitStructure);
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_InitStructure.TIM_Period = 100 - 1; //重装值
TIM_InitStructure.TIM_Prescaler = 720 - 1; //预分频
TIM_InitStructure.TIM_RepetitionCounter = 0; //重复计数,高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
- 配置输出比较,通过修改CCR值改变占空比
c
//输入/比较器模块
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); //因为一些属性是高级定时器的,初始化给个初始值
//配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //极性,此处设置为高电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能
TIM_OCInitStructure.TIM_Pulse = 0; //比较值,即CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
- 启动定时器
c
//开启时钟
TIM_Cmd(TIM2, ENABLE);
完整代码如下:
c
/**
* @brief 初始化PWM功能
* @parm 无
* @retval 无
*/
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//时钟源使用内部时钟源,72MHz
TIM_InternalClockConfig(TIM2);
// //引脚重定义
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //TIM2引脚重定向
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //关闭A15的调试接口,使其变成普通引脚使用
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //注意使用复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//时基单元
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_TimeBaseStructInit(&TIM_InitStructure);
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_InitStructure.TIM_Period = 100 - 1; //重装值
TIM_InitStructure.TIM_Prescaler = 720 - 1; //预分频
TIM_InitStructure.TIM_RepetitionCounter = 0; //重复计数,高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
//输入/比较器模块
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); //因为一些属性是高级定时器的,初始化给个初始值
//配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //极性,此处设置为高电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能
TIM_OCInitStructure.TIM_Pulse = 0; //比较值,即CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
//开启时钟
TIM_Cmd(TIM2, ENABLE);
}
同时提供修改CRR值的函数
c
/**
* @brief 设置比较值
* @parm Compare:比较值 范围:0 ~ 255
* @retval 无
*/
void PWM_SetCompare(uint8_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
主程序每隔20ms修改CRR值,实现LED灯由 熄灭 -> 亮度增大 -> 完全点亮 -> 亮度减小 -> 熄灭 的循环
c
int main()
{
PWM_Init();
while(1)
{
for(uint8_t i = 0; i < 100; ++i)
{
PWM_SetCompare(i);
Delay_ms(20);//延迟20ms
}
for(uint8_t i = 0; i < 100; ++i)
{
PWM_SetCompare(100 - i);
Delay_ms(20);//延迟20ms
}
}
}
完整程序链接:【STM32】输出比较PWM
舵机转向
舵机是一种根据输入PWM信号占空比来控制输出角度的装置
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms ~ 2.5ms
硬件电路如下:
接线图如下:舵机需要5V驱动,正极接STLINK
PWM初始化大抵相同,需要注意 ARR、PSC 的设置
c
/**
* @brief 初始化PWM功能
* @parm 无
* @retval 无
*/
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //注意使用复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//内部时钟
TIM_InternalClockConfig(TIM2);
//时基单元
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_InitStructure.TIM_Period = 20000 - 1; //重装值,计数周期
TIM_InitStructure.TIM_Prescaler = 72 - 1; //预分频
TIM_InitStructure.TIM_RepetitionCounter = 0; //重复计数,高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
//输入/比较器模块
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); //因为一些属性是高级定时器的,初始化给个初始值
//配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //极性,此处设置为高电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能
TIM_OCInitStructure.TIM_Pulse = 0; //比较值,即CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
//开启时钟
TIM_Cmd(TIM2, ENABLE);
}
此处设置 PSC = 72,ARR = 20000
频率为50Hz,即周期为20ms
-90° 为0.5ms,对应CRR为500,-45°对应为1000...
c
/**
* @brief 设置舵机角度 -90为0.5ms,对应CRR为500,-45°对应为1000.....
* @parm 角度 范围:0 ~ 180
* @retval 无
*/
void Servos_SetAngle(float Angle)
{
PWM_SetCompare(Angle / 180 * 2000 + 500);
}
完整程序链接:
PWM控制直流电机
直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向
硬件电路
PWMA 和 PWMB 接收PWM波形
AIN1、AIN2 和 BIN1、BIN2控制电机正反转
定义AIN1高电平,AIN2低电平为正转,则AIN1低电平,AIN2高电平为反转
接线图如下
PWM配置
c
/**
* @brief 初始化PWM功能
* @parm 无
* @retval 无
*/
void PWM_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化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);
//内部时钟
TIM_InternalClockConfig(TIM2);
//时基单元
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_InitStructure.TIM_Period = 100 - 1; //重装值,计数周期
TIM_InitStructure.TIM_Prescaler = 36 - 1; //预分频
TIM_InitStructure.TIM_RepetitionCounter = 0; //重复计数,高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
//输入/比较器模块
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure); //因为一些属性是高级定时器的,初始化给个初始值
//配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式
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_Cmd(TIM2, ENABLE);
}
还需要配置 A4 和 A5引脚控制电机正反转
c
/**
* @brief 舵机初始化
* @parm 无
* @retval 无
*/
void Motor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化输出高低电平的引脚
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();
}
/**
* @brief 设置舵机角度 0为500,45为1000,90为1500....
* @parm 角度 范围:0 ~ 180
* @retval 无
*/
void Motor_SetSpeed(int16_t Speed)
{
if(Speed >= 0)//正转
{
//设置GPIO引脚高低电平
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
//设置占空比,输出PWM波形
PWM_SetCompare(Speed);
}
else//反转
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
GPIO_SetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare(-Speed);
}
}
完整程序链接:
以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。