第5章 外部中断实验

第五章 外部中断实验

1. 硬件设计

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

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

2. 软件设计

2.1 EXTI初始化及NVIC配置

void EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    // KEY_UP高电平有效
    GPIO_InitStructure.Pin = KEY_UP_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
    GPIO_InitStructure.Pull = GPIO_PULLDOWN;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
    // 普通按键低电平有效
    GPIO_InitStructure.Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
    GPIO_InitStructure.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStructure);
    // NVIC配置
    // 中断线0即PA0连接到EXTI0(KEY_up)
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); // 抢占优先级为2,子优先级为0
    HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能中断
    // 中断线2即PE2连接到EXTI2(KEY2)
    HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 1); // 抢占优先级为2,子优先级为
    HAL_NVIC_EnableIRQ(EXTI2_IRQn);
    // 中断线3即PE3连接到EXTI3(KEY1)
    HAL_NVIC_SetPriority(EXTI3_IRQn, 2, 2); // 抢占优先级为2,子优先级为
    HAL_NVIC_EnableIRQ(EXTI3_IRQn);
    // 中断线4即PE4连接到EXTI4(KEY0)
    HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 3); // 抢占优先级为2,子优先级为
    HAL_NVIC_EnableIRQ(EXTI4_IRQn);
}

2.2 中断函数(HAL库)

/**
  * @brief  This function handles EXTI interrupt request.
  * @param  GPIO_Pin Specifies the pins connected EXTI line
  * @retval None
  */
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

这个函数是HAL自带的,作用就是当中断线产生中断时,首先清除中断标志,然后执行我们的中断服务函数

2.3 中断回调及中断服务函数

// 中断回调函数
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY_UP_PIN); // 调用HAL库的中断处理函数
}
void EXTI2_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY2_PIN);
}
void EXTI3_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY1_PIN);
}
void EXTI4_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(KEY0_PIN);
}

// 中断服务函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    delay_ms(100);
    switch(GPIO_Pin)
    {
        case KEY_UP_PIN:
            if(KEY_UP_GET == 1) // KEY_UP按下
            {
                LED1 = !LED1;
                LED0 = !LED1;
            }
            break;
        case KEY0_PIN:
            if(KEY0_GET == 0)
            {
                LED0 = !LED0;
            }
            break;
        case KEY2_PIN:
            if(KEY2_GET == 0)
            {
                LED1 = !LED1;
            }
            break;
        case KEY1_PIN:
            if(KEY1_GET == 0)
            {
                LED0 = !LED0;
                LED1 = !LED1;
            }
            break;
    }
}

2.4 主函数

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "exti.h"

int main(void)
{
	HAL_Init();
	Stm32_Clock_Init(336,8,2,7);
	delay_init(168);
	uart_init(115200);
	LED_Init();
	KEY_Init();
	EXTI_Init();
	while(1)
	{
		printf("串口正常工作中...\r\n");
		delay_ms(1000);
	}
}

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,当然最重要的是写中断函数,我们来简单回顾一下整个流程,为了简化我们只使用一个按键:

4.1 实验目标

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

4.2 硬件连接

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

4.3 参考代码

#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 第一次修订,后期不再维护

2024.12.23 再次简化内容和代码

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