编码器

1、 编码器介绍
  编码器,是一种用来测量机械旋转或位移的传感器。这种传感器能够测量机械部件在旋转或直线运动时的位移位置或速度等信息,并将其转换成一系列电信号。并反馈给控制器,使控制器知道当前机械运动的位置、角度等信息。

  编码器(encoder)是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。

  按照读出方式编码器可以分为接触式和非接触式两种;根据检测原理,可分为光学式、磁式、感应式和电容式;根据内部机械结构的运动方式,可分为线性编码器和旋转编码器;根据其刻度实现方法及信号输出形式,又可分为增量式、绝对式以及混合式三种

  增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。绝对式编码器的每一个位置对应一个确定的数字码,因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。

  这些种类的编码器并不是相对独立的,它们经过组合后,就变成了常见的有光电绝对式、光电增量式、磁电绝对式和磁电增量式这四种编码器。
  编码器种类繁多,这里主要讲解旋转编码器,如下图所示,外形很像一个电机。

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

  混合式绝对式编码器,它输出两组信息:一组信息用于检测磁极位置,带有绝对信息功能;另一组则和增量式编码器的输出信息完全相同。
2、旋转编码器原理
  旋转编码器的原理示意图如下图所示。旋转编码器内部大都由码盘、光电检测装置和信号处理电路等部分构成。码盘上刻了若干圈线槽,线槽等距并且可透光,当码盘旋转时就会周期性的透过和遮挡来自光电检测装置的光线,这样检测装置就会周期性的生成若干电信号。但是这些电信号通常比较微弱,需要加入一套处理电路对信号进行放大和整形,最后把信号整形为脉冲信号并向外输出。

   增量型编码器工作原理:https://weibo.com/tv/show/1034:4828823414571072

  https://www.bilibili.com/video/BV1rS4y1P71w?spm_id_from=333.788.recommend_more_video.0&vd_source=808f356eadbc3a9f8a25854d28839790

  https://www.bilibili.com/video/BV1yi4y1K7w8?spm_id_from=333.788.recommend_more_video.1&vd_source=808f356eadbc3a9f8a25854d28839790

  虽然旋转编码器的原理在总体上差不多,但是对于这些原理的具体实现方法却有很大不同。
  2.1、增量式编码器原理

  首先来看增量式编码器。增量式编码器都有 A、 B 两通道信号输出,这是因为增量式编码器的码盘上有两圈线槽,两圈线槽的之间会错开一定的角度,这个角度会使得光电检测装置输出的两相信号相差 1/4 周期 (90°)。码盘的具体工作方式如下图所示。图中黑色代表透光,白色代表遮光。当码盘转动时,内圈和外圈的线槽会依次透过光线,光电检测装置检测到光线通断的变化,就会相应的输出脉冲信号,因为内外圈遮光和透光时候存在时间差,所以也就有了 A、B 两通道信号的相位差。

         7-3 增量式编码器码盘运作方式 1

         7-4 增量式编码器码盘运作方式 2

       7-5 增量式编码器码盘运作方式 3

  根据两相信号变化的先后顺序就可以判断运动方向,记录输出的脉冲个数可以知道位移量的大小,同时通过输出信号的频率就能得到速度。
  一些增量式编码器上会有 4 圈线槽,分别对应 A、 B、 -A、 -B 四相信号,相邻两相信号间也是差1/4 周期,只不过这种编码器会把-A 和-B 两相信号反相,然后叠加到 A、通道 B,用来增强信号。除了通道 A、通道 B 以外,很多增量式编码器还会设置一个额外的通道 Z 输出信号。通道 Z 信号也在码盘上有对应的线槽,不过只有一条,码盘转一圈才会经过一次。通道 Z 信号一般用做参考零位,指示设备位置或者清除积累量。

  增量式编码器计数起点任意设定,可实现多圈无限累加和测量。需要提高分辨率时,可触发 A、B 两通道信号的上升沿和下降沿对原脉冲数进行倍频。但是当接收设备停机重启后,增量式编码器需要重新寻找参考零点。 

  增量式编码器的码盘由交替的透明(明)和不透明(暗)刻线组成,物理刻线总数通常是标称“线数”的2倍。
  例如:标称“1000线”的编码器,码盘实际有 2000条物理刻线(1000明 + 1000暗)。

  编码器线数行业标准定义:
    标称的“线数”(如1000线)指每转输出的完整周期信号数(即A相或B相的完整方波周期数),而非物理刻线总数。
    每个完整周期对应码盘的1明 + 1暗刻线,因此:标称线数(PPR)= 物理刻度总线/2

  信号生成原理:
    编码器的光电传感器(如红外对管)在码盘旋转时,每经过1明 + 1暗刻线会输出一个完整的电信号周期(A相或B相的上升沿+下降沿)。
    因此:1000个明暗刻线对(共2000条刻线)→ 输出1000个完整脉冲周期(PPR=1000)。

  (1)磁电增量式
  一种较为常用的增量式编码器是霍尔编码器。霍尔增量式编码器在结构上和光电式几乎相同,只不过检测原理变成了霍尔效应。内部元件也稍有不同,霍尔编码器的码盘上不是线槽,而是不同的磁极,或者有些直接把电机的旋转磁场当作码盘,然后检测装置换成了霍尔传感器。输出和光电式相同,仍然是相位差 1/4 周期的 A、 B 两通道信号。

  原理:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。
  磁电增量式编码器的具体工作原理如图 7.1.3 所示:

  图 7.1.3 中,磁电增量式编码器的结构包含:磁盘、霍尔传感器以及信号转换电路 3 个部分,其中,磁盘是由一个个交替排布的 S 极和 N 极磁极组成;霍尔传感器可以把磁场的变化转换成电信号的变化,它通常有 A、 B 两相(有的还有 Z 相),这两相的安装位置形成一定的夹角,这使得输出的 A、 B 两相信号有 90°的相位差;信号转换电路可以把电信号转换成脉冲信号。
  在实际应用中,磁盘会装在电机的转轴上,它会随着电机的转轴旋转,而磁盘上面的 S 极和 N 极就会交替地经过霍尔传感器的 A、 B 两相,霍尔传感器就可以把磁盘上的磁场变化转换为电信号的变化,输入到信号转换电路中,经过信号的转换之后,我们就可以得到 A、 B 两相脉冲信号了。从上图中可以看到, A、 B 两相脉冲信号存在 90°的相位差,而磁盘的正反转方向就决定了是 A 相信号在前还是 B 相在前。

  (2)光电增量式
  原理:利用光电系统,将位移转换成计数脉冲,用脉冲个数计算位移和速度。
  光电增量式编码器的具体工作原理如图 7.1.4 所示:

  图 7.1.4 中,光电增量式编码器的结构包含:光电码盘、光源、透镜、受光元件以及信号转换电路 5 个部分,其中,光电码盘上有一个个均匀排布的透光孔;光源和透镜形成一个聚光系统;受光元件可以把光线的变化转换成电信号的变化,它通常有 A、 B 两相(有的还有 Z相),这两相的安装位置形成一定的夹角,这使得输出的 A、 B 两相信号有 90°的相位差;信号转换电路可以把电信号转换成脉冲信号。
  在实际应用中,光电码盘会装在电机的转轴上,它会随着电机的转轴旋转,而码盘上面的透光孔会间歇性地经过 A、 B 两相,受光元件就可以把光线变化转换为电信号的变化,输入到信号转换电路中,经过信号的转换之后,我们就可以得到 A、 B 两相脉冲信号了。从上图中可以看到, A、 B 两相脉冲信号存在 90°的相位差,而码盘的正反转方向就决定了是 A 相信号在前还是 B 相在前。

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

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

  下图是一个简化版的绝对式编码器码盘,其中白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。

 

         7-6 绝对式编码器码盘(自然二进制)

  光电绝对式编码器的二进制码盘上有很多圈线槽,我们称为码道。图中码盘有 3 条码道,一共可表示 2^3=8 个二进制数,所以整个码盘被分成了 8 个扇区,每个扇区表示一个 3 位二进制数,每个二进制数对应一个转轴的位置信息。这些区域中,黑块不透光,代表 1;白块透光,代表 0。当码盘随着电机转轴旋转,光线会照射到不同的区域,受光元件就能感受到不同的光线情况,最后经过信号的处理,就可以直接输出该区域对应的二进制码了,而我们通过这个二进制码即可得出码盘(电机转轴)的当前位置(角度)。大家需要注意:二进制码盘的每一个位置对应一个确定的二进制码,因此这一类编码器常被应用于位置以及角度测量。码盘采用自然二进制编码,自然二进制编码的优点是很方便直观,但是受编码器制造和安装精度的影响,实际应用中二进制数的每一位不可能同时改变,或者出现码盘停在两个扇区中间,这些情况都很容易造成读数错误。

  上述的自然二进制码盘读数很方便直观, 但是它在实际应用中容易造成读数偏差很大,例如:当码盘停止旋转时,光线照射在 0000 和 1111 这两个相邻的区域之间,此时输出的二进制数可能是 0000~1111 中的任何一个,此时的读数和码盘的实际位置可能就相差很远了。为了避免读数和实际位置出现巨大偏差,我们可以改进一下二进制码的排列方式,使用格雷码形式。

  下图是一个使用格雷码的码盘,同样的,白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。

           7-7 绝对式编码器码盘(格雷码)

  图中码盘的码道数与上面的自然二进制码盘完全一致,也能表示 8 个 3 位二进制数,只不过将编码方式换成了格雷码。利用任意相邻的二进制格雷码数都只有一位不同的特性,采用这种编码的码盘在一定程度上克服了自然二进制码盘容易产生读数错误的问题。
  绝对式编码器还分为单圈绝对式编码器和多圈绝对式编码器,上面举的两个例子都是针对单圈也就是 360° 以内的情况,当码盘转动超过 360°,输出的编码会重复,这样不符合绝对式编码器数据唯一的要求,所以就出现了多圈绝对式编码器。多圈绝对式编码器的量程可以超过 360°,并且通常超出很多,其内部结构也比单圈的复杂,但是基本原理都是一样的。

  测量旋转超过360度范围,用到多圈绝对值编码器,编码器生产运用钟表齿轮机械原理,当中心码盘旋转时,通过齿轮传动另一组码盘(或多组齿轮,多组码盘),在单圈编码的基础上再增加圈数的编码,以扩大编码器的测量范围,这样的绝对编码器就称为多圈式绝对编码器,它同样是由机械位置确定编码,每个位置编码不重复,而无需记忆。
  多圈编码器另一个优点是由于测量范围大,使用往往富裕较多, 这样在安装时不必要费劲找零点, 将某一中间位置作为起始点就可以了,大大简化了安装调试难度。

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

  最大转速:指编码器机械系统所能承受的最高转速。

