【STM32】STM32F103C8T6使用外部中断法和输入捕获法进行编码器测速

系列文章目录

·【STM32】新建工程模板及配置

·【STM32】STM32与PC端、HC-06、ROS进行USART串口通信

·【ROS】ROS上位机使用Serial库和boost::asio库与STM32进行USART通讯

·【STM32】STM32F103C8T6+L298N通过PWM控制直流电机转速

·【STM32】STM32F103C8T6使用外部中断法和输入捕获法进行编码器测速

·【STM32】STM32F103C8T6实现直流电机速度PID控制


前言

如果我们想对电机进行速度或者转角的精确控制,需要使用到很多算法,比如非常经典的PID控制算法,或者一些只能算法,但这些算法都需要传感器来提供转速或转角的反馈值,对于电机来说,编码器是非常流行并且实用的电机配套传感器,本文使用STM32F103C8T6+L298N+MG513P30电机进行直流电机的编码器测速。

一、编码器原理

1.分类

 光电式编码器的精准度比霍尔式要高,但是由于它需要红外线发生器和接收器,相对来说造价要贵一些。现在我们比较常用的是霍尔式增量编码器,有很多电机都会自带编码器。

在这里插入图片描述

2.测速方法分类

(1)M法测速

编码器输出的脉冲个数代表了位置,那么单位时间里的脉冲个数表示这段时间里的平均速度。因此,我们可以通过计量单位时间脉冲个数即可以估算出平均速度,称为M法测速(测脉冲个数)测速原理如图所示。

例如,若编码器每转产生N个脉冲,在T时间(单位s)产生m个脉冲,那么平均转速如公式所示。

n=\frac{m}{NT}(r/s)=\frac{m\cdot 60}{NT}(r/min)         

式中 n——平均转速(r/min);

T——测速采样时间(s);

m——T时间内测得的编码器脉冲个数;

N——编码器每转脉冲数。

(2)T法测速

若用M法测速,在记录时间短、速度低的时候,只能记录几个脉冲,则分辨率降低。针对该问题,目前解决方法为:可以采用输出码盘脉冲为一个时间间隔,然后用计数器记录在这段时间里高速脉冲源发出的脉冲数。即通过采集到脉冲源脉冲数来计量编码器两个脉冲时间间隔,从而估算出速度,称为T法测速(测脉冲周期),测速原理图如图所示。

T法测速,利用编码器产生的脉冲用作门电路的触发信号;用已知频率f的时钟信号做输入。若控制门电路在编码器脉冲上升沿到来时开始导通,再次上升沿到来时关闭,即计数器只记录一个编码器脉冲周期内的时钟脉冲个数。若在编码器相邻脉冲之间记录的脉冲时钟个数为m,那么,可以计算两个编码器脉冲的时间间隔为m /f;若编码器每转有N个线脉冲输出,那么我们就知道编码器转过1/N转时需要时间m /f。据此,可计算与编码器同轴转速为公式所示。

n=\frac{f}{Nm}(r/s)=\frac{f\cdot 60}{Nm}(r/min)

式中 n——平均转速(r/min);

f——时钟脉冲频率(个/s);

m——两个编码器脉冲之间的时钟脉冲个数;

N——编码器每转脉冲数。

编码器一般会输出两路信号,分别称为A相和B相,它们相差90°,因此编码器也称为十字码盘,通过捕获两路输出信号可以测算出电机的转速和转向。

STM32使用编码器的方法有两种分别是外部中断法和输入捕获法,这两种方法都属于M法测速,两种方法比较来说外部中断法占用CPU资源较多,平时比较常用的是输入捕获法,但博主两种方法都调试出来了,因此记录下来跟大家分享一下。

二、外部中断法测速

对于外部中断的知识,各个讲STM32的教程都有,我就不过多赘述,外部中断的初始化都一样,主要是出发外部中断时需要进行的操作。

在这里插入图片描述

 看这张图,正转方向是信号向右走,因为我们是同时捕获两路信号,有以下几种情况:

捕获相捕获情况另一项情况结果
A相下降沿B相低电平(对应图中4处)正转
下降沿B相高电平(对应图中2处)反转
A相上升沿B相高电平(对应图中2处)正转
上升沿B相低电平(对应图中4处)反转
B相下降沿A相高电平(对应图中3处)正转
下降沿A相低电平(对应图中1处)反转
B相上升沿A相低电平(对应图中1处)正转
上升沿A相高电平(对应图中3处)反转

