粘貼過來的代碼都亂了,貼個原文鏈接吧: https://blog.csdn.net/D_SEngineer?spm=1011.2124.3001.5113 不定時發送福利,歡迎小伙伴們交流學習。 |
終于到實戰階段了,前面學習的理論部分著實令人乏味呀。今天來看看增量式PID怎么在STM32中實現控制電機恒速運行的。 硬件連接我用的戰艦開發板和配套的屏幕,電機是帶編碼器的電機,第一章有關于硬件的介紹,這里就不多少了。
1.使用通用定時器3 通道1 PA6引腳輸出PWM
2.編碼器輸入捕獲 用的是 通用定時器2 PA0 PA1引腳
3.電機引腳如下圖

電機電源正負極接到TB6612驅動的AO1和AO2(或者BO1和BO2,看你驅動器左側接的是AIN1、AIN2還是BIN1、BIN2了),編碼器電源接5V電源,黃綠信號線接單片機PA0和PA1,如果接反系統會出現異常,可以仿真看下接收到的脈沖值是不是很大(本例中的count),很大說明接反了。將線反接即可。另外就是驅動的PWMA(或者PWMB)接到單片機的PA6引腳。驅動器的STBY接到5V電源上,BIN1和BIN2一個接高一個接低即可。
接線很簡單,不會的直接問淘寶賣家。其實看了代碼只要知道PWM輸出引腳和編碼器輸入引腳就可以接線了,其他都是固定接線方式。下圖是我的硬件圖,很亂,小車還沒組裝好,都是散件,湊合看吧。  代碼實現的功能- 按鍵設定目標轉速
- stm32通過pwm控制電機維持目標轉速運轉
- 并且具備顯示功能,把重要參數顯示在屏幕上
代碼思路- 要先把PWM調通,實現PWM控制電機旋轉
- 在實現PWM控制電機旋轉之后,加上按鍵調節占空比
- 將編碼器檢測加進去,能實現檢測到電機反饋的脈沖數
- 將編碼器檢測的脈沖數顯示出來,或者串口打印出來,總之要實時知道這個值,方便后面調試PID參數
- 之后再將PID加進去
- 整理代碼
部分重要源碼講解- PWM
PWM的生成之前章節已經講解過了,在這里就不復述了,直接上代碼。在這里需要注意一點就是之前PWM設置的是1Khz,這里我們設置成20Khz。大家可以看看注釋,理解一下怎么設置成不同的頻率。
/*使用通用定時器3 通道1 PA6引腳輸出PWM------------ PWM信號 周期和占空比的計算 --------------- ARR :自動重裝載寄存器的值 CLK_cnt:計數器的時鐘,等于 Fck_int / (psc+1) = 72M/(psc+1) PWM 信號的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M 占空比P=CCR/(ARR+1) 時鐘分頻71,系統總線時鐘72M,可以算出定時器時鐘為72/(3+1)=18M在這里我們需要設定PWM頻率為20Khz=0.05ms,因此自動重裝載的值ARR=900.因為定時器時鐘為18M=0.0556us,因此只需要累加900次,就等于0.05ms=20Khz了。因此我們配置如下TIM_TimeBaseInitTypeStructure.TIM_Prescaler = 3; //定時器時鐘分頻TIM_TimeBaseInitTypeStructure.TIM_Period = (900 - 1); //自動重裝載的值 */void PWM_Init(void){ //基本定時器初始化部分 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; //初始化定時器 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); //使能定時器時鐘 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //死區時間時鐘源,該例程只進行PWM輸出,沒有用到死區,因此不設定 TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //向上計數模式 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //自動重裝載的值設定為1000 TIM_TimeBaseInitStructure.TIM_Period = (900 - 1); //定時器周期分頻數 TIM_TimeBaseInitStructure.TIM_Prescaler = 3; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //初始化定時器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上的預裝載寄存器 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能定時器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
- 編碼器輸入
不懂的可以仔細看看代碼注釋,注釋也看不懂的,可以翻翻我之前的帖子,代碼如下:
//輸入捕獲 通用定時器2 PA0 PA1void Advance_TIM_Init(void){ //GPIO初始化 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 設置中斷來源 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 設置搶占優先級 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 設置子優先級 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); //基本定時器初始化部分 //使能定時器時鐘 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//設置缺省值 // 自動重裝載寄存器的值,累計TIM_Period+1個頻率后產生一個更新或者中斷 TIM_TimeBaseStructure.TIM_Period = 60000; // 驅動CNT計數器的時鐘 = Fck_int/(psc+1) TIM_TimeBaseStructure.TIM_Prescaler = 0; // 時鐘分頻因子 ,配置死區時間時需要用到 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 計數器計數模式,設置為向上計數 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 重復計數器的值,沒用到不用管 TIM_TimeBaseStructure.TIM_RepetitionCounter=0; // 初始化定時器 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經驗值,不必糾結,起濾波作用 TIM_ICInit(TIM2, &TIM_ICInitStructure); //使能中斷 TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE); // 清除中斷標志位 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 使能高級控制定時器,計數器開始計數 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
- 按鍵和屏幕顯示
按鍵很簡單,我注釋的也很詳細,這是放在主函數中循環檢測的,這個也不用多說了吧,直接代碼:
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == 0) { SetPoint=160; //設定目標 z=1; } if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0) { SetPoint=120; //設定目標 z=2; } if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == 0) { SetPoint=20; //設定目標 z=3; } if(z != j) { LCD_Clear(WHITE); //delay_ms(120); } j = z ; LCD_Fill(0,SetPoint,lcddev.width,SetPoint+2,BLUE); //目標值設定,一條直線 //如果FLAG=1時,說明200ms到了,將采樣的count值打印在屏幕上, //k是x軸坐標,200MS+1,conunt是y軸坐標,代表當前時刻的采樣的脈沖值 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
重點來了,學了這么多理論,你會發現,其實代碼就一點點,兩三行就完事了。先看代碼:
/********************增量式PID控制設計************************************/int IncPIDCalc(int PresentPoint) { int iError , iIncpid; //iIncpid增量值 iError當前誤差 iError = SetPoint-PresentPoint; //增量計算 iIncpid =(Proportion * iError) //E[k]項 -(Integral * LastError) //E[k-1]項 +(Derivative * PrevError); //E[k-2]項 PrevError = LastError; LastError=iError; return (iIncpid);}是不是很簡單,就這么幾行代碼,但是我卻寫了好幾篇帖子來學習它的理論部分。過來之后就能看清廬山真面目了。 根據前面推導的公式,這里只是將這些公式用C寫出來。首先定義一個當前誤差iError和一個計算出來的增量值iIncpid。當前誤差iError=目標值SetPoint-傳感器采集的當前值PresentPoint,之后進行增量式PID運算,得出增量值iIncpid。

將式子和程序對比下,是不是一樣的?再看看還是不一樣。上面式子中沒有減號,代碼中卻有;上面式子中明明微分項是𝐄𝐤−𝟐𝐄(𝐤−𝟏)+𝐄(𝐤−𝟐),即便將Kp*Td/T看成Kd也還是和程序不一樣呀。程序中確直接乘上一個上上次誤差PrevError,你說一樣嗎?你心中有沒有這樣的疑問?有的話也不奇怪,我朋友看了也有(斜眼笑)。 解惑

看完上面的推導,應該能解決你心中的疑惑了吧。 源碼昨天開通了一個公眾號,以后代碼就放到公眾號了,后續我會將之前的代碼也都放到公眾號上,歡迎小伙伴們交流學習哈。關注公眾號回復
“增量式PID”
獲取源碼。
 調試過程本來想寫在這個帖子上的,不過一看時間有點晚了,明天再開一貼吧,明天還有工作呢,不熬夜,早睡早起。晚安,小伙伴們。
|