4、编码器输入接口

  编码器输入接口带有一阶RC低通滤波,二极管1N4148把电压钳位在3.3V,使得电压可以适配 stm32 的I/O 电平。带有3.3V,510Ω的上拉电阻,可以输出 11V电压到外部设备,但输入就不适宜直接接过高的电压。

5、编码器测速原理

  这里我们所使用的编码器是磁电增量式编码器,它安装在直流有刷电机的尾部,实物图如图 7.2.1 所示:

  图 7.2.1 中,直流有刷电机的编码器有 A、 B 两相,它们会输出两个相位差为 90°的脉冲。可通过比较 A 相在前还是 B 相在前,以判别编码器的正转与反转,通过零位脉冲,可获得编码器的零位参考位。当电机正转时, A 相脉冲在前;当电机反转时,则是 B 相脉冲在前。
  有了 A、 B 两相脉冲信号之后,我们应该如何去处理这些信号,把它们转换成电机的转速呢?这里就涉及到一个非常重要的功能:定时器编码器接口模式。
  STM32 定时器的编码器接口模式就相当于带有方向选择的外部时钟,也就是说,在此模式下,外部输入的脉冲信号可以作为计数器的时钟,而计数的方向则是由脉冲相位的先后所决定的。定时器编码器接口模式的原理如图 7.2.2 所示:

  图 7.2.2 中,当电机(编码器)正转时,输出两相脉冲信号, A 相脉冲在前,此时编码器接口把脉冲信号作为计数器的脉冲,计数方式为递增计数;当电机(编码器)反转时,计数方式就变成了递减计数。

  1) 如单相联接,用于单方向计数,单方向测速。
  2) A、 B 两相联接,用于正反向计数、判断正反向和测速。
  3) A、 B、 Z 三相联接,用于带参考位修正的位置测量。
  4) A、 A-, B、 B-, Z、 Z-连接,由于带有对称负信号的连接,电流对于电缆贡献的电磁场为 0,衰减最小,抗干扰最佳,可传输较远的距离。

  上述的内容只是对编码器接口的原理进行简单的介绍,让大家有一个感性的认识,接下来我们深入研究一下编码器接口的原理:
  (1)编码器接口框图
  我们首先看编码器的接口框图部分,了解一下脉冲信号进入编码器接口的途径,这里以通用定时器为例,具体框图如图 7.2.3 所示:

  图 7.2.3 中, A、 B 两相脉冲信号从 TIMx_CH1 和 TIMx_CH2 这两个通道输入,经过滤波器和边沿检测器(可以设置滤波和反相)的处理,进入到编码器接口控制器中。大家需要注意,TIMx_CH3 和 TIMx_CH4 是不支持编码器接口模式的。
  (2)编码器接口计数原理
  编码器接口可以利用输入脉冲的边沿进行计数,我们通过计数值的变化量,就可以算出输入脉冲信号的变化量,也就可以进一步计算出电机的转速了。接下来我们看一下编码器接口是如何根据脉冲边沿计数的,它的计数方向与输入脉冲信号的关系如表 7.2.1 所示:

  表 7.2.1 来自于《STM32F4xx 参考手册_V4(中文版) .pdf》中的表 75(第 415 页),上表中的有效边沿我们可以通过代码去配置,一共有 3 种边沿检测方式,其中仅在 TI1 处计数表示只检测 TI1 上的脉冲边沿变化,这时候的计数方向需要结合 TI2FP2 上的电平情况来确定,其他的两种边沿检测方式原理也是一样的,下面我们以一个实例来理解这个表格的内容:
  假设我们把 A 相接在 CH1(TI1), B 相接在 CH2(TI2),选择仅在 TI1 处计数(仅检测A 相边沿)。此时编码器接口计数方向和输入脉冲信号的关系如下表:

   编码器输出的 AB 两相脉冲信号如图 7.2.4 所示:

  图 7.2.4 中, A、 B 两相输出的脉冲信号有两种情况:当编码器正转, A 相在前;当编码器反转, B 相在前,我们选择仅在 TI1 处计数,也就是只检测 A 相的边沿。接下来我们分别介绍这两种情况下的计数方向:
  正转:当 A 相上升沿到来时(图中①处),我们需要关注 B 相的电平高低,从图 7.2.4 中可看到 B 相此时是低电平,结合表 7.2.2,可以得知此时计数方向为递增计数;当 A 相下降沿到来时(图中②处),从图中可以看到 B 相此时是高电平,结合表 7.2.2,可以得知此时计数方向为递增计数;当 A 相上升沿再次到来时(图中③处),同理可得此时计数方向为递增计数。
  综上所得,我们可以知道此时编码器正转对应的计数方向就是递增计数。
  反转:当 A 相上升沿到来时(图中④处),我们需要关注 B 相的电平高低,从图 7.2.4 中可看到 B 相此时是高电平,结合表 7.2.2,可以得知此时计数方向为递减计数;当 A 相下降沿到来时(图中⑤处),从图中可以看到 B 相此时是低电平,结合表 7.2.2,可以得知此时计数方向为递减计数;当 A 相上升沿再次到来时(图中⑥处),同理可得此时计数方向为递减计数。
  综上所得,我们可以知道此时编码器反转对应的计数方向就是递减计数。
  上述的就是仅在 TI1 处计数的实例分析,其他两种边沿计数方式的原理是一样的,大家可以举一反三,我们这里就不再展开分析。

  把两个通道组合起来看的话,可以发现A和B各自的上升沿和下降沿都能计数,至少在1/2个原始方波周期内就可以计数一次, 最多1/4个原始方波周期。这样计数频率就是原始方波信号的2倍或4倍,换句话说就是,将编码器的分辨率提高了2到4倍,具体如下图所示。

  上图中的方波信号如果只看其中一个通道的上升沿,那计数频率就等于这个通道信号的频率。如果在通道A的上升沿和下降沿都进行计数,计数频率就是通道A的两倍,即2倍频。 如果同时对两个通道的上升沿和下降沿都计数,那计数频率就变成了原始信号的4倍,即4倍频。
  假设有个增量式编码器它的分辨率是600PPR,能分辨的最小角度是0.6°,对它进行4倍频之后就相当于把分辨率提高到了600*4=2400PPR,此时编码器能够分辨的最小角度为0.15°。 

  注意:

  简单来说,1倍频计数就是完整检测到一个周期的A或B方波输出一个脉冲,2倍频计数就是检测到一个周期的A或B方波的1个上升沿和1个下降沿,而4倍频则是在一个周期内检测到A的1个上升沿1个下降沿以及B的1个上升沿1个下降沿,每个上升沿或下降沿都会输出一个脉冲,从而在一个周期内输出4个脉冲,进而提高了分辨率和定位精度。

  1)、选择仅在 TI1 或者 TI2完整周期处计数,就相当于对脉冲信号进行了 1 倍频(一个周期计数1次),此时如果编码器输出 10 个脉冲信号,那么就会计数 10 次。

  2)、选择仅在 TI1 或者 TI2 处计数上升沿和下降沿都计数,就相当于对脉冲信号进行了 2 倍频(两个边沿,即上升沿和下降沿都会计数),此时如果编码器输出 10 个脉冲信号,那么就会计数 20 次。

  3)、选择的是在 TI1 和 TI2 处均计数,就相当于对脉冲信号进行了 4 倍频(上升沿和下降沿都会计数),此时如果编码器输出 10 个脉冲信号,那么就会计数40 次。如下图所示:

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

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

  (3)STM32编码器接口模式的计数方向

  自动方向检测:
  当定时器配置为编码器接口模式(通过TIMx_SMCR寄存器设置)时,计数方向完全由编码器A、B两相的相位关系决定:
    正转(A相领先B相) → 计数器向上计数(TIMx_CNT递增)。
    反转(B相领先A相) → 计数器向下计数(TIMx_CNT递减)。
  硬件自动控制:
    此时,TIMx_CR1.DIR位(方向位)会被硬件自动更新(只读),DIR=0表示向上,DIR=1表示向下。

  (4)常用测速方法简介

  对于电机转速的测量,可以把增量式编码器安装到电机上,用控制器对编码器脉冲进行计数,然后通过特定的方法求出电机转速,常用的编码器测速方法一般有三种: M 法、 T 法和 M/T 法。
  M 法:又叫做频率测量法。这种方法是在一个固定的定时时间内(以秒为单位),统计这段时间的编码器脉冲数,计算速度值。设编码器单圈总脉冲数为 C,在时间 T0 内,统计到的编码器脉冲数为 M0,则转速 n 的计算公式为:n=M0/(C*T0)
  公式中的编码器单圈总脉冲数 C 是常数,所以转速 n 跟 M0 成正比。这就使得在高速测量时 M0变大,可以获得较好的测量精度和平稳性,但是如果速度很低,低到每个 T0 内只有少数几个脉冲,此时算出的速度误差就会比较大,并且很不稳定。也有一些方法可以改善 M 法在低速测量的准确性,增量式编码器倍频技术就是其中一种,比如原本捕获到的脉冲 M0 只有4 个,经过 4 倍频后,相同电机状态 M0 变成了 16 个,也就提升了低速下的测量精度。
  T 法:又叫做周期测量法。这种方法是建立一个已知频率的高频脉冲并对其计数,计数时间由捕获到的编码器相邻两个脉冲的间隔时间 Te 决定,计数值为 M1。设编码器单圈总脉冲数为 C,高频脉冲的频率为 F0,则转速 n 的计算公式为:n=1/(C*Te)=F0/(C*M1)
  公式中的编码器单圈总脉冲数 C 和高频脉冲频率 F0 是常数,所以转速 n 跟 M1 成反比。从公式可以看出,在电机高转速的时候,编码器脉冲间隔时间 TE 很小,使得测量周期内的高频脉冲计数值 M1 也变得很少,导致测量误差变大,而在低转速时, TE 足够大,测量周期内的 M1 也足够多,所以 T 法和 M 法刚好相反,更适合测量低速。
  M/T 法:这种方法综合了 M 法和 T 法各自的优势,既测量编码器脉冲数又测量一定时间内的高频脉冲数。在一个相对固定的时间内,计数编码器脉冲数 M0,并计数一个已知频率为F0 的高频脉冲,计数值为 M1,计算速度值。设编码器单圈总脉冲数为 C,则转速 n 的计算公式为:n=(F0*M0)/(C*M1)
  由于M/T 法公式中的F0和C是常数,所以转速n就只受M0和M1的影响。电机高速时,M0增大,M1减小,相当于M法,低速时,M1增大,M0减小,相当于 T 法。

