一、PWM简介
PWM(Pulse Width Modulation,脉冲宽度调制)是一种常用于模拟信号控制的技术,通过调节方波的占空比(即信号高电平与周期的比例)来模拟不同的平均电压或功率水平。PWM信号可以用于控制电机速度、调节LED亮度、音频信号处理、电池充电管理以及温度控制等多种应用。
PWM应用:
它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量,通信,功率控制与变换等许多领域。脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。常见应用有:电机控制,DAC输出等
二、工作原理
1.重要参数
1.频率(Frequency):指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期),单位为Hz。例如,50Hz表示一秒钟有50个周期。
2.周期(Period):一个脉冲信号的时间,T=1/f,其中f是频率。例如,50Hz的频率对应的周期是20ms。
3.占空比(Duty Cycle):一个脉冲周期内,高电平的时间与整个周期时间的比例,单位为%(0%-100%)。例如,80%的占空比表示高电平时间占整个周期的80%。
2.工作原理
PWM通过改变占空比来调节输出的有效电压。在固定的频率下,占空比越大,输出的平均电压就越高;占空比越小,输出的平均电压就越低。
案例:
(1)LED呼吸灯:通过改变PWM信号的占空比,可以控制LED灯的亮度,实现呼吸灯效果。当频率足够高时(通常大于50Hz),人眼无法察觉LED的闪烁,只会看到亮度的变化。(2)电机转速控制:PWM信号可以用来控制直流电机的转速。通过调节PWM信号的占空比,可以改变电机两端的平均电压,从而控制电机的转速。占空比越大,电机转速越快;占空比越小,电机转速越慢。需要注意的是,电机的控制频率需要适中,太低会导致运动不稳定,太高则电机可能反应不过来。
(3)舵机控制:舵机的控制也是通过PWM信号实现的。通常使用一个固定的频率(如50Hz),通过改变PWM信号的高电平时间(即占空比)来控制舵机的转角。不同的高电平时间对应舵机不同的转角。
3.STM32控制方法
PWM的工作原理基于对脉冲的宽度和周期进行调制。
下面是PWM的基本工作原理:
(1)设定目标数值:首先,确定需要控制的目标数值,例如调节电机的速度或LED的亮度。这个目标数值通常以一个百分比或占空比的形式表示。
(2)确定频率:选择PWM信号的频率,即脉冲的周期。频率决定了脉冲的重复速率,通常以赫兹(Hz)表示。常见的频率范围是几百赫兹到几十千赫兹。
(3)计算占空比:根据目标数值和所选频率,计算所需的占空比。占空比表示高电平时间占周期的比例。例如,如果目标是50%的亮度或速度,则占空比为50%。
(4)生成PWM信号:使用计时器和计数器来生成PWM信号。计时器根据所选频率生成一个固定周期的计时事件,并从0开始计数。计数器在每个计时事件中递增,当计数值小于占空比所对应的计数阈值时,输出为高电平;否则,输出为低电平。
(5)输出控制:根据计数器的值,控制输出引脚的电平状态。在计数值小于阈值时,输出为高电平;在计数值大于等于阈值时,输出为低电平。这样就形成了一系列固定周期、宽度可变的脉冲信号。
4.定时器设置
重要寄存器:
计数器寄存器 (TIMx_CNT);
自动装载寄存器 (TIMx_ARR);
捕获/比较寄存器(TIMx_CCRx) 。原理:
当0-t1这段时间,计数器寄存器的CNT的值是小于CCR,输出高电平。
当t1-t2这段时间,计数器寄存器的CNT的值是大于CCR且小于ARR的,输出低电平。
当CNT的值达到ARR里的值时,产生溢出事件,自动清零再次从0开始向上计数。
输出比较功能
STM32编码器实现速度闭环
三、PWM应用
1.扩展知识(定时器的向上/向下计数)
#include <stdio.h>
using namespace std;
int main()
{
short i = 65535;
printf("%d\n",i);
}
signed short int的取值范围-32768~32767,传入的65535溢出。short int类型截取65535后八位11111111(八个1),首位为1,认定为负数。由于负数在计算机内由补码形式表示,负数的补码为取反加1,最后得到00000001,结果为-1.
2.PWM的PID控制算法
// 控制电机正反转
#define AIN1(PinState) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_13, (GPIO_PinState)PinState)
#define AIN2(PinState) HAL_GPIO_WritePin(GPIOE, GPIO_PIN_15, (GPIO_PinState)PinState)
#define BIN1(PinState) HAL_GPIO_WritePin(GPIOC, GPIO_PIN_3, (GPIO_PinState)PinState)
#define BIN2(PinState) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, (GPIO_PinState)PinState)
#define PWMA TIM4->CCR1 // 设置PWM1占空比
#define PWMB TIM5->CCR3 // 设置PWM2占空比
// PID调节参数
struct pid_arg PID = {
.Balance_Kp = 2024,
.Balance_Kd = 9,
.Velocity_Kp = -9,
.Velocity_Ki = -0.9,
.Turn_Kp = 9,
.Turn_Kd = 0.9,
};
// 功能:读取编码器值(当作小车当前前进的速度)
int Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch (TIMX)
{
case 1:
Encoder_TIM = (short)TIM1->CNT;
TIM1->CNT = 0;
break;
case 2:
Encoder_TIM = (short)TIM2->CNT;
TIM2->CNT = 0;
break;
default:
Encoder_TIM = 0;
}
return Encoder_TIM;
}
/*
*功能:平衡PD控制
*形参:(float Angle):x轴的角度/(float Gyro):x轴的角速度
*返回值:经过PID转换之后的PWM值
*/
int Vertical_Ring_PD(float Angle, float Gyro)
{
float Bias;
int balance;
Bias = Angle - Mechanical_balance;
balance = PID.Balance_Kp * Bias + Gyro * PID.Balance_Kd;
return balance;
// printf("balance = %f\n",balance);
}
/*
*功能: 速度PI控制
*形参: (int encoder_left):左轮编码器值/(int encoder_right):编码器右轮的值/(float Angle):x轴角度值
*返回值: 经过PID转换之后的PWM值
*/
int Vertical_speed_PI(int encoder_left, int encoder_right, float Angle, float Movement)
{
static float Velocity, Encoder_Least, Encoder;
static float Encoder_Integral;
Encoder_Least = (encoder_left + encoder_right) - 0; // 获取最新速度偏差=测量速度(左右编码器之和)-目标速度(此处为零)
Encoder *= 0.8f; // 一阶低通滤波器,上次的速度占85%
Encoder += Encoder_Least * 0.2f; // 一阶低通滤波器, 本次的速度占15%
Encoder_Integral += Encoder; // 积分出位移 积分时间:10ms
Encoder_Integral = Encoder_Integral - Movement;
if (Encoder_Integral > 10000)
Encoder_Integral = 10000; // 积分限幅
if (Encoder_Integral < -10000)
Encoder_Integral = -10000; // 积分限幅
Velocity = Encoder * PID.Velocity_Kp + Encoder_Integral * PID.Velocity_Ki; // 速度控制
if (Turn_off(Angle) == 1)
Encoder_Integral = 0; // 电机关闭后清除积分
return Velocity;
}
/*
*功能:输出PWM控制电机
*形参;(int motor1):电机1对应的PWM值/(int motor2):电机2对应的PWM值
*返回值:无
*/
void Set_PWM(int motor1, int motor2)
{
if (motor1 > 0)
AIN2(1), AIN1(0);
else
AIN2(0), AIN1(1);
PWMA = Dead_Zone + (abs(motor1)) * 1.17;
if (motor2 > 0)
BIN1(1), BIN2(0);
else
BIN1(0), BIN2(1);
PWMB = Dead_Zone + (abs(motor2)) * 1.17;
}