stm32(HAL)库编码器电机pid代码及利用VOFA+对Pid波形显示调参
基本介绍
PID控制是一种经典的反馈控制算法,它通过不断地调整输出来使系统的实际值与设定值尽量接近,并保持在设定值附近。PID控制器由三个部分组成:比例§、积分(I)和微分(D)。
比例作用(P):比例作用通过测量实际值与设定值之间的偏差,乘以一个比例系数来产生输出。输出与偏差成正比,用来调整系统的响应速度和稳定性。较大的比例系数会增加系统的灵敏度,但可能导致过渡振荡。
积分作用(I):积分作用通过将偏差的累积值乘以一个积分系数来产生输出。积分作用能够消除系统的静差,提高系统的稳定性和响应速度。然而,过大的积分系数可能导致系统过度响应和振荡。
微分作用(D):微分作用通过测量偏差变化率,并乘以一个微分系数来产生输出。微分作用能够预测系统的未来趋势,以防止过冲和振荡。合适的微分系数可以提高系统的响应速度和稳定性,但过大的微分系数可能导致灵敏度降低。
PID控制器的输出是比例、积分和微分的加权和,即输出 = P * 偏差 + I * 积分值 + D * 变化率。通过调整比例、积分和微分系数,可以根据实际需求优化系统的性能。
pid的控制规律:
Kp——比例系数; Ti——积分时间常数; TD——微分时间常数
利用pid控制电机是我们学习pid的基础,以下是我用HAL库通过stm32控制编码器电机pid代码及利用VOFA+对Pid波形显示调参
配置与程序部分
以F103为例配置hal,其实其他版本的也差不多
先基础配置时钟树,RCC,SYS,这里可以略过了
先配置io输出引脚,供一个电机,到下面写代码会define控制引脚高电平低电平,控制电机正反转
接下来需要配置3个定时器,分别用于pwm输出,编码器捕获,和中断。编码器捕获用于识别脉冲,计算速度反馈给pid调速,中断定时器里就用于中断回调计算pid。接下来配置定时器
配置定时器2打开编码器捕获
定时器3,pwm输出
定时器4
我们还要利用usart与上位机通信显示波形
这里我用的usart2,usart1和I2c是之前我用来和蓝牙通信的和iled的,可以不管
最后还要去NVIC里设置中断优先
这里配置就完成了,建立工程
首先我们需要打开定时器和USART,放在main中,while之前,定时器初始化之后,为了方便我们之后的函数都写在main.c里都可以,首先定时器就是以下代码
// 为了方便我们的函数都写在main.c里
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL); //开启编码器模式
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_Base_Start(&htim2);
HAL_TIM_Base_Start_IT(&htim4); //开启定时器的中断
HAL_TIM_Base_Start(&htim4);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_Base_Start(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
因为我们需要用VOFA+来打印波形,要用printf命令(VOFA的特点就是不用协议有printf就可打印波形非常好用),所以需要在函数前include <stdio.h>就可
// An highlighted block
#include <stdio.h>
了解上面的pid的控制规律
然后就写pid的代码了
直接把下面的代码无脑复制到main前面就行了
// An highlighted block
typedef struct __PID_Increment_Struct//一个定义结构体
{
float Kp, Ki, Kd; //系数
float Error_Last1; //上次误差
float Error_Last2; //上次误差
float Out_Last; //上次输出
} PID_Increment_Struct;
void motor1()//正//BIN()是我在main.h里define的,往下可以看到
{
BIN1(0);
BIN2(1);}
void motor0()//反//如果你的反了改0,1即可
{
BIN1(1);
BIN2(0);}
//vofa的FireWater数据协议 换行结尾 /n或/r/n 逗号分隔通道
//指定三个通道
/* USER CODE BEGIN 0 */
#define Encoder_TIM_Handle htim2
#define Motor_MAX_Duty 2000
// PID_Increment_Struct PID_Speed = {80, 5};
float Get_Speed()//计算速度
{
int16_t zj;
float Speed = 0;
zj = __HAL_TIM_GetCounter(&Encoder_TIM_Handle);
__HAL_TIM_SetCounter(&Encoder_TIM_Handle, 0);
Speed = (float)zj / (4 * 11 * 30) * 100 * 60;//30是减速比,11是基础脉冲,4是因为4分频,可不改,最后算出速度是一分钟多少转
b=Speed;//提取数值到外部变量,到时候放到while中显示实时速度
return Speed;
}
//float Get_Angle()//计算角度(只速度控制可忽略)
//{
// int16_t zj;
// float angle = 0;
// zj = __HAL_TIM_GetCounter(&Encoder_TIM_Handle);
// angle = (float)zj / (4 * 15 * 34) * 360;
// return angle;
//}
float PID_Increment(PID_Increment_Struct *PID, float Current, float Target)//pid计算
{
float err, //误差
out, //输出
proportion, //比例
differential; //微分
err = (float)Target - (float)Current; //计算误差
proportion = (float)err - (float)PID->Error_Last1; //计算比例项
differential = (float)err - 2 * (float)PID->Error_Last1 + (float)PID->Error_Last2; //计算微分项
if(err<=40&&err>-40)
out = (float)PID->Out_Last + (float)PID->Kp * proportion+(float)PID->Ki * err+ (float)PID->Kd * differential; //计算PID
else//如果不if else,调试的时候如果单片机只是5v上电但电机没转,你接12v电源开电机,电机速度会拉满,这就不好了
out = (float)PID->Out_Last + (float)PID->Kp * proportion+ (float)PID->Kd * differential; //计算PID
PID->Error_Last2 = PID->Error_Last1; //更新上上次误差
PID->Error_Last1 = err; //更新误差
PID->Out_Last = out; //更新上此输出
return out;
}
void motor(int16_t Speed)
{
if (Speed == 0)
{
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,Motor_MAX_Duty+1 );
}
else if (Speed > 0)
{
motor0();
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,Speed);
}
else if (Speed < 0)
{
Speed *= -1;
motor1();
__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,Speed);
}
}
PID_Increment_Struct PID_Speed = {20, 0.1, 0};//这个参数是我调的,不同参数得看不同电机
//PID_Increment_Struct PID_Angle = {3.1, 0, 0.06};//角度控制的,可忽略
//float angle;
float mb_speed_last;
float aa = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
float Speed = 0;
float set_speed = 0;
float mb_speed;
if (htim == &htim2)
{
}
else if (htim == &htim4)//注释的都是角度控制的,到时候可以在深入使用
{
// angle += Get_Angle();
// mb_speed = (int16_t)PID_Increment(&PID_Angle, angle, aa);
// if (PID_Angle.Error_Last1 > 360)
// mb_speed = 300;
// else if (PID_Angle.Error_Last1 < -360)
// mb_speed = -300;
Speed = Get_Speed();
mb_speed = 120;//目标速度
set_speed = PID_Increment(&PID_Speed, Speed, mb_speed);
if (set_speed > 2000)
set_speed = 2000;
else if (set_speed < -2000)
set_speed = -2000;
motor(set_speed);
if (set_speed > 100 || set_speed < -100)//死区控制,改善电机异响
motor(set_speed);
a=set_speed;//提取数值到外部变量,到时候放到while中显示实时占空比
}
}
宏定义电机输出变量
#define BIN1(state) HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,(GPIO_PinState)(state)) //IN1
#define BIN2(state) HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,(GPIO_PinState)(state)) //IN2
//控制电机正反的输出
接下来在while里加入printf命令
printf("%2f\n",b);//显示实时速度,vofa的FireWater数据协议 记得要换行结尾 \n,不然打印不出来,逗号分隔通道
下载编译烧录就可完成了
vofa+调试显示波形调参
需要下载vofa可从官网下链接: link
打开vofa,按图选择FireWater,FireWater是用来显示波形的协议,更多可参考官网的使用说明链接: link
选择控件,将波形显示器加入到主屏幕上,长按拖动即可
x轴默认为t轴,y轴选择io,即变量,单片机接上usb to ttl 转换器,再接上位机,识别到串口,打开串口,给电机上电即可显示出波形,愉快的调参。
我最后的结果是这样的