01 前言
如果我们想对电机进行速度或者转角的精确控制,需要使用到很多算法,比如非常经典的PID控制算法,或者一些只能算法,但这些算法都需要传感器来提供转速或转角的反馈值,对于电机来说,编码器是非常流行并且实用的电机配套传感器,本文使用STM32F103C8T6+L298N+MG513P30电机进行直流电机的编码器测速。
02 编码器原理
1.分类
光电式编码器的精准度比霍尔式要高,但是由于它需要红外线发生器和接收器,相对来说造价要贵一些。现在我们比较常用的是霍尔式增量编码器,有很多电机都会自带编码器。
2.测速方法分类
(1)M法测速
编码器输出的脉冲个数代表了位置,那么单位时间里的脉冲个数表示这段时间里的平均速度。因此,我们可以通过计量单位时间脉冲个数即可以估算出平均速度,称为M法测速(测脉冲个数)测速原理如图所示。
例如,若编码器每转产生N个脉冲,在T时间(单位s)产生m个脉冲,那么平均转速如下式所示:
式中 n——平均转速(r/min);
T——测速采样时间(s);
m——T时间内测得的编码器脉冲个数;
N——编码器每转脉冲数。
(2)T法测速
若用M法测速,在记录时间短、速度低的时候,只能记录几个脉冲,则分辨率降低。针对该问题,目前解决方法为:可以采用输出码盘脉冲为一个时间间隔,然后用计数器记录在这段时间里高速脉冲源发出的脉冲数。即通过采集到脉冲源脉冲数来计量编码器两个脉冲时间间隔,从而估算出速度,称为T法测速(测脉冲周期),测速原理图如图所示。
T法测速,利用编码器产生的脉冲用作门电路的触发信号;用已知频率f的时钟信号做输入。若控制门电路在编码器脉冲上升沿到来时开始导通,再次上升沿到来时关闭,即计数器只记录一个编码器脉冲周期内的时钟脉冲个数。若在编码器相邻脉冲之间记录的脉冲时钟个数为m,那么,可以计算两个编码器脉冲的时间间隔为m /f;若编码器每转有N个线脉冲输出,那么我们就知道编码器转过1/N转时需要时间m /f。据此,可计算与编码器同轴转速为公式所示。
式中 n ——平均转速(r/min);
f ——时钟脉冲频率(个/s);
m ——两个编码器脉冲之间的时钟脉冲个数;
N ——编码器每转脉冲数。
编码器一般会输出两路信号,分别称为A相和B相,它们相差90°,因此编码器也称为十字码盘,通过捕获两路输出信号可以测算出电机的转速和转向。
STM32使用编码器的方法有两种分别是外部中断法和输入捕获法,这两种方法都属于M法测速,两种方法比较来说外部中断法占用CPU资源较多,平时比较常用的是输入捕获法,但博主两种方法都调试出来了,因此记录下来跟大家分享一下。
03 外部中断法测速
对于外部中断的知识,各个讲STM32的教程都有,我就不过多赘述,外部中断的初始化都一样,主要是出发外部中断时需要进行的操作。
看这张图,正转方向是信号向右走,因为我们是同时捕获两路信号,有以下几种情况:
然后我们设置两个变量用来存储捕获的脉冲数,每捕获到一次脉冲信号根据上表进行判断,正转时使其加1;反转时使其减1;然后配置一个定时器,每隔一段时间反馈一次测速值。
1.外部中断配置
先编写一个函数初始化外部中断,使用PB12-15引脚复用为外部中断输入,外部中断配置步骤如下:
1.端口初始化:RCC_APB2PeriphClockCmd()、GPIO_Init()
2.使能复用功能时钟:RCC_APB2PeriphClockCmd()
3.设置IO口与中断线的映射关系:GPIO_EXTILineConfig()
4.初始化线上中断:EXTI_Init()
5.配置中断分组:NVIC_Init()
完整函数如下:
/**************************************************************************
功能:应用外部中断方式采集编码器数据,使用M法测速反馈车轮实时速度
函数:Encoder_EXTIX_Init(void) EXTI15_10_IRQHandler(void)
作者:K.Fire
日期:2022.01.30
引脚:PB12(左轮A相) PB13(左轮B相) PB14(右轮A相) PB15(右轮B相)
参数:void
*************************************************************************/
int Encoder_L_EXTI=0;
int Encoder_R_EXTI=0;
void Encoder_EXTIX_Init(void)
{
//1.端口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 |GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//2.使能复用功能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
//3.设置IO口与中断线的映射关系
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15);
//4.初始化线上中断
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line12 | EXTI_Line13 | EXTI_Line14 | EXTI_Line15;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;//跳变沿触发
EXTI_Init(&EXTI_InitStruct);
//5.配置中断分组
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
}
2.外部中断服务函数
函数的判断逻辑与上表一致,外部中断捕获判断函数如下:
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line12) != RESET)//左轮A相 PB12
{
EXTI_ClearITPendingBit(EXTI_Line12); //清除LINE上的中断标志位
if(PBin(12)==0) //这里判断检测到的是否是下降沿
{
if(PBin(13)==0) Encoder_L_EXTI++;//B相的电平如果是低,电机就是正转加1
else Encoder_L_EXTI--;//否则就是反转减1
}
else //上升沿
{
if(PBin(13)==1) Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
else Encoder_L_EXTI--;//否则就是反转减1
}
}
if(EXTI_GetITStatus(EXTI_Line13) != RESET)//左轮B相 PB13
{
EXTI_ClearITPendingBit(EXTI_Line13); //清除LINE上的中断标志位
if(PBin(13)==0) //这里判断检测到的是否是下降沿
{
if(PBin(12)==1) Encoder_L_EXTI++;//B相的电平如果是高,电机就是正转加1
else Encoder_L_EXTI--;//否则就是反转减1
}
else //上升沿
{
if(PBin(12)==0) Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
else Encoder_L_EXTI--;//否则就是反转减1
}
}
if(EXTI_GetITStatus(EXTI_Line14) != RESET)//右轮A相 PB14
{
EXTI_ClearITPendingBit(EXTI_Line14); //清除LINE上的中断标志位
if(PBin(14)==0) //这里判断检测到的是否是下降沿
{
if(PBin(15)==0) Encoder_R_EXTI++;//B相的电平如果是低,电机就是正转加1
else Encoder_R_EXTI--;//否则就是反转减1
}
else //上升沿
{
if(PBin(15)==1) Encoder_R_EXTI++; //B相电平如果为高,电机就是正转加1
else Encoder_R_EXTI--;//否则就是反转减1
}
}
if(EXTI_GetITStatus(EXTI_Line15) != RESET)//右轮B相 PB15
{
EXTI_ClearITPendingBit(EXTI_Line15); //清除LINE上的中断标志位
if(PBin(15)==0) //这里判断检测到的是否是下降沿
{
if(PBin(14)==1) Encoder_R_EXTI++;//A相的电平如果是高,电机就是正转加1
else Encoder_R_EXTI--;//否则就是反转减1
}
else //上升沿
{
if(PBin(14)==0) Encoder_R_EXTI++; //A相电平如果为低,电机就是正转加1
else Encoder_R_EXTI--;//否则就是反转减1
}
}
}