源:增量式PID的stm32實現,整定過程html
首先說說增量式PID的公式,這個關係到MCU算法公式的書寫,實際上兩個公式的寫法是同一個公式變換來得,不一樣的是係數的差別。
資料上比較多的是:
還有一種是:
算法
感受第二種的Kp Ki Kd比較清楚,更好理解,下面介紹的就以第二種來吧。(比例、積分、微分三個環節的做用這裏就詳細展開,百度會有不少)
硬件部分:
控制系統的控制對象是4個空心杯直流電機,電機帶光電編碼器,能夠反饋轉速大小的波形。電機驅動模塊是普通的L298N模塊。
芯片型號,STM32F103ZET6
軟件部分:
PWM輸出:TIM3,能夠直接輸出4路不通佔空比的PWM波
PWM捕獲:STM32除了TIM6 TIM7其他的都有捕獲功能,使用TIM1 TIM2 TIM4 TIM5四個定時器捕獲四個反饋信號
PID的採樣和處理:使用了基本定時器TIM6,溢出時間就是個人採樣週期,理論上T越小效果會越好,這裏我取20ms,依據控制對象吧,若是控制水溫什麼的採樣週期會是幾秒幾分鐘什麼的。
上面的PWM輸出和捕獲關於定時器的設置都有例程,我這裏是這樣的:
TIM3輸出四路PWM,在引腳 C 的 GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9輸出
四路捕獲分別是TIM4 TIM1 TIM2 TIM5 ,對應引腳是: PB7 PE11 PB3 PA1
高級定時器tim1的初始化略不一樣,它的中斷」名稱「和通用定時器不一樣,見代碼:函數
/*功能名稱IM3_PWM_Init(u16 arr,u16 psc) 描述 TIM3產生四路PWM */ void TIM3_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外設和AFIO複用功能模塊時鐘使能 GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE); //Timer3全映射 GPIOC-> 6,7,8,9 //用於TIM3的CH2輸出的PWM經過該LED顯示 //設置該引腳爲複用輸出功能,輸出TIM3 CH1 CH2 CH3 CH4 的PWM脈衝波形 GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; //初始化GPIO GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_ResetBits(GPIOC,GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9);//默認電機使能端狀態:不使能 TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 這裏是72分頻,那麼時鐘頻率就是1M TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //選擇定時器模式:TIM脈衝寬度調製模式1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能 TIM_OCInitStructure.TIM_Pulse = 0; //設置待裝入捕獲比較寄存器的脈衝值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高 TIM_OC1Init(TIM3, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR1上的預裝載寄存器 TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR2上的預裝載寄存器 TIM_OC3Init(TIM3, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR3上的預裝載寄存器 TIM_OC4Init(TIM3, &TIM_OCInitStructure); //根據TIM_OCInitStruct中指定的參數初始化外設TIMx TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIMx在CCR4上的預裝載寄存器 TIM_ARRPreloadConfig(TIM3, ENABLE); //使能TIMx在ARR上的預裝載寄存器 TIM_Cmd(TIM3, ENABLE); //使能TIMx外設 } /*功能名稱TIM4_PWMINPUT_INIT(u16 arr,u16 psc) 描述 PWM輸入初始化*/ void TIM4_PWMINPUT_INIT(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化結構體 NVIC_InitTypeDef NVIC_InitStructure; //中斷配置 TIM_ICInitTypeDef TIM4_ICInitStructure; //TIM4 PWM配置結構體 GPIO_InitTypeDef GPIO_InitStructure; //IO口配置結構體 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //Open TIM4 clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //open gpioB clock GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIO 7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 /*配置中斷優先級*/ NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM4_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM4_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM4_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM4_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM4_ICInitStructure.TIM_ICFilter = 0x3; //Filter:過濾 TIM_PWMIConfig(TIM4, &TIM4_ICInitStructure); //PWM輸入配置 TIM_SelectInputTrigger(TIM4, TIM_TS_TI2FP2); //選擇有效輸入端 TIM_SelectSlaveMode(TIM4, TIM_SlaveMode_Reset); //配置爲主從復位模式 TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);//啓動定時器的被動觸發 TIM_ITConfig(TIM4, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中斷配置 TIM_ClearITPendingBit(TIM4, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 TIM_Cmd(TIM4, ENABLE); } void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_CC2) != RESET)//捕獲1發生捕獲事件 { duty_TIM4 = TIM_GetCapture1(TIM4); //採集佔空比 if (TIM_GetCapture2(TIM4)>600) period_TIM4 = TIM_GetCapture2(TIM4);//簡單的處理 CollectFlag_TIM4 = 0; } TIM_ClearITPendingBit(TIM4, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 } /*功能名稱TIM1_PWMINPUT_INIT(u16 arr,u16 psc) 描述 PWM輸入初始化*/ void TIM1_PWMINPUT_INIT(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化結構體 NVIC_InitTypeDef NVIC_InitStructure; //中斷配置 TIM_ICInitTypeDef TIM1_ICInitStructure; //PWM配置結構體 GPIO_InitTypeDef GPIO_InitStructure; //IO口配置結構體 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //Open TIM1 clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); //open gpioE clock GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE); //Timer1徹底重映射 TIM1_CH2->PE11 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //GPIO 11 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOE, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 /*配置中斷優先級*/ NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn; //TIM1捕獲中斷 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM1_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM1_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM1_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM1_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM1_ICInitStructure.TIM_ICFilter = 0x03; //Filter:過濾 TIM_PWMIConfig(TIM1, &TIM1_ICInitStructure); //PWM輸入配置 TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2); //選擇有效輸入端 TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset); //配置爲主從復位模式 TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);//啓動定時器的被動觸發 // TIM_ITConfig(TIM1, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中斷配置 TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE); //通道2 捕獲中斷打開 //TIM_ClearITPendingBit(TIM1, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 TIM_Cmd(TIM1, ENABLE); } void TIM1_CC_IRQHandler(void) { { if (TIM_GetITStatus(TIM1, TIM_IT_CC2) != RESET)//捕獲1發生捕獲事件 { duty_TIM1 = TIM_GetCapture1(TIM1); //採集佔空比 if (TIM_GetCapture2(TIM1)>600) period_TIM1 = TIM_GetCapture2(TIM1); CollectFlag_TIM1 = 0; } } TIM_ClearITPendingBit(TIM1, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 } /*功能名稱TIM2_PWMINPUT_INIT(u16 arr,u16 psc) 描述 PWM輸入初始化*/ void TIM2_PWMINPUT_INIT(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化結構體 NVIC_InitTypeDef NVIC_InitStructure; //中斷配置 TIM_ICInitTypeDef TIM2_ICInitStructure; //TIM2 PWM配置結構體 GPIO_InitTypeDef GPIO_InitStructure; //IO口配置結構體 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Open TIM2 clock // RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //open gpioB clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外設和AFIO複用功能模塊時鐘 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //關閉JTAG GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE); //Timer2徹底重映射 TIM2_CH2->PB3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //GPIO 3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //浮空輸入 上拉輸入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 /*配置中斷優先級*/ NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM2_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM2_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM2_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM2_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM2_ICInitStructure.TIM_ICFilter = 0x3; //Filter:過濾 TIM_PWMIConfig(TIM2, &TIM2_ICInitStructure); //PWM輸入配置 TIM_SelectInputTrigger(TIM2, TIM_TS_TI2FP2); //選擇有效輸入端 TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); //配置爲主從復位模式 TIM_SelectMasterSlaveMode(TIM2, TIM_MasterSlaveMode_Enable);//啓動定時器的被動觸發 TIM_ITConfig(TIM2, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中斷配置 TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 TIM_Cmd(TIM2, ENABLE); } void TIM2_IRQHandler(void) { { if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)//捕獲1發生捕獲事件 { duty_TIM2 = TIM_GetCapture1(TIM2); //採集佔空比 if (TIM_GetCapture2(TIM2)>600) period_TIM2 = TIM_GetCapture2(TIM2); CollectFlag_TIM2 = 0; } } TIM_ClearITPendingBit(TIM2, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 } /*功能名稱TIM5_PWMINPUT_INIT(u16 arr,u16 psc) 描述 PWM輸入初始化*/ void TIM5_PWMINPUT_INIT(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //TIM的初始化結構體 NVIC_InitTypeDef NVIC_InitStructure; //中斷配置 TIM_ICInitTypeDef TIM5_ICInitStructure; //TIM4 PWM配置結構體 GPIO_InitTypeDef GPIO_InitStructure; //IO口配置結構體 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //Open TIM4 clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //open gpioB clock GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //GPIO 1 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //浮空輸入 上拉輸入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器週期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來做爲TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 /*配置中斷優先級*/ NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM5_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM5_ICInitStructure.TIM_ICFilter = 0x3; //Filter:過濾 TIM_PWMIConfig(TIM5, &TIM5_ICInitStructure); //PWM輸入配置 TIM_SelectInputTrigger(TIM5, TIM_TS_TI2FP2); //選擇有效輸入端 TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_Reset); //配置爲主從復位模式 TIM_SelectMasterSlaveMode(TIM5, TIM_MasterSlaveMode_Enable);//啓動定時器的被動觸發 TIM_ITConfig(TIM5, TIM_IT_CC2|TIM_IT_Update, ENABLE); //中斷配置 TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 TIM_Cmd(TIM5, ENABLE); } void TIM5_IRQHandler(void) { { if (TIM_GetITStatus(TIM5, TIM_IT_CC2) != RESET)//捕獲1發生捕獲事件 { duty_TIM5 = TIM_GetCapture1(TIM5); //採集佔空比 if (TIM_GetCapture2(TIM5)>600) period_TIM5 = TIM_GetCapture2(TIM5); CollectFlag_TIM5 = 0; } } TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中斷標誌位 }
PID部分:
準備部分:先定義PID結構體:編碼
typedef struct { int setpoint;//設定目標 int sum_error;//偏差累計 float proportion ;//比例常數 float integral ;//積分常數 float derivative;//微分常數 int last_error;//e[-1] int prev_error;//e[-2] }PIDtypedef;
這裏注意一下成員的數據類型,依據實際須要來定的。
在文件中定義幾個關鍵變量:spa
float Kp = 0.32 ; //比例常數 float Ti = 0.09 ; //積分時間常數 float Td = 0.0028 ; //微分時間常數 #define T 0.02 //採樣週期 #define Ki Kp*(T/Ti) // Kp Ki Kd 三個主要參數 #define Kd Kp*(Td/T)
PID.H裏面主要的幾個函數:調試
void PIDperiodinit(u16 arr,u16 psc); //PID 採樣定時器設定 void incPIDinit(void); //初始化,參數清零清零 int incPIDcalc(PIDtypedef*PIDx,u16 nextpoint); //PID計算 void PID_setpoint(PIDtypedef*PIDx,u16 setvalue); //設定 PID預期值 void PID_set(float pp,float ii,float dd);//設定PID kp ki kd三個參數 void set_speed(float W1,float W2,float W3,float W4);//設定四個電機的目標轉速
PID處理過程:
岔開一下:這裏我控制的是電機的轉速w,實際上電機的反饋波形的頻率f、電機轉速w、控制信號PWM的佔空比a三者是大體線性的正比的關係,這裏強調這個的目的是
由於樓主在前期一直搞不懂我控制的轉速怎麼和TIM4輸出的PWM的佔空比聯繫起來,後來想清楚裏面的聯繫以後經過公式把各個係數算出來了。
正題:控制流程是這樣的,首先我設定我須要的車速(對應四個輪子的轉速),而後PID就是開始響應了,它先採樣電機轉速,獲得誤差值E,帶入PID計算公式,獲得調整量也就是最終更改了PWM的佔空比,不斷調節,直到轉速在穩態的一個小範圍上下浮動。
上面講到的「獲得調整量」就是增量PID的公式:code
int incPIDcalc(PIDtypedef *PIDx,u16 nextpoint) { int iError,iincpid; iError=PIDx->setpoint-nextpoint; //當前偏差 /*iincpid= //增量計算 PIDx->proportion*iError //e[k]項 -PIDx->integral*PIDx->last_error //e[k-1] +PIDx->derivative*PIDx->prev_error;//e[k-2] */ iincpid= //增量計算 PIDx->proportion*(iError-PIDx->last_error) +PIDx->integral*iError +PIDx->derivative*(iError-2*PIDx->last_error+PIDx->prev_error); PIDx->prev_error=PIDx->last_error; //存儲偏差,便於下次計算 PIDx->last_error=iError; return(iincpid) ; }
註釋掉的是第一種寫法,沒註釋的是第二種以Kp KI kd爲係數的寫法,實際結果是同樣的。
處理過程放在了TIM6,溢出週期時間就是是PID裏面採樣週期(區分於反饋信號的採樣,反饋信號採樣是1M的頻率)
相關代碼:htm
void TIM6_IRQHandler(void) // 採樣時間到,中斷處理函數 { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//更新中斷 { frequency1=1000000/period_TIM4 ; //經過捕獲的波形的週期算出頻率 frequency2=1000000/period_TIM1 ; frequency3=1000000/period_TIM2 ; frequency4=1000000/period_TIM5 ; /********PID1處理**********/ PID1.sum_error+=(incPIDcalc(&PID1,frequency1)); //計算增量並累加 pwm1=PID1.sum_error*4.6875 ; //pwm1 表明將要輸出PWM的佔空比 frequency1=0; //清零 period_TIM4=0; /********PID2處理**********/ PID2.sum_error+=(incPIDcalc(&PID2,frequency2)); //計算增量並累加 Y=Y+Y' pwm2=PID2.sum_error*4.6875 ; //將要輸出PWM的佔空比 frequency2=0; period_TIM1=0; /********PID3處理**********/ PID3.sum_error+=(incPIDcalc(&PID3,frequency3)); //常規PID控制 pwm3=PID3.sum_error*4.6875 ; //將要輸出PWM的佔空比 frequency3=0; period_TIM2=0; /********PID4處理**********/ PID4.sum_error+=(incPIDcalc(&PID4,frequency4)); //計算增量並累加 pwm4=PID4.sum_error*4.6875 ; //將要輸出PWM的佔空比 frequency4=0; period_TIM5=0; } TIM_SetCompare(pwm1,pwm2,pwm3,pwm4); //從新設定PWM值 TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除中斷標誌位 }
上面幾個代碼是PID實現的關鍵部分
整定過程:
辦法有很多,這裏用的是先KP,再TI,再TD,在微調。其餘的辦法特別是有個尼古拉斯法我發現不適合我這個控制對象。
先Kp,就是消除積分和微分部分的影響,這裏我糾結過究竟是讓Ti 等於一個很大的值讓Ki=Kp*(T/Ti)裏面的KI接近零,仍是直接定義KI=0,TI=0.
而後發現前者無法找到KP使系統震盪的臨界值,第二個辦法能夠獲得預期的效果:即KP大了會產生震盪,小了會讓系統穩定下來,固然這個時候是有穩態偏差的。
隨後把積分部分加進去,KI=Kp*(T/Ti)這個公式用起來,而且不斷調節TI 。TI太大系統穩定時間比較長。
而後加上Kd =Kp*(Td/T),對於系統響應比較滯後的狀況效果好像好一些,我這裏的電機反映挺快的,因此Td值很小。
最後就是幾個參數調節一下,讓波形好看一點。這裏的波形實際反映的是採集回來的轉速值,用STM32的DAC功能輸出和轉速對應的電壓,用示波器採集的。
最後的波形是這樣的:對象
pwm3=PID3.sum_error*4.6875 ; blog
電機反饋信號的頻率f 和電機是成正比的,也就是說,我若是須要電機轉速 w=2pi rad/s的話,我對應的捕獲頻率是電機在這個轉速時光電編碼器反饋波形的頻率大小。因此我PID計算的實際上是以這個頻率爲標準的調整量,PID的設定值也是頻率,增量計算的也是頻率,固然你用轉速 w 和PWM佔空比 a 作PID的計算也是可行的,只要找到 轉速=(係數1)*頻率=(係數2)*佔空比 這個關係裏面的係數就行。
我來講說這個4.6875怎麼來的吧,這裏我把佔空比當作「佔空量」(佔空比:在一串理想的脈衝週期序列中(如方波),正脈衝的持續時間與脈衝總週期的比值。),若是PWM週期是1000份,高電平是300份,那個人佔空量就是300,在STM32裏面這個佔空量是能夠直接做爲參數設定給定時器的,用的函數就是setcompare(),你本身查一下歷程看看。
好,個人電機的最大轉速是2rps,也就是一秒2圈,個人編碼器這個時候的反饋頻率應該是1536(查看編碼器的參數),這個時候佔空比須要100%,也就是1000的佔空量。電機不轉呢,反饋頻率就是0,佔空量就是0。
簡單的就是佔空量對應0頻率,1000佔空量對應1536頻率。獲得 佔空量=0.651*頻率。
那我程序是4.6875呢?我繼續說!
例程裏面關於PWM波形輸出的TIM3的初始化是:TIM3_PWM_Init(1000-1,72-1);
這樣就是72分頻,定時器頻率變成1M,對應成1 us計算很方便。1000是指PWM波形的週期是1000,這裏正好是1000us(注意:這裏1000這個參數越大,說明佔空比的分辨率越高,可是在定時器頻率不變的前提下,pwm週期越大,輸出PWM的頻率就越小)。可是這樣的參數下,PWM的頻率只有1K,電機產出明顯的噪音,通過調試,電機在10K的頻率下控制的效果比較好。也就是說我要湊個10Kpwm輸出,可是pwm週期不能過小,咱要保證控制精度啊。
因此經過計算:TIM3_PWM_Init(7200-1,1-1);//定時器72M運行,週期7200份,電機頻率正好是10K
好了這裏的7200放到以前的計算中,係數就是4.6875了。
關於定時器的一些設置作在了main函數裏面,上面沒給出,如今貼出來!
float Kp = 0.32 ; //比例常數 float Ti = 0.09 ; //積分常數 float Td = 0.015 ; //微分常數 #define T 0.02 //採樣週期 //#define Ka Kp*(1+(T/Ti)+(Td/T)) //另外一種公式的三個參數 //#define Kb (Kp)*(1+(2*Td/T)) //#define Kc Kp*Td/T #define Ki Kp*(T/Ti) // Kp Ki Kd 三個主要參數 #define Kd Kp*(Td/T) u8 start_flag=0; u16 pwm1=0,pwm2=0,pwm3=0,pwm4=0; //PWM 波形佔空量 佔空比=PWMx/7200 u8 flag_lcd=0;//液晶屏幕更新標誌 u8 flag_bluetooth =0;//藍牙驗證狀態 1:已發出驗證信息 0:未發出驗證信息 u8 status_bluetooth=0;//藍牙鏈接狀態位 1:已鏈接 0:未鏈接 int main(void) { u8 len ,t; SystemInit(); delay_init(72); //延時初始化 NVIC_Configuration(); //中斷配置 中斷分組2:2位搶佔優先級,2位響應優先級 init_LCD_IO() ; //初始化LCD控制引腳 PG4 5 uart_init(9600); //串口初始化 lcd_init(); //LCD顯示 LED_GPIO_Config(); // led 初始化 MOTOR_INIT(); Dac1_Init(); //DAC初始化 incPIDinit(); //PID初始化 置零 fuzzy_init() ; KEY_Init(); EXTIX_Init(); //外部中斷初始化 TIM3_PWM_Init(7200-1,1-1); //參數1*參數2/(72e6)=1/f f:須要的電機頻率 //PWM輸出 頻率:1KHZ pwm週期:1000us 參數: 1000-1 72-1 定時器頻率 1M 特色;電機頻率過低,電機噪音,精度Vmax/1000 //PWM輸出 頻率:10KHZ pwm週期:100us 參數: 100-1 72-1 定時器頻率 1M 特色;頻率合適,控制精度過低 //PWM輸出 頻率:10KHZ 週期:7200 參數: 1000-1 72-1 TIM4_PWMINPUT_INIT(0xffff,72-1); //pwm輸入初始化以1M的頻率捕捉 TIM1_PWMINPUT_INIT(0xffff,72-1); //pwm輸入初始化以1M的頻率捕捉 TIM2_PWMINPUT_INIT(0xffff,72-1); //pwm輸入初始化以1M的頻率捕捉 TIM5_PWMINPUT_INIT(0xffff,72-1); //pwm輸入初始化以1M的頻率捕捉 MOTOR_OUT(1,0,1,0,1,0,1,0);//轉速全爲正,速度都是0 PID_set(Kp,Ki,Kd); //初始化 PID參數 printf("Kp=%f\r\n",Kp); //輸出參數:便於調試觀察 printf("Ki=%f\r\n",Ki); printf("Kd=%f\r\n",Kd); // PID_setpoint(&PID1,500); //開機就設定輪子轉動,便於調試,可註釋掉 // PID_setpoint(&PID2,200); // PID_setpoint(&PID3,500); // PID_setpoint(&PID4,300); PIDperiodinit(40,36000-1); //設定PID採樣週期 T=20ms 72000 000/36 000 = 2 KHz 和 T 對應 //set_speed(3,3,3,3); TIM_Cmd(TIM6, ENABLE); //使能TIMx 開啓PID處理 while(1) { // printf("Kp=%f\r\n",PID3.proportion2); //輸出參數:便於調試觀察 if(USART_RX_STA&0x8000) //若是完成一次接收 { TIM_Cmd(TIM6, DISABLE); // 關閉PID運算 stop(); //PID相關參數清零,而且小車中止運動 len=USART_RX_STA&0x3fff;//獲得這次接收到的數據長度,本控制系統應該len==15 或者 2 printf("MCU_GET:"); for(t=0;t<len;t++) //返回因此數值 { USART_SendData(USART1, USART_RX_BUF[t]);//向串口1發送數據 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發送結束 } printf("\r\n");//插入換行 if(len==15) //長度15的是速度控制報文 { LED1=0;// LIGHT ON //報文格式 :x+正負號+x速度+y+正負號+y速度+w+正負號+w速度 //例如 :x+123y+000w+000,表示只有x方向速度的,其他均爲零的 table1[3]=USART_RX_BUF[1];table1[4]=USART_RX_BUF[2]; table1[5]=USART_RX_BUF[3];table1[6]=USART_RX_BUF[4]; //更新到LCD table2[3]=USART_RX_BUF[6];table2[4]=USART_RX_BUF[7]; table2[5]=USART_RX_BUF[8];table2[6]=USART_RX_BUF[9]; table3[3]=USART_RX_BUF[11];table3[4]=USART_RX_BUF[12]; table3[5]=USART_RX_BUF[13];table3[6]=USART_RX_BUF[14]; lcd_init(); //LCD顯示 X=(USART_RX_BUF[2]-0x30)*100+(USART_RX_BUF[3]-0x30)*10+(USART_RX_BUF[4]-0x30) ; //獲得Vx if(USART_RX_BUF[1]=='-')X=-X; //加上正負號 Y=(USART_RX_BUF[7]-0x30)*100+(USART_RX_BUF[8]-0x30)*10+(USART_RX_BUF[9]-0x30) ; //獲得Xy if(USART_RX_BUF[6]=='-')Y=-Y; W=(USART_RX_BUF[12]-0x30)*100+(USART_RX_BUF[13]-0x30)*10+(USART_RX_BUF[14]-0x30) ; //獲得w W=W/100.0; //實際的W值縮小100倍 if(USART_RX_BUF[11]=='-')W=-W; printf("X=%d\r\n",X); //參數反饋,便於調試 printf("Y=%d\r\n",Y); printf("W=%f\r\n",W); kinematics(X,Y,W,&W1,&W2,&W3,&W4); //運動學方程計算,獲得四個輪子的轉速 printf("W1=%f\r\n",W1); printf("W2=%f\r\n",W2); printf("W3=%f\r\n",W3); printf("W4=%f\r\n",W4); // LED0=!LED0; //LED翻轉 set_speed(W1,W2,W3,W4); //設定輪子轉速,實際是更新了PID目標值 TIM_Cmd(TIM6, ENABLE); //使能TIMx 開啓PID LED1=1;// LIGHT Off } if(len==2) { if ((USART_RX_BUF[0]=='B') && (USART_RX_BUF[1]=='L') && (flag_bluetooth==1)) { status_bluetooth=1 ; flag_bluetooth=0; printf("blue_OK\r\n"); } } USART_RX_STA=0; //數據處理完畢,清除狀態寄存器,準備下組數據接收 len=0; } } }