6、STM32编码器配置代码示例

  在STM32中使用定时器测试编码器时,预分频(Prescaler) 和 自动重装载值(Auto-Reload Register, ARR) 的设置需根据编码器信号特性、定时器时钟频率及测量需求来确定。以下是具体设置原则及原因分析:  

  6.1、预分频(Prescaler)的设置

  (1) 推荐值
    通常设为 0(不分频),即 Prescaler = 0。
    原因:

      编码器模式直接捕获外部脉冲,分频会降低计数精度。保持PSC=0可确保每个脉冲都被准确记录。
      编码器信号频率可能较高(如几百Hz到几十kHz,编码器的信号频率与电机的转速成正比,转速越快,编码器的脉冲频率越高),不分频可确保计数器(CNT)能精确捕获每个脉冲边沿(不分频一般为84MHz)。
      分频会降低计数分辨率,可能导致高速旋转时丢失脉冲。
  (2) 特殊情况
    若编码器信号频率极高(如 >1MHz),且定时器时钟频率较低(如APB1时钟为36MHz):
    可适当分频(如 Prescaler = 1),但需确保计数器不溢出。
    公式:实际计数器时钟 = 定时器时钟 / (Prescaler + 1)
  6.2、自动重装载值(ARR)的设置

  (1) 推荐值
    设为最大值(如 0xFFFF 或 0xFFFFFFFF,取决于定时器位数)。
    原因:
      编码器模式下,计数器(CNT)会在检测到脉冲边沿时自动增减,ARR仅用于限制计数范围。
      设为最大值可避免频繁溢出,简化方向判断和位置计算(如使用 int32_t 扩展计数范围)。
  (2) 特殊情况
    需要周期性复位计数器(如每1秒清零):
    可设置较小的ARR值,并启用定时器更新中断(TIM_IT_Update)。

  6.3、关键原因分析
  (1) 为什么不分频(Prescaler = 0)?
    高分辨率需求:编码器脉冲通常需要精确计数,分频会导致丢失边沿。
    避免溢出:高速旋转时,分频可能使计数器溢出频率增加,导致位置计算错误。
  (2) 为什么ARR设为最大值?
    减少溢出中断:溢出越少,软件处理越简单(无需频繁处理溢出中断)。
    方向判断简化:在正交编码器模式下,计数器可自动增减,ARR最大值允许更长的无复位计数。
  (3) 例外情况
    低速编码器+长周期测量:若编码器转速极低且需长时间累计(如几天),可设置较小的ARR并配合软件扩展计数(如 int64_t)。

