对射式红外传感器计次&旋转编码器计次

对射式红外传感器计次&旋转编码器计次

中断处理说明

  • 对设式红外传感器采用的是外部中断,那么何种时候何种外设要采用外部中断的处理方式呢?

​​①对实时性要求极高的事件​​:有些事件需要系统在极短时间内(例如毫秒级甚至微秒级)做出反应。例如,工业控制中的​​急停按钮​​、安全系统中的​​异常报警​​信号
。如果使用轮询,可能会因为检测的微小延迟而错过最佳处理时机,造成严重后果。
​​②事件发生频率低且随机​​:像​​按键操作​​、​​无线遥控信号的接收​​这类事件,完全是随用户操作而定,大部分时间处于“等待”状态。如果让主程序不停地去检查(轮询)按键是否被按下,CPU 时间将被大量浪费在无意义的查询上。使用中断,CPU 可以在此期间处理其他任务,只在事件发生时被“唤醒”处理,极大提高了效率。
③​​系统需要低功耗运行​​:在电池供电的物联网设备中,MCU(微控制器单元) 大部分时间处于睡眠模式以节省电量。此时,任何轮询操作都无法进行。只有像外部中断这样的​​异步事件​​才能将 CPU 从低功耗模式中唤醒,使其处理任务后继续休眠。
④总结来说,当一个外部事件​​难以预测、要求快速响应、且不常发生时,外部中断就是最佳的解决方案。它能让你的系统更高效、更实时地运作。

  • 采用外部中断的注意事项:

​​①保持中断服务函数简短高效​​:中断服务程序的黄金法则是​​快进快出​​。它不应该执行复杂的计算或包含延时操作。理想情况下,它只负责完成最紧急的任务,比如清除中断标志、设置一个事件标志位,或者将数据存入缓冲区,然后迅速退出。具体的处理逻辑应放到主循环中完成
​​②妥善处理中断抖动​​:特别是机械触点(如按键、继电器)在通断瞬间会产生一系列抖动的电信号,可能导致一次动作触发多次中断。常用的解决方法包括硬件上并联电容进行滤波,或在软件中断服务函数中加入​​简单的延时消抖(Delay函数)逻辑​​。延时消抖的具体时间需要根据实际情况来确定。
③​​
合理配置中断优先级​​
:当一个系统有多个中断源时,你需要通过 NVIC 为它们设置合理的​​抢占优先级​​和​​子优先级​​。这确保了更紧急的事件(如系统故障报警)能够打断正在处理的普通事件(如按键扫描),保证系统的实时性和可靠性。

  • 外部中断的参与设备:
    进行外部中断需要对GPIO、AFIO、EXTI、NVIC等进行中断设置。外部中断流程图:
    在这里插入图片描述
    NVIC(内核设备)中断流程图:
    在这里插入图片描述

对射式红外传感器计次

工程步骤及代码
  • 接线图如图:
    在这里插入图片描述

  • 外部中断涉及很多元件,故设置也比常规的复杂很多,如图:

  • 在Hanrware文件夹下新建一个CountSensor.h文件和CountSensor.c文件,CountSensor.c代码如下:
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"


uint8_t CountNum;

//初始化外部中断
void CountSensor_Init(void)
{	
	//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//开启AFIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	//GPIO_Mode_IPU为上拉输入,将GPIO没有信号输入的时候默认置为高电平
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	//指定GPIOB的第14号引脚有权进入AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	
	//初始化EXTI函数,EXTI只有一个结构体参数
	EXTI_InitTypeDef EXTI_InitStructure;
	//GPIO的14号引脚和EXTI的14号对应
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	//使用下降沿(EXTI_Trigger_Falling,电平由1到0触发)的触发方式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	//EXTI_Init只有一个参数
	EXTI_Init(&EXTI_InitStructure);
	
	//配置NVIC,因为NVIC是内核函数,应在杂项文件(misc.h)中寻找
	//NVIC分组:确定几位为抢占优先级,几位为响应优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//NVIC初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	//选择的引脚为GPIO14,对应EXTI14,EXTI14包含于EXTI15_10_IRQn,IRQ:中断请求
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	//在NVIC_PriorityGroupConfig函数中选用的组为2组,对应抢占优先级PreemptionPriority和响应优先级SubPriority的范围均为0~3
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	//NVIC_Init只有一个参数
	NVIC_Init(&NVIC_InitStructure);

}

//中断计数函数
uint8_t Count(void)
{
	return CountNum;
}

