在學習江協科技PID課程時,做一些筆記,對應視頻3-1,對應代碼:13
13-雙環PID定速定位置控制-代碼封裝
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "RP.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include "PID.h"uint8_t KeyNum;int16_t Speed, Location; //速度,位置/*定義PID結構體變量*/
PID_t Inner = { //內環PID結構體變量,定義的時候同時給部分成員賦初值.Kp = 0.3, //比例項權重.Ki = 0.3, //積分項權重.Kd = 0, //微分項權重.OutMax = 100, //輸出限幅的最大值.OutMin = -100, //輸出限幅的最小值
};PID_t Outer = { //外環PID結構體變量,定義的時候同時給部分成員賦初值.Kp = 0.3, //比例項權重.Ki = 0, //積分項權重.Kd = 0.4, //微分項權重.OutMax = 20, //輸出限幅的最大值.OutMin = -20, //輸出限幅的最小值
};int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化Key_Init(); //非阻塞式按鍵初始化Motor_Init(); //電機初始化Encoder_Init(); //編碼器初始化RP_Init(); //電位器旋鈕初始化Serial_Init(); //串口初始化,波特率9600Timer_Init(); //定時器初始化,定時中斷時間1ms/*OLED打印一個標題*/OLED_Printf(0, 0, OLED_8X16, "2*PID Control");OLED_Update();while (1){/*按鍵修改目標值*//*解除以下注釋后,記得屏蔽電位器旋鈕修改目標值的代碼*//*調節內環目標值使用Inner.Target,調節外環目標值使用Outer.Target*/
// KeyNum = Key_GetNum(); //獲取鍵碼
// if (KeyNum == 1) //如果K1按下
// {
// Inner.Target += 10; //目標值加10
// }
// if (KeyNum == 2) //如果K2按下
// {
// Inner.Target -= 10; //目標值減10
// }
// if (KeyNum == 3) //如果K3按下
// {
// Inner.Target = 0; //目標值歸0
// }/*解除下面一段代碼的注釋,進行內環PID調參*//*進行內環PID調參時,請注釋掉外環控制內環的部分代碼*//*RP_GetValue函數返回電位器旋鈕的AD值,范圍:0~4095*//* 除4095.0可以把AD值歸一化,再乘上一個系數,可以調整到一個合適的范圍*/
// Inner.Kp = RP_GetValue(1) / 4095.0 * 2; //修改Kp,調整范圍:0~2
// Inner.Ki = RP_GetValue(2) / 4095.0 * 2; //修改Ki,調整范圍:0~2
// Inner.Kd = RP_GetValue(3) / 4095.0 * 2; //修改Kd,調整范圍:0~2
// Inner.Target = RP_GetValue(4) / 4095.0 * 300 - 150; //修改目標值,調整范圍:-150~150
//
// /*OLED顯示*/
// OLED_Printf(0, 16, OLED_8X16, "Kp:%4.2f", Inner.Kp); //顯示Kp
// OLED_Printf(0, 32, OLED_8X16, "Ki:%4.2f", Inner.Ki); //顯示Ki
// OLED_Printf(0, 48, OLED_8X16, "Kd:%4.2f", Inner.Kd); //顯示Kd
//
// OLED_Printf(64, 16, OLED_8X16, "Tar:%+04.0f", Inner.Target); //顯示目標值
// OLED_Printf(64, 32, OLED_8X16, "Act:%+04.0f", Inner.Actual); //顯示實際值
// OLED_Printf(64, 48, OLED_8X16, "Out:%+04.0f", Inner.Out); //顯示輸出值
//
// OLED_Update(); //OLED更新,調用顯示函數后必須調用此函數更新,否則顯示的內容不會更新到OLED上
//
// Serial_Printf("%f,%f,%f\r\n", Inner.Target, Inner.Actual, Inner.Out); //串口打印目標值、實際值和輸出值
// //配合SerialPlot繪圖軟件,可以顯示數據的波形/*解除下面一段代碼的注釋,進行外環PID調參*//*內環PID調參完成后,加上外環控制內環的部分代碼,再進行外環PID調參*//*RP_GetValue函數返回電位器旋鈕的AD值,范圍:0~4095*//* 除4095.0可以把AD值歸一化,再乘上一個系數,可以調整到一個合適的范圍*/
// Outer.Kp = RP_GetValue(1) / 4095.0 * 2; //修改Kp,調整范圍:0~2
// Outer.Ki = RP_GetValue(2) / 4095.0 * 2; //修改Ki,調整范圍:0~2
// Outer.Kd = RP_GetValue(3) / 4095.0 * 2; //修改Kd,調整范圍:0~2Outer.Target = RP_GetValue(4) / 4095.0 * 816 - 408; //修改目標值,調整范圍:-408~408/*OLED顯示*/OLED_Printf(0, 16, OLED_8X16, "Kp:%4.2f", Outer.Kp); //顯示KpOLED_Printf(0, 32, OLED_8X16, "Ki:%4.2f", Outer.Ki); //顯示KiOLED_Printf(0, 48, OLED_8X16, "Kd:%4.2f", Outer.Kd); //顯示KdOLED_Printf(64, 16, OLED_8X16, "Tar:%+04.0f", Outer.Target); //顯示目標值OLED_Printf(64, 32, OLED_8X16, "Act:%+04.0f", Outer.Actual); //顯示實際值OLED_Printf(64, 48, OLED_8X16, "Out:%+04.0f", Outer.Out); //顯示輸出值OLED_Update(); //OLED更新,調用顯示函數后必須調用此函數更新,否則顯示的內容不會更新到OLED上Serial_Printf("%f,%f,%f\r\n", Outer.Target, Outer.Actual, Outer.Out); //串口打印目標值、實際值和輸出值//配合SerialPlot繪圖軟件,可以顯示數據的波形}
}void TIM1_UP_IRQHandler(void)
{/*定義靜態變量(默認初值為0,函數退出后保留值和存儲空間)*/static uint16_t Count1, Count2; //分別用于內環和外環的計次分頻if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET){/*每隔1ms,程序執行到這里一次*/Key_Tick(); //調用按鍵的Tick函數/*內環計次分頻*/Count1 ++; //計次自增if (Count1 >= 40) //如果計次40次,則if成立,即if每隔40ms進一次{Count1 = 0; //計次清零,便于下次計次/*獲取實際速度值和實際位置值*//*Encoder_Get函數,可以獲取兩次讀取編碼器的計次值增量*//*此值正比于速度,所以可以表示速度,但它的單位并不是速度的標準單位*//*此處每隔40ms獲取一次計次值增量,電機旋轉一周的計次值增量約為408*//*因此如果想轉換為標準單位,比如轉/秒*//*則可將此句代碼改成Speed = Encoder_Get() / 408.0 / 0.04;*/Speed = Encoder_Get(); //獲取編碼器增量,得到實際速度Location += Speed; //實際速度累加,得到實際位置/*以下進行內環PID控制*//*內環獲取實際值*/Inner.Actual = Speed; //內環為速度環,實際值為速度值/*PID計算及結構體變量值更新*/PID_Update(&Inner); //調用封裝好的函數,一步完成PID計算和更新/*內環執行控制*//*內環輸出值給到電機PWM*/Motor_SetPWM(Inner.Out);}/*外環計次分頻*/Count2 ++; //計次自增if (Count2 >= 40) //如果計次40次,則if成立,即if每隔40ms進一次{Count2 = 0; //計次清零,便于下次計次/*以下進行外環PID控制*//*外環獲取實際值*/Outer.Actual = Location; //外環為位置環,實際值為位置值/*PID計算及結構體變量值更新*/PID_Update(&Outer); //調用封裝好的函數,一步完成PID計算和更新/*外環執行控制*//*外環的輸出值作用于內環的目標值,組成串級PID結構*/Inner.Target = Outer.Out;}TIM_ClearITPendingBit(TIM1, TIM_IT_Update);}
}
PID.c:
#include "stm32f10x.h" // Device header
#include "PID.h"/*** 函 數:PID計算及結構體變量值更新* 參 數:PID_t * 指定結構體的地址* 返 回 值:無*/
void PID_Update(PID_t *p)
{/*獲取本次誤差和上次誤差*/p->Error1 = p->Error0; //獲取上次誤差p->Error0 = p->Target - p->Actual; //獲取本次誤差,目標值減實際值,即為誤差值/*外環誤差積分(累加)*//*如果Ki不為0,才進行誤差積分,這樣做的目的是便于調試*//*因為在調試時,我們可能先把Ki設置為0,這時積分項無作用,誤差消除不了,誤差積分會積累到很大的值*//*后續一旦Ki不為0,那么因為誤差積分已經積累到很大的值了,這就導致積分項瘋狂輸出,不利于調試*/if (p->Ki != 0) //如果Ki不為0{p->ErrorInt += p->Error0; //進行誤差積分}else //否則{p->ErrorInt = 0; //誤差積分直接歸0}/*PID計算*//*使用位置式PID公式,計算得到輸出值*/p->Out = p->Kp * p->Error0+ p->Ki * p->ErrorInt+ p->Kd * (p->Error0 - p->Error1);/*輸出限幅*/if (p->Out > p->OutMax) {p->Out = p->OutMax;} //限制輸出值最大為結構體指定的OutMaxif (p->Out < p->OutMin) {p->Out = p->OutMin;} //限制輸出值最小為結構體指定的OutMin
}
PID.h:
#ifndef __PID_H
#define __PID_Htypedef struct {float Target;float Actual;float Out;float Kp;float Ki;float Kd;float Error0;float Error1;float ErrorInt;float OutMax;float OutMin;
} PID_t;void PID_Update(PID_t *p);#endif
一、雙環 PID 實現原理
結構
外環(位置環):
直接對位置誤差(目標位置 ? 實際位置)進行 PID 計算,輸出的結果不是直接驅動電機,而是作為內環速度環的目標值。內環(速度環):
對速度誤差(目標速度 ? 實際速度)進行 PID 控制,輸出 PWM 信號驅動電機。
工作流程
外環根據位置誤差計算一個期望速度(正負代表轉動方向和快慢)。
內環將期望速度作為目標,與實時測得的速度比較后,用 PID 算法快速調整 PWM,使實際速度跟隨期望速度。
最終實現位置精確控制的同時,速度變化過程平滑、快速。
代碼對應關系
1.外環
Outer.Actual = Location;
PID_Update(&Outer);
Inner.Target = Outer.Out; // 外環輸出作為內環目標速度
2.內環:
Inner.Actual = Speed;
PID_Update(&Inner);
Motor_SetPWM(Inner.Out);
二、雙環 PID 的優點(相對單環 PID)
穩態精度高
外環處理位置誤差,能消除位置的長期偏差(穩態誤差小)。
內環快速消除速度誤差,讓外環的輸出更快收斂到目標。
動態性能好
內環抑制了速度的突變,提高系統響應速度。
外環不直接控制 PWM,避免了位置環大幅度輸出導致的振蕩。
抗干擾能力強
內環對速度的快速調節可以抵消外部擾動(負載變化、摩擦力變化等)。
抑制超調與振蕩
外環輸出受內環限幅(
OutMax
/OutMin
)保護,避免因位置誤差大而直接全速輸出造成沖過頭。
三、調參技巧(先內環后外環)
1. 為什么要先調內環
內環(速度環)是系統快速響應部分,直接影響外環效果。
如果內環速度跟隨不準,外環即使參數再好,也會出現震蕩、超調甚至失穩。
2. 調內環步驟
關掉外環(直接給內環一個固定的速度目標值)。
先調 Kp:從小到大增加,直到速度響應快且基本無振蕩。
再調 Ki:消除速度穩態誤差,但不要太大,防止低頻振蕩。
視情況加 Kd:抑制速度快速變化的振蕩。
確保內環在目標速度變化時能快速平穩跟隨。
3. 調外環步驟
開啟外環,內環保持調好的參數。
外環先調 Kp:從小到大增加,觀察位置響應速度和超調情況。
若存在位置穩態誤差,可適當增加 Ki(位置環 Ki 一般很小)。
外環 Kd 用于抑制位置變化過快時的超調(相當于對速度的進一步約束)。
外環輸出限幅 (
OutMax
) 要合理,一般設為電機中速范圍,防止位置誤差大時直接給滿速。