如何用STM32让相对编码器说话?

发布者:Ziyu2022最新更新时间:2024-04-10 来源: elecfans关键字: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相对位置就知道电机是正转还是反转了。

b8e1be74-86b9-11eb-8b86-12bb97331649.png

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

b90de332-86b9-11eb-8b86-12bb97331649.png

正转


反转

▍使用STM32,让编码器说话

背景

STM32中提供了编码器接口,比较适用于相对编码器的应用场景。在手册中可以看到:


可以看到这里使用专用的模块就能完成相应的计数,通过数据的变化就能测出电机的转速。

所以,我想让编码器说话。在家翻箱倒柜以后,我准备了如下几个东西:

(1)带编码器的直流电机:这是作为编码器的载体使用,电机编码器的分辨率较低,每圈只有16个脉冲。但不影响测试。

(2)直流电源:用来直观的调电机的转速和正反转。

为了避免打广告的嫌疑,就不贴电源和电机图片了。

(3)STM32开发板:在家翻箱倒柜,找出2015年在21ic获得的STM32072 discovery板

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

试验第一步,让LED数码管显示起来。

因为显示数据是最终目的。使用的这个板子,是集成了HC595锁存器的板子。相比于网上买的大部分51开发板数码管电机设计,使用两个HC595,可以大大减少pin脚的数量。网上使用的4位数码管,需要8个pin作为段选或者位选,非常麻烦。

根据HC595的手册,具有锁存加移位的特性(图中我标注所示)

bc24263a-86b9-11eb-8b86-12bb97331649.png

最上面的3个SH-CP/DS/ST-CP,像极了SPI通信波形,只要合理配置,只需要3个信号线即可完成4数码管的轮流显示。

于是在开发板的pin做了如下硬件配置

Pin(数码管) 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); }

实际上每次只会有一个数码管亮,为了较好的视觉体验,将数码管进行千位百位十位个位循环显示,这样做的好处是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,进行下图模式的计数。

be704acc-86b9-11eb-8b86-12bb97331649.png

即效果如下:


代码如下

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。编码器的波形也用示波器显示出来了。


关键字:STM32  码盘 引用地址:如何用STM32让相对编码器说话?

上一篇:STM32串口通信基本原理详解
下一篇:TouchGFX是一个基于STM32硬件由C++写成的软件框架

推荐阅读最新更新时间:2024-11-09 16:12

STM32基础2--SMT32CubeMX的 code目录
1.0:Code的目录结构 在上一篇文章生成代码后,通过MDK打开项目,可以看到如下的项目结构。对于GPIO来说,我们只需要关注两个文件 main.h , main.c , gpio.h , gpio.c 。 2.0:main.h main.h 可以看到引入头文件#include stm32f4xx_hal.h ,以及对GPIO进行了宏定义。 GPIO宏定义是由于在配置GPIO引脚时使用User Label /* USER CODE BEGIN Header */ /** ************************************************************************
[单片机]
<font color='red'>STM32</font>基础2--SMT32CubeMX的 code目录
STM32-快速上手输入捕获
配置步骤 使能定时器(通用定时器在APB1下)和相关IO(APB2下)时钟 void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState); void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState); 初始化IO口为输入模式 void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); 初始化定时器(主要是配置ARR和PSC) void TIM_TimeBas
[单片机]
stm32 can不稳定的解决方法
问题出现的背景 需要写一个新的电机的驱动,使用can通讯,驱动比较简单,很快就写好了。自己单独测试一个电机的时候没有问题,正反转测什么的都很正常。本以为事情会很顺利,但是在测两个电机的时候,却发现很严重的问题,经常左电机不装或者是右电机不转。 问题的解决步骤 发送: 3 个发送邮箱 发送报文的优先级特性可软件配置 记录发送 SOF 时刻的时间戳 接收: 3 级深度的2个接收 FIFO 14 个位宽可变的过滤器组 - 由整个 CAN 共享 标识符列表 FIFO 溢出处理方式可配置 记录接收 SOF 时刻的时间戳
[单片机]
stm32 读取bmp图像的信息
在sd卡文件系统下读取bmp图像和显示是比较容易的,为了给jpeg解码提供一个过程,这里我先介绍一下bmp的读取方式 这里主要是介绍读取bmp信息的一些方法 首先说一下BMP的4个组成部分: 1.文件头信息块 0000-0001:文件标识,为字母ASCII码“BM”。 0002-0005:文件大小。 0006-0009:保留,每字节以“00”填写。 000A-000D:记录图像数据区的起始位置。各字节的信息依次含义为:文件头信息块大小,图像描述信息块的大小,图像颜色表的大小,保留(为01)。 2.图像描述信息块 000E-0011:图像描述信息块的大小,常为28H。 0012-0015:图像宽度。 0016-
[单片机]
STM32微控制器的工作原理和应用 STM32微控制器的命名规则
STM32微控制器的工作原理和应用 STM32微控制器是一种基于ARM Cortex-M内核的高性能、低功耗、低成本的微控制器。它广泛应用于各种嵌入式系统,包括工业控制、消费电子、医疗设备、汽车电子等领域。 STM32微控制器的工作原理是基于ARM Cortex-M内核的,它可以区分为不同型号的Cortex-M0、Cortex-M3、Cortex-M4和Cortex-M7等系列。这些内核提供了高性能、低功耗和丰富的功能,支持多种存储器接口和总线结构,具有高度可扩展性。STM32微控制器集成了丰富的外设模块,用于处理各种输入和输出接口。常见的外设包括通用输入输出(GPIO)、定时器(TIM)、串行通信接口(USART、SPI、I
[单片机]
STM32位带操作-详解-计算过程
位带操作 位带操作的概念其实很多年前就有了,那还是 8051 单片机开创的先河。如今,CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版。 官方解释 先来看一下Cortex-M3权威指南中描述的位带操作: 支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写操作。在CM3中,有两个区中实现了位带。其中一个是 SRAM 区的最低1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。 正点原子库
[单片机]
<font color='red'>STM32</font>位带操作-详解-计算过程
LVGL | lvgl最新版本在STM32上的移植使用
lvgl简介 LittlevGL是一个免费的开源图形库,提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素、漂亮的视觉效果和低内存占用。 特点: 强大的构建模组 按钮、图表、列表、滑块、图像等 先进的图形 动画、反锯齿、半透明、平滑滚动 多样的输入设备 触摸板、鼠标、键盘、编码器等 多显示器支持 支持同时使用多个TFT或单色显示器 多语言支持 UTF-8格式文字编码 完全自定义 图形元素 硬件无关 可用于任意微控制器或显示器 可裁剪 用于小内存(80 KB FLASH,12 KB RAM)操作 操作系统、外部存储以及GPU 支持但非必须 单帧缓存 即可实现先进的图形效果 C语言编写 以最大化兼容(C
[单片机]
LVGL | lvgl最新版本在<font color='red'>STM32</font>上的移植使用
stm32 用dac输出正弦波
前段时间师兄拜托写一个单片机代码,由于之前没整过,在这记录一下。也希望可以帮助到正在学这个的朋友们.stm32F4并不支持自动生成正弦波,事先在正弦波上找500个点存在一个数组里,然后当每一个时钟到来的时候,dac就会输出电压值。 void sin_Generation(void) {u16 n;for(n=0;n tableSize;n++) {sinTable = (sin(2*PI*n/tableSize)+1)*2047;} 经过线性转换后,数字输入会转换为 0 到 VREF+ 之间的输出电压。各 DAC 通道引脚的模拟输出电压通过以下公式确定: DACoutput =Vref*dor/4095
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved