第5章 外部中断实验

第五章 外部中断实验

1. 硬件设计

本实验用到的硬件资源和按键输入实验一模一样,不多介绍了

但是这里我们使用的是中断来检测按键,还是KEY_UP 控制 DS0, DS1 互斥点亮; KEY2 控制 DS0, 按一次亮,再按一次灭; KEY1 控制 DS1,效果同 KEY2; KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。

2. 软件设计

2.1 GPIO初始化

// 外部中断初始化
void EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOA_CLK_ENABLE(); // 开启GPIOA时钟
    __HAL_RCC_GPIOE_CLK_ENABLE(); // 开启GPIOE时钟
    // 下面是KEY_UP的配置(高电平有效)
    GPIO_Initure.Pin = GPIO_PIN_0;// PA0
    GPIO_Initure.Mode = GPIO_MODE_IT_RISING;// 上升沿触发
    GPIO_Initure.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(GPIOA, &GPIO_Initure);
    // 下面是普通按键配置(低电平有效)
    GPIO_Initure.Pin = GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4; // PE2,3,4
    GPIO_Initure.Mode = GPIO_MODE_IT_FALLING;            // 下降沿触发
    GPIO_Initure.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOE, &GPIO_Initure);

2.2 NVIC配置

    // 中断线0-PA0
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); // 抢占优先级为2,子优先级为0
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);         // 使能中断线0
    // 中断线2-PE2
    HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 1); // 抢占优先级为2,子优先级为1
    HAL_NVIC_EnableIRQ(EXTI2_IRQn);         // 使能中断线2
    // 中断线3-PE3
    HAL_NVIC_SetPriority(EXTI3_IRQn, 2, 2); // 抢占优先级为2,子优先级为2
    HAL_NVIC_EnableIRQ(EXTI3_IRQn);         // 使能中断线3
    // 中断线4-PE4
    HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 3); // 抢占优先级为2,子优先级为3
    HAL_NVIC_EnableIRQ(EXTI4_IRQn);         // 使能中断线4
}

2.3 中断服务函数

// 中断服务函数
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用中断处理公用函数
}
void EXTI2_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2); // 调用中断处理公用函数
}
void EXTI3_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3); // 调用中断处理公用函数
}
void EXTI4_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); // 调用中断处理公用函数
}

2.4 中断服务函数内容

// 中断服务程序中需要做的事情
// 在HAL库中所有的外部中断服务函数都会调用此函数
// GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(100); // 消抖
    switch(GPIO_Pin)
    {
        case GPIO_PIN_0:
            if(WK_UP == 1) // 高电平有效
            {
                LED1 = !LED1; // 控制LED0,LED1互斥点亮
                LED0 = !LED1;
            }
            break;
        case GPIO_PIN_2:
            if(KEY2 == 0) // LED1翻转
            {
                LED1 = !LED1;    
            }
            break;
        case GPIO_PIN_3:
            if(KEY1 == 0) // 同时控制LED0,LED1翻转 
            {
                LED0 = !LED0;
                LED1 = !LED1;
            }
            break;
        case GPIO_PIN_4:
            if(KEY0 == 0)  
            {
                LED0 = !LED0; // 控制LED0翻转
            }
            break;
    }
}
  • 主函数
int main(void)
{
    HAL_Init(); //初始化 HAL 库
    Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz
    delay_init(168); //初始化延时函数
    uart_init(115200); //初始化 USART
    LED_Init(); //初始化 LED
    KEY_Init(); //初始化按键
    EXTI_Init(); //外部中断初始化

    while(1)
    {
        printf("OK\r\n"); //打印 OK 提示程序运行
        delay_ms(1000); //每隔 1s 打印一次
    }
}

3. 重点部分分析

exti.c 文件总共包含 6 个函数。外部中断初始化函数 void EXTIX_Init 用来配置 IO 口外部中断相关步骤并使能中断,另一个函数 HAL_GPIO_EXTI_Callback 是外部中断共用回调函数,用来处理所有外部中断真正的控制逻辑。其他 4 个都是中断服务函数。

void EXTI0_IRQHandler(void)是外部中断 0 的服务函数,负责 KEY_UP 按键的中断检测;

void EXTI2_IRQHandler(void)是外部中断 2 的服务函数,负责 KEY2 按键的中断检测;

void EXTI3_IRQHandler(void)是外部中断 3 的服务函数,负责 KEY1 按键的中断检测;

void EXTI4_IRQHandler(void)是外部中断 4 的服务函数,负责 KEY0 按键的中断检测;

下面我们分别介绍这几个函数。

首先是外部中断初始化函数 void EXTIX_Init(void), 该函数内部主要做了两件事情。首先是调用 IO 口初始化函数 HAL_GPIO_Init 来初始化 IO 口,其次是设置中断优先级并使能中断线。

接下来我们看看外部中断服务函数,一共 4 个。所有的中断服务函数内部都只调用了同样一个函数 HAL_GPIO_EXTI_IRQHandler,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零, 并且调用中断处理共用回调函数 HAL_GPIO_EXTI_Callback。

最后是外部中断回调函数 HAL_GPIO_EXTI_Callback,该函数用来编写真正的外部中断控制逻辑。该函数有一个入口参数就是 IO 口序号。所以我们在该函数内部,一般通过判断 IO 口序号值来确定中断是来自哪个 IO 口,也就是哪个中断线,然后编写相应的控制逻辑。所以在该函数内部,我们通过 switch 语句判断 IO 口来源,例如是来自 GPIO_PIN_0,那么一定是来自PA0,因为中断线一次只能连接一个 IO 口,而四个 IO 口中序号为 0 的 IO 口只有 PA0,所以中断线 0 一定是连接 PA0,也就是外部中断由 PA0 触发。

4. 小结

总的来说外部中断还算比较简单,核心就是设置中断号和NVIC,当然最重要的是写中断函数,我们来简单回顾一下整个流程,为了简化我们只使用一个按键:

实验目标

  1. 使用外部中断检测按键按下事件。
  2. 点亮 LED 指示灯作为反馈。

硬件连接

  1. 按键连接
  • 将一个按键的一端连接到开发板的某个 GPIO 引脚(例如 PA0)。
  • 按键的另一端连接到 GND。
  1. LED 连接
  • 如果使用开发板上的 LED,可以直接使用。否则,将 LED 的正极连接到 GPIO 引脚(例如 PC13),负极接地,并串联一个 220Ω 电阻。

参考代码

#include "main.h"

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

int main(void)
{
    // 初始化 HAL 库
    HAL_Init();
    // 配置系统时钟
    SystemClock_Config();
    // 初始化 GPIO
    MX_GPIO_Init();
    while (1)
    {
        // 主循环可以为空,因为中断会处理 LED 点亮
    }
}
// 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == GPIO_PIN_0) // 检查是否为 PA0
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 切换 LED 状态
    }
}

// GPIO 初始化函数
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    // 启用 GPIO 时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    // 配置 PA0 为外部中断
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发中断
    GPIO_InitStruct.Pull = GPIO_PULLDOWN; // 下拉
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    // 配置 PC13 为输出,用于 LED
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低频
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    // 配置中断优先级
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能外部中断
}

// 配置系统时钟
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = 16;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}

2024.9.29 第一次修订,后期不再维护

posted @ 2024-09-29 10:47  hazy1k  阅读(13)  评论(0编辑  收藏  举报