若要對伺服系統(tǒng)中的電機進行高精度控制,需要準確的轉子角度位置,這時候自然會想到,如果能張江轉子每一圈進行細分,這樣每次轉多少角度便能精確知道。在這樣的背景下,相對編碼器就誕生了。
在網上找到下文這個圖,很形象的表征了相對編碼器的原理。
如圖所示,在碼盤上平均開出很多個等間距的槽,一段是LED燈發(fā)出信號,另一端是接收器接收信號。如果信號能穿過碼盤,則接收信號為高電平,反之則為低電平。這樣當轉子轉起來以后,就不斷的處高低電平。這就是編碼器基本原理。
可以看到這里有三個信號,A/B/Z,這時候就要想為什么要3個信號呢?如果僅僅對一圈做細分,命名一個信號就可以了。這就涉及到下面兩個問題。
(1)如果是1個信號channel A,電機是正轉還是反轉就不知道了。需要一個相對的參考信號channel B,A和B相互呈一個角度,這樣通過A和B的相對位置就能知道電機是順時鐘轉還是逆時針轉了。
(2)如果是2個信號,其中一旦有碼盤有損壞,就可能出現檢測結果無法校驗的情況。舉個例子,如果一圈開了16個槽,則每旋轉一圈,正常情況下就有16個高低電平的信號出來。但如果一個槽壞了,實際上每轉一圈只有15個信號出來,但這時如果僅僅通過channel A和channel B是無法判斷的。在進行數據處理時還是認為16個信號為一圈,處理結果就有較大的偏差。為了避免這樣的問題,補充z信號,一圈只出一個,這樣就能相互交驗了。一方面通過對A或者B計數,知道z是否有問題,反之對z信號計數就能知道A/B是否有問題。
所以就有了上圖的z/A/B三個信號,共同組成了一個功能齊全的編碼器。
在網上經??吹秸fA/B之間相互差90°,這個90°是認為360°為一個周期而言的。如下圖所示。通過看A/B相對位置就知道電機是正轉還是反轉了。
實測波形,如下圖所示(示波器不太好,有點毛刺)
正轉
反轉
▍使用STM32,讓編碼器說話
背景
STM32中提供了編碼器接口,比較適用于相對編碼器的應用場景。在手冊中可以看到:
可以看到這里使用專用的模塊就能完成相應的計數,通過數據的變化就能測出電機的轉速。
所以,我想讓編碼器說話。在家翻箱倒柜以后,我準備了如下幾個東西:
(1) 帶編碼器的直流電機:這是作為編碼器的載體使用,電機編碼器的分辨率較低,每圈只有16個脈沖。但不影響測試。
(2) 直流電源:用來直觀的調電機的轉速和正反轉。
為了避免打廣告的嫌疑,就不貼電源和電機圖片了。
(3) STM32開發(fā)板:在家翻箱倒柜,找出2015年在21ic獲得的STM32072 discovery板
(4) LED數碼管。用來通過編碼器的數據處理,顯示電機的轉速。
試驗第一步,讓LED數碼管顯示起來。
因為顯示數據是最終目的。使用的這個板子,是集成了HC595鎖存器的板子。相比于網上買的大部分51開發(fā)板數碼管電機設計,使用兩個HC595,可以大大減少pin腳的數量。網上使用的4位數碼管,需要8個pin作為段選或者位選,非常麻煩。
根據HC595的手冊,具有鎖存加移位的特性(圖中我標注所示)
最上面的3個SH-CP/DS/ST-CP,像極了SPI通信波形,只要合理配置,只需要3個信號線即可完成4數碼管的輪流顯示。
于是在開發(fā)板的pin做了如下硬件配置
Pin(數碼管) | 74HC595 | SPI | Pin |
SCLK | Pin11(shift) | SPICLK | PB13 |
RCLK | Pin12(Storage) | NSS | PB12 |
DIO | Pin14(datainput) | SPIMOSI | PC3 |
QH | Pin9(dataoutput) | SPIMISO | PC2 |
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); }
實際上每次只會有一個數碼管亮,為了較好的視覺體驗,將數碼管進行千位百位十位個位循環(huán)顯示,這樣做的好處是4個數碼管輪流顯示,其亮度相同,避免出現一個數碼管過亮的情形,影響視覺體驗。數碼管代碼如下:
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,進行下圖模式的計數。
即效果如下:
代碼如下
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);} 然后在中斷服務函數中,將編碼器的相對值計算出來,并根據編碼器計數的相對變化,計算出電機的轉速。具體代碼如下:
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中斷溢出,記得清除中斷標志位
void TIM3_IRQHandler (){ if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update); }} 實際效果如下圖所示(東西太多,手機不好拍動圖,只能靜物顯示),可知,當電機電壓9.32V時,轉速為843rpm。當電壓為18.7V時,轉速為1687rpm。編碼器的波形也用示波器顯示出來了。還不錯哈,哈哈哈。
▍結論
本文使用STM32F0 discovery開發(fā)板,完成了編碼器計數和電機轉速的計算,并通過數碼管將電機轉速實時顯示出來。
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!