电机控制基础知识--直流有刷电机编码器测速
编码器是一种能将直线位移、角位移数据转换为脉冲信号、二进制编码的设备。它本质上就是一个传感器,可以把角位移或直线位移转换成电信号,并反馈给控制器,使控制 器知道当前机械运动的位置、角度等信息。编码器常用的有磁电增量式、光电增量式以及光电绝对式三种,本篇文章主要讲解磁电增量式编码器,其原理是:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。如下图所示:由图可见,磁电增量式编码器的结构包
1. 引言
在电机控制中,常需要对电机的转速进行检测,一般采用编码器测速的方式。本期将介绍编码器的概念、编码器测速的原理、以及如何以STM32为例来编写代码实现测速功能。
2. 编码器简介
编码器是一种能将直线位移、角位移数据转换为脉冲信号、二进制编码的设备。 它本质上就是一个传感器,可以把角位移或直线位移转换成电信号,并反馈给控制器,使控制 器知道当前机械运动的位置、角度等信息。
编码器常用的有磁电增量式、光电增量式以及光电绝对式三种,本篇文章主要讲解磁电增量式编码器,其原理是:利用霍尔效应,将位移转换成计数脉冲,用脉冲个数计算位移和速度。如下图所示:

由图可见,磁电增量式编码器的结构包含:磁盘、霍尔传感器以及信号转换电路3个部分,其中,磁盘是由一个个交替排布的S极和N极磁极组成;霍尔传感器可以把磁场的变化转换成电信号的变化,它通常有A、B两相(有的还有Z相),这两相的安装位置形成一定的 夹角,这使得输出的A、B两相信号有90°的相位差;信号转换电路可以把电信号转换成脉冲信号。
一般而言,磁盘会装在电机的转轴上,它会随着电机的转轴旋转,而磁盘上面的S极和N极就会交替地经过霍尔传感器的A、B两相,霍尔传感器就可以把磁盘上的磁场变化转换为电信号的变化(霍尔传感器原理不过多展开,有兴趣自行百度霍尔效应),输入到信号转换电路中,经过信号的转换之后,我们就可以得到A、B两相脉冲信号。从上图中可以看到,A、B两相脉冲信号存在90°的相位差,而磁盘的正反转方向就决定了是A相信号在前还是B相在前。
了解编码器的基本结构后,来看一下编码器的基本参数。如下所示:
分辨率:编码器每个计数单位之间产生的距离,它是编码器可以测量到的最小的距离。对于增量式编码器,分辨率表示为编码器的转轴每旋转一圈所输出的脉冲数(PPR), 也称为多少线,本篇文章以11线为例。
精度:编码器分辨率和精度是两个独立的概念,精度是指编码器输出的信号数据与 实际位置之间的误差,常用角分′、角秒″表示。
最大响应频率:编码器每秒能输出的最大脉冲数,单位Hz,也称为PPS。
最大转速:指编码器机械系统所能承受的最高转速。
3. 编码器的测速原理
在第2章节讲过,直流有刷电机的编码器有A、B两相。它们会输出两个相位差为90°的脉冲。一般来讲,当电机正转时,A相脉冲在前;当电机反转时,则是B相脉冲在前。那此时问题来了,经过编码器中的霍尔传感器检测到磁场,转变为脉冲信号后,如何将这些脉冲信号转换为电机的转速呢?
如章节2所示,电机转轴上布满了磁铁,当电机转一圈时,安装在电机转轴上的霍尔传感器可以检测到11个脉冲;而电机转速是1min转多少圈,因此只需要去计算1min内检测到的脉冲数,再除以分辨率,就可以得到1min转动的圈数,即电机的转速。
因此,问题的关键又转移到了如何去计算出1min内的脉冲数。好在STM32的定时器里有个编码器接口模式功能可以解决这个问题,STM32定时器功能如下图所示:

接下来着重看一下STM32的编码器接口模式功能。
3.1 STM32编码器接口模式
依据STM32中文参考手册_V10,编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到 TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。个人解释来说,编码器接口模式就是一个计数器,对外部输入的脉冲信号进行计数;而计数的方向则是由脉冲相位的先后所决定的,计数范围在0-65535之间。如下图所示:

