#ifndef_EXTI_H#define_EXTI_H#include"../Driver/SYSTEM/sys/sys.h"/******************************************************************************************//* 引脚 和 中断编号 & 中断服务函数 定义 */#defineALARM_INT_GPIO_PORTGPIOB#defineALARM_INT_GPIO_PINGPIO_PIN_7#defineALARM_INT_IRQnEXTI9_5_IRQn#defineALARM_INT_IRQHandlerEXTI9_5_IRQHandler/******************************************************************************************//******************************************************************************************//* 引脚 定义 */#defineALARM_GPIO_PORTGPIOB#defineALARM_GPIO_PINGPIO_PIN_7/******************************************************************************************/#defineALARMHAL_GPIO_ReadPin(ALARM_GPIO_PORT,ALARM_GPIO_PIN)/* 读取ALARM引脚 */#defineALARM_PRES7/* ALARM相关的SQW拉低引脚电平 */voidextix_init(void);/* 外部中断初始化 */#endif#include"../Driver/BSP/exti/exti.h"#include"../Driver/SYSTEM/sys/sys.h"#include"../Driver/SYSTEM/delay/delay.h"#include"../Driver/BSP/LED/led.h"#include"../Driver/BSP/beep/beep.h"#include"../Driver/BSP/key/key.h"/** * @brief ALARM 外部中断服务程序 * @param 无 * @retval 无 */voidALARM_INT_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(ALARM_INT_GPIO_PIN);/* 调用中断处理公用函数 清除ALARM所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */__HAL_GPIO_EXTI_CLEAR_IT(ALARM_INT_GPIO_PIN);/* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */}/** * @brief 中断服务程序中需要做的事情 在HAL库中所有的外部中断服务函数都会调用此函数 * @param GPIO_Pin:中断引脚号 * @retval 无 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){delay_ms(10);/* 消抖 */switch(GPIO_Pin){caseALARM_INT_GPIO_PIN:if(ALARM==0){BEEP_TOGGLE();/* 蜂鸣器状态取反 */LED1_TOGGLE();/* LED1状态取反 */}break;default:break;}}/** * @brief 外部中断初始化程序 * @param 无 * @retval 无 */voidextix_init(void){GPIO_InitTypeDef gpio_init_struct;__HAL_RCC_GPIOB_CLK_ENABLE();// 使能GPIOB时钟__HAL_RCC_AFIO_CLK_ENABLE();// 关键:关闭 JTAG,只保留 SWD,释放 PB3/PB4/PB7__HAL_AFIO_REMAP_SWJ_NOJTAG();gpio_init_struct.Pin=ALARM_GPIO_PIN;gpio_init_struct.Mode=GPIO_MODE_IT_FALLING;/* 下升沿触发 */gpio_init_struct.Pull=GPIO_PULLUP;//上拉HAL_GPIO_Init(ALARM_GPIO_PORT,&gpio_init_struct);/* ALARM配置为下降沿触发中断 */HAL_NVIC_SetPriority(ALARM_INT_IRQn,10,0);/* 抢占10,子优先级0 */HAL_NVIC_EnableIRQ(ALARM_INT_IRQn);/* 使能中断线5_9 */}这个EXTI移植到freertos里面,你看该怎么改?移到 FreeRTOS 关键修改:中断里绝对不能用 delay_ms(),必须用全局标志位 + 任务处理
我直接给你改好、能直接用、FreeRTOS 安全版代码,只改关键地方,不破坏你原有逻辑。
一、核心问题(必须改)
你原来代码有 2 个FreeRTOS 致命错误:
- 中断服务函数里用了 delay_ms(10)→ 会卡死系统
- 中断里直接操作蜂鸣器、LED→ 虽然能跑,但不规范、不安全
FreeRTOS 正确做法:
中断只做一件事:置位一个全局 uint8_t 标志位
真正的消抖、蜂鸣器、LED 全部放到FreeRTOS 任务里执行!
二、我帮你改好的代码(直接替换)
1. exti.h 新增全局标志位(你要的 uint8 原子变量)
#ifndef_EXTI_H#define_EXTI_H#include"../Driver/SYSTEM/sys/sys.h"/******************************************************************************************//* 引脚 和 中断编号 & 中断服务函数 定义 */#defineALARM_INT_GPIO_PORTGPIOB#defineALARM_INT_GPIO_PINGPIO_PIN_7#defineALARM_INT_IRQnEXTI9_5_IRQn#defineALARM_INT_IRQHandlerEXTI9_5_IRQHandler/******************************************************************************************//******************************************************************************************//* 引脚 定义 */#defineALARM_GPIO_PORTGPIOB#defineALARM_GPIO_PINGPIO_PIN_7/******************************************************************************************/#defineALARMHAL_GPIO_ReadPin(ALARM_GPIO_PORT,ALARM_GPIO_PIN)/* 读取ALARM引脚 *//* 新增:FreeRTOS 中断安全 全局标志位(uint8_t 单写单读,原子安全) */externuint8_tg_alarm_trigger;voidextix_init(void);/* 外部中断初始化 */#endif2. exti.c 改成 FreeRTOS 安全版
#include"../Driver/BSP/exti/exti.h"#include"../Driver/SYSTEM/sys/sys.h"#include"../Driver/BSP/LED/led.h"#include"../Driver/BSP/beep/beep.h"/* 定义全局标志位(中断里写,任务里读,单字节原子安全) */uint8_tg_alarm_trigger=0;/** * @brief ALARM 外部中断服务程序 * @retval 无 */voidALARM_INT_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(ALARM_INT_GPIO_PIN);__HAL_GPIO_EXTI_CLEAR_IT(ALARM_INT_GPIO_PIN);}/** * @brief 中断回调(只置标志位,不做任何耗时操作) * @param GPIO_Pin * @retval 无 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_Pin==ALARM_INT_GPIO_PIN){if(ALARM==0){/* 只置位标志位!!! */g_alarm_trigger=1;}}}/** * @brief 外部中断初始化程序 * @retval 无 */voidextix_init(void){GPIO_InitTypeDef gpio_init_struct;__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_AFIO_CLK_ENABLE();__HAL_AFIO_REMAP_SWJ_NOJTAG();gpio_init_struct.Pin=ALARM_GPIO_PIN;gpio_init_struct.Mode=GPIO_MODE_IT_FALLING;gpio_init_struct.Pull=GPIO_PULLUP;HAL_GPIO_Init(ALARM_GPIO_PORT,&gpio_init_struct);/* FreeRTOS 中断优先级必须 ≥ 配置的最低优先级(如 10 11 12 13 14 15) */HAL_NVIC_SetPriority(ALARM_INT_IRQn,10,0);HAL_NVIC_EnableIRQ(ALARM_INT_IRQn);}三、你必须在 FreeRTOS 里建一个处理任务(关键)
任务代码(随便放 main 或 task.c 里)
/* 外部引用标志位 */externuint8_tg_alarm_trigger;/* ALARM 处理任务 */voidalarm_task(void*pvParameters){while(1){/* 检测标志位 */if(g_alarm_trigger==1){/* 任务里可以安全用 消抖、操作硬件 */delay_ms(10);if(ALARM==0)// 再确认一次{BEEP_TOGGLE();LED1_TOGGLE();}g_alarm_trigger=0;// 清除标志}/* 任务延时,让出CPU */vTaskDelay(20);}}四、为什么这样改?(对应你刚才问的知识点)
g_alarm_trigger = 1
单字节、全局、单次写 →原子安全,不用信号量if(g_alarm_trigger == 1)
单字节、单次读 →原子安全,不用信号量中断只做“标记”,任务做“处理”
这是FreeRTOS 最标准、最安全的中断处理模型。delay_ms 只能放任务里,不能放中断里
中断里不能阻塞,否则系统直接崩溃。
五、最终总结(你可以直接记住)
移到 FreeRTOS 只改 3 点:
- 中断里删掉 delay_ms()、BEEP、LED
- 中断里只给 uint8_t 全局标志位置 1
- 建一个 FreeRTOS 任务,读标志位 → 消抖 → 控制外设
安全规则:
- 中断:只写标志位(uint8_t 原子)
- 任务:只读标志位 + 处理业务
- 完全不需要信号量
必须加!一定要加 volatile!
我直接给你讲清楚为什么、加在哪、怎么加,这是嵌入式/FreeRTOS 最容易踩的巨坑。
1. 最终正确写法(直接复制)
/* 正确:必须加 volatile */externvolatileuint8_tg_alarm_trigger;/* 定义处也必须加 volatile */volatileuint8_tg_alarm_trigger=0;2. 为什么 uint8_t 原子了,还需要 volatile?
你之前理解的uint8_t 全局变量 = 原子操作,只对了一半:
- 原子性:保证读写不会被切成两半
- volatile:禁止编译器优化,保证每次都去内存读最新值
这是两个完全不同的东西!
编译器会自作聪明干坏事(不加 volatile 必出 bug)
编译器看到你的任务代码:
while(1){if(g_alarm_trigger==1)// 不加 volatile{...}}编译器会优化成:
寄存器=g_alarm_trigger;// 只读一次while(1){if(寄存器==1){...}}结果:中断把 g_alarm_trigger 改成 1 了,任务永远不知道!
因为它只看寄存器,不看内存里的最新值。
3. volatile 的作用(一句话)
告诉编译器:这个变量会在中断/别的任务突然改变,不许优化,每次都必须去内存读最新值!
4. 总结 3 条铁律(嵌入式中断 + FreeRTOS 必背)
- 中断与任务共享的全局变量 = 必须加 volatile
- 单字节 uint8_t + volatile = 读写原子,不需要信号量
- 多字节/多步骤操作 = 必须信号量/临界区
你这个场景完美符合第 2 条:
volatileuint8_tg_alarm_trigger;✅ 安全
✅ 原子
✅ 不卡死
✅ 编译器不瞎优化
5. 最终修改版(直接用)
exti.h
/* 中断安全共享标志位:volatile + uint8_t 黄金组合 */externvolatileuint8_tg_alarm_trigger;exti.c
volatileuint8_tg_alarm_trigger=0;终极结论
volatile = 强制读最新值
uint8_t = 保证读写不被切碎
两个一起用 = 中断与任务之间传递标志位的最完美方案!