帶編碼器的直流減速電機——基於STM32F407

首先,什麼是編碼器?

編碼器是將信號或數據進行編制、轉換爲可用以通訊、傳輸和存儲的信號形式的設備。在這裏,編碼器就是能夠將電機的轉動信息(比如轉速、轉動角度等)轉換爲脈衝信號的設備。按照原理可分爲(常見的)光電編碼器(光學式)和霍爾編碼器(磁式)。

接着,編碼器的作用以及爲什麼要用編碼器?

如上所述,編碼器能夠將電機的機械幾何位移轉化爲脈衝信號或數字量。也就是說,有了編碼器,我們通過檢測編碼器輸出的脈衝信號,就能獲取電機轉動角度、轉速等相關信息。這樣我們不但能定性的控制電機的轉向、轉速,還能定量的測量。

那麼,編碼器的原理是什麼以及怎麼運用呢?

簡單來說,就是電機帶動碼盤轉動,碼盤的結構使得當電機在轉動時會產生A、B兩相的脈衝信號,且這兩路脈衝信號的相位差爲90度(即正交)。如下圖:
(圖源自網絡)
由於A、B信號正交,因此可以根據兩個信號哪個先哪個後來判斷方向,根據每個信號脈衝數量的多少及每圈電機產生多少脈衝數就可以算出當前行走的距離,如果再加上定時器的話還可以計算出速度。

好了,現在我們的處境是,我們有一個帶編碼器的直流減速電機,我們知道當電機轉動的時候,它會產生A、B兩相正交脈衝信號,通過檢測脈衝信號我們就可以獲取電機的運動狀況。
那麼,我們通過什麼手段來檢測脈衝數呢?
其中一種思路是,我們通過定時器的輸入捕獲或者GPIO引腳的外部中斷來檢測邊沿變化,以此來檢測脈衝數。這方法好像沒毛病,當電機正常運轉時行得通。但是如果電機輸出的脈衝信號出現了毛刺呢?這樣誤差就來了,怎麼辦?通過軟件編寫算法來濾去毛刺似乎有點困難,於是我們想到通過硬件來處理這個毛刺。(而STM32正好有硬件編碼器,nice!)
這裏的脈衝輸入是一種特殊的輸入捕獲情況,因此stm32專門在定時器中提供了編碼器模式,可大大簡化解析過程。

STM32的編碼器接口模式
在該模式下能計算電機輸出脈衝信號的個數,且stm32根據其內部機制能夠消除毛刺的干擾。

配置過程:
由於編碼器接口模式是一種特殊的輸入捕獲,所以要先配置一下輸入捕獲(畢竟你要通過捕獲邊沿來檢測脈衝)。在輸入捕獲過程中,我們把A6、A7複用爲TIM3,作爲輸入捕獲的引腳,對電機的A、B相脈衝進行輸入捕獲。
輸入捕獲配置完成之後再配置一下編碼器模式就可以開始工作了。

於是緊接着我們來看看編碼器模式的配置函數TIM_EncoderInterfaceConfig:
STM32官方庫函數
其輸入參數中,TIMx就是輸入捕獲時設置的TIM3,polarity就分別是TI1和TI2的捕獲極性(TI1是定時器輸入通道1,TI2同理),即A、B兩相信號的捕獲極性(上升沿捕獲或者下降沿捕獲),重點來說說編碼器的模式TIM_EncoderMode:
當模式選爲TIM_EncoderMode_TI1時,計數器僅在TI1的邊沿處計數
當模式選爲TIM_EncoderMode_TI2時,計數器僅在TI2的邊沿處計數
當模式選爲TIM_EncoderMode_TI12時,計數器在TI1和TI2邊沿處均計數
也就是說,模式1,我只以A相的脈衝信號爲我的檢測信號,TI2我就不管了。(B只是相對A滯後了90度,其它都是一樣的,所以檢測A就夠了)。其它同理。
模式1和模式2顯然就沒什麼差別了,那模式3前者有何不同呢?
這就涉及到網友們所說的編碼器軟件四倍頻技術了:
四倍頻
如圖,A、B兩相正交信號,如果根據模式1或模式2對脈衝信號進行檢測,假設我們上升沿捕獲一次,那麼圖中的信號我們只檢測到3個上升沿,也就是三個脈衝。但是如果採用模式3,對TI1和TI2檢測上升沿,那麼同一段時間我們檢測到6個上升沿。如果下降沿也檢測,那麼一共就是12個邊沿跳變。每個實際來的脈衝會被檢測4次,同樣是12/4=3個脈衝,但是這樣的話精度大大提高了。
入口參量:(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising);
通過這樣配置,我們就完成了編碼器軟件的初始化。通過以上配置,我們完成了TIM3輸入捕獲的初始化以及編碼器接口模式的相關配置,每來一個跳變沿,計數值變化一次。

