电机编码器

目录

  • 前言
  • 一、编码器介绍
    • 1.1 编码器分类
    • 1.2 编码器基本参数
  • 二、编码器工作原理
    • 2.1 磁电增量式
    • 2.2 光电增量式
    • 2.3 光电绝对式
  • 三、编码器测速原理
    • 3.1 增量式编码器倍频技术
    • 3.2 常用测速方法简介
    • 3.3 STM32的编码器接口简介
      • 3.3.1 编码器接口框图
      • 3.3.2 编码器接口计数原理
    • 3.4 编码器接口相关结构体
  • 四、示例代码
    • 4.1 头文件相关宏定义
    • 4.2 定时器初始化
    • 4.3 中断处理
    • 4.4 编码器速度计算


前言

  本文主要介绍电机编码器的作用与使用


一、编码器介绍

1.1 编码器分类

  编码器的分类方式有很多,我们这里列举两种分类方式:按检测原理和编码类型
电机编码器_第1张图片
  在实际的应用中,这四类编码器并不是相对独立的,它们经过组合后,就变成了光电绝对式、光电增量式、磁电绝对式和磁电增量式这四种编码器。

  1. 增量式编码器
      增量式旋转编码器是将设备运动时的位移信息变成连续的脉冲信号,脉冲个数表示位移量的大小。只有当设备运动的时候增量式编码器才会输出信号。编码器一般会把这些信号分为通道 A和通道 B 两组输出,并且这两组信号间有 90° 的相位差。同时采集这两组信号就可以知道设备的运动和方向。除了通道 A、通道 B 以外,很多增量式编码器还会设置一个额外的通道 Z 输出信号,用来表示编码器特定的参考位置,传感器转一圈 Z 轴信号才会输出一个脉冲。增量式编码器只输出设备的位置变化和运动方向,不会输出设备的绝对位置。
  2. 绝对式编码器
      绝对式旋转编码器是将设备运动时的位移信息通过二进制编码的方式变成数字量直接输出。这种编码器与增量式编码器的区别主要在内部的码盘。绝对式编码器的码盘利用若干透光和不透光的线槽组成一套二进制编码,这些二进制码与编码器转轴的每一个不同角度是唯一对应的,读取这些二进制码就能知道设备的绝对位置,所以叫它绝对式编码器。绝对式编码器一般常用自然二进制、格雷码或者 BCD 码等编码方式。
  3. 混合式绝对式编码器
      混合式绝对式编码器,它输出两组信息:一组信息用于检测磁极位置,带有绝对信息功能;另一组则和增量式编码器的输出信息完全相同。

1.2 编码器基本参数

  • 分辨率:指编码器能够分辨的最小单位。对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转 (Pulse Per Revolution 或 PPR)。码盘上透光线槽的数目其实就等于分辨率,也叫多少线,较为常见的有 5-6000 线。对于绝对式编码器,内部码盘所用的位数就是它的分辨率,单位是位 (bit),具体还分单圈分辨率和多圈分辨率。
  • 精度:首先明确一点,精度与分辨率是两个不同的概念。精度是指编码器每个读数与转轴实际位置间的最大误差,通常用角度、角分或角秒来表示。例如有些绝对式编码器参数表里会写 ±20′′,这个就表示编码器输出的读数与转轴实际位置之间存在正负 20 角秒的误差,精度由码盘刻线加工精度、转轴同心度、材料的温度特性、电路的响应时间等各方面因素共同决定。
  • 最大响应频率:指编码器每秒输出的脉冲数,单位是 Hz,也称为 PPS。计算公式:最大响应频率 = 分辨率 * 轴转速/60。
  • 信号输出形式:对于增量式编码器,每个通道的信号独立输出,输出电路形式通常有集电极开路输出、推挽输出、差分输出等。对于绝对式编码器,由于是直接输出几十位的二进制数,为了确保传输速率和信号质量,一般采用串行输出或总线型输出,例如同步串行接口 (SSI)、RS485、CANopen 或 EtherCAT 等,也有一部分是并行输出,输出电路形式与增量式编码器相同。
  • 最大转速:指编码器机械系统所能承受的最高转速。

二、编码器工作原理

  这里主要介绍磁电增量式、光电增量式以及光电绝对式这 3 种常用编码器的工作原理

2.1 磁电增量式

