找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 2971|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

零基礎(chǔ)制作平衡小車【連載】9---增量式PID實(shí)戰(zhàn)(附源碼)

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:223481 發(fā)表于 2020-12-2 17:19 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式

粘貼過(guò)來(lái)的代碼都亂了,貼個(gè)原文鏈接吧:

https://blog.csdn.net/D_SEngineer?spm=1011.2124.3001.5113

不定時(shí)發(fā)送福利,歡迎小伙伴們交流學(xué)習(xí)。


終于到實(shí)戰(zhàn)階段了,前面學(xué)習(xí)的理論部分著實(shí)令人乏味呀。今天來(lái)看看增量式PID怎么在STM32中實(shí)現(xiàn)控制電機(jī)恒速運(yùn)行的。

硬件連接

我用的戰(zhàn)艦開發(fā)板和配套的屏幕,電機(jī)是帶編碼器的電機(jī),第一章有關(guān)于硬件的介紹,這里就不多少了。
1.使用通用定時(shí)器3 通道1 PA6引腳輸出PWM
2.編碼器輸入捕獲 用的是 通用定時(shí)器2 PA0 PA1引腳
3.電機(jī)引腳如下圖

電機(jī)電源正負(fù)極接到TB6612驅(qū)動(dòng)的AO1和AO2(或者BO1和BO2,看你驅(qū)動(dòng)器左側(cè)接的是AIN1、AIN2還是BIN1、BIN2了),編碼器電源接5V電源,黃綠信號(hào)線接單片機(jī)PA0和PA1,如果接反系統(tǒng)會(huì)出現(xiàn)異常,可以仿真看下接收到的脈沖值是不是很大(本例中的count),很大說(shuō)明接反了。將線反接即可。另外就是驅(qū)動(dòng)的PWMA(或者PWMB)接到單片機(jī)的PA6引腳。驅(qū)動(dòng)器的STBY接到5V電源上,BIN1和BIN2一個(gè)接高一個(gè)接低即可。
接線很簡(jiǎn)單,不會(huì)的直接問(wèn)淘寶賣家。其實(shí)看了代碼只要知道PWM輸出引腳和編碼器輸入引腳就可以接線了,其他都是固定接線方式。下圖是我的硬件圖,很亂,小車還沒組裝好,都是散件,湊合看吧。

代碼實(shí)現(xiàn)的功能
  • 按鍵設(shè)定目標(biāo)轉(zhuǎn)速
  • stm32通過(guò)pwm控制電機(jī)維持目標(biāo)轉(zhuǎn)速運(yùn)轉(zhuǎn)
  • 并且具備顯示功能,把重要參數(shù)顯示在屏幕上
代碼思路
  • 要先把PWM調(diào)通,實(shí)現(xiàn)PWM控制電機(jī)旋轉(zhuǎn)
  • 在實(shí)現(xiàn)PWM控制電機(jī)旋轉(zhuǎn)之后,加上按鍵調(diào)節(jié)占空比
  • 將編碼器檢測(cè)加進(jìn)去,能實(shí)現(xiàn)檢測(cè)到電機(jī)反饋的脈沖數(shù)
  • 將編碼器檢測(cè)的脈沖數(shù)顯示出來(lái),或者串口打印出來(lái),總之要實(shí)時(shí)知道這個(gè)值,方便后面調(diào)試PID參數(shù)
  • 之后再將PID加進(jìn)去
  • 整理代碼
部分重要源碼講解
  • PWM
    PWM的生成之前章節(jié)已經(jīng)講解過(guò)了,在這里就不復(fù)述了,直接上代碼。在這里需要注意一點(diǎn)就是之前PWM設(shè)置的是1Khz,這里我們?cè)O(shè)置成20Khz。大家可以看看注釋,理解一下怎么設(shè)置成不同的頻率。