至此,爲了避免我們忘了初衷,現在我們來回顧一下我們最開始用編碼器的初衷,前面我們說要用編碼器來幹嘛來着?獲取電機轉動角度、轉速。
現在的處境是,我們通過STM32的TIM3的編碼器模式,能夠測出任意時刻的脈衝值了。現在要解決的就是如何將這個脈衝值轉換成我們所要得到的信息(即轉動角度、轉速)。
首先,根據帶編碼器的直流減速電機的原理,顯然無論電機的轉速如何,每轉產生的脈衝數是固定的。這裏假設電機每轉產生260個脈衝(具體數據各位自行查看自己的電機參數啦),那麼只要我們用‘電機已產生的脈衝數’除以‘260個/轉’,就可以得到電機轉了多少圈,一圈即爲360度,由此便可將脈衝數和轉動角度聯繫起來。
這裏要注意一點,由於我們用的是編碼器模式3(也就是TIM_EncoderMode_TI12),我們得到的脈衝數是電機實際產生的脈衝數的四倍。則電機實際產生的脈衝數應爲‘得到的計數值’除以4。於是,電機轉動圈數爲脈衝數除以260再除以4。
轉動角度算出來了,轉速呢?則是根據計數方向(遞增計數或遞減計數)來判斷的,所以我們接下來的工作就是要搞清楚計數方向和編碼器信號的關係。
讓我們來看一下STM32的中文參考手冊:
中文參考手冊
網上幾乎在這張表的講解上都是一帶而過,真正能把這張表講清楚的幾乎沒有。後來看到一個人在表格上做了類似上圖的註釋,我才終於搞懂了。
大家還記得我們前面提到的TIM_EncoderInterfaceConfig函數嗎,裏面有個參量就是選擇模式的,對吧。讓我們再來回顧一下:
STM32官方庫函數
注意到各模式後面的“xx edge depending on xx level”了嗎?這就是上面這個表裏的第二縱列所提到的“相反信號的電平”。
TIM_EncoderMode_TI1: Counter counts on TI1FP1 edge depending on TI2FP2 level,意思就是當選擇模式1時,計數器根據TI2的電平高低來記錄TI1的邊沿信號。
這樣說可能不好理解,我們來舉個具體的例子:
中文參考手冊
如上圖,電機轉動時產生A、B兩相信號通過TI1、TI2輸入到TIM3。
假設我們選擇的是模式1,即計數器僅在TI1的邊沿處計數。我們觀察TI1的上升沿,若此時對應的TI2信號處於低電平(下圖紅框),於是根據表格我們可以得到計數方向爲遞增,假設計數器遞增時電機正向轉動,則可判斷此時電機正轉。
在這裏插入圖片描述
在這裏插入圖片描述
再譬如,假設我們選擇模式2,即計數器僅在TI2的邊沿處計數。我們觀察TI2的下降沿,若此時對應的TI1信號爲高電平(下圖藍框),於是根據表格我們可以得到計數方向爲遞減,假設計數器遞減時電機反向轉動,則可判斷此時電機反轉。
在這裏插入圖片描述
在這裏插入圖片描述
也就是說,當以TI1爲計數信號時,需要根據TI2的電平(也就是第二縱列的“相對信號的電平”)來進行判斷是向上計數還是向下計數。TI2也同理。
e.g.如果“僅在TI1計數”,當TI1爲“上升”沿(表中第三縱列),此時要根據TI2的電平來做判斷。根據下圖,可知當TI1爲上升沿的時候TI2爲低電平,則根據上表可得知計數器爲向上計數。這在下圖得到了驗證。
在這裏插入圖片描述
再例如,如果“僅在TI2計數”時,當TI2爲下降沿時,如果TI1爲低電平,則爲向上計數(上圖左側);當TI2爲下降沿時,如果TI1爲高電平,則爲向下計數(上圖中部);當TI2爲上升沿時,如果TI1爲高電平,則爲向上計數(上圖左部);當TI2爲上升沿時,如果TI1爲低電平,則爲向下計數(圖中中部);
至此爲止,我們便實現了這樣的操作:通過使用帶編碼器的直流減速電機產生正交脈衝,通過STM32的TIM的編碼器模式對脈衝進行計數,根據計數方向和編碼器信號的關係來判斷電機轉動方向,利用脈衝計數值來計算電機轉動位移。