原理:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。具体工作原理如图 所示
电机编码器_第2张图片
  磁电增量式编码器的结构包含:磁盘、霍尔传感器以及信号转换电路 3 个部分,其中,磁盘是由一个个交替排布的 S 极和 N 极磁极组成;霍尔传感器可以把磁场的变化转换成电信号的变化,它通常有 A、B 两相(有的还有 Z 相),这两相的安装位置形成一定的夹角,这使得输出的 A、B 两相信号有 90°的相位差;信号转换电路可以把电信号转换成脉冲信号。
  在实际应用中,磁盘会装在电机的转轴上,它会随着电机的转轴旋转,而磁盘上面的 S 极和 N 极就会交替地经过霍尔传感器的 A、B 两相,霍尔传感器就可以把磁盘上的磁场变化转换为电信号的变化,输入到信号转换电路中,经过信号的转换之后,我们就可以得到 A、B 两相脉冲信号了。从上图中可以看到,A、B 两相脉冲信号存在 90°的相位差,当电机正转时,A 相脉冲在前;当电机反转时,则是 B 相脉冲在前。

2.2 光电增量式

原理:利用光电系统,将位移转换成计数脉冲,用脉冲个数计算位移和速度。
电机编码器_第3张图片

  上节提到过,增量式编码器都有 A、B 两通道信号输出,这是因为增量式编码器的码盘上有两圈线槽,两圈线槽的之间会错开一定的角度,这个角度会使得光电检测装置输出的两相信号相差 1/4 周期 (90°)。码盘的具体工作方式如下图所示。图中黑色代表透光,白色代表遮光。当码盘转动时,内圈和外圈的线槽会依次透过光线,光电检测装置检测到光线通断的变化,就会相应的输出脉冲信号,因为内外圈遮光和透光时候存在时间差,所以也就有了 A、B 两通道信号的相位差。
电机编码器_第4张图片
电机编码器_第5张图片
电机编码器_第6张图片
  根据两相信号变化的先后顺序就可以判断运动方向,记录输出的脉冲个数可以知道位移量的大小,同时通过输出信号的频率就能得到速度。
  一些增量式编码器上会有 4 圈线槽,分别对应 A、B、-A、-B 四相信号,相邻两相信号间也是差1/4 周期,只不过这种编码器会把-A 和-B 两相信号反相,然后叠加到 A、通道 B,用来增强信号。除了通道 A、通道 B 以外,很多增量式编码器还会设置一个额外的通道 Z 输出信号。通道 Z 信号也在码盘上有对应的线槽,不过只有一条,码盘转一圈才会经过一次。通道 Z 信号一般用做参考零位,指示设备位置或者清除积累量。
  增量式编码器计数起点任意设定,可实现多圈无限累加和测量。需要提高分辨率时,可触发 A、B 两通道信号的上升沿和下降沿对原脉冲数进行倍频。但是当接收设备停机重启后,增量式编码器需要重新寻找参考零点。
  另一种较为常用的增量式编码器是霍尔编码器。霍尔增量式编码器在结构上和光电式几乎相同,只不过检测原理变成了霍尔效应。内部元件也稍有不同,霍尔编码器的码盘上不是线槽,而是不同的磁极,或者有些直接把电机的旋转磁场当作码盘,然后检测装置换成了霍尔传感器。输出和光电式相同,仍然是相位差 1/4 周期的 A、B 两通道信号。

2.3 光电绝对式

原理:当码盘处于不同位置(角度)时,光敏元件根据受光与否转换出相应的电平信号,最后转换成二进制数输出。

  绝对式编码器在总体结构上与增量式比较类似,都是由码盘、检测装置和放大整形电路构成,但是具体的码盘结构和输出信号含义不同。绝对式编码器的码盘上有很多圈线槽,被称为码道,每一条码道内部线槽数量和长度都不同。它们共同组成一套二进制编码,一条码道对应二进制数的其中一个位,通常是码盘最外侧的码道表示最低位,最内侧的码道表示最高位。码道的数量决定了二进制编码的位数,一个绝对式编码器有 N 条码道,它就能输出 N 位二进制数,且输出二进制数的总个数是 2^N 个。这些二进制数与转轴的机械位置是固定的,和编码器外部因素无关,所以叫做绝对式编码器。在接收设备断电重启后绝对式编码器无需寻找参考零点。

  下图是一个简化版的绝对式编码器码盘,其中白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。图中码盘有 3 条码道,一共可表示 2^3=8 个二进制数,所以整个码盘被分成了 8 个扇区,每个扇区表示一个 3 位二进制数,每个二进制数对应一个转轴的位置信息。码盘采用自然二进制编码,自然二进制编码的优点是很方便直观,但是受编码器制造和安装精度的影响,实际应用中二进制数的每一位不可能同时改变,或者出现码盘停在两个扇区中间,这些情况都很容易造成读数错误。