//中断函数,在10到15这个分组内判断是不是要求的14号引脚信号传入
void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line14) == SET)
    {	
    //第二个if嵌套是为了消除抖动
		if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			CountNum++;
		}
		EXTI_ClearITPendingBit(EXTI_Line14);
		
        
    }
}

值得注意的是:
①要开启GPIO和AFIO两个时钟,EXTI和NVIC时钟不用管.
②在配置AFIO时只需要GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);一句代码即可,指定好那个GPIO的哪个引脚信号有权进入AFIO.
③配置EXTI函数(只有一个形参)也是配置一个结构体,该结构体有四个参数,其中一个参数要选择触发方式,有上升沿触发、下降沿触发、上升和下降沿触发,上升沿触发是指从0电平跳变为1电平时触发,其余两种类似。具体如图所示:
在这里插入图片描述
④在配置NVIC时要先确定NVIC的分组,具体分组如图所示, 抢占优先级是需要立即执行的程序,子优先级也就是响应优先级,是排队等待的程序
在这里插入图片描述
⑤继续配置NVIC,NVIC也是一个结构体函数,该结构体有四个参数,也是只含有一个形参,不过NVIC属于内核程序,官方给的函数文件在misc.h中查看.
⑥在中断函数中,第一个if语句的判断条件EXTI_GetITStatus(EXTI_Line14) == SET是为了检查EXTI14号线是否有信号通过,因为我外接设备选用的是GPIO14号引脚,对应EXTI的14号信号通道,而EXTI的14号信号通道对应NVIC的中断请求分组里(NVIC_IRQChannel )的EXTI15_10_IRQn分组(包含第10号信道到15号信道),故引用此if语句判断进来的信号是不是从我规定的第14号信道进来的.
在中断函数中,第二个if嵌套是为了消除抖动.

main.c代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	OLED_Init();
	CountSensor_Init();
	OLED_ShowString(1,1,"Count:");
	OLED_ShowString(2,1,"Level:");
		
	while(1)
	{
		OLED_ShowNum(1,7,Count(),5);
		OLED_ShowNum(2,7,GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14),1);
	}
}

旋转编码器计次

工程步骤及代码
  • 接线图如图:
    在这里插入图片描述
    与 对设式红外传感器计次相似,不再过多赘述.代码如下:
    Encoder.c代码:
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count;
//初始化外部中断
void Encoder_Init(void)
{	
	//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//开启AFIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	//GPIO_Mode_IPU为上拉输入,将GPIO没有信号输入的时候默认置为高电平
	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);
	
	//指定GPIOB的第0号和第1号引脚有权进入AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
	
	//初始化EXTI函数,EXTI只有一个结构体参数
	EXTI_InitTypeDef EXTI_InitStructure;
	//GPIO的14号引脚和EXTI的14号对应
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	//使用下降沿(EXTI_Trigger_Falling,电平由1到0触发)的触发方式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_Init(&EXTI_InitStructure);
	
	//配置NVIC,因为NVIC是内核函数,应在杂项文件(misc.h)中寻找
	//NVIC分组:确定几位为抢占优先级,几位为响应优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	//NVIC初始化
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	//选择的引脚为GPIO0,对应EXTI0,IRQ:中断请求
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);	
	//选择的引脚为GPIO1,对应EXTI0,IRQ:中断请求
	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&NVIC_InitStructure);
	}

//编码器反转
void EXTI0_IRQHandler (void)
{
	if(EXTI_GetFlagStatus(EXTI_Line0) == SET)
	{
		//第二个if语句是为了防止抖动现象干扰程序正常运行
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == 0)
		{
			Encoder_Count--;
		}
	 EXTI_ClearITPendingBit(EXTI_Line0);  
	}
}

//编码器正转
void EXTI1_IRQHandler (void)
{
	if(EXTI_GetFlagStatus(EXTI_Line1) == SET)
	{	
		//第二个if语句是为了防止抖动现象干扰程序正常运行
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0) == 0)
		{
			Encoder_Count++;
		}
	EXTI_ClearITPendingBit(EXTI_Line1);  
	}
}

//计数器实现
int16_t Count_Encoder(void)
{
	int16_t Temp;
	Temp = Encoder_Count;
	Encoder_Count = 0;
	return Temp;
}

main.c代码:

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

int16_t Num;
int main(void)
{
	OLED_Init();
	Encoder_Init();
	OLED_ShowString(1,1,"Count:");
	
	while(1)
	{
		Num += Count_Encoder();	
		OLED_ShowSignedNum(1,7,Num,5);
	}
}

