【WB32库开发】第7章 外部中断/事件控制器(EXTI)

在上一章中简单介绍了NVIC。大家对中断的管理机制应该有了一定的了解,本章就通过学习WB32F103上的控制器资源来带领大家了解NVIC的实际应用。
若对基础知识有一定了解的可从7-5节“固件库例程讲解—配置EXTI”开始看起。

7.1 EXTI简介

EXTI,全称External interrupt /event controller,即外部中断/事件控制器,包含多达 19 个用于产生事件/中断请求的边沿检测器。
每根输入线都可单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发)。每根输入线还可单独屏蔽。挂起寄存器用于保持中断请求的状态线。

7.2EXTI功能框图剖析

其实我们在学习例程库时,仅需学会使用对应函数即可,但在博客当中我们仍将部分原理简单讲述一下,希望大家在会用的基础上,也了解一些底层的原理,在以后的学习中学会自己分析外设功能。
EXTI的功能框图如图7-1所示,大家可以看到图中很多信号线上打了一个斜杠并标注“19”字样,这是表示在控制器内部类似的信号线路有19个,这与EXTI总共有19个中断/事件线是吻合的,因此学习其中一个便可。
EXTI分为两部分功能:一个是产生中断,另一个是产生事件,这两个功能在硬件电路上有所不同,从图中标号3处位置分出两条线路,一条3-4-5用于产生中断,一条3-6-7-8用于产生事件。
请添加图片描述

                              图7-1EXTI功能框图

首先看产生中断的线路(1-2-3-4-5):
编号 1 是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件。输入线一般是存在电平变化的信号,我们在配置触发信号时可以了解。

编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR)和下降沿触发选择寄存器(EXTI_FTSR)对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号0。而EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。

编号 3 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器(EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1 就可以输出 1 给编号 4 和编号 6 电路。

编号 4 电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果EXTI_IMR 设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR)内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。

编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。

接着看产生事件的线路(1-2-3-6-7-8),此线路最终输出一个脉冲信号:
编号1到编号3电路与产生中断线共用,不再赘述。
编号6 电路是一个与门,它一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。如果 EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_EMR 来实现是否产生事件的目的。

编号 7 是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。

编号 8 是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换。

产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。

7.3中断/事件线

EXTI共有19个中断/事件线,每个GPIO都可以被设置成为输入线中断/事件线 输入源 ,三根特定外设中断/事件线由外设触发。见表7-1

                                           表7-1 EXTI中断/事件线
中断/事件线输入源
EXTI0PX0(X 可为 A,B,C,D)
EXTI1PX1(X 可为 A,B,C,D)
EXTI2PX2(X 可为 A,B,C,D)
EXTI3PX3(X 可为 A,B,C,D)
EXTI4PX4(X 可为 A,B,C,D)
EXTI5PX5(X 可为 A,B,C,D)
EXTI6PX6(X 可为 A,B,C,D)
EXTI7PX7(X 可为 A,B,C,D)
EXTI8PX8(X 可为 A,B,C,D)
EXTI9PX9(X 可为 A,B,C,D)
EXTI10PX10(X 可为 A,B,C,D)
EXTI11PX11(X 可为 A,B,C,D)
EXTI12PX12(X 可为 A,B,C,D)
EXTI13PX13(X 可为 A,B,C,D)
EXTI14PX14(X 可为 A,B,C,D)
EXTI15PX15(X 可为 A,B,C,D)
EXTI16PVD 输出
EXTI17RTC 闹钟事件
EXTI18USB 唤醒事件

EXTI0-EXTI15用于GPIO,我们可以通过编程控制任何一个GPIO作为EXTI的输入源,由表7-1可知,EXTI0可以通过AFIO的外部中断配置寄存器1(AFIO_EXTICR1)的EXTI0[3:0]位选择配置为PA0、PB0、PC0、PD0(请注意自己实际使用的开发板GPIO端口),其他EXTI线(EXTI中断/事件线)的配置类似。

此时心中产生很多疑惑的同学们不必纠结,后续的实践部分会帮助我们更好的了解理论知识。