电机编码器_第7张图片
  为了避免出现读数错误,可以使用格雷码来解决。下图是一个使用格雷码的码盘,同样的,白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。图中码盘的码道数与上面的自然二进制码盘完全一致,也能表示 8 个 3 位二进制数,只不过将编码方式换成了格雷码。利用任意相邻的二进制格雷码数都只有一位不同的特性,采用这种编码的码盘在一定程度上克服了自然二进制码盘容易产生读数错误的问题。
电机编码器_第8张图片
  绝对式编码器还分为单圈绝对式编码器和多圈绝对式编码器,上面举的两个例子都是针对单圈也就是 360° 以内的情况,当码盘转动超过 360°,输出的编码会重复,这样不符合绝对式编码器数据唯一的要求,所以就出现了多圈绝对式编码器。多圈绝对式编码器的量程可以超过 360°,并且通常超出很多,其内部结构也比单圈的复杂,但是基本原理都是一样的。

三、编码器测速原理

3.1 增量式编码器倍频技术

增量式编码器输出的脉冲波形信号形式常见的有两种:
①一种是占空比 50% 的方波,通道 A 和 B 相位差为 90°;
②另一种则是正弦波这类模拟信号,通道 A 和 B 相位差同样为 90°。

  对于第 1 种形式的方波信号,如果把两个通道组合起来看的话,可以发现 A 和 B 各自的上升沿和下降沿都能计数,至少在 1/2 个原始方波周期内就可以计数一次,最多 1/4 个原始方波周期。这样计数频率就是原始方波信号的 2 倍或 4 倍,换句话说就是,将编码器的分辨率提高了 2 到 4倍,具体如下图所示:
电机编码器_第9张图片
  图中的方波信号如果只看其中一个通道的上升沿,那计数频率就等于这个通道信号的频率。如果在通道 A 的上升沿和下降沿都进行计数,计数频率就是通道 A 的两倍,即 2 倍频。如果同时对两个通道的上升沿和下降沿都计数,那计数频率就变成了原始信号的 4 倍,即 4 倍频。
  假设有个增量式编码器它的分辨率是 600PPR,能分辨的最小角度是 0.6°,对它进行 4 倍频之后就相当于把分辨率提高到了 600*4=2400PPR,此时编码器能够分辨的最小角度为 0.15°。编码器倍频技术还可用来扩展一些测速方法的速度适用范围。例如电机测速通常使用 M 法进行测量(M法在下节介绍),编码器 4 倍频后可以扩展 M 法的速度下限。

3.2 常用测速方法简介

  上一节提到了增量式编码器倍频技术可以扩展 M 法的测量范围,那么现在我们就来讲解下这个M 法究竟是怎样测速的,以及简单介绍一些常用的编码器测速方法。对于电机转速的测量,可以把增量式编码器安装到电机上,用控制器对编码器脉冲进行计数,然后通过特定的方法求出电机转速,常用的编码器测速方法一般有三种:M 法、T 法和 M/T 法。

  1. M 法:又叫做频率测量法
      这种方法是在一个固定的定时时间内(以秒为单位),统计这段时间的编码器脉冲数,计算速度值。设编码器单圈总脉冲数为 C,在时间 T0 内,统计到的编码器脉冲数为 M0,则转速 n 的计算公式为:
    电机编码器_第10张图片
      公式中的编码器单圈总脉冲数 C 是常数,所以转速 n 跟 M0 成正比。这就使得在高速测量时 M0变大,可以获得较好的测量精度和平稳性,但是如果速度很低,低到每个 T0 内只有少数几个脉冲,此时算出的速度误差就会比较大,并且很不稳定。也有一些方法可以改善 M 法在低速测量的准确性,上一节提到的增量式编码器倍频技术就是其中一种,比如原本捕获到的脉冲 M0 只有4 个,经过 4 倍频后,相同电机状态 M0 变成了 16 个,也就提升了低速下的测量精度

  2. T 法:又叫做周期测量法。
      这种方法是建立一个已知频率的高频脉冲并对其计数,计数时间由捕获到的编码器相邻两个脉冲的间隔时间 TE 决定,计数值为 M1。设编码器单圈总脉冲数为 C,高频脉冲的频率为 F0,则转速 n 的计算公式为:
    电机编码器_第11张图片
      公式中的编码器单圈总脉冲数 C 和高频脉冲频率 F0 是常数,所以转速 n 跟 M1 成反比。从公式可以看出,在电机高转速的时候,编码器脉冲间隔时间 TE 很小,使得测量周期内的高频脉冲计数值 M1 也变得很少,导致测量误差变大,而在低转速时,TE 足够大,测量周期内的 M1 也足够多,所以 T 法和 M 法刚好相反,更适合测量低速。

  3. M/T 法
      这种方法综合了 M 法和 T 法各自的优势,既测量编码器脉冲数又测量一定时间内的高频脉冲数。在一个相对固定的时间内,计数编码器脉冲数 M0,并计数一个已知频率为F0 的高频脉冲,计数值为 M1,计算速度值。设编码器单圈总脉冲数为 C,则转速 n 的计算公式为:
    电机编码器_第12张图片
      由于 M/T 法公式中的 F0 和 C 是常数,所以转速 n 就只受 M0 和 M1 的影响。电机高速时,M0 增大,M1 减小,相当于 M 法,低速时,M1 增大,M0 减小,相当于 T 法。

