当前位置: 首页 > news >正文

FreeRTOS——按键控制任务的挂起和恢复

今天的学习内容是任务的挂起和恢复我使用按键控制任务挂起和恢复又使用了外部中断控制恢复任务踩了好几个坑终于把流程跑顺了。按键控制任务挂起和恢复一、实验功能说明这次实验是在之前双任务点灯的基础上加了两个按键来控制任务状态整体逻辑很简单两个业务任务任务 1 控制 LED0 每秒闪烁任务 2 控制 LED1 每秒闪烁同时串口打印各自的运行日志新增一个按键任务专门处理按键扫描不干扰业务逻辑PA0 按键按下时挂起任务 1LED0 停止闪烁串口不再输出任务 1 的日志PC13 按键按下时恢复任务 1LED0 继续闪烁日志恢复输出。本来以为照着教程写就能跑通结果一开始按一次按键会触发两次打印搞了了半天才搞明白问题出在哪后面也会把踩坑的过程写出来。二、完整实验代码#include sys.h #include delay.h #include usart.h #include led.h #include FreeRTOS.h #include task.h #include key.h // 开始任务配置 #define START_TASK_SIZE 128 #define START_TASK_PRIO 1 TaskHandle_t StartTask_Hander; void start_task( void * pvParameters ); // 任务1配置 #define TASK1_TASK_SIZE 128 #define TASK1_TASK_PRIO 4 TaskHandle_t TASK1Task_Hander; void task1_task( void * pvParameters ); // 任务2配置 #define TASK2_TASK_SIZE 128 #define TASK2_TASK_PRIO 3 TaskHandle_t Task2Task_Hander; void task2_task( void * pvParameters ); // 任务key配置 #define KEY_TASK_SIZE 128 #define KEY_TASK_PRIO 2 TaskHandle_t KeyTask_Hander; void key_task( void * pvParameters ); //主函数 int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//中断分组4 delay_init(); uart_init(115200); LED_Init(); Key_Init(); //创建开始任务 xTaskCreate( (TaskFunction_t) start_task, (char *) start_task, (uint16_t) START_TASK_SIZE, (void *) NULL, (UBaseType_t) START_TASK_PRIO, (TaskHandle_t *) StartTask_Hander ); vTaskStartScheduler(); //开启任务调度 } //开始任务负责创建所有业务任务随后自删除 void start_task( void * pvParameters ) { //创建任务1 xTaskCreate( (TaskFunction_t) task1_task, (char *) task1_task, (uint16_t) TASK1_TASK_SIZE, (void *) NULL, (UBaseType_t) TASK1_TASK_PRIO, (TaskHandle_t *) TASK1Task_Hander ); //创建任务2 xTaskCreate( (TaskFunction_t) task2_task, (char *) task2_task, (uint16_t) TASK2_TASK_SIZE, (void *) NULL, (UBaseType_t) TASK2_TASK_PRIO, (TaskHandle_t *) Task2Task_Hander ); //创建任务key xTaskCreate( (TaskFunction_t) key_task, (char *) key_task, (uint16_t) KEY_TASK_SIZE, (void *) NULL, (UBaseType_t) KEY_TASK_PRIO, (TaskHandle_t *) KeyTask_Hander ); //开始任务完成使命自我删除 vTaskDelete(StartTask_Hander); } //任务1LED0每秒闪烁并打印日志 void task1_task( void * pvParameters ) { char task1_num 0; while(1) { task1_num; LED0 -LED0; vTaskDelay(1000); printf(Task1 is running %d\r\n,task1_num); } } //任务2LED1每秒闪烁并打印日志 void task2_task( void * pvParameters ) { char task2_num 0; while(1) { task2_num; LED1 -LED1; vTaskDelay(1000); printf(Task2 is running %d\r\n,task2_num); } } //按键任务 void key_task( void * pvParameters ) { while(1) { // 处理 PA0 按键挂起任务1 if( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskDelay(20); // 消抖延时 if( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskSuspend( TASK1Task_Hander ); printf(Task1 is Supend \r\n); // 等待按键松手避免长按重复触发 while( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskDelay(10); } } } // 处理 PC13 按键恢复任务1 if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) 0) { vTaskDelay(20); // 消抖延时 if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) 0) { vTaskResume( TASK1Task_Hander ); printf(Task1 is Resume \r\n); // 等待按键松手避免长按重复触发 while( GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) 0 ) { vTaskDelay(10); } } } vTaskDelay(10); } }注意这里只附了主函数代码按键配置和led的配置前面的博客里面都有去找找添加进工程就好了。三、代码说明1. 任务配置部分这次我还是用了动态创建任务的方式四个任务分别定义了栈大小和优先级任务 1 优先级最高任务 2 次之按键任务优先级最低这样不会出现按键任务抢占业务任务的情况。每个任务都定义了句柄后面挂起和恢复都要靠这个句柄来操作。2. main 函数和开始任务和之前的例子一样main 函数只做硬件初始化和创建开始任务调度器启动后就交给 FreeRTOS 管理。开始任务负责创建三个业务任务创建完成后就自我删除不占用系统资源这也是工程里比较规范的写法。3. 业务任务逻辑任务 1 和任务 2 的逻辑没什么变化都是循环翻转 LED同时打印运行日志去掉了之前删除任务的逻辑改成了按键控制挂起恢复。4. 按键任务核心部分也是之前踩坑的地方一开始写的按键任务逻辑很简单就是判断按键按下延时消抖后直接执行挂起 / 恢复操作结果按一次按键会触发两次打印而且长按的时候会反复执行甚至出现任务多次挂起后恢复不了的情况。后来查了资料才明白原来的消抖逻辑不完善而且没有等待按键松手导致按键按下的过程中任务循环会多次检测到低电平重复执行操作。修改后的代码加了两层判断和松手等待现在按一次按键只会触发一次操作长按也不会重复执行了。四、运行现象和踩坑修复记录1. 预期现象程序运行后LED0 和 LED1 都会正常闪烁串口同时输出两个任务的日志。按下 PA0LED0 停止闪烁串口不再输出任务 1 的日志按下 PC13LED0 恢复闪烁日志也恢复输出。2. 踩坑修复过程坑 1按一次按键触发两次打印一开始的代码只加了简单的延时消抖没有等待按键松手导致按键按下的过程中任务循环多次检测到低电平重复执行printf和挂起操作。解决方法是在按键触发后加一个while循环等待按键松手直到检测到高电平才退出保证一次按下只触发一次逻辑。坑 2按键消抖延时太长反应迟钝一开始用了 100ms 的消抖延时结果按键按下后要等好久才会响应改成 20ms 就正常了机械按键的抖动一般只有几毫秒20ms 足够消抖了。坑 3任务多次挂起后恢复不了一开始长按按键的时候任务会被多次挂起FreeRTOS 里任务的挂起是计数的挂起几次就要恢复几次才能正常运行所以单次按恢复键没反应。加了松手等待后长按不会重复挂起这个问题就解决了。五、总结按键控制任务挂起 / 恢复的关键要点这次实验让我对 FreeRTOS 的任务状态管理有了更直观的理解整理一下通用的实现要点以后写代码可以直接套用提前定义好要控制的任务句柄挂起和恢复都需要用到句柄单独创建一个低优先级的按键任务专门处理按键扫描不干扰业务逻辑按键扫描必须加双层判断 延时消抖同时等待按键松手避免长按重复触发挂起任务用vTaskSuspend(句柄)恢复任务用vTaskResume(句柄)注意任务挂起计数的问题按键任务里加适当延时避免频繁扫描占用 CPU 资源。外部中断恢复任务一、我这次想实现的功能PA0 按键→ 挂起任务 1LED0 停止闪烁PC13 外部中断→ 恢复任务 1LED0 继续闪烁任务 2 正常运行不受影响串口能看到挂起、恢复、中断触发信息结果一开始按 PA0 能挂起但按 PC13 完全没反应串口不打印、灯不亮、中断像死了一样…二、我踩过的真实大坑1. 写了中断初始化但 main 函数没调用我写了Exti_Init()结果在 main 里忘了写Exti_Init();中断都没开启你按烂按键都没用啊这是新手最容易犯的低级错误。2. 中断通道写错一开始写了 EXTI0_IRQnPC13 是 EXTI13属于EXTI10~EXTI15中断号必须是EXTI15_10_IRQn我之前写成了EXTI0_IRQn完全不匹配中断根本进不去。3. 中断服务函数名写错PC13 必须用void EXTI15_10_IRQHandler(void)名字错一个字母都进不去中断。4. 中断里不能加延时我最开始在中断里写了delay_xms(20);直接导致系统卡死、死机、不调度。中断里严禁延时快进快出5. 忘记清除中断标志位没写这句EXTI_ClearITPendingBit(EXTI_Line13);后果一触发就死循环进中断系统直接卡死。6. FreeRTOS 中断里必须用 FromISR 函数最开始我直接写vTaskResume(TASK1Task_Hander);在中断里绝对不行 必须用xTaskResumeFromISR(TASK1Task_Hander);三、最终正确代码我现在跑通的版本我直接把我最终能用的代码放出来复制就能跑。1. exti.c 中断初始化 中断服务函数#include exti.h #include stm32f10x.h #include led.h #include key.h #include FreeRTOS.h #include task.h void Exti_Init(void) { EXTI_InitTypeDef EXTIInitstruct; NVIC_InitTypeDef NVICInitstruct; Key_Init(); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13); EXTIInitstruct.EXTI_Line EXTI_Line13; EXTIInitstruct.EXTI_Mode EXTI_Mode_Interrupt; EXTIInitstruct.EXTI_Trigger EXTI_Trigger_Falling; EXTIInitstruct.EXTI_LineCmd ENABLE; EXTI_Init(EXTIInitstruct); // 重点PC13 必须用 EXTI15_10_IRQn NVICInitstruct.NVIC_IRQChannel EXTI15_10_IRQn; NVICInitstruct.NVIC_IRQChannelPreemptionPriority 6; NVICInitstruct.NVIC_IRQChannelSubPriority 0; NVICInitstruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVICInitstruct); } // 外部中断句柄 extern TaskHandle_t TASK1Task_Hander; void EXTI15_10_IRQHandler(void) { BaseType_t YieldRequired pdFALSE; if(EXTI_GetITStatus(EXTI_Line13) ! RESET) { // 中断里恢复任务必须用 FromISR YieldRequired xTaskResumeFromISR(TASK1Task_Hander); printf(外部中断触发 → 恢复任务1\r\n); // 清除标志位必须写 EXTI_ClearITPendingBit(EXTI_Line13); } portYIELD_FROM_ISR(YieldRequired); }2. main.c 我最终完整版#include sys.h #include delay.h #include usart.h #include led.h #include FreeRTOS.h #include task.h #include key.h #include exti.h // 开始任务 #define START_TASK_SIZE 128 #define START_TASK_PRIO 1 TaskHandle_t StartTask_Hander; void start_task( void * pvParameters ); // 任务1 #define TASK1_TASK_SIZE 128 #define TASK1_TASK_PRIO 4 TaskHandle_t TASK1Task_Hander; void task1_task( void * pvParameters ); // 任务2 #define TASK2_TASK_SIZE 128 #define TASK2_TASK_PRIO 3 TaskHandle_t Task2Task_Hander; void task2_task( void * pvParameters ); // 按键任务 #define KEY_TASK_SIZE 128 #define KEY_TASK_PRIO 2 TaskHandle_t KeyTask_Hander; void key_task( void * pvParameters ); int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); delay_init(); uart_init(115200); LED_Init(); Key_Init(); Exti_Init(); // 必须开启外部中断 xTaskCreate( start_task, start_task, START_TASK_SIZE, NULL, START_TASK_PRIO, StartTask_Hander ); vTaskStartScheduler(); } void start_task( void * pvParameters ) { xTaskCreate( task1_task, task1_task, TASK1_TASK_SIZE, NULL, TASK1_TASK_PRIO, TASK1Task_Hander ); xTaskCreate( task2_task, task2_task, TASK2_TASK_SIZE, NULL, TASK2_TASK_PRIO, Task2Task_Hander ); xTaskCreate( key_task, key_task, KEY_TASK_SIZE, NULL, KEY_TASK_PRIO, KeyTask_Hander ); vTaskDelete(StartTask_Hander); } void task1_task( void * pvParameters ) { char task1_num 0; while(1) { task1_num; LED0 ~LED0; vTaskDelay(1000); printf(Task1 is running %d\r\n,task1_num); } } void task2_task( void * pvParameters ) { char task2_num 0; while(1) { task2_num; LED1 ~LED1; vTaskDelay(1000); printf(Task2 is running %d\r\n,task2_num); } } void key_task( void * pvParameters ) { while(1) { if( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskDelay(20); if( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskSuspend( TASK1Task_Hander ); printf(任务1已挂起\r\n); while( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) 0 ) { vTaskDelay(10); } } } vTaskDelay(10); } }四、现在运行效果完全正常开机 → LED0、LED1 同时闪烁按PA0→ 任务 1 挂起 → LED0 停止闪烁按PC13→ 外部中断触发 → 任务 1 恢复 → LED0 继续闪烁串口打印清晰不重复、不乱码、不卡死终于完美跑通了五、我总结的外部中断恢复任务黄金规则新手必看中断初始化一定要在 main 里调用PC13 中断号是EXTI15_10_IRQn中断服务函数名必须是EXTI15_10_IRQHandler中断里不能有任何延时必须清除中断标志位FreeRTOS 中断里必须用FromISR 结尾的函数中断优先级不能太高一般设置为 6~8 最安全总结从按键扫描到外部中断我算是真正把 FreeRTOS 任务挂起 / 恢复吃透了。 最开始按键按一次触发两次后来中断不响应再到系统卡死… 一步一步踩坑一步一步修正最后终于跑通。写这篇就是想告诉大家FreeRTOS 不难坑多而已踩完就通透了如果你也在做按键 中断控制任务希望这篇能帮你少走 90% 的弯路。
http://www.gsyq.cn/news/1414776.html

