回顧 上一章節學習了PWM生成,剛好買的元器件也都到了。測試下代碼,完美運行。這不又趁著周末,進行下一個環節—定時器編碼器模式。
目的是為下一步PID控制做準備。 遇到的問題周末學習編碼器模式也是一波三折呀,沒人指點真的是寸步難行呀。氣的直咬牙。
說說啥情況:
我用上節的PWM輸出控制電機旋轉,用編碼器采集脈沖,想把這個脈沖顯示在LCD上,就這個顯示功能搗鼓了一天多,最后還是沒成功,最后沒辦法了,只能串口打印出來。就在昨天晚上準備更新博客的時候想想不能就這么算了,又去調試了幾下,奇跡出現了,LCD竟然可以顯示了,弄完之后十點多了,就去睡覺了。目前問題還是不確定在哪,之前的代碼修改的也找不到了,晚上回家再試試改成之前的代碼,找找問題的根本原因。
改天單獨開個帖子說說吧,今天先學習編碼器。 編碼器的作用首先需要明白的一點就是為什么要用編碼器模式,而不是直接用輸入捕獲。
最主要的原因就是編碼器采集的是兩個信號,根據兩個信號的高低電平來判斷是正轉還是反轉,比輸入捕獲用一個信號的抗干擾能力強。如果一個信號有干擾,而另一個信號沒有干擾,則計數器不會計數,
就像下圖這樣,圖片在中文參考手冊中找的

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

上圖是定時器的框圖,首先我們會從電機屁股后面的編碼器引出兩根信號線接到單片機的編碼器引腳,如上圖第一個框內,接到某定時器的TIMx_CH1和TIMx_CH2兩個通道的引腳上。進來之后進行濾波和邊沿檢測之后輸出TI1FP1和TI1FP2,之后就直接到2號框了。2號框是將TI1FP1和TI1FP2接入編碼器接口,在這里判斷是該向上還是向下計數,之后來到3號框,進行分頻,最后來到CNT計數器進行計數。我們可以定時采集CNT里面的值,這樣就能算出電機的轉速和其他一些參數了。
具體配置一會看代碼吧。大致了解一下定時器框圖。 2.理解計數方式
框圖明白了,腦海里就明白了編碼器工作的大致原理了。但還需要明白一些細節方面的東西。比如它是怎么通過兩路信號進行計數的,到底是向上還是向下計數等等。 

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

到這里,基本上編碼器模式已經掌握的差不多了,下面就開始代碼了。 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); //死區時間時鐘源,該例程只進行PWM輸出,沒有用到死區,因此不設定 TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //向上計數模式 TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //自動重裝載的值設定為1000 TIM_TimeBaseInitStructure.TIM_Period = (1000 - 1); //定時器周期分頻數 TIM_TimeBaseInitStructure.TIM_Prescaler = (72 - 1); //重復計數器的值,只有高級定時器1和8有重復計數功能,其他都沒有,因此設定為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; // 設置中斷組為0 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); // 設置中斷來源 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 設置搶占優先級 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 設置子優先級 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);//設置缺省值 // 自動重裝載寄存器的值,累計TIM_Period+1個頻率后產生一個更新或者中斷 TIM_TimeBaseStructure.TIM_Period = (359*4); // 驅動CNT計數器的時鐘 = Fck_int/(psc+1) TIM_TimeBaseStructure.TIM_Prescaler = (72 - 1); // 時鐘分頻因子 ,配置死區時間時需要用到 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 計數器計數模式,設置為向上計數 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 重復計數器的值,沒用到不用管 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; //濾波設置 TIM_ICInitStructure.TIM_ICFilter = 0x0; //設置捕獲的邊沿 上升沿 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising ; // 1分頻,即捕獲信號的每個有效邊沿都捕獲 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1 ; // 設置捕獲通道的信號來自于哪個輸入通道,有直連和非直連兩種 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 初始化PWM輸入模式,不能用下面這個函數進行初始化輸入捕獲模式,會出錯。 //普通的輸入捕獲模式用TIM_ICInit函數,特殊的PWM捕獲用TIM_PWMIConfig函數 //TIM_ICInit(TIM2,&TIM_ICInitStructure); TIM_PWMIConfig(TIM2, &TIM_ICInitStructure); // 選擇輸入捕獲的觸發信號 TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); // PWM輸入模式時,從模式必須工作在復位模式,當捕獲開始時,計數器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); // 使能高級控制定時器,計數器開始計數 TIM_Cmd(TIM2, ENABLE);}/*單位時間編碼器計數 輸入定時器 輸出速度值*/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輸出
較上一節講得做了引腳變動,這次使用定時器3,PA6引腳作為PWM輸出
具體配置和上一節差不多,不在啰嗦。
2.編碼器采集
編碼器采集用的是通用定時器2的PA0,PA1引腳,程序已經詳細的注釋了。講幾個重要的參數配置、其中用 /* */ 注釋的是普通的輸入捕獲模式、
3.編碼器配置中的主要參數講解
①中斷優先級
不用問,問就是最高優先級,再平衡小車系統中,電機的速度和位置是至關重要的,而采集編碼器數據就是作為計算速度和位置的重要參數、
②TIM_TimeBaseStructure.TIM_Period = (359 * 4);
這行代碼是配置自動重裝載的值,在這里設置359 * 4其實意義不大,因為我們做的是實驗,而且轉速不是很快,編碼器一圈才11個脈沖,因此采樣值很小,滿占空比才到達50左右,因此該數值只要大于60就行。網上很多都是一圈300脈沖的那種高精度編碼器,因此該值需要大于在采樣周期能采樣的最大值,再留一些余量就行了。假設你編碼器1s采樣一次,一次采樣500個脈沖,而你設定ARR=400,這肯定是不行的,因此該值應該根據自己的實際情況進行設定。
③TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
在編碼器模式中,該配置不起作用,正反項計數是根據兩個信號的前后順序來決定的,親測該配置對計數無作用。
④TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
重中之重
這個函數明白了就好理解,不要被表象給迷惑到就行。
1.這個函數是開啟編碼器模式的,第一個參數是選擇定時器,在這里我們選擇定時器2;
2.第二個參數是選擇計數器模式的,進入到函數體中可以找到具體配置的是TIMx_SMCR寄存器中的SMS【2-0】位。具體說明如下:

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

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

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

