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

FreeRTOS 手动移植教程(二):任务管理——多任务创建、优先级抢占与删除

上一篇我们成功搭建了标准库与 FreeRTOS 的工程,并运行了一个 LED 闪烁任务。本篇文章将在此基础上创建多个任务,直观感受优先级抢占时间片轮转,并掌握任务删除及参数传递的方法。所有代码均基于上一篇文章的工程,可直接添加运行。


一、实验目标与硬件准备

1.1 实验现象

  • 创建三个不同优先级的任务,控制三个 LED 以不同频率闪烁;
  • 高优先级任务就绪时可立即抢占低优先级任务;
  • 同优先级任务之间自动轮流执行;
  • 某个任务运行一定次数后自行删除。

1.2 硬件连接

本文需要三个 LED,如仅有一块最小系统板,可按以下方式外接:

  • LED1:PA0 —— 串联 220Ω 限流电阻,低电平点亮
  • LED2:PA1 —— 串联 220Ω 限流电阻,低电平点亮
  • LED3:PC13 —— 使用板载 LED,同样为低电平点亮

若你的 LED 是高电平点亮,只需在初始化时反向设置电平极性即可。


二、扩展板级驱动:支持多 LED

在上一篇bsp_led.c的基础上扩展,提供多个 LED 的初始化与翻转函数。

bsp_led.h

#ifndefBSP_LED_H#defineBSP_LED_H#include"stm32f10x.h"voidLED_InitAll(void);voidLED1_Toggle(void);voidLED2_Toggle(void);voidLED3_Toggle(void);#endif

bsp_led.c

#include"bsp_led.h"voidLED_InitAll(void){GPIO_InitTypeDef GPIO_InitStructure;/* 使能 GPIOA 和 GPIOC 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);/* PA0 - LED1 */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_0);// 输出高电平,LED 熄灭/* PA1 - LED2 */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_1);/* PC13 - LED3 */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_SetBits(GPIOC,GPIO_Pin_13);}voidLED1_Toggle(void){GPIOA->ODR^=GPIO_Pin_0;}voidLED2_Toggle(void){GPIOA->ODR^=GPIO_Pin_1;}voidLED3_Toggle(void){GPIOC->ODR^=GPIO_Pin_13;}

三、创建多任务,观察优先级抢占

3.1 任务代码

main.c中创建三个任务,优先级分别设为 1、2、3(数字越大优先级越高),每个任务控制一个 LED。

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"#include"bsp_led.h"TaskHandle_t Task1_Handle=NULL;TaskHandle_t Task2_Handle=NULL;TaskHandle_t Task3_Handle=NULL;/* LED1 闪烁任务 —— 优先级 1(最低) */voidvTask1(void*pvParameters){while(1){LED1_Toggle();vTaskDelay(pdMS_TO_TICKS(200));// 周期约 400ms}}/* LED2 闪烁任务 —— 优先级 2 */voidvTask2(void*pvParameters){while(1){LED2_Toggle();vTaskDelay(pdMS_TO_TICKS(500));// 周期约 1s}}/* LED3 闪烁任务 —— 优先级 3(最高) */voidvTask3(void*pvParameters){while(1){LED3_Toggle();vTaskDelay(pdMS_TO_TICKS(1000));// 周期约 2s}}intmain(void){LED_InitAll();xTaskCreate(vTask1,"Task1",128,NULL,1,&Task1_Handle);xTaskCreate(vTask2,"Task2",128,NULL,2,&Task2_Handle);xTaskCreate(vTask3,"Task3",128,NULL,3,&Task3_Handle);vTaskStartScheduler();while(1);}

3.2 实验现象与原理

下载运行后,观察三个 LED:

  • LED3(最高优先级):稳定以 2s 周期闪烁,几乎不受干扰;
  • LED2(中等优先级):以 1s 周期闪烁,但偶尔会有微小的停顿(被高优先级任务抢占);
  • LED1(最低优先级):闪烁频率明显低于预期(可能会很慢),因为 CPU 大部分时间被高优先级任务占用,它只能在所有高优先级任务都阻塞时才获得执行权。