3.3 STM32的编码器接口简介

  本节以磁电增量式编码器为例

3.3.1 编码器接口框图

  直流有刷电机的编码器有 A、B 两相,它们会输出两个相位差为 90°的脉冲。当电机正转时,A 相脉冲在前;当电机反转时,则是 B 相脉冲在前。STM32 芯片内部有专门用来采集增量式编码器方波信号的接口,这些接口实际上是 STM32 定时器的其中一种功能。不过编码器接口功能只有高级定时器 TIM1、TIM8 和通用定时器 TIM2 到TIM5 才有。
  STM32 定时器的编码器接口模式就相当于带有方向选择的外部时钟,也就是说,在此模式下,外部输入的脉冲信号可以作为计数器的时钟,而计数的方向则是由脉冲相位的先后所决定的。定时器编码器接口模式的原理如图所示:
电机编码器_第13张图片
  我们重点关注编码器接口是如何实现信号采集和倍频的。我们首先看编码器的接口框图部分,了解一下脉冲信号进入编码器接口的途径,这里以通用定时器为例
电机编码器_第14张图片

3.3.2 编码器接口计数原理

  编码器接口用到了定时器的输入捕获部分,《STM32F4xx 参考手册》给出了的编码器信号与计数器方向和计数位置之间的关系,如下表所示:
电机编码器_第15张图片

  首先需要解释一下,表中的 TI1 和 TI2 对应编码器的通道 A 和通道 B,而 TI1FP1 和 TI2FP2 则对应反相以后的 TI1、TI2。STM32 的编码器接口在计数的时候,并不是单纯采集某一通道信号的上升沿或下降沿,而是需要综合另一个通道信号的电平。表中“相反信号的电平”指的就是在计数的时候所参考的另一个通道信号的电平,这些电平决定了计数器的计数方向。

  为了便于大家理解 STM32 编码器接口的计数原理,我们将表中的信息提出转换成一系列图像。首先看下图,下图所展示的信息对应表格中“仅在 TI1 处计数”。图中包含 TI1、TI2 两通道的信号,以及计数器的计数方向,其中 TI1 比 TI2 提前 1/4 个周期,以 TI1 的信号边沿作为有效边沿。当检测到 TI1 的上升沿时,TI2 为低电平,此时计数器向上计数 1 次,下一时刻检测到 TI1 的下降沿时,TI2 为高电平,此时计数器仍然向上计数一次,以此类推。这样就能把 TI1 的上升沿和下降沿都用来计数,即实现了对原始信号的2倍频。
电机编码器_第16张图片
  接下来看如下图像,图中同样包含 TI1、TI2 两通道的信号,以及计数器的计数方向,其中 TI1 比TI2 滞后 1/4 个周期,以 TI1 的信号边沿作为有效边沿。当检测到 TI1 的上升沿时,TI2 为高电平,此时计数器向下计数 1 次,下一时刻检测到 TI1 的下降沿时,TI2 为低电平,此时计数器仍然向下计数一次,以此类推。这样同样是把 TI1 的上升沿和下降沿都用来计数,同样实现了对原始信号的 2 倍频,只不过变成向下计数了。
电机编码器_第17张图片
  最后如下图所示,下图所展示的信息对应表格中“在 TI1 和 TI2 处均计数”。这种采样方式可以把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考,相当于原来仅在一个通道处计数的 2 倍,所以这种就能实现对原始信号的4倍频:
电机编码器_第18张图片
  下面我们以一个实例来理解这个表格的内容。假设我们把 A 相接在 CH1(TI1),B 相接在 CH2(TI2),选择仅在 TI1 处计数(仅检测A 相边沿)。此时编码器接口计数方向和输入脉冲信号的关系如下表:
