手把手教你怎么用STM32让相对编码器说话

▍编码器的由来和原理若要对伺服系统中的电机进行高精度控制,需要准确的转子角度位置,这时候自然会想到,如果能张江转子每一圈进行细分,这样每次转多少角度便能精确知道。在这样的背景下,相对编码器就诞生了。在网上找到下文这个图,很形象的表征了相对编码器的原理。

如图所示,在码盘上平均开出很多个等间距的槽,一段是LED灯发出信号,另一端是接收器接收信号。如果信号能穿过码盘,则接收信号为高电平,反之则为低电平。这样当转子转起来以后,就不断的处高低电平。这就是编码器基本原理。可以看到这里有三个信号,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三个信号,共同组成了一个功能齐全的编码器。在网上经常看到说A/B之间相互差90°,这个90°是认为360°为一个周期而言的。如下图所示。通过看A/B相对位置就知道电机是正转还是反转了。

实测波形,如下图所示(示波器不太好,有点毛刺)

正转

反转▍使用STM32,让编码器说话背景STM32中提供了编码器接口,比较适用于相对编码器的应用场景。在手册中可以看到:

可以看到这里使用专用的模块就能完成相应的计数,通过数据的变化就能测出电机的转速。所以,我想让编码器说话。在家翻箱倒柜以后,我准备了如下几个东西:(1)带编码器的直流电机:这是作为编码器的载体使用,电机编码器的分辨率较低,每圈只有16个脉冲。但不影响测试。(2)直流电源:用来直观的调电机的转速和正反转。为了避免打广告的嫌疑,就不贴电源和电机图片了。(3)STM32开发板:在家翻箱倒柜,找出2015年在21ic获得的STM32072 discovery板

(4)LED数码管。用来通过编码器的数据处理,显示电机的转速。

试验第一步,让LED数码管显示起来。因为显示数据是最终目的。使用的这个板子,是集成了HC595锁存器的板子。相比于网上买的大部分51开发板数码管电机设计,使用两个HC595,可以大大减少pin脚的数量。网上使用的4位数码管,需要8个pin作为段选或者位选,非常麻烦。根据HC595的手册,具有锁存加移位的特性(图中我标注所示)

最上面的3个SH-CP/DS/ST-CP,像极了SPI通信波形,只要合理配置,只需要3个信号线即可完成4数码管的轮流显示。于是在开发板的pin做了如下硬件配置Pin(数码管)  74HC595SPIPinSCLKPin11(shift)SPICLKPB13RCLKPin12(Storage)NSSPB12DIOPin14(datainput)SPIMOSIPC3QHPin9(dataoutput)SPIMISOPC2SPI配置代码如下(配置了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); }实际上每次只会有一个数码管亮,为了较好的视觉体验,将数码管进行千位百位十位个位循环显示,这样做的好处是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_CH2TIM_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开发板,完成了编码器计数和电机转速的计算,并通过数码管将电机转速实时显示出来。END本文系21ic蓝V作者grhr编纂

(0)

相关推荐

  • 船模制作——遥控模块 基于stm32和nrf24l01(固件库开发)

    目录 nrf24l01介绍 引脚图 引脚功能介绍 模式配置方法 官方宏 stm32配置 引脚映射 初始化函数 SPI模拟通信函数 nrf24l01配置函数 nrf24l01发射和接收 nrf24l01 ...

  • nrf24l01(程序)

    2016-10-03 21:01:44 ryan_jianjian #include 'RF24L01.h' #include 'delay.h' u16 RTD_wait = 0; //发送等待时间 ...

  • STM32F1系列之常用外设说明

    STM32F1系列之常用外设说明

  • NRF24L01双向传输(一对一)

    NRF24L01双向传输(一对一) 简介 本文章记录两个NRF24L01无线模块实现双向传输的软件设计~ 为什么可以双向传输呢?这要归功于它具有Enhanced ShockBurst,可以工作在主接收 ...

  • 手把手教你VSCode搭建STM32开发环境

    干货福利,第一时间送达! 摘要:作为一个51单片机或STM32单片机的使用者,keil一直是我们的必备的一款工具之一.但keil的一些问题也一直存在,当然也有人用其他的,比如STM32CubeIDE. ...

  • 手把手教你在STM32上实现OLED视频播放(很简单也很硬很肝!)

    我们这一代的年轻人基本上都很喜欢逛B站,大部分老人都认为我们这些年轻人上B站是为了看动漫.看游戏等等,谁跟你B站是用来看这些的,B站是用来学习的! 前一段时间就刷到一个视频,某B站Up主利用小熊派开发 ...

  • 老师傅手把手教你清洗摩托车化油器,全图示

    许多摩友换机油挺勤快,但清洗化油器的重要性却认识不足,有的一年也不清洗一次,导致化油器在经过长时间的使用后,量孔中有许多沉淀和胶质,浮子室掺杂了许多杂质或水份.这就是摩托车难启动或无法启动.油门响应迟 ...

  • 8步手把手教你学会薪酬设计

    上次说到薪酬诊断方法,就像医生看病一样,有问题需要良方下单开药,方能治理企业"顽疾",今天就聊聊如何进行薪酬设计. Part 1薪酬结构定义 广义结构:对统一组织内部的不同职位或者 ...

  • 全图示手把手教你排除宝马F650GS小链异响故障

    作者:摩托中国 崔力根 朋友的一辆宝马650GS摩旅回来发现小链条部位有异响,自己买了小链让我帮他换. 当拆开发动机上盖以后,发现小链并没有拉长,反而是因为液压张紧器失效引起的. 正常的张紧器是两段组 ...

  • 手把手教你安装踏板摩托车普利珠(全图示)

    作者: 摩托中国 高泽睿 取下螺丝,配合利用橡胶锤或木锤,把边盖取下,不用怕,这不算开发动机. 夹持器 使用夹持器,相当于我常说的卡钳,使用这种夹持器可避免卡钳损坏固定盘上叶片的可能,结合使用17的套 ...

  • 手把手教你“五步”测量摩托车缸压

    作者:摩托中国 缸压表 有许多小伙伴不知道如何使用气缸压力表测量缸压,今天我就手把手的教你一下,共分五步,按步走就可以了. 第一步:发动机运转至正常温度,冷却液的温度85-95度. 第二步:拆除全部火 ...

  • 手把手教你在家种植生姜

    生姜,是厨房中不可缺少的一种调味果蔬,但由于近年来物价不断上涨,生姜的价格也水涨船高(小编表示都快吃不起了~),既然如此何不自己在家种植,自给自足丰衣足食.那么,本文就来教大家怎样在家(阳台也可以哦~ ...