STM32外部中断读取编码器计数值

文章介绍了如何通过STM32的外部中断来读取编码器的正交信号,以此避免占用宝贵的定时器资源。通过提供的GuiStar_Encoder_Init、GuiStar_Encoder_Read_Current_Value和GuiStar_Encoder_GetSpeed等函数,实现了编码器的中断读取,提高了系统的效率和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32外部中断读取编码器计数值:


stm32的定时器是可以直接读取编码器的正交信号的,这也说明定时器的强大之处,但是呢,stm32的定时器个数是有限的,当用一个定时器来读取编码器的,那这个定时器就不能再干其他任何事情了,驱动四个电机只需要一个定时器,但是读取编码器也直接用掉了一个定时器,这是对硬件资源极大的浪费


定时器在一个项目中往往还有比读取编码器更重要的作用,因此,读取编码器这种简单的活就不要再让定时器做了,太浪费硬件资源了,所以,本文提供外部中断的方法来读取编码器的正交信号,毕竟stm32的每一个引脚都可以配置外部中断的。利用提供的三个函数
GuiStar_Encoder_Init,
GuiStar_Encoder_Read_Current_Value,
GuiStar_Encoder_GetSpeed。

代码中用到了IO.h和IO_Init,它们是初始化IO口为浮空输入模式的封装,源码如下:

提示:下面的两个文件的作用是stm32位带操作和stm32引脚初始化函数的封装,如果要深究位带操作,建议看野火哥的视频,本次的编码器读取只用到了IO_Init这个函数,没有涉及位带操作

其实位带操作也仅仅是原理很难理解而已,但是用起来是真的香啊,它把stm32单片机玩儿出了51单片机的感觉。哈哈


IO.h

#ifndef __IO_H
#define __IO_H


#include "stm32f10x.h"

#define SYSTEM_SUPPORT_OS
																	    
	 

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12)    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12)    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) 
 

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  


void IO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin_x, GPIOMode_TypeDef GPIO_Mode);

#endif

IO.c:

#include "stm32f10x.h"

/**
  * @brief  完成指定IO口的时钟开启,模式选择
  * @param  GPIOx        			可选GPIOA,GPIOB,GPIOC,GPIOD
  * @param  GPIO_Pin_x   			选择Pin
  * @param  GPIO_Mode   			选择GPIO模式,有以下八种模式可供选择:
			  GPIO_Mode_AIN 		模拟输入
			  GPIO_Mode_IN_FLOATING 浮空输入
			  GPIO_Mode_IPD 		下拉输入
			  GPIO_Mode_IPU 		上拉输入
			  GPIO_Mode_Out_OD 		开漏输出(带上拉或者下拉)
			  GPIO_Mode_Out_PP 		推挽输出(带上拉或者下拉
			  GPIO_Mode_AF_OD 		复用开漏输出(带上拉或者下拉)
			  GPIO_Mode_AF_PP 		复用推挽输出(带上拉或者下拉)
  * @param 
  * @retval 无
  */
void IO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin_x, GPIOMode_TypeDef GPIO_Mode)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_x;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	if(GPIOx==GPIOA)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
		GPIO_Init(GPIOA, &GPIO_InitStructure);
	}
	
	if(GPIOx==GPIOB)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
		GPIO_Init(GPIOB, &GPIO_InitStructure);
	}
	
	if(GPIOx==GPIOC)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
		GPIO_Init(GPIOC, &GPIO_InitStructure);
	}

	if(GPIOx==GPIOD)
	{
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
		GPIO_Init(GPIOD, &GPIO_InitStructure);
	}
}


接下来的两个文件中涉及外部中断的配置,我之前封装过相关函数了,请看这里外部中断函数封装

GuiStar_Encoder.h:

#ifndef __GUISTAR_ENCODER_H__
#define __GUISTAR_ENCODER_H__

#include "stm32f10x.h"                  // Device header
#include "IO.h"
#include "GuiStar_EXTI.h"

