主控:STM32F103C8T6
1. 電機(jī)測速
在進(jìn)行速度控制之前,我們首先需要進(jìn)行速度采樣,這里參見這篇博文
2. 電機(jī)驅(qū)動
? 這里不細(xì)說電機(jī)驅(qū)動模塊的選型和使用,而是說一個常見的誤區(qū)。我們驅(qū)動電機(jī)要使用兩路PWM,一般是一路給PWM信號,一路是純低電平。但這其實是不好的,正確的做法是一路給PWM,另一路給純高電平。此時PWM的占空比越低,電機(jī)的速度越快。
? 如果大家使用的是類似于A4950或者DRV8870這樣的電機(jī)驅(qū)動芯片,它們的數(shù)據(jù)手冊中都會有這樣的描述
? 這是DRV8870的,明確說明了PWM加高電平是最佳控制方式。
這是A4950的,用曲線圖的方式說明了PWM加高電平時電流會更加穩(wěn)定。
? 此外,如果使用PWM加高電平的控制方式,在設(shè)置速度為0時,兩路信號都會輸出純高電平,此時會觸發(fā)芯片的剎車模式,使得電機(jī)快速停止轉(zhuǎn)動,避免我們制作的小車出現(xiàn)剎不住車的情況。
3. 速度環(huán)實現(xiàn)
? PID的原理就不贅述了,我們直接看代碼。
? 現(xiàn)在我們已經(jīng)在定時器中斷中完成了電機(jī)的速度采樣,得到了電機(jī)的速度,接下來我們需要進(jìn)行PID計算,并輸出相應(yīng)占空比的PWM給電機(jī)。
? 但是在此之前,我們需要編寫PID的計算函數(shù)和進(jìn)行相關(guān)初始化,下面是代碼
PID.h部分
#ifndef _PID_H_
#define _PID_H_
#include "stm32f1xx.h"
#include "encoder.h"
#include <stdio.h>
#include "control.h"
//PID三個參數(shù)的值
#define KP_speed 2
#define KI_speed 0
#define KD_speed 0
typedef struct _PID//PID參數(shù)結(jié)構(gòu)體
{
float kp,ki,kd;
float err,lastErr;
float integral,maxIntegral; //積分值
float output,maxOutput;
}PID;
void PID_Init(void);
float Speed_PID_Realize(PID* pid,float target,float feedback);//一次PID計算
PID.c部分
#include "pid.h"
PID pid_speed;
/**********************************
* 功能:PID結(jié)構(gòu)體參數(shù)初始化
* 輸入:無
* 返回:無
* *******************************/
void PID_Init(void)//PID參數(shù)初始化
{
pid_speed.err = 0;
pid_speed.integral = 0;
pid_speed.maxIntegral = 1000;
pid_speed.maxOutput = __HAL_TIM_GetAutoreload(&PWM_TIM);
pid_speed.lastErr = 0;
pid_speed.output = 0;
pid_speed.kp = KP_speed;
pid_speed.ki = KI_speed;
pid_speed.kd = KD_speed;
}
/****************************************
* 作用:速度環(huán)PID計算
* 參數(shù):PID參數(shù)結(jié)構(gòu)體地址;目標(biāo)值;反饋值
* 返回值:無
* ****************************************/
float Speed_PID_Realize(PID* pid,float target,float feedback)//一次PID計算
{
pid->err = target - feedback;
if(pid->err < 0.3 && pid->err > -0.3) pid->err = 0;//pid死區(qū)
pid->integral += pid->err;
if(pid->ki * pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral / pid->ki;//積分限幅
else if(pid->ki * pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral / pid->ki;
if(target == 0) pid->integral = 0; // 剎車時清空i
pid->output = (pid->kp * pid->err) + (pid->ki * pid->integral) + (pid->kd * (pid->err - pid->lastErr));//全量式PID
//輸出限幅
if(target >= 0)//正轉(zhuǎn)時
{
if(pid->output < 0) pid->output = 0;
else if(pid->output > pid->maxOutput) pid->output = pid->maxOutput;
}
else if(target < 0)//反轉(zhuǎn)時
{
if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
else if(pid->output > 0) pid->output = 0;
}
pid->lastErr = pid->err;
if(target == 0) pid->output = 0; // 剎車時直接輸出0
return pid->output;
}
? 這里的速度環(huán)代碼在一般的PID上加了點東西。首先是PID死區(qū),即err值很小時認(rèn)為err=0,讓速度發(fā)生很小的抖動時,PID輸出不會變化,避免大幅度震蕩的產(chǎn)生;其次是當(dāng)目標(biāo)值為0的時候讓積分部分和輸出同時等于0,使剎車更加迅速。
? PID_Init()函數(shù)需要放在main.c的循環(huán)之前,Speed_PID_Realize()函數(shù)需要放在定時器中斷的電機(jī)測速部分后面。
? 所以現(xiàn)在的定時器中斷函數(shù)如下
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定時器回調(diào)函數(shù),用于計算速度和PID計算
{
if(htim->Instance==GAP_TIM.Instance)//間隔定時器中斷,是時候計算速度了
{
/**********************************電機(jī)測速************************************/
motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上計數(shù)(正轉(zhuǎn)),返回值為0,否則返回值為1
motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一個周期內(nèi)的總計數(shù)值等于目前計數(shù)值加上溢出的計數(shù)值
if(motor1.lastCount - motor1.totalCount > 19000) // 在計數(shù)值溢出時進(jìn)行防溢出處理
{
motor1.overflowNum++;
motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一個周期內(nèi)的總計數(shù)值等于目前計數(shù)值加上溢出的計數(shù)值
}
else if(motor1.totalCount - motor1.lastCount > 19000) // 在計數(shù)值溢出時進(jìn)行防溢出處理
{
motor1.overflowNum--;
motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一個周期內(nèi)的總計數(shù)值等于目前計數(shù)值加上溢出的計數(shù)值
}
motor1.speed = (float)(motor1.totalCount - motor1.lastCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 3000;//算得每秒多少轉(zhuǎn),除以4是因為4倍頻
motor1.speed = Speed_Low_Filter(motor1.speed,speed_Record);
motor1.lastCount = motor1.totalCount; //記錄這一次的計數(shù)值
/***************************PID速度環(huán)**********************************/
motor_Out = Speed_PID_Realize(&pid_speed,Target_Speed,motor1.speed);
//Target_Speed是目標(biāo)速度,自行定義就好
if(motor_Out >= 0)
{
__HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_FORWARD, 1000);
__HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_BACKWARD, 1000-motor_Out);
}
else
{
__HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_BACKWARD, 1000);
__HAL_TIM_SetCompare(&MOTOR1_TIM, MOTOR1_CHANNEL_FORWARD, 1000+motor_Out);
}
/**********************************************************************/
}
}
如果覺得定時器中斷函數(shù)看起來很亂,可以將測速和PID分別封裝成函數(shù),定時器中斷負(fù)責(zé)調(diào)用函數(shù)即可。
現(xiàn)在我們就已經(jīng)能實現(xiàn)電機(jī)的速度環(huán)控制了,剩下的就是進(jìn)行PID調(diào)參了。
4. 速度環(huán)調(diào)參
這里需要說明一下,我給電機(jī)測速加上了平均濾波,PID參數(shù)在濾波和不濾波的情況下會有比較大的區(qū)別
電機(jī)速度環(huán)比較好調(diào),我的調(diào)參經(jīng)驗是這樣:
- 先讓I=D=0,使P從很小值開始增加,直到電機(jī)的速度達(dá)到目標(biāo)速度的一半左右。
- 一點點增大I,使得電機(jī)的速度能夠很快達(dá)到目標(biāo)值,哪怕有點震蕩、超調(diào)也沒事。
- 增大D,使超調(diào)和震蕩逐步減小,還剩有一點點超調(diào)就行了。保留一點點超調(diào)是為了使達(dá)到穩(wěn)定所需的時間比較短。
為了更好地進(jìn)行PID調(diào)參,我們最好使用能畫曲線的串口上位機(jī),這里推薦VOFA+,使用VOFA+進(jìn)行PID調(diào)參可以看這里
速度環(huán)調(diào)得好的效果應(yīng)該是這樣的:
上圖中,綠線是電機(jī)的實際速度,紅線是電機(jī)的目標(biāo)速度。我們主要檢查以下幾點:
- 從靜止到正最大轉(zhuǎn)速
- 從正最大轉(zhuǎn)速到反最大轉(zhuǎn)速
- 從正最大轉(zhuǎn)速或反最大轉(zhuǎn)速到靜止
如果電機(jī)到正反最大轉(zhuǎn)速所需時間較短,有一點點超調(diào),沒有震蕩;到靜止時所需時間很短,且沒有震蕩,那么就說明速度環(huán)調(diào)好了。
下圖展示了速度環(huán)調(diào)好后電機(jī)速度穩(wěn)定和停下來所需的時間
電機(jī)速度穩(wěn)定需要60ms左右,而停下來只需要20ms左右文章來源:http://www.zghlxwxcb.cn/news/detail-419236.html
當(dāng)然不同電機(jī)不同環(huán)境下情況會不一樣,我這里只是一個參考。文章來源地址http://www.zghlxwxcb.cn/news/detail-419236.html
到了這里,關(guān)于【STM32】使用HAL庫進(jìn)行電機(jī)速度環(huán)PID控制,代碼+調(diào)參的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!