编码器分类及原理和测速应用(含代码)
文章目录
杂谈
这篇博文写的时间确实有几天了,主要是想让需要的人更好地运用这一模块,同时将自己的使用经验分享给大家,就像当初迷茫的我,也是CSDN的 大佬们的指点迷津对我有了很大的帮助。
这几阶段,我主要是将一些模块知识的理解与运用,和一些项目的经验,后期打算深入编程语言与嵌入式相关技术。还有那一直想好好深入学习的数据结构,哈哈。
前言
编码器在项目、竞赛中被广泛运用。很多运动控制系统都是一个闭环系统,而在这个闭环系统中,各类型的电机必然是执行器,但只有电机,那这个运动控制只能是开环的,我们需要一个反馈值,用于实现电机控制的闭环结构。编码器被广泛应用于电机测速,实现电机闭环控制。
下篇博文是上次写的关于电机控制的博文
电机控制程序
一、何为编码器
编码器(encoder)是将信号(如比特流)或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。
编码器把角位移或直线位移转换成电信号,前者称为码盘,后者称为码尺。
二、编码器的分类
按照读出方式,编码器可以分为接触式和非接触式两种。
按照工作原理,编码器可分为增量式和绝对式两类。
我们在通常使用中,常以工作原理的形式进行分类,以供选型使用。
1、增量式编码器
增量式编码器基本原理:
增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。
先上图,留个印象。这是逐飞科技的一款正交编码器(有带方向的),(不是打广告)。
对于增量式的编码器,有几个基本的概念。
1、五根线,三根数据线A线、B线、Z线,还有VCC与GND。
其中,A、B均为数据输出线,输出的是脉冲数,用于数据加工处理。关于A、B线的不同之处,见下图。两者输出的脉冲存在90度的相位差。
Z线一般是零点信号的,就是当编码器旋转到零点位置时,它会发出一个脉冲用于表示,这个位置是生产商固定的,也可以称作机械零位。
对于增量式编码器的工作原理可见这篇博文:
增量式编码器详解
2、绝对式编码器
绝对式编码器基本原理:
与增量式通过计数脉冲数和判断脉冲的方向不同,绝对式编码器直接输出数字的传感器,更准确的说,是能给出与每个角位置相对应的完整的数宇量输出 。
所谓的位置信息大致可以这样理解,每个位置是唯一的,编码器旋转过程中,直接读取位置即可。编码器中有光码盘(可理解为下图)。
在它的圆形码盘上沿径向有若干同心码道,每条上由透光和不透光的扇形区相间组成,相邻码道的扇区数目是双倍关系,码盘上的码道数就是它的二进制数码的位数,在码盘的一侧是光源,另一侧对应每一码道有一光敏元件;当码盘处于不同位置时,各光敏元件根据受光照与否转换出相应的电平信号,形成二进制数。
这种编码器的特点是不要计数器,在转轴的任意位置都可读出一个固定的与位置相对应的数字码。显然,码道越多,分辨率就越高,对于一个具有 N 位二进制分辨率的编码器,其码盘必须有N 条码道。
(光码盘)
绝对式编码器类别:单圈绝对编码器和多圈绝对编码器
**1、旋转单圈绝对编码器,**以转动中测量光电码盘各道刻线,以获取唯一的编码,当转动超过360度时,编码又回到原点,这样就不符合绝对编码唯一的原则,这样的编码只能用于旋转范围360度以内的测量,称为单圈绝对值编码器。
如果要测量旋转超过360度范围,就要用到多圈绝对编码器。
2、旋转多圈绝对编码器,编码器生产厂家运用钟表齿轮机械的原理,当中心码盘旋转时,通过齿轮传动另一组码盘(或多组齿轮,多组码盘),在单圈编码的基础上再增加圈数的编码,以扩大编码器的测量范围,这样的绝对编码器就称为多圈式绝对编码器,它同样是由机械位置确定编码,每个位置编码唯一不重复,而无需记忆。
这部分可详细参考这篇博文。绝对式编码器
3、编码器运用实例
先看图,这是我在智能车赛测速用的编码器,WCHF103芯片与四个编码器之间的数据传输依靠的是SPI的“分时复用”,对于分时复用的概念,前几篇博文已经介绍过了。走SPI通信这种办法,确实有一定的优点,少用一个定时器,哈哈。
后来,我又写了这个编码器与32的芯片进行SPI通信的程序,有需要的可以评论区留下邮箱。
3、霍尔编码器
如下图。这个编码器挺好,便宜实用,上面两种有些小贵。霍尔编码器也是增量式编码器中的一种,有A、B相两相输出,与文章开篇的那种不同,但理解起来是一样的道理。
接下来我就讲讲自己对带编码器的直流减速电机的理解。
三、带编码器的直流减速电机详解
1、直流减速电机的概念
1)直流因为是直流电。
2)根据公式P=FV,功率相同条件下,力和速度成反比。
2)编码器主要用于测速。
(图片更加形象生动)
2、如何运用编码器进行测速
在带霍尔传感器的直流电机的条件下,电机转动一圈,通过霍尔传感器的A、B两相输出一定数量的脉冲,我们可以根据一定时间内的脉冲数计算出电机的瞬时速度。
记脉冲数有两种方式:
1) 我们通过定时器的输入捕获或者GPIO引脚的外部中断来检测边沿变化,以此来检测脉冲数,但是会有毛刺,也就是错误的脉冲信号。
2) 我们以stm32芯片作为主控,利用32的定时器外设的输入捕获功能,配置相关输入通道为编码器接口模式,就可以进行脉冲数的计数。
在第二种方式中,错误的脉冲信号会被输入滤波器过滤掉,所以更推荐也更常用。
注意点如下:
1) 通用、高级定时器具有输入捕获功能,基本定时器没有。
2) 在高级、通用定时器中不全都有编码器接口模式,只有TIM1-5、8具有编码器接口模式。(以STM32F103ZET6为例)
3) 一个定时器有四个独立的输入通道CH1-4,但只有CHI,CH2,也就是TI1、TI2,能作为编码器接口的输入通道。所以,用多少个霍尔编码器,就需要多少个定时器。
4) 编码器模式下,定时器可以理解为计数器。
根据上图,编码器的A、B两相通过接线,接到定时器输入通道1、2对应的GPIO引脚上即可,这样,电机转动,就会通过编码器产生连续的脉冲输入到T1,T2中。
接下来需要详细分析一下如何利用编码器接口模式进行测速的。
首先我们应该理解脉冲计数的原理。对于计数,有三种模式,
1) 计数器在T1输出的脉冲的上升沿或下降沿计数。
2) 计数器在T2输出的脉冲的上升沿或下降沿计数。
3) 计数器在T1和T2的输出脉冲的上升沿与下降沿计数。
但我们经常用第三种模式,因为提高了采样精度。具体的解释,可以看这个大佬的解释,好理解且实用博文链接
三种模式以及相关的计数方向如下表。
这里我先对这个表进行分析,便于理解。先分析表格。以仅在T1计数为例,相对应信号的电平就是指TI2是高电平还是低电平。TI1FP1信号指TI1的脉冲,然后脉冲分为上升沿与下降沿两种,同理可理解相应的表中的TI1FP2。
表中第一行,可以理解为在TI2上的脉冲为高电平时,如果TI1的脉冲为上升沿,则计数器+1,;如果TI1的脉冲为下降沿,则计数器-1。对于TI2的脉冲不进行计数,因为已经使用过其高低电平了,并且规定了只对TI1计数了。
其他行亦是这个道理。
下面这个表便是第三种模式下,计数器的工作过程,从图中我们可以知道,可以通过计数器是递增还是递减,来判断电机的正反转。而且可以看到,毛刺没过滤了,计数器碰到没有计数。
到这里想必对原理已经有较为清楚的理解了。
接下来,我们需要理解在编码过程中两个重要的参数。很重要!!!
1)重装载值
2)预分频系数
这两个系数在定时、PWM输出中都被运用到,且具有不同的含义,但这里我就详细讲一下在编码器计数中如何设定。
重装载值:
这里我们需要明白一点,电机转一圈输出的脉冲数是固定的,一般为360,具体的看情况,速度快了,也就可以理解为相同时间,转的圈数增多了。
遇到一个脉冲,计数器计数一次,当计数达到重装载值,就会产生溢出中断,计数器并清零重新计数,所以重装载值就是计数器脉冲计数的最大值。
预分频系数
原先定时器用作定时或者PWM输出,我们的预分频系数分的是32芯片的内部时钟,就是这个公式:CK_CNT=TIMxCLK/(PSC+1),这里的PSC就是预分频系数。但定时器用作编码器测速,这时时钟是外部时钟,也就是编码器采集的电机产生的脉冲。
预分频分的就是外部时钟的频率。
举个例子,如果你预分频系数为2,假设电机旋转一圈产生100个脉冲,则此时你单片机只能记录50个脉冲。
这两个因素应该理解的差不多了。
3、脉冲数转变成速度值方法
1、那我们如何计算转速呢,设重装载值为ARR,预分频系数置PSC,电机线数为360
那么电机转一圈,编码器采集到360*4 / PSC 个脉冲,因为预分频系数为PSC。再用定时器定时m秒,记录m秒内溢出的次数为n次,得到速度为 v=( n * ARR+当前的计数值) / (360 * 4 / PSC )/ m 。
总的来说,就是算每秒转了几圈。
2、预分频系数设置为 0 ,自动重装载值设为65535。然后在周期中断里每次去读取TIMx->CNT的值,并清零,以便于下一次读取。之所以在周期中断中,是因为我们读取的CNT的值相当于当前编码器的角度信息,以5ms的时间周期去读取,就可以看做相应时间编码器的变化,类似于测的效果。
下面这篇这篇博文可以作为借鉴。
博文链接
4、程序代码
第一种测速方式为例。
这段测速的思路引自这位大佬,具体文章和代码见下面链接。
测速方式一原文
int Encoder_Timer_Overflow; //编码器溢出次数(每389*4溢出一次)
u16 Previous_Count; //上次TIM3->CNT的值
void TIM3_Encode_init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; //GPIOA6和GPIOA7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //浮空
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA6和PA7
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3); //GPIOA6复用为定时器3通道1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM3); //GPIOA7复用为定时器3通道2
TIM_TimeBaseStructure.TIM_Period = arr; //(编码器线数-1)*4 四倍频原理
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分频因子,不分频
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //初始化TIM3
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //选择输入端IC1映射到TI1上
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; //映射到TI1上
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter =6; //配置输入滤波器
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel=TIM_Channel_2; //选择输入端IC2映射到TI2上
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI; //映射到TI2上
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter=6; //配置输入滤波器
TIM_ICInit(TIM3,&TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising );//编码器配置(定时器、编码模式、上升沿、上升沿)
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断分组配置
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02; //响应优先级2
NVIC_Init(&NVIC_InitStructure); //配置定时器3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
}
void TIM3_IRQHandler(void) //定时器3中断服务函数
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
Encoder_Timer_Overflow++;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
u32 Read_Encoder(void)
{
u32 Count; //一段时间内转过的脉冲数
u16 Current_Count; //当前TIM3->CNT的值
u16 Enc_Timer_Overflow_one;
Enc_Timer_Overflow_one=Encoder_Timer_Overflow;
Current_Count = TIM_GetCounter(TIM3); //获得当前TIM3->CNT的值
Encoder_Timer_Overflow=0; //清零,方便下次计算
if((TIM3->CR1&0x0010) == 0x0010) //如果反转
Count = (u32)((Enc_Timer_Overflow_one)* -1*(4*ENCODER_PPR) - (Current_Count - Previous_Count)); //计算出一个时间转过的脉冲数
else //如果正转
Count = (u32)(Current_Count - Previous_Count + (Enc_Timer_Overflow_one) * (4*ENCODER_PPR)); //计算出一个时间转过的脉冲数
Previous_Count = Current_Count;
return(Count);
}
//中间省略定时5的配置
void TIM5_IRQHandler(void) //定时器5中断服务函数
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
{
encode=Read_Encoder(); //获得编码器的值
printf("编码器脉冲数为:%d\r\n",encode);
speed=encode/390.0f/4.0f/0.05f;
printf("电机转速为:%f\r\n",speed);
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
总结
文章有些冗长,笔者我想尽量将各方面解释具体到位,不妥之处或者有疑问,可以在评论区留言,我会及时回复的。
更多推荐
所有评论(0)