拓冰建站拓冰建站
首页 / 资讯中心 / 正文

初识状态机

1.前言在STM32的入门项目中按键控制LED大概是最常见的练手实验了。但很多初学者习惯在中断服务函数ISR里直接做翻转LED、延时消抖等操作——这在工程上其实是不推荐的写法。中断服务函数应该遵循 “短、小、快” 的原则。本文从一个实际项目出发逐步讲解如何用 “中断置标志 主循环状态机” 的架构优雅地处理多个按键的中断事件。这套模式在嵌入式开发中非常经典2.实验器材STM32F407开发板标准库三个按键KEY_UPPA0、KEY0PE4、KEY1PE3两个LEDLED1PF9、LED2PF103.功能需求KEY_UP 按下 → 两个LED同时翻转KEY0 按下 → 仅翻转 LED1PF9KEY1 按下 → 仅翻转 LED2PF104.设计思路传统的写法是在每个按键的中断服务函数里直接调用 GPIO_ToggleBits() 翻转LED。这样做虽然简单但有两个问题中断函数变“重”翻转LED虽然不耗时但如果后续需要加入更复杂的逻辑比如长按/短按判断、组合键等中断函数会越来越臃肿。违反“短小快”原则中断服务函数应该只做最轻量级的工作。更优雅的做法是 “中断只负责通知主循环负责干活”中断服务函数检测到按键按下 → 设置一个全局标志位 → 立即退出主循环while(1)检测标志位 → 执行对应的LED翻转动作 → 清除标志位这就是 事件驱动架构 的雏形5.volatile关键字的作用在中断和主循环之间共享的变量必须使用 volatile 关键字修饰。原因编译器默认会对变量进行优化可能会把变量的值缓存在寄存器中而不是每次从内存读取。但在中断程序中变量的值可能在任意时刻被异步修改——编译器并不知道这一点。volatile 的作用就是告诉编译器“这个变量随时可能被外部改变每次使用时都必须从内存重新读取不要做任何优化”。// 正确用法volatileuint8_tkey_event0;// 错误用法编译器可能优化导致主循环永远读不到变化uint8_tkey_event0;6.完整代码6.1全局变量定义#includestm32f4xx.h#includeled.h#includedelay.h// 全局事件标志volatile 防止编译器优化// 0无事件1KEY_UP2KEY03KEY1volatileuint8_tkey_event0;6.2按键GPIO初始化voidKEY_Init(void){GPIO_InitTypeDef GPIO_InitStructure;// 使能 GPIOA 和 GPIOE 时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);// ----- KEY_UP (PA0) 下拉输入高电平有效-----GPIO_InitStructure.GPIO_PinGPIO_Pin_0;GPIO_InitStructure.GPIO_ModeGPIO_Mode_IN;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_100MHz;GPIO_InitStructure.GPIO_PuPdGPIO_PuPd_DOWN;// 下拉GPIO_Init(GPIOA,GPIO_InitStructure);// ----- KEY0 (PE4) 上拉输入低电平有效-----GPIO_InitStructure.GPIO_PinGPIO_Pin_4;GPIO_InitStructure.GPIO_PuPdGPIO_PuPd_UP;// 上拉GPIO_Init(GPIOE,GPIO_InitStructure);// ----- KEY1 (PE3) 上拉输入低电平有效-----GPIO_InitStructure.GPIO_PinGPIO_Pin_3;GPIO_InitStructure.GPIO_PuPdGPIO_PuPd_UP;// 上拉GPIO_Init(GPIOE,GPIO_InitStructure);}6.3EXTI配置voidEXTI_Config(void){EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;// 使能 SYSCFG 时钟用于外部中断映射RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);// 映射引脚到 EXTI 通道SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA,EXTI_PinSource0);// PA0 → EXTI0SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4);// PE4 → EXTI4SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource3);// PE3 → EXTI3// ----- EXTI0PA0上升沿触发-----EXTI_InitStructure.EXTI_LineEXTI_Line0;EXTI_InitStructure.EXTI_ModeEXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_TriggerEXTI_Trigger_Rising;// 高有效 → 上升沿EXTI_InitStructure.EXTI_LineCmdENABLE;EXTI_Init(EXTI_InitStructure);// ----- EXTI4PE4下降沿触发-----EXTI_InitStructure.EXTI_LineEXTI_Line4;EXTI_InitStructure.EXTI_TriggerEXTI_Trigger_Falling;// 低有效 → 下降沿EXTI_Init(EXTI_InitStructure);// ----- EXTI3PE3下降沿触发-----EXTI_InitStructure.EXTI_LineEXTI_Line3;EXTI_InitStructure.EXTI_TriggerEXTI_Trigger_Falling;EXTI_Init(EXTI_InitStructure);// ----- NVIC 优先级配置分组22位抢占 2位响应-----NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// KEY_UP 优先级最高抢占0NVIC_InitStructure.NVIC_IRQChannelEXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority0;NVIC_InitStructure.NVIC_IRQChannelSubPriority0;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);// KEY0抢占1响应0NVIC_InitStructure.NVIC_IRQChannelEXTI4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;NVIC_InitStructure.NVIC_IRQChannelSubPriority0;NVIC_Init(NVIC_InitStructure);// KEY1抢占1响应1NVIC_InitStructure.NVIC_IRQChannelEXTI3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;NVIC_InitStructure.NVIC_IRQChannelSubPriority1;NVIC_Init(NVIC_InitStructure);}6.4主函数状态机事件分发intmain(void){// 系统初始化delay_init(168);LED_Init();KEY_Init();EXTI_Config();while(1){// ---- 状态机核心事件检测与分发 ----if(key_event!0){switch(key_event){case1:// KEY_UP两个LED同时翻转LED1_TOGGLE();LED2_TOGGLE();break;case2:// KEY0仅翻转 LED1LED1_TOGGLE();break;case3:// KEY1仅翻转 LED2LED2_TOGGLE();break;default:break;}// 事件处理完毕清除标志key_event0;}// 主循环可以执行其它任务...}}6.5LED驱动led.h#ifndef__LED_H#define__LED_H#includestm32f4xx.h#defineLED1_PINGPIO_Pin_9#defineLED2_PINGPIO_Pin_10#defineLED1_ON()GPIO_SetBits(GPIOF,LED1_PIN)#defineLED1_OFF()GPIO_ResetBits(GPIOF,LED1_PIN)#defineLED1_TOGGLE()GPIO_ToggleBits(GPIOF,LED1_PIN)#defineLED2_ON()GPIO_SetBits(GPIOF,LED2_PIN)#defineLED2_OFF()GPIO_ResetBits(GPIOF,LED2_PIN)#defineLED2_TOGGLE()GPIO_ToggleBits(GPIOF,LED2_PIN)voidLED_Init(void);#endif
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门