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

嵌入式ARM 2021-03-10 00:00


▍编码器的由来和原理

若要对伺服系统中的电机进行高精度控制,需要准确的转子角度位置,这时候自然会想到,如果能张江转子每一圈进行细分,这样每次转多少角度便能精确知道。在这样的背景下,相对编码器就诞生了。

在网上找到下文这个图,很形象的表征了相对编码器的原理。


如图所示,在码盘上平均开出很多个等间距的槽,一段是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(数码管)
  
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); }

实际上每次只会有一个数码管亮,为了较好的视觉体验,将数码管进行千位百位十位个位循环显示,这样做的好处是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开发板,完成了编码器计数和电机转速的计算,并通过数码管将电机转速实时显示出来。

END

本文系21ic蓝V作者grhr编纂


推荐阅读
你怎样选择开源免费RTOS?
GD32也开始假货翻新泛滥了
工程师姓什么很重要!别再叫我“X工”!!!

→点关注,不迷路←
嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 125浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 116浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