【深入浅出:编码器测速的技巧】

文章目录

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

  • 前言

    详解编码器测速原理及实现
    参考csdn


    一、AB相编码器计数原理

    适用于霍尔编码器和带光栅的光电编码器
    编码器样子

    让遮挡与透过形成高低电平的脉冲

    此图为B 提前 A 90° 为反向旋转,相位差相差90°的波形就叫正交波形
    当只有一个方波 只能测位置、速度,不能知道方向
    而两个方波,就能一个测速,一个测方向
    例:
    A下降沿触发 并且B是低电平 判断反转
    B下降沿触发 并且A是低电平 判断正转

    四倍频

    还有一种用法 叫四倍频,它是数 A相 和 B相 的上升和下降脉冲 4个信号来测速可以用来提高精度,因为一般只需要考虑A相一个上升或下降沿计数就行,这里数4个,所以叫四倍频。
    stm32的编码器接口应该默认是四倍频的:
    这里的大佬,说
    手动转一圈的脉冲数 n:1560 个;
    且用的是13线编码器 和 1:120转速比的电机,那么计算一圈的脉冲数则是
    13 *(120)*4 = 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);
    	}
    }
    
    
    

    四倍频的代码(更新)

    在原来基础上修改,这里写的四倍频不管向前还是向后转后会加加,理论上按照00 ,01 ,10,11 四倍频也能有方向,但觉得写的太烦,没写,下面的四倍频已实操过可以用来调pid,在本来的脉冲数上*4就行。

    //四倍频
    
    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);//清楚标志位
    	}
    }
    
    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);//清楚标志位
    	}
    }
    

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

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【深入浅出:编码器测速的技巧】

    发表评论