2019年電賽已經結束,雖然結果不能令人滿意,但閑下來,還是總結一下電賽學到的東西與失敗的地方。這一次先來談一下一階旋轉倒立擺。
一、題目分析:
拿到一道題目,其實最應該做的事情是分析題目,因為我們往往可以發現某些發揮題是在基礎題的基礎上進行的,但是,可能某些發揮題需要在基礎題的基礎上修改結構,我們也可以發現,題目中的某些問題具有相似性,當我們合並同類項的時候,可以把題目的要求變得簡單。一下,我粘貼過來2013年一階旋轉倒立擺的題目以及要求:



我們很容易知道本題的核心是使得擺桿保持穩定,之后再其基礎上進行一些功能的延伸。為了更好地理解題意,我做了如下的分析:
可以看到,題目中所要求完成的就是通過對一個電機的控制來實現的,控制題的核心還是要落在控制上。歷屆電子設計大賽經常被人戲稱為電子機械設計大賽,雖然控制題的核心是落在控制上,但系統的機械結構也至關重要。此次練習采用的是平衡小車之家的套件,所以省去了搭硬件的很多麻煩。單單就完成題目要求的功能來說,數值分析並不是很必要(強迫症或者沖擊國獎者除外)。因此,本篇內容僅僅從代碼的角度來進行分析。
俗話說的好,不寫注釋的程序猿是流氓。我個人很喜歡在主函數的前面寫上所用引腳的定義,這樣一來方便接線,二來可以避免引腳重復使用,造成不必要的麻煩。
/************************************************
圓周倒立擺實驗
ADC:PA1 Motor:PC0 PC1 Pwm:PB4 PB5
編碼器A相:PB6 B相:PB7
按鍵:PA0 2 3 4
@JackFu
************************************************/
先來談一談我程序的風格,
一、
在主函數里我將初始化的函數寫在了Board_Init();這個函數里,這樣看起來比較方便后期的更改與檢查,減少了主函數的代碼量,使得主函數看起來簡潔、調理。
void Board_Init(void)
{
delay_init();
Adc_Init();
KEY_Init();
Ecoder_TIM4_Init();
Motor_Init();
Timer_pwm_Init(999,8);
// Timer_Init_TIM5(4999,71);
// TIM_SetCompare2(TIM3,800);
uart_init(115200);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
二、我一般習慣於建立兩個文件夾:HARDWARE、CONTROL。其中前者用來存放於硬件相關的一些配置函數,后者用來放與軟件相關的一些配置函數。這樣方便后期的維護與移植。
對於其中用到的基本配置,如ADC,ENCODER,MOTOR等等,此處不再贅述。網上有很多的例程與講解。
該題的核心控制為電機,關鍵函數如下:
//電機調節函數 (控制電機倒立,很重要)
void Motor_Config(short pwm)
{
int speed;
if(start == 1) //開啟倒立,調節倒立擺平衡
{
if(pwm<0 && pwm > -PWM_MAX)
{
speed = PWM_MAX + pwm;
if(speed < 0)
{
speed = 0;
}
TIM_SetCompare2(TIM3,speed);
Motor_reverse();
}
else if(pwm > 0 && pwm < PWM_MAX)
{
speed = PWM_MAX - pwm;
if(speed > 400)
{
speed = 400;
}
TIM_SetCompare2(TIM3,speed);
Motor_Forward();
}
}
else //電位器偏角過大,無法調節,為了安全,關閉電機
{
Motor_stop();
}
}
傳入的參數為電機ENCODER,電位器ADC采集值后經過PID處理的數據,具體的處理函數如下:
short PID_Pos_PosCalc(short NextPoint)
{
register float iError,dError;
iError = pos_Set_Pos - NextPoint; // 偏差
pos_SumError += iError*0.2; // 積分
if(pos_SumError > 1000.0) //積分限幅1000
pos_SumError = 1000.0;
if(pos_SumError < -1000.0)
pos_SumError = -1000.0;
dError = iError - pos_LastError; // 當前微分
pos_LastError = iError;
return(short)(pos_p * iError*0.8 + pos_i * pos_SumError + pos_d * dError); //返回計算值
}
short PID_Ang_PosCalc(short NextPoint)
{
register float iError,dError;
iError = ang_Set_Pos - NextPoint; // 偏差
ang_SumError += iError; // 積分
if(ang_SumError > 1000.0) //積分限幅1000
ang_SumError = 1000.0;
if(ang_SumError < -1000.0)
ang_SumError = -1000.0;
dError = iError - ang_LastError; // 當前微分
ang_LastError = iError;
return(short)(ang_p * iError + ang_i * ang_SumError + ang_d * dError); //返回計算值
}
void Task3(void)
{
short temp,temp1,temp2;
temp = 0;
temp1 = ANG_MIN_VAL; //角度最大值
temp2 = ANG_MAX_VAL; //角度最小值
ang_Cur_Pos = Get_Adc_Average(ADC_Channel_1,15); //獲取當前角度
ang_Set_Pos = ANG_MID_VAL; //將平衡時位置角度應當采樣的值賦給設定值
if((ang_Cur_Pos > temp1)&&(ang_Cur_Pos<temp2))
{
temp += PID_Ang_PosCalc(ang_Cur_Pos); //角度還在可以調整的范圍內
start = 1;
}
else
{ //角度過大,調整不了,為了安全,關機
start = 0;
}
// if(++Position_Target>5)
pos_Cur_Pos = PID_Pos_PosCalc(Encoder),Position_Target=0;
temp -= pos_Cur_Pos;
// if(pos_Cur_Pos > 0 && flag == -1)
// {
// ANG_MID_VAL = ANG_MID_VAL-5;
// flag = flag*(-1);
// }
// else if(pos_Cur_Pos < 0 && flag == 1)
// {
// ANG_MID_VAL = ANG_MID_VAL+5;
// flag = flag*(-1);
// }
Motor_Config(temp);
printf("%d ",temp);
printf("%d\r\n",Encoder);
}
該題的控制采用了雙環的PID來進行處理,在進行學習的過程中發現,僅僅采用電位器環的PID可以使得擺桿倒立起來,但整個擺桿會整體順時針或者逆時針的旋轉,並不能滿足題目要求。正如前面所述,在考慮機械結構時,要整體考慮,否則當做到這里時,發現需要用雙環來進行控制,再改機械結構,會浪費很多的時間。
這里附上源碼 (鏈接: Mycode(雙環控制)