7.4 EXTI初始化结构体

关于C语言中的结构体知识不再赘述,感兴趣的话可以自行复习C语言的相关知识,在目前WB32库函数的教程中只要会使用结构体来配置相应外设功能即可。

/** 
  * @brief  EXTI Init Structure definition  
  */
typedef struct
{
  uint32_t EXTI_Line;               /*!< 指定中断/事件线 */
   
  EXTIMode_TypeDef EXTI_Mode;       /*!< 指定EXTI模式 */

  EXTITrigger_TypeDef EXTI_Trigger; /*!< 指定EXTI触发类型 */

  FunctionalState EXTI_LineCmd;     /*!< 指定EXTI使能或失能 */ 
} EXTI_InitTypeDef;

这就是EXTI初始化结构体了,其中包括了四个结构体成员,根据注释我们可以大致了解这些结构体成员是用来做什么的:

  1. EXTI_Line:EXTI 中断/事件线选择,可选 EXTI0 至 EXTI19,可参考表 7-1 选择。

  2. EXTI_Mode:EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。

  3. EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降沿触发 ( EXTI_Trigger_Falling) 或者上升沿和下降沿都触发( EXTI_Trigger_Rising_Falling)。

  4. EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用(DISABLE)。

7.5 固件库例程讲解—配置EXTI

看过视频教程的朋友们应该已经很熟悉如何找到固件库中的例程了,此处再最后讲解一遍:
首先找到下载的固件库(笔者目前使用的固件库版本为V0.17.1)
在这里插入图片描述
然后按照如下路径进入我们今天要学习的工程文件夹
在这里插入图片描述
进入后第一步打开readme.txt,里面的内容可以让你快速了解此工程的功能。
第二步打开工程,正式开始我们今天的学习。

在这里插入图片描述

7.5.1固件库例程分析

讲解固件库中例程时,我们先分析代码,再根据代码做出相应的硬件电路设计。
1)工程目录分析
在这里插入图片描述
在这里可以清楚的看到本工程结构,从上到下依次为:

名称说明
startup_wb32f10x.sWB32f10x启动文件
system_wb32f10x.cWB32系统源文件(主要用来配置系统时钟频率)
main.c由用户编写的主函数
misc.cmisc.c中存放着NVIC的外设驱动
wb32f10x_exti.cEXTI标准库函数
wb32f10x_gpio.cGPIO标准库函数
wb32f10x_rcc.cRCC标准库函数

2)EXTI软件设计编程要点

1.初始化用来产生中断的GPIO
2.初始化EXTI
3.配置NVIC
4.编写中断服务函数

3)main.c代码及注释
(为使新手方便阅读,删减了部分函数)

/* Includes ------------------------------------------------------------------*/
#include "wb32f10x.h"

/* Private typedef   私有类型定义----------------------------------------------*/
/* Private define    私有定义--------------------------------------------------*/
/* Private macro     私有宏定义------------------------------------------------*/
/* Private variables 私有变量--------------------------------------------------*/
EXTI_InitTypeDef   EXTI_InitStructure;
NVIC_InitTypeDef   NVIC_InitStructure;

/* Private function prototypes  私有函数原型-----------------------------*/
void EXTI0_Config(void);

/* Private functions 私有函数--------------------------------------------------*/

/**
  * @brief  Main program
  * @param  None
  * @return None
  */
int main(void)
{
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);   //设置中断优先级分组为2

  /* Enable GPIOB clock 开启GPIOB时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOB, ENABLE);

  /* Configure PB13 and PB14 in output push-pull mode 配置PB14推挽输出模式 */
  GPIO_Init(GPIOB, GPIO_Pin_14, GPIO_MODE_OUT |GPIO_OTYPE_PP |GPIO_PUPD_NOPULL |GPIO_SPEED_HIGH);

  /* Turn off LED1  关闭 LED1*/
  GPIO_SetBits(GPIOB, GPIO_Pin_14);

  /* Configure PA0 in interrupt mode 配置PA0中断模式*/
  EXTI0_Config();

  /* Generate software interrupt: simulate a falling edge applied on EXTI0 line */
	/* 生成软件中断: 在EXTI0上模拟一个下降沿触发 */
  //EXTI_GenerateSWInterrupt(EXTI_Line0);

  /* 空循环 */
  while(1)
  {
  }
}

