stm32 TIM1PWM多通道捕獲模式測速
因為一個專案的要求,需要測速,之前就想學一下stm32的捕獲模式了,借這個機會,剛好利用一下,現在分享出來。
硬體:
MCU:stm32f103zet6
平臺:航太電子流行7號智慧小車
顯示:mini12864
軟體:
語言:C語言
IDE:keil5
測速部分環境是典型的測速方法,測速碼盤加光電對管,微控制器採集光電對管模組傳來的PWM波,要對兩個輪子測速,實際速度取倆輪的較大值。
軟體上採用了TIM1的捕獲模式,使用了通道CH1和CH4,分別對應PA8和PA11,先看下官方對輸入捕獲模式的描述:
在輸入捕獲模式下,當檢測到ICx訊號上相應的邊沿後,計數器的當前值被鎖存到捕獲/比較暫存器(TIMx_CCRx)中。當發生捕獲事件時,相應的CCxIF標誌(TIMx_SR暫存器)被置1,如果開放了中斷或者DMA操作,則將產生中斷或者DMA請求。如果發生捕獲事件時CCxIF標誌已經為高,那麼重複捕獲標誌CCxOF(TIMx_SR暫存器)被置1。寫CCxIF=0可清除CCxIF,或讀取儲存在TIMx_CCRx暫存器中的捕獲資料也可清除CCxIF。寫CCxOF=0可清除CCxOF。
以下例子說明如何在TI1輸入的上升沿時捕獲計數器的值到TIMx_CCR1暫存器中,步驟如下:
● 選擇有效輸入端:TIMx_CCR1必須連線到TI1輸入,所以寫入TIMx_CCR1暫存器中的CC1S=01,只要CC1S不為’00’,通道被配置為輸入,並且TIMx_CCR1暫存器變為只讀。
● 根據輸入訊號的特點,配置輸入濾波器為所需的頻寬(即輸入為TIx時,輸入濾波器控制位是TIMx_CCMRx暫存器中的ICxF位)。假設輸入訊號在最多5個內部時鐘週期的時間內抖動,我們須配置濾波器的頻寬長於5個時鐘週期;因此我們可以(以fDTS頻率)連續取樣8次,以確認在TI1上一次真實的邊沿變換,即在TIMx_CCMR1暫存器中寫入IC1F=0011。
● 選擇TI1通道的有效轉換邊沿,在TIMx_CCER暫存器中寫入CC1P=0(上升沿)。
● 配置輸入預分頻器。在本例中,我們希望捕獲發生在每一個有效的電平轉換時刻,因此預分頻器被禁止(寫TIMx_CCMR1暫存器的IC1PS=00)。
● 設定TIMx_CCER暫存器的CC1E=1,允許捕獲計數器的值到捕獲暫存器中。 ● 如果需要,通過設定TIMx_DIER暫存器中的CC1IE位允許相關中斷請求,通過設定TIMx_DIER暫存器中的CC1DE位允許DMA請求。
當發生一個輸入捕獲時:
● 產生有效的電平轉換時,計數器的值被傳送到TIMx_CCR1暫存器。
● CC1IF標誌被設定(中斷標誌)。當發生至少2個連續的捕獲時,而CC1IF未曾被清除,CC1OF也被置1。
● 如設定了CC1IE位,則會產生一箇中斷。
● 如設定了CC1DE位,則還會產生一個DMA請求。 為了處理捕獲溢位,建議在讀出捕獲溢位標誌之前讀取資料,這是為了避免丟失在讀出捕獲溢位標誌之後和讀取資料之前可能產生的捕獲溢位資訊。
注: 設定TIMx_EGR暫存器中相應的CCxG位,可以通過軟體產生輸入捕獲中斷和/或DMA請求。
注意我們在初始化時需要設定好跳變的取樣次數,因為車輪產生的PWM波頻率相對捕獲速度會很低,所以我們應該把取樣次數設定大一點,我們設定的是最大,32次對應值時0x0f。之前沒注意,一直讓他為預設的0,導致得到的結果很大,而且變動也很大,這是取樣不準的結果。
下面是對TM1捕獲模式的初始化,採用72M的晶振速度,配置的定時器頻率是10KHz,週期是65535:
speed.c
void MeaSpeedInit(void)
{
//引數宣告
GPIO_InitTypeDef WheelGPIO_InitStructure;
NVIC_InitTypeDef WheelNVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//速度捕獲通道時基和通道配置 時鐘 10000
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 7200-1;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
/**************左輪配置******************/
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0f;
//左輪速度捕獲輸入管腳配置 PA8
WheelGPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
WheelGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
WheelGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &WheelGPIO_InitStructure);
TIM_ICInit(TIM1, &TIM_ICInitStructure);
TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);
/**************左輪配置結束******************/
/**************右輪配置******************/
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0f;
//右輪速度捕獲輸入管腳配置 PA11
WheelGPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
WheelGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
WheelGPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &WheelGPIO_InitStructure);
TIM_ICInit(TIM1, &TIM_ICInitStructure);
TIM_ITConfig(TIM1, TIM_IT_CC4, ENABLE);
/**************右輪配置結束******************/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
WheelNVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;
WheelNVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
WheelNVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
WheelNVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&WheelNVIC_InitStructure);
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_Cmd(TIM1, ENABLE);
}
注意下面幾個巨集定義,不同通道獲取的捕獲值在不同的暫存器中:
//定義左輪捕捉通道與捕獲值 TIM1通道1
#define LEFTCAPTURECHANNEL TIM1, TIM_IT_CC1
#define LEFTCAPTUREVALUE TIM_GetCapture1(TIM1)
//<span style="font-family: Arial, Helvetica, sans-serif;">TIM1通道4</span>
#define RIGHTCAPTURECHANNEL TIM1, TIM_IT_CC4
#define RIGHTCAPTUREVALUE TIM_GetCapture4(TIM1)
另外,由於使用到的是高階定時器1,中斷入口是 TIM1_CC_IRQn
stm32f10x_it.c
/**-------------------------------------------------------
* @函式名 TIM1_IRQHandler
* @功能 TIM1中斷處理函式,輪子測速捕獲模式
* @引數 無
* @返回值 無
***------------------------------------------------------*/
void TIM1_CC_IRQHandler(void)
{
WheelCaptureIRQ();
}
下面來看中斷處理函式,記錄最相鄰的兩個下降沿的時間,並記錄到buf中,buf中的資料為最近的每一個脈衝週期的時間,方便後面的濾波計算,左右輪測速在同一個中斷函式中
speed.c
/*
左右輪測脈衝週期中斷服務函式 在stm32f10x-it.c中的TIMx_IRQHandler()處註冊使用
*/
void WheelCaptureIRQ(void)
{
if(TIM_GetITStatus(LEFTCAPTURECHANNEL) == SET)
{
speed_contiue=100;
TIM_ClearITPendingBit(LEFTCAPTURECHANNEL);
if(LeftWheelCaptureTime == 0)
{
LeftWheel1stCapture = LEFTCAPTUREVALUE;
LeftWheelCaptureTime = 1;
}
else if(LeftWheelCaptureTime == 1)
{
LeftWheel2ndCapture = LEFTCAPTUREVALUE;
if (LeftWheel2ndCapture > LeftWheel1stCapture)
{
LeftWheelPulsePeriod = (LeftWheel2ndCapture - LeftWheel1stCapture);
}
else
{
LeftWheelPulsePeriod = ((0xFFFF - LeftWheel1stCapture) + LeftWheel2ndCapture);
}
LeftPeriodBuf[LeftPeriodIndex++] = LeftWheelPulsePeriod;//記錄最近的10個值
if(LeftPeriodIndex == PERIOD_BUFSIZE)
LeftPeriodIndex = 0;
LeftWheelCaptureTime= 0;
}
}
if(TIM_GetITStatus(RIGHTCAPTURECHANNEL) == SET)
{
speed_contiue=100;
TIM_ClearITPendingBit(RIGHTCAPTURECHANNEL);
if(RightWheelCaptureTime == 0)
{
RightWheel1stCapture = RIGHTCAPTUREVALUE;
RightWheelCaptureTime = 1;
}
else if(RightWheelCaptureTime == 1)
{
RightWheel2ndCapture = RIGHTCAPTUREVALUE;
if (RightWheel2ndCapture > RightWheel1stCapture)
{
RightWheelPulsePeriod = (RightWheel2ndCapture - RightWheel1stCapture);
}
else
{
RightWheelPulsePeriod = ((0xFFFF - RightWheel1stCapture) + RightWheel2ndCapture);
}
RightPeriodBuf[RightPeriodIndex++] = RightWheelPulsePeriod;//記錄最近的10個值
if(RightPeriodIndex == PERIOD_BUFSIZE) RightPeriodIndex = 0;
RightWheelCaptureTime= 0;
}
}
}
下面開始計算速度,濾除最大值和最小值,然後求加權平均值,這樣得到的相對穩定的速度值,下面兩個函式可以在需要速度的時候呼叫即可,不需要實時計算因為LeftPeriodBuf和RightPeriodBuf的值一直會是最新的。
unsigned int GetLeftSpeed(void)
{
int index,intex_temp=0;
uint16_t LeftPeriodBufTemp[PERIOD_BUFSIZE];
u8 max_index=0,min_index=0;
uint16_t LeftValueTemp=0;
unsigned int LeftValueAvg=0;//平均值
u8 index_total=0;
//將儲存的資料按順序讀取到本地快取
for(index = LeftPeriodIndex;index < PERIOD_BUFSIZE ;index++)
{
LeftPeriodBufTemp[intex_temp++] = LeftPeriodBuf[index];
}
for(index = 0;index < LeftPeriodIndex ;index++)
{
LeftPeriodBufTemp[intex_temp++] = LeftPeriodBuf[index];
}
//取得最大值及其編號
LeftValueTemp = LeftPeriodBufTemp[0];
for(index = 0;index < PERIOD_BUFSIZE-1; index++)
{
if(LeftValueTemp < LeftPeriodBufTemp[index+1])
{
max_index = index+1;
LeftValueTemp = LeftPeriodBufTemp[index+1];
}
}
//取得最小值及其編號
LeftValueTemp = LeftPeriodBufTemp[0];
for(index = 0;index < PERIOD_BUFSIZE-1; index++)
{
if(LeftValueTemp > LeftPeriodBufTemp[index+1])
{
min_index = index+1;
LeftValueTemp = LeftPeriodBufTemp[index+1];
}
}
//去掉最大值和最小值 求加權平均值
for(index = 0;index < PERIOD_BUFSIZE;index++)
{
if((index != min_index) && (index != max_index))
{
LeftValueAvg += LeftPeriodBufTemp[index]*(index+1);
index_total += index+1;
}
}
LeftValueAvg = LeftValueAvg/index_total;//取平均值
for(index = 0;index < PERIOD_BUFSIZE;index++)
{
printf("%d\t",LeftPeriodBufTemp[index]);
}
if(LeftValueAvg == 0) return 0;
LeftValueAvg = 10445/LeftValueAvg;
printf("\t%d\r\n",LeftValueAvg);
return LeftValueAvg;
}
unsigned int GetRightSpeed(void)
{
int index,intex_temp=0;
uint16_t RightPeriodBufTemp[PERIOD_BUFSIZE];
u8 max_index=0,min_index=0;
uint16_t RightValueTemp=0;
unsigned int RightValueAvg=0;//平均值
u8 index_total=0;
//將儲存的資料按順序讀取到本地快取
for(index = RightPeriodIndex;index < PERIOD_BUFSIZE ;index++)
{
RightPeriodBufTemp[intex_temp++] = RightPeriodBuf[index];
}
for(index = 0;index < RightPeriodIndex ;index++)
{
RightPeriodBufTemp[intex_temp++] = RightPeriodBuf[index];
}
//取得最大值及其編號
RightValueTemp = RightPeriodBufTemp[0];
for(index = 0;index < PERIOD_BUFSIZE-1; index++)
{
if(RightValueTemp < RightPeriodBufTemp[index+1])
{
max_index = index+1;
RightValueTemp = RightPeriodBufTemp[index+1];
}
}
//取得最小值及其編號
RightValueTemp = RightPeriodBufTemp[0];
for(index = 0;index < PERIOD_BUFSIZE-1; index++)
{
if(RightValueTemp > RightPeriodBufTemp[index+1])
{
min_index = index+1;
RightValueTemp = RightPeriodBufTemp[index+1];
}
}
//去掉最大值和最小值 求加權平均值
for(index = 0;index < PERIOD_BUFSIZE;index++)
{
if((index != min_index) && (index != max_index))
{
RightValueAvg += RightPeriodBufTemp[index]*(index+1);
index_total += index+1;
}
}
RightValueAvg = RightValueAvg/index_total;//取平均值
if(RightValueAvg == 0) return 0;
RightValueAvg = 10445/RightValueAvg;
return RightValueAvg;
}