/*使用通用定時(shí)器3  通道1  PA6引腳輸出PWM------------ PWM信號(hào) 周期和占空比的計(jì)算 --------------- ARR :自動(dòng)重裝載寄存器的值 CLK_cnt:計(jì)數(shù)器的時(shí)鐘,等于 Fck_int / (psc+1) = 72M/(psc+1) PWM 信號(hào)的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M 占空比P=CCR/(ARR+1) 時(shí)鐘分頻71,系統(tǒng)總線時(shí)鐘72M,可以算出定時(shí)器時(shí)鐘為72/(3+1)=18M在這里我們需要設(shè)定PWM頻率為20Khz=0.05ms,因此自動(dòng)重裝載的值A(chǔ)RR=900.因?yàn)槎〞r(shí)器時(shí)鐘為18M=0.0556us,因此只需要累加900次,就等于0.05ms=20Khz了。因此我們配置如下TIM_TimeBaseInitTypeStructure.TIM_Prescaler = 3;                   //定時(shí)器時(shí)鐘分頻TIM_TimeBaseInitTypeStructure.TIM_Period = (900 - 1);                //自動(dòng)重裝載的值 */void PWM_Init(void){    //基本定時(shí)器初始化部分        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    TIM_OCInitTypeDef TIM_OCInitStructure;    //初始化定時(shí)器    TIM_DeInit(TIM3);        //GPIO初始化部分    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO,ENABLE);    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA,&GPIO_InitStructure);        //使能定時(shí)器時(shí)鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //死區(qū)時(shí)間時(shí)鐘源,該例程只進(jìn)行PWM輸出,沒有用到死區(qū),因此不設(shè)定    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //向上計(jì)數(shù)模式    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    //自動(dòng)重裝載的值設(shè)定為1000    TIM_TimeBaseInitStructure.TIM_Period = (900 - 1);    //定時(shí)器周期分頻數(shù)    TIM_TimeBaseInitStructure.TIM_Prescaler = 3;    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;        //初始化定時(shí)器3    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);            //PWM初始化部分    //pwm1模式    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    //初始化電平    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    //使能輸出比較    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //初始化PWM占空比為0    TIM_OCInitStructure.TIM_Pulse = 0;    //初始化pwm    TIM_OC1Init(TIM3,&TIM_OCInitStructure);        //使能TIM3在CCR2上的預(yù)裝載寄存器    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);     //使能定時(shí)器3    TIM_Cmd(TIM3,ENABLE);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 編碼器輸入
    不懂的可以仔細(xì)看看代碼注釋,注釋也看不懂的,可以翻翻我之前的帖子,代碼如下:
//輸入捕獲  通用定時(shí)器2  PA0  PA1void Advance_TIM_Init(void){    //GPIO初始化        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;    TIM_ICInitTypeDef  TIM_ICInitStructure;    NVIC_InitTypeDef NVIC_InitStructure;                             // 設(shè)置中斷來(lái)源    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                         // 設(shè)置搶占優(yōu)先級(jí)                      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;                   // 設(shè)置子優(yōu)先級(jí)    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    NVIC_Init(&NVIC_InitStructure);        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(GPIOA,&GPIO_InitStructure);            //基本定時(shí)器初始化部分    //使能定時(shí)器時(shí)鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//設(shè)置缺省值    // 自動(dòng)重裝載寄存器的值,累計(jì)TIM_Period+1個(gè)頻率后產(chǎn)生一個(gè)更新或者中斷        TIM_TimeBaseStructure.TIM_Period = 60000;                // 驅(qū)動(dòng)CNT計(jì)數(shù)器的時(shí)鐘 = Fck_int/(psc+1)        TIM_TimeBaseStructure.TIM_Prescaler = 0;                // 時(shí)鐘分頻因子 ,配置死區(qū)時(shí)間時(shí)需要用到        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;                        // 計(jì)數(shù)器計(jì)數(shù)模式,設(shè)置為向上計(jì)數(shù)        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;                        // 重復(fù)計(jì)數(shù)器的值,沒用到不用管        TIM_TimeBaseStructure.TIM_RepetitionCounter=0;                // 初始化定時(shí)器        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);        //編碼器模式    TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);            TIM_ICStructInit(&TIM_ICInitStructure);    TIM_ICInitStructure.TIM_ICFilter = 10;                //10經(jīng)驗(yàn)值,不必糾結(jié),起濾波作用    TIM_ICInit(TIM2, &TIM_ICInitStructure);            //使能中斷    TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);                // 清除中斷標(biāo)志位        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);        // 使能高級(jí)控制定時(shí)器,計(jì)數(shù)器開始計(jì)數(shù)    TIM_Cmd(TIM2, ENABLE);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 按鍵和屏幕顯示
    按鍵很簡(jiǎn)單,我注釋的也很詳細(xì),這是放在主函數(shù)中循環(huán)檢測(cè)的,這個(gè)也不用多說(shuō)了吧,直接代碼:
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == 0)  {     SetPoint=160;           //設(shè)定目標(biāo)     z=1; } if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0) {     SetPoint=120;           //設(shè)定目標(biāo)     z=2; }  if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == 0) {     SetPoint=20;           //設(shè)定目標(biāo)     z=3; }  if(z != j) {     LCD_Clear(WHITE);       //delay_ms(120); } j = z ;  LCD_Fill(0,SetPoint,lcddev.width,SetPoint+2,BLUE);        //目標(biāo)值設(shè)定,一條直線          //如果FLAG=1時(shí),說(shuō)明200ms到了,將采樣的count值打印在屏幕上,         //k是x軸坐標(biāo),200MS+1,conunt是y軸坐標(biāo),代表當(dāng)前時(shí)刻的采樣的脈沖值 if(flag == 1)    {     k++;     LCD_Fill(k,count,k+2,count+2,BLUE);     flag = 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 增量式PID
    重點(diǎn)來(lái)了,學(xué)了這么多理論,你會(huì)發(fā)現(xiàn),其實(shí)代碼就一點(diǎn)點(diǎn),兩三行就完事了。先看代碼:
/********************增量式PID控制設(shè)計(jì)************************************/int IncPIDCalc(int PresentPoint) {    int iError , iIncpid;       //iIncpid增量值   iError當(dāng)前誤差    iError = SetPoint-PresentPoint;                    //增量計(jì)算    iIncpid =(Proportion * iError)                  //E[k]項(xiàng)              -(Integral * LastError)     //E[k-1]項(xiàng)              +(Derivative * PrevError);  //E[k-2]項(xiàng)    PrevError = LastError;    LastError=iError;    return (iIncpid);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

是不是很簡(jiǎn)單,就這么幾行代碼,但是我卻寫了好幾篇帖子來(lái)學(xué)習(xí)它的理論部分。過(guò)來(lái)之后就能看清廬山真面目了。

根據(jù)前面推導(dǎo)的公式,這里只是將這些公式用C寫出來(lái)。首先定義一個(gè)當(dāng)前誤差iError和一個(gè)計(jì)算出來(lái)的增量值iIncpid。當(dāng)前誤差iError=目標(biāo)值SetPoint-傳感器采集的當(dāng)前值PresentPoint,之后進(jìn)行增量式PID運(yùn)算,得出增量值iIncpid。

將式子和程序?qū)Ρ认,是不是一樣的?再看看還是不一樣。上面式子中沒有減號(hào),代碼中卻有;上面式子中明明微分項(xiàng)是𝐄𝐤−𝟐𝐄(𝐤−𝟏)+𝐄(𝐤−𝟐),即便將Kp*Td/T看成Kd也還是和程序不一樣呀。程序中確直接乘上一個(gè)上上次誤差PrevError,你說(shuō)一樣嗎?你心中有沒有這樣的疑問(wèn)?有的話也不奇怪,我朋友看了也有(斜眼笑)。

解惑

看完上面的推導(dǎo),應(yīng)該能解決你心中的疑惑了吧。

源碼

昨天開通了一個(gè)公眾號(hào),以后代碼就放到公眾號(hào)了,后續(xù)我會(huì)將之前的代碼也都放到公眾號(hào)上,歡迎小伙伴們交流學(xué)習(xí)哈。關(guān)注公眾號(hào)回復(fù)
“增量式PID”
獲取源碼。

調(diào)試過(guò)程

本來(lái)想寫在這個(gè)帖子上的,不過(guò)一看時(shí)間有點(diǎn)晚了,明天再開一貼吧,明天還有工作呢,不熬夜,早睡早起。晚安,小伙伴們。


分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏2 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表