說明:如果有哪里說錯了或者說得不好的話還請大家指出來,及時糾正錯誤,或者哪里有更好的解決方法也可以提出來,我們一起學習交流,
目錄
一、編碼電機
二、單片機相關定時器的作用以及配置
1、TIM2的配置
2、TIM3的配置
3、TIM4的配置
4、PID函式
5、讀定時器的計數值
6、calc_motor_rotate_speed()函式
三、2020電賽C題
四、擴展
五、效果展示
一、編碼電機
如果想要控制小車的速度就需要得到小車的速度,想要得到小車的速度就需要用到編碼電機,下面先來大概看一下編碼電機,

大概就是這個樣子了,這里就不介紹它的原理了,可以去查閱相關資料,
二、單片機相關定時器的作用以及配置
本次用到了3個定時器,分別是TIM2、TIM3、TIM4,定時器2用于定時,也就是定時10ms產生中斷,然后到中斷服務函式中去計算速度,定時器3用于輸出PWM波來控制小車的行走,定時器4用來配置成編碼器模式,用來讀取編碼器的資料,
1、TIM2的配置
#include "encoderTim.h"
/*********************
通用定時器6初始化
arr:自動重裝值,
psc:時鐘預分頻數
定時器溢位時間計算方法:Tout=((arr+1)*(psc+1))/Ft us.
Ft=定時器作業頻率,單位:Mhz
************************/
void TIM2_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); ///使能TIM時鐘
TIM_TimeBaseInitStructure.TIM_Period = arr; //自動重裝載值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定時器分頻
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上計數模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM7
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允許定時器6更新中斷 定時器溢位中斷
TIM_Cmd(TIM2,ENABLE); //使能定時器6 開始作業
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定時器6中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //搶占優先級1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子優先級3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //中斷使能
NVIC_Init(&NVIC_InitStructure);
}
TIM2中斷服務函式
void TIM2_IRQHandler(void) //
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){ //檢測是否發送中斷
calc_motor_rotate_speed();
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除中斷
}
TIM2用于定時10ms,然后進入中斷服務函式,執行calc_motor_rotate_speed()函式,對于這個函式的作用,后面再說明,
2、TIM3的配置
#include "iopwm.h"
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA外設
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能定時器3時鐘
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7|GPIO_Pin_6; // GPIOA.7;GPIOA.6
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //TIM3_CH2 TIM3_CH1
GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIO
//定時器TIM3初始化
TIM_TimeBaseInitStruct.TIM_Period=arr;//設定在下一個更新事件裝入活動的自動重裝載暫存器周期的值
TIM_TimeBaseInitStruct.TIM_Prescaler=psc;//設定用來作為TIMx時鐘頻率除數的預分頻值
TIM_TimeBaseInitStruct.TIM_ClockDivision=0;//設定時鐘分割:TDTS = Tck_tim
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//TIM向上計數模式
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//根據TIM_TimeBaseInitStruct中指定的引數初始化TIMx的時間基數單位
//初始化TIM3 Channel 2 PWM模式
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM2;//選擇定時器模式:TIM脈沖寬度調制模式2
TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//比較輸出使能
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_Low;//輸出極性:TIM輸出比較極性
TIM_OC2Init(TIM3,&TIM_OCInitStruct);//根據T指定的引數初始化外設TIM3 OC2
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);使能TIM3在CCR2上的預裝載暫存器
TIM_OC1Init(TIM3,&TIM_OCInitStruct);//根據T指定的引數初始化外設TIM3 OC1
TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);使能TIM3在CCR1上的預裝載暫存器
TIM_Cmd(TIM3,ENABLE);//使能定時器
}
TIM3主要就是輸出PWM信號來控制電機
3、TIM4的配置
#include "encoder.h"
//TIM4 通道1通道2 正交編碼器
void TIM4_ENCODER_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_ICInitTypeDef TIM_ICInitStruct; //輸入捕獲
//PB6 ch1 A,PB7 ch2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能TIM4時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//使能GPIOA時鐘
GPIO_StructInit(&GPIO_InitStructure);//將GPIO_InitStruct中的引數按預設值輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//PA6 PA7浮空輸入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*時基初始化*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); /*使能定時器時鐘 APB1*/
TIM_DeInit(TIM4);
//TIM_TimeBaseStructInit(&TIM_TimeBaseStruct);
TIM_TimeBaseStruct.TIM_Prescaler = ENCODER_TIM_PSC; /*預分頻 0*/
TIM_TimeBaseStruct.TIM_Period = ENCODER_TIM_PERIOD; /*周期(重裝載值)65535*/
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; /*連續向上計數模式*/
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct);
/*編碼器模式配置:同時捕獲通道1與通道2(即4倍頻),極性均為Rising*/
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_ICStructInit(&TIM_ICInitStruct);
TIM_ICInitStruct.TIM_ICFilter = 0; /*輸入通道的濾波引數*/
TIM_ICInit(TIM4, &TIM_ICInitStruct); /*輸入通道初始化*/
TIM_SetCounter(TIM4, CNT_INIT); /*CNT設初值 0 */
TIM_ClearFlag(TIM4,TIM_IT_Update); /*中斷標志清0*/
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); /*中斷使能*/
TIM_Cmd(TIM4,ENABLE); /*使能CR暫存器*/
}
//定時器4中斷服務函式
void TIM4_IRQHandler(void)
{
if(TIM4->SR&0X0001){}//溢位中斷
TIM4->SR&=~(1<<0);//清除中斷標志位
}
TIM4主要就是配置成編碼器模式,用來計數編碼器脈沖個數
4、PID函式
#include "PIDEncoder.h"
#define P_DATA 1.5 //P引數
#define I_DATA 0.1 //I引數
#define D_DATA -0.4 //D引數
typedef struct
{
double Proportion; //比例常數 Proportional Const
double Integral; //積分常數 Integral Const
double Derivative; //微分常數 Derivative Const
int LastError; //Error[-1]
int PrevError; //Error[-2]
}PID;
static PID sPID;
static PID *sptr = &sPID;
void PID_Init(void)
{
sptr->LastError=0; //Error[-1]
sptr->PrevError=0; //Error[-2]
sptr->Proportion=P_DATA; //比例常數
sptr->Integral=I_DATA; //積分常數
sptr->Derivative=D_DATA; //微分常數
}
/**************
入口引數:NextPoint:當前輸出值 SetPoint: 設定值
回傳值:PID調整輸出
***************/
int PID_Calc(int NextPoint,int SetPoint)
{
int iError,Outpid; //當前誤差
iError=SetPoint-NextPoint; //增量計算
Outpid=(sptr->Proportion * iError) //E[k]項
-(sptr->Integral * sptr->LastError) //E[k-1]項
+(sptr->Derivative * sptr->PrevError); //E[k-2]項
sptr->PrevError=sptr->LastError; //存盤誤差,用于下次計算
sptr->LastError=iError;
return Outpid; //回傳增量值
}
PID函式就是用來不斷調整的,使當前值逐漸接近目標值
5、讀定時器的計數值
// 讀取定時器計數值
int read_encoder(void)
{
int encoderNum = 0;
encoderNum = (int)((int16_t)(TIM4->CNT)); /*CNT為uint32, 轉為int16*/
TIM_SetCounter(TIM4, CNT_INIT);/*CNT設初值*/
return encoderNum;
}
6、calc_motor_rotate_speed()函式
calc_motor_rotate_speed()函式在TIM2的中斷服務函式里面,10ms時間到了就執行這個函式
//計算電機轉速(被另一個定時器100ms呼叫1次)
void calc_motor_rotate_speed()
{
/*讀取編碼器的值,正負代表旋轉方向*/
encoderNum = read_encoder();
/* 轉速(1秒鐘轉多少圈)=單位時間內的計數值/總解析度*時間系數 */
// rotateSpeed = (int)encoderNum/TOTAL_RESOLUTION*10;
// Speed2 = (rotateSpeed * 6.0 * 3.14)/1.0; //速度 1秒鐘轉的圈數 X 一圈的距離(輪子周長)/1s
Speed = ((encoderNum/(48.4*4)) * 18.84)/0.1;//((總的脈沖數/電機一圈的脈沖*減數比*4倍頻)*輪子周長)/10ms
para_L = PID_Calc(Speed,SetPoint); //,計數得到增量式PID的增量數值
if((para_L < -3) || (para_L > 3)){ // 不做 PID 調整,避免誤差較小時頻繁調節引起震蕩,
Moto_Left += para_L;
}
if(Moto_Left>19000)Moto_Left=19000;//限幅
if(Moto_Left<-19000)Moto_Left=-19000;//限幅
TIM_SetCompare1(TIM3, Moto_Left);
TIM_SetCompare2(TIM3, 0);
OLED_ShowString(0,1,"hope");
OLED_ShowNum(32,1,SetPoint,8,16);
OLED_ShowString(97,1,"cm/s");
OLED_ShowString(0,4,"now:");
OLED_ShowNum(32,4,Speed,8,16);
OLED_ShowString(97,4,"cm/s");
}
函式中第一步獲取編碼器的值,第二步通過輪子的周長,減數比等一些引數計算出小車當前的實際速度,然后將計算出的實際速度和目標速度作為PID_Calc(Speed,SetPoint)函式的引數進行PID調整,然后得到相應的調整值,再將限幅后的值加載到電機上面,這樣就進行了一次調整,然后在OLED螢屏上面進行顯示目標速度和實際速度,可以看出調整的程序,
三、2020電賽C題
先來看一下2020年TI被電子設計大賽的C題

