EXTI外部中断之对射式红外传感器计次应用案例

系列文章目录

STM32中断系统之EXTI外部中断



前言

提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本文主要探讨利用EXTI外部中断实现一个对射式红外传感器计次的功能。


一、应用案例简介

本案例利用EXTI外部中断实现了一个对射式红外传感器计次的功能。当使用挡光片挡在红外对管的中间,此时输出指示灯熄灭(输出高电平),然后再拿开挡光片,此时输出指示灯亮了(输出低电平)。在灭到亮之间会产生一个下降沿,这个下降沿触发单片机引脚的外部中断,然后执行数字加1的中断程序。如此类推,实现了对挡光次数的统计并显示在OLED显示屏上。

二、电路接线图

对射式红外传感器模块VCC、GND分别接电源正负极,DO数字输出端接到PB14口。当使用挡光片在这个对射式红外对管中间经过时,DO口就会输出电平变化的信号,这个电平跳变的信号就会触发STM32 PB14号口的中断,这就是对射式红外传感器计次的原理。

在这里插入图片描述

三、应用案例代码

传感器头文件CountSensor.h:

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
uint16_t CountSensor_Get(void);

#endif

传感器实现文件CountSensor.c:

#include "stm32f10x.h"                  // Device header

uint16_t CountSensor_Count;

void CountSensor_Init(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_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;
	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);
}

uint16_t CountSensor_Get(void)
{
	return CountSensor_Count;
}

void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			CountSensor_Count ++;
		}
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

主程序文件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:");
	
	while (1)
	{
		OLED_ShowNum(1, 7, CountSensor_Get(), 5);
	}
}

四、应用案例分析

4.1 配置外部中断

首先,在CountSensor_Init函数里完成外部中断的初始化配置。那要如何配置外部中断呢?我们可以看一下下面这个外部中断整体结构图。简单来说,我们只需要把这个外部中断从GPIO到NVIC这一路中出现的外设模块都配置好,把这条信号电路给打通就行了。

在这里插入图片描述

外部中断配置步骤如下:
1. 配置RCC: 把涉及的外设的时钟都打开。
2. 配置GPIO: 选择端口为输入模式。
3. 配置AFIO: 选择我们用的这一路GPIO并将它连接到后面的EXTI。
4. 配置EXTI: 选择边沿触发方式,比如上升沿、下降沿或者双边沿。选择触发响应方式,可以选择中断响应或事件响应,当然我们一般都是中断响应。
5. 配置NVIC: 给中断选择一个合适的优先级

最后,通过NVIC,外部中断信号就能进入CPU了,这样CPU才能收到中断信号,才能跳转到中断函数里执行中断程序,那接下来让我们一个一个来分析。

首先第一步,配置RCC时钟。

4.1.1 配置RCC时钟

  • 调用RCC_APB2PeriphClockCmd函数分别开启GPIOB和AFIO(AFIO也是APB2的外设)的时钟。
  • 接着还有EXTI和NVIC两个外设的时钟,这两个外设的时钟是一直都打开着的,所以不需要我们再开启时钟了。

注意:EXTI作为一个独立的外设,按理来说应该是需要开启时钟的,但是寄存器里面却没有EXTI时钟的控制位,这个原因手册里也没有找到,网上也没有确切的答案。可能是和EXTI唤醒有关,或者是其他一些电路上设计的考虑,那我们暂时就不用管了,知道它不需要开启时钟就行了。另外NVIC也不需要开启时钟,因为NVIC是内核的外设,内核的外设都是不需要开启时钟的。人家跟CPU一起都是住“皇宫”里的,而RCC管的都是内核外的外设,所以RCC管不着NVIC。

配置RCC时钟代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

好,那到这里时钟就配置完了,接着我们就来进行第二步,配置GPIO。

4.1.2 配置GPIO

GPIO的配置在前面GPIO的章节已经讲过了,这里就不再累述了,在这里只讲一下一些需要注意的点。