4.TIM_ICInitStructure.TIM_ICFilter = 10;
這個是設定濾波的,設定0的時候不是很穩。具體參考中文手冊如下圖:
 寫到這里,輸入捕獲的配置基本完后了。下面就是采樣了,我采用系統滴答定時器進行1s采樣一次,這個值是輸入捕獲1s中采樣的編碼器的值。并把它顯示到屏幕上或者串口打印出來。 int Read_Encoder(u8 TIMX);
這個函數參考網上的代碼寫的,把這放到滴答定時器中斷里。如下圖 void SysTick_Handler(void){ flag ++; if(flag >= 1000) { flag = 0; Frequency1 = Read_Encoder(2); }}之后主函數一直循環顯示即可。這個是顯示在屏幕上的。 int main(void) { char aa[10; delay_init(); //延時函數初始化 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打印一次編碼器角度,用手去撥動編碼器 使其慢速旋轉 } }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
總結基本都是老套路了、
①定義函數結構體
②使能GPIO時鐘
③配置GPIO
④配置中斷優先級
⑤使能定時器時鐘
⑥配置基本定時器
⑦配置編碼器模式
⑧初始化輸入捕獲
⑨使能定時器 最主要的還是理解其中的一些重要的參數,其實也不難(不過對于新手確實難,弄過一遍回過頭才覺得簡單),進入到函數體中仔細看看函數,在對著手冊看寄存器內容說明也就明白啦。 感悟這個帖子已經寫了好幾天了,7月3號就開始寫,寫到現在才弄完。中間過程很艱苦,不過一旦走出來,滿滿的成就感。 LCD相關的函數操作直接找的別人的代碼,我還沒有仔細研究,先拿來用吧。還是先把項目完成吧,拖得時間長了就沒信心了。 一個人搞軟件有時候真的是累,不是那種身體勞累,是心累。搞了幾天還沒把一個問題解決的那種心情,真的是煩躁,恨不得把電腦砸了。他不像干體力活,加把勁就干完了,這種加把勁都不到往哪加。就像這個lcd顯示采集到的編碼器的值,就這一個問題弄了兩三天。各種百度,同樣代碼別人就行,到我這就不行了。到目前為止還沒有真正找到答案、-_-|| 源碼代碼放到公眾號了,后續我會將之前的代碼也都放到公眾號上,歡迎小伙伴們交流學習哈。關注公眾號回復
“編碼器模式”
獲取源碼。

|