由图可见,当电机正转时,输出两相脉冲信号,A相脉冲在前,此时编码器接口把脉冲信号作为计数器的脉冲,计数方式为递增计数;当电机反转时,计数方式就变成了递减计数。
该计数方向的规定也可以在STM32中文参考手册_V10的表77找到,如下图所示:

接下来以一个示例来对上表进行说明,当A相上升沿到来时(如下图中①处),我们需要关注B相的电平高低,从下图中可看到B相此时是低电平,结合表77,可以得知此时计数方向为递增计数;当A相下降沿到来时(下图中②处),从图中可以看到B相此时是高电平,结合表77,可以得知此时计数方向为递增计数;当A相上升沿再次到来时(下图中③处),同理可得此时计数方向为递增计数。 综上所得,可以知道此时电机正转对应的计数方向就是递增计数。

此外,还需要注意一点的是关于计数模式的选择:如果选择仅在A相或者B相处计数,就相当于对脉冲信号进行了2倍频(因为此时会在两个边沿计数,上升沿和下降沿), 此时如果编码器输出10个脉冲信号,那么就会计数20次。如果选择的是在A和B处均计数,就相当于对脉冲信号进行了4倍频,此时如果编码器输出10个脉冲信号,那么就会计数 40 次。因此,通过计数次数来计算电机速度的时候,需要除以相应的倍频系数。
综上可知如何计算1min内的脉冲数后,即可得到电机转速的具体公式如下:电机转速 = 一分钟内计数变化量 / 倍频系数 / 编码器线数 / 减速比;其中,减速比指的是瞬时输入速度与输出速度的比值,如输入转速为1500r/min,输出转速为25r/min,那么其减速比则为60:1。
3.2 编码器接口
了解到编码器接口模式的计数原理之后,又出现了个问题--即外部输入的脉冲信号是怎么进入到编码器接口的呢?也可以在参考手册找到答案,定时器的编码器接口框图如下图所示:

由上图可见,A、B两相脉冲信号从TIMx_CH1和TIMx_CH2这两个通道输入,经过滤波器和边沿检测器(可以设置滤波和反相)的处理,进入到编码器接口控制器中。此外, TIMx_CH3和TIMx_CH4是不支持编码器接口模式的。
综上,了解了如何计算脉冲数变化量从而得到电机转速,接下来看一下具体的代码实现。
4. 编码器测速编程
STM32定时器有高级定时器、通用定时器及基本定时器之分。在之前的电机控制基础知识讲解文章中,采用了高级定时器来控制直流有刷电机;因此,此处采用通用定时器来实现编码器测速功能。接下来先说一下几个关键的寄存器配置。
4.1 关键寄存器
先来看TIMx_CCMR1寄存器,如下图所示:

需要操作的关键位由上图可见,主要解释一下滤波器的设置。假设IC1F[3:0]=0011,并设置IC1映射到TI1上。表示以fCK_INT为采样频率,当连续8次都是 采样到TI1为高电平或者低电平,滤波器才输出一个有效输出边沿。当8次采样中有高有低, 那就保持原来的输出,这样可以滤除高频干扰信号,从而达到滤波的效果。
接下来看捕获/比较使能寄存器TIMx_CCER,如下图所示:

由上图可见,需要注意的是CC1位,当配置为输入时,可以选择反相或者不反相,选择反相时代表电机转动方向与编码器计数方向不一致时,可以通过设置为反向来快速纠正方向。
最后关注从模式控制寄存器TIMx_SMCR,如下图所示:

