標(biāo)題: 零基礎(chǔ)制作平衡小車【連載】4---STM32定時(shí)器編碼器模式(附源碼) [打印本頁(yè)]

作者: 自動(dòng)化工程師    時(shí)間: 2020-11-27 14:14
標(biāo)題: 零基礎(chǔ)制作平衡小車【連載】4---STM32定時(shí)器編碼器模式(附源碼)
回顧

上一章節(jié)學(xué)習(xí)了PWM生成,剛好買的元器件也都到了。測(cè)試下代碼,完美運(yùn)行。這不又趁著周末,進(jìn)行下一個(gè)環(huán)節(jié)—定時(shí)器編碼器模式。
目的是為下一步PID控制做準(zhǔn)備。

遇到的問題

周末學(xué)習(xí)編碼器模式也是一波三折呀,沒人指點(diǎn)真的是寸步難行呀。氣的直咬牙。
說說啥情況:
我用上節(jié)的PWM輸出控制電機(jī)旋轉(zhuǎn),用編碼器采集脈沖,想把這個(gè)脈沖顯示在LCD上,就這個(gè)顯示功能搗鼓了一天多,最后還是沒成功,最后沒辦法了,只能串口打印出來。就在昨天晚上準(zhǔn)備更新博客的時(shí)候想想不能就這么算了,又去調(diào)試了幾下,奇跡出現(xiàn)了,LCD竟然可以顯示了,弄完之后十點(diǎn)多了,就去睡覺了。目前問題還是不確定在哪,之前的代碼修改的也找不到了,晚上回家再試試改成之前的代碼,找找問題的根本原因。
改天單獨(dú)開個(gè)帖子說說吧,今天先學(xué)習(xí)編碼器。

編碼器的作用

首先需要明白的一點(diǎn)就是為什么要用編碼器模式,而不是直接用輸入捕獲。
最主要的原因就是編碼器采集的是兩個(gè)信號(hào),根據(jù)兩個(gè)信號(hào)的高低電平來判斷是正轉(zhuǎn)還是反轉(zhuǎn),比輸入捕獲用一個(gè)信號(hào)的抗干擾能力強(qiáng)。如果一個(gè)信號(hào)有干擾,而另一個(gè)信號(hào)沒有干擾,則計(jì)數(shù)器不會(huì)計(jì)數(shù),
就像下圖這樣,圖片在中文參考手冊(cè)中找的

圖中紅線標(biāo)注的是毛刺,當(dāng)TI1有干擾的情況下,計(jì)數(shù)器是不會(huì)繼續(xù)向上累加的。如果用輸入捕獲的話,左邊紅色框中有兩個(gè)脈沖,也就是說累加器會(huì)加兩次。這就出現(xiàn)了誤差,本來不該加,他卻加了。
這就是首選編碼器模式的主要原因。知道了他的作用還需要知道原理。

編碼器采集原理

1.理解框圖

上圖是定時(shí)器的框圖,首先我們會(huì)從電機(jī)屁股后面的編碼器引出兩根信號(hào)線接到單片機(jī)的編碼器引腳,如上圖第一個(gè)框內(nèi),接到某定時(shí)器的TIMx_CH1和TIMx_CH2兩個(gè)通道的引腳上。進(jìn)來之后進(jìn)行濾波和邊沿檢測(cè)之后輸出TI1FP1和TI1FP2,之后就直接到2號(hào)框了。2號(hào)框是將TI1FP1和TI1FP2接入編碼器接口,在這里判斷是該向上還是向下計(jì)數(shù),之后來到3號(hào)框,進(jìn)行分頻,最后來到CNT計(jì)數(shù)器進(jìn)行計(jì)數(shù)。我們可以定時(shí)采集CNT里面的值,這樣就能算出電機(jī)的轉(zhuǎn)速和其他一些參數(shù)了。
具體配置一會(huì)看代碼吧。大致了解一下定時(shí)器框圖。

2.理解計(jì)數(shù)方式
框圖明白了,腦海里就明白了編碼器工作的大致原理了。但還需要明白一些細(xì)節(jié)方面的東西。比如它是怎么通過兩路信號(hào)進(jìn)行計(jì)數(shù)的,到底是向上還是向下計(jì)數(shù)等等。



其實(shí)也很簡(jiǎn)單,結(jié)合上面兩張圖片很好理解。簡(jiǎn)單說一下,明白一個(gè)其他都明白了。

