资讯详情

【一文读懂】如何用编码器测速

文章目录

  • 前言
  • 一、AB相编码器计数原理
    • 四倍频
  • 二、使用一些参数
    • 编码器参数
    • 电机参数
    • 轮子参数
    • 计算
  • 三、代码如下(更符合原则的代码)


前言

详细说明编码器的测速原理和实现 参考csdn


一、AB相编码器计数原理

光电编码器适用于霍尔编码器和光栅 编码器样子 在这里插入图片描述 阻挡和脉冲通过形成高低电平 此图为B 提前 A 90° 相位差为90°波形称为正交波形 只有一个方波 只能测量位置和速度,不能知道方向 两个方波可以测量速度和方向 例: A下降沿触发 而B是低电平 判断反转 B下降沿触发 而A是低电平 判断正转

四倍频

还有一种用法 叫四倍频,是数 A相 和 B相 脉冲的上升和下降 四个信号可以用来测量速度来提高精度,因为一般只需要考虑A相上升或下降沿计数,这里有四个,所以称为四倍频率。 stm默认情况下,32编码器接口应为4倍: 这里的大佬,说 手动转一圈的脉冲数 n:1560 个; 13线编码器使用 和 1:120转速比的电机,计算一圈脉冲数是 131204 = 1560;其中4个应该是一个周期数的4个脉冲沿。以后再计算。

二、使用一些参数

编码器参数

编码器线数:线数是编码器的分辨率,即转动脉冲数 例:光电编码器:500ppr (500线) 霍尔编码器: 13ppr (13线)

电机参数

减速比 :输入速/输出速

一般减速比的表示方法是以1为分母,以:连接的输入转速与输出转速之比,如输入转速为1500r/min,输出转速为25r/min,其减速比为:i=60:1。

轮子参数

用直尺 或商家给出的参数 得到 轮子直径 :65mm = 0.065m

计算

例 :500线的编码器 电机减速比为30:1 也就是说 电机实际转30圈 但现实轮只转一圈 然后轮子转一圈 编码器获得的脉冲数为 500*30 = 15000 (个)

设 定时器设定时间为t,D为直径,则

e: 由程序得到 PI : 3.1415926 D : 0.065m n :15000 另定时500ms t = 0.5s 所以 speed = e * 0.00002722713 (m/s) 数据显示良好 speed =1000* e * 0.00002722713 (mm/s)

三、代码如下(更符合原则的代码)

未使用32编码器接口

Enconder.c:

#include "stm32f10x.h" // Device header //-32768 ~ 32767 编码器范围 定时器是16位 ///这里没用stm32自带编码器接口(TIM_EncoderInterfaceConfig) , 而是用原理写出来  int16_t Encoder_Count_Left;//带符号 int16_t Encoder_Count_Right;//带符号 // B接PB1 A接PB0  //A下降沿触发 而B是低电平 判断反转 //B下降沿触发 而A是低电平 判断正转  //左编码 E1B E1A // PB1 PB0 void Encoder_Init_Left(void) { 
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);    GPIO_InitTypeDef GPIO_InitStructure;  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //AFIO中断引脚选择 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//第0个线路拨到GPIOB上 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);// EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式 也有事件模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); } //单位时间内读取编码器计数 变化的值 int16_t Encoder_Get_Left(void) { 
          int16_t Temp; Temp = Encoder_Count_Left; Encoder_Count_Left = 0; return Temp; } //如果是9-10 中断函数 只需要将两个if并列的放在一个函数里就行了 //A下降沿触发 并且B是低电平 判断反转 void EXTI0_IRQHandler(void) { 
          if (EXTI_GetITStatus(EXTI_Line0) == SET) { 
          if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) { 
          Encoder_Count_Left --; } EXTI_ClearITPendingBit(EXTI_Line0); } } //B下降沿 A低电平 判断正转 void EXTI1_IRQHandler(void) { 
          if (EXTI_GetITStatus(EXTI_Line1) == SET) { 
          if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) { 
          Encoder_Count_Left ++; } EXTI_ClearITPendingBit(EXTI_Line1);//清楚标志位 } } //右编码 E2B E2A // PB11 PB10 void Encoder_Init_Right(void) { 
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //AFIO中断引脚选择 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);//第0个线路拨到GPIOB上 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11);// EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line10 | EXTI_Line11; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式 也有事件模式 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_Init(&NVIC_InitStructure); } //变化的值 int16_t Encoder_Get_Right(void) { 
          int16_t Temp; Temp = Encoder_Count_Right; Encoder_Count_Right = 0; return Temp; } //如果是10-15 中断函数 只需要将两个if并列的放在一个函数里就行了 //因为轮子是对称转 所以镜像 相反 //右编码 E2B E2A // PB11 PB10 //A下降沿触发 并且B是低电平 判断正转 void EXTI15_10_IRQHandler(void) { 
          if (EXTI_GetITStatus(EXTI_Line10) == SET) { 
          if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) { 
          Encoder_Count_Right ++; } EXTI_ClearITPendingBit(EXTI_Line10); } B下降沿 A低电平 判断反转 if (EXTI_GetITStatus(EXTI_Line11) == SET) { 
          if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0) { 
          Encoder_Count_Right --; } EXTI_ClearITPendingBit(EXTI_Line11);//清楚标志位 } } void Get_Motor_Speed(int *LSpeed,int *RSpeed) { 
          static int LWheelEncoderNow = 0; static int RWheelEncoderNow = 0; static int LWheelEncoderLast = 0; static int RWheelEncoderLast = 0; //记录本次左右编码器数据 LWheelEncoderNow += Encoder_Get_Left(); RWheelEncoderNow += Encoder_Get_Right(); //500ms测速 ( *1000 )单位改为mm/s 变化显示更清楚 *LSpeed = (LWheelEncoderNow - LWheelEncoderLast)* 1000*0.00002722713; *RSpeed = (RWheelEncoderNow - RWheelEncoderLast)* 1000*0.00002722713; //记录上次编码器数据 LWheelEncoderLast = LWheelEncoderNow; RWheelEncoderLast = RWheelEncoderNow; } /* static int 不管在函数内还是函数外,都作为一个全局变量可以保存它被修改以后的值。 而 int 则没有这一功能,只有作为全局变量时能保存修改。放在函数内部时,每次调用都用的是一个新的数。 */ 

