▍編碼器的由來和原理
若要對伺服系統(tǒng)中的電機(jī)進(jìn)行高精度控制,需要準(zhǔn)確的轉(zhuǎn)子角度位置,這時候自然會想到,如果能張江轉(zhuǎn)子每一圈進(jìn)行細(xì)分,這樣每次轉(zhuǎn)多少角度便能精確知道。在這樣的背景下,相對編碼器就誕生了。
在網(wǎng)上找到下文這個圖,很形象的表征了相對編碼器的原理。
如圖所示,在碼盤上平均開出很多個等間距的槽,一段是LED燈發(fā)出信號,另一端是接收器接收信號。如果信號能穿過碼盤,則接收信號為高電平,反之則為低電平。這樣當(dāng)轉(zhuǎn)子轉(zhuǎn)起來以后,就不斷的處高低電平。這就是編碼器基本原理。
可以看到這里有三個信號,A/B/Z,這時候就要想為什么要3個信號呢?如果僅僅對一圈做細(xì)分,命名一個信號就可以了。這就涉及到下面兩個問題。
(1)如果是1個信號channel A,電機(jī)是正轉(zhuǎn)還是反轉(zhuǎn)就不知道了。需要一個相對的參考信號channel B,A和B相互呈一個角度,這樣通過A和B的相對位置就能知道電機(jī)是順時鐘轉(zhuǎn)還是逆時針轉(zhuǎn)了。
(2)如果是2個信號,其中一旦有碼盤有損壞,就可能出現(xiàn)檢測結(jié)果無法校驗的情況。舉個例子,如果一圈開了16個槽,則每旋轉(zhuǎn)一圈,正常情況下就有16個高低電平的信號出來。但如果一個槽壞了,實際上每轉(zhuǎn)一圈只有15個信號出來,但這時如果僅僅通過channel A和channel B是無法判斷的。在進(jìn)行數(shù)據(jù)處理時還是認(rèn)為16個信號為一圈,處理結(jié)果就有較大的偏差。為了避免這樣的問題,補(bǔ)充z信號,一圈只出一個,這樣就能相互交驗了。一方面通過對A或者B計數(shù),知道z是否有問題,反之對z信號計數(shù)就能知道A/B是否有問題。
所以就有了上圖的z/A/B三個信號,共同組成了一個功能齊全的編碼器。
在網(wǎng)上經(jīng)??吹秸fA/B之間相互差90°,這個90°是認(rèn)為360°為一個周期而言的。如下圖所示。通過看A/B相對位置就知道電機(jī)是正轉(zhuǎn)還是反轉(zhuǎn)了。
實測波形,如下圖所示(示波器不太好,有點毛刺)
正轉(zhuǎn)
反轉(zhuǎn)
▍使用STM32,讓編碼器說話
背景
STM32中提供了編碼器接口,比較適用于相對編碼器的應(yīng)用場景。在手冊中可以看到:
可以看到這里使用專用的模塊就能完成相應(yīng)的計數(shù),通過數(shù)據(jù)的變化就能測出電機(jī)的轉(zhuǎn)速。
所以,我想讓編碼器說話。在家翻箱倒柜以后,我準(zhǔn)備了如下幾個東西:
(1)帶編碼器的直流電機(jī):這是作為編碼器的載體使用,電機(jī)編碼器的分辨率較低,每圈只有16個脈沖。但不影響測試。
(2)直流電源:用來直觀的調(diào)電機(jī)的轉(zhuǎn)速和正反轉(zhuǎn)。
為了避免打廣告的嫌疑,就不貼電源和電機(jī)圖片了。
(3)STM32開發(fā)板:在家翻箱倒柜,找出2015年在21ic獲得的STM32072 discovery板
(4)LED數(shù)碼管。用來通過編碼器的數(shù)據(jù)處理,顯示電機(jī)的轉(zhuǎn)速。
試驗第一步,讓LED數(shù)碼管顯示起來。
因為顯示數(shù)據(jù)是最終目的。使用的這個板子,是集成了HC595鎖存器的板子。相比于網(wǎng)上買的大部分51開發(fā)板數(shù)碼管電機(jī)設(shè)計,使用兩個HC595,可以大大減少pin腳的數(shù)量。網(wǎng)上使用的4位數(shù)碼管,需要8個pin作為段選或者位選,非常麻煩。
根據(jù)HC595的手冊,具有鎖存加移位的特性(圖中我標(biāo)注所示)
最上面的3個SH-CP/DS/ST-CP,像極了SPI通信波形,只要合理配置,只需要3個信號線即可完成4數(shù)碼管的輪流顯示。
于是在開發(fā)板的pin做了如下硬件配置
Pin(數(shù)碼管) 74HC595SPIPin
SCLKPin11(shift)SPICLKPB13
RCLKPin12(Storage)NSSPB12
DIOPin14(datainput)SPIMOSIPC3
QHPin9(dataoutput)SPIMISOPC2
SPI配置代碼如下(配置了SPI幾個pin腳的定義,時鐘,SPI模式等):
void SPI_Digital_Tube_Config(void){ SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;
/* Disable the SPI peripheral */ SPI_Cmd(SPI2, DISABLE); /* Enable SCK, MOSI, MISO and NSS GPIO clocks */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); RCC_AHBPeriphClockCmd(SPI_Digital_Tube_SCK_GPIO_CLK | SPI_Digital_Tube_MOSI_GPIO_CLK| SPI_Digital_Tube_NSS_GPIO_CLK, ENABLE);
/* SPI pin mappings */ GPIO_PinAFConfig(SPI_Digital_Tube_SCK_GPIO_PORT, SPI_Digital_Tube_SCK_SOURCE, SPI_Digital_Tube_SCK_AF); GPIO_PinAFConfig(SPI_Digital_Tube_MOSI_GPIO_PORT, SPI_Digital_Tube_MOSI_SOURCE, SPI_Digital_Tube_MOSI_AF); GPIO_PinAFConfig(SPI_Digital_Tube_MISO_GPIO_PORT, SPI_Digital_Tube_MISO_SOURCE, SPI_Digital_Tube_MISO_AF); GPIO_PinAFConfig(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_SOURCE, SPI_Digital_Tube_NSS_AF);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;
/* SPI SCK pin configuration */ GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_SCK_PIN; GPIO_Init(SPI_Digital_Tube_SCK_GPIO_PORT, &GPIO_InitStructure);
/* SPI MOSI pin configuration */ GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_MOSI_PIN; GPIO_Init(SPI_Digital_Tube_MOSI_GPIO_PORT, &GPIO_InitStructure);
/* SPI MISO pin configuration */ GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_MISO_PIN; GPIO_Init(SPI_Digital_Tube_MISO_GPIO_PORT, &GPIO_InitStructure);
/* SPI NSS pin configuration */ GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_NSS_PIN; GPIO_Init(SPI_Digital_Tube_NSS_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Pin = SPI_Digital_Tube_NSS_PIN; GPIO_Init(SPI_Digital_Tube_NSS_GPIO_PORT, &GPIO_InitStructure);
/* SPI configuration -------------------------------------------------------*/ SPI_I2S_DeInit(SPI2); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;// SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_Init(SPI2, &SPI_InitStructure);
/* Initialize the FIFO threshold */ SPI_RxFIFOThresholdConfig(SPI2, SPI_RxFIFOThreshold_QF);
/* Enable the SPI peripheral */ SPI_Cmd(SPI2, ENABLE);
// /* Enable NSS output for master mode */// SPI_SSOutputCmd(SPI2, ENABLE);}
使用TIM6作為定時器,配置代碼如下(1ms定時周期):
static void BASIC_TIM_Mode_Config(void){ TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE); TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;//1ms TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;//47 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure); TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update); TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE); TIM_Cmd(BASIC_TIM, ENABLE); }
實際上每次只會有一個數(shù)碼管亮,為了較好的視覺體驗,將數(shù)碼管進(jìn)行千位百位十位個位循環(huán)顯示,這樣做的好處是4個數(shù)碼管輪流顯示,其亮度相同,避免出現(xiàn)一個數(shù)碼管過亮的情形,影響視覺體驗。數(shù)碼管代碼如下:
void DisplayNumber(uint16_t num){ uint8_t mythousandNum,myhundredNum,mytenNum,myunitNum=0; if(num》9999)num=9999; mythousandNum=num/1000%10; myhundredNum=num/100%10; mytenNum=num/10%10; myunitNum=num%10; switch(mydisplaybit) { case thousaud: Display16(mythousandNum,4); mydisplaybit=hundred; break; case hundred: Display16(myhundredNum,3); mydisplaybit=ten; break; case ten: Display16(mytenNum,2); mydisplaybit=unit; break; case unit: Display16(myunitNum,1); mydisplaybit=thousaud; break; default: Display16(mythousandNum,4); mydisplaybit=hundred; break; }}
static void Display16(uint8_t num,uint8_t place){ GPIO_ResetBits(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_PIN); uint16_t Temp=((Num[num])《《8)+((0x01)《《(place-1)); SPI2_Send_Byte16(Temp); GPIO_SetBits(SPI_Digital_Tube_NSS_GPIO_PORT, SPI_Digital_Tube_NSS_PIN);}
然后,每隔0.5s累加一次。在定時器中累計
void TIM6_DAC_IRQHandler(){ static uint16_t counter=0; static uint16_t num_buffer=0; if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) { counter++; if(counter》499) { num_buffer++; counter=0; } DisplayNumber(num_buffer); TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update); } }
所以,初試成功。
試驗第二步,讓編碼器說話。
首先,在STM32中配置編碼器。
使用PA6和PA7作為定時器3的通道1和通道2,進(jìn)行下圖模式的計數(shù)。
即效果如下:
代碼如下
void TIM3_EncoderConfig(void){ TIM_ICInitTypeDef TIM_ICInitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure;
HALL_TIM_APBxClock_FUN(ENCODER_TIM_CLK, ENABLE);
/* GPIOA clock enable */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //PA6 & PA7 RCC_AHBPeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); /* phase A & B*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_1);//TIM3_CH1 GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_1);//TIM3_CH2
TIM_DeInit(TIM3); TIM_TimeBaseStructure.TIM_Period =0xffff; TIM_TimeBaseStructure.TIM_Prescaler =0; TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode =TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_BothEdge,TIM_ICPolarity_BothEdge);
TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInit(TIM3, &TIM_ICInitStructure);
// Clear all pending interrupts TIM_ClearFlag(TIM3, TIM_FLAG_Update); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//Reset counter TIM_SetCounter(TIM3,0); TIM_Cmd(TIM3, ENABLE);
/* Enable the TIM1 global Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);}
然后在中斷服務(wù)函數(shù)中,將編碼器的相對值計算出來,并根據(jù)編碼器計數(shù)的相對變化,計算出電機(jī)的轉(zhuǎn)速。具體代碼如下:
void TIM6_DAC_IRQHandler(){ static uint16_t counter=0; static uint16_t num_buffer=0; static uint16_t temp_now=0; static uint16_t temp_pre=0; static uint16_t speed=0; if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) { counter++; temp_now=(TIM_GetCounter(TIM3)&0xffff); if(counter》499) { num_buffer=(temp_now-temp_pre)》0?temp_now-temp_pre:temp_pre-temp_now; speed=100*num_buffer*60/64; counter=0; } DisplayNumber(speed); if(counter%10==0)temp_pre=temp_now; TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update); } }
同時,為了防止TIM3中斷溢出,記得清除中斷標(biāo)志位
void TIM3_IRQHandler (){ if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); }}
實際效果如下圖所示(東西太多,手機(jī)不好拍動圖,只能靜物顯示),可知,當(dāng)電機(jī)電壓9.32V時,轉(zhuǎn)速為843rpm。當(dāng)電壓為18.7V時,轉(zhuǎn)速為1687rpm。編碼器的波形也用示波器顯示出來了。還不錯哈,哈哈哈。
▍結(jié)論
本文使用STM32F0 discovery開發(fā)板,完成了編碼器計數(shù)和電機(jī)轉(zhuǎn)速的計算,并通過數(shù)碼管將電機(jī)轉(zhuǎn)速實時顯示出來。
原文標(biāo)題:手把手教你怎么用STM32讓相對編碼器說話
文章出處:【微信公眾號:嵌入式ARM】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
責(zé)任編輯:haq
-
編碼器
+關(guān)注
關(guān)注
45文章
3655瀏覽量
134893 -
STM32
+關(guān)注
關(guān)注
2270文章
10915瀏覽量
356778
原文標(biāo)題:手把手教你怎么用STM32讓相對編碼器說話
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論