电机编码器_第19张图片
  编码器输出的 A、B 两相脉冲信号如下图所示,图中,A、B 两相输出的脉冲信号有两种情况:当编码器正转,A 相在前;当编码器反转,B 相在前,我们选择仅在 TI1 处计数,也就是只检测 A 相的边沿。
电机编码器_第20张图片
  以正转为例,当 A 相上升沿到来时(图中①处),我们需要关注 B 相的电平高低,从图中可看到 B 相此时是低电平,结合表可以得知此时计数方向为递增计数;当 A 相下降沿到来时(图中②处),从图中可以看到 B 相此时是高电平,结合表可以得知此时计数方向为递增计数;当 A 相上升沿再次到来时(图中③处),同理可得此时计数方向为递增计数。综上所得,我们可以知道此时编码器正转对应的计数方向就是递增计数。反转同理

注意:选择仅在 TI1 或者 TI2 处计数,就相当于对脉冲信号进行了 2 倍频(两个边沿),此时如果编码器输出 10 个脉冲信号,那么就会计数 20 次。选择的是在 TI1 和 TI2 处均计数,就相当于对脉冲信号进行了 4 倍频,此时如果编码器输出 10 个脉冲信号,那么就会计数40 次。因此,我们通过计数次数来计算电机速度的时候,需要除以相应的倍频系数。

  至此,A、B 两相脉冲信号的变化就转换成了定时器的计数变化。接下来我们就可以通过一分钟内计数的变化量来计算电机的速度,具体公式如下:

电机转速 = 一分钟内计数变化量 / 倍频系数 / 编码器线数 / 减速比

3.4 编码器接口相关结构体

  HAL 库函数对定时器外设建立了多个初始化结构体,其中编码器接口用到的有时基初始化结构体 TIM_Base_InitTypeDef ,和编码器初始化配置结构体 TIM_Encoder_InitTypeDef 。初始化结构体成员用于设置定时器工作环境参数,并由定时器相应初始化配置函数调用,最终这些参数将会写入到定时器相应的寄存器中。

  1. TIM_Base_InitTypeDef
      时基结构体 TIM_Base_InitTypeDef 用于定时器基础参数设置,与 HAL_TIM_Base_Init 函数配合使用完成配置。
typedef struct
{
	uint32_t Prescaler; 	//定时器预分频器设置
	uint32_t CounterMode; 	//计数模式
	uint32_t Period; 		//定时器周期
	uint32_t ClockDivision; //时钟分频
	uint32_t RepetitionCounter; //重复计算器
	uint32_t AutoReloadPreload; //自动重载预装载值
}TIM_Base_InitTypeDef;
  1. TIM_Encoder_InitTypeDef
      编码器初始化配置结构体 TIM_Encoder_InitTypeDef 用于定时器的编码器接口模式,与HAL_TIM_Encoder_Init 函数配合使用完成初始化配置操作。高级定时器 TIM1 和 TIM8 以及通用定时器 TIM2 到 TIM5 都带有编码器接口,使用时都必须单独设置
typedef struct
{
	uint32_t EncoderMode; 	//编码器模式
	uint32_t IC1Polarity; 	//输入信号极性
	uint32_t IC1Selection; 	//输入通道
	uint32_t IC1Prescaler; 	//输入捕获预分频器
	uint32_t IC1Filter; 	//输入捕获滤波器
	uint32_t IC2Polarity; 	//输入信号极性
	uint32_t IC2Selection; 	//输入通道
	uint32_t IC2Prescaler; 	//输入捕获预分频器
	uint32_t IC2Filter; 	//输入捕获滤波器
}TIM_Encoder_InitTypeDef;
  • EncoderMode:编码器模式选择,用来设置计数器采集编码器信号的方式,可选通道 A 计数、通道 B 计数和双通道计数。它设定 TIMx_SMCR 寄存器的 SMS[2:0] 位。这个成员实际是用来设置编码器接口的倍频数的,当选择通道 A 或 B 计数时为 2 倍频,双通道计数时为4 倍频。
  • ICxPolarity:输入捕获信号极性选择,用于设置定时器通道在编码器模式下的输入信号是否反相。它设定 TIMx_CCER 寄存器的 CCxNP 位和 CCxP 位。
  • ICxSelection: 输 入 通 道 选 择,ICx 的 信 号 可 来 自 三 个 输 入 通 道, 分 别为 TIM_ICSELECTION_DIRECTTI、TIM_ICSELECTION_INDIRECTTI 或IM_ICSELECTION_TRC。它设定 TIMx_CCMRx 寄存器的 CCxS[1:0] 位的值。定时器在编码器接口模式下,此成员只能设置为 TIM_ICSELECTION_DIRECTTI。
  • ICxPrescaler:输入捕获通道预分频器,可设置 1、2、4、8 分频。它设定 TIMx_CCMRx 寄存器的 ICxPSC[1:0] 位的值。
  • ICxFilter:输入捕获滤波器设置,可选设置 0x0 至 0x0F。它设定 TIMx_CCMRx 寄存器ICxF[3:0] 位的值。

