【程序】【必须】编码器电机测速,原理+代码+滤波

这篇具有很好参考价值的文章主要介绍了【程序】【必须】编码器电机测速,原理+代码+滤波。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考资料:

  1. https://blog.csdn.net/lzzzzzzm/article/details/119416134
  2. 野火STM32电机开发教程

1. 编码器种类及原理

常见的编码器有两种,分别为霍尔编码器和GMR编码器。

1.1 霍尔编码器

​ 霍尔编码器圆盘上分布有磁极,当圆盘随电机主轴转动时,会输出两路相位差90°的方波,用这两路方波可测出电机的转速和转向。霍尔编码器一般是13线的,就是转一圈每项会输出13个脉冲,这个精度基本能够满足大部分使用场景的要求。

1.2 光电编码器

​ 如图,打孔码盘随电机进行旋转。每当光线穿过圆孔,输出电平就会改变,如此产生方波,测量方波的频率即可测出电机转速。

1.3 GMR编码器

​ GMR编码器利用巨磁阻效应进行测速,GMR编码器一般是500线的,转一圈每项会输出500个脉冲,精度比霍尔编码器高得多,适合对精度要求高的环境或者最求完美的人。

​ 下图来自淘宝店铺轮趣科技

2. 常用测速方法

2.1 倍频技术

​ 编码器会输出两路方波信号,如果只在通道A的上升沿计数,那就是1倍频;通道A的上升、下降沿计数,那就是2倍频;如果在通道A、B的上升、下降沿计数,那就是4倍频。

​ 使用倍频可以最大程度地利用两路信号,提高测速的灵敏度。

【程序】【必须】编码器电机测速,原理+代码+滤波

​ 下面说的三种测速方法只是在软件计算上的区别,硬件上是没有改变的

2.1 M法测速(周期测量法)

​ 简单地说就是根据单位时间一共有多少个脉冲来计算转速。

​ 设转速为n(r/s);测量时间为 T 0 T_0 T0(s); T 0 T_0 T0时间内的脉冲数为 M 0 M_0 M0;电机转一圈产生的脉冲数为C;则转速计算公式为
n = M 0 C T 0 n=\frac{M_0}{CT_0} n=CT0M0
​ 当 M 0 M_0 M0很大,即转速快时,这个方法测得精度和平稳性都很好,但当 M 0 M_0 M0很小,速度改变带来的 M 0 M_0 M0变化很小,即转速慢时算出的误差就很大。所以M法测速适用于高转速场景

2.2 T法测速(频率测量法)

​ T法测速是这样操作的:是指先建立一个频率已知且固定的高频脉冲,当编码器读到一个信号,开始对高频脉冲进行计数,编码器第二个信号到来后,停止计数。根据对高频脉冲计数的次数、高频脉冲频率和电机转一圈编码器产生的脉冲数进行速度计算。

​ 设转速为n(r/s);两个脉冲的时间间隔为 T E T_E TE(s);电机转一圈产生的脉冲数为C; F 0 F_0 F0(Hz)为编码器输出脉冲的频率; M 1 M_1 M1为高频脉冲的计数值,则转速计算公式为

n = 1 C T E = F 0 C M 1 n=\frac{1}{CT_E}=\frac{F_0}{CM_1} n=CTE1=CM1F0
理解: C T E CT_E CTE为当前速度下电机转一圈需要的时间,1圈除以1圈所需要的时间即可得到转速

其中 T E T_E TE M 1 M_1 M1 F 0 F_0 F0有如下关系
T E = M 1 F 0 T_E=\frac{M_1}{F_0} TE=F0M1
​ 当 T E T_E TE很大即转速很慢时,T法测速有较高的精度和平稳度,但当 T E T_E TE很小,即转速很快时,速度改变带来的 T E T_E TE变化很小,算出的误差就很大。所以T法测速适用于低转速场景

2.3 M/T法测速

​ M/T法综合了M法和T法的优势,计算公式如下。

n = F 0 M 0 C M 1 n=\frac{F_0M_0}{CM_1} n=CM1F0M0