值得注意的是:
①在计数器实现函数int16_t Count_Encoder(void);中可以就写一句return Encoder_Count;在main.c直接将此数字打印到OLED屏幕上,但是为什么不这么做呢?
答:可能是因为Encoder_Count是一个全局变量,可能在​​任何时候被中断服务程序修改​​,使用Temp返回,在main.c函数中作累加和保存,防止因为中断崩溃Encoder_Count丢失.

效果图
  • 正转屏幕显示正数次数:
  • 反转显示负数次数:
<think> 首先,用户的问题是:“我想获取使用中断、对射红外传感器旋转编码器的方法”。我需要参考提供的引用内容来构建回答。 回顾引用内容: - 引用[1]:解释了使用对射红外传感器和一个光栅编码盘来测量速度和位置。它提到输出方波信号,用外部中断捕获边沿来判断位置和速度,但由于只有一路输出,无法区分方向。建议使用其他编码器来测量方向。 - 引用[2]:描述了在STM32中,当挡光片经过对射红外传感器时,触发中断(PB14号口),在中断函数中执行变量++程序,并在主循环中显示变量。涉及设计思路。 - 引用[3]:列出了STM32 EXTI外部中断相关的库函数,如GPIO_PinRemapConfig、GPIO_EXTILineConfig等,用于引脚重映射和中断配置。 用户的需求是了解中断、对射红外传感器旋转编码器的使用方法。结合引用,这似乎是针对STM32微控制器的应用。 系统指令要求: - 使用中文回答。 - 所有行内数学表达用$...$格。 - 独立公用$$...$$格单独成段。 - LaTeX语法正确。 - 生成相关问题。 - 在回答中引用的段落末尾添加引用标识,如[^1]。 回答结构: - 回答应该结构清晰,帮助用户逐步解决问题。 - 保持真实可靠,基于引用内容。 步骤: 1. **理解概念**:首先解释对射红外传感器旋转编码器和中断的基本原理。 2. **硬件连接**:描述传感器和编码器如何连接到STM32。 3. **软件配置**:使用STM32 HAL库或标准外设库配置中断。 4. **中断处理**:编写中断服务程序(ISR)来捕获信号。 5. **应用示例**:基于引用,提供一个简单的代码示例,比如计次应用。 6. **注意事项**:讨论方向测量问题,如引用[1]提到的单路输出限制。 7. **相关问题**:在最后生成相关问题。 关键点从引用: - 对射红外传感器产生方波信号(高低电平变化)。 - 使用外部中断(EXTI)捕获信号的边沿(上升沿或下降沿)。 - 在中断函数中计数(如变量++)。 - 主循环中显示计数值。 - 配置涉及GPIO初始化、中断优先级设置等。 - 引用[3]提到库函数:GPIO_EXTILineConfig等用于配置中断。 生成代码示例: - 类似引用[2]的描述,提供一个简单的STM32中断处理代码。 - 使用C语言,因为引用中是STM32相关的。 添加引用标识: - 在引用内容后自然添加[^1], [^2], [^3]。 行内数学表达: - 可能涉及频率计算,如方波频率表示转速。公:$f = \frac{n}{t}$,其中f是频率,n是脉冲数,t是时间。 - 或者速度公:$v = \frac{\Delta \theta}{\Delta t}$,但保持简单。 独立公:如果有必要,单独成段。 最终回答结构: 1. **介绍基本原理** 2. **硬件设置** 3. **软件配置步骤** 4. **代码示例** 5. **方向测量扩展(可选)** 6. **相关问题** 相关问题生成: - 基于内容,如: - 如何区分旋转方向? - 如何配置STM32中断优先级? - 如何处理中断抖动? - 其他传感器应用。 确保LaTeX正确: - 行内:例如,方波的频率$f$表示转速。 - 独立:如果有公。 现在,撰写回答。 </think> ### 中断、对射红外传感器旋转编码器的使用方法 在STM32微控制器中,结合外部中断(EXTI)、对射红外传感器旋转编码器,可以实现位置计数、速度测量等功能。对射红外传感器通过检测光栅编码盘的遮挡变化输出方波信号(高低电平交替),外部中断捕获这些信号边沿(如上升沿或下降沿)进行计数;旋转编码器通常输出多路信号以区分方向(如A/B相)。下面我将基于引用内容,一步步解释配置和使用方法。整个过程包括硬件连接、软件配置和中断处理,确保结构清晰可靠。 #### 1. **工作原理概述** - **对射红外传感器**:当光栅编码盘(或挡光片)在传感器之间旋转时,红外光被交替遮挡和透射,输出方波信号。方波的个数代表转过的角度(位置),方波的频率$f$表示转速($f = \frac{n}{t}$,其中$n$是脉冲数,$t$是时间)[^1]。 - **旋转编码器**:如果使用单路输出编码器(如简单光栅盘),只能测量位置和速度;若使用双路输出(如正交编码器),通过比较A/B相信号相位差,可以区分正反转方向[^1]。 - **外部中断(EXTI)**:STM32的EXTI模块监视GPIO引脚的电平变化(如边沿触发),当检测到变化时,触发中断服务程序(ISR),在ISR中执行计数逻辑[^3]。 #### 2. **硬件设置** - **连接方**: - 将对射红外传感器的输出端(DO引脚)连接到STM32的GPIO引脚(如PB14,引用示例)。 - 如果使用旋转编码器: - 单路输出:直接连接DO到GPIO引脚。 - 双路输出:连接A相和B相到两个不同GPIO引脚(如PA0和PA1),通过相位差判断方向。 - 确保传感器供电(通常3.3V或5V),并添加适当的上拉/下拉电阻以稳定信号。 - **关键组件**:光栅编码盘(银色圆圈)安装在旋转轴上,当盘转动时,传感器输出方波[^1]。 #### 3. **软件配置步骤(基于STM32 HAL库)** 配置EXTI中断包括初始化GPIO、设置中断优先级和编写ISR。以下是详细步骤: **步骤1: GPIO初始化** - 配置GPIO引脚为输入模,开启中断功能。引用[3]提到必须使用`GPIO_EXTILineConfig`函数选择中断引脚源。 ```c // 示例:配置PB14引脚为外部中断输入 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_14; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发中断 GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上拉/下拉,传感器信号通常自带 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 配置中断源(关键步骤,引用[3]) GPIO_EXTILineConfig(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_14); // 选择PB14引脚 ``` **步骤2: EXTI中断配置** - 设置中断优先级和使能中断。引用[3]指出需处理AFIO数据选择器。 ```c // 设置中断优先级 HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0); // 假设使用EXTI15_10中断组 HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 使能中断 ``` **步骤3: 编写中断服务程序(ISR)** - 在ISR中处理信号变化,例如计数变量递增。引用[2]描述在中断中执行变量++,并在主循环显示。 ```c volatile uint32_t pulseCount = 0; // 全局变量,存储脉冲计数 // ISR定义 void EXTI15_10_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_14) != RESET) { // 检查PB14中断标志 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_14); // 清除中断标志 pulseCount++; // 每次中断触发计数增加 } } // 主函数中初始化后,循环显示计数 int main(void) { HAL_Init(); SystemClock_Config(); // 初始化GPIO和中断(如上步骤) while (1) { OLED_Display(pulseCount); // 假设OLED显示函数,引用[2] HAL_Delay(100); // 延迟以降低CPU负载 } } ``` #### 4. **方向测量扩展(可选)** - 如果使用双路旋转编码器(A/B相),可以区分正反转: - 连接A相和B相到两个GPIO引脚(如PA0和PA1)。 - 在ISR中比较A/B相信号边沿:如果A相率先变化则为正转,B相率先变化则为反转。 - 代码示例: ```c // ISR for A/B phase void EXTI0_IRQHandler(void) { // A相中断 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == HIGH) { // 读B相状态 pulseCount++; // 正转计数 } else { pulseCount--; // 反转计数 } __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); } ``` #### 5. **注意事项** - **信号抖动处理**:传感器输出可能因机械振动产生抖动,建议在硬件(加滤波器)或软件(在ISR中加延迟去抖)处理。 - **中断性能**:高转速时中断频率可能过高(方波频率$f$可能达kHz级),需优化ISR代码(避免复杂计算)或使用定时器捕获模替代。 - **引脚重映射**:引用[3]提到`GPIO_PinRemapConfig`可用于引脚复用,但本示例未涉及。 - **方向限制**:单路输出传感器无法区分方向,引用[1]建议升级到双路编码器解决。 通过以上步骤,您可以实现位置计数和速度测量应用。代码基于STM32 HAL库,实际中需根据具体MCU型号调整寄存器设置[^1][^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

必胜的思想钢印

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

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

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

打赏作者

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

抵扣说明:

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

余额充值