当前位置:首页>java>STM32"零代码”实现按键控制LED流水灯(中断方式)

STM32"零代码”实现按键控制LED流水灯(中断方式)

  • 2026-10-21 13:27:54
STM32"零代码”实现按键控制LED流水灯(中断方式)

上一篇文章链接:STM32"零代码”实现按键控制LED流水灯

书接上回,在上文中实现了按键控制流水灯工作模式。

    /* 主循环 */    while (1)    {        /* 检测按键是否按下,按下则切换模式 */        if(KEY_Check() == 1)        {            LED_Mode_Switch(); // 切换流水灯模式            LED_All_Off();     // 切换模式前熄灭所有LED,避免混亮            HAL_Delay(200);    // 防连按:短延时避免一次按键切换多次        }        /* 根据当前模式执行对应流水灯函数 */        switch(current_mode)        {            case MODE_WATERFALL_1:                LED_Waterfall_1();                break;            case MODE_WATERFALL_SINGLE:                LED_Waterfall();                break;            case MODE_WATERFALL_GROUP:                LED_Waterfall_Group();                break;            default:                current_mode = MODE_WATERFALL_1; // 异常时默认模式1                break;        }    }

以上为main.c文件中,主循环中的代码。

一、代码执行过程分析

1.1 while循环

while是 C 语言中的一种循环控制结构,用于在特定条件为真时重复执行一段代码。

while 循环的语法如下:

while (条件表达式) {    // 循环体:条件为真时执行的代码}
程序中
 while (1)
中的“1”是一个常数,而在c语言中,无论任何运算,“0”为假,其他非零值都代表真。
因此,这里
 while (1)
表示,其条件表达式永远为真,其内部的循环体,也就是上面代码中Line3~Line27之间的程序,需要一遍又一遍的去反复从上到下执行,因此,这种写法也称“死循环”。
丢给AI的第一问:
单片机程序中,为什么要写 死循环?
答复:
你想知道在单片机程序里为什么一定要写死循环,这个问题问到了单片机编程的核心特点,非常关键。简单来说,单片机作为嵌入式系统,它的程序和电脑上的程序运行逻辑完全不同:电脑程序运行完一个任务就会退出,但单片机需要持续上电运行、处理实时任务,死循环正是为了让程序能一直 “活” 着,持续执行我们设定的功能。死循环的核心作用(结合单片机特性)1. 防止程序 “跑飞”,保证系统持续运行单片机的程序是从 main() 函数开始执行的,如果没有死循环,main() 函数执行完最后一行代码后,程序计数器会继续指向内存中后面的未知地址,导致:程序执行乱码、硬件行为不可控(比如引脚电平乱跳);触发单片机的复位(不同内核表现不同),系统反复重启,无法稳定工作。死循环(比如 while(1))会让程序一直卡在循环体内,避免执行到未知内存区域,保证系统 “不跑偏”。2. 持续处理实时任务单片机的核心价值是实时响应外部事件(比如按键按下、传感器数据变化、串口接收数据等),这些事件都是随机发生的,需要程序一直 “监听” 和处理:3. 适配单片机的 “裸机” 运行模式大部分入门级单片机程序是 “裸机编程”(无操作系统),没有任务调度器,所有逻辑都需要在死循环里靠 “轮询” 或 “中断” 实现:轮询:在死循环里依次检查各个外设 / 事件(比如先查按键,再读传感器,再处理串口);中断:即使在死循环里,外部事件(比如定时器到点、按键中断)也能打断循环,处理完中断后又回到死循环继续运行。

常见的死循环写法

不同单片机内核的死循环写法略有不同,但本质都是 “无限循环”:
// 最常用(推荐):51/STM32/Arduino等通用while(1) {    // 业务逻辑}// 等效写法(可读性稍差)for(;;) {    // 业务逻辑}// 极端情况(慎用,可能屏蔽中断)while(1);  // 空死循环,一般用于程序出错后“卡死”
这里涉及到c语言的一些语法知识,具体内容,请单击文末阅读原文,跳转到对应页学习,或者复制下面的链接,在浏览器中查看。
  1. 关键字(Keywords)
  2. 标识符(Identifiers)
  3. 常量(Constants)
  4. 运算符(Operators)
  5. 分隔符(Separators)
C语言基本语法:
https://www.runoob.com/cprogramming/c-basic-syntax.html
有了以上基础,本文开头的程序,执行过程大概如下面的流程图:

二、 程序分析

在上图流程图中,绿色①和蓝色②两部分从执行顺序上,是顺序执行,每次while循环,先执行①,再执行②,如此循环。
程序中,大部分指令执行时间是比较快的,大概微妙级别,在①中,主要时间用在
HAL_Delay(200); 
延时函数上,200代表要延时200毫秒。
在②中,根据 current_mode 的值,执行三种工作模式其中的一种,对应其中的一个函数。
每一个函数内部,为了实现流水灯效果,各有不同次数和不同延时时长的延时函数。
例如 LED_Waterfall() 函数功能代码如下:
voidLED_Waterfall(void){ /* 正序点亮:仅1灯亮 */ for (int i = LED0; i <= LED9; i++) { LED_All_Off();  LED_On((LED_Name)i);  HAL_Delay(100);  } /* 逆序熄灭:仅1灯亮 */ for (int i = LED9; i >= LED0; i--) { LED_Off((LED_Name)i);  if (i > LED0)  { LED_On((LED_Name)(i-1)); } HAL_Delay(100);  } LED_All_Off(); }
使用AI解释一下以上代码:
首先,先给你一个整体结论:这段程序实现了10 个 LED 灯(LED0~LED9)从左到右逐个点亮(仅当前灯亮),再从右到左逐个熄灭(仍仅当前灯亮) 的流水灯效果,最后关闭所有 LED,完成一轮流水灯循环。完整代码逐段解释1. 函数整体说明voidLED_Waterfall(void)这是一个无返回值、无参数的函数,函数名 LED_Waterfall 直译就是 “LED 瀑布”,对应流水灯的视觉效果(灯像水流一样依次亮灭)。2. 第一部分:正序点亮(仅 1 灯亮)/* 正序点亮:仅1灯亮 */for (int i = LED0; i <= LED9; i++){    LED_All_Off();          // 先关闭所有LED    LED_On((LED_Name)i);    // 点亮第i个LED    HAL_Delay(100);         // 延时100毫秒,保持当前状态}核心逻辑:让 LED 从LED0 到LED9 逐个 “单独点亮”,形成 “流水向右走” 的效果。逐行拆解:for (int i = LED0; i <= LED9; i++):循环变量 i 从 LED0(第一个灯)开始,到 LED9(第十个灯)结束,每次加 1,对应正序遍历所有 LED。LED_All_Off();:每次循环先关闭所有LED,确保同一时间只有 1 个灯亮(避免多个灯同时亮)。LED_On((LED_Name)i);:点亮第 i 个 LED,(LED_Name)i 是把整型 i 强制转换为 LED 名称的枚举类型(比如LED0/LED1 是提前定义的枚举常量),适配 LED_On 函数的参数要求。HAL_Delay(100);:调用 STM32 的 HAL 库延时函数,延时 100ms(0.1 秒),让当前点亮的 LED 保持亮的状态,人眼能清晰看到流水效果。执行效果示例:i=LED0:全灭 → 亮 LED0 → 保持 100msi=LED1:全灭 → 亮 LED1 → 保持 100ms...i=LED9:全灭 → 亮 LED9 → 保持 100ms最终正序结束时,只有 LED9 是亮的。3. 第二部分:逆序熄灭(仅 1 灯亮)/* 逆序熄灭:仅1灯亮 */for (int i = LED9; i >= LED0; i--){    LED_Off((LED_Name)i);   // 关闭第i个LED    if (i > LED0)           // 避免i=LED0时,i-1超出范围    {        LED_On((LED_Name)(i-1)); // 点亮前一个LED    }    HAL_Delay(100);         // 延时100毫秒,保持当前状态}核心逻辑:从LED9 往 LED0 方向,逐个关闭当前灯、点亮前一个灯,形成 “流水向左走” 的效果。逐行拆解:for (int i = LED9; i >= LED0; i--):循环变量 i 从 LED9 开始,到 LED0 结束,每次减 1,对应逆序遍历所有 LED。LED_Off((LED_Name)i);:先关闭第 i 个 LED(比如 i=LED9 时,先灭 LED9)。if (i > LED0):判断是否不是第一个灯(LED0),防止 i-1 变成负数(比如 i=LED0 时,i-1=LED-1,超出定义范围)。LED_On((LED_Name)(i-1));:如果不是第一个灯,点亮前一个 LED(比如 i=LED9 时,点亮 LED8;i=LED8 时,点亮 LED7,以此类推)。HAL_Delay(100);:延时 100ms,保持当前 “仅前一个灯亮” 的状态。执行效果示例(承接正序结束后 LED9 亮的状态):i=LED9:灭 LED9 → 亮 LED8 → 保持 100msi=LED8:灭 LED8 → 亮 LED7 → 保持 100ms...i=LED1:灭 LED1 → 亮 LED0 → 保持 100msi=LED0:灭 LED0 → 无点亮操作(因为 i 不大于 LED0)→ 保持 100ms最终逆序结束时,所有 LED 都灭了(代码里最后还会显式关一次,双重保障)。4. 最后一步:关闭所有 LEDLED_All_Off();  // 确保流水灯循环结束后,所有LED都熄灭这行是 “兜底” 操作,即使逆序循环结束后有个别灯没灭,也能强制关闭所有 LED,保证一轮流水灯结束后状态干净。完整执行流程可视化为了更直观,用 LED0~LED9 的亮灭状态展示一轮完整流程:正序阶段:LED0 亮→LED1 亮→...→LED9 亮(每个灯单独亮,其余灭,每次停 100ms)逆序阶段:LED9 灭、LED8 亮→LED8 灭、LED7 亮→...→LED1 灭、LED0 亮→LED0 灭(每个灯单独亮,其余灭,每次停 100ms)最终状态:所有 LED 熄灭
在LED_Waterfall()函数中,大体有两个循环,每个循环内部循环10次,每次使用依次循环函数 HAL_Delay(100),即延时100毫秒。
执行一次LED_Waterfall()函数,需要使用至少100*20=2秒
程序中的问题:
程序运行后,如果程序正在执行LED_Waterfall()时,这时按下按键,程序不会立即修改LED流水灯模式,因为这时,程序还没有执行到判断按键是否按下的程序部分。
想要让本次按键有效,就需要按下的时间“久”一点,直到程序执行完LED_Waterfall()之后,下一次循环开始,执行判断按键是否按下,这时才会发现,按键按下了。

三、 中断

豆包:用通俗的语言或用生活中的例子说明一下stm32中的中断

你想让我用通俗的语言和生活例子讲明白 STM32 里的中断,这个需求特别好 —— 中断是单片机的核心难点,用生活化的场景类比,一下子就能懂。先给你一个核心结论:STM32 的中断,就像你正在做一件事时,突然有紧急情况需要你立刻处理,处理完后再回到原来的事继续做,本质是 “打断当前任务、优先处理紧急事、处理完回归原任务” 的机制。用生活例子彻底讲透中断我用 “你在家做饭” 这个场景,对应 STM32 的 “主循环执行常规任务”,一步步类比:场景 1:无中断的 “傻等”(轮询方式)假设你正在厨房煮米饭(对应 STM32 主循环里的常规任务,比如while(1)里的 LED 闪烁),同时还要等快递上门(对应需要处理的外部事件,比如按键按下、串口收数据)。如果没有 “中断”(相当于没有手机),你只能:煮米饭煮到一半,跑门口看一眼有没有快递;回到厨房继续煮,煮一会儿又跑门口看;反复跑(对应 STM32 的 “轮询”:while(1)里不停检查按键 / 串口状态)。问题:米饭可能煮糊(主任务被频繁打断,执行不连贯);快递来了可能没及时开门(响应有延迟);你全程累(STM32CPU 空耗,效率低)。场景 2:有中断的 “高效处理”(中断方式)还是煮米饭 + 等快递,但你有手机(对应 STM32 的 “中断控制器”),快递员会给你打电话(对应 “中断触发信号”):你安心在厨房煮米饭(STM32 执行主循环的常规任务,比如控制 LED、读取传感器);快递到了,手机响了(外部事件触发中断,比如按键按下产生电平变化,触发 EXTI 中断);你立刻关火(暂停当前的主任务),接电话、去门口取快递(执行 “中断服务函数”,处理紧急事件);取完快递,回到厨房,重新开火继续煮米饭(中断处理完成,回到主循环暂停的位置,继续执行原任务)。这个过程就是 STM32 中断的核心逻辑:不主动傻等,有紧急事才立刻处理,处理完回归常态。总结中断的核心:优先处理紧急事件,避免 CPU “傻等” 外部事件,提升执行效率和响应速度;生活类比:就像你做事时被紧急事打断,处理完紧急事再回去做原来的事,STM32 的中断就是这个逻辑;关键优势:相比 “轮询”,中断让 CPU 能专注执行主任务,只有真正有需要时才响应外部事件,是单片机处理实时性任务的核心手段。
简单理解:main.c中的main()函数内执行的代码、直接调用或者间接调用的函数,即常规任务,常规任务按照程序逻辑、程序先后依次(顺序、分支、循环)执行。
而一些特殊的事件,就可以使用中断完成(当然这些中断不是随便定义的,而是单片机生产厂家在生产单片机时提前定义好的),使用者根据某特定单片机数据手册,学会使用该中断,就可以提高单片机对特定事件的响应的实时性(当中断发生时,立即执行中断“要求”执行的代码,执行完后,再返回执行常规任务)。

四、使用GPIO中断实现按键检测

在STM32中,要实现中断方式检测按键,需要将对应GPIO引脚配置为中断模式,并使能对应的中断通道(在STM32中,每一个中断事件对应一个中断通道,类似于一个中断功能的开关,使能即将该功能对应开关打开/接通),最后在程序中实现中断服务函数与回调函数。
要实现使用中断方式,检测按键,原理图不需要修改。
直接将上文main.c中的内容,全文复制,丢给AI后,增加提示词:将以上代码中按键初始化、按键按下时,切换流水灯模式修改为使用中断方式。
你希望将代码中原本的轮询方式检测按键、切换流水灯模式,修改为使用外部中断的方式来实现,这样可以避免主循环一直检测按键状态,提升程序的效率。修改思路GPIO 初始化修改:将 PA10 从普通输入模式改为中断输入模式,并配置中断触发方式(下降沿触发,对应按键按下)。中断服务函数配置:编写 EXTI15_10_IRQHandler 中断服务函数(PA10 对应 EXTI10),并调用 HAL 库的中断回调函数。中断回调函数实现:在回调函数中完成模式切换、LED 清零、防连按等逻辑。主循环优化:移除主循环中的按键轮询代码,只保留流水灯模式执行逻辑。
得到的main.c代码:
#include"main.h"/* 1. 枚举定义10个LED,便于语义化调用 */typedef enum {    LED0 = 0,  // 对应PA0    LED1,      // 对应PA1    LED2,      // 对应PA2    LED3,      // 对应PA3    LED4,      // 对应PA4    LED5,      // 对应PA5    LED6,      // 对应PA6    LED7,      // 对应PA7    LED8,      // 对应PA8    LED9       // 对应PA9} LED_Name;/* 2. 枚举定义3种流水灯模式 */typedef enum {    MODE_WATERFALL_1 = 0,  // 模式1:正序点亮+逆序熄灭(多灯亮)    MODE_WATERFALL_SINGLE, // 模式2:单灯依次亮+逆序灭(仅1灯亮)    MODE_WATERFALL_GROUP   // 模式3:分组亮灭(前5+后5)} LED_Mode;/* 3. 数组关联枚举与GPIO引脚,方便for循环遍历 */typedef struct {    GPIO_TypeDef *GPIOx;  // GPIO端口    uint16_t GPIO_Pin;    // GPIO引脚} LED_HandleTypeDef;const LED_HandleTypeDef LED_Array[] = {    {GPIOA, GPIO_PIN_0},  // LED0    {GPIOA, GPIO_PIN_1},  // LED1    {GPIOA, GPIO_PIN_2},  // LED2    {GPIOA, GPIO_PIN_3},  // LED3    {GPIOA, GPIO_PIN_4},  // LED4    {GPIOA, GPIO_PIN_5},  // LED5    {GPIOA, GPIO_PIN_6},  // LED6    {GPIOA, GPIO_PIN_7},  // LED7    {GPIOA, GPIO_PIN_8},  // LED8    {GPIOA, GPIO_PIN_9}   // LED9};/* 全局变量:当前流水灯模式(初始为模式1) */LED_Mode current_mode = MODE_WATERFALL_1;/* 函数声明 */voidSystemClock_Config(void);staticvoidMX_GPIO_Init(void);voidLED_On(LED_Name led);voidLED_Off(LED_Name led);voidLED_All_Off(void);voidLED_Mode_Switch(void);// 模式切换函数voidHAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); // 中断回调函数声明// 3种流水灯模式函数声明voidLED_Waterfall_1(void);voidLED_Waterfall(void);voidLED_Waterfall_Group(void);intmain(void){    /* 初始化HAL库 */    HAL_Init();    /* 配置系统时钟为64MHz */    SystemClock_Config();    /* 初始化GPIO(LED+按键中断) */    MX_GPIO_Init();    /* 主循环:仅执行流水灯逻辑,无任何按键轮询代码 */    while (1)    {        /* 根据当前模式执行对应流水灯函数 */        switch(current_mode)        {            case MODE_WATERFALL_1:                LED_Waterfall_1();                break;            case MODE_WATERFALL_SINGLE:                LED_Waterfall();                break;            case MODE_WATERFALL_GROUP:                LED_Waterfall_Group();                break;            default:                current_mode = MODE_WATERFALL_1; // 异常时默认模式1                break;        }    }}/**  * @brief  流水灯模式切换函数:循环切换3种模式  * @param  无  * @retval 无  */voidLED_Mode_Switch(void){    current_mode = (LED_Mode)((current_mode + 1) % 3);    // 模式循环逻辑:0→1→2→0}/**  * @brief  点亮指定LED  * @param  led:枚举类型的LED编号(LED0~LED9)  * @retval 无  * @note   LED为共阳极接法(低电平点亮,高电平熄灭)  */voidLED_On(LED_Name led){    HAL_GPIO_WritePin(LED_Array[led].GPIOx, LED_Array[led].GPIO_Pin, GPIO_PIN_RESET);}/**  * @brief  熄灭指定LED  * @param  led:枚举类型的LED编号(LED0~LED9)  * @retval 无  */voidLED_Off(LED_Name led){    HAL_GPIO_WritePin(LED_Array[led].GPIOx, LED_Array[led].GPIO_Pin, GPIO_PIN_SET);}/**  * @brief  熄灭所有LED  * @param  无  * @retval 无  */voidLED_All_Off(void){    for (int i = LED0; i <= LED9; i++)    {        LED_Off((LED_Name)i);    }}/**  * @brief  模式1:正序点亮,逆序熄灭,间隔100ms(多灯亮)  * @param  无  * @retval 无  */voidLED_Waterfall_1(void){    /* 第一步:依次点亮10个LED(LED0→LED9) */    for (int i = LED0; i <= LED9; i++)    {        LED_On((LED_Name)i);        HAL_Delay(100);    }    /* 第二步:逆序熄灭10个LED(LED9→LED0) */    for (int i = LED9; i >= LED0; i--)    {        LED_Off((LED_Name)i);        HAL_Delay(100);    }}/**  * @brief  模式2:单灯依次亮→逆序灭,间隔100ms(仅1灯亮)  * @param  无  * @retval 无  */voidLED_Waterfall(void){    /* 正序点亮:仅1灯亮 */    for (int i = LED0; i <= LED9; i++)    {        LED_All_Off();                  LED_On((LED_Name)i);            HAL_Delay(100);             }    /* 逆序熄灭:仅1灯亮 */    for (int i = LED9; i >= LED0; i--)    {        LED_Off((LED_Name)i);           if (i > LED0)                   {            LED_On((LED_Name)(i-1));        }        HAL_Delay(100);             }    LED_All_Off();              }/**  * @brief  模式3:分组亮灭(前5+后5),间隔200ms  * @param  无  * @retval 无  */voidLED_Waterfall_Group(void){    /* 点亮前5个LED */    LED_All_Off();    for (int i = LED0; i <= LED4; i++)    {        LED_On((LED_Name)i);    }    HAL_Delay(200);    /* 熄灭前5个LED */    for (int i = LED0; i <= LED4; i++)    {        LED_Off((LED_Name)i);    }    /* 点亮后5个LED */    for (int i = LED5; i <= LED9; i++)    {        LED_On((LED_Name)i);    }    HAL_Delay(200);    /* 熄灭后5个LED */    for (int i = LED5; i <= LED9; i++)    {        LED_Off((LED_Name)i);    }}/**  * @brief System Clock Configuration  * @retval None  */voidSystemClock_Config(void){  RCC_OscInitTypeDef RCC_OscInitStruct = {0};  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};  /** 初始化RCC振荡器 */  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;  RCC_OscInitStruct.HSIState = RCC_HSI_ON;  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)  {    Error_Handler();  }  /** 初始化CPU、AHB、APB总线时钟 */  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)  {    Error_Handler();  }}/**  * @brief  GPIO初始化(LED输出+PA10中断输入)  * @param  无  * @retval 无  */staticvoidMX_GPIO_Init(void){    GPIO_InitTypeDef GPIO_InitStruct = {0};    /* 使能GPIOA时钟 */    __HAL_RCC_GPIOA_CLK_ENABLE();    /* 配置LED引脚:PA0~PA9 推挽输出、上拉、高速 */    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3                         | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7                        | GPIO_PIN_8 | GPIO_PIN_9;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;            GPIO_InitStruct.Pull = GPIO_PULLUP;                    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;          HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    /* 配置按键引脚:PA10 下降沿中断触发、上拉 */    GPIO_InitStruct.Pin = GPIO_PIN_10;    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发(按键按下为低)    GPIO_InitStruct.Pull = GPIO_PULLUP;           // 内部上拉    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);    /* 配置并使能EXTI15_10中断 */    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);   // 中断优先级(可按需调整)    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);           // 使能中断通道    /* 初始化熄灭所有LED */    LED_All_Off();}/**  * @brief  EXTI15_10中断服务函数(PA10对应EXTI10)  * @param  无  * @retval 无  */voidEXTI15_10_IRQHandler(void){    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10); // 调用HAL库中断处理函数,触发回调}/**  * @brief  GPIO中断回调函数(无消抖,按键按下立即切换模式)  * @param  GPIO_Pin:触发中断的引脚  * @retval 无  */voidHAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){    if(GPIO_Pin == GPIO_PIN_10) // 确认是PA10按键中断    {        LED_Mode_Switch();      // 直接切换流水灯模式        LED_All_Off();          // 切换前熄灭所有LED,避免混亮        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_10); // 清除中断标志位    }}/**  * @brief  错误处理函数  * @param  无  * @retval 无  */voidError_Handler(void){    __disable_irq();    while (1)    {    }}#ifdef USE_FULL_ASSERTvoidassert_failed(uint8_t *file, uint32_t line){}#endif/* USE_FULL_ASSERT */
修改说明:
关键修改点说明1. GPIO 初始化修改:将 PA10 的模式从GPIO_MODE_INPUT改为GPIO_MODE_IT_FALLING(下降沿触发中断)。新增HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ配置并使能 EXTI15_10 中断(PA10 属于 EXTI10,对应 EXTI15_10 中断通道)。2. 新增中断服务函数:EXTI15_10_IRQHandler:EXTI15_10 中断的服务函数,必须按照 STM32 中断向量表的命名规则编写,内部调用 HAL 库的HAL_GPIO_EXTI_IRQHandler触发回调函数。3. 中断回调函数实现:HAL_GPIO_EXTI_Callback:HAL 库的 GPIO 中断回调函数(弱函数,用户可重写),在这里实现按键的消抖、模式切换逻辑。4. 主循环优化:移除了主循环中所有按键轮询相关代码,主循环只专注于执行流水灯逻辑,程序效率更高。5. 总结按键检测从轮询方式改为外部中断方式,主循环无需持续检测按键,CPU 利用率更高。中断触发方式为下降沿触发(对应按键按下)。核心修改点包括:GPIO 中断模式配置、中断服务函数编写、中断回调函数实现、主循环按键逻辑移除。
将以上代码,复制,粘贴,替换上一篇中的main.c文件,编译,下载...运行效果如下:
已关注
关注
重播 分享 赞
对比可以发现,使用中断方式实现按键检测,相比查询方式按键检测,按键在任何时候按下,基本可以“及时”检测到,并同时修改LED流水灯模式。
在 STM32 系列微控制器中,中断系统是内核与外设交互的核心模块,也是整个系统架构中复杂度较高的部分。其复杂性体现在多个维度:从底层的中断工作机制(如中断请求的产生、挂起与抢占规则),到不同类型中断的触发方式(如外部输入中断的电平 / 边沿触发、外设中断的事件触发、异常中断的硬件 / 软件触发等),再到中断响应的完整流程(中断优先级判定、上下文保护、中断服务函数调用),以及中断返回阶段的关键操作(上下文恢复、中断标志清除、指令流回归主程序),每个环节都涉及严格的硬件逻辑和软件适配规则。更详细的内容可以参考《STM32 F1 中文参考手册》、《STM32 F1 中文参考手册》或其他相关资料。
不过,使用简单提示词由AI生成的程序,虽然可以实现本文要求,但是还是存在一些问题,使得程序在运行过程中,并不是那么“完美”。
有哪些问题,又有哪些改进措施,欢迎留言讨论。

最新文章

随机文章