关于霍尔编码器读取电机速度的一些相关操作(STM32的编码器模式,MSP的通过输入捕获记录

一.前言闲谈      

  前几天24年的电赛刚刚过去,整个四天三夜也是非常记忆深刻的。我们队选择的是H题,我想只要是做控制的,看到这题目列表都会毫不犹豫的选择H题。因为相比其他题目而言H题简直是太简单了,以至于我们小队的几个人在早上8点之后拿到题目,都有点怀疑这个题目是不是有点问题,怎么可能这么简单。就这个题第一眼看过去感觉都比平衡车都简单,但仔细的看过里面的一些规则要求,以及深刻的用过MSPM0G3507这个板子之后你就会发现这个板子没那么简单。说来也是巧合,我们当时准备模块的时候其他控制需要用上的基本所有的模块都已经准备好了,但唯独没有准备MPU6050。就这一点当时给我们造成了十分阻力,让我们在前期有了想去做其他题的准备,但是回头一想我们都准备了这么久的车,再去做其他类型的题目,那之前的准备岂不是白白准备的。就这样整整弄了三天左右,能够达到所有的基本要求,发挥项目也能稳定下来。唯一缺点就是整个系统的速度提不上去,这个也是本次整个电赛唯一的遗憾吧,没能吧速度提上去。好了闲话少谈,接下来回归正题,说一下编码器测速。

二编码器测速的操作原理

        首先我们得清楚一点的是:带编码器的电机转一圈,它所输出的脉冲数都是一定的,也就是编码器的分辨率。通过这一点,我们就可以在固定时间内测编码器的脉冲数,这样就可得到电机的转速了。通过这一特点我们就可以得到单倍频的实际小车速度了,设p为电机转动一圈所得到的脉冲数,时间t内得到脉冲数为m。我们所求的转速a如下公式一所示:

        这个只是实现了一个简单测速的功能,二倍频,四倍频就是在此基础之上利用编码器的上升沿和下降沿等等物理特性所得到的,这里久不再次描述了,只是简单了解以下即可,我们的目的还是懂得其原理,会利用就行了。

三.STM32端的四倍频编码器测速

以下是STM32测速的具体代码:

#include "encoder.h"
#include "stm32f4xx.h" 
#include "pwm.h"
#include "pid.h"
#include"usart.h" 
int readshuduzuo,readshuduyou;


int tiaochu=0,c=0;
void Encoder_TIM4_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB ,ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;  //TIM4_CH1  TIM4_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4); 
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4); 	
	                                                                                                                                                                                                                                                                              
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 0xffff;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 0x00;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
	
	//±àÂëÆ÷ģʽ
	TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//TIM_ICPolarity_Rising²¶»ñ·¢ÉúÔÚICxµÄÉÏÉýÑØ
    TIM_ICStructInit(&TIM_ICInitStructure); //½«½á¹¹ÌåÖеÄÄÚÈÝȱʡÊäÈë
    TIM_ICInitStructure.TIM_ICFilter = 0;//Â˲¨Æ÷Öµ
    TIM_ICInit(TIM4, &TIM_ICInitStructure);  
	
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; 
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_SetCounter(TIM4, 8400);
	TIM_Cmd(TIM4, ENABLE);
}
void TIM4_IRQHandler(void)
{ 		    
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET) //Òç³öÖжÏ
	{
		
	}
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //Çå³ýÖжϱê־λ 					  
}







//±àÂëÆ÷ÓÒ  tim3  abÏࣺa67