第一个GPIO_Mode 参数,对于外部中断来说,要选择浮空输入、上拉输入或者下拉输入其中的一个模式。当然,像这种其他外设使用GPIO口的情况,如果你不清楚该配置为什么模式,可以看一下参考手册。找到GPIO这一章,有一个外设的GPIO配置表,里面有写每个外设的各个引脚都需要配置为什么模式。

在这里插入图片描述

那在最后,就有EXTI输入线,他给的推荐配置就是浮空、上拉或者下拉。所以在这里,我就给一个GPIO_Mode_IPU,上拉输入,默认为高电平的输入方式。

在这里插入图片描述

GPIO初始化配置部分代码如下:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

这样GPIO部分我们就配置好了,然后我们就来进行第三步,配置AFIO。

4.1.3 配置AFIO

简单介绍一下AFIO相关函数。这个AFIO外设,ST公司并没有给它分配专门的库函数文件,它的库函数是和GPIO在一个文件里的,那我们找一下GPIO的.h文件,找到stm32f10x_gpio.h文件,然后拉到最后。

void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

首先是 GPIO_AFIODeInit 函数,这个函数是用来复位AFIO外设的,调用一下这个函数,AFIO外设的配置就会全部清除。

然后下面这个 GPIO_PinLockConfig 函数,这个函数是用来锁定GPIO配置的,调用这个函数,参数指定某个引脚,那这个引脚的配置就会被锁定,防止意外更改。这个也是GPIO外设的函数,用的不多,了解一下即可。

然后是 GPIO_EventOutputConfigGPIO_EventOutputCmd 函数,这两个函数是用来配置AFIO的事件输出功能的,用得也不多,了解一下即可。

接下来 GPIO_PinRemapConfigGPIO_EXTILineConfig 函数,这两个函数就比较重要了。

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

GPIO_PinRemapConfig 函数可以用来进行引脚重映射。第一个参数可以选择你要重映射的方式,第二个参数是新的状态。

然后 GPIO_EXTILineConfig 函数就是我们本章节外部中断需要用到的函数了。调用这个函数,就可以配置AFIO的数据选择器,来选择我们想要中断的引脚。

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

那最后一个,GPIO_ETH_MediaInterfaceConfig 函数,这个是和以太网有关的。

先回到这个主线任务上来,我们现在想要配置AFIO外部中断引脚选择,也就是调用 GPIO_EXTILineConfig 函数即可,以下是该函数的定义及说明。

**
  * @brief  Selects the GPIO pin used as EXTI Line.
  * @param  GPIO_PortSource: selects the GPIO port to be used as source for EXTI lines.
  *   This parameter can be GPIO_PortSourceGPIOx where x can be (A..G).
  * @param  GPIO_PinSource: specifies the EXTI line to be configured.
  *   This parameter can be GPIO_PinSourcex where x can be (0..15).
  * @retval None
  */
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
{
  uint32_t tmp = 0x00;
  /* Check the parameters */
  assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
  assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
  
  tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
  AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
  AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
}

那上面这里简介写的是,选择GPIO pin作为外部中断线。第一个参数是GPIO_PortSource,选择某个GPIO外设作为外部中断源,这个参数可以是GPIO_PortSourceGPIOx,其中x可以是A到G。我们用的是PB14号引脚,所以就是GPIO_PortSourceGPIOB。第二个参数是GPIO_PinSource,指定要配置的外部中断线,这个参数可以是GPIO_PinSourcex,其中x可以是0到15。所以我们这里就是GPIO_PinSource14,代表连接PB14号口的第14个中断线路。

到这里,AFIO外部中断引脚选择配置就完成了,直接调用 GPIO_EXTILineConfig 函数即可。当执行完这个函数之后,AFIO的第14个数据选择器就拨好了,其中输入端被拨到了GPIOB外设上,对应的就是PB14号引脚,输出端固定连接的是EXTI的第14个中断线路。这样,PB14号引脚的电平信号就可以顺利通过AFIO进入到后级电路EXTI了。

AFIO配置代码如下:

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

那接下来,我们就可以进入第四步,配置EXTI了。

4.1.4 配置EXTI