Encoder.h

#ifndef __ENCODER_H
#define __ENCODER_H

//左编码 E1B E1A
// PB1 PB0
void Encoder_Init_Left(void);
int16_t Encoder_Get_Left(void);

//右编码 E2B E1A
// PB11 PB10
void Encoder_Init_Right(void);
int16_t Encoder_Get_Right(void);

void Get_Motor_Speed(int *LSpeed,int *RSpeed);


#endif

Timer.c

#include "stm32f10x.h" // Device header

//extern uint16_t Num; //引用其他文件(主函数)的Num变量

void TIM4_Timer_Init(u16 arr,u16 psc)
{ 
        
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//TIM2 APB1总线上的外设
	
	TIM_InternalClockConfig(TIM4); //选择内部时钟
	
	//CK_CNT_OV = CK_PSC/(PSC+1)/(ARR+1)
	//预分频给少点 自动重装给多点 以一个比较高的频率计比较多的数
	//预分频给多点 自动重装给少点 以一个低的频率计比较少的数
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //配置时基单元
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频 /1分频 影响不大
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseInitStructure.TIM_Period = arr; //周期 ARR自动重装器的值 10000 - 1
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;  //PSC预分频器的值 10k的计数频率 计10000的数 7200 - 1
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值 //高级定时器才有 这里通用计时器 不用
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
	
	
	TIM_ClearFlag(TIM4, TIM_FLAG_Update);//手动把更新中断标志位清除一下 防止 reset直接到1
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //使能中断 update 更新中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //优先级分组
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //3通道 //f10x.h 255行
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM4, ENABLE); //启动定时器
}

/* void TIM2_IRQHandler(void) //定时器2 的中断函数 { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)//看更新中断标志位 { Num++; TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } */

Timer.h

#ifndef __TIMER_H
#define __TIMER_H

void TIM4_Timer_Init(u16 arr,u16 psc);

#endif

main.c

#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Encoder.h"
#include "Timer.h"

uint8_t KeyNum;
int8_t Speed; 
int16_t NumL;
int16_t NumR;
int LSpeedNow = 0;
int RSpeedNow = 0;

int main(void)
{ 
        
	OLED_Init();
	Motor_Init_Left();
	Motor_Init_Right();
	Encoder_Init_Left();
	Encoder_Init_Right();
	TIM4_Timer_Init(5000-1,7200-1);//500ms 定时
	
	OLED_ShowString(1, 1, "VLeft:");
	OLED_ShowString(2, 1, "VRight:");
	OLED_ShowString(3, 1, "LSpd:");
	OLED_ShowString(4, 1, "RSpd:");
	
	while (1)
	{ 
        
		//KeyNum = Key_GetNum();
		/* Delay_ms(1000); KeyNum = 1; if (KeyNum == 1) { Speed += 20; if (Speed > 100) { Speed = -100; } } */
		Speed = 10;
		Motor_SetSpeed_Left(Speed);
		Motor_SetSpeed_Right(Speed);
		
		//NumL += Encoder_Get_Left();//调用这个函数的间隙里,旋转编码器产生的正负脉冲数
		OLED_ShowSignedNum(3, 5, LSpeedNow, 3);
		
		//NumR += Encoder_Get_Right();//调用这个函数的间隙里,旋转编码器产生的正负脉冲数
		OLED_ShowSignedNum(4, 5, RSpeedNow, 3);
		
		OLED_ShowSignedNum(1, 8, Speed, 3);
		OLED_ShowSignedNum(2, 8, Speed, 3);
		
		OLED_ShowSignedNum(4, 10, NumL, 2);
	}
}

void TIM4_IRQHandler(void) //定时器4 的中断函数
{ 
        
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)//看更新中断标志位
	{ 
        
		Get_Motor_Speed(&LSpeedNow,&RSpeedNow);
		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	}
}


至此就可以在oled上显示速度了。

标签: 连接器pb291

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司