首先假定此時(shí)是電機(jī)正轉(zhuǎn),還有一點(diǎn)要知道,編碼器兩個(gè)信號(hào)相差90°的相位角。一個(gè)周期的信號(hào)可以分為0 π/2 π 3/2*π 2π,如上圖,剛好差了π/2。上圖是兩個(gè)通道上下邊沿都檢測(cè)(SMS=’011’) 。
當(dāng)TI1為上升沿時(shí)時(shí),此時(shí)TI2為低電平,對(duì)應(yīng)下面的圖,先看【有效邊沿】,因?yàn)槭强吹腡I1上升沿,所以第一項(xiàng)選擇【僅在TI1計(jì)數(shù)】,之后找對(duì)應(yīng)的【TI1FP1 信號(hào)項(xiàng)】,選擇上升。因?yàn)閳D中TI1是上升沿,對(duì)應(yīng)TI2是低電平,因此在【相對(duì)信號(hào)的電平】那一項(xiàng)應(yīng)該選擇低,對(duì)應(yīng)起來就是向上計(jì)數(shù)。如下圖紅色部分。
理解了TI1上升沿計(jì)數(shù)之后,其他的都一樣了。

到這里,基本上編碼器模式已經(jīng)掌握的差不多了,下面就開始代碼了。

STM32F103ZET6生成編碼器代碼#include "PWM.h"//通用定時(shí)器TIM3   PA6void PWM_Init(void){    //基本定時(shí)器初始化部分        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    TIM_OCInitTypeDef TIM_OCInitStructure;    //初始化定時(shí)器    TIM_DeInit(TIM3);        //GPIO初始化部分    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA,&GPIO_InitStructure);        //使能定時(shí)器時(shí)鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //死區(qū)時(shí)間時(shí)鐘源,該例程只進(jìn)行PWM輸出,沒有用到死區(qū),因此不設(shè)定    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //向上計(jì)數(shù)模式    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    //自動(dòng)重裝載的值設(shè)定為1000    TIM_TimeBaseInitStructure.TIM_Period = (1000 - 1);    //定時(shí)器周期分頻數(shù)    TIM_TimeBaseInitStructure.TIM_Prescaler = (72 - 1);    //重復(fù)計(jì)數(shù)器的值,只有高級(jí)定時(shí)器1和8有重復(fù)計(jì)數(shù)功能,其他都沒有,因此設(shè)定為0    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;        //初始化定時(shí)器3    TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);                        //PWM初始化部分    //pwm1模式    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;    //初始化電平    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    //使能輸出比較    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //初始化PWM占空比為0    TIM_OCInitStructure.TIM_Pulse = 1000;    //初始化pwm    TIM_OC1Init(TIM3,&TIM_OCInitStructure);        //使能TIM3在CCR2上的預(yù)裝載寄存器    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);     //使能定時(shí)器3    TIM_Cmd(TIM3,ENABLE);    }//編碼器模式  通用定時(shí)器2  PA0  PA1void Advance_TIM_Init(void){    //GPIO初始化        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;    TIM_ICInitTypeDef  TIM_ICInitStructure;    NVIC_InitTypeDef NVIC_InitStructure;             // 設(shè)置中斷組為0    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);                                // 設(shè)置中斷來源    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                         // 設(shè)置搶占優(yōu)先級(jí)    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;                   // 設(shè)置子優(yōu)先級(jí)    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    NVIC_Init(&NVIC_InitStructure);        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        GPIO_Init(GPIOA,&GPIO_InitStructure);            //基本定時(shí)器初始化部分    //使能定時(shí)器時(shí)鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//設(shè)置缺省值    // 自動(dòng)重裝載寄存器的值,累計(jì)TIM_Period+1個(gè)頻率后產(chǎn)生一個(gè)更新或者中斷        TIM_TimeBaseStructure.TIM_Period = (359*4);                // 驅(qū)動(dòng)CNT計(jì)數(shù)器的時(shí)鐘 = Fck_int/(psc+1)        TIM_TimeBaseStructure.TIM_Prescaler = (72 - 1);                // 時(shí)鐘分頻因子 ,配置死區(qū)時(shí)間時(shí)需要用到        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;                        // 計(jì)數(shù)器計(jì)數(shù)模式,設(shè)置為向上計(jì)數(shù)        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;                        // 重復(fù)計(jì)數(shù)器的值,沒用到不用管        TIM_TimeBaseStructure.TIM_RepetitionCounter=0;                // 初始化定時(shí)器        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);        //編碼器模式    TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);    TIM_ICStructInit(&TIM_ICInitStructure);    TIM_ICInitStructure.TIM_ICFilter = 10;    TIM_ICInit(TIM2, &TIM_ICInitStructure);    /*    //輸入捕獲初始化部分    //選擇捕獲通道    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;    //濾波設(shè)置    TIM_ICInitStructure.TIM_ICFilter = 0x0;    //設(shè)置捕獲的邊沿  上升沿    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising ;    // 1分頻,即捕獲信號(hào)的每個(gè)有效邊沿都捕獲    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1 ;    // 設(shè)置捕獲通道的信號(hào)來自于哪個(gè)輸入通道,有直連和非直連兩種    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;    // 初始化PWM輸入模式,不能用下面這個(gè)函數(shù)進(jìn)行初始化輸入捕獲模式,會(huì)出錯(cuò)。    //普通的輸入捕獲模式用TIM_ICInit函數(shù),特殊的PWM捕獲用TIM_PWMIConfig函數(shù)    //TIM_ICInit(TIM2,&TIM_ICInitStructure);    TIM_PWMIConfig(TIM2, &TIM_ICInitStructure);            // 選擇輸入捕獲的觸發(fā)信號(hào)    TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);        // PWM輸入模式時(shí),從模式必須工作在復(fù)位模式,當(dāng)捕獲開始時(shí),計(jì)數(shù)器CNT會(huì)被復(fù)位    TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);    TIM_SelectMasterSlaveMode(TIM2,TIM_MasterSlaveMode_Enable);     */    //使能中斷    TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);                // 清除中斷標(biāo)志位        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);        // 使能高級(jí)控制定時(shí)器,計(jì)數(shù)器開始計(jì)數(shù)    TIM_Cmd(TIM2, ENABLE);}/*單位時(shí)間編碼器計(jì)數(shù) 輸入定時(shí)器 輸出速度值*/int Read_Encoder(u8 TIMX){        int Encoder_TIM;            switch(TIMX)        {                case 2:  Encoder_TIM= (short)TIM2 -> CNT;  TIM2 -> CNT=0;break;                case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;                        case 4:  Encoder_TIM= (short)TIM4 -> CNT;  TIM4 -> CNT=0;break;                        default:  Encoder_TIM=0;        }        return Encoder_TIM;}

