本文大纲
- 编码器的定义、作用与分类
- 编码器的工作原理
- 代码实现直流电机编码器计数
欢迎移步我的哔哩哔哩账号观看相关内容的讲解视频。
本篇文章及视频参考了大量网络作品,包括csdn,知乎,哔哩哔哩等,不具有版权效应。
编码器的定义、作用与分类
在实际操作中,为了保证电机驱动轮转动的速度和方向符合预期,我们需要对电机的转动速度与方向进行精确控制。
而编码器的作用是,电机每转过一个角度,编码器获得一个脉冲信号,从而获得电机的实际转动参数(速度与方向)。
编码器主要分为如下几类,可以先看一下留个印象,对编码器分类和原理的理解对后续的实践影响不大。
我们在本篇blog及相关视频中采用增量式霍尔磁编码器进行演示。
- 增量式:将位移转化为周期性电信号,继而转化成技术脉冲,用脉冲个数表示位移大小。
- 霍尔磁:通过磁电转换将输出轴上的机械几何位移量转换成脉冲或数字量。
霍尔编码器由霍尔码盘和霍尔元件组成。霍尔码盘是在一定直径的圆板上等分的不同的磁极,与电动机同轴。电动机旋转时,霍尔元件通过切割磁感线产生电流,输出若干脉冲信号,为判断电机转动方向,一般输出两组存在一定相位差的方波信号。
编译器的工作原理
我们可以简单地把编译器工作的流程表示为如下四个部分:
- 编码器生成脉冲信号
- 单片机捕获脉冲信号
- 公式计算速度
- 输出速度
同时补充一些后续实践可能用到的参数概念:
- 分辨率:指编码器能够分辨的最小单位。对于增量式编码器,其分辨率表示为编码器转轴旋转一圈所产生的脉冲数,即脉冲数/转(Pulse Per Revolution或 PPR)。
- 精度精度:是指编码器每个读数与转轴实际位置间的最大误差,通常用角度、角分或角秒来表示。
- 最大响应频率:编码器每秒输出的脉冲数,单位是Hz。计算公式为:最大响应频率=分辨率*轴转速/60。
- 信号输出形式:对于增量式编码器,每个通道的信号独立输出,输出电路形式通常有集电极开路输出、推挽输出、差分输出等。
下面是编码器信号采集方式:
单片机 |
采集方式 |
例子 |
自带编码器接口 |
输入捕获法(直接使用硬件计数) |
STM32 |
没有编码器接口 |
外部中断法(通过外部中断读取在跳变沿触发中断) |
Arduino |
测速方法
本篇教程中我们采用M法测量转速。前文我们提到,电机每转过一个角度,编码器获得一个脉冲信号,因此,单位时间内脉冲的个数可以用来表示这段时间内的平均速度,我们可以通过计量这个数值来估算平均速度,这就是M法测速,原理如图所示。
我们可以给出如下公式:
n——平均转速(r/min);
T——测速采样时间(s);
m——T时间内测得的编码器脉冲个数;
N——编码器每转脉冲数。
代码实现直流电机编码器计数
我们现在需要解决的问题是,如何用代码实现定时接收编码器的脉冲并得到对应的参数,即T时间内测得的的编码器脉冲个数m。在单片机相关代码的编写中,我们一般通过外部中断或输入捕获实现,这里我先给出外部中断的具体方法,以后有时间会讲解后者。
外部中断,是单片机实时地处理外部事件的一种内部机制。当外部事件发生时,单片机的中断系统将迫使CPU暂停正在执行的程序,转而去进行中断事件的处理,中断完毕后返回被中断的程序处,继续执行下去。
这里引用江协科技STM32单片机EXTI外部中断教学视频的一例比较形象的比喻。医院看病一般是要排队的,先挂号的先看病,后挂号的后看病,这就对应了单片机在不中断情况下按顺序运行程序。但是如果有情况紧急的病人出现,医院就会暂停当前的问诊,优先去治疗他。EXTI外部中断就是这种“情况紧急”,它会迫使CPU终止当前程序(一般是main()),处理完中断事件后才能回到当时中断的位置继续执行。
一般格式如下
1 2 3 4 5 6 7 8 9
| int mian(void) { while(1){ } } void EXTI0_IRQHandler(void) { }
|
在掌握了EXTI相关原理之后,我们可以开始编写函数来配置外部中断。
外部中断配置函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
int Encoder_L_EXTI=0; int Encoder_R_EXTI=0; void Encoder_EXTIX_Init(void) { 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); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15); 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); 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); }
|
配置环境是单片机使用过程中比较复杂且常见的步骤,这里我们不妨先将这冗长的函数封装起来,不求甚解地进入下一步骤。
外部中断服务函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| void EXTI15_10_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line12) != RESET) { EXTI_ClearITPendingBit(EXTI_Line12); if(PBin(12)==0) { if(PBin(13)==0) Encoder_L_EXTI++; else Encoder_L_EXTI--; } else { if(PBin(13)==1) Encoder_L_EXTI++; else Encoder_L_EXTI--; } } if(EXTI_GetITStatus(EXTI_Line13) != RESET) { EXTI_ClearITPendingBit(EXTI_Line13); if(PBin(13)==0) { if(PBin(12)==1) Encoder_L_EXTI++; else Encoder_L_EXTI--; } else { if(PBin(12)==0) Encoder_L_EXTI++; else Encoder_L_EXTI--; } } if(EXTI_GetITStatus(EXTI_Line14) != RESET) { EXTI_ClearITPendingBit(EXTI_Line14); if(PBin(14)==0) { if(PBin(15)==0) Encoder_R_EXTI++; else Encoder_R_EXTI--; } else { if(PBin(15)==1) Encoder_R_EXTI++; else Encoder_R_EXTI--; } } if(EXTI_GetITStatus(EXTI_Line15) != RESET) { EXTI_ClearITPendingBit(EXTI_Line15); if(PBin(15)==0) { if(PBin(14)==1) Encoder_R_EXTI++; else Encoder_R_EXTI--; } else { if(PBin(14)==0) Encoder_R_EXTI++; else Encoder_R_EXTI--; } } }
|
随后我们编写脉冲获取函数。
脉冲获取函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
int Read_Encoder(u8 TIMX) { int Encoder_TIM; switch(TIMX) { case 1:Encoder_TIM=Encoder_L_EXTI; Encoder_L_EXTI=0; break; case 2:Encoder_TIM=Encoder_R_EXTI; Encoder_R_EXTI=0; break; case 3:Encoder_TIM=TIM3 -> CNT; TIM3 -> CNT=0;break; case 4:Encoder_TIM=TIM4 -> CNT; TIM4 -> CNT=0;break; default:Encoder_TIM=0;break; } return Encoder_TIM; }
extern int Current_LN,Current_RN; void Get_SpeedNow(int* CurrentVelcity_L,int* CurrentVelcity_R) { Current_LN = Read_Encoder(1); Current_RN = Read_Encoder(2); *CurrentVelcity_L = Current_LN * MPN * 100; *CurrentVelcity_R = Current_RN * MPN * 100; printf("The Current Left Wheel Velocity is: %d mm/s.\r\n",*CurrentVelcity_L); printf("The Current Right Wheel Velocity is: %d mm/s.\r\n",*CurrentVelcity_R); }
|