四、示例代码

  这里以STM32f4的HAL库为例,标准库同理

4.1 头文件相关宏定义

  这里用TIM3进行编码器接口输入,TIM6当作基本定时用来固定时间计算一次速度

#define ROTO_RATIO      44  /* 线数*倍频系数,即11*4=44 */
#define REDUCTION_RATIO 30  /* 减速比30:1 */

/* 通用定时器 定义  */
#define GTIM_TIMX_ENCODER_CH1_GPIO_PORT         GPIOC
#define GTIM_TIMX_ENCODER_CH1_GPIO_PIN          GPIO_PIN_6
#define GTIM_TIMX_ENCODER_CH1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)  /* PC口时钟使能 */

#define GTIM_TIMX_ENCODER_CH2_GPIO_PORT         GPIOC
#define GTIM_TIMX_ENCODER_CH2_GPIO_PIN          GPIO_PIN_7
#define GTIM_TIMX_ENCODER_CH2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)  /* PC口时钟使能 */

/* TIMX 引脚复用设置
 * 因为PC6/PC7, 默认并不是TIM3的功能脚, 必须开启复用, 才可以用作TIM3的CH1/CH2功能
 */
#define GTIM_TIMX_ENCODERCH1_GPIO_AF            GPIO_AF2_TIM3                                /* 端口复用到TIM3 */
#define GTIM_TIMX_ENCODERCH2_GPIO_AF            GPIO_AF2_TIM3                                /* 端口复用到TIM3 */

#define GTIM_TIMX_ENCODER                       TIM3                                         /* TIM3 */
#define GTIM_TIMX_ENCODER_INT_IRQn              TIM3_IRQn
#define GTIM_TIMX_ENCODER_INT_IRQHandler        TIM3_IRQHandler

#define GTIM_TIMX_ENCODER_CH1                   TIM_CHANNEL_1                                /* 通道1 */
#define GTIM_TIMX_ENCODER_CH1_CLK_ENABLE()      do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0)   /* TIM3 时钟使能 */

#define GTIM_TIMX_ENCODER_CH2                   TIM_CHANNEL_2                                /* 通道2 */
#define GTIM_TIMX_ENCODER_CH2_CLK_ENABLE()      do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0)   /* TIM3 时钟使能 */


/* 基本定时器 定义 
 * 捕获编码器值,用于计算速度
 */
#define BTIM_TIMX_INT                           TIM6
#define BTIM_TIMX_INT_IRQn                      TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler                TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE()              do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0)    /* TIM6 时钟使能 */

/* 编码器参数结构体 */
typedef struct 
{
  int encode_old;                  /* 上一次计数值 */
  int encode_now;                  /* 当前计数值 */
  float speed;                     /* 编码器速度 */
} ENCODE_TypeDef;

extern ENCODE_TypeDef g_encode;    /* 编码器参数变量 */

4.2 定时器初始化

  1. 通用定时器TIM3
TIM_HandleTypeDef g_timx_encode_chy_handle;         /* 定时器x句柄 */
TIM_Encoder_InitTypeDef g_timx_encoder_chy_handle;  /* 定时器编码器句柄 */