void GuiStar_Encoder_Init(GPIO_TypeDef* INT1_Port, uint16_t INT1_Pin,GPIO_TypeDef* INT2_Port, uint16_t INT2_Pin, uint8_t Reversal);
int GuiStar_Encoder_Read_Current_Value(void);
int GuiStar_Encoder_GetSpeed(void);

#endif

GuiStar_Encoder.c:

#include "GuiStar_Encoder.h"
GPIO_TypeDef* 			INT2_port;
uint16_t 				INT2_pin;
uint8_t 				reversal;
int GuiStar_Encoder_Current_Value=0;

void Encoder_IQHeader(void)
{
	if(GPIO_ReadInputDataBit(INT2_port,INT2_pin)==SET)
	{
		if(reversal==0)
			GuiStar_Encoder_Current_Value++;
		else GuiStar_Encoder_Current_Value--;
	}
	else if(GPIO_ReadInputDataBit(INT2_port,INT2_pin)==RESET)
	{
		if(reversal==0)
			GuiStar_Encoder_Current_Value--;
		else GuiStar_Encoder_Current_Value++;
	}
}

/**	
  * @brief  	外部中断读取正交编码器初始化函数
  * @param  	INT1_Port 选择接口一的端口(GPIOA,GPIOB,GPIOC,GPIOD)
  * @param  	INT1_Pin  选择接口一的引脚 GPIO_Pin_x(x从0到15)
  * @param  	INT2_Port 选择接口二的端口(GPIOA,GPIOB,GPIOC,GPIOD)
  * @param  	INT2_Pin  选择接口二的引脚 GPIO_Pin_x(x从0到15)
  * @param  	Reversal  是否反向(给0正向,给1反向,正向与反向是相对的)
  * @attention  两个接口不能同时选择9到10引脚,也不能同时选择10到15引脚!!!
  * @retval 
  */
void GuiStar_Encoder_Init(GPIO_TypeDef* INT1_Port, uint16_t INT1_Pin,GPIO_TypeDef* INT2_Port, uint16_t INT2_Pin, uint8_t Reversal)
{
	u8 INT1_Num=0;
	u8 INT2_Num=0;
	INT2_port=INT2_Port;
	INT2_pin=INT2_Pin;
	reversal=Reversal;
	for(;((1<<INT1_Num) & (INT1_Pin))==0;)
	{
		INT1_Num++;
	};
	for(;((1<<INT2_Num) & (INT2_Pin))==0;)
	{
		INT2_Num++;
	};
	
	IO_Init(INT1_Port,INT1_Pin,GPIO_Mode_IN_FLOATING);//初始化接口1引脚为浮空输入
	IO_Init(INT2_Port,INT2_Pin,GPIO_Mode_IN_FLOATING);//初始化接口2引脚为浮空输入
	GuiStar_EXTI_Init(INT1_Port,INT1_Pin,EXTI_Trigger_Rising);//配置接口1外部中断
	
	if(INT1_Num==0)
	{
		GuiStar_EXTI_SetEXTI0_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num==1)
	{
		GuiStar_EXTI_SetEXTI1_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num==2)
	{
		GuiStar_EXTI_SetEXTI2_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num==3)
	{
		GuiStar_EXTI_SetEXTI3_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num==4)
	{
		GuiStar_EXTI_SetEXTI4_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num>=5&&INT1_Num<=9)
	{
		GuiStar_EXTI_SetEXTI9_5_IRQHandler(Encoder_IQHeader);
	}
	else if(INT1_Num>=10&&INT1_Num<=15)
	{
		GuiStar_EXTI_SetEXTI15_10_IRQHandler(Encoder_IQHeader);
	}
}

/**
  * @brief  返回正交编码器当前值
  * @param  无
  * @retval 正交编码器当前值
  */
int GuiStar_Encoder_Read_Current_Value(void)
{
	return GuiStar_Encoder_Current_Value;
}

/**
  * @brief  函数内部先将编码器当前计数值锁存,再清零计数变量(GuiStar_Encoder_Current_Value),最后返回锁存值
  * @param  无
  * @retval 无
  */