然后我们设置两个变量用来存储捕获的脉冲数,每捕获到一次脉冲信号根据上表进行判断,正转时使其加1;反转时使其减1;然后配置一个定时器,每隔一段时间反馈一次测速值。

1.外部中断配置

先编写一个函数初始化外部中断,使用PB12-15引脚复用为外部中断输入,外部中断配置步骤如下:

  1. 端口初始化:RCC_APB2PeriphClockCmd()、GPIO_Init()
  2. 使能复用功能时钟:RCC_APB2PeriphClockCmd()
  3. 设置IO口与中断线的映射关系:GPIO_EXTILineConfig()
  4. 初始化线上中断:EXTI_Init()
  5. 配置中断分组:NVIC_Init()

完整函数如下:

/**************************************************************************
功能:应用外部中断方式采集编码器数据,使用M法测速反馈车轮实时速度
函数:Encoder_EXTIX_Init(void)  EXTI15_10_IRQHandler(void)
作者:K.Fire
日期:2022.01.30
引脚:PB12(左轮A相) PB13(左轮B相) PB14(右轮A相) PB15(右轮B相)
参数:void
*************************************************************************/

int Encoder_L_EXTI=0;
int Encoder_R_EXTI=0;

void Encoder_EXTIX_Init(void)
{
	//1.端口初始化
	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);
	
	//2.使能复用功能时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

	//3.设置IO口与中断线的映射关系
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource12);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource13);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource15);
	
	//4.初始化线上中断
	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);
	
	//5.配置中断分组
	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);

 
}

2.外部中断服务函数

函数的判断逻辑与上表一致,外部中断捕获判断函数如下:

void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line12) != RESET)//左轮A相 PB12
	{
		EXTI_ClearITPendingBit(EXTI_Line12);  //清除LINE上的中断标志位
		if(PBin(12)==0)   //这里判断检测到的是否是下降沿
		{
			if(PBin(13)==0)   Encoder_L_EXTI++;//B相的电平如果是低,电机就是正转加1
			else             Encoder_L_EXTI--;//否则就是反转减1
		}
		else                  //上升沿
		{ 
			if(PBin(13)==1)  Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
			else             Encoder_L_EXTI--;//否则就是反转减1
		}
	}
	
	if(EXTI_GetITStatus(EXTI_Line13) != RESET)//左轮B相 PB13
	{
		EXTI_ClearITPendingBit(EXTI_Line13);  //清除LINE上的中断标志位
		if(PBin(13)==0)   //这里判断检测到的是否是下降沿
		{
			if(PBin(12)==1)   Encoder_L_EXTI++;//B相的电平如果是高,电机就是正转加1
			else             Encoder_L_EXTI--;//否则就是反转减1
		}
		else                  //上升沿
		{ 
			if(PBin(12)==0)  Encoder_L_EXTI++; //B相电平如果为高,电机就是正转加1
			else             Encoder_L_EXTI--;//否则就是反转减1
		}
	}
	
	if(EXTI_GetITStatus(EXTI_Line14) != RESET)//右轮A相 PB14
	{
		EXTI_ClearITPendingBit(EXTI_Line14);  //清除LINE上的中断标志位
		if(PBin(14)==0)   //这里判断检测到的是否是下降沿
		{
			if(PBin(15)==0)   Encoder_R_EXTI++;//B相的电平如果是低,电机就是正转加1
			else             Encoder_R_EXTI--;//否则就是反转减1
		}
		else                  //上升沿
		{ 
			if(PBin(15)==1)  Encoder_R_EXTI++; //B相电平如果为高,电机就是正转加1
			else             Encoder_R_EXTI--;//否则就是反转减1
		}
	}
	
	if(EXTI_GetITStatus(EXTI_Line15) != RESET)//右轮B相 PB15
	{
		EXTI_ClearITPendingBit(EXTI_Line15);  //清除LINE上的中断标志位
		if(PBin(15)==0)   //这里判断检测到的是否是下降沿
		{
			if(PBin(14)==1)   Encoder_R_EXTI++;//A相的电平如果是高,电机就是正转加1
			else             Encoder_R_EXTI--;//否则就是反转减1
		}
		else                  //上升沿
		{ 
			if(PBin(14)==0)  Encoder_R_EXTI++; //A相电平如果为低,电机就是正转加1
			else             Encoder_R_EXTI--;//否则就是反转减1
		}
	}
	
}

注意一下,在使用引脚时不要重复,因为Px0(x=AB..F)都是使用的同一条外部中断线(EXIT0)

3.读取捕获脉冲数值

配置完定时器后,每过一段时间调用读取函数,通过公式可以计算出实际的脉冲值。