我们先来看一下EXTI的库函数文件,看一下EXTI都有哪些库函数可以用。我们找到stm32f10x_exti.h文件,拖到最后,这些就是EXTI的所有库函数了。

void EXTI_DeInit(void);
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

首先第一个,EXTI_DeInit函数,调用它就可以把EXTI的配置都清除,恢复成上电默认的状态。

然后第二个,EXTI_Init函数,调用这个函数就可以根据这个结构体里的参数配置EXTI外设,我们初始化EXTI主要用到的就是这个函数。

接着第三个,EXTI_StructInit函数,调用这个函数,可以把参数传递的结构体变量赋一个默认值。

那接着看第四个,EXTI_GenerateSWInterrupt函数,这个函数是用来软件触发外部中断的,调用这个函数,参数给一个指定的中断线,就能软件触发一次这个外部中断。

然后剩下的这四个函数也是库函数的模版函数,很多模块都有这四个函数。因为在外设运行的过程中会产生一些状态标志位,比如外部中断来了,是不是会有一个挂起寄存器置了一个标志位?对于其他外设来说,比如串口收到数据会置标志位,定时器时间到也会置标志位。这些标志位都是放在状态寄存器里的,当程序想要获取这些标志位时,就可以用到这四个函数。

其中,这前两个函数。EXTI_GetFlagStatus函数可以获取指定的标志位是否被置1了,EXTI_ClearFlag函数可以对置1的标志位进行清除。那对于这些标志位,有的比较紧急,在置标志位后会触发中断。在中断函数里,如果你想查看标志位和清除标志位那就用下面两个函数。EXTI_GetITStatus函数可以获取中断标志位是否被置1了,EXTI_ClearITPendingBit函数可以清除挂起标志位。

所以总结一下就是,如果你想在主程序里查看和清除标志位,就用前两个函数EXTI_GetFlagStatus和EXTI_ClearFlag,如果你想在中断函数里查看和清除标志位,就用下面这两个函数。其实本质上,这四个函数都是对状态寄存器的读写。上面两个和下面两个是类似的功能,都是读写状态寄存器,只不过是下面这两个函数只能读写与中断有关的标志位,并且对中断是否允许做出了判断,而上面这两个函数只是一般的读写标志位,没有额外的处理,能不能触发中断的标志位都能读取。所以建议在主程序里用上面两个,中断程序里用下面两个。当然你如果非要在中断里用上面两个那其实也是没有问题的,只不过是库函数针对这两种情景,区分了这两类读写函数。

ok,那我们进入正题,完成EXTI的配置调用EXTI_Init函数即可,我们先来看一下该函数的声明。

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

再来看一下EXTI_InitTypeDef结构体的声明

** 
  * @brief  EXTI Init Structure definition  
  */

typedef struct
{
  uint32_t EXTI_Line;               /*!< Specifies the EXTI lines to be enabled or disabled.
                                         This parameter can be any combination of @ref EXTI_Lines */
   
  EXTIMode_TypeDef EXTI_Mode;       /*!< Specifies the mode for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  EXTITrigger_TypeDef EXTI_Trigger; /*!< Specifies the trigger signal active edge for the EXTI lines.
                                         This parameter can be a value of @ref EXTIMode_TypeDef */

  FunctionalState EXTI_LineCmd;     /*!< Specifies the new state of the selected EXTI lines.
                                         This parameter can be set either to ENABLE or DISABLE */ 
}EXTI_InitTypeDef;

首先是第一个参数EXTI_Line,这个是指定我们要配置的中断线。那我们需要用PB14所在的第14个线路,所以选择EXTI_Line14。

接下来看第二个参数EXTI_Mode,这个是指定外部中断线的模式。有中断模式和事件模式,那我们这里选中断模式,选择EXTI_Mode_Interrupt。

接着再看第三个参数EXTI_Trigger,这个是指定触发信号的有效边沿。有上升沿触发、下降沿触发和双边沿触发。我这里选一个下降沿触发EXTI_Trigger_Falling。

最后看第四个参数EXTI_LineCmd,这个是指定选择的中断线的新状态。我们选择开启中断,所以选择ENABLE。

EXTI外部中断代码如下:

EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);

这样我们的外部中断就配置完成了,我们当前的配置是,将EXTI的第14个线路配置为中断模式,下降沿触发,然后开启中断,这样PB14的电平信号就能通过EXTI通向下一级NVIC了。

那最后,我们就来执行第五步,配置NVIC。

4.1.5 配置NVIC

我们还是先看一下库函数文件里的函数,因为NVIC是内核外设,所以它的库函数是被ST发配到杂项这里来了,我们打开misc.h文件,拖到最后,这些就是NVIC相关函数了。

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);

我们先来看一下第一个函数NVIC_PriorityGroupConfig,这个函数是用来中断分组的,参数是中断分组的方式。

然后第二个函数NVIC_Init,根据结构体里面指定的参数初始化NVIC。

接着看第三个函数NVIC_SetVectorTable,设置中断向量表。第四个函数NVIC_SystemLPConfig,系统低功耗配置。这两个函数用的不多,了解即可。

在配置中断之前,先指定一下中断的分组然后再使用NVIC_Init函数初始化NVIC就行了,大概的思路就是这样。

先来看一下NVIC_PriorityGroupConfig函数的定义及相关说明。

**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

这个简介是配置优先级分组:先占优先级和从占优先级,这里先占优先级就是抢占优先级,从占优先级就是响应优先级,我们这里选择NVIC_PriorityGroup_2。

好,那我们进入最后一步,配置NVIC,先来看一下NVIC_Init函数的声明。

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

NVIC_InitTypeDef结构体声明如下:

** 
  * @brief  NVIC Init Structure definition  
  */

typedef struct
{
  uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.
                                                   This parameter can be a value of @ref IRQn_Type 
                                                   (For the complete STM32 Devices IRQ Channels list, please
                                                    refer to stm32f10x.h file) */

  uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channel
                                                   specified in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specified
                                                   in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;

我们先来看一下第一个参数NVIC_IRQChannel,这个是指定中断通道来开启或关闭,我们来看一下关于这个参数的描述。

/*!< Specifies the IRQ channel to be enabled or disabled.This parameter can be a value of @ref IRQn_Type (For the complete STM32 Devices IRQ Channels list, please refer to stm32f10x.h file) */

这里有个括号,写的是对于完整的STM32中断通道列表,请参考stm32f10x.h文件,这个意思是IRQn_Type的定义不在这个文件,需要到stm32f10x.h文件里找。

在这里插入图片描述

可以看到这里有非常多的中断通道,因为这个库函数可以兼容所有的F1系列芯片,但是不同的芯片中断通道列表是不一样的,所以这里有很多条件编译用来选择你使用芯片的中断通道列表。由于我们的芯片是MD中等密度的,所以只需要展开这个MD的条件编译即可。

然后找到这个EXTI15_10_IRQn,STM32的EXTI10到EXTI15都是合并到这个通道里,这样通道就指定好了。

接下来两个参数NVIC_IRQChannelPreemptionPriority、NVIC_IRQChannelSubPriority,就是指定所选通道的抢占优先级和响应优先级了。

我们跳转到它的定义,然后找到这张表,我们在前面选择了分组2 NVIC_PriorityGroup_2,那抢占优先级和响应优先级的取值范围都是0 ~ 3,这里我将抢占优先级和响应优先级都设置为1。

** @defgroup NVIC_Priority_Table 
  * @{
  */

**
@code  
 The table below gives the allowed values of the pre-emption priority and subpriority according
 to the Priority Grouping configuration performed by NVIC_PriorityGroupConfig function
  ============================================================================================================================
    NVIC_PriorityGroup   | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority  | Description
  ============================================================================================================================
   NVIC_PriorityGroup_0  |                0                  |            0-15             |   0 bits for pre-emption priority
                         |                                   |                             |   4 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------
   NVIC_PriorityGroup_1  |                0-1                |            0-7              |   1 bits for pre-emption priority
                         |                                   |                             |   3 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_2  |                0-3                |            0-3              |   2 bits for pre-emption priority
                         |                                   |                             |   2 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_3  |                0-7                |            0-1              |   3 bits for pre-emption priority
                         |                                   |                             |   1 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_4  |                0-15               |            0                |   4 bits for pre-emption priority
                         |                                   |                             |   0 bits for subpriority                       
  ============================================================================================================================
@endcode
*/

最后一个参数NVIC_IRQChannelCmd,指定中断通道是使能还是失能,我们选择ENABLE。

NVIC配置代码如下:

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

那到这里,整个外部中断的配置也就结束了。外部中断的信号从GPIO到AFIO,再到EXTI,再到NVIC,最终通向CPU,这样才能让CPU由主程序跳转到中断程序里执行。

4.2 编写中断函数

在STM32中,中断函数的名字都是固定的,每个中断通道都对应一个中断函数。中断函数的名字我们可以参考一下启动文件,这里面以IRQHandler结尾的字符串就是中断函数的名字,我们可以找到这个EXTI15_10_IRQHandler这一项,这就是EXTI10到EXTI15的中断函数(中断函数都是无参数无返回值的)。

中断函数代码如下:

void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			CountSensor_Count ++;
		}
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}

