记编码器的学习历程

编码器以及其原理

首先关于编码器概述不用说太多,网上一搜一大把(以下介绍为复制内容):

编码器分为光电和霍尔编码器是一种将角位移或者角速度转换成一连串电数字脉冲的旋转式传感器,我们可以通过编码器测量到位移或者速度信息。编码器从输出数据类型上分,可以分为增量式编码器和绝对式编码器。

从编码器检测原理上来分,还可以分为光学式、磁式、感应式、电容式。常见的是光电编码器(光学式)和霍尔编码器(磁式)。两种(以下介绍为复制内容):

光电编码器是一种通过光电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。光电编码器是由光码盘和光电检测装置组成。光码盘是在一 定直径的圆板上等分地开通若干个长方形孔。由于光电码盘与电动机同轴,电动机旋转时,检测装置检测输出若干脉冲信号,为判断转向,一般输出两组存在一 定相位差的方波信号。

霍尔编码器是一种通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量的传感器。霍尔编码器是由霍尔码盘和霍尔元件组成。霍尔码盘是在一 定直径的圆板上等分地布置有不同的磁极。霍尔码盘与电动机同轴,电动机旋转时,霍尔元件检测输出若干脉冲信号,为判断转向,一般输出两组存在一定相位差的方波信号。

下图为正交编码器的输出波形图:

1620478338406.png

除了这些还有一个参数对我们很有用,那就是编码器的线数。

线数是什么呢?

我们可以简单的理解为线数是多少,那么编码器每转动一圈就会产生多少个脉冲信号。


程序实现(废话不说纯享版)

实例环境:STM32F401CCU6+CubeMX+HAL库

首先随便打开一个定时器,打开编码器模式:

1620478445392.png

设置为TI1和TI2共同触发:

1620478459280.png

打开串口一以便接收数据(真的不是懒得配置屏幕显示)

接下来我们需要额外打开一个定时器来测出单位时间产生了多少脉冲,我用了TIM4来进行定时:

1620478474987.png

并且打开定时器中断:

1620478513729.png

再打开一个定时器用于产生PWM波(20kHz占空比50%)进而控制电机转动。

1620478530063.png

电机驱动为TB6612,使用并不麻烦,此处不再赘述。

为了方便调试,我们再打开两个连接着开关的IO口用于控制PWM的输出(一个用于控制电机转动与停止,另一个调节占空比),接着生成工程。

敲代码:

//变量定义
/* USER CODE BEGIN PV */
float freq=0,CNT_prevalue=0,period=0,velocity=0,position=0;
/* USER CODE END PV */

/* USER CODE BEGIN 0 */
//包含头文件
#include "stdio.h"
#include "math.h"
//串口重定向
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#else
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif
PUTCHAR_PROTOTYPE{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1 , 0xffff);
    return ch;
}
//定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    velocity=(TIM1->CNT*1.0f-CNT_prevalue)*1.0f*1000/(13*4);
    //公式应该是:
    // (CNT2-CNT1)*计数器溢出频率/(编码器线数*采集边沿数)
    //PS:因为设置了TI1和TI2共同采样,所以采集边沿为4个。
    period = fabs( velocity * 360 );
    position = (int)period % 360;
    value = TIM1 -> CNT;
}
/* USER CODE END 0 */

//循环前后
/* USER CODE BEGIN WHILE */
    HAL_TIM_Base_Start_IT(&htim4);//开启定时器4中断
    HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);//开启PWM通道
    HAL_TIM_Encoder_Start(&htim1,TIM_CHANNEL_2);//开启定编码器接口通道
    HAL_TIM_Encoder_Start(&htim1,TIM_CHANNEL_1);//同上
while (1)
{
    //控制电机
    if(!HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1))
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,1);
    else
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,0);
    //输出计算结果
    printf(" v=%.1f r/s\n period=%.1f degrees/s\n position=%.0f degrees\n",velocity,period,position);
    //调节方波占空比
    if(!HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0))
    {
        TIM3->CCR1+210>4200?TIM3->CCR1=0:TIM3->CCR1+=210;
    }
    HAL_Delay(200);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

烧录,测试,结果如图:

1620478729839.png

教程结束。


程序实现(墨迹墨迹解读版)

方法其实很好理解,只要测量单位时间内输出的脉冲个数即可间接得知转速。

那么如果想测得脉冲个数,相信大家一定会很容易想到输入捕获。

输入捕获大家应该都会,好的教程结束,大家加油!

我也是第一个想到了输入捕获,不过出现这个想法的时候就有一个问题一直难以解决:

如何判断电机的转向呢?这是个值得思考的问题,因为我们后面想要实现很多功能都需要知道电机的转向,然而仅仅使用输入捕获只能测出脉冲的个数进而测出速度,确实没办法知道电机的转向。此路不通的情况下,就要开始思考其他的方法了。

好在经过好心人的提醒我得知:STM32具有专门的硬件编码器接口,于是立刻跑去翻手册。那么让我们先来看看STM32的硬件编码器接口是怎么回事:

(PS:如果其他的单片机没有如果没有编码器外设的话,我们可以使用GPIO中断进行,在中断里面判断另一相的电平来判断方向,该方法感兴趣的读者可以自己尝试 )

1620478834327.png