​ 理解:公式中只有 M 0 M_0 M0 T 0 T_0 T0时间内的脉冲数)、 M 1 M_1 M1(高频脉冲的计数值)为变量。当转速快时, M 1 M_1 M1变小, M 0 M_0 M0变大,相当于M法;当转速慢时, M 1 M_1 M1变大, M 0 M_0 M0变小,相当于T法。

3. STM32实现编码器M法测速

​ 下面是使用M法测速的实例代码。

3.1 CubeMax配置

​ 为了进行测速,我们一共需要3个定时器,作用分别是:①输出PWM;②编码器模式进行脉冲计数;③计时,确定每次测速的时间间隔。

​ 其中,用于定时的定时器③可以用输出PWM的定时器①代替,输出PWM的定时器一样有更新中断,只要在更新中断里运行测速程序即可。但由于PWM定时器的频率很快,所以我们会间隔很多个更新中断后进行测速。

​ 具体配置如下:

​ TIM2:编码器输入定时器

【程序】【必须】编码器电机测速,原理+代码+滤波

​ 这里开启了两个通道计数,即Encoder Mode中设置为Encoder Mode TI1 and TI2。这里就是上文倍频技术的4倍频。

​ 编码器模式下的定时器其实是个计数器,在编码器的脉冲到来时,Counter会相应地加和减,正转时加,反转时减,溢出后到达另一个极端值,比如说向上计数到达20001时会变成0

​ 接下来我们需要设定编码器的两个引脚为上拉,防止误触发。

TIM3:PWM输出定时器

​ STM32F103的定时器时钟来源于APB总线时钟,最高为72MHz,我们一般也配置为72MHz。APB时钟经过PSC分频后得到实际的定时器的计数频率。定时器的计数频率为
f c o u n t = f A P B P S C + 1 f_{count} = \frac{f_{APB}}{PSC+1} fcount=PSC+1fAPB
​ 当计数值达到ARR寄存器的设定值后计数值归零,重新开始计数,完成一个周期。在一个周期中,PWM高电平时间由比较寄存器(ARRARR)的值决定。在设置PWM mode1且向上计数时,计数值小于ARR的值时是高电平,大于ARR值是低电平。所以PWM频率是这样计算的
f P W M = f A P B ( P S C + 1 ) ( A R R + 1 ) f_{PWM} = \frac{f_{APB}}{(PSC+1)(ARR+1)} fPWM=(PSC+1)(ARR+1)fAPB

​ 上图中设置初始PWM频率为100Hz。但是设置频率最好在20Hz~20000Hz以外,因为这个频率内的PWM波会让电机发出明显的电流声。我们可以将PSC设置为3-1,将ARR设置为1000-1,我这里作为演示就先不管了。

​ TIM4:计时间隔定时器

【程序】【必须】编码器电机测速,原理+代码+滤波

​ 设定为10Hz即1秒计算10次速度。

​ 最后要开启中断,并保证编码器定时器的中断优先级高于计时间隔定时器的中断优先级,避免编码器输入被间隔计时中断。

【程序】【必须】编码器电机测速,原理+代码+滤波

​ 其他基础配置不再赘述。

3.2 接线

编码器电机、电机驱动(这里用的L298n)、STM32、电源(可以是12V电池)的接线如下

编码器电机 电机驱动 STM32 电机驱动供电
VM VCC
VDD PWM1 PA6
VSS PWM2 PA7
3V3 3V3
GND GND GND GND
编码器通道1 PA0
编码器通道2 PA1

3.3 代码编写

encoder.h中的内容

#ifndef _ENCODER_H_
#define _ENCODER_H_

#include "stm32f1xx.h"

//电机1的编码器输入引脚
#define MOTO1_ENCODER1_PORT GPIOA
#define MOTO1_ENCODER1_PIN  GPIO_PIN_0
#define MOTO1_ENCODER2_PORT GPIOA
#define MOTO1_ENCODER2_PIN  GPIO_PIN_1

//定时器号
#define ENCODER_TIM htim2
#define PWM_TIM     htim3
#define GAP_TIM     htim4

#define MOTOR_SPEED_RERATIO 45u    //电机减速比
#define PULSE_PRE_ROUND 11 //一圈多少个脉冲
#define RADIUS_OF_TYRE 34 //轮胎半径,单位毫米
#define LINE_SPEED_C RADIUS_OF_TYRE * 2 * 3.14
#define RELOADVALUE __HAL_TIM_GetAutoreload(&ENCODER_TIM)    //获取自动装载值,本例中为20000
#define COUNTERNUM __HAL_TIM_GetCounter(&ENCODER_TIM)        //获取编码器定时器中的计数值

