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