正点原子 发表于 2023-6-5 15:18:09

《ATK-DFPGL22G之FPGA开发指南_V1.0》 第十二章 外部中断实验

本帖最后由 正点原子 于 2023-6-6 17:54 编辑

1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016






第十二章 外部中断实验

前两章介绍了GPIO在输出模式和输入模式下最基本的的操作,本章将介绍如何将GPIO引脚作为外部中断输入来使用。通过本章的学习,读者将学习到GPIO作为外部中断输入的使用。
本章分为如下几个小节:
12.1 硬件设计
12.2 程序设计
12.3 下载验证


12.1 硬件设计
12.1.1 例程功能
1. 按下KEY0按键可控制LED0状态翻转,按下KEY_UP按键可控制LED1翻转。
12.1.2 硬件资源
1. LED
      LED0 - PF9
      LED1 - PF10
2. 按键
      KEY0 - PE4
      KEY_UP - PA0
12.1.3 原理图
本章实验使用的两个APM32E103最小系统板板载按键,分别为KEY0按键和KEY_UP按键,其于板载MCU的连接原理图,如下图所示:
图12.1.3.1 按键与MCU的连接原理图
从上面的原理图中可以看出,KEY0按键和KEY_UP按键的一端连接到了电源正极,而另一端分别与MCU的PE4引脚和PA0引脚相连接,并且在上一小节的介绍中,也说明了对于该硬件设计,PE4引脚和PA0引脚应当配置为下拉,这样一来,在按键被按下的时候,PE4引脚或PA0引脚就会从原来的低电平状态变为高电平状态,在这期间就会有一个上升沿的跳变,因此可以使用该上升沿信号作为中断的触发源。
12.2 程序设计
12.2.1 Geehy标准库的GPIO驱动
针对本章的实验要求,仅需将对应的GPIO引脚配置为输入下拉即可,请参考上一章节中的内容。
12.2.2 Geehy标准库的EINT驱动
EINT(外部中断/事件控制器)可以用于管理从GPIO引脚等I/O引脚输入的信号引起的中断或事件,即外部中断/事件。使用时,具体的步骤如下:
①:配置EINT线的模式、触发条件和是否使能等配置项
②:使能EINT线中断,并配置其相关的中断优先级
在Geehy标准库中对应的驱动函数如下:
①:配置EINT
该函数用于配置EINT的各项参数,其函数原型如下所示:
void EINT_Config(EINT_Config_T* eintConfig);
该函数的形参描述,如下表所示:
表12.2.3.1 函数EINT_Config()形参描述
该函数的返回值描述,如下所示:
表12.2.3.2 函数EINT_Config()返回值描述
该函数使用EINT_Config_T类型的结构体变量传入EINT的配置参数,该结构体的定义如下所示:
typedef enum
{
    EINT_MODE_INTERRUPT                        = 0x00,                        /* 中断请求模式 */
    EINT_MODE_EVENT                              = 0x04                        /* 事件请求模式 */
} EINT_MODE_T;

typedef enum
{
    EINT_TRIGGER_RISING                        = 0x08,                        /* 上升沿触发 */
    EINT_TRIGGER_FALLING                = 0x0C,                        /* 下降沿触发 */
    EINT_TRIGGER_RISING_FALLING      = 0x10                        /* 上边沿触发 */
} EINT_TRIGGER_T;

typedef enum
{
    EINT_LINENONE                              = 0x00000,                /* 不选择外部中断线 */
    EINT_LINE_0                                        = 0x00001,                /* 外部中断线0 */
    EINT_LINE_1                                        = 0x00002,                /* 外部中断线1 */
    EINT_LINE_2                                        = 0x00004,                /* 外部中断线2 */
    EINT_LINE_3                                        = 0x00008,                /* 外部中断线3 */
    EINT_LINE_4                                        = 0x00010,                /* 外部中断线4 */
    EINT_LINE_5                                        = 0x00020,                /* 外部中断线5 */
    EINT_LINE_6                                        = 0x00040,                /* 外部中断线6 */
    EINT_LINE_7                                        = 0x00080,                /* 外部中断线7 */
    EINT_LINE_8                                        = 0x00100,                /* 外部中断线8 */
    EINT_LINE_9                                        = 0x00200,                /* 外部中断线9 */
    EINT_LINE_10                              = 0x00400,                /* 外部中断线10 */
    EINT_LINE_11                              = 0x00800,                /* 外部中断线11 */
    EINT_LINE_12                              = 0x01000,                /* 外部中断线12 */
    EINT_LINE_13                              = 0x02000,                /* 外部中断线13 */
    EINT_LINE_14                              = 0x04000,                /* 外部中断线14 */
    EINT_LINE_15                              = 0x08000,                /* 外部中断线15 */
    EINT_LINE_16                              = 0x10000,                /* 外部中断线16 */
    EINT_LINE_17                              = 0x20000,                /* 外部中断线17 */
    EINT_LINE_18                              = 0x40000,                /* 外部中断线18 */
} EINT_LINE_T;
typedef struct
{
    uint32_t                line;                                                /* 外部中断线 */
    EINT_MODE_T                mode;                                                /* 模式 */
    EINT_TRIGGER_T      trigger;                                        /* 触发模式 */
    uint8_t                        lineCmd;                                        /* 使能或禁止 */
} EINT_Config_T;
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_eint.h"

