起因
前一篇文章實(shí)現(xiàn)了使用TB6612驅(qū)動(dòng)電機(jī)及編碼器測(cè)速,但是在實(shí)際測(cè)速的過(guò)程中,如果我們人為給電機(jī)一個(gè)阻力,電機(jī)的速度將會(huì)下降,編碼器接口獲取到的脈沖數(shù)也會(huì)減少。
但是如果要使電機(jī)保持一個(gè)恒定的速度
,即使遇到阻力它的速度也不會(huì)下降。這個(gè)時(shí)候就需要引入PID算法了。
不僅僅是速度,很多參數(shù)都可以通過(guò)PID算法進(jìn)行閉環(huán)控制,比如溫度,角度等。
PID是反饋環(huán)的調(diào)節(jié)機(jī)制
就拿電機(jī)速度為例
電機(jī)的轉(zhuǎn)速有誤差,把實(shí)測(cè)轉(zhuǎn)速輸入和設(shè)定比較的差值用PID運(yùn)算輸出控制占空比,那么增減轉(zhuǎn)速就可以實(shí)現(xiàn)精確控制了
作為一個(gè)菜雞選手,感覺(jué)自己好菜,將學(xué)習(xí)中遇到的問(wèn)題和經(jīng)歷記錄一下
如何調(diào)節(jié)PID參數(shù)?如何根據(jù)PID算法調(diào)節(jié)電機(jī)的速度?如何使用PID實(shí)現(xiàn)閉環(huán)控制?
啊~,怎么這么多,自己又陷入了無(wú)限的內(nèi)耗中,搞不出來(lái),看不懂文章,PID什么鬼?
目前暫時(shí)實(shí)現(xiàn)了電機(jī)的速度環(huán)調(diào)參
在學(xué)習(xí)了一段時(shí)間后,自己對(duì)這個(gè)東西理解的好像深一點(diǎn)了,僅停留在會(huì)用的地步,對(duì)三個(gè)參數(shù)也是淺顯的理解,知道了如何配合串口上位機(jī)進(jìn)行調(diào)參,總的來(lái)說(shuō)還是挺有收獲的。
一、什么是開(kāi)環(huán)系統(tǒng)?
在沒(méi)有引入PID時(shí),我們控制速度的系統(tǒng)是一個(gè)開(kāi)環(huán)系統(tǒng)
,控制電機(jī)的速度一般是通過(guò)控制PWM的占空比來(lái)控速。
我們需要目標(biāo)速度,就一步一步的去嘗試,速度慢了,就加大占空比,快了,就減小占空比,但是在實(shí)際嘗試的過(guò)程中,我發(fā)現(xiàn),很難人為控制占空比得到一個(gè)很精確的速度。
這個(gè)開(kāi)環(huán)系統(tǒng)就是,設(shè)置占空比–>得到電機(jī)轉(zhuǎn)速–>根據(jù)電機(jī)轉(zhuǎn)速來(lái)判斷占空比是否應(yīng)該減小OR增大–>調(diào)整占空比
二、什么是PID?
PID:Proportional(比例)、Integral(積分)、Differential(微分)的縮寫(xiě)。
用一句話(huà)來(lái)說(shuō),就是對(duì)輸入偏差進(jìn)行積分微分計(jì)算,用運(yùn)算的疊加結(jié)果去控制執(zhí)行機(jī)構(gòu)
聽(tīng)起來(lái)很簡(jiǎn)單吧,一句話(huà)就講完了。
啊??,要是這么簡(jiǎn)單就好了。來(lái)看看下面的圖,就是一個(gè)基本的PID控制框圖
這就是形成了一個(gè)閉環(huán)系統(tǒng),r(t)是輸入量,u(t)是輸出量
貼一篇鏈接:
一文讀懂PID控制算法(拋棄公式,從原理上真正理解PID控制)
大家可以康康看,好好理解
KP,KI,KD三個(gè)參數(shù)的作用
KP增加時(shí)
,響應(yīng)速度變快,當(dāng)Ki增加時(shí)
,能最終趨于目標(biāo)值,KD增加時(shí)
,可以減小震蕩。
P
:比例控制系統(tǒng)快速響應(yīng),快速接近于目標(biāo)值,但是存在靜態(tài)誤差,輸出到達(dá)不了目標(biāo)值,會(huì)有誤差。I
:積分控制系統(tǒng)的準(zhǔn)確性,消除累積的誤差,輸出到達(dá)目標(biāo)值D
:微分控制系統(tǒng)的穩(wěn)定性,具有超前的控制作用,防止輸出超過(guò)目標(biāo)
大家理解這三個(gè)參數(shù)的作用,這樣在調(diào)參的時(shí)候就會(huì)很快,是加大還是減小參數(shù)。
三、PID算法的離散化
1、什么是位置式PID?
這方面我就不大寫(xiě)闊論了,好多大佬都比我講的好,我在這里貼出幾個(gè)鏈接,大家可以參考
增量式pid+位置式PID(電機(jī)位置閉環(huán)控制)
位置式 PID 控制算法和增量式 PID 控制算法
這個(gè)大佬的教程也很不錯(cuò),我也學(xué)習(xí)了很久,頓悟了很多
而且這個(gè)大佬的教程是一系列的,大家可以有選擇的學(xué)習(xí)
PID-電機(jī)速度控制-B
電機(jī)控制進(jìn)階——PID速度控制–CSDN
如果大家不理解位置式PID公式的話(huà),可以首先了解如何使用,如何調(diào)參,如何使用PID進(jìn)行閉環(huán)控制,這里以速度為例
大家可以看我下面位置式PID的實(shí)現(xiàn),我直接給出了源代碼,大家可以參考
位置式是離散型的PID,大家記住比例Kp,積分Ki,微分Kd這三個(gè)參數(shù)的作用,再就是位置式的代碼化實(shí)現(xiàn)
2、位置式PID實(shí)現(xiàn)
pid.h
#ifndef __PID_H
#define __PID_H
#include "sys.h"
typedef struct
{
float target_val; //目標(biāo)值
float err; //偏差值
float err_last; //上一個(gè)偏差值
float Kp,Ki,Kd; //比例、積分、微分系數(shù)
float integral; //積分值
float output_val; //輸出值
}PID;
extern PID pid;
pid.h
#include "pid.h"
#include "encoder.h"
//位置式 有誤差,速度比較慢
PID pid;
void PID_param_init(void)
{
/* 初始化參數(shù) */
pid.err=0.0;
pid.err_last=0.0;
pid.integral=0.0; //積分項(xiàng)
pid.Kp=15.0; //最優(yōu)
pid.Ki=1.0; //0.05 0.1 p 15.0 i 0.1 kd 2.5
pid.Kd=1.5; //調(diào)節(jié)成功 p 25.0 i 0.03 kd 0.025
//kd 4.0 最大 15 1.0 1.5
pid.target_val = 26; //目標(biāo)值
pid.output_val=0.0; //輸出值
}
//位置式pid 傳入實(shí)際值即可
float PID_realize(float actual_val)
{
/*計(jì)算目標(biāo)值與實(shí)際值的誤差*/
pid.err = pid.target_val - actual_val; //目標(biāo)值和實(shí)際值的誤差
/*積分項(xiàng)*/
pid.integral += pid.err; //誤差累積
/*PID算法實(shí)現(xiàn)*/
pid.output_val = pid.Kp * pid.err +
pid.Ki * pid.integral +
pid.Kd * (pid.err - pid.err_last); //位置式
/*誤差傳遞*/
pid.err_last = pid.err;
/*返回當(dāng)前實(shí)際值*/
return pid.output_val;
}
還有一種位置
3、什么是增量式PID?
關(guān)于這個(gè)增量式PID大家可以參考下面的鏈接,好好學(xué)習(xí),大佬還是講的很明白的
電機(jī)速度環(huán)和位置環(huán)PID調(diào)參教程–B站
【STM32F4系列】【HAL庫(kù)】電機(jī)控制(轉(zhuǎn)速和角度)(PID實(shí)戰(zhàn)1)_32 hal庫(kù)將pid坐標(biāo)轉(zhuǎn)換化為角度_Hz1213825的博客-CSDN博客
4、增量式PID實(shí)現(xiàn)
一般使用增量式PI就可以控制住速度了,所以我們采用增量式PI控制速度,閉環(huán)速度環(huán),大家可以參考下面的代碼,傳入目標(biāo)值和當(dāng)前值,輸出PI運(yùn)算后的輸出
進(jìn)行PI調(diào)參,進(jìn)而控制電機(jī)速度
// 速度環(huán)pi控制 使用增量式
/**************************************************************************
函數(shù)功能:增量PI控制器
入口參數(shù):編碼器測(cè)量值,目標(biāo)速度
返回 值:電機(jī)PWM
根據(jù)增量式離散PID公式
out+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k)代表本次偏差
e(k-1)代表上一次的偏差 以此類(lèi)推
out代表增量輸出
在我們的速度控制閉環(huán)系統(tǒng)里面,只使用PI控制
pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
**************************************************************************/
int Incremental_PI(int Encoder, int Target)
{
float Kp = 10.0, Ki = 1;
static int error, out, err_last; // 誤差 輸出 上一次誤差
error = Encoder - Target; // 求出速度偏差,由測(cè)量值減去目標(biāo)值。
out += Kp * (error - err_last) + Ki * error; // 使用增量 PI 控制器求出電機(jī) PWM。
err_last = error; // 保存上一次偏差
return out; // 增量輸出
}
四、采用VOFA+調(diào)試PID
參考我的這一篇文章:如何使用VOFA+?一款好用的上位機(jī)軟件(VOFA+的三種數(shù)據(jù)傳輸協(xié)議)——以PID調(diào)參為例
大家可以選擇
firewater協(xié)議格式
或者justfloat協(xié)議格式
,這兩種協(xié)議都可以顯示數(shù)據(jù)的波形,可以一邊調(diào)參,一邊查看曲線(xiàn),再修改參數(shù),直至達(dá)到目標(biāo)值為止。
firewater協(xié)議格式
我做出個(gè)示例
使用firewater協(xié)議格式,可以是任何類(lèi)型的數(shù)據(jù),但是以逗號(hào)隔開(kāi),最后必須以\n結(jié)尾,這樣在上位機(jī)中就可以顯示出波形了
下方分別代表當(dāng)前速度,目標(biāo)速度,輸出
printf("%f,%f,%f\n",current,target,out); //脈沖,目標(biāo)值,out
justfloat協(xié)議格式
如果這種協(xié)議不懂得話(huà)
大家可以直接使用我編寫(xiě)的庫(kù),簡(jiǎn)單好用
vofa.c
/*
要點(diǎn)提示:
1. float和unsigned long具有相同的數(shù)據(jù)結(jié)構(gòu)長(zhǎng)度
2. union據(jù)類(lèi)型里的數(shù)據(jù)存放在相同的物理空間
*/
typedef union
{
float fdata;
unsigned long ldata;
} FloatLongType;
/*
將浮點(diǎn)數(shù)f轉(zhuǎn)化為4個(gè)字節(jié)數(shù)據(jù)存放在byte[4]中
*/
void Float_to_Byte(float f,unsigned char byte[])
{
FloatLongType fl;
fl.fdata=f;
byte[0]=(unsigned char)fl.ldata;
byte[1]=(unsigned char)(fl.ldata>>8);
byte[2]=(unsigned char)(fl.ldata>>16);
byte[3]=(unsigned char)(fl.ldata>>24);
}
void JustFloat_Test(void) //justfloat 數(shù)據(jù)協(xié)議測(cè)試
{
float a=1,b=2; //發(fā)送的數(shù)據(jù) 兩個(gè)通道
u8 byte[4]={0}; //float轉(zhuǎn)化為4個(gè)字節(jié)數(shù)據(jù)
u8 tail[4]={0x00, 0x00, 0x80, 0x7f}; //幀尾
//向上位機(jī)發(fā)送兩個(gè)通道數(shù)據(jù)
Float_to_Byte(a,byte);
//u1_printf("%f\r\n",a);
u1_SendArray(byte,4); //1轉(zhuǎn)化為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x80 0x3F
Float_to_Byte(b,byte);
u1_SendArray(byte,4); //2轉(zhuǎn)換為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x00 0x40
//發(fā)送幀尾
u1_SendArray(tail,4); //幀尾為 0x00 0x00 0x80 0x7f
}
//向vofa發(fā)送數(shù)據(jù) 三個(gè)數(shù)據(jù) 三個(gè)通道 可視化顯示 幀尾
void vofa_sendData(float a,float b,float c)
{
//float a=1,b=2; //發(fā)送的數(shù)據(jù) 兩個(gè)通道
u8 byte[4]= {0}; //float轉(zhuǎn)化為4個(gè)字節(jié)數(shù)據(jù)
u8 tail[4]= {0x00, 0x00, 0x80, 0x7f}; //幀尾
//向上位機(jī)發(fā)送兩個(gè)通道數(shù)據(jù)
Float_to_Byte(a,byte);
//u1_printf("%f\r\n",a);
u1_SendArray(byte,4); //1轉(zhuǎn)化為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x80 0x3F
Float_to_Byte(b,byte);
u1_SendArray(byte,4); //2轉(zhuǎn)換為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x00 0x40
Float_to_Byte(c,byte);
u1_SendArray(byte,4);
//發(fā)送幀尾
u1_SendArray(tail,4); //幀尾為 0x00 0x00 0x80 0x7f
}
使用
void vofa_sendData(float a,float b,float c) //a,b,c代表三個(gè)通道波形
五、目前
我要實(shí)現(xiàn)的是速度環(huán)和位置閉環(huán)
,我現(xiàn)在首先目標(biāo)是單環(huán)控制,首先速度環(huán),然后位置環(huán)
速度環(huán)采用增量式PI控制,位置環(huán)采用位置式PID控制
速度環(huán)沒(méi)問(wèn)題(在這里無(wú)論是位置式還是增量式都實(shí)現(xiàn)了進(jìn)行速度控制,這里建議增量式PI
)
但是目前位置式PID控制位置環(huán)出現(xiàn)了點(diǎn)問(wèn)題,當(dāng)我調(diào)參的時(shí)候,無(wú)論目標(biāo)速度調(diào)成多大,電機(jī)的轉(zhuǎn)速總是會(huì)趨近于最大轉(zhuǎn)速,目前這個(gè)問(wèn)題還未解決,暫定
大家可以參考我的代碼,采用VOFA+上位機(jī)顯示調(diào)參波形,根據(jù)波形進(jìn)行調(diào)參,希望可以幫助到大家。
我只貼出主要控制代碼,我的工程文件將會(huì)開(kāi)源,大家可以下載,參考
/*
* @Author: _oufen
* @Date: 2023-03-31 18:23:31
* @LastEditTime: 2023-04-01 19:34:31
* @Description:
*/
#include "timer4.h"
#include "led.h"
#include "encoder.h"
#include "motor.h"
#include "vofa.h"
#include "usart.h"
// int encoder_speed; // 實(shí)際速度 近似脈沖
// int target_speed = 30; // 目標(biāo)速度 每10ms 30個(gè)脈沖
// int Moto1; // 輪子輸出值
int encoder_position;
int target_position = 13;
int Moto1;
void Timer4_Init(u16 psc, u16 arr) // Timer4_Init(7200-1,1000-1);
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
TIM_DeInit(TIM4); // 定時(shí)器4恢復(fù)默認(rèn)設(shè)置
// MY_GPIO_Init(GPIOB,GPIO_Pin_6,GPIO_Mode_AF_PP);
// MY_GPIO_Init(GPIOB,GPIO_Pin_7,GPIO_Mode_AF_PP);
// MY_GPIO_Init(GPIOB,GPIO_Pin_8,GPIO_Mode_AF_PP);
// MY_GPIO_Init(GPIOB,GPIO_Pin_9,GPIO_Mode_AF_PP);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM4, TIM_IT_Update); // 清除更新中斷請(qǐng)求位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}
// 向vofa發(fā)送數(shù)據(jù) 三個(gè)數(shù)據(jù) 三個(gè)通道 可視化顯示 幀尾
void vofa_sendData(float a, float b, float c)
{
u8 byte[4] = {0}; // float轉(zhuǎn)化為4個(gè)字節(jié)數(shù)據(jù)
u8 tail[4] = {0x00, 0x00, 0x80, 0x7f}; // 幀尾
// 向上位機(jī)發(fā)送兩個(gè)通道數(shù)據(jù)
Float_to_Byte(a, byte);
// u1_printf("%f\r\n",a);
u1_SendArray(byte, 4); // 1轉(zhuǎn)化為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x80 0x3F
Float_to_Byte(b, byte);
u1_SendArray(byte, 4); // 2轉(zhuǎn)換為4字節(jié)數(shù)據(jù) 就是 0x00 0x00 0x00 0x40
Float_to_Byte(c, byte);
u1_SendArray(byte, 4);
// 發(fā)送幀尾
u1_SendArray(tail, 4); // 幀尾為 0x00 0x00 0x80 0x7f
}
// 速度環(huán)pi控制 使用增量式
/**************************************************************************
函數(shù)功能:增量PI控制器
入口參數(shù):編碼器測(cè)量值,目標(biāo)速度
返回 值:電機(jī)PWM
根據(jù)增量式離散PID公式
out+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k)代表本次偏差
e(k-1)代表上一次的偏差 以此類(lèi)推
out代表增量輸出
在我們的速度控制閉環(huán)系統(tǒng)里面,只使用PI控制
pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
**************************************************************************/
int Incremental_PI(int Encoder, int Target)
{
float Kp = 10.0, Ki = 1;
static int error, out, err_last; // 誤差 輸出 上一次誤差
error = Encoder - Target; // 求出速度偏差,由測(cè)量值減去目標(biāo)值。
out += Kp * (error - err_last) + Ki * error; // 使用增量 PI 控制器求出電機(jī) PWM。
err_last = error; // 保存上一次偏差
return out; // 增量輸出
}
/**************************************************************************
函數(shù)功能:位置式PID控制器
入口參數(shù):編碼器測(cè)量位置信息,目標(biāo)位置
返回 值:電機(jī)PWM
根據(jù)位置式離散PID公式
out=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]
e(k)代表本次偏差
e(k-1)代表上一次的偏差
∑e(k)代表e(k)以及之前的偏差的累積和;其中k為1,2,,k;
out代表輸出
**************************************************************************/
int Position_PID(int Encoder, int Target)
{
float Position_KP = 15, Position_KI = 0.1, Position_KD = 0.1; // pid
static float error, out, Integral_error, error_last; // 誤差 輸出 積分 上一次誤差
error = Encoder - Target; // 求出速度偏差,由測(cè)量值減去目標(biāo)值。
Integral_error += error; // 求出偏差的積分
out = Position_KP * error + Position_KI * Integral_error + Position_KD * (error - error_last); // 位置式PID控制器
error_last = error; // 保存上一次偏差
return out; // 增量輸出
}
int myabs(int a)
{
int temp;
if (a < 0)
temp = -a;
else
temp = a;
return temp;
}
void Set_pwm(int pwm)
{
if (pwm > 0)
AIN1 = 0, AIN2 = 1;
else
AIN1 = 1, AIN2 = 0;
PWMA = myabs(pwm); //PWMA --> TIM1->CCR1
}
void Xianfu_Pwm(void)
{
int Amplitude = 99;
if (Moto1 < -Amplitude)
Moto1 = -Amplitude;
if (Moto1 > Amplitude)
Moto1 = Amplitude;
}
// 定時(shí)器定時(shí)調(diào)用
/*void AutoReloadCallback()
{
encoder_speed += Read_Encoder(2); // 讀取真實(shí)速度
//printf("Encoder = %d\r\n", encoder_speed);
Moto1 = Incremental_PI(encoder_speed, target_speed); // PID計(jì)算
Xianfu_Pwm(); // 對(duì)輸出進(jìn)行限幅
// printf("Moto1 = %d\r\n", PWMA);
Set_pwm(Moto1);
vofa_sendData(encoder_speed, target_speed, PWMA); // 向上位機(jī)發(fā)送數(shù)據(jù)
}*/
void AutoReloadCallback()
{
encoder_position = Read_Encoder(2); // 讀取真實(shí)速度
//printf("Encoder = %d\r\n", encoder_position);
Moto1 = Position_PID(encoder_position, target_position); // PID計(jì)算
Xianfu_Pwm(); // 對(duì)輸出進(jìn)行限幅
// printf("Moto1 = %d\r\n", PWMA);
Set_pwm(Moto1);
vofa_sendData(encoder_position, target_position, PWMA); // 向上位機(jī)發(fā)送數(shù)據(jù)
}
void TIM4_IRQHandler(void) // 10ms
{
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
AutoReloadCallback(); // 定時(shí)調(diào)用
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
LED1 = !LED1;
}
}
六、后面一段時(shí)間
如果有時(shí)間得話(huà),可能會(huì)玩一玩平衡小車(chē),現(xiàn)成的硬件都有,據(jù)我了解,主要是角度環(huán),直立環(huán),速度環(huán),三環(huán)閉環(huán)。
也許會(huì)對(duì)PID的理解和調(diào)參更進(jìn)一步
如果沒(méi)有時(shí)間的話(huà),將會(huì)直接上手搭車(chē),根據(jù)實(shí)際練習(xí)和學(xué)習(xí)其他模塊,比如循跡,電機(jī)驅(qū)動(dòng),串口通信(和MV的通信),藍(lán)牙,實(shí)際控制邏輯的編寫(xiě)等。
還是任重道遠(yuǎn),從0到1,從無(wú)到有
七、參考代碼
大家可以參考我的代碼,已經(jīng)全部開(kāi)源
附帶我的學(xué)習(xí)筆記和收集到的各種開(kāi)源代碼,希望大家可以學(xué)明白,也希望我的微博力量可以幫助到大家
加油加油文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-407719.html
oufen/PID調(diào)試–我的代碼文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-407719.html
到了這里,關(guān)于我的PID學(xué)習(xí)歷程---PID位置式和增量式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!