该寄存器的SMS[2:0]位,用于从模式选择,其实就是选择计数模式。如果需要仅在 TI1边沿处计数(A接TI1),则设置SMS[2:0]=010;如果需要仅在 TI2边沿处(B接TI2)计数,则设置SMS[2:0]=001;如果需要在 TI1、TI2边沿处都计数,则设置SMS[2:0]=011。
4.2 代码编写
接下来看详细的代码编写,仅供演示,请谨慎参考。首先是编码器初始化驱动函数如下:
/*TIM3编码器功能初始化*/
void TIM3_encode_init(u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/*使能TIM3时钟和GPIO*/
RCC_APB2PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
io_set(GPIOA, GPIO_Pin_6, GPIO_Mode_IN_FLOATING); //初始化TIM3_CH1
io_set(GPIOA, GPIO_Pin_7, GPIO_Mode_IN_FLOATING); //初始化TIM3_CH2
/*设置定时器参数*/
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/*设置编码器,此处选择4倍频,需要对TI1(A相)和TI2(B相)进行计数*/
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择CH1
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择是否反相,不反向
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //选择输入通道,对应寄存器CCIS[1:0]
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //不分频,每一个边沿触发一次捕获
TIM_ICInit(TIM3,&TIM_ICInitStructure); //初始化TIM3编码器接口
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择CH2
TIM_ICInitStructure.TIM_ICFilter = 10; //设置滤波器
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //选择是否反相,不反向
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //选择输入通道,对应寄存器CCIS[1:0]
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //不分频,每一个边沿触发一次捕获
TIM_ICInit(TIM3,&TIM_ICInitStructure); //初始化TIM3编码器接口
/*配置编码器接口,选择TI1、TI2同时计数*/
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
/*设置TIM3中断,因为最大计数为65535,超过会溢出,开启更新中断*/
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
上述编码器初始化代码中之所以要配置中断相关的初始化,就是考虑到计数脉冲值溢出65535时,此时计数值会变为0或者是65535,因此需要处理溢出情况。接下来是定时器TIM4的初始化,用于计算转速,代码如下所示:
void TIM4_init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //时钟使能
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE ); //使能指定的TIM2中断,允许更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM4, ENABLE);
}
接下来看对应的中断函数以及转速计算,代码如下:
/*获取计数脉冲值*/
int get_encoder_value(void)
{
u16 buffer;
buffer=TIM_GetCounter(TIM3)+(overflow*65536);
TIM_SetCounter(TIM3,0);
return buffer;
}
/*获取计数方向*/
u8 TIM_GetDirection(TIM_TypeDef* TIMx)
{
return (TIMx->CR1 & TIM_CR1_DIR) ? 1 : 0;
}
/*获取电机转动速度*/
float get_speed(int encode_value,u8 ms)
{
u8 i = 0, j = 0;
float temp = 0.0;
float speed;
static uint8_t sp_count = 0, k = 0;
static float speed_arr[10] = {0.0}; // 存储速度进行滤波运算
if (sp_count == ms) // 计算一次速度
{
//保存电机转速,30为减速比,4倍频,11线
speed_arr[k++] = (float)(encode_value * ((1000 / ms) * 60.0) / 30 / (11*4));
/* 累计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值越小则上一次输出对本次输出影响越大,整体曲线越平稳,但是对于速度变化的响应也会越慢
*/
speed = (float)( ((float)0.48 * temp) + (speed * (float)0.52) );
k = 0;
}
sp_count = 0;
}
sp_count ++;
return speed;
}
/*TIM3定时器中断函数,用于处理溢出*/
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查 TIM3 更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除 TIM3 更新中断标志
if(TIM_GetDirection(TIM3)) //获取dir
{
overflow--; //dir=1,为递减计数
}
else
{
overflow++; //dir=0,为递增计数
}
}
}
/*TIM4定时器中断函数,用于计算1ms的计数脉冲值和计算速度*/
void TIM4_IRQHandler(void)
{
int count;
float speed;
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) //检查 TIM4 更新中断发生与否
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update ); //清除 TIM4 更新中断标志
count = get_encoder_value(); //获取脉冲值
speed=get_speed(count,50); //50ms计算一次速度
printf("motor speed is %.1f\r\n",speed);
}
}
5. 参考文献
[1] STM32中文参考手册_V10;
[2] DMF407电机控制专题教程
更多推荐



所有评论(0)