学习STM32CubeMX旋转编码器模块

前言

雨打灯难灭,风吹色更明。
若飞天上去,定作月边星。——李白《咏萤火》


一、旋转编码器介绍

旋转编码器是一种可以左右旋转,同时也可以按下,也可以按下旋转的器件,通过左右旋转对应着内部不同开关的导通,同时按下也可以旋转,由此看来旋转编码器可以实现很复杂的功能,简单的通过左右旋转可以调节音量、亮暗等功能,按键可以发挥普通按键的作用,按下按键的同时左右旋转又可以区别普通旋转的按键,因此可以用一个旋转编码器同时调节音量和亮暗(举例),同时也可以通过不同的转速实现不同的功能,综上所述,它的功能很复杂,用处也很多。

旋转编码器是用来测量转速并配合 PWM 技术可以实现快速调速的装置,光电式旋转编码器通过光电转换,可将输出轴的角位移、角速度等机械量转换成相应的电脉冲以数字量输出(REP)

  • 按信号的输出类型分为: 电压输出、集电极开路输出、推拉互补输出和长线驱动输出。
  • 形式分类:
    有轴型:有轴型又可分为夹紧法兰型、同步法兰型和伺服安装型等。
    轴套型:轴套型又可分为半空型、全空型和大口径型等。
    以编码器工作原理可分为: 光电式、磁电式和触点电刷式。
    按码盘的刻孔方式不同分类编码器可分为增量式和绝对式两类。
  • 旋转编码器的原理和特点

  • 原理:

  • 旋转编码器是集光机电技术于一体的速度位移传感器。当旋转编码器轴带动光栅盘旋转时,经发光元件发出的光被光栅盘狭缝切割成断续光线,并被接收元件接收产生初始信号。该信号经后继电路处理后,输出脉冲或代码信号。
  • 旋转编码器是由一个中心有轴的光电码盘组成,其上有环形通、暗的刻线,有光电发射和接收器件读取,获得四组正弦波信号组合成A、B、C、D,每个正弦波相差90度相位差,将C、D信号反向,叠加在A、B两相上,可以增强稳定信号;另每转输出一个Z相脉冲以代表零位参考位。因为A、B两相相差90度,可以通过比较A相在前还是B相在前,借此来判别编码器的正转与反转,通过零位脉冲,可获得编码器的零位参考位。
  • 特点:

    体积小、重量轻、品种多、功能全、频响高、分辨能力高,力矩小,耗能低,性能稳定,可靠使用寿命长等特点。

  • 1、增量式编码器

    增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。增量式编码器也称为正交编码器,是通过两个信号线的脉冲输出来进行数据处理,一个输出脉冲就对应于一个增量位移,编码器每转动一定的位移,就会产生一个脉冲信号。通过读取单位时间脉冲信号的数量,达到测量速度的效果(v = s / t),通过对脉冲信号的累加,和编码器的码盘周长 (转一圈对应的距离) 便可以达到计算距离的效果(s = n * d)

  • 工作原理:

    增量式旋转编码器通过两个光敏接收管来转化角度码盘的时序和相位关系,得到角度码盘角度位移量的增加(正方向)或减少(负方向)。
    增量式编码器工作原理图解

  • 上图A、B两点间的距离为S2,A、B表示两个相同的光敏接收管,下面的角度码盘的光栅间距分别为S0(凹槽)和S1(凸起)。
  • 当角度码盘匀速转动时,可知输出波形图中的S0:S1:S2比值与实际图的S0:S1:S2比值相同,同理,当角度码盘变速转动时,输出波形图中的S0:S1:S2比值与实际图的S0:S1:S2比值仍相同。
  • 通过输出波形图可知每个运动周期:
  • 顺时针运动 逆时针运动
    A B A B
    1 1 1 1
    0 1 1 0
    0 0 0 0
    1 0 0 1
  • 我们把当前的A、B输出值保存起来,与下一个到来的A、B输出值做比较,就可以得出角度码盘转动的方向;

  • 如果光栅格S0等于S1时,也就是S0和S1弧度夹角相同,且S2等于S0的1/2,那么可得到此次角度码盘运动位移角度为S0弧度夹角的1/2,再除以所用的时间,就得到此次角度码盘运动位移的角速度。

  • S0等于S1时,且S2等于S0的1/2时,1/4个运动周期就可以得到运动方向位和位移角度,如果S0不等于S1,S2不等于S0的1/2,那么要1个运动周期才可以得到运动方向位和位移角度了。

    (我们用的鼠标的滚轮也是这个原理。)

  • 实际使用的增量式编码器输出三组方波脉冲A、B和Z(有的叫C相)相。A、B两组脉冲相位差90º,可以判断出旋转方向和旋转速度。而Z相脉冲又叫做零位脉冲(有时也叫索引脉冲),为每转一周输出一个脉冲,Z相脉冲代表零位参考位,通过零位脉冲,可获得编码器的零位参考位,专门用于基准点定位,如下图所示:

  • 增量是编码器转轴旋转时,有相应的脉冲输出,其计数起点可以任意设定,可实现多圈无限累加和测量。编码器轴转动一圈会输出固定的脉冲数,脉冲数由编码器码盘上面的光栅的线数所决定,编码器以每旋转360度提供多少通或暗的刻线称为分辨率,也称解析分度、或称作多少线,一般在每转5~10000线,当需要提高分辩率时,可利用90度相位差的A、B两路信号进行倍频或者更换高分辩率编码器。

  • 增量型编码器精度取决于机械和电气的因素,这些因素有:光栅分度误差、光盘偏心、轴承偏心、电子读数装置引入的误差以及光学部分的不精确性,误差存在于任何编码器中。

  • 编码器的信号输出有正弦波(电流或电压)、方波(TTL、HTL)等多种形式。并且都可以用差分驱动方式,含有对称的A+/A-、B+/B-、Z+/Z-三相信号,由于带有对称负信号的连接,电流对于电缆贡献的电磁场为0,信号稳定衰减最小,抗干扰最佳,可传输较远的距离,例如:对于TTL的带有对称负信号输出的编码器,信号传输距离可达150米。对于HTL的带有对称负信号输出的编码器,信号传输距离可达300米。

  • 增量式编码器轴旋转时,有相应的相位输出。其旋转方向的判别和脉冲数量的增减,需借助后部的判向电路和计数器来实现。其计数起点可任意设定,并可实现多圈的无限累加和测量。还可以把每转发出一个脉冲的Z信号,作为参考机械零位。当脉冲已固定,而需要提高分辨率时,可利用带90度相位差A,B的两路信号,对原脉冲数进行倍频。

  • 编码器时序:

  • 实物图:

  • 我使用的是下面这种比较简单的五脚旋转编码器:


    二、引脚配置

    我使用的旋转编码器有五个引脚,可能还会有其他不同类型的会有不同的引脚,
    这五个引脚分别为GND, VCC(+), SW, DT, CLK,具体的引脚已经在简介的图中标明了。

  • VCC:接电源正极 3.3~5V
  • GND:接地;
  • SW(Switch:开关)PA7(TIM3_CH2)
  • DT(DT:数据)PA6(TIM3_CH1)
  • CLK(CLK:时钟)PA5(任选一个引脚:配置为上拉输入模式)
  • 接线表:

    旋转编码器的接口 STM32的IO口 设置的工作模式
    VCC VCC(3.3V)
    GND GND
    SW(Switch:开关) PA7 定时器编码模式
    DT(DT:数据) PA6 定时器编码模式
    CLK(CLK:时钟) PA5 GPIO_Mode_IPU(上拉输入模式)
    PA9 TX(USART1 串口1通信)
    PA10 RX(USART1 串口1通信)

    三、新建工程

    1.打开STM32CubeMX软件,点击“新建工程”

    2. 选择 MCU 和封装

    3.配置时钟



    具体学习可以参考:博客网站-RCC学习

    4.配置调试模式

    5.串口(USART1)配置

    6.定时器(TIM)配置

  • 配置为编码器模式:
  • 7.生成代码

    输入项目名称和路径。(注:路径中不允许出现中文。)

    选择应用的IDE,开发环境MDK-ARM V5

    每个外设生成独立的 ’.c/.h’ 文件

  • 不勾: 所有初始化代码都生成在 main.c
  • 勾选: 初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

    点击 GENERATE CODE 生成代码
  • 8.构建工程

    DeBug的模式根据不同的芯片进行选择:


    四、编写代码

    main.c文件中,添加一下代码:

  • 重写fgetfput函数:勾选微库(这个很重要),添加头文件<stdio.h>
  • /* USER CODE BEGIN Includes */
    #include<stdio.h>
    /* USER CODE END Includes */
    
    /**
      * 函数功能: 重定向c库函数printf到DEBUG_USARTx
      * 输入参数: 无
      * 返 回 值: 无
      * 说    明:无
      */
    int fputc(int ch, FILE *f)
    {
      HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
      return ch;
    }
     
    /**
      * 函数功能: 重定向c库函数getchar,scanf到DEBUG_USARTx
      * 输入参数: 无
      * 返 回 值: 无
      * 说    明:无
      */
    int fgetc(FILE *f)
    {
      uint8_t ch = 0;
      HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
      return ch;
    }
    
    
  • 添加代码encoder文件对(.c/.h):
  • 首先要新建文件,然后在添加到工程中去;

  • 将新建的文件添加到工程中去;
  • 返回上一级,寻找自己新建的文件:


    此时,完成了添加encoder.c文件:encoder.c中添加如下代码,即可将encoder.h添加进工程
  • #include "encoder.h"
    
  • encoder.c中添加代码:
  • #include "encoder.h"
    
    uint8_t lock;    //旋钮锁死标志(1表示锁死)
    uint16_t count; //计数标志
    
    uint8_t ENCODER_READ(void)
    {
      uint8_t key = 0;       //存放按键的值
      uint8_t level;         //记录按钮另一端的电平值
      
      if(HAL_GPIO_ReadPin(encoder_port_A, encoder_right) && HAL_GPIO_ReadPin(encoder_port_A, encoder_left)) //读取此时
      {
        lock = 0;  //判断旋钮是否锁死
      }
      if(!HAL_GPIO_ReadPin(encoder_port_A, encoder_right) && lock == 0) //判断是否旋转按钮,同时判断是否有按钮锁死
      {
        HAL_Delay(100);
        
        level = HAL_GPIO_ReadPin(encoder_port_A,encoder_left);       //把旋钮另一端电平状态记录
        
        HAL_Delay(13);  //延时
        
        if(!HAL_GPIO_ReadPin(encoder_port_A, encoder_right))     //去抖
        {
          if(level == 0)
          {
            key = 1;  //右转
          }
          else
          {
            key = 2;  //左转
          }
          count = 0; //初始锁死判断计数器
          while(!HAL_GPIO_ReadPin(encoder_port_A, encoder_right) && count < 60000)  //等待放开旋钮,同时累加判断锁死
          {
            count++;
            lock = 1;
            HAL_Delay(200);
          }
        }
      }
      
      if(!HAL_GPIO_ReadPin(encoder_port_A,encoder_down) && lock == 0)
      {
        HAL_Delay(20);
        if(!HAL_GPIO_ReadPin(encoder_port_A,encoder_down))    //消抖
        {
          key=3;
        }
      }
      return key;
    }
    
  • encoder.h中添加代码:
  • #ifndef _ENCODER_H_
    #define _ENCODER_H_
    
    #include "main.h"
    #include "tim.h"
    
    
    #define encoder_port_A    GPIOA
    
    #define encoder_left      GPIO_PIN_6	//定义IO接口,DT, 旋钮左转
    #define encoder_right     GPIO_PIN_5	//定义IO接口,CLK,旋钮右转
    #define encoder_down      GPIO_PIN_7	//定义IO接口,SW, 旋钮按下
    
    uint8_t ENCODER_READ(void);     //接口读取值
    
    #endif
    
    
  • 在主函数main.c中添加代码:
  • /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
      int b;
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_TIM3_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    
      printf("encoder is ready:\r\n");
      
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
        b = ENCODER_READ(); //读出旋转器编码器值
        
        HAL_Delay(1000);  
        
        if(b==1)
        {
          printf("编码器右转\r\n");
        }
        if(b==2)
        {
          printf("编码器左转\r\n");
        }
        if(b==3)
        {
          printf("编码器按下\r\n");
        }
    
      }
      /* USER CODE END 3 */
    }
    
    

    五、实验结果

    谢谢阅读!!!

    物联沃分享整理
    物联沃-IOTWORD物联网 » 学习STM32CubeMX旋转编码器模块

    发表评论