1.PWM輸出
較上一節(jié)講得做了引腳變動(dòng),這次使用定時(shí)器3,PA6引腳作為PWM輸出
具體配置和上一節(jié)差不多,不在啰嗦。
2.編碼器采集
編碼器采集用的是通用定時(shí)器2的PA0,PA1引腳,程序已經(jīng)詳細(xì)的注釋了。講幾個(gè)重要的參數(shù)配置、其中用

/*   */

注釋的是普通的輸入捕獲模式、
3.編碼器配置中的主要參數(shù)講解
①中斷優(yōu)先級(jí)
不用問,問就是最高優(yōu)先級(jí),再平衡小車系統(tǒng)中,電機(jī)的速度和位置是至關(guān)重要的,而采集編碼器數(shù)據(jù)就是作為計(jì)算速度和位置的重要參數(shù)、
②TIM_TimeBaseStructure.TIM_Period = (359 * 4);
這行代碼是配置自動(dòng)重裝載的值,在這里設(shè)置359 * 4其實(shí)意義不大,因?yàn)槲覀冏龅氖菍?shí)驗(yàn),而且轉(zhuǎn)速不是很快,編碼器一圈才11個(gè)脈沖,因此采樣值很小,滿占空比才到達(dá)50左右,因此該數(shù)值只要大于60就行。網(wǎng)上很多都是一圈300脈沖的那種高精度編碼器,因此該值需要大于在采樣周期能采樣的最大值,再留一些余量就行了。假設(shè)你編碼器1s采樣一次,一次采樣500個(gè)脈沖,而你設(shè)定ARR=400,這肯定是不行的,因此該值應(yīng)該根據(jù)自己的實(shí)際情況進(jìn)行設(shè)定。
③TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
在編碼器模式中,該配置不起作用,正反項(xiàng)計(jì)數(shù)是根據(jù)兩個(gè)信號(hào)的前后順序來決定的,親測(cè)該配置對(duì)計(jì)數(shù)無作用。
④TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
重中之重
這個(gè)函數(shù)明白了就好理解,不要被表象給迷惑到就行。
1.這個(gè)函數(shù)是開啟編碼器模式的,第一個(gè)參數(shù)是選擇定時(shí)器,在這里我們選擇定時(shí)器2;
2.第二個(gè)參數(shù)是選擇計(jì)數(shù)器模式的,進(jìn)入到函數(shù)體中可以找到具體配置的是TIMx_SMCR寄存器中的SMS【2-0】位。具體說明如下:

因此選擇的是編碼器模式3,根據(jù)另一個(gè)信號(hào)的電平來決定怎么計(jì)數(shù),上面在將編碼器計(jì)數(shù)原理的時(shí)候也說過了,向上向下計(jì)數(shù)是要看另一路信號(hào)的電平的。
3.最后兩個(gè)參數(shù)也是最不容易理解的,主要是好多都是根據(jù)字面意思講解,沒有到函數(shù)體中看到底是配置的哪一個(gè)寄存器。當(dāng)你真正的進(jìn)去看函數(shù)體的時(shí)候你就明白了。
這兩個(gè)參數(shù)從下面這張圖中還以為是配置上升沿、下降沿還是雙邊沿檢測(cè)的,其實(shí)不是,