/**
  * @brief  Configure PA0 in interrupt mode 配置PA0中断模式
  * @param  None
  * @return None
  */
void EXTI0_Config(void)                                        //配置EXTI0
{
  /* Enable GPIOA clock 使能GPIOA时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOA, ENABLE);

  /* Configure PA0 pin as input pull-down 配置PA0为下拉输入*/
  GPIO_Init(GPIOA, GPIO_Pin_0, GPIO_MODE_IN |GPIO_PUPD_DOWN);

  /* Enable AFIO clock 使能IO端口复用时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_EXTI |RCC_APB1Periph_AFIO, ENABLE);

  /* Configure and enable EXTI0 interrupt 设置且使能EXTI0中断*/
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;             //选择EXTI0作为中断源
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //配置抢占优先级为0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		   //配置子优先级为0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        	   //使能中断
  NVIC_Init(&NVIC_InitStructure);                              //初始化NVIC结构体

  /* Connect EXTI0 Line to PA0 pin 将EXTI0映射在 PA0 上*/
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

  /* Configure EXTI0 line 配置EXTI0*/
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;                   //选择EXTI0
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;          //选择产生中断
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;       //选择上升沿触发
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;                    //使能EXTI0
  EXTI_Init(&EXTI_InitStructure);                              //初始化EXTI结构体

}


/**
  * @brief  This function handles External line 0 interrupt request.处理外部中断EXTI0请求的函数
  * @param  None
  * @return None
  */
void EXTI0_IRQHandler(void)                                    /*中断服务函数 */
{
  if(EXTI_GetITStatus(EXTI_Line0) != RESET)                    //判断EXTI_Line0中断标志位的状态
  {
    /* Toggle LED1 */
    GPIO_ToggleBits(GPIOB, GPIO_Pin_14);					   //若产生中断则翻转PB14的电平输出

    /* Clear the EXTI line 0 pending bit */
    EXTI_ClearITPendingBit(EXTI_Line0);                        //清除EXTI_Line0中断标志位,为下一次进入中断做准备
  }
} 


4)代码分析
新手在看到如上代码时可能会有些迷茫,那么我们来根据本工程要实现的功能对EXTI配置过程进行逐条分析。
首先,要明确本例程中设置了一个中断向量PA0(EXTI0),若检测到触发信号,则执行EXTI0中断服务函数,即翻转PB14所在GPIO的电平,从而达到点亮或关闭LED灯的目的。

首先看配置中断的函数 EXTI0_Config(void)

void EXTI0_Config(void)                                        //配置EXTI0
{
  /* Enable GPIOA clock 使能GPIOA时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOA, ENABLE);

  /* Configure PA0 pin as input pull-down 配置PA0为下拉输入*/
  GPIO_Init(GPIOA, GPIO_Pin_0, GPIO_MODE_IN |GPIO_PUPD_DOWN);

  /* Enable AFIO clock 使能IO端口复用时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_EXTI |RCC_APB1Periph_AFIO, ENABLE);

  /* Configure and enable EXTI0 interrupt 设置且使能EXTI0中断*/
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;             //选择EXTI0作为中断源
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //配置抢占优先级为0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		   //配置子优先级为0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        	   //使能中断
  NVIC_Init(&NVIC_InitStructure);                              //初始化NVIC结构体

  /* Connect EXTI0 Line to PA0 pin 将EXTI0映射在 PA0 上*/
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

  /* Configure EXTI0 line 配置EXTI0*/
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;                   //选择EXTI0
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;          //选择产生中断
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;       //选择上升沿触发
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;                    //使能EXTI0
  EXTI_Init(&EXTI_InitStructure);                              //初始化EXTI结构体

}

若使用EXTI,首先需要开启所使用的GPIO端口的时钟且初始化,用到的EXTI必须在开启EXTI时钟的同时开启AFIO时钟。