void Encoder_TIM3_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA ,ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;  //TIM4_CH1  TIM4_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3); 
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_TIM3); 	
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 0xffff;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 0x00;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
	
	//±àÂëÆ÷ģʽ
	TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//TIM_ICPolarity_Rising²¶»ñ·¢ÉúÔÚICxµÄÉÏÉýÑØ
    TIM_ICStructInit(&TIM_ICInitStructure); //½«½á¹¹ÌåÖеÄÄÚÈÝȱʡÊäÈë
    TIM_ICInitStructure.TIM_ICFilter = 0;//Â˲¨Æ÷Öµ
    TIM_ICInit(TIM3, &TIM_ICInitStructure);  
	
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; 
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_SetCounter(TIM3, 0);
	TIM_Cmd(TIM3, ENABLE);
}
void TIM3_IRQHandler(void)
{ 		    
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //Òç³öÖжÏ
	{
		
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //Çå³ýÖжϱê־λ 					  
}


void TIM2_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  ///ʹÄÜTIM3ʱÖÓ
	
  TIM_TimeBaseInitStructure.TIM_Period = arr; 	//×Ô¶¯ÖØ×°ÔØÖµ
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //¶¨Ê±Æ÷·ÖƵ
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //ÏòÉϼÆÊýģʽ
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//³õʼ»¯TIM3
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //ÔÊÐí¶¨Ê±Æ÷3¸üÐÂÖжÏ
	TIM_Cmd(TIM2,ENABLE); //ʹÄܶ¨Ê±Æ÷3
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //¶¨Ê±Æ÷3ÖжÏ
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x03; //ÇÀÕ¼ÓÅÏȼ¶1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //×ÓÓÅÏȼ¶3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
}
void TIM2_IRQHandler(void)//¶¨Ê±(10ms)¶ÁÈ¡±àÂëÆ÷Öµ(¼´ËÙ¶È),³¬Éù²¨²â¾à
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //Òç³öÖжÏ
	{	shu=shu+1;
		readshuduzuo=(int16_t)TIM_GetCounter(TIM4);
		readshuduyou=(int16_t)TIM_GetCounter(TIM3);
			TIM_SetCounter(TIM4,0);
			TIM_SetCounter(TIM3,0);
		if(biaozhi==1&&tiaochu==0){
			v=Velocity(mubiaoshudu,readshuduzuo,readshuduyou);
			r=Vertical(167,y);
			Set_Pwm(r+v,-r+v);
									}
		
									
		//1Í£Ö¹
		if (a1==1 && x==2 && biaozhi==1)
		{	tiaochu=1;
			//Set_Pwm(0,0);
		
			Velocity_Ki=0;
			v=Velocity(0,readshuduzuo,readshuduyou);
			Set_Pwm(v,v);
			if(readshuduzuo==0 ||readshuduyou==0)
				{
						Set_Pwm(0,0);
				}		
		}
		
		
		//2Í£Ö¹
		if (a1==2 && x==4 && biaozhi==1&&da==0)
		{	tiaochu=1;
			Set_Pwm(0,0);
		}
		//3Í£
		
		if (a1==3 && w==5 && biaozhi==1&&da==1)
		{Serial_SendByte(1);
			c=1;
			tiaochu=1;
			Vertical_Kp=0;
			Vertical_Kd=0;
			Set_Pwm(0,0);
		}
		
		if (a1==0&&x==1&&biaozhi==1&&da==0)
		{Serial_SendByte(1);
			c1=1;
			tiaochu=1;
			Vertical_Kp=0;
			Vertical_Kd=0;
			Velocity_Kp=-20000;
		
			Velocity_Ki=0;
			v=Velocity(0,readshuduzuo,readshuduyou);
			Set_Pwm(v,v);
		}
		
		
		
	}
		
	
	
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //Çå³ýÖжϱê־λ
}







        

TIM2_Int_Init(8400-1,50-1);  定时器2的重装载值和分频系数(定时10ms)

        利用TIM3,TIM4作为编码器模式读取脉冲数,在TIM2中得到左右两边的具体编码器值。这里简单说明一下为什么还需要一个定时器TIM2来定时10ms去读取两边编码器的值,并且在读取之后又立马清零。TIM2每格10ms记录一次左右两边的脉冲数,这样就像相当于得到了你所要控制物体(电机)的速度,如果将此时的脉冲数除以电机转动一圈的脉冲数,让后再除以10ms,你就可以得到此时电机转动的真实速度了。清零是为了下一次测速的准确性。

四MSPM0G3507端的二倍频测速

        以下是基于MSPG3507端的编码器测速源码:

#include "econder.h"
#include <ti/driverlib/m0p/dl_interrupt.h>
#include "ti/driverlib/dl_gpio.h"

uint8_t x,y,z,sum,x1,y1,z1,sum1;

int32_t left_count=0,right_count=0;



void Exit_Init(void)
{
 NVIC_EnableIRQ(GPIOB_INT_IRQn);
}