在中断函数里,一般都是先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数。因为这个函数是EXTI10到EXTI15都能进来,所以要先判断一下是不是我们想要的EXTI14进来的。最后,中断结束后一定要再调用一下清除中断标志位的函数,因为只要中断标志位置1了,程序就会跳转到中断函数。如果你不清除中断标志位,那它就会一直申请中断,这样程序就会不断响应中断,执行中断函数,那程序就会卡死在中断函数里了。所以我们每次中断程序结束后,都应该清除一下中断标志位。

对射式红外传感器计次工程代码:EXTI外部中断之对射式红外传感器计次应用案例

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在CubeIDE中编写C语言代码来控制对射红外传感器中断流水灯可以分为以下几个步骤: 1. 创建一个新的工程:在CubeIDE中创建一个新的工程,并选择适合你的开发板。 2. 配置相应的引脚:使用CubeIDE的Pinout & Configuration工具配置红外传感器的发射器和接收器引脚,以及LED灯的引脚。 3. 编写代码:打开main.c文件,编写以下代码: ```c #include "main.h" #include "stm32f1xx_hal.h" // 定义红外发射器引脚 #define IR_EMITTER_PIN GPIO_PIN_0 #define IR_EMITTER_PORT GPIOA // 定义红外接收器引脚 #define IR_RECEIVER_PIN GPIO_PIN_1 #define IR_RECEIVER_PORT GPIOA // 定义LED灯引脚 #define LED_PIN GPIO_PIN_13 #define LED_PORT GPIOC // 定义中断状态变量 volatile uint8_t irInterruptFlag = 0; // 中断回调函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == IR_RECEIVER_PIN) { // 红外接收器中断触发 irInterruptFlag = 1; } } int main(void) { HAL_Init(); SystemClock_Config(); // 初始化GPIO GPIO_InitTypeDef GPIO_InitStruct = {0}; // 初始化红外发射器引脚 GPIO_InitStruct.Pin = IR_EMITTER_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(IR_EMITTER_PORT, &GPIO_InitStruct); // 初始化红外接收器引脚 GPIO_InitStruct.Pin = IR_RECEIVER_PIN; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(IR_RECEIVER_PORT, &GPIO_InitStruct); // 初始化LED灯引脚 GPIO_InitStruct.Pin = LED_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct); while (1) { if (irInterruptFlag) { // 红外接收器中断触发,LED灯亮起 HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET); HAL_Delay(500); // 可根据需要调整延时时间 HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET); irInterruptFlag = 0; } } } ``` 以上代码假设红外发射器引脚连接到PA0,红外接收器引脚连接到PA1,LED灯引脚连接到PC13。代码中使用了STM32的HAL库来进行GPIO的初始化和中断处理。 4. 编译和下载:编译代码并下载到你的开发板中。 请注意,在实际使用中,你可能需要根据你的硬件连接和需求进行适当的修改。此外,还需要根据你所使用的具体开发板和外设接口,进行相应的引脚配置和库函数调用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值