其實(shí)不是這樣,從函數(shù)體中找到這兩個(gè)參數(shù),其實(shí)是配置CCER寄存器中的CC1E、CC1P、CC2E以及CC2P,CC1E和CC2E是使能的,CC1P、CC2P是配置極性的,具體看中文手冊(cè),我也給你截好圖了

反向和不反向的意思就是從0到ARR計(jì)數(shù)還是從ARR到0 計(jì)數(shù)、而該位正是配置這一點(diǎn)的,在一個(gè)TIM_ICPolarity_BothEdge這個(gè)定義并不能在這個(gè)函數(shù)的形參中使用。該函數(shù)只能配置反向和不反向,下圖紅色框中需要注意下。

4.TIM_ICInitStructure.TIM_ICFilter = 10;
這個(gè)是設(shè)定濾波的,設(shè)定0的時(shí)候不是很穩(wěn)。具體參考中文手冊(cè)如下圖:

寫到這里,輸入捕獲的配置基本完后了。下面就是采樣了,我采用系統(tǒng)滴答定時(shí)器進(jìn)行1s采樣一次,這個(gè)值是輸入捕獲1s中采樣的編碼器的值。并把它顯示到屏幕上或者串口打印出來。

int Read_Encoder(u8 TIMX);
這個(gè)函數(shù)參考網(wǎng)上的代碼寫的,把這放到滴答定時(shí)器中斷里。如下圖

void SysTick_Handler(void){    flag ++;    if(flag >= 1000)    {        flag = 0;        Frequency1 = Read_Encoder(2);    }}

之后主函數(shù)一直循環(huán)顯示即可。這個(gè)是顯示在屏幕上的。

int main(void) {                char aa[10;         delay_init();                     //延時(shí)函數(shù)初始化            LCD_Fill(30,130,239,130+16,WHITE);    LCD_Init();    //uart_init(115200);         //串口初始化為115200    SysTick_Config(SystemCoreClock / 1000);//1ms    Advance_TIM_Init();            PWM_Init();        LCD_ShowString(30,50,16,"Show Frequency1",0);    while(1)        {                sprintf(aa,"%d",Frequency1);        LCD_ShowString(30,70,16,aa,0);                  //printf("count = %d\r\n",Frequency1);        //delay_ms(10);//每隔1s打印一次編碼器角度,用手去撥動(dòng)編碼器  使其慢速旋轉(zhuǎn)        } }總結(jié)

基本都是老套路了、
①定義函數(shù)結(jié)構(gòu)體
②使能GPIO時(shí)鐘
③配置GPIO
④配置中斷優(yōu)先級(jí)
⑤使能定時(shí)器時(shí)鐘
⑥配置基本定時(shí)器
⑦配置編碼器模式
⑧初始化輸入捕獲
⑨使能定時(shí)器

最主要的還是理解其中的一些重要的參數(shù),其實(shí)也不難(不過對(duì)于新手確實(shí)難,弄過一遍回過頭才覺得簡(jiǎn)單),進(jìn)入到函數(shù)體中仔細(xì)看看函數(shù),在對(duì)著手冊(cè)看寄存器內(nèi)容說明也就明白啦。

感悟

這個(gè)帖子已經(jīng)寫了好幾天了,7月3號(hào)就開始寫,寫到現(xiàn)在才弄完。中間過程很艱苦,不過一旦走出來,滿滿的成就感。

LCD相關(guān)的函數(shù)操作直接找的別人的代碼,我還沒有仔細(xì)研究,先拿來用吧。還是先把項(xiàng)目完成吧,拖得時(shí)間長(zhǎng)了就沒信心了。

一個(gè)人搞軟件有時(shí)候真的是累,不是那種身體勞累,是心累。搞了幾天還沒把一個(gè)問題解決的那種心情,真的是煩躁,恨不得把電腦砸了。他不像干體力活,加把勁就干完了,這種加把勁都不到往哪加。就像這個(gè)lcd顯示采集到的編碼器的值,就這一個(gè)問題弄了兩三天。各種百度,同樣代碼別人就行,到我這就不行了。到目前為止還沒有真正找到答案、-_-||

源碼

代碼放到公眾號(hào)了,后續(xù)我會(huì)將之前的代碼也都放到公眾號(hào)上,歡迎小伙伴們交流學(xué)習(xí)哈。關(guān)注公眾號(hào)回復(fù)
“編碼器模式”
獲取源碼。



作者: 自動(dòng)化工程師    時(shí)間: 2020-11-27 14:15
代碼全亂了。

作者: 自動(dòng)化工程師    時(shí)間: 2020-12-1 15:29
自動(dòng)化工程師 發(fā)表于 2020-11-27 14:15
代碼全亂了。

沒辦法,粘貼過來就這樣了。原文不在這寫的。




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1