函数如下:

/**************************************************************************
功能:获取不同方式下的脉冲值
函数:int Read_Encoder(u8 TIMX)
作者:K.Fire
日期:2022.01.30
参数:1:外部中断法左轮 2:外部中断法右轮 3:输入捕获法左轮 4:输入捕获法右轮
**************************************************************************/
 
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;
}

/**************************************************************************
功能:获取并打印输出实际速度值
函数:void Get_SpeedNow(float* CurrentVelcity_L,float* CurrentVelcity_R)
作者:K.Fire
日期:2022.01.30
参数:CurrentVelcity_L:左轮实时速度(地址)  CurrentVelcity_R:右轮实时速度(地址)
**************************************************************************/

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);
}

4.测试结果

通过USART1串口向电脑输出实时速度值,结果如下:

三、输入捕获法测速

输入捕获是将TIM定时器的CHx通道配置为输入捕获模式,每捕获到一个信号会将响应定时器的CNT计数器的值加/减1,然后每隔一段时间提取并清空计数器的值就可以测算出电机的实时转速。

1.输入捕获模式配置

输入捕获模式的初始化,各个STM32的教学视频都有,只是在配置编码器模式时,需要用到一个编码器模式的配置函数:TIM_EncoderInterfaceConfig()

输入捕获模式的初始化步骤如下:

  1. 使能定时器和通道对应的时钟:RCC_APB1PeriphClockCmd()
  2. 初始化IO口:GPIO_Init()
  3. 初始化定时器:TIM_TimeBaseInit()
  4. 配置编码器模式:TIM_EncoderInterfaceConfig()
  5. 初始化输入捕获通道:TIM_ICInit()
  6. 开启更新中断:TIM_ITConfig()
  7. 使能定时器:TIM_Cmd()

完整的代码如下:

/**************************************************************************
功能:应用输入捕获方式采集编码器数据,使用M法测速反馈车轮实时速度
函数:void Encoder_CAP_Init()
作者:K.Fire
日期:2022.01.30
引脚:PA6(左轮A相) PA6(左轮B相) PB6(右轮A相) PB7(右轮B相)
参数:无
**************************************************************************/

void Encoder_CAP_Init_L()
{
	/*1.使能定时器和通道对应的时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	/*2.初始化IO口*/
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;//推挽下拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	/*3.初始化定时器*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period = 0xffff;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 0x0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	//配置编码器模式
	TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	
	/*4.初始化输入捕获通道*/
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICStructInit(&TIM_ICInitStruct);
	TIM_ICInitStruct.TIM_ICFilter = 10;//设置滤波器,重复检测10次,防止抖动,增加精度
	TIM_ICInit(TIM3,&TIM_ICInitStruct);
	
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
	
	/*5.开启更新中断*/
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	
	TIM_SetCounter(TIM3,0);//计数器置零
	TIM3->CNT = 0x7fff;

//	/*4.初始化输入捕获通道*/
//	TIM_ICInitTypeDef TIM_ICInitStruct;
//	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1 | TIM_Channel_2;//通道1和2
//	TIM_ICInitStruct.TIM_ICFilter = 10;//设置滤波器,重复检测10次,防止抖动,增加精度
//	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_TIM_ICPolarity_Rising;//上升沿捕获
//	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;//配置输入分频,不分频
//	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;//不设置重映射
//	TIM_ICInit(TIM2,&TIM_ICInitStruct);
//	
//	/*5.开启捕获中断,并配置中断*/
//	TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
//	
//	NVIC_InitTypeDef NVIC_InitStruct;
//	NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
//	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
//	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
//	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
//	NVIC_Init(&NVIC_InitStruct);
	
	/*6.使能定时器*/
	TIM_Cmd(TIM3,ENABLE);
	
	/*7.编写中断服务函数*/
	
}

注释部分是当时学输入捕获时跟教程写的,不用管。

一个编码器需要占用一个定时器,这里我使用的是TIM3和TIM4

注意:在初始化IO口时,选择输入输出模式有点问题。

我看网上有的说选择输入浮空模式、有的说选择输入上拉模式,但是我调试的时候这两种都不出结果,我改成输入下拉模式的是才调试成功,大家在实际调试过程中可以几个输入模式都试一下。

2.读取捕获脉冲数值

这部分和上面外部中断法的函数一样,输入3和4参数使用输入捕获方式提取计数器的值。

/**************************************************************************
功能:获取不同方式下的脉冲值
函数:int Read_Encoder(u8 TIMX)
作者:K.Fire
日期:2022.01.30
参数:1:外部中断法左轮 2:外部中断法右轮 3:输入捕获法左轮 4:输入捕获法右轮
**************************************************************************/
 
