找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2647|回復: 2
打印 上一主題 下一主題
收起左側(cè)

零基礎(chǔ)制作平衡小車【連載】4---STM32定時器編碼器模式(附源碼)

[復制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:223481 發(fā)表于 2020-11-27 14:14 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
回顧

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

遇到的問題

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

編碼器的作用

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

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

編碼器采集原理

1.理解框圖

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

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



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

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

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

STM32F103ZET6生成編碼器代碼#include "PWM.h"//通用定時器TIM3   PA6void PWM_Init(void){    //基本定時器初始化部分        GPIO_InitTypeDef GPIO_InitStructure;    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    TIM_OCInitTypeDef TIM_OCInitStructure;    //初始化定時器    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);        //使能定時器時鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //死區(qū)時間時鐘源,該例程只進行PWM輸出,沒有用到死區(qū),因此不設(shè)定    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //向上計數(shù)模式    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;    //自動重裝載的值設(shè)定為1000    TIM_TimeBaseInitStructure.TIM_Period = (1000 - 1);    //定時器周期分頻數(shù)    TIM_TimeBaseInitStructure.TIM_Prescaler = (72 - 1);    //重復計數(shù)器的值,只有高級定時器1和8有重復計數(shù)功能,其他都沒有,因此設(shè)定為0    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;        //初始化定時器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上的預裝載寄存器    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);     //使能定時器3    TIM_Cmd(TIM3,ENABLE);    }//編碼器模式  通用定時器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)先級    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;                   // 設(shè)置子優(yōu)先級    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);            //基本定時器初始化部分    //使能定時器時鐘    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);//設(shè)置缺省值    // 自動重裝載寄存器的值,累計TIM_Period+1個頻率后產(chǎn)生一個更新或者中斷        TIM_TimeBaseStructure.TIM_Period = (359*4);                // 驅(qū)動CNT計數(shù)器的時鐘 = Fck_int/(psc+1)        TIM_TimeBaseStructure.TIM_Prescaler = (72 - 1);                // 時鐘分頻因子 ,配置死區(qū)時間時需要用到        TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;                        // 計數(shù)器計數(shù)模式,設(shè)置為向上計數(shù)        TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;                        // 重復計數(shù)器的值,沒用到不用管        TIM_TimeBaseStructure.TIM_RepetitionCounter=0;                // 初始化定時器        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分頻,即捕獲信號的每個有效邊沿都捕獲    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1 ;    // 設(shè)置捕獲通道的信號來自于哪個輸入通道,有直連和非直連兩種    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;    // 初始化PWM輸入模式,不能用下面這個函數(shù)進行初始化輸入捕獲模式,會出錯。    //普通的輸入捕獲模式用TIM_ICInit函數(shù),特殊的PWM捕獲用TIM_PWMIConfig函數(shù)    //TIM_ICInit(TIM2,&TIM_ICInitStructure);    TIM_PWMIConfig(TIM2, &TIM_ICInitStructure);            // 選擇輸入捕獲的觸發(fā)信號    TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);        // PWM輸入模式時,從模式必須工作在復位模式,當捕獲開始時,計數(shù)器CNT會被復位    TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);    TIM_SelectMasterSlaveMode(TIM2,TIM_MasterSlaveMode_Enable);     */    //使能中斷    TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);                // 清除中斷標志位        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);        // 使能高級控制定時器,計數(shù)器開始計數(shù)    TIM_Cmd(TIM2, ENABLE);}/*單位時間編碼器計數(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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

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

/*   */
  • 1

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

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

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

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

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

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

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

void SysTick_Handler(void){    flag ++;    if(flag >= 1000)    {        flag = 0;        Frequency1 = Read_Encoder(2);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

int main(void) {                char aa[10;         delay_init();                     //延時函數(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打印一次編碼器角度,用手去撥動編碼器  使其慢速旋轉(zhuǎn)        } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
總結(jié)

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

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

感悟

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

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

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

源碼

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


評分

參與人數(shù) 1黑幣 +30 收起 理由
2420255487 + 30 絕世好帖!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏3 分享淘帖 頂1 踩
回復

使用道具 舉報

沙發(fā)
ID:223481 發(fā)表于 2020-11-27 14:15 | 只看該作者
代碼全亂了。
回復

使用道具 舉報

板凳
ID:223481 發(fā)表于 2020-12-1 15:29 | 只看該作者

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

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復 返回頂部 返回列表