接下來是代碼部分,有些容易讓人迷惑的地方(至少是讓我迷惑了很久):
輸入捕獲初始化時Period和Prescaler的作用:
這裏的period即爲計數器的重裝載值,prescaler即爲預分頻係數。注意在編碼器模式時,要把TIM理解爲計數器,而不是定時器,這樣的話,時鐘信號就不是系統內部產生的,而是通過PA6、PA7輸入到TIM3的TI1和TI2的。
有了這麼個理解,再來看Period和Prescaler就不難理解了。Period就是計數器每一次能檢測脈衝的最大值,每來一個脈衝計數值就加一,當計數值超過period就溢出中斷。Prescaler就是對電機產生的脈衝信號進行分頻的分頻係數。比如當分頻係數爲2時,每當電機產生兩個脈衝,stm32才認爲接收到一個有效脈衝,計數值才加一。

以下爲部分代碼

//思路:初始化TIM的編碼器模式後,在main函數裏死循環不斷的讀取CNT的值,從而來獲得電機的脈衝數數據,以此來計算電機的轉速、所轉圈數等參數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "pwm.h"
#include "encode.h"
#include "exti.h"
#include "key.h"

u32 pwm = 50000;

int main(void)
{
	uint32_t cnt_temp;			//用於暫存TIM的計數值,即TIM檢測到的脈衝的數量
	float pulse;				//電機產生的實際脈衝值
	float round;				//電機轉的圈數
	
	encoder_init();				//TIM3編碼器模式初始化,A6、A7分別作爲A相和B相的脈衝輸入
	exti_init();				//外部中斷,用於通過按鍵修改數據(比如PWM)
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//設置系統中斷優先級分組2
	delay_init(168);  								//初始化延時函數
	uart_init(115200);								//初始化串口波特率爲115200
 	TIM4_PWM_Init(50000-1,84-1);					//TIM4初始化,用於產生PWM供給直流減速電機
													//84M/84=1Mhz的計數頻率,重裝載值50000,所以PWM頻率爲100000/50000=2hz,即整個週期爲500ms
	while(1)
	{
		TIM_SetCompare1(TIM4,pwm);					//設置供給電機的PWM值
		cnt_temp = read_cnt();						//得到脈衝計數值
		pulse = cnt_temp/4.0f;						//由於是TIM_EncoderMode_TI12,所以要四分頻,即除以四,得到實際的脈衝值
		round = cnt_temp/4.0f/260.0f;				//假設電機每轉產生260個脈衝,則通過該公式可求出電機轉了幾圈
		printf("cnt_temp:%d\r\n", cnt_temp);		//向串口打印脈衝計數值
		printf("pulse:%f\r\n", pulse);				//向串口打印實際脈衝值
		printf("round:%f\r\n", round);				//向串口打印電機轉了幾圈
		delay_ms(1000);								//每1s循環更新一次
//		if(TIM3->CR1&1<<4)	printf("forward");
//		else	printf("backward");
	}
}