void example_fun(void)
{
    EINT_Config_T eint_init_struct;
   
    /* 配置使能EINT0的上升沿信号触发中断 */
    eint_init_struct.line                = EINT_LINE_0;
    eint_init_struct.mode                = EINT_MODE_INTERRUPT;
    eint_init_struct.trigger      = EINT_TRIGGER_RISING;
    eint_init_struct.lineCmd      = ENABLE;
    EINT_Config(&eint_init_struct);
}
②:配置EINT线中断
该函数用于使能NVIC(嵌套向量中断控制器)的请求,因此不仅能用于配置EINT的中断,还能配置其他外设等的中断,其函数原型如下所示:
void NVIC_EnableIRQRequest(      IRQn_Type irq,
                                                            uint8_t preemptionPriority,
                                                            uint8_t subPriority);
该函数的形参描述,如下表所示:
表12.2.3.3 函数NVIC_EnableIRQRequest()形参描述
该函数的返回值描述,如下表所示:
表12.2.3.4 函数NVIC_EnableIRQRequest()返回值描述
该函数的使用示例,如下所示:
#include "apm32e10x.h"

void example_fun(void)
{
    /* 使能EINT0中断,并设置其抢占优先级为2,子优先级为0 */
    NVIC_EnableIRQRequest(EINT0_IRQn, 2, 0);
}

/* 使能了中断,就可以实现其对应的中断回调函数 */
void EINT0_IRQHandler(void)
{
    /* Do something. */
}
12.2.3 外部中断驱动
本实验的外部中断驱动主要就是配置GPIO引脚作为EINT的外部中断源,并在其对应的中断回调函数中处理按键被按下后执行的操作。
外部中断驱动中,对GPIO引脚、EINT的相关定义,如下所示:
/* 引脚和中断编号以及中断服务函数的定义 */
#define KEY0_INT_GPIO_PORT          GPIOE
#define KEY0_INT_GPIO_PIN         GPIO_PIN_4
#define KEY0_INT_GPIO_CLK_ENABLE()do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOE); }while(0)
#define KEY0_INT_IRQn               EINT4_IRQn
#define KEY0_INT_IRQHandler         EINT4_IRQHandler
#define KEY0_EINT                   EINT_LINE_4
#define KEY0_EINT_PORT_SOURCE       GPIO_PORT_SOURCE_E
#define KEY0_EINT_PIN_SOURCE      GPIO_PIN_SOURCE_4

#define WKUP_INT_GPIO_PORT          GPIOA
#define WKUP_INT_GPIO_PIN         GPIO_PIN_0
#define WKUP_INT_GPIO_CLK_ENABLE()do{ RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA); }while(0)
#define WKUP_INT_IRQn               EINT0_IRQn
#define WKUP_INT_IRQHandler         EINT0_IRQHandler
#define WKUP_EINT                   EINT_LINE_0
#define WKUP_EINT_PORT_SOURCE       GPIO_PORT_SOURCE_A
#define WKUP_EINT_PIN_SOURCE      GPIO_PIN_SOURCE_0
外部中断驱动中,外部中断的初始化函数,如下所示:
/**
* @brief       初始化外部中断
* @param       无
* @retval      无
*/
void eint_init(void)
{
    GPIO_Config_T gpio_init_struct;
    EINT_Config_T eint_init_struct;
   
    KEY0_INT_GPIO_CLK_ENABLE();                           /* KEY0时钟使能 */
    WKUP_INT_GPIO_CLK_ENABLE();                           /* WKUP时钟使能 */
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_AFIO);      /* AFIO功能时钟使能 */
   
    gpio_init_struct.pin = KEY0_INT_GPIO_PIN;
gpio_init_struct.mode = GPIO_MODE_IN_PD;            /* 下拉输入 */
GPIO_Config(KEY0_INT_GPIO_PORT, &gpio_init_struct);   /* 配置KEY0引脚 */

    gpio_init_struct.pin = WKUP_INT_GPIO_PIN;