/**
 * @brief       通用定时器TIMX 通道Y 编码器接口模式 初始化函数
 * @note
 *              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void gtim_timx_encoder_chy_init(uint16_t arr, uint16_t psc)
{
    /* 定时器x配置 */
    g_timx_encode_chy_handle.Instance = GTIM_TIMX_ENCODER;                      /* 定时器x */
    g_timx_encode_chy_handle.Init.Prescaler = psc;                              /* 定时器分频 */
    g_timx_encode_chy_handle.Init.Period = arr;                                 /* 自动重装载值 */
    g_timx_encode_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;       /* 时钟分频因子 */
    
    /* 定时器x编码器配置 */
    g_timx_encoder_chy_handle.EncoderMode = TIM_ENCODERMODE_TI12;               /* TI1、TI2都检测,4倍频 */
    g_timx_encoder_chy_handle.IC1Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_timx_encoder_chy_handle.IC1Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_timx_encoder_chy_handle.IC1Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_timx_encoder_chy_handle.IC1Filter = 10;                                   /* 滤波器设置 */
    g_timx_encoder_chy_handle.IC2Polarity = TIM_ICPOLARITY_RISING;              /* 输入极性,非反向 */
    g_timx_encoder_chy_handle.IC2Selection = TIM_ICSELECTION_DIRECTTI;          /* 输入通道选择 */
    g_timx_encoder_chy_handle.IC2Prescaler = TIM_ICPSC_DIV1;                    /* 不分频 */
    g_timx_encoder_chy_handle.IC2Filter = 10;                                   /* 滤波器设置 */
    HAL_TIM_Encoder_Init(&g_timx_encode_chy_handle, &g_timx_encoder_chy_handle);/* 初始化定时器x编码器 */
     
    HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH1);     /* 使能编码器通道1 */
    HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle,GTIM_TIMX_ENCODER_CH2);     /* 使能编码器通道2 */
    __HAL_TIM_ENABLE_IT(&g_timx_encode_chy_handle,TIM_IT_UPDATE);               /* 使能更新中断 */
    __HAL_TIM_CLEAR_FLAG(&g_timx_encode_chy_handle,TIM_IT_UPDATE);              /* 清除更新中断标志位 */
    
}

/**
 * @brief       定时器底层驱动,时钟使能,引脚配置
                此函数会被HAL_TIM_Encoder_Init()调用
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIMX_ENCODER)
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIMX_ENCODER_CH1_GPIO_CLK_ENABLE();                                 /* 开启通道y的GPIO时钟 */
        GTIM_TIMX_ENCODER_CH2_GPIO_CLK_ENABLE();
        GTIM_TIMX_ENCODER_CH1_CLK_ENABLE();                                      /* 开启定时器时钟 */
        GTIM_TIMX_ENCODER_CH2_CLK_ENABLE();

        gpio_init_struct.Pin = GTIM_TIMX_ENCODER_CH1_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                                 /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        gpio_init_struct.Alternate = GTIM_TIMX_ENCODERCH1_GPIO_AF;               /* 端口复用 */
        HAL_GPIO_Init(GTIM_TIMX_ENCODER_CH1_GPIO_PORT, &gpio_init_struct);  
        
        gpio_init_struct.Pin = GTIM_TIMX_ENCODER_CH2_GPIO_PIN;                   /* 通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                                 /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_NOPULL;                                     /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                           /* 高速 */
        gpio_init_struct.Alternate = GTIM_TIMX_ENCODERCH2_GPIO_AF;               /* 端口复用 */
        HAL_GPIO_Init(GTIM_TIMX_ENCODER_CH2_GPIO_PORT, &gpio_init_struct);         
       
        HAL_NVIC_SetPriority(GTIM_TIMX_ENCODER_INT_IRQn, 2, 0);                  /* 中断优先级设置 */
        HAL_NVIC_EnableIRQ(GTIM_TIMX_ENCODER_INT_IRQn);                          /* 开启中断 */
    }
}

/**
 * @brief       定时器中断服务函数
 * @param       无
 * @retval      无
 */
void GTIM_TIMX_ENCODER_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_encode_chy_handle);
}
  1. 基本定时器TIM6
TIM_HandleTypeDef timx_handler;         /* 定时器参数句柄 */

/**
 * @brief       基本定时器TIMX定时中断初始化函数
 * @note
 *              基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 时钟预分频数
 * @retval      无
 */
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
    timx_handler.Instance = BTIM_TIMX_INT;                              /* 基本定时器X */
    timx_handler.Init.Prescaler = psc;                                  /* 设置预分频器  */
    timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP;                 /* 向上计数器 */
    timx_handler.Init.Period = arr;                                     /* 自动装载值 */
    timx_handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;           /* 时钟分频因子 */
    HAL_TIM_Base_Init(&timx_handler);
    
    HAL_TIM_Base_Start_IT(&timx_handler);                               /* 使能基本定时器x和及其更新中断:TIM_IT_UPDATE */
    __HAL_TIM_CLEAR_IT(&timx_handler,TIM_IT_UPDATE);                    /* 清除更新中断标志位 */
}

