本文主要介绍电机编码器的作用与使用
编码器的分类方式有很多,我们这里列举两种分类方式:按检测原理和编码类型
在实际的应用中,这四类编码器并不是相对独立的,它们经过组合后,就变成了光电绝对式、光电增量式、磁电绝对式和磁电增量式这四种编码器。
这里主要介绍磁电增量式、光电增量式以及光电绝对式这 3 种常用编码器的工作原理
原理:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。具体工作原理如图 所示
磁电增量式编码器的结构包含:磁盘、霍尔传感器以及信号转换电路 3 个部分,其中,磁盘是由一个个交替排布的 S 极和 N 极磁极组成;霍尔传感器可以把磁场的变化转换成电信号的变化,它通常有 A、B 两相(有的还有 Z 相),这两相的安装位置形成一定的夹角,这使得输出的 A、B 两相信号有 90°的相位差;信号转换电路可以把电信号转换成脉冲信号。
在实际应用中,磁盘会装在电机的转轴上,它会随着电机的转轴旋转,而磁盘上面的 S 极和 N 极就会交替地经过霍尔传感器的 A、B 两相,霍尔传感器就可以把磁盘上的磁场变化转换为电信号的变化,输入到信号转换电路中,经过信号的转换之后,我们就可以得到 A、B 两相脉冲信号了。从上图中可以看到,A、B 两相脉冲信号存在 90°的相位差,当电机正转时,A 相脉冲在前;当电机反转时,则是 B 相脉冲在前。
原理:利用光电系统,将位移转换成计数脉冲,用脉冲个数计算位移和速度。
上节提到过,增量式编码器都有 A、B 两通道信号输出,这是因为增量式编码器的码盘上有两圈线槽,两圈线槽的之间会错开一定的角度,这个角度会使得光电检测装置输出的两相信号相差 1/4 周期 (90°)。码盘的具体工作方式如下图所示。图中黑色代表透光,白色代表遮光。当码盘转动时,内圈和外圈的线槽会依次透过光线,光电检测装置检测到光线通断的变化,就会相应的输出脉冲信号,因为内外圈遮光和透光时候存在时间差,所以也就有了 A、B 两通道信号的相位差。
根据两相信号变化的先后顺序就可以判断运动方向,记录输出的脉冲个数可以知道位移量的大小,同时通过输出信号的频率就能得到速度。
一些增量式编码器上会有 4 圈线槽,分别对应 A、B、-A、-B 四相信号,相邻两相信号间也是差1/4 周期,只不过这种编码器会把-A 和-B 两相信号反相,然后叠加到 A、通道 B,用来增强信号。除了通道 A、通道 B 以外,很多增量式编码器还会设置一个额外的通道 Z 输出信号。通道 Z 信号也在码盘上有对应的线槽,不过只有一条,码盘转一圈才会经过一次。通道 Z 信号一般用做参考零位,指示设备位置或者清除积累量。
增量式编码器计数起点任意设定,可实现多圈无限累加和测量。需要提高分辨率时,可触发 A、B 两通道信号的上升沿和下降沿对原脉冲数进行倍频。但是当接收设备停机重启后,增量式编码器需要重新寻找参考零点。
另一种较为常用的增量式编码器是霍尔编码器。霍尔增量式编码器在结构上和光电式几乎相同,只不过检测原理变成了霍尔效应。内部元件也稍有不同,霍尔编码器的码盘上不是线槽,而是不同的磁极,或者有些直接把电机的旋转磁场当作码盘,然后检测装置换成了霍尔传感器。输出和光电式相同,仍然是相位差 1/4 周期的 A、B 两通道信号。
原理:当码盘处于不同位置(角度)时,光敏元件根据受光与否转换出相应的电平信号,最后转换成二进制数输出。
绝对式编码器在总体结构上与增量式比较类似,都是由码盘、检测装置和放大整形电路构成,但是具体的码盘结构和输出信号含义不同。绝对式编码器的码盘上有很多圈线槽,被称为码道,每一条码道内部线槽数量和长度都不同。它们共同组成一套二进制编码,一条码道对应二进制数的其中一个位,通常是码盘最外侧的码道表示最低位,最内侧的码道表示最高位。码道的数量决定了二进制编码的位数,一个绝对式编码器有 N 条码道,它就能输出 N 位二进制数,且输出二进制数的总个数是 2^N 个。这些二进制数与转轴的机械位置是固定的,和编码器外部因素无关,所以叫做绝对式编码器。在接收设备断电重启后绝对式编码器无需寻找参考零点。
下图是一个简化版的绝对式编码器码盘,其中白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。图中码盘有 3 条码道,一共可表示 2^3=8 个二进制数,所以整个码盘被分成了 8 个扇区,每个扇区表示一个 3 位二进制数,每个二进制数对应一个转轴的位置信息。码盘采用自然二进制编码,自然二进制编码的优点是很方便直观,但是受编码器制造和安装精度的影响,实际应用中二进制数的每一位不可能同时改变,或者出现码盘停在两个扇区中间,这些情况都很容易造成读数错误。
为了避免出现读数错误,可以使用格雷码来解决。下图是一个使用格雷码的码盘,同样的,白色块透光表示 0,黑色块不透光表示 1。码盘上的二进制数逆时针依次增大。图中码盘的码道数与上面的自然二进制码盘完全一致,也能表示 8 个 3 位二进制数,只不过将编码方式换成了格雷码。利用任意相邻的二进制格雷码数都只有一位不同的特性,采用这种编码的码盘在一定程度上克服了自然二进制码盘容易产生读数错误的问题。
绝对式编码器还分为单圈绝对式编码器和多圈绝对式编码器,上面举的两个例子都是针对单圈也就是 360° 以内的情况,当码盘转动超过 360°,输出的编码会重复,这样不符合绝对式编码器数据唯一的要求,所以就出现了多圈绝对式编码器。多圈绝对式编码器的量程可以超过 360°,并且通常超出很多,其内部结构也比单圈的复杂,但是基本原理都是一样的。
增量式编码器输出的脉冲波形信号形式常见的有两种:
①一种是占空比 50% 的方波,通道 A 和 B 相位差为 90°;
②另一种则是正弦波这类模拟信号,通道 A 和 B 相位差同样为 90°。
对于第 1 种形式的方波信号,如果把两个通道组合起来看的话,可以发现 A 和 B 各自的上升沿和下降沿都能计数,至少在 1/2 个原始方波周期内就可以计数一次,最多 1/4 个原始方波周期。这样计数频率就是原始方波信号的 2 倍或 4 倍,换句话说就是,将编码器的分辨率提高了 2 到 4倍,具体如下图所示:
图中的方波信号如果只看其中一个通道的上升沿,那计数频率就等于这个通道信号的频率。如果在通道 A 的上升沿和下降沿都进行计数,计数频率就是通道 A 的两倍,即 2 倍频。如果同时对两个通道的上升沿和下降沿都计数,那计数频率就变成了原始信号的 4 倍,即 4 倍频。
假设有个增量式编码器它的分辨率是 600PPR,能分辨的最小角度是 0.6°,对它进行 4 倍频之后就相当于把分辨率提高到了 600*4=2400PPR,此时编码器能够分辨的最小角度为 0.15°。编码器倍频技术还可用来扩展一些测速方法的速度适用范围。例如电机测速通常使用 M 法进行测量(M法在下节介绍),编码器 4 倍频后可以扩展 M 法的速度下限。
上一节提到了增量式编码器倍频技术可以扩展 M 法的测量范围,那么现在我们就来讲解下这个M 法究竟是怎样测速的,以及简单介绍一些常用的编码器测速方法。对于电机转速的测量,可以把增量式编码器安装到电机上,用控制器对编码器脉冲进行计数,然后通过特定的方法求出电机转速,常用的编码器测速方法一般有三种:M 法、T 法和 M/T 法。
M 法:又叫做频率测量法
这种方法是在一个固定的定时时间内(以秒为单位),统计这段时间的编码器脉冲数,计算速度值。设编码器单圈总脉冲数为 C,在时间 T0 内,统计到的编码器脉冲数为 M0,则转速 n 的计算公式为:
公式中的编码器单圈总脉冲数 C 是常数,所以转速 n 跟 M0 成正比。这就使得在高速测量时 M0变大,可以获得较好的测量精度和平稳性,但是如果速度很低,低到每个 T0 内只有少数几个脉冲,此时算出的速度误差就会比较大,并且很不稳定。也有一些方法可以改善 M 法在低速测量的准确性,上一节提到的增量式编码器倍频技术就是其中一种,比如原本捕获到的脉冲 M0 只有4 个,经过 4 倍频后,相同电机状态 M0 变成了 16 个,也就提升了低速下的测量精度
T 法:又叫做周期测量法。
这种方法是建立一个已知频率的高频脉冲并对其计数,计数时间由捕获到的编码器相邻两个脉冲的间隔时间 TE 决定,计数值为 M1。设编码器单圈总脉冲数为 C,高频脉冲的频率为 F0,则转速 n 的计算公式为:
公式中的编码器单圈总脉冲数 C 和高频脉冲频率 F0 是常数,所以转速 n 跟 M1 成反比。从公式可以看出,在电机高转速的时候,编码器脉冲间隔时间 TE 很小,使得测量周期内的高频脉冲计数值 M1 也变得很少,导致测量误差变大,而在低转速时,TE 足够大,测量周期内的 M1 也足够多,所以 T 法和 M 法刚好相反,更适合测量低速。
M/T 法
这种方法综合了 M 法和 T 法各自的优势,既测量编码器脉冲数又测量一定时间内的高频脉冲数。在一个相对固定的时间内,计数编码器脉冲数 M0,并计数一个已知频率为F0 的高频脉冲,计数值为 M1,计算速度值。设编码器单圈总脉冲数为 C,则转速 n 的计算公式为:
由于 M/T 法公式中的 F0 和 C 是常数,所以转速 n 就只受 M0 和 M1 的影响。电机高速时,M0 增大,M1 减小,相当于 M 法,低速时,M1 增大,M0 减小,相当于 T 法。
本节以磁电增量式编码器为例
直流有刷电机的编码器有 A、B 两相,它们会输出两个相位差为 90°的脉冲。当电机正转时,A 相脉冲在前;当电机反转时,则是 B 相脉冲在前。STM32 芯片内部有专门用来采集增量式编码器方波信号的接口,这些接口实际上是 STM32 定时器的其中一种功能。不过编码器接口功能只有高级定时器 TIM1、TIM8 和通用定时器 TIM2 到TIM5 才有。
STM32 定时器的编码器接口模式就相当于带有方向选择的外部时钟,也就是说,在此模式下,外部输入的脉冲信号可以作为计数器的时钟,而计数的方向则是由脉冲相位的先后所决定的。定时器编码器接口模式的原理如图所示:
我们重点关注编码器接口是如何实现信号采集和倍频的。我们首先看编码器的接口框图部分,了解一下脉冲信号进入编码器接口的途径,这里以通用定时器为例
编码器接口用到了定时器的输入捕获部分,《STM32F4xx 参考手册》给出了的编码器信号与计数器方向和计数位置之间的关系,如下表所示:
首先需要解释一下,表中的 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倍频。
接下来看如下图像,图中同样包含 TI1、TI2 两通道的信号,以及计数器的计数方向,其中 TI1 比TI2 滞后 1/4 个周期,以 TI1 的信号边沿作为有效边沿。当检测到 TI1 的上升沿时,TI2 为高电平,此时计数器向下计数 1 次,下一时刻检测到 TI1 的下降沿时,TI2 为低电平,此时计数器仍然向下计数一次,以此类推。这样同样是把 TI1 的上升沿和下降沿都用来计数,同样实现了对原始信号的 2 倍频,只不过变成向下计数了。
最后如下图所示,下图所展示的信息对应表格中“在 TI1 和 TI2 处均计数”。这种采样方式可以把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考,相当于原来仅在一个通道处计数的 2 倍,所以这种就能实现对原始信号的4倍频:
下面我们以一个实例来理解这个表格的内容。假设我们把 A 相接在 CH1(TI1),B 相接在 CH2(TI2),选择仅在 TI1 处计数(仅检测A 相边沿)。此时编码器接口计数方向和输入脉冲信号的关系如下表:
编码器输出的 A、B 两相脉冲信号如下图所示,图中,A、B 两相输出的脉冲信号有两种情况:当编码器正转,A 相在前;当编码器反转,B 相在前,我们选择仅在 TI1 处计数,也就是只检测 A 相的边沿。
以正转为例,当 A 相上升沿到来时(图中①处),我们需要关注 B 相的电平高低,从图中可看到 B 相此时是低电平,结合表可以得知此时计数方向为递增计数;当 A 相下降沿到来时(图中②处),从图中可以看到 B 相此时是高电平,结合表可以得知此时计数方向为递增计数;当 A 相上升沿再次到来时(图中③处),同理可得此时计数方向为递增计数。综上所得,我们可以知道此时编码器正转对应的计数方向就是递增计数。反转同理
注意:选择仅在 TI1 或者 TI2 处计数,就相当于对脉冲信号进行了 2 倍频(两个边沿),此时如果编码器输出 10 个脉冲信号,那么就会计数 20 次。选择的是在 TI1 和 TI2 处均计数,就相当于对脉冲信号进行了 4 倍频,此时如果编码器输出 10 个脉冲信号,那么就会计数40 次。因此,我们通过计数次数来计算电机速度的时候,需要除以相应的倍频系数。
至此,A、B 两相脉冲信号的变化就转换成了定时器的计数变化。接下来我们就可以通过一分钟内计数的变化量来计算电机的速度,具体公式如下:
电机转速 = 一分钟内计数变化量 / 倍频系数 / 编码器线数 / 减速比
HAL 库函数对定时器外设建立了多个初始化结构体,其中编码器接口用到的有时基初始化结构体 TIM_Base_InitTypeDef ,和编码器初始化配置结构体 TIM_Encoder_InitTypeDef 。初始化结构体成员用于设置定时器工作环境参数,并由定时器相应初始化配置函数调用,最终这些参数将会写入到定时器相应的寄存器中。
typedef struct
{
uint32_t Prescaler; //定时器预分频器设置
uint32_t CounterMode; //计数模式
uint32_t Period; //定时器周期
uint32_t ClockDivision; //时钟分频
uint32_t RepetitionCounter; //重复计算器
uint32_t AutoReloadPreload; //自动重载预装载值
}TIM_Base_InitTypeDef;
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;
这里以STM32f4的HAL库为例,标准库同理
这里用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; /* 编码器参数变量 */
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);
}
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); /*定时器回调函数*/
}
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计算一次速度*/
}
}
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 ++;
}