粘貼過(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);}是不是很簡(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)晚了,明天再開一貼吧,明天還有工作呢,不熬夜,早睡早起。晚安,小伙伴們。
|