01 前言
如果我們想對電機進行速度或者轉角的精確控制,需要使用到很多算法,比如非常經典的PID控制算法,或者一些只能算法,但這些算法都需要傳感器來提供轉速或轉角的反饋值,對于電機來說,編碼器是非常流行并且實用的電機配套傳感器,本文使用STM32F103C8T6+L298N+MG513P30電機進行直流電機的編碼器測速。
02 編碼器原理
1.分類
光電式編碼器的精準度比霍爾式要高,但是由于它需要紅外線發生器和接收器,相對來說造價要貴一些。現在我們比較常用的是霍爾式增量編碼器,有很多電機都會自帶編碼器。
2.測速方法分類
(1)M法測速
編碼器輸出的脈沖個數代表了位置,那么單位時間里的脈沖個數表示這段時間里的平均速度。因此,我們可以通過計量單位時間脈沖個數即可以估算出平均速度,稱為M法測速(測脈沖個數)測速原理如圖所示。
例如,若編碼器每轉產生N個脈沖,在T時間(單位s)產生m個脈沖,那么平均轉速如下式所示:
式中 n——平均轉速(r/min);
T——測速采樣時間(s);
m——T時間內測得的編碼器脈沖個數;
N——編碼器每轉脈沖數。
(2)T法測速
若用M法測速,在記錄時間短、速度低的時候,只能記錄幾個脈沖,則分辨率降低。針對該問題,目前解決方法為:可以采用輸出碼盤脈沖為一個時間間隔,然后用計數器記錄在這段時間里高速脈沖源發出的脈沖數。即通過采集到脈沖源脈沖數來計量編碼器兩個脈沖時間間隔,從而估算出速度,稱為T法測速(測脈沖周期),測速原理圖如圖所示。
T法測速,利用編碼器產生的脈沖用作門電路的觸發信號;用已知頻率f的時鐘信號做輸入。若控制門電路在編碼器脈沖上升沿到來時開始導通,再次上升沿到來時關閉,即計數器只記錄一個編碼器脈沖周期內的時鐘脈沖個數。若在編碼器相鄰脈沖之間記錄的脈沖時鐘個數為m,那么,可以計算兩個編碼器脈沖的時間間隔為m /f;若編碼器每轉有N個線脈沖輸出,那么我們就知道編碼器轉過1/N轉時需要時間m /f。據此,可計算與編碼器同軸轉速為公式所示。
式中 n ——平均轉速(r/min);
f ——時鐘脈沖頻率(個/s);
m ——兩個編碼器脈沖之間的時鐘脈沖個數;
N ——編碼器每轉脈沖數。
編碼器一般會輸出兩路信號,分別稱為A相和B相,它們相差90°,因此編碼器也稱為十字碼盤,通過捕獲兩路輸出信號可以測算出電機的轉速和轉向。
STM32使用編碼器的方法有兩種分別是外部中斷法和輸入捕獲法,這兩種方法都屬于M法測速,兩種方法比較來說外部中斷法占用CPU資源較多,平時比較常用的是輸入捕獲法,但博主兩種方法都調試出來了,因此記錄下來跟大家分享一下。
03 外部中斷法測速
對于外部中斷的知識,各個講STM32的教程都有,我就不過多贅述,外部中斷的初始化都一樣,主要是出發外部中斷時需要進行的操作。
看這張圖,正轉方向是信號向右走,因為我們是同時捕獲兩路信號,有以下幾種情況:
然后我們設置兩個變量用來存儲捕獲的脈沖數,每捕獲到一次脈沖信號根據上表進行判斷,正轉時使其加1;反轉時使其減1;然后配置一個定時器,每隔一段時間反饋一次測速值。
1.外部中斷配置
先編寫一個函數初始化外部中斷,使用PB12-15引腳復用為外部中斷輸入,外部中斷配置步驟如下:
1.端口初始化:RCC_APB2PeriphClockCmd()、GPIO_Init()
2.使能復用功能時鐘:RCC_APB2PeriphClockCmd()
3.設置IO口與中斷線的映射關系:GPIO_EXTILineConfig()
4.初始化線上中斷:EXTI_Init()
5.配置中斷分組:NVIC_Init()
完整函數如下:
/**************************************************************************
功能:應用外部中斷方式采集編碼器數據,使用M法測速反饋車輪實時速度
函數:Encoder_EXTIX_Init(void) EXTI15_10_IRQHandler(void)
作者:K.Fire
日期:2022.01.30
引腳:PB12(左輪A相) PB13(左輪B相) PB14(右輪A相) PB15(右輪B相)
參數:void
*************************************************************************/
int Encoder_L_EXTI=0;
int Encoder_R_EXTI=0;
void Encoder_EXTIX_Init(void)
{
//1.端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 |GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//2.使能復用功能時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//3.設置IO口與中斷線的映射關系
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15);
//4.初始化線上中斷
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line12 | EXTI_Line13 | EXTI_Line14 | EXTI_Line15;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//跳變沿觸發
EXTI_Init(&EXTI_InitStruct);
//5.配置中斷分組
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
2.外部中斷服務函數
函數的判斷邏輯與上表一致,外部中斷捕獲判斷函數如下:
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line12) != RESET)//左輪A相 PB12
{
EXTI_ClearITPendingBit(EXTI_Line12); //清除LINE上的中斷標志位
if(PBin(12)==0) //這里判斷檢測到的是否是下降沿
{
if(PBin(13)==0) Encoder_L_EXTI++;//B相的電平如果是低,電機就是正轉加1
else Encoder_L_EXTI--;//否則就是反轉減1
}
else //上升沿
{
if(PBin(13)==1) Encoder_L_EXTI++; //B相電平如果為高,電機就是正轉加1
else Encoder_L_EXTI--;//否則就是反轉減1
}
}
if(EXTI_GetITStatus(EXTI_Line13) != RESET)//左輪B相 PB13
{
EXTI_ClearITPendingBit(EXTI_Line13); //清除LINE上的中斷標志位
if(PBin(13)==0) //這里判斷檢測到的是否是下降沿
{
if(PBin(12)==1) Encoder_L_EXTI++;//B相的電平如果是高,電機就是正轉加1
else Encoder_L_EXTI--;//否則就是反轉減1
}
else //上升沿
{
if(PBin(12)==0) Encoder_L_EXTI++; //B相電平如果為高,電機就是正轉加1
else Encoder_L_EXTI--;//否則就是反轉減1
}
}
if(EXTI_GetITStatus(EXTI_Line14) != RESET)//右輪A相 PB14
{
EXTI_ClearITPendingBit(EXTI_Line14); //清除LINE上的中斷標志位
if(PBin(14)==0) //這里判斷檢測到的是否是下降沿
{
if(PBin(15)==0) Encoder_R_EXTI++;//B相的電平如果是低,電機就是正轉加1
else Encoder_R_EXTI--;//否則就是反轉減1
}
else //上升沿
{
if(PBin(15)==1) Encoder_R_EXTI++; //B相電平如果為高,電機就是正轉加1
else Encoder_R_EXTI--;//否則就是反轉減1
}
}
if(EXTI_GetITStatus(EXTI_Line15) != RESET)//右輪B相 PB15
{
EXTI_ClearITPendingBit(EXTI_Line15); //清除LINE上的中斷標志位
if(PBin(15)==0) //這里判斷檢測到的是否是下降沿
{
if(PBin(14)==1) Encoder_R_EXTI++;//A相的電平如果是高,電機就是正轉加1
else Encoder_R_EXTI--;//否則就是反轉減1
}
else //上升沿
{
if(PBin(14)==0) Encoder_R_EXTI++; //A相電平如果為低,電機就是正轉加1
else Encoder_R_EXTI--;//否則就是反轉減1
}
}
}