int Read_Encoder(u8 TIMX)
{
    int Encoder_TIM;    
    switch(TIMX)
	{
		case 3:Encoder_TIM=TIM3->CNT-0x7fff;  TIM3->CNT=0x7fff;break;	
		case 4:Encoder_TIM=TIM4->CNT-0x7fff;  TIM4->CNT=0x7fff;break;
	    default:Encoder_TIM=0;break;
	}
		return Encoder_TIM;
}

/**************************************************************************
功能:获取并打印输出实际速度值
函数:void Get_SpeedNow(float* CurrentVelcity_L,float* CurrentVelcity_R)
作者:K.Fire
日期:2022.01.30
参数:CurrentVelcity_L:左轮实时速度(地址)  CurrentVelcity_R:右轮实时速度(地址)
**************************************************************************/

extern int Current_LN,Current_RN;//速度脉冲数

void Get_SpeedNow(int* CurrentVelcity_L,int* CurrentVelcity_R)
{
	/*输入捕获方式*/
	Current_LN = -Read_Encoder(3);
	Current_RN = Read_Encoder(4);
		
	*CurrentVelcity_L = (MPN * Current_LN * 1000 * 10)/T;//单位:mm/s
	*CurrentVelcity_R = (MPN * Current_RN * 1000 * 10)/T;
		
	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);
	
}

3.测试结果

 测试结果略有误差...

总结

注意:左轮电机和右轮电机是相反的,需要实际测试确定,比如我这里,向前走的时候,右轮是正转,这时左轮反转的时候才是前进,在编写程序时需要给左轮的脉冲值加个负号。