相关文章:

  • 高端人形机器人轴承厂家与品牌怎么选?关节轴承核心技术解析 - 品牌2025
  • 矿山做业实景透明.智能预警透明化三维立体重构视频孪生数字孪生解决方案
  • 食品级硅胶认证标准解析:筑牢安全底线,看懂行业准入核心要求
  • 5分钟AI图像分层终极指南:一键将单图变多层PSD
  • Obsidian Projects 终极指南:如何在笔记中实现高效项目管理
  • WRF嵌套网格设计工具盘点:除了DomainWizard,还有哪些好用的网页版和QGIS插件?
  • 2026年6月重磅推荐 | 罗杰杜彼官方售后服务网络2026焕新升级公告 - 资讯速览
  • 在Mac上打造专业级SIP电话:Telephone开源项目深度解析
  • 互联网大厂 Java 求职面试:从微服务到安全框架的技术探讨
  • 华为云ecs与openstack nova的关系:如果说 Nova 是 OpenStack 这个“开源发动机原型”,那么华为云 ECS 就是基于这个原型,经过深度魔改、强化并对外开售的“豪华量产车”。
  • 2026重庆黄金回收避坑实测 新手卖金不亏价选店全攻略 - 奢侈品回收测评
  • 《机乎 vs Moltbook:2026 年 AI 社交平台深度对比》
  • 零成本颠覆传统:3步构建企业级条码系统的开源革命
  • DDrawCompat:Windows老游戏兼容性修复的终极技术方案
  • Linux 组调度与 cgroup 集成:容器资源隔离的底层实现
  • 苹果设备降级神器:LeetDown让你的旧iPhone/iPad重获新生
  • Super Productivity终极指南:如何用时间盒管理法提升10倍工作效率
  • 三步构建离线图书馆:WebToEpub帮你将网页小说永久收藏
  • 为什么越来越多的企业,开始用“数字人“接待客户?
  • 2026论文全流程终极榜单:10款AI智能降重工具,智能改写快速定稿成文
  • 从零开始掌握Smithbox:魂系列游戏修改的终极指南
  • 网页视频无法保存?这个开源工具让你轻松捕获每一个精彩瞬间
  • 猫抓插件终极指南:三步轻松下载网页视频音频资源
  • taotoken助力claudecode用户摆脱封号与token不足困扰
  • Logrotate 配置指南
  • 5大核心能力解析:原神自动化助手如何重塑游戏体验
  • 如何高效使用Python自动化工具:实战应用完整指南
  • CesiumHeatmap:突破三维空间热力图可视化的技术瓶颈
  • 视频PPT提取终极指南:3分钟从视频中智能提取幻灯片
  • Linux服务器内存告急?别慌,先看看是不是rsyslog在‘偷吃’内存