本章代码:

git clone https://gitee.com/jumayusi/unit3-gd32-exti.git

3.1 中断系统硬件与软件结构

ARM异常/中断硬件结构

       单片机的NVIC(嵌套向量中断控制器)模块可以统筹不同的中断触发源信号,执行不同的中断服务函数。ARM单片机的中断时异常的一个子集,异常通常包含两个部分,系统异常(比如系统复位异常、总线访问异常、硬错误异常)和外设中断(interrupt)。

异常触发源

注意这个异常编号是Hard Code,并不是普通排序。

此外,ARM单片机有存储器映射机制。

 AI回答:

分配这些地址是为了存储中断服务函数使用的:

3.2 EXTI中断硬件结构

         GPIO口并没有直接连接到NVIC,而是通过了EXTI控制器。EXTI(中断/事件控制器)包括20个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。EXTI有三种触发类型:上升沿触发、下降沿触发和任意沿触发。EXTI中的每一个边沿检测电路都可以独立配置和屏蔽。

EXTI硬件结构

注意同一时刻只有一个GPIO可以连接到EXTI线。

GD32F303的GPIO口存在共用一个中断触发源,因而共用一个中断触发函数的现象。

EXTI模块和NVIC之间可以多路链接。

中断优先级

   Cortex-M4内部存在不可更改的中断优先级。

3.3 EXTI使能但未实现中断函数会有什么问题

本节课的目标是通过外部中断检测KEY1这个按键是否被按下。

分两步走:

代码:

#include <stdint.h>
#include "gd32f30x_exti.h"
#include "gd32f30x_gpio.h"
#include "gd32f30x_rcu.h"

static void keyGPIOInit(void)
{ 
   rcu_periph_clock_enable(RCU_GPIOA);
   gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_2MHZ,GPIO_PIN_0);
}

static void keyExtiInit(void)
{ 
  //使能exti时钟
  rcu_periph_clock_enable(RCU_AF);
  //I/O连接到exti线
  gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA,GPIO_PIN_SOURCE_0);
  //配置上升沿/下降沿_mode_enum mod
  exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_RISING);
  //清除标志
  exti_interrupt_flag_clear(EXTI_0);
  //使能中断
  nvic_irq_enable(EXTI0_IRQn,1,1);
}
/**
***********************************************************
* @brief key硬件初始化
* @param
* @return 
***********************************************************
*/
void keyDrvInit(void)
{
    keyGPIOInit();
    keyExtiInit();
    
}

抢占优先级(Preemption Priority):核心是 “打断权”。高抢占优先级的中断可以打断正在执行的低抢占优先级中断,先执行自己的逻辑。

响应优先级(Sub Priority/Response Priority):核心是 “排队权”。当多个中断抢占优先级相同时,CPU 会按照响应优先级的高低决定先执行哪个;若响应优先级也相同,则按中断的硬件编号(IRQn)从小到大执行。

响应优先级也可以叫做子优先级

3.4 EXTI中断未清除标志位会有什么问题

本节课的目标是用中断机制实现按下KEY1后翻转D4小灯。

led翻转的解耦写法:

/**
***********************************************************
* @brief 翻转LED
* @param ledNo,LED标号,0~2
* @return 
***********************************************************
*/
void ToggleLed(uint8_t ledNo)
{
   FlagStatus bit_state;
   bit_state=gpio_input_bit_get(g_gpioList[ledNo].gpio, g_gpioList[ledNo].pin);
   bit_state=(FlagStatus)(1-bit_state);
   gpio_bit_write(g_gpioList[ledNo].gpio, g_gpioList[ledNo].pin, bit_state);
}

注意上面强制转换数据类型的必要性!

中断服务函数:

/**
***********************************************************
* @brief EXTI0中断服务函数
* @param
* @return 
***********************************************************
*/
void EXTI0_IRQHandler(void)
{
  if(exti_interrupt_flag_get(EXTI_0)==SET){
      ToggleLed(LED1);
      exti_interrupt_flag_clear(EXTI_0);

  }
}