获取到了实时速度值后,就可以用PID算法对电机转速进行准确控制了。


 

  • 64
    点赞
  • 419
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
使用STM32C8T6和JGB37-520编码器电机进行测距,您需要进行以下步骤: 1. 连接JGB37-520编码器电机和STM32C8T6单片机。 2. 配置STM32C8T6的定时器,以便读取编码器信号。 3. 通过读取编码器信号,计算电机的转速和位置。 4. 使用电机的转速和位置信息,计算电机的线速度和加速度。 5. 将电机的线速度和加速度转换为距离。 下面是一些可能有帮助的代码片段: 1. 连接JGB37-520编码器电机和STM32C8T6单片机 JGB37-520编码器电机有两个信号线,一个是A相信号线,一个是B相信号线。将A相信号线连接到STM32C8T6的TIMx_CH1引脚,将B相信号线连接到TIMx_CH2引脚。 2. 配置STM32C8T6的定时器,以便读取编码器信号 使用STM32的定时器来读取编码器信号。您需要配置定时器的输入捕获模式,以便捕获编码器信号的上升沿和下降沿。您还需要设置定时器的计数器和预分频器,以便在每个捕获事件之间测量时间。下面是一个示例配置: ```c // 定义定时器和GPIO引脚 #define TIMx TIM2 #define TIMx_CLK RCC_APB1Periph_TIM2 #define TIMx_IRQn TIM2_IRQn #define TIMx_IRQHandler TIM2_IRQHandler #define TIMx_CH1_GPIO_PORT GPIOA #define TIMx_CH1_GPIO_PIN GPIO_Pin_0 #define TIMx_CH1_GPIO_CLK RCC_AHB1Periph_GPIOA #define TIMx_CH1_SOURCE GPIO_PinSource0 #define TIMx_CH1_AF GPIO_AF_TIM2 #define TIMx_CH2_GPIO_PORT GPIOA #define TIMx_CH2_GPIO_PIN GPIO_Pin_1 #define TIMx_CH2_GPIO_CLK RCC_AHB1Periph_GPIOA #define TIMx_CH2_SOURCE GPIO_PinSource1 #define TIMx_CH2_AF GPIO_AF_TIM2 // 初始化定时器 void TIM_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能定时器和GPIO时钟 RCC_APB1PeriphClockCmd(TIMx_CLK, ENABLE); RCC_AHB1PeriphClockCmd(TIMx_CH1_GPIO_CLK | TIMx_CH2_GPIO_CLK, ENABLE); // 配置GPIO为TIMx通道1和通道2 GPIO_InitStructure.GPIO_Pin = TIMx_CH1_GPIO_PIN | TIMx_CH2_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将GPIO引脚映射到TIMx通道1和通道2上 GPIO_PinAFConfig(TIMx_CH1_GPIO_PORT, TIMx_CH1_SOURCE, TIMx_CH1_AF); GPIO_PinAFConfig(TIMx_CH2_GPIO_PORT, TIMx_CH2_SOURCE, TIMx_CH2_AF); // 配置定时器为输入捕获模式 TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_ICInit(TIMx, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_ICInit(TIMx, &TIM_ICInitStructure); // 使能定时器中断 NVIC_InitStructure.NVIC_IRQChannel = TIMx_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 启动定时器 TIM_Cmd(TIMx, ENABLE); // 启用定时器的捕获中断 TIM_ITConfig(TIMx, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); } // 定时器中断处理函数 void TIMx_IRQHandler(void) { if (TIM_GetITStatus(TIMx, TIM_IT_CC1) != RESET) { // 处理A相信号 TIM_ClearITPendingBit(TIMx, TIM_IT_CC1); } else if (TIM_GetITStatus(TIMx, TIM_IT_CC2) != RESET) { // 处理B相信号 TIM_ClearITPendingBit(TIMx, TIM_IT_CC2); } } ``` 3. 通过读取编码器信号,计算电机的转速和位置 当定时器捕获到编码器信号的上升沿或下降沿时,您需要更新电机的位置和速度。在处理A相信号时,如果B相信号也发生了变化,则电机向前转动;如果B相信号没有发生变化,则电机向后转动。在处理B相信号时,您可以使用相同的逻辑来确定电机的方向。下面是一个示例实现: ```c // 定义编码器参数 #define ENCODER_RESOLUTION 1000.0f // 编码器分辨率 #define WHEEL_DIAMETER 0.1f // 轮子直径(单位:米) #define GEAR_RATIO 100.0f // 减速比 #define PI 3.1415926 // 定义电机状态 typedef struct { uint32_t position; // 电机的位置(单位:脉冲) float speed; // 电机的速度(单位:转/秒) } motor_t; motor_t motor; // 处理A相信号中断 void handle_encoder_A_interrupt(void) { if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)) { // 编码器向前转动 motor.position++; } else { // 编码器向后转动 motor.position--; } } // 处理B相信号中断 void handle_encoder_B_interrupt(void) { if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) { // 编码器向前转动 motor.position++; } else { // 编码器向后转动 motor.position--; } } // 计算电机的速度和位置 void calculate_motor_speed_and_position(void) { static uint32_t last_position = 0; static uint32_t last_time = 0; // 计算电机的位置 uint32_t current_position = motor.position; float delta_position = current_position - last_position; last_position = current_position; // 计算电机的速度 uint32_t current_time = TIM_GetCounter(TIM2); float delta_time = current_time - last_time; last_time = current_time; float delta_angle = delta_position / ENCODER_RESOLUTION * 2 * PI / GEAR_RATIO; float delta_distance = delta_angle * WHEEL_DIAMETER / 2; motor.speed = delta_distance / delta_time; } ``` 4. 使用电机的转速和位置信息,计算电机的线速度和加速度 使用电机的速度和位置信息,可以计算电机的线速度和加速度。您需要将电机的速度转换为线速度,然后使用两个连续的速度值来计算电机的加速度。下面是一个示例实现: ```c // 计算电机的线速度和加速度 void calculate_motor_linear_speed_and_acceleration(float *linear_speed, float *acceleration) { static float last_speed = 0; static uint32_t last_time = 0; // 计算电机的线速度 *linear_speed = motor.speed * WHEEL_DIAMETER / 2; // 计算电机的加速度 uint32_t current_time = TIM_GetCounter(TIM2); float delta_time = current_time - last_time; last_time = current_time; *acceleration = (*linear_speed - last_speed) / delta_time; last_speed = *linear_speed; } ``` 5. 将电机的线速度和加速度转换为距离 最后,您可以使用电机的线速度和加速度来计算电机的距离。下面是一个示例实现: ```c // 计算电机移动的距离 void calculate_motor_distance(float *distance) { static float last_speed = 0; static uint32_t last_time = 0; // 计算电机的线速度和加速度 float linear_speed, acceleration; calculate_motor_linear_speed_and_acceleration(&linear_speed, &acceleration); // 计算电机移动的距离 uint32_t current_time = TIM_GetCounter(TIM2); float delta_time = current_time - last_time; last_time = current_time; *distance += (last_speed + linear_speed) / 2 * delta_time; last_speed = linear_speed; } ``` 以上是一个简单的示例,可以帮助您开始使用STM32C8T6和JGB37-520编码器电机进行测距。但是,请注意,您需要进行更多的调试和测试,以确保代码的正确性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值