/********************************************************************************
                                   参数宏定义
*******************************************************************************/
//定时器周期
#define GENERAL_TIME2_PERIOD                    65536     //编码器模式时一般将周期定义为最大值,编码器模式下计数器(CNT)会在检测到脉冲边沿时自动增减,设为最大值可避免频繁溢出

//预分配用来设置定时器的时钟频率
#define GENERAL_TIME2_PRESCALER                    1        //预分频器,通常设为0(不分频),确保每个编码器脉冲都被计数。

//编码器模式,一般选择A、B相都计数
#define GENERAL_TIME2_ENCODERMODE_TIX            TIM_ENCODERMODE_TI12


/********************************************************************************
                                   宏定义
*******************************************************************************/
//通用定时器2
#define GENERAL_TIME2                           TIM3                        
#define GENERAL_TIME2_CLK_ENABLE                    __TIM3_CLK_ENABLE
#define GENERAL_TIME2_CLK_DISABLE                __TIM3_CLK_DISABLE

#define GENERAL_TIME2_IRQ                       TIM3_IRQn                   
#define GENERAL_TIME2_IRQHandler                TIM3_IRQHandler             



/********************************************************************************
                                   引脚宏定义
*******************************************************************************/
//通用定时器PWM引脚
#define GENERAL_TIME2_CH1_GPIO_PORT                GPIOC
#define GENERAL_TIME2_CH1_GPIO_PIN                 GPIO_PIN_6
#define GENERAL_TIME2_CH1_GPIO_AF                GPIO_AF2_TIM3
#define GENERAL_TIME2_CH1_GPIO_CLK_ENABLE        __GPIOC_CLK_ENABLE


