使用STM32(HAL)库编写编码器电机PID代码并利用VOFA进行PID波形显示和调参

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 转换器,再接上位机,识别到串口,打开串口,给电机上电即可显示出波形,愉快的调参。

我最后的结果是这样的

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32(HAL)库编写编码器电机PID代码并利用VOFA进行PID波形显示和调参

发表评论