开发板型号为MSP432P401r
今日得以继续我的MSP432电赛速通之路,本篇使用MSP432编程学习霍尔编码器M/T公式法测速概念,最终实现用外部中断方式测得小车行走路程,文章学习讲解原理、附上实例实践、附上关键代码、附上整体测试工程供下载测试使用。
这是一个装有13线霍尔编码器的电机,今日我们将学会如何使用它来进行测距离与测速概念:
学习之前需要确保自己 已经掌握以下有关MSP432的知识:
TB6612驱动电机、串口通信、定时器捕获、定时器配置PWM输出等
如果有关TB6612驱动电机怎么写还是不熟练,可以看我这篇文章:
【MSP432电机驱动学习—上篇】TB6612带稳压电机驱动模块、MG310电机、霍尔编码器_NULL指向我的博客-CSDN博客
如果串口通信不熟悉,可以看这篇:
MSP432学习笔记10:串口接收字符串命令并执行任务_NULL指向我的博客-CSDN博客
MSP432学习笔记3:串口通信初步配置收发_NULL指向我的博客-CSDN博客
如果定时器捕获的基础又忘了,可以看这篇:
MSP432学习笔记9:定时器A-----捕获_NULL指向我的博客-CSDN博客
如果都学习过了,比较熟悉了,我们就接着往下进行学习:
编码器及原理简介:
测速测正反转原理简介:
编码器是一种用来测量机器旋转或位移的传感器,能够测量机械部件在旋转或直线运动时的位移信息或速度等信息。
电机旋转时,编码器的码盘与电机同轴旋转
霍尔编码器圆盘上分布有磁极,当圆盘随电机主轴转动时,会输出两路相位差90°的方波,用这两路方波可测出电机的转速和转向。
霍尔编码器一般是13线的,就是转一圈每项会输出13个脉冲,这个精度基本能够满足大部分使用场景的要求,其次要结合电机减速比来计算,进行相应的乘计算得出车轮子转一圈,编码器会输出多少脉冲,比如13线霍尔编码器装载于减速比为20的电机上,那车轮转一圈,霍尔编码器的脉冲数就是13*20=260个。
从该图中我们发现,这个连接了编码器的电机,共有六条接线,有俩条是电机的正负接线,有俩条是编码器的地线,另外俩条线称为:编码器B相与编码器A相的线便是我们需要深入学习的编码器信号输出脚,我们暂且从下文开始分别称他们为E1B与E1A :
E1A和E1B是13线霍尔编码器的两个输出信号,用于输出轴旋转方向和位置信息。
E1A是正交方波信号,当轴逆时针旋转时,E1A会先于E1B产生信号变化,用于判断旋转方向。
E1B是正交方波信号,当轴顺时针旋转时,E1B会先于E1A产生信号变化,用于判断旋转方向。
两个信号数量相同,相位差90度,可以通过对信号的计数和相位差计算来确定轴的位置。
编码器相关参数了解:
霍尔编码器分辨率(PPR):
霍尔编码器的分辨率是指它所能检测到的旋转或线性位移量的最小变化量。
分辨率通常取决于以下几个因素:
1. 传感器本身的特性:
霍尔编码器使用霍尔效应传感器或磁电阻传感器来测量磁场变化,这些传感器的特性会直接影响分辨率。不同类型的传感器具有不同的灵敏度和噪声水平,因此会对分辨率产生影响。
2. 磁场的强度和分布:
霍尔编码器的分辨率还受到磁场的强度和分布的影响。较强的磁场可以提高编码器的灵敏度,从而提高分辨率。同时,磁场的均匀性和稳定性也对分辨率产生影响。
3. 编码器的工作频率和采样率:
编码器的工作频率和采样率决定了对旋转或位移变化的采样频率。较高的工作频率和采样率可以提高分辨率,但也会增加系统的复杂性和成本。
4. 信号处理和解码算法:
最后,编码器所采用的信号处理和解码算法也会影响分辨率。一些高级的算法可以有效地提高分辨率,并处理传感器噪声和非线性等问题。
5.其他因素:
机械传动装置的精度、机械松动等。
霍尔编码器精度:
霍尔编码器的精度通常是以位/转或弧度/转来衡量的。具体的精度取决于编码器的设计和制造质量。通常情况下,高精度的霍尔编码器的精度会比较高,误差较小。
对于位/转的精度,计算公式为:
精度 = (编码器所能输出的位数) / (电机输出轴每转的脉冲数)
编码器倍频:
某编码器有N个筛格\线,理论上电机带动转一圈,只能输出N个信号,倍频可以使这个信号变为原来的n倍。
这个倍频不是体现在硬件电路上的,而是体现在编程捕获信号处理上的:
编码器输出的脉冲一般占空比都是50%的矩形方波,A相通道与B相通道的相位差为90°
只用通道A,且只捕获上升沿,即为1倍频:
只用通道A,且捕获上升沿与下降沿,即为2倍频:
即用通道A又用通道B,且都是上升下降沿捕获,就是4倍频:
编码器相关数学计算:
首先看 购买的电机与编码器的电气参数:
由图可知,我购买的是260线霍尔编码器,车轮转动一圈会输出260个脉冲,4倍频后就是1040
最终推荐使用公式三M/T综合法,前俩种用来递进式理解公式三的M/T综合法原理:
M法测速计算公式1——高速:
这是建立在电机运转高速,脉冲频率相当高的情况下使用,此时脉冲的俩个上升沿之间间隔时间极其短,不方便测量了拿出来作数学运算,会有较大误差。
因此采用在采样时间内计数上升沿个数,然后作计算的方式
假设:
1.编码器单圈总脉冲数为C
2.统计时间为T,(一般为1S)
3.T时间内计数捕获的脉冲数位M0
4.需要求得的转速为n(圈/秒)
这个公式在电机高速运转下,T时间内捕获的脉冲数M0足够大时,计算相对简便精确,
但在低速状态下误差十分大,因为M0基数小,是个位数级别的了,少一个脉冲计数,计算结果就会有很大误差,下图举例为:
上升沿捕获,刚好最大误差少记数一个脉冲的情况:
如果是上升沿与下降沿捕获计数(2倍频),那最大误差就可能少记数达到2个脉冲了
双通道4倍频捕获计数,可以让脉冲数乘4倍捕获,也许可以提高公式1在低速下的准确性,但也不是根本上解决问题的办法
T法测速计算公式2.1——低速:
此公式适用于电机运转低速,脉冲频率低的时候使用,此时脉冲的俩个上升沿之间时间间隔比较长,可以拿来作数学计算,算出脉冲的频率了:
假设1:
1.编码器单圈总脉冲数为C
2.捕获的俩个脉冲间隔时间为TE
下图公式表示求出1S内有多少个编码器周期,就表示捕获计数到了多少次上升沿,借此计算转速
T法测速计算公式2.2——低速:
此公式也是用来计算电机低速时的转速的,其实这个公式最终化简后与2.1是一样的,只不过是同一个公式,俩种理解罢了。
这里的理解是,假定一个固定频率为F0的高速脉冲,测量编码器周期内高速脉冲上升沿个数,以求得转速。
M\T 综合测速公式3——兼顾:
这种方法综合了M 法和T 法各自的优势,既测量编码器脉冲数又测量一定时间内的高频脉冲数。
在一个相对固定的采样时间内,假设:
1.编码器单圈总脉冲数C(常数)
2.编码器脉冲数产生 M0个 (测量值)
3.固定频率为F0的高频脉冲(常数)
4.采样时间内高频脉冲计数值为M1
由于M/T 法公式中的 F0和C 是常数,所以转速n 就只受 M0和 M1的影响。
1、高速时, M0增大, M1减小,相当于M 法
2、低速时, M1增大, M0减小,相当于T 法。
使用示波器观察编码器的输出波形:
编程说明:
首先我们编写函数让TB6612能够驱动起来,包含分派引脚,组织接线,初始化引脚,初始化定时器,编写加减速、正反转函数等,这些在上篇都已经讲过了,此处不多加赘述,我直接贴代码透露引脚分配与函数组成,此处就只贴出TB6612.h文件中内容了,其余代码会在后续完整贴出,这里不影响理解就行:
介绍代码实现功能:
以下为上位机的有效命令,通过串口A0发送给MSP432,MSP432接收后会做相应控制:
要使电机加速:上位机发送ADD\r\n\0
要使电机减速:上位机发送REDUCE\r\n\0
要使电机正转:上位机发送COROTATION\r\n\0
要使电机反转:上位机发送REVERSE\r\n\0
要使电机停转:上位机发送STOP\r\n\0以下为上位机发送其余错误指令后MSP432的回应:
ERROR!\r\n
首先写好宏定义,引脚位带操作,声明函数等:
#include <ti/devices/msp432p4xx/driverlib/driverlib.h>
//定义定时器中断需要的初始化变量
//定义调整占空比的变量 占空比 = PWM_DIVA/CCR0
//占空比越大电机转速也越大
#define CLKDIV 48 //时钟分频
#define CCR0 99 //时基(改变此项频率会变)
#define CCR1_MIN 3 //PWMA最小值
#define CCR1_MAX 99 //PWMA最大值
#define DIV_PWM 3 //PWMA加减分度值,改变此项能改变PWMA的加减分度
// 位带操作
//定义引脚名称,STBY引脚接5V或者3.3V皆可
#define AIN1 BITBAND_PERI(P4OUT,0)
#define AIN2 BITBAND_PERI(P4OUT,1)
#define PWMA BITBAND_PERI(P7OUT,6)
extern uint16_t PWM_DIVA;//改变占空比作用的变量
void inint_TB6612(void);
void speedup_A(void); //加速
void speeddown_A(void); //减速
void set_A(uint16_t i); //设置正反转
然后就是串口通信相关编程,基本思路是:定义好串口命令,填上对应逻辑即可:
定义串口用到的变量数组:
#include "string.h"
void handle_uart(void);
//定义串口程序需要用到的变量
char USART0_save[20]; //存字符串命令的数组
char USART0_xb=0; //帮助数组下标位移
char USART0_flag=0; //接收完成标志
//定义命令字符串,用于与接收进行比较 ,不可修改
const char str1_order[]="ADD\r\n\0"; //加占空比
const char str2_order[]="REDUCE\r\n\0"; //减占空比
const char str3_order[]="COROTATION\r\n\0"; //正转
const char str4_order[]="REVERSE\r\n\0"; //反转
const char str5_order[]="STOP\r\n\0"; //停转
char error_receive[]="ERROR!\r\n"; //错误命令
串口接收中断的编写处理:
//串口服务函数,串口1接收命令,存在数组中
void EUSCIA0_IRQHandler(void)
{
uint32_t status = UART_getEnabledInterruptStatus(EUSCI_A0_BASE);
if(status & EUSCI_A_UART_RECEIVE_INTERRUPT_FLAG) //接收中断
{
USART0_save[USART0_xb++]=MAP_UART_receiveData(EUSCI_A0_BASE);
if(USART0_xb== 20){USART0_xb=0; } //下标最大不超过20
if(USART0_save[USART0_xb-1]=='\0'){USART0_flag=1;} //命令以\0结尾
}
}
中断接收标志处理函数的编写,整体函数其实就是一个由众多if()语句组成的判断处理函数,一进入就是一个最大的前提:if(USART0_flag==1),即串口接收中断服务函数确认接收到了完整字符串并存在了数组之中,将USART0_flag标志置1,此时就是对数组中字符串命令进行判定操作的时机,然后就是五条命令的判断:先判断命令长度,再根据其判断是否为接受到的命令字符串,根据情况发送不同回应,最后别忘了清空数组与数组下标索引(变量)。
此函数直接放于主函数while(1)循环中即可,随时判断进入:
//串口中断处理函数:
void handle_uart(void)
{
if(USART0_flag==1)
{
printf("%s",USART0_save); //先重复接受到的字符串
USART0_flag=0; //再清理标志位
//先判断命令长度,再根据其判断是否为接受到的命令字符串,根据情况发送不同回应
if(USART0_xb==6)
{
if(strncmp(USART0_save,str1_order,6)==0) //接收到命令1,执行加速(加占空比)
{printf("PWM_DIVA=%d\r\n",PWM_DIVA);speedup_A();}
else
printf("%s",error_receive);
}
else if(USART0_xb==9)
{
if(strncmp(USART0_save,str2_order,9)==0) //接收到命令2,执行减速(减占空比)
{ printf("PWM_DIVA=%d\r\n",PWM_DIVA);speeddown_A();}
else
printf("%s",error_receive);
}
else if(USART0_xb==13) //接收到命令3,执行正转
{
if(strncmp(USART0_save,str3_order,13)==0)
{ printf("SET COROTATION\r\n");set_A(1);}
else
printf("%s",error_receive);
}
else if(USART0_xb==10)
{
if(strncmp(USART0_save,str4_order,10)==0) //接收到命令4,执行反转
{ printf("SET REVERSE\r\n");set_A(2);}
else
printf("%s",error_receive);
}
else if(USART0_xb==7)
{
if(strncmp(USART0_save,str5_order,7)==0) //接收到命令5,执行停转
{ printf("SET STOP\r\n");set_A(0);}
else
printf("%s",error_receive);
}
else
printf("%s",error_receive);
printf("\r\n");
memset(USART0_save,0,sizeof(USART0_save)); //处理完命令别忘了将数组清零,以便接收下个命令
USART0_xb=0; //重置数组下标
}
}
示波器测量波形视频演示:
示波器测电机编码器输出信号视频
此阶段完整工程下载地址:
https://download.csdn.net/download/qq_64257614/87965347?spm=1001.2014.3001.5503
编码器测距离在MSP432的编程实战:
在经过前面的示波器显示波形模块的学习后,我们对编码器在电机不同状态下的输出信号有了更感性的认知了,但我们可以循序渐进,首先尝试使用编码器测距离,这个不涉及时间的概念,因此相对逻辑也更简单些,代码也可顺延上个阶段的代码进行继续编程:
介绍代码实现功能:
以下为上位机的有效命令,通过串口A0发送给MSP432,MSP432接收后会做相应控制:
要使电机加速:上位机发送ADD\r\n\0
要使电机减速:上位机发送REDUCE\r\n\0
要使电机正转:上位机发送COROTATION\r\n\0
要使电机反转:上位机发送REVERSE\r\n\0
要使电机停转:上位机发送STOP\r\n\0以下为上位机发送其余错误指令后MSP432的回应:
ERROR!\r\n
公式计算问题的解决:
已知我使用的霍尔编码器的线数是13,电机的减速比是20,因此我的电机转一圈,理论上应该有13*20=260个脉冲信号。
当我接受到N个脉冲时,我可以根据N与260的比值了解到轮子转动了究竟多少圈,据此可以计算轮子前进的路程X。
其中还有一个常理需要实地测量计算出来,就是轮子的周长,这个值有俩种获取途径:
1.固守圆规:测量轮子半径直径等,通过圆周长数学公式进行计算得出圆周长C
2.化圆为线:通过使用软质地尺绕轮一圈进行最简单粗暴的直接测量出圆周长C
最后就是路程X的计算公式了,该公式其余都是常量,只有N(脉冲信号个数)需要测量:
X=(N/260)*C
编程问题解决:
编程上,我们只需要弄明白如何获取上升沿或者下降沿次数即可,这里有俩种思路,分别占用不同的单片机资源:
1.占用通用引脚资源:外部中断法:配置一个通用普通引脚为外部中断模式,设置好上升沿或下降沿触发的方式,然后在中断服务部分中使用变量计数中断次数,达到计数脉冲数的目的。
2.占用定时器资源:定时器捕获法:定时器有捕获的模块,我们也可用定时器捕获上升沿、下降沿等,达到计数脉冲的目的。
此处演示最最简单的外部中断法的实现:
定义好需要用到的变量、引脚:
//外部中断测速
#define INT_CAP BITBAND_PERI(P4OUT,2)
extern uint32_t CAPTURE; //外部中断次数记录
extern uint32_t DISTANCE; //行驶总路程长度单位cm
uint16_t uart_cnt=0; //串口计时标志
uint16_t uart_flag=0; //串口发送标志
uint16_t Sta; //轮子正反转情况1正转0反转
配置好需要的外部中断引脚:
void Interrupt_CAP_inint() //外部中断初始化
{
//1.配置GPIO输入
MAP_GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P4,GPIO_PIN2);
//2.清除中断标志位
MAP_GPIO_clearInterruptFlag(GPIO_PORT_P4, GPIO_PIN2);
//3.配置触发方式 上升沿
GPIO_interruptEdgeSelect(GPIO_PORT_P4,GPIO_PIN2,GPIO_LOW_TO_HIGH_TRANSITION);
//4.开启外部中断
GPIO_enableInterrupt(GPIO_PORT_P4,GPIO_PIN2);
//5.开启端口中断
Interrupt_enableInterrupt(INT_PORT4);
//6.开启总中断
Interrupt_enableMaster();
}
编写外部中断服务函数:
//编写GPIO ISR(外部中断中断服务函数)
void PORT4_IRQHandler(void)
{
uint16_t status;
status=GPIO_getEnabledInterruptStatus(GPIO_PORT_P4);
GPIO_clearInterruptFlag(GPIO_PORT_P4,status);
GPIO_clearInterruptFlag(GPIO_PORT_P4,status);
if(status & GPIO_PIN2)//对应P4^2中断
{
if(Sta==1){CAPTURE++;} //正转就增加
if(Sta==0){CAPTURE--;} //反转就减小
}
}
然后为了使用串口向上位机报告,我们需要初始化一通用定时器来定时,关键代码如下:
此处我引用了他人库中的函数,初始化使用了一个周期为0.05s的定时器
#include "timA.h"
TimA0_Int_Init(37499,64); //初始化一个周期为0.05s的通用定时器
//通用定时器中断中断服务函数
void TA0_0_IRQHandler(void)
{
MAP_Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_0);
if(++uart_cnt==40) //每2秒打印一次总运行路程
{uart_cnt=0;uart_flag=1;}
}
此段处理发送的函数在主函数while(1)循环直接调用即可。
void handle_printf_distance(void)
{
if(uart_flag==1)
{
uart_flag=0;
DISTANCE=(CAPTURE*15/260);
printf("CAPTURE=%d\r\n",CAPTURE);
printf("DISTANCE=%d\r\n",DISTANCE);//精确到小数点后2位输出距离
}
}
视频演示:
最终下载后能每过2S向上位机汇报一次总脉冲与总路程,电机正转加厘米数,反转就减:
可以从我的视频中发现有个小BUG,那就是当反转时会减去CAPTURE捕获值,但没考虑减到0以下的情况,因此对数据处理在0以下的情况就不是很好,产生了虚高的数。
编码器测距离
测试工程下载:
https://download.csdn.net/download/qq_64257614/87966413?spm=1001.2014.3001.5503
编码器M/T公式测速在MSP432的编程思路:
使用MSP432捕获,以及M/T公式法计算速度:
此处沿用上个标题中的串口控制程序来继续编程:
对于编码器A\B相的信号捕获,我们其实有俩种思路,一种是复用引脚为中断,在中断服务函数中记录次数,另一种就是用定时器捕获进行编码器的信号捕获。
我们一般推荐使用定时器捕获编码器信号,外部中断的方式如果次数很多的话,可能会占用相当的MCU资源,况且定时器写代码捕获编码器信号契合度也比较高,更简洁。
本文在此部分点到即止,不展示代码。