作为中断/事件输入线时,需要把GPIO配置为输入模式,可以根据自己的要求配置GPIO输入的上下拉状态,本例程中PA0配置为下拉输入,即默认PA0端口为低电平(0)。

使用NVIC_InitStructure这个宏定义名称调用NVIC_InitTypeDef中的结构体成员,完成对PA0的优先级配置,并使能中断通道(此处不理解的请复习第五章内容)。

GPIO_EXTILineConfig函数用来指定中断/事件线的输入源,本质上是设定外部中断配置寄存器的 AFIO_EXTICRx 值,该函数接收两个参数,第一个参数指定 GPIO 端口源,第二个参数为选择对应 GPIO 引脚源编号。

我们的目的是产生中断,执行中断服务函数,EXTI 选择中断模式,PA0使用上升沿触发方式,并使能 EXTI 线。

本部分EXTI配置已经完毕,接下来进入中断服务函数中:

void EXTI0_IRQHandler(void)                                    /*中断服务函数 */
{
  if(EXTI_GetITStatus(EXTI_Line0) != RESET)                    //判断EXTI_Line0中断标志位的状态
  {
    /* Toggle LED1 */
    GPIO_ToggleBits(GPIOB, GPIO_Pin_14);					   //若产生中断则翻转PB14的电平输出

    /* Clear the EXTI line 0 pending bit */
    EXTI_ClearITPendingBit(EXTI_Line0);                        //清除EXTI_Line0中断标志位,为下一次进入中断做准备
  }

当中断发生时,对应的中断服务函数就会被执行,我们可以实现想要完成的控制功能。

为了确保中断确实发生,在中断服务函数中调用中断标志位状态读取函数(EXTI_GetITStatus)读取外设中断标志位并判断标志位状态。

EXTI_GetITStatus 函数用来获取 EXTI 的中断标志位状态,如果 EXTI 线有中断发生函数返回“SET”否则返回“RESET”。实际上,EXTI_GetITStatus 函数是通过读取EXTI_PR 寄存器值来判断 EXTI 线状态的。

PA0的中断服务函数我们让 LED1(PB14) 翻转其状态。执行任务后需要调用 EXTI_ClearITPendingBit 函数清除 EXTI 线的中断标志位。

最后进入主函数:

int main(void)
{
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);   //设置中断优先级分组为2

  /* Enable GPIOB clock 开启GPIOB时钟*/
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 |RCC_APB1Periph_GPIOB, ENABLE);

  /* Configure PB13 and PB14 in output push-pull mode 配置PB14推挽输出模式 */
  GPIO_Init(GPIOB, GPIO_Pin_14, GPIO_MODE_OUT |GPIO_OTYPE_PP |GPIO_PUPD_NOPULL |GPIO_SPEED_HIGH);

  /* Turn off LED1  关闭 LED1*/
  GPIO_SetBits(GPIOB, GPIO_Pin_14);

  /* Configure PA0 in interrupt mode 配置PA0中断模式*/
  EXTI0_Config();

  /* Generate software interrupt: simulate a falling edge applied on EXTI0 line */
	/* 生成软件中断: 在EXTI0上模拟一个下降沿触发 */
  //EXTI_GenerateSWInterrupt(EXTI_Line0);

  /* 空循环 */
  while(1)
  {
  }
}

使用NVIC_PriorityGroupConfig函数设置中断优先级分组为第二组,初始化PB14作为控制LED1的端口,并将PB14置位,默认关闭LED1。

调用EXTI配置函数(编程时请注意,若自定义函数在main函数下,则需要在main函数前进行声明,详见代码注释)。

程序进入空循环,等待中断触发。

7.6 下载验证

本例编译烧录后,每当PA0接到高电平时LED1就会翻转一次状态。

本节课主要为讲解EXTI的配置过程,也可按照本教程中的代码注释去对照例程把没有注释的地方自己注释一遍,固件库中例程的实验现象我们将会在对应视频教程中给大家展示,如果在阅读例程代码时遇到问题也可在群里提出,我会及时给大家反馈。

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值