gpio_init_struct.mode = GPIO_MODE_IN_PD;            /* 下拉输入 */
    GPIO_Config(WKUP_INT_GPIO_PORT, &gpio_init_struct);   /* 配置WKUP引脚 */
    /* 选择用作EINT线路的KEY0引脚 */
GPIO_ConfigEINTLine(KEY0_EINT_PORT_SOURCE, KEY0_EINT_PIN_SOURCE);
    /* 选择用作EINT线路的WKUP引脚 */
    GPIO_ConfigEINTLine(WKUP_EINT_PORT_SOURCE, WKUP_EINT_PIN_SOURCE);
eint_init_struct.line = KEY0_EINT;                  /* 外部中断线4 */
/* 设置EINT线路为中断请求 */
eint_init_struct.mode = EINT_MODE_INTERRUPT;
/* 设置EINT线路的上升沿为中断请求 */
eint_init_struct.trigger = EINT_TRIGGER_RISING;
/* 定义EINT线路为使能状态 */
    eint_init_struct.lineCmd = ENABLE;
    EINT_Config(&eint_init_struct);
   
    eint_init_struct.line = WKUP_EINT;                  /* 外部中断线0 */
/* 设置EINT线路为中断请求 */
eint_init_struct.mode = EINT_MODE_INTERRUPT;
/* 设置EINT线路的上升沿为中断请求 */
eint_init_struct.trigger = EINT_TRIGGER_RISING;
/* 定义EINT线路为使能状态 */
    eint_init_struct.lineCmd = ENABLE;
    EINT_Config(&eint_init_struct);
   
    NVIC_EnableIRQRequest(KEY0_INT_IRQn, 0, 0);         /* 抢占0,子优先级0 */
    NVIC_EnableIRQRequest(WKUP_INT_IRQn, 3, 0);         /* 抢占3,子优先级0 */
}
在外部中断的初始化函数中,除了使能按键对应GPIO端口的时钟,还配置了GPIO和EINT。
在外部中断的初始化函数中,开启了两个EINT的中断,因此需要编写这两个EINT中断对应的中断回调函数,如下所示:
/**
* @brief       KEY0按键外部中断服务程序
* @param       无
* @retval      无
*/
void KEY0_INT_IRQHandler(void)
{
    delay_ms(20);                                           /* 消抖 */
   
    if (EINT_ReadIntFlag((EINT_LINE_T)KEY0_INT_GPIO_PIN))
    {
      LED0_TOGGLE();                                    /* LED0 状态取反 */
      LED1_TOGGLE();                                    /* LED1 状态取反 */
/* 默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
      EINT_ClearIntFlag((EINT_LINE_T)KEY0_INT_GPIO_PIN);
    }
}

/**
* @brief       WK_UP按键外部中断服务程序
* @param       无
* @retval      无
*/
void WKUP_INT_IRQHandler(void)
{
    delay_ms(20);                                           /* 消抖 */
   
    if (EINT_ReadIntFlag((EINT_LINE_T)WKUP_INT_GPIO_PIN))
    {
      LED1_TOGGLE();                                    /* LED1 状态取反 */
/* 默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
      EINT_ClearIntFlag((EINT_LINE_T)WKUP_INT_GPIO_PIN);
    }
}

在按键被按下的一瞬间,其与MCU连接的GPIO引脚会收到一个上升沿的信号,就会触发EINT的中断,并运行对应中断回调函数。
12.2.4 实验应用代码
本实验的应用代码,如下所示:
int main(void)
{
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);      /* 设置中断优先级分组为组4 */
    sys_apm32_clock_init(15);                                                /* 配置系统时钟 */
    delay_init(120);                                                                /* 初始化延时功能 */
usart_init(115200);                                                    /* 初始化串口 */
    led_init();                                                                                        /* 初始化LED */
    eint_init();                                                                            /* 初始化外部中断 */
    while (1)
    {
      printf("OK\r\n");
      delay_ms(1000);
    }
}
本实验的应用代码很简单,在初始化完LED和外部中断后,就进入一个while循环,以便通过串口助手查看单片机运行状态。另外,按键控制LED亮灭状态翻转的操作都在对应EINT的中断回调函数中完成了。
12.3 下载验证
在完成编译和烧录操作后,可以看到板子上的LED0处于亮起状态,而LED1是处于熄灭的状态,若此时按下KEY0按键,则能够看到LED0处于熄灭状态,而LED1处于亮起状态,如此两盏LED灯便发生了一次状态翻转,同样的,若此时按下KEY_UP按键,则能够看LED1的亮灭状态发生了一次翻转,与预期的实验现象效果相符。
页: [1]
查看完整版本: 《ATK-DFPGL22G之FPGA开发指南_V1.0》 第十二章 外部中断实验