同一时刻只有一个GPIO可以挂载到EXTI线上,下面这个地方如果增加一行source_select,代码就失效了。

3.5 中断抢占优先级有什么作用

先验证相同优先级中断的并行处理:

#include <stdint.h>
#include "gd32f30x.h"
#include "gd32f30x_exti.h"
#include "gd32f30x_gpio.h"
#include "gd32f30x_rcu.h"
#include "led_drv.h"

static void keyGPIOInit(void)
{ 
   rcu_periph_clock_enable(RCU_GPIOA);
   gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_2MHZ,GPIO_PIN_0);

   rcu_periph_clock_enable(RCU_GPIOG);
   gpio_init(GPIOG,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_2MHZ,GPIO_PIN_13);
   gpio_init(GPIOG,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_2MHZ,GPIO_PIN_14);
}

static void keyExtiInit(void)
{ 
  //使能exti时钟
  rcu_periph_clock_enable(RCU_AF);
  //I/O连接到exti线
  gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA,GPIO_PIN_SOURCE_0);
  //配置上升沿/下降沿_mode_enum mod
  exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
  //清除标志
  exti_interrupt_flag_clear(EXTI_0);
  //使能中断
  nvic_irq_enable(EXTI0_IRQn,1,1);

  //I/O连接到exti线
  gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG,GPIO_PIN_SOURCE_13);
  //配置上升沿/下降沿_mode_enum mod
  exti_init(EXTI_13, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
  //清除标志
  exti_interrupt_flag_clear(EXTI_13);

  //I/O连接到exti线
  gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOG,GPIO_PIN_SOURCE_14);
  //配置上升沿/下降沿_mode_enum mod
  exti_init(EXTI_14, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
  //清除标志
  exti_interrupt_flag_clear(EXTI_14);
  
  nvic_irq_enable(EXTI10_15_IRQn,1,1);
}
/**
***********************************************************
* @brief key硬件初始化
* @param
* @return 
***********************************************************
*/
void keyDrvInit(void)
{
    keyGPIOInit();
    keyExtiInit();
    
}

/**
***********************************************************
* @brief EXTI0中断服务函数,对应KEY1按键
* @param
* @return 
***********************************************************
*/
void EXTI0_IRQHandler(void)
{
  if(exti_interrupt_flag_get(EXTI_0)==SET){
      ToggleLed(LED1);
      exti_interrupt_flag_clear(EXTI_0);
  }
}

/**
***********************************************************
* @brief EXTI10_15中断服务函数,对应KEY2按键、KEY3按键
* @param
* @return 
***********************************************************
*/
void EXTI10_15_IRQHandler(void)
{
  if(exti_interrupt_flag_get(EXTI_13)==SET){
      ToggleLed(LED2);
      exti_interrupt_flag_clear(EXTI_13);
  }else if(exti_interrupt_flag_get(EXTI_14)==SET){
      ToggleLed(LED3);
      exti_interrupt_flag_clear(EXTI_14);
  }
}

烧录至单片机后,可以利用三个按键操作三盏小灯。

这时候我们先修改EXTI10_15的抢占优先级为0,

然后在EXTI0的中断服务函数里面设置一个死循环。

/**
***********************************************************
* @brief EXTI0中断服务函数,对应KEY1按键
* @param
* @return 
***********************************************************
*/
void EXTI0_IRQHandler(void)
{
  if(exti_interrupt_flag_get(EXTI_0)==SET){
      ToggleLed(LED1);
      exti_interrupt_flag_clear(EXTI_0);
      while(1);
  }
}

现在的实验现象是key1点亮D4之后无法翻转,但是key2、key3依然可用。注意,如果抢占优先级相同时修改子优先级是无法实现抢占CPU资源的!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