這里要求了小車到達終點的時間,而且木板的角度還要變化,可以發現小車行駛的路程始終是沒有改變的,所以這里就可以使用編碼電機得到小車實際的速度,然后還知道小車的路程,在規定的時間內到達終點,這樣就可以通過速度 = 路程 /速度來設定相應的目標速度了,不管木板的角度如何變化,小車都會自己調整速度,在規定的時間內到達終點,從而實作了小車速度的倍訓控制,這里還可以加一個陀螺儀進行精確的調整,
當然了不用編碼電機也可以實作,把每一個角度的對應要輸出多少PWM寫死就好了,但是這樣會很麻煩,
四、擴展
這個例程是利用編碼器輸出的脈沖計算得到電機的速度,設定一個目標速度,這樣通過PID的調整就可以實作實際速度逐漸接近目標速度,從而實作速度的控制,
有了這個思想就可以擴展到其他方面了,比如對溫度的控制,有些對溫度要求很高的場所,溫度要一直恒溫,不能太高或者太低,如果是人來進行調整的話那就有點麻煩了,這里也可以通過這個方法來實作,只需要將速度改為溫度即可,即得到當前的實際溫度就可以了,當然還有控制水位高度那些都是類似的了,
這樣是不是就實作了一個簡單的恒溫控制系統,或者水位控制系統,
五、效果展示
視頻中的PID引數還沒有調好,只能說大概是這個現象,作為參考,
STM32單片機實作對小車速度的控制
調得不好,調得不好,調得不好!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/400357.html
標籤:其他