void GROUP1_IRQHandler(void)
{
	switch(DL_Interrupt_getPendingGroup(DL_INTERRUPT_GROUP_1)) 
	{
		case DL_INTERRUPT_GROUP1_IIDX_GPIOB:
		if(DL_GPIO_getEnabledInterruptStatus(GPIOB, DL_GPIO_PIN_16))
		{
			x=1;//AÖжϣ¬Ôòx=1;BÖжϣ¬Ôòx=0;
			//ÖжϷ¢Éúʱ£¬AÏà¸ßµçƽ£¬y=1£»·´Ö®y=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_16)) y=1;
			else y=0;
			//ÖжϷ¢Éúʱ£¬BÏà¸ßµçƽ£¬z=1£»·´Ö®z=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_0)) z=1;
			else z=0;
			sum=x+y+z;//ÇóºÍÅжÏת¶¯·½Ïò£¬Å¼ÊýÕýת£¬ÆæÊý·´×ª
			if(sum==0||sum==2) left_count++;
			else left_count--;
			DL_GPIO_clearInterruptStatus(GPIOB, DL_GPIO_PIN_16);
		}
		
		if(DL_GPIO_getEnabledInterruptStatus(GPIOB, DL_GPIO_PIN_0))
		{
			x=0;//AÖжϣ¬Ôòx=1;BÖжϣ¬Ôòx=0;
			//ÖжϷ¢Éúʱ£¬AÏà¸ßµçƽ£¬y=1£»·´Ö®y=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_16)) y=1;
			else y=0;
			//ÖжϷ¢Éúʱ£¬BÏà¸ßµçƽ£¬z=1£»·´Ö®z=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_0)) z=1;
			else z=0;
			sum=x+y+z;//ÇóºÍÅжÏת¶¯·½Ïò£¬Å¼ÊýÕýת£¬ÆæÊý·´×ª
			if(sum==0||sum==2) left_count++;
			else left_count--;
			DL_GPIO_clearInterruptStatus(GPIOB, DL_GPIO_PIN_0);
		}
//
	if(DL_GPIO_getEnabledInterruptStatus(GPIOB, DL_GPIO_PIN_20))
		{
			x1=1;//AÖжϣ¬Ôòx=1;BÖжϣ¬Ôòx=0;
			//ÖжϷ¢Éúʱ£¬AÏà¸ßµçƽ£¬y=1£»·´Ö®y=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_20)) y1=1;
			else y1=0;
			//ÖжϷ¢Éúʱ£¬BÏà¸ßµçƽ£¬z=1£»·´Ö®z=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_13)) z1=1;
			else z1=0;
			sum1=x1+y1+z1;//ÇóºÍÅжÏת¶¯·½Ïò£¬Å¼ÊýÕýת£¬ÆæÊý·´×ª
			if(sum1==0||sum1==2) right_count++;
			else right_count--;
			DL_GPIO_clearInterruptStatus(GPIOB, DL_GPIO_PIN_20);			
		}
			if(DL_GPIO_getEnabledInterruptStatus(GPIOB, DL_GPIO_PIN_13))
		{
			x1=0;//AÖжϣ¬Ôòx=1;BÖжϣ¬Ôòx=0;
			//ÖжϷ¢Éúʱ£¬AÏà¸ßµçƽ£¬y=1£»·´Ö®y=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_20)) y1=1;
			else y1=0;
			//ÖжϷ¢Éúʱ£¬BÏà¸ßµçƽ£¬z=1£»·´Ö®z=0;
			if(DL_GPIO_readPins(GPIOB,DL_GPIO_PIN_13)) z1=1;
			else z1=0;
			sum1=x1+y1+z1;//ÇóºÍÅжÏת¶¯·½Ïò£¬Å¼ÊýÕýת£¬ÆæÊý·´×ª
			if(sum1==0||sum1==2) right_count++;
			else right_count--;
			DL_GPIO_clearInterruptStatus(GPIOB, DL_GPIO_PIN_13);
		}	
	
		break;
	}
}






        由于TI的MSPM0G3507这款办卡必须得用图形化界面来初始化和配置一系列的IO口定时器,PWM,中断,等等。其只有7个定时器,虽然也有编码器模式,但是不管如何配置只能够配置出来一个编码器。所有只能用IO口输入捕获的方式来测速了,第一个编码的AB项为PB0、16,第二个编码的AB项为PB20,13只需要将这四个io口配置成为输入方式,拉低就可以利用了。当然还有最后重要的一点还是多长时间读取一次,也就是定时器如何设置。具体如下:

#include "tim.h"
#include <ti/driverlib/dl_timera.h>
#include "ti/driverlib/dl_gpio.h"
#include "usart.h"
#include "usart1.h"
#include "kme.h"
#include "led.h"


extern int32_t left_count;
extern int32_t right_count;
extern int32_t left_now;
extern int32_t right_now;
extern int32_t Speed;
extern int cx;