原理分析:
configUSE_PREEMPTION = 1时,内核会在每个系统节拍中断中检查是否有更高优先级任务就绪。一旦高优先级任务延时结束,立即剥夺当前任务的 CPU,通过 PendSV 切换到高优先级任务。这就解释了为何低优先级任务的实际执行频率会变慢。

注意事项:
如果高优先级任务一直在死循环且从不阻塞(例如没有vTaskDelay),那么低优先级任务将永远得不到执行(任务饥饿)。因此在实时系统中,高优先级任务必须适时“让出” CPU。


四、同优先级任务与时间片轮转

4.1 创建两个同优先级任务

将任务 1 和任务 2 都改为优先级 1,并删除任务 3。

xTaskCreate(vTask1,"Task1",128,NULL,1,&Task1_Handle);xTaskCreate(vTask2,"Task2",128,NULL,1,&Task2_Handle);

两个任务优先级相同,当它们同时就绪时,FreeRTOS 默认会采用时间片轮转(Time Slicing),每个任务轮流运行一个系统节拍(默认为 1ms)。

4.2 实验验证

两个 LED 仍会按照各自vTaskDelay设置的频率闪烁,互不影响。这是因为它们在各自延时期间都处于阻塞态,不会浪费 CPU。
但如果将两个任务的延时都去掉,改成纯死循环:

voidvTask1(void*pvParameters){while(1)LED1_Toggle();}voidvTask2(void*pvParameters){while(1)LED2_Toggle();}

此时两个任务永远不阻塞,且优先级相同。在系统节拍中断中,内核会轮流切换它们,两个 LED 将以大约 1ms 的间隔交替翻转,用示波器可以观察到非常规律的方波。

时间片轮转仅在configUSE_PREEMPTION = 1configUSE_TIME_SLICING = 1(默认开启)时,对同优先级且都就绪的任务生效。


五、任务的删除

5.1 删除自身

调用vTaskDelete(NULL);可以删除当前运行的任务,其占用的堆栈和 TCB 资源会被自动回收。

示例:任务运行 5 次后自我删除。