int GuiStar_Encoder_GetSpeed(void)
{
	int temp;
	temp=GuiStar_Encoder_Current_Value;//先将编码器当前计数值锁存
	GuiStar_Encoder_Current_Value=0;//再清零计数变量(GuiStar_Encoder_Current_Value)
	return temp;//返回锁存值
}


本文结束,感谢观看!

<think>首先,用户的问题是:“我想使用外部中断测量编码器速度 请问如何通过外部中断实现编码器测速”。这是基于提供的引用内容,所以我需要参考这些引用来构建我的回答。 引用内容: - [^1]: 描述了TIM编码器测速,使用A相和B相的边沿作为数器时钟,数器根据另一相电平增减。 - [^2]: 涉及MSP432外部中断捕获法测速,包括变量定义和中断处理逻辑。 - [^3]: 提到STM32使用外部中断实现测速,并提供了GitHub链接。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户可见层: - 回答要结构清晰,逐步解决问题。 我的回答结构: 1. **介绍外部中断测速的基本原理**:解释编码器如何工作,为什么使用外部中断。 2. **逐步实现方法**: - 硬件连接:描述编码器连接到微控制器的GPIO。 - 中断配置:设置外部中断,响应编码器边沿。 - 中断处理函数:在中断中数脉冲。 - 速度算:基于脉冲数和时间算速度。 3. **参考引用内容**:自然地融入引用,如使用TIM或外部中断捕获法。 4. **代码示例**:提供伪代码或基于引用[2]和[3]的代码片段。 5. **注意事项**:如溢出处理、倍频等。 6. **生成相关问题**:在末尾添加相关问题。 确保数学表达式正确: - 例如,速度公式:$v = \frac{\Delta n \cdot k}{t}$,其中$\Delta n$是脉冲数变化,$k$是比例因子,$t$是时间间隔。 - 独立公式如:$$v = \frac{\Delta n \cdot k}{t}$$ 生成相关问题: - 基于主题,如优化方法、应用场景、常见问题。 完整回答大纲: 1. 引言:解释外部中断测速的原理。 2. 步骤: a. 硬件设置 b. 中断初始化 c. 中断服务例程 d. 速度算逻辑 3. 代码示例 4. 注意事项 5. 相关问题 使用中文回答。</think>### 外部中断实现编码器测速方法 编码器测速是机器人、电机控制等领域的常见需求,通过外部中断捕获编码器脉冲边沿,可以高效测量速度。增量式编码器通常输出A相和B相信号,其边沿变化对应旋转位置和方向。使用外部中断实现测速的核心是:在中断服务例程中数脉冲,并结合时间算速度。以下我将基于引用内容,逐步解释实现过程。整个过程确保真实可靠,参考了嵌入式系统(如STM32、MSP432)的通用实践[^1][^2][^3]。 #### 步骤1: 理解基本原理 增量式编码器的A相和B相信号在旋转时产生方波,其边沿(上升沿或下降沿)对应脉冲。速度算基于脉冲数和时间间隔: - **速度公式**:$$v = \frac{\Delta n \cdot k}{t}$$ 其中: - $v$ 是速度(单位如cm/s), - $\Delta n$ 是时间$t$内的脉冲数变化量, - $k$ 是比例因子(例如,$k = \frac{\text{轮周长}}{\text{每转脉冲数}}$,单位cm/脉冲), - $t$ 是测速周期(单位s)。 - 使用外部中断时,每个边沿触发中断,在中断中更新数。A相和B相的电平组合可判断方向(正转或反转),实现双向测速[^1][^2]。 #### 步骤2: 硬件连接与初始化 - **硬件连接**: - 将编码器的A相信号连接到微控制器(如STM32)的GPIO引脚(配置为外部中断输入)。 - B相信号连接到另一个GPIO引脚(用于判断方向)。 - 确保接地良好,避免噪声干扰。 - **初始化配置**(以STM32为例): - 启用GPIO时钟和外部中断线(如EXTI)。 - 设置GPIO模式为输入,并启用中断(上升沿和/或下降沿触发)。 - 配置NVIC(Nested Vectored Interrupt Controller)以处理中断优先级。 - 初始化时器(如TIM)用于测速周期定时[^2][^3]。 #### 步骤3: 中断服务例程实现 在外部中断处理函数中,捕获脉冲并更新数。关键点包括: - **数逻辑**: - 当A相边沿触发中断时,读取B相电平:高电平表示正转(数增加),低电平表示反转(数减少)。 - 使用全局变量记录脉冲数(如`CAPTURE`),并处理数器溢出(如使用`CAT_OUT_TIME`记录溢出次数)[^2]。 - **方向判断**:通过A相和B相信号的相位差确定旋转方向,避免误数。 - **示例中断服务例程伪代码**(基于引用[2]和[3]): ```c // 定义全局变量(参考引用[2]) uint32_t CAPTURE = 0; // 当前脉冲数 uint32_t CAT_OUT_TIME = 0; // 溢出数 uint32_t CAPTURE_LAST = 0; // 上一次数 void EXTI_IRQHandler(void) { if (EXTI_GetFlagStatus(EXTI_Line0) != RESET) { // 假设A相连接EXTI_Line0 // 读取B相电平判断方向 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == HIGH) { // B相高电平,正转 CAPTURE++; } else { // B相低电平,反转 CAPTURE--; } // 处理溢出:当CAPTURE达到最大值(如65535)时重置并增加溢出数 if (CAPTURE >= MAX_COUNT) { CAT_OUT_TIME++; CAPTURE = 0; } EXTI_ClearFlag(EXTI_Line0); // 清除中断标志 } } ``` #### 步骤4: 速度算与定时器整合 脉冲数后,使用定时器周期性算速度: - **设置测速周期**:启用一个定时器(如TIM),每$t$毫秒(例如20ms)触发中断。 - **算速度**: - 在定时器中断中,读取当前脉冲数(`CAPTURE_NEW`)。 - 算脉冲变化量:$\Delta n = \text{(CAPTURE_NEW + CAT_OUT_TIME \times MAX_COUNT) - CAPTURE_LAST}$。 - 应用速度公式:$v = \frac{\Delta n \cdot k}{t}$,其中$k$基于轮周长和编码器分辨率(例如,$k = \frac{WHEEL}{N}$,$WHEEL$为轮周长,$N$为每转脉冲数)[^2]。 - **示例速度算代码**: ```c // 定时器中断服务例程(测速周期触发) void TIM_IRQHandler(void) { if (TIM_GetFlagStatus(TIMx, TIM_FLAG_Update) != RESET) { CAPTURE_NEW = CAPTURE; // 获取最新数 uint32_t pulse_change = (CAT_OUT_TIME * MAX_COUNT + CAPTURE_NEW) - CAPTURE_LAST; CAPTURE_LAST = CAPTURE_NEW; // 更新上一次数 // 算速度(单位cm/s),假设k = WHEEL / (每转脉冲数 * 倍频因子) SPEED = (pulse_change * WHEEL) / (SPEED_MES_CYCLE / 1000.0); // SPEED_MES_CYCLE单位为ms TIM_ClearFlag(TIMx, TIM_FLAG_Update); // 清除中断标志 } } ``` #### 注意事项 - **溢出处理**:数器(如`CAPTURE`)易溢出,需用辅助变量(如`CAT_OUT_TIME`)记录高位数,确保大范围测速[^2]。 - **倍频技术**:为提升精度,可对A相和B相边沿都数(倍频),例如4倍频(每个周期4个边沿),公式中$k$需调整[^2]。 - **噪声抑制**:在GPIO配置中启用滤波(如施密特触发器),或在软件中添加去抖动逻辑(如延时读取)。 - **性能优化**:中断服务例程应尽量简短,避免复杂算;使用硬件编码器模式(如TIM)可减少CPU负载[^1]。 - **实时性**:测速周期$t$宜过长(建议10-50ms),以平衡精度和响应速度。 通过上述步骤,您可高效实现编码器测速。实际应用中,参考具体微控制器手册(如STM32的EXTI和TIM模块)调试代码[^3]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GuiStar_李什么恩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值