void TimerA1_Init(void)
{   
	NVIC_EnableIRQ(TIMA1_INT_IRQn);
	DL_Timer_startCounter(TIMA1);
}
void TIMA1_IRQHandler(void)
{
	 switch (DL_TimerA_getPendingInterrupt(TIMA1))
  {
    case DL_TIMERA_IIDX_ZERO:
   

//	   receive_data_usart();
	//ÉãÏñͷʶ±ð
		 cx=USART_RX_BUF[0];



	//±àÂëÆ÷¶ÁÈ¡ËÙ¶È
	left_now=-left_count; 
  right_now=-right_count;
	Speed=(left_now+right_now)/2;
	//
	kfp_x.source=cx;
  kfp_x.out=KalmanFilter(&kfp_x,kfp_x.source);

	
	//±àÂëÆ÷ËÙ¶ÈÇå0
	left_count=0;  
	right_count=0;	
	
	 break;

    default:
     break;
  }
}

配置图如下:

以上就是所需的一些具体配置图。本次的编码器测速具体操作也就如上所示了,希望这些对大家有所帮助。

  • 29
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用四线霍尔编码器测量电机速度值,需要通过读取编码器输出的脉冲数来计算电机的转速。以下是一个基本的示例程序,使用 MSP432P401R 开发板和四线霍尔编码器来测量电机速度值: ```c #include <msp.h> #include <math.h> #define ENCODER_PULSES_PER_REV 2048 // 每转脉冲数 #define SAMPLE_INTERVAL_MS 10 // 采样间隔,单位 ms volatile uint32_t encoder_count = 0; // 编码器计数器 volatile float motor_speed = 0.0; // 电机速度 void init_encoder(void) { // 初始化编码器 P2->DIR &= ~(BIT4 | BIT5); // P2.4 和 P2.5 设为输入 P2->REN |= BIT4 | BIT5; // P2.4 和 P2.5 内部上拉 P2->OUT |= BIT4 | BIT5; P2->SEL0 &= ~(BIT4 | BIT5); // P2.4 和 P2.5 设置为 GPIO P2->SEL1 &= ~(BIT4 | BIT5); P2->IES &= ~(BIT4 | BIT5); // P2.4 和 P2.5 下降沿触发 P2->IFG &= ~(BIT4 | BIT5); // 清除中断标志 P2->IE |= BIT4 | BIT5; // 使能中断 } void init_timer(void) { // 初始化定时器 TIMER_A0->CTL = 0x0200; // 停止定时器 TIMER_A0->CTL = 0x0220; // 分频器为 8,选择 SMCLK,设置为连续计数模式 TIMER_A0->CCTL[0] = 0x0010; // 匹配模式,输出比较中断使能 TIMER_A0->CCR[0] = SAMPLE_INTERVAL_MS * (SystemCoreClock / 8000) - 1; // 设置计数器上限 TIMER_A0->EX0 = 0x0006; // 分频器为 7 TIMER_A0->CTL |= 0x0024; // 启动定时器 } void TA0_0_IRQHandler(void) { // 定时器中断处理函数 encoder_count = 0; // 清空编码器计数器 TIMER_A0->CCTL[0] &= ~BIT0; // 清除中断标志 } void PORT2_IRQHandler(void) { // 编码器中断处理函数 if (P2->IFG & BIT4) // P2.4 下降沿触发 { encoder_count++; // 编码器计数器加 1 } if (P2->IFG & BIT5) // P2.5 下降沿触发 { encoder_count--; // 编码器计数器减 1 } P2->IFG &= ~(BIT4 | BIT5); // 清除中断标志 } int main(void) { WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD; // 初始化编码器和定时器 init_encoder(); init_timer(); while (1) { // 计算电机速度 motor_speed = (float)encoder_count / ENCODER_PULSES_PER_REV * 1000.0 / SAMPLE_INTERVAL_MS * 60.0; // 这里可以根据需要将速度值输出到串口或 LCD 显示屏等设备上 // 等待下一次采样 __delay_cycles((SystemCoreClock / 1000) * SAMPLE_INTERVAL_MS); } } ``` 在这个程序中,你需要根据具体的编码器设置每转脉冲数 ENCODER_PULSES_PER_REV。程序的主要思路是通过定时器定时采样编码器计数器的值,计算两次采样间隔内编码器计数器的变化量,从而计算电机的转速。需要注意的是,在采样间隔内如果电机速度发生了变化,计算结果可能会不准确。因此,采样间隔需要根据具体的应用场景进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电赛张小七

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值