好家伙,选择接口模式之后直接就选择计数边沿,有输入捕获内味了嗷(可是为什么只选择TI1和TI2的边沿却不是选择TI3、TI4呢?)。

我们打开CubeMX试着配置一下:

首先随便打开一个定时器(此处其实建议使用片内32位定时器),打开编码器模式:

1620478949658.png

可以看到选下编码器模式之后上面五个配置选项就锁定了。也就是说编码器模式的设置以及频道的使用其实是早就内定好的,并不像其他的模式一样可以自己选择通道。

这件事情其实在参考手册的通用定时器框图里面也得到了验证:

1620479003499.png

至于手册说的边沿选择这句话 :

1620479026932.png

是在这里设置的:

1620479037219.png

正如手册所说,可以选择仅TI1、仅TI2和TI与TI2共同使用三种方式。

继续阅读手册:

1620479059771.png

极性?我们回忆一下PWM波中的极性,表示的是OCxREF和实际输出OCx之间的关系(即极性相同或相反)。好的,此处拓展想一下,这里的极性应该也是选择一个二值的量,很明显就是检测边沿。

对应到CubeMX里面:

1620479200390.png

CubeMX里面接下来的IC Selection选项里面只有直接选项,从图里面就可以明确的看出来。继续看手册:

1620479223708.png

输入滤波器的编程对应的应该就是这里:

1620479251663.png

至此,CubeMX里面的可设置项就看完了,我们继续看看手册里面还说了什么:

1620479273239.png

上面的内容挺让人头大的,不过好在重点就两句。第一句的意思其实就是说只有当编码器产生脉冲的时候,CNT的值才会改变。第二句的意思就是说输入改变的时候会更改计数器的计数方向。

1620479305031.png

看到这里我们就懂了,这模式不但能计数,还能辨别转动方向。

后面给出了一个实例我们来看一下:

1620479327782.png

上面给出的波形就是编码器的波形,很容易看出编码器正转时CNT递增,反之递减。

因此,我们只需要在中断里面连续进行两次采样,对两次采样的CNT值做差,如果结果为正说明CNT正向计数,即电机正向旋转,反之反向。除此之外,我们还可以通过读取TIMx_CR1寄存器的DIR位来获得计数方向进而得知电机转向:

1620479372193.png

实现代码前面给过了,就不赘述了。


补充说明

2021.5.17补充:

关于利用GPIO中断写出软件编码器的实现方法:

由于最近在学MSP432,其上并没有硬件编码器接口,因此只能自己写软件编码器,实现了一下发现其实并不难,写在此作为之前内容的补充。

PS:以下实现代码为使用MSP432的函数实现的,STM32版本如果有机会写的话其实就是没机会了会写的。

首先是分析:

1621253575694

观察编码器AB相的输出不难看出,当电机正向旋转时,下方相位领先上方90°,反转反之。

因此,要判断旋转方向其实就是判断两侧输入的相位差,但是单片机并不是示波器,它没法看出两个波形的全貌,只能看到某一瞬间两个波形的高低电平状况。那么有没有好的方法可以利用瞬间的电平状态判断出两个波形的相位差呢?答案是有的。

其实很简单,我们观察下当上方波形出现上升沿的时候,对应下方波形的电平就会发现:

正向旋转的时候对应为低电平;

反向旋转的时候对应位高电平。

如此我们便实现了对方向的判断。

与此同时,上升沿的个数其实也对应着波形中出现的高电平个数,因此可以利用计数上升沿个数的方法来实现对速度的测量。

MSP432实现代码如下(IDE:Clion):

//
// Created by ZheWana on 2021/5/17.
//

#include "ti/devices/msp432p4xx/driverlib/driverlib.h"
#include "UARTRetarget.h"
/* Standard Includes */
#include <stdint.h>
#include <stdbool.h>

//定义技术脉冲个数的计数变量CNT
int32_t CNT;

//GPIO中断处理函数
void GPIO_IRQHandler(void) {
    uint8_t temp = GPIO_getInputPinValue(GPIO_PORT_P3, GPIO_PIN2);
    uint32_t status;
    status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P3);
    MAP_GPIO_clearInterruptFlag(GPIO_PORT_P3, status);
    if (status & GPIO_PIN3) {
        MAP_GPIO_toggleOutputOnPin(GPIO_PORT_P1, GPIO_PIN0);
        if (temp == GPIO_INPUT_PIN_HIGH) {
            CNT++;
        } else if (temp == GPIO_INPUT_PIN_LOW) {
            CNT--;
        }
    }
}


int main(void) {
    WDT_A_holdTimer();

    GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0);

    //使用P3.3和P3.2作为AB相输入
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P3, GPIO_PIN3);
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P3, GPIO_PIN2);
    
    //将P3.3作为中断引脚
    GPIO_clearInterruptFlag(GPIO_PORT_P3, GPIO_PIN3);
    GPIO_enableInterrupt(GPIO_PORT_P3, GPIO_PIN3);
    GPIO_interruptEdgeSelect(GPIO_PORT_P3, GPIO_PIN3, GPIO_LOW_TO_HIGH_TRANSITION);

    Interrupt_enableInterrupt(INT_PORT3);
    Interrupt_enableMaster();
    Interrupt_registerInterrupt(INT_PORT3, GPIO_IRQHandler);

    while (1) {

    }
}
上一篇
下一篇