/**
 * @brief       定时器底册驱动,开启时钟,设置中断优先级
                此函数会被HAL_TIM_Base_Init()函数调用
 * @param       无
 * @retval      无
 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == BTIM_TIMX_INT)
    {
        BTIM_TIMX_INT_CLK_ENABLE();                                     /*使能TIM时钟*/
        HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3);                 /* 抢占1,子优先级3,组2 */
        HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn);                         /*开启ITM3中断*/
    }
}

/**
 * @brief       基本定时器TIMX中断服务函数
 * @param       无
 * @retval      无
 */
void BTIM_TIMX_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&timx_handler);                                  /*定时器回调函数*/
}

4.3 中断处理

volatile int g_timx_encode_count = 0;                                   /* 溢出次数 */

/**
 * @brief       获取编码器的值
 * @param       无
 * @retval      编码器值
 */
int gtim_get_encode(void)
{
    return ( int32_t )__HAL_TIM_GET_COUNTER(&g_timx_encode_chy_handle) + g_timx_encode_count * 65536;       /* 当前计数值+之前累计编码器的值=总的编码器值 */
}

/**
 * @brief       定时器更新中断回调函数
 * @param        htim:定时器句柄指针
 * @note        此函数会被定时器中断函数共同调用的
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3)
    {
        if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_timx_encode_chy_handle))   /* 判断CR1的DIR位 */
        {
            g_timx_encode_count--;                                      /* DIR位为1,也就是递减计数 */
        }
        else
        {
            g_timx_encode_count++;                                      /* DIR位为0,也就是递增计数 */
        }
    }
    else if (htim->Instance == TIM6)
    {
        int Encode_now = gtim_get_encode();                             /* 获取编码器值,用于计算速度 */

        speed_computer(Encode_now, 50);                                 /* 中位平均值滤除编码器抖动数据,50ms计算一次速度*/
    }
}

4.4 编码器速度计算

ENCODE_TypeDef g_encode;     /*编码器参数变量*/

/**
 * @brief       电机速度计算
 * @param       encode_now:当前编码器总的计数值
 *              ms:计算速度的间隔,中断1ms进入一次,例如ms = 5即5ms计算一次速度
 * @retval      无
 */
void speed_computer(int32_t encode_now, uint8_t ms)
{
    uint8_t i = 0, j = 0;
    float temp = 0.0;
    static uint8_t sp_count = 0, k = 0;
    static float speed_arr[10] = {0.0};                     /* 存储速度进行滤波运算 */

    if (sp_count == ms)                                     /* 计算一次速度 */
    {
        /* 计算电机转速 
           第一步 :计算ms毫秒内计数变化量
           第二步 ;计算1min内计数变化量:g_encode.speed * ((1000 / ms) * 60 ,
           第三步 :除以编码器旋转一圈的计数次数(倍频倍数 * 编码器分辨率)
           第四步 :除以减速比即可得出电机转速
        */
        g_encode.encode_now = encode_now;                                /* 取出编码器当前计数值 */
        g_encode.speed = (g_encode.encode_now - g_encode.encode_old);    /* 计算编码器计数值的变化量 */
        
        speed_arr[k++] = (float)(g_encode.speed * ((1000 / ms) * 60.0) / REDUCTION_RATIO / ROTO_RATIO );    /* 保存电机转速 */
        
        g_encode.encode_old = g_encode.encode_now;          /* 保存当前编码器的值 */

        /* 累计10次速度值,后续进行滤波*/
        if (k == 10)
        {
            for (i = 10; i >= 1; i--)                       /* 冒泡排序*/
            {
                for (j = 0; j < (i - 1); j++) 
                {
                    if (speed_arr[j] > speed_arr[j + 1])    /* 数值比较 */
                    { 
                        temp = speed_arr[j];                /* 数值换位 */
                        speed_arr[j] = speed_arr[j + 1];
                        speed_arr[j + 1] = temp;
                    }
                }
            }
            
            temp = 0.0;
            
            for (i = 2; i < 8; i++)                         /* 去除两边高低数据 */
            {
                temp += speed_arr[i];                       /* 将中间数值累加 */
            }
            
            temp = (float)(temp / 6);                       /*求速度平均值*/
            
            /* 一阶低通滤波
             * 公式为:Y(n)= qX(n) + (1-q)Y(n-1)
             * 其中X(n)为本次采样值;Y(n-1)为上次滤波输出值;Y(n)为本次滤波输出值,q为滤波系数
             * q值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢
             */
            //g_motor_data.speed = (float)( ((float)0.48 * temp) + (g_motor_data.speed * (float)0.52) );
            k = 0;
        }
        sp_count = 0;
    }
    sp_count ++;
}

你可能感兴趣的:(单片机,单片机)