//把TIM3理解爲一個計數器而不是一個定時器,則沒有了時序信號。
//這裏TIM3的時鐘信號(或者說是計數信號)將由電機編碼器輸出的脈衝代替,也就是說電機脈衝信號成爲TIM3的信號,電機每產生一個脈衝被TIM3檢測到,則計數器CNT加一(類比於時序信號時每隔一個時間段計數值加一)
//這樣的話,輸入捕獲的自動重裝載值period則影響着脈衝值計數到多少之後就溢出,比如65535的話,則接收到65535個脈衝信號之後計數值置零溢出
//這樣的話,輸入捕獲的預分頻係數prescaler的作用是,當我不分頻時,來一個電機脈衝信號我計數值就加一,當我二分頻時,只有接收到兩個脈衝信號我才認爲是一個有效脈衝,計數值才加一,簡單來說就是計數值總體除以二了
//這樣我們就把輸入捕獲初始化完成了,接下來是編碼器模式的初始化
//設爲TIM_EncoderMode_TI12模式,即計數器在TI1和TI2上升沿處均計數,再根據設置的極性是TIM_ICPolarity_Rising,也就是在TI1和TI2的上升沿計數器累加(或累減)-->那麼到時候要除以二
//這樣,編碼器的初始化就完成了,接下來我們只要通過函數得出它的計數值,就可以知道電機產生的脈衝數,再根據電機的參數(每轉產生多少個脈衝)就可以得到電機轉了幾圈
#include "encode.h"
#include "sys.h"

//初始化編碼器
void encoder_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);    
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3);
	
	//Specifies the prescaler value used to divide the TIM clock.
	//也就是說,這裏的TIM3的時鐘信號其實是由A/B相的頻率來決定的,類似於外部時鐘,然後分頻就是對這個脈衝頻率分頻,比如二分頻就是把兩個脈衝記爲一個脈衝。
	TIM_TimeBaseStructure.TIM_Prescaler = 1-1;					//這裏我們把它設爲1,即不分頻
	TIM_TimeBaseStructure.TIM_Period = 65535;					//每來一個脈衝信號的上升沿(下面有設置)計數值就累加(或累減),65535則爲最大計數值,就溢出了
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //這裏按理來說應該不起作用,因爲計數方向是受TI1和TI2信號的影響的
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	
	TIM_ICStructInit(&TIM_ICInitStructure);						//Fills each TIM_ICInitStruct member with its default value
	//相當於:
	//	void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct)
	//	{
	//	  /* Set the default configuration */
	//	  TIM_ICInitStruct->TIM_Channel = TIM_Channel_1;
	//	  TIM_ICInitStruct->TIM_ICPolarity = TIM_ICPolarity_Rising;
	//	  TIM_ICInitStruct->TIM_ICSelection = TIM_ICSelection_DirectTI;
	//	  TIM_ICInitStruct->TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//	  TIM_ICInitStruct->TIM_ICFilter = 0x00;
	//	}
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); //配置爲編碼器模式,計數器在TI1和TI2上升沿處均計數

	TIM_SetCounter(TIM3, 0);		//將脈衝計數值設爲零
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3
}

// 讀取定時器計數值
uint32_t read_cnt(void)
{
	uint32_t encoder_cnt;
	encoder_cnt = TIM3->CNT;		//讀取計數器CNT的值,CNT系uint32_t型的變量
	TIM_SetCounter(TIM3, 0);		//每一次讀取完計數值後將計數值清零,重新開始累加脈衝,方便下一次計數
	return encoder_cnt;				//返回的值就是本次讀到的計數值
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章