#define GENERAL_TIME2_CH2_GPIO_PORT                GPIOC
#define GENERAL_TIME2_CH2_GPIO_PIN                 GPIO_PIN_7
#define GENERAL_TIME2_CH2_GPIO_AF                GPIO_AF2_TIM3
#define GENERAL_TIME2_CH2_GPIO_CLK_ENABLE        __GPIOC_CLK_ENABLE




/********************************************************************************
                                   输入/捕获通道定义
*******************************************************************************/
//通道号
#define GENERAL_TIME2_CH1                        TIM_CHANNEL_1
#define GENERAL_TIME2_CH2                        TIM_CHANNEL_2
#define GENERAL_TIME2_CH3                        TIM_CHANNEL_3
#define GENERAL_TIME2_CH4                        TIM_CHANNEL_4


/********************************************************************************
                                   结构体定义
*******************************************************************************/
typedef struct GENERAL_TIME2_ParamTypeDef
{
    int overflow_count;//定时器溢出计数
}GENERAL_TIME2_ParamTypeDef;

TIM_HandleTypeDef GENERAL_TIME2_HandleStruct;

GENERAL_TIME2_ParamTypeDef GENERAL_TIME2_ParamStruct;


/**
  * @brief  通用定时器中断优先级配置
  * @param  无
  * @retval 无
  */