voidvTask_SelfDelete(void*pvParameters){intcount=0;while(1){LED1_Toggle();vTaskDelay(pdMS_TO_TICKS(200));count++;if(count>=5){vTaskDelete(NULL);// 删除自己,任务在此处结束}}}

5.2 删除其他任务

通过任务句柄,一个任务可以删除另一个任务。例如在任务 A 中删除任务 B:

TaskHandle_t TaskB_Handle=NULL;voidvTaskA(void*pvParameters){vTaskDelay(pdMS_TO_TICKS(1000));if(TaskB_Handle!=NULL){vTaskDelete(TaskB_Handle);// 删除任务 BTaskB_Handle=NULL;}// ...}

被删除任务的堆栈和 TCB 内存会立即释放(若使用heap_4.cheap_2.c等支持释放的策略),句柄也应设置为 NULL 以防止悬空指针。


六、任务参数传递

xTaskCreatepvParameters参数可以传递任意类型指针,使同一个任务函数处理不同的硬件或数据。

6.1 传递整数 ID

直接使用整数 ID 来区分不同的 LED(将 int 强制转换为 void* 传递):

voidvLED_Task(void*pvParameters){intled_id=(int)pvParameters;// 取出 IDwhile(1){switch(led_id){case0:LED1_Toggle();break;case1:LED2_Toggle();break;case2:LED3_Toggle();break;}vTaskDelay(pdMS_TO_TICKS(500));}}intmain(void){LED_InitAll();xTaskCreate(vLED_Task,"LED0",128,(void*)0,1,NULL);xTaskCreate(vLED_Task,"LED1",128,(void*)1,2,NULL);xTaskCreate(vLED_Task,"LED2",128,(void*)2,3,NULL);vTaskStartScheduler();while(1);}

虽然直接将整数强制转换为指针是一种常见技巧,但请注意它依赖于 CPU 架构。在 32 位 Cortex-M 上指针与 int 宽度相同,此用法安全有效。

6.2 传递字符串

voidvPrintTask(void*pvParameters){char*msg=(char*)pvParameters;while(1){// 例如通过串口打印 msgvTaskDelay(pdMS_TO_TICKS(1000));}}// 创建时:xTaskCreate(vPrintTask, "Print", 256, "Hello RTOS", 1, NULL);

七、总结

本篇通过动手实验,展示了 FreeRTOS 最核心的任务管理机制:

  • 优先级抢占:高优先级就绪立即剥夺 CPU;
  • 时间片轮转:同优先级任务公平共享 CPU;
  • 任务删除:动态回收任务资源;
  • 参数传递:实现通用的任务处理逻辑。

这些是日常项目中使用频率最高的操作。下一篇文章将深入系统节拍与延时函数,分析vTaskDelay的实现原理,并介绍精度更高的vTaskDelayUntil用法。


下一篇:FreeRTOS 任务延时与时间管理 —— 从裸机 delay 到 vTaskDelayUntil。

http://www.jsqmd.com/news/946115/

相关文章:

  • ROS节点自启动踩坑实录:从startup Application到robot_upstart,我为什么最终选择了后者?
  • 从扫地机到自动驾驶:聊聊SLAM技术如何用激光雷达和视觉传感器搞定室内外定位
  • POP3协议抓包避坑指南:Wireshark过滤器这样设,一眼锁定关键认证数据
  • Linux 内核中的内存映射:从信号捕获到自动维护监控系统
  • 选购宝马专修,宝诚汇是你的明智之选 - 工业品牌热点
  • 从‘暴力破解’到‘算法还原’:深度解析super_mega_protection.exe的密钥校验逻辑
  • Seraphine:英雄联盟智能辅助工具的终极完整指南
  • 2000年中国高速/国道/铁路线状GIS数据包(SHP格式,含完整坐标系)
  • 如何撰写高质量研究周报:从信息筛选到价值呈现的工程实践
  • AirSim 1.3.1 Python API实战:用代码控制天气、时间与碰撞检测,打造动态仿真环境
  • 互联网大厂Java面试:从Spring框架到微服务场景的技术问答
  • 性价比高的全屋定制厂家直供门窗哪个靠谱
  • 一高科技集团三大业务布局助力教育高质量发展
  • 别再手动传证书了!K8s里用cert-manager自动管理TLS证书的保姆级教程
  • Cadence 16.6老用户的福音:Library Builder汉化版详细菜单解读与配置实战
  • 别再乱用tinyint(1)了!详解MySQL、MyBatis与Java类型映射的“潜规则”与最佳实践
  • MySQL 8.0在Docker里大小写敏感踩坑记:从‘表不存在’到彻底解决的完整复盘
  • LabVIEW 2019 生成 .NET DLL 实战:手把手教你让C# WinForm调用LabVIEW加法函数
  • 别扔!全志A13老平板变身Linux小主机:Armbian镜像制作与Lima开源GPU驱动实战
  • 保姆级教程:手把手教你用FrontEnd Plus和十六进制编辑器破解Java试用版限制(附字节码修改原理)
  • 2026年现阶段海珠区小规模代理记账企业推荐:如何甄选专业、合规、高价值的财税伙伴? - 2026年企业资讯
  • 设计团队效率提升370%的秘密:我们用LLM+向量数据库重构了整个设计资产管理系统(内部泄露版技术栈全图)
  • 从手机干扰到汽车失灵:聊聊我们身边那些‘看不见’的电磁兼容(EMC)问题
  • 绕过软件保护实战:不修改super_mega_protection.exe,如何暴力破解它的用户名?
  • EduCoder实训答案查询网站是怎么做出来的?从爬虫到前端的全栈技术拆解
  • 英伟达RTX Spark登场,端侧AI能否打破现状?
  • STM32在线升级时中断卡死?手把手教你用RAM运行中断函数(F0/F1通用)
  • Capstone:多架构支持的终极反汇编器,2025 - 2026 年多版本更新亮点多!
  • 用LabelMe标注时图片闪退?可能是PIL模块在‘挑食’(附Python一键修复脚本)
  • GPT-5.5 新手快速上手与实战指南