typedef struct _Motor
{
    int32_t lastCount;   //上一次计数值
    int32_t totalCount;  //总计数值
    int16_t overflowNum; //溢出次数
    float speed;         //电机转速
    uint8_t direct;      //旋转方向
}Motor;

#endif

encoder.c中的内容

#include "encoder.h"

Motor motor1;

void Motor_Init(void)
{
    HAL_TIM_Encoder_Start(&ENCODER_TIM, TIM_CHANNEL_ALL);      //开启编码器定时器
    __HAL_TIM_ENABLE_IT(&ENCODER_TIM,TIM_IT_UPDATE);           //开启编码器定时器更新中断,防溢出处理
    HAL_TIM_Base_Start_IT(&GAP_TIM);                       //开启100ms定时器中断
    HAL_TIM_PWM_Start(&PWM_TIM, TIM_CHANNEL_2);            //开启PWM
    HAL_TIM_PWM_Start(&PWM_TIM, TIM_CHANNEL_1);            //开启PWM
    __HAL_TIM_SET_COUNTER(&ENCODER_TIM, 10000);                //编码器定时器初始值设定为10000
    motor1.lastCount = 0;                                   //结构体内容初始化
    motor1.totalCount = 0;
    motor1.overflowNum = 0;                                  
    motor1.speed = 0;
    motor1.direct = 0;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器回调函数,用于计算速度
{
    if(htim->Instance==ENCODER_TIM.Instance)//编码器输入定时器溢出中断,用于防溢出                   
    {      
        if(COUNTERNUM < 10000) motor1.overflowNum++;       //如果是向上溢出
        else if(COUNTERNUM >= 10000) motor1.overflowNum--; //如果是向下溢出
        __HAL_TIM_SetCounter(&ENCODER_TIM, 10000);             //重新设定初始值
    }
    else if(htim->Instance==GAP_TIM.Instance)//间隔定时器中断,是时候计算速度了
    {
        motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上计数(正转),返回值为0,否则返回值为1
        motor1.totalCount = COUNTERNUM + motor1.overflowNum * RELOADVALUE;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        motor1.speed = (float)(motor1.totalCount - motor1.totalCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 10;//算得每秒多少转
        //motor1.speed = (float)(motor1.totalCount - motor1.totalCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 10 * LINE_SPEED_C//算得车轮线速度每秒多少毫米
        motor1.lastCount = motor1.totalCount; //记录这一次的计数值
    }
}

使用时需要在main.c的循环之前调用Motor_Init函数进行初始化。

如果发现无法进入编码器中断导致totalCount经常溢出归零,可以尝试换一种防溢出的方法,代码如下

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器回调函数,用于计算速度
{	
    if(htim->Instance==GAP_TIM.Instance)//间隔定时器中断,是时候计算速度了
    {
        motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上计数(正转),返回值为0,否则返回值为1
        motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        
        if(motor1.lastCount - motor1.totalCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum++;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        else if(motor1.totalCount - motor1.lastCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum--;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        
        motor1.speed = (float)(motor1.totalCount - motor1.lastCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 3000;//算得每秒多少转,除以4是因为4倍频
        motor1.lastCount = motor1.totalCount; //记录这一次的计数值
}

3.4 结果

现在将测得的速度值输出到串口,就可以看到电机的实时转速了

【程序】【必须】编码器电机测速,原理+代码+滤波

4. 滤波

​ 到这里测速就已经完成了,但是如果后续想要进行PID控制,我们最好对测得的速度进行检查。

​ 如果将测得的速度值用VOFA+上位机画出来,我们可能会看到这样的曲线

​ 从图中我们可以看到,速度值在目标速度附近来回小幅度震荡,始终不稳定。这是因为编码器测速得到的速度值是离散的,如果电机的速度值刚好卡在两个离散值中间,我们测得的速度值就会在这两个离散值中间来回震荡。如果我们想要解决这个问题,最好先对测速的精度进行分析。

​ 对于M法测速来说,测速的公式如下,其中,k是将速度换算成rpm的比例系数
s p e e d = Δ p u l s e / ( 4 ∗ 减速比 ∗ 编码器线数 ) ∗ k speed=\Delta pulse / (4 * 减速比 * 编码器线数) * k speed=Δpulse/(4减速比编码器线数)k
​ 由于除号后面的都是定值,所以我们只要分析每次采样的脉冲数对速度的影响即可。

​ 我们假设现在测速频率是50Hz,减速比为30,编码器线数为500,那么脉冲数每变化1,速度的变化为
Δ s p e e d = 1 ( 4 ∗ 减速比 ∗ 编码器线数 ) ∗ 3000 = 1.923 r p m \Delta speed = \frac{1}{(4 * 减速比 * 编码器线数)} * 3000 = 1.923 rpm Δspeed=(4减速比编码器线数)13000=1.923rpm
​ 所以我们测得的速度只能是1.923rpm的整数倍。如果想要提高精度,在电机不变的情况下,我们可以使用500线的GMR编码器或者降低测速频率。

​ 在VOFA+中,我们可以测得震荡时波峰和波谷的差值为1.92左右,和我们的计算相符。

​ 为了改善这一现象,我们可以对速度采样值使用平均滤波,即将最近几次的速度采样值存放到数组中,每测得一个新的速度,就将新速度存入数组,将最早测得的速度值从数组中删除,我们使用的速度值是数组中所有速度的平均值。实现代码如下

#define SPEED_RECORD_NUM 20 // 经测试,50Hz个采样值进行滤波的效果比较好

float speed_Record[SPEED_RECORD_NUM]={0};

/*
 * 进行速度的平均滤波
 * 输入新采样到的速度,存放速度的数组,
 * 返回滤波后的速度
 */
float Speed_Low_Filter(float new_Spe,float *speed_Record)
{
    float sum = 0.0f;
    test_Speed = new_Spe;
    for(uint8_t i=SPEED_RECORD_NUM-1;i>0;i--)//将现有数据后移一位
    {
        speed_Record[i] = speed_Record[i-1];
        sum += speed_Record[i-1];
    }
    speed_Record[0] = new_Spe;//第一位是新的数据
    sum += new_Spe;
    test_Speed = sum/SPEED_RECORD_NUM;
    return sum/SPEED_RECORD_NUM;//返回均值
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器回调函数,用于计算速度
{	
    if(htim->Instance==GAP_TIM.Instance)//间隔定时器中断,是时候计算速度了
    {
        motor1.direct = __HAL_TIM_IS_TIM_COUNTING_DOWN(&ENCODER_TIM);//如果向上计数(正转),返回值为0,否则返回值为1
        motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        
        if(motor1.lastCount - motor1.totalCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum++;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        else if(motor1.totalCount - motor1.lastCount > 19000) // 在计数值溢出时进行防溢出处理
        {
            motor1.overflowNum--;
            motor1.totalCount = COUNTERNUM_1 + motor1.overflowNum * RELOADVALUE_1;//一个周期内的总计数值等于目前计数值加上溢出的计数值
        }
        
        motor1.speed = (float)(motor1.totalCount - motor1.lastCount) / (4 * MOTOR_SPEED_RERATIO * PULSE_PRE_ROUND) * 3000;//算得每秒多少转,除以4是因为4倍频
        /*******************在这里添加滤波函数************************/
        motor1.speed = Speed_Low_Filter(motor1.speed,speed_Record);
        /**********************************************************/
        motor1.lastCount = motor1.totalCount; //记录这一次的计数值
}

​ 经过滤波后的速度曲线如下。

​ 绿线是原始速度,红线是目标速度,粉线是滤波后的速度。可以看到,滤波后的速度值明显要平滑很多,这对我们后期的PID调试是很有利的。文章来源地址https://www.toymoban.com/news/detail-422979.html

到了这里,关于【程序】【必须】编码器电机测速,原理+代码+滤波的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【32单片机学习】(3)霍尔编码器减速直流电机控制及测速

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 1.实验现象 2.实验接线及原理图 接线图 原理图  电机接线图 3.代码部分 1.主函数  main.c 2.按键部分   key.c  key.h pwm代码   pwm.c  pwm.h 电机驱动   motor.c   motor.h  OLED显示 oled.c oled.h  编码器

    2024年02月11日
    浏览(58)
  • 编码器测速原理与实现

    通常情况下编码器旋转一周会输出固定的脉冲数,即编码器的分辨率,通过测量固定时间T内编码器输出的脉冲数即可求得电机的转速。 假设编码器的分辨率为P,T时间内测得脉冲数m个,则单倍频(编码器转动一圈输出的脉冲数与分辨率相同)情况下电机转速为: (其中m/p为

    2024年02月17日
    浏览(49)
  • STM32定时器编码器模式实现直流有刷电机测速(HAL库)

    最近在做一个单片机大作业,要用到直流有刷,在这里把学习编码器的知识记录一下,学习参考资料: 正点原子DMF407电机控制专题教程_V1.0 我所使用的编码器是市面上常见的 磁电增量式编码器 ,其有AB两相,用于输出电机转动时的 脉冲数 ,AB两相的先后顺序决定了电机的

    2023年04月24日
    浏览(66)
  • AS5600步进电机编码器(原理图+pcb+stm32控制代码)

    AS5600是一个易于编程的磁性旋转位置传感器,具有高分辨率的12位模拟或PWM输出。这种非接触式系统测量一个直径磁化的轴上磁铁的绝对角度。 引脚如下图 他有两种供电模式:5V和3.3V 我们为了和stm32F103C8T6单片机的电压一致,也使用3.3V供电,然后开始画PCB。 使用嘉立创EDA画

    2024年02月03日
    浏览(60)
  • stm32-编码器测速

    编码电机 旋转编码器 A,B相分别接通道一和二的引脚,VCC,GND接单片机VCC,GND 以前的代码是通过触发外部中断,然后在中断函数里手动进行计次。使用编码器接口的好处就是节约软件资源。对于频繁执行,操作简单的任务,一般设计一个硬件电路模块来自动完成。 使用定时器

    2024年03月19日
    浏览(50)
  • msp432p401r 编码器电机程序

            编码器电机的原理网上有很多,如果不懂可以看下这个博主的文章STM32应用(九)编码器及其测速原理、L298N电机驱动控制编码器电机_長空雁的博客-CSDN博客         在这里博主只讨论如何去用软件采集测速。 编码器的AB相会输出方波,因为432p系列没有编码器模式

    2024年02月14日
    浏览(52)
  • MSP430——Timer(输出比较编码器测速)(五)

    之前写过关于定时器输出PWM波的简便方法和利用定时器测量频率,由于之前采用的测周法,这个方法当时测量的频率是非常精准的,但是对于测速度而言,我们采用PID算法的话,就会使得轮子停止响应非常的迅速,在之前算法的基础上,这个当轮子突然停止,也就是说突然一

    2024年02月15日
    浏览(37)
  • STM32-微项目07-旋转编码器计数及测速

    一、微项目实现目标: 检测旋转编码器模式下,检测旋转编码器的转动计数值及转速。并且区分转向,一侧转动增加cout,转速值为正,一侧转动减少count,转速值为负;   二、微项目硬件配置需求: 1,stm32F103C8T6核心板一块 2,0.96寸OLED显示,用于显示计数 3,旋转编码器,

    2024年02月08日
    浏览(39)
  • stm32f103单片机—编码器测速

    stm32f103ZET6开发板(非指定) MG513P3012V型号电机(带霍尔编码器)(非指定) 此种测速方法要求单片机的定时器具有编码器模式,对于stm32f1系列,具备编码器模式的定时器有TIM1/2/3/4/5/8, 定时器使用通道1、2来实现编码器功能 ,接线时注意把A/B相接到定时器通道1/2的引脚。 电

    2024年02月06日
    浏览(76)
  • 【STM32】【HAL库】定时器编码器模式测速

    目录 概述 HAL设置  定时器的编码器模式 定时器设置  常用函数 代码 电机AB相增量型编码器的介绍和解码方法在这里介绍过了 电机编码器 https://blog.csdn.net/m0_57585228/article/details/125791283 测速可以使用外部中断进行脉冲计数 很多型号的单片机中有专门的电路来计算脉冲的速度和

    2023年04月18日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包