static void GENERAL_TIME_NvicConfig(void)
{ 
    HAL_NVIC_SetPriority(GENERAL_TIME2_IRQ, 0, 2);
    HAL_NVIC_EnableIRQ(GENERAL_TIME2_IRQ);    
        
}


/**
  * @brief  通用定时器2模式配置
  * @param  无
  * @retval 无
  */
static void GENERAL_TIME2_HandleConfig(void)
{    
    GENERAL_TIME2_CLK_ENABLE();

    GENERAL_TIME2_HandleStruct.Instance = GENERAL_TIME2;
    GENERAL_TIME2_HandleStruct.Init.Period = GENERAL_TIME2_PERIOD-1;        //编码器模式时一般将周期定义为最大值,编码器模式下计数器(CNT)会在检测到脉冲边沿时自动增减,设为最大值可避免频繁溢出
    GENERAL_TIME2_HandleStruct.Init.Prescaler = GENERAL_TIME2_PRESCALER-1;    //通常设为0(不分频),确保每个编码器脉冲都被计数。
    GENERAL_TIME2_HandleStruct.Init.CounterMode=TIM_COUNTERMODE_UP;            //计数器计数模式,编码器模式时该配置无效,它会根据A、B相先后顺序决定向上计数还是向下计数
    GENERAL_TIME2_HandleStruct.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;    //时钟分频因子

//    HAL_TIM_Base_Init(&GENERAL_TIME2_HandleStruct);////基础初始化(可选,编码器模式会自动处理)
}


/**
  * @brief  通用定时器编码器模式配置
  * @param  无
  * @retval 无
  */
static void GENERAL_TIME2_EncoderModeConfig(void) 
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_Encoder_InitTypeDef TIM_Encoder_InitStruct;//编码器

    
    GENERAL_TIME2_CH1_GPIO_CLK_ENABLE();
    GENERAL_TIME2_CH2_GPIO_CLK_ENABLE();


    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    

    GPIO_InitStruct.Alternate = GENERAL_TIME2_CH1_GPIO_AF;//设置复用
    GPIO_InitStruct.Pin = GENERAL_TIME2_CH1_GPIO_PIN;
    HAL_GPIO_Init(GENERAL_TIME2_CH1_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Alternate = GENERAL_TIME2_CH2_GPIO_AF;//设置复用
    GPIO_InitStruct.Pin = GENERAL_TIME2_CH2_GPIO_PIN;    
    HAL_GPIO_Init(GENERAL_TIME2_CH2_GPIO_PORT, &GPIO_InitStruct);
    
    
    
    /*定时器只有TIMx_CH1 和 TIMx_CH2是支持编码器模式,TIMx_CH3 和 TIMx_CH4 是不支持编码器接口模式的。
      在 TIM_ENCODERMODE_TI1/TI2/TI12 模式下,STM32的定时器硬件自动关联A相(TI1)和B相(TI2)的所有边沿(上升沿+下降沿),实现正交解码(四倍频)。
    用户无需(也不应)手动配置输入捕获边沿,否则会干扰硬件逻辑。
      “编码器模式下不得使用IC1Polarity和IC2Polarity此配置”,因为编码器接口的边沿检测由硬件自动管理,手动配置可能导致冲突。
      IC1Polarity 和 IC2Polarity 仅用于信号反相纠正(如A/B相反接时设为 TIM_ICPOLARITY_FALLING),不影响边沿检测数量。*/
    TIM_Encoder_InitStruct.EncoderMode        = GENERAL_TIME2_ENCODERMODE_TIX;//编码器模式:TIM_ENCODERMODE_TI1(仅TI1计数)、TIM_ENCODERMODE_TI2(仅TI2计数)、TIM_ENCODERMODE_TI12(TI1和TI2均计数,模式3)。
    TIM_Encoder_InitStruct.IC1Polarity        = TIM_ICPOLARITY_RISING;//输入通道1(TI1)极性,这里设置 IC1Polarity = TIM_ICPOLARITY_RISING,不是表示仅捕获上升沿,此配置仅用于纠正信号极性(例如A相反相时),不限制边沿检测数量。
    TIM_Encoder_InitStruct.IC1Selection       = TIM_ICSELECTION_DIRECTTI;//输入通道选择,ICx的信号可来自三个输入通道,分 别为 TIM_ICSELECTION_DIRECTTI(直接输入到TI1)、 TIM_ICSELECTION_INDIRECTTI(交叉输入到TI2) 或IM_ICSELECTION_TRC(触发输入)。它设定 TIMx_CCMRx 寄存器的 CCxS[1:0] 位的值。定时器在编码器接口模式下,此成员只能设置为 TIM_ICSELECTION_DIRECTTI。
    TIM_Encoder_InitStruct.IC1Prescaler       = TIM_ICPSC_DIV1;//这里不分频
    TIM_Encoder_InitStruct.IC1Filter          = 13; // 输入滤波器(0x00~0x0F),值越大滤波越强,抑制噪声,最大滤波0x0F
    TIM_Encoder_InitStruct.IC2Polarity        = TIM_ICPOLARITY_RISING;
    TIM_Encoder_InitStruct.IC2Selection       = TIM_ICSELECTION_DIRECTTI;
    TIM_Encoder_InitStruct.IC2Prescaler       = TIM_ICPSC_DIV1;
    TIM_Encoder_InitStruct.IC2Filter          = 13; // max 0x0F
    
    __HAL_TIM_SET_COUNTER(&GENERAL_TIME2_HandleStruct,0);//将计数器计数清0

    HAL_TIM_Encoder_Init(&GENERAL_TIME2_HandleStruct, &TIM_Encoder_InitStruct);//编码器初始化

    __HAL_TIM_CLEAR_IT(&GENERAL_TIME2_HandleStruct, TIM_IT_UPDATE);  // 清除更新中断标志位
    __HAL_TIM_URS_ENABLE(&GENERAL_TIME2_HandleStruct);               // 仅允许计数器溢出才产生更新中断
    __HAL_TIM_ENABLE_IT(&GENERAL_TIME2_HandleStruct,TIM_IT_UPDATE);  // 使能更新中断


    HAL_TIM_Encoder_Start(&GENERAL_TIME2_HandleStruct, TIM_CHANNEL_ALL);//编码器启动
}




/**
  * @brief  通用定时器初始化
  * @param  无
  * @retval 无
  */
void GENERAL_TIME_ConfigInit(void)
{
    GENERAL_TIME_NvicConfig();  
    GENERAL_TIME2_HandleConfig();
    GENERAL_TIME2_EncoderModeConfig();
}


/**
  * @brief  GENERAL_TIME2获取编码器值
  * @param  无
  * @retval 无
  */
int GENERAL_TIME2_GetEncoderValue(void)
{
    return (int32_t)__HAL_TIM_GET_COUNTER(&GENERAL_TIME2_HandleStruct) + GENERAL_TIME2_ParamStruct.overflow_count*65536;
}




/************************************************************************************************
  * @ 函 数 名:HAL_TIM_PeriodElapsedCallback
  * @ 功能说明:定时器周期中断回调函数
  * @ 参    数:无
  * @ 返 回 值:无
************************************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&BASIC_TIME1_HandleStruct))
    {
        if (__HAL_TIM_IS_TIM_COUNTING_DOWN(&GENERAL_TIME2_HandleStruct))//判断是递增计数还是递减计数
        {
            GENERAL_TIME2_ParamStruct.overflow_count--;       //向下计数溢出
        }
        else
        {        
            GENERAL_TIME2_ParamStruct.overflow_count++;       //向上计数溢出
        }
    }
}







#define MOTOR_FORWARD_ROTATION        0    //电机正转
#define MOTOR_REVERSE_ROTATION        1    //电机反转 

#define MOTOR_DISABLE                0    //电机失能
#define MOTOR_ENABLE                1    //电机使能


#define MOTOR_ENCODER_LINE_COUNT_1                100      //电机编码器线数
#define MOTOR_REDUCTION_RATIO_1                  30       //电机减速比
#define MOTOR_PULSE_NUM_1                         (MOTOR_REDUCTION_RATIO_1*MOTOR_ENCODER_LINE_COUNT_1*4) // Pulse/r 每圈可捕获的脉冲数






/************************************************************************************************
  * @ 函 数 名:MOTOR_GetLocation
  * @ 功能说明:获取电机位置,MOTOR_GET_LOCATION_TIME_MS_1ms执行一次
  * @ 参    数:Motor_Number:电机编号
  * @ 返 回 值:无
************************************************************************************************/
void MOTOR_GetLocation(uint8_t Motor_Number)
{
    if (Motor_Number == MOTOR_NUMBER_1)
    {
        MOTOR_RunStruct[0].encoder_capture_value = GENERAL_TIME2_GetEncoderValue();
        
        //电机目前的位置,编码器的值/每圈可捕获的脉冲数*360
        MOTOR_RunStruct[0].at_present_location = (float)MOTOR_RunStruct[0].encoder_capture_value/(float)MOTOR_PULSE_NUM_1*360.0f;
    }
}

 

posted @ 2025-04-09 17:25  孤情剑客  阅读(710)  评论(0)    收藏  举报