FreeRTOS 创建单任务与多任务(SRAM 动态内存)
2026/8/20 10:30:54 网站建设 项目流程

一、前言

在 FreeRTOS 中,任务创建有两种方式:动态内存创建静态内存创建。本文使用最常见的动态内存创建方式,通过STM32F103 标准库 + FreeRTOS演示:

  • 创建单任务:让一个 LED 闪烁;
  • 创建多任务:让两个 LED 以不同频率闪烁。

硬件:STM32F103C8T6,LED1 接 PC13,LED2 接 PA0(若你的板子不同,修改引脚即可)。
软件:Keil MDK5、标准外设库 V3.5、FreeRTOS V10.x。


二、动态内存创建任务原理

调用xTaskCreate()时,FreeRTOS 会从堆中动态申请两块内存:

  1. 任务控制块(TCB):保存任务状态、优先级、栈指针等信息;
  2. 任务栈:任务运行时局部变量和函数调用使用。

因此必须满足:

  • configSUPPORT_DYNAMIC_ALLOCATION1
  • configTOTAL_HEAP_SIZE足够大(例如 8KB)。

三、工程准备(标准库移植注意)

  1. 将 FreeRTOS 源码加入工程,并添加头文件路径;
  2. 准备一个标准库点灯工程;
  3. 重要:注释掉stm32f10x_it.c中的以下三个中断函数,避免与 FreeRTOS 冲突:
// stm32f10x_it.c 中注释掉下面三个函数// void SVC_Handler(void) {}// void PendSV_Handler(void) {}// void SysTick_Handler(void) {}

然后在FreeRTOSConfig.h中做中断映射:

#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler

四、FreeRTOSConfig.h 关键配置

#ifndefFREERTOS_CONFIG_H#defineFREERTOS_CONFIG_H#defineconfigUSE_PREEMPTION1#defineconfigUSE_IDLE_HOOK0#defineconfigUSE_TICK_HOOK0#defineconfigCPU_CLOCK_HZ(72000000UL)#defineconfigTICK_RATE_HZ(1000UL)#defineconfigMAX_PRIORITIES(5)#defineconfigMINIMAL_STACK_SIZE((unsignedshort)128)#defineconfigTOTAL_HEAP_SIZE(8*1024)#defineconfigMAX_TASK_NAME_LEN(16)#defineconfigUSE_16_BIT_TICKS0#defineconfigIDLE_SHOULD_YIELD1#defineconfigUSE_MUTEXES0#defineconfigQUEUE_REGISTRY_SIZE0#defineconfigCHECK_FOR_STACK_OVERFLOW0#defineconfigUSE_RECURSIVE_MUTEXES0#defineconfigUSE_MALLOC_FAILED_HOOK0#defineconfigUSE_APPLICATION_TASK_TAG0#defineconfigUSE_COUNTING_SEMAPHORES0#defineconfigGENERATE_RUN_TIME_STATS0/* 动态内存创建必须为 1 */#defineconfigSUPPORT_DYNAMIC_ALLOCATION1#defineconfigSUPPORT_STATIC_ALLOCATION0/* 使能 vTaskDelay 等 API */#defineINCLUDE_vTaskDelay1#defineINCLUDE_vTaskDelete1/* 中断函数映射 */#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler#endif/* FREERTOS_CONFIG_H */

五、创建单任务(完整代码)

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"/* LED1 定义 */#defineLED1_PORTGPIOC#defineLED1_PINGPIO_Pin_13/* 任务函数声明 */staticvoidLed1_Task(void*pvParameters);/* LED1 初始化 */staticvoidLED1_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_Pin=LED1_PIN;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(LED1_PORT,&GPIO_InitStructure);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 默认熄灭 */}/* LED1 闪烁任务 */staticvoidLed1_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED1_PORT,LED1_PIN);/* 点亮 */vTaskDelay(500/portTICK_PERIOD_MS);/* 延时 500ms */GPIO_SetBits(LED1_PORT,LED1_PIN);/* 熄灭 */vTaskDelay(500/portTICK_PERIOD_MS);/* 延时 500ms */}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* 必须 */LED1_Init();/* 动态内存创建单任务 */BaseType_t xReturn=xTaskCreate(Led1_Task,/* 任务函数 */"Led1Task",/* 任务名称 */configMINIMAL_STACK_SIZE,/* 栈深度(字) */NULL,/* 任务参数 */1,/* 优先级 */NULL/* 任务句柄 */);if(xReturn==pdPASS){vTaskStartScheduler();/* 启动调度器 */}while(1){}/* 不会执行到这里 */}

说明:这就是最简单的单任务创建流程,LED1 以 1Hz 频率闪烁。


六、创建多任务(完整代码)

在单任务基础上,增加第二个 LED(PA0)和第二个任务,两个任务并发运行。

#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"/* LED 定义 */#defineLED1_PORTGPIOC#defineLED1_PINGPIO_Pin_13#defineLED2_PORTGPIOA#defineLED2_PINGPIO_Pin_0/* 任务函数声明 */staticvoidLed1_Task(void*pvParameters);staticvoidLed2_Task(void*pvParameters);/* LED 初始化 */staticvoidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;/* 使能 GPIOC 和 GPIOA 时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA,ENABLE);/* LED1: PC13 推挽输出 */GPIO_InitStructure.GPIO_Pin=LED1_PIN;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(LED1_PORT,&GPIO_InitStructure);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 默认熄灭 *//* LED2: PA0 推挽输出 */GPIO_InitStructure.GPIO_Pin=LED2_PIN;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(LED2_PORT,&GPIO_InitStructure);GPIO_SetBits(LED2_PORT,LED2_PIN);/* 默认熄灭 */}/* LED1 闪烁任务(慢闪) */staticvoidLed1_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED1_PORT,LED1_PIN);/* 点亮 */vTaskDelay(500/portTICK_PERIOD_MS);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 熄灭 */vTaskDelay(500/portTICK_PERIOD_MS);}}/* LED2 闪烁任务(快闪) */staticvoidLed2_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED2_PORT,LED2_PIN);/* 点亮 */vTaskDelay(200/portTICK_PERIOD_MS);GPIO_SetBits(LED2_PORT,LED2_PIN);/* 熄灭 */vTaskDelay(200/portTICK_PERIOD_MS);}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* 必须 */LED_Init();/* 创建任务1:LED1 闪烁,优先级 1 */BaseType_t xReturn1=xTaskCreate(Led1_Task,"Led1Task",configMINIMAL_STACK_SIZE,NULL,1,/* 优先级 1 */NULL);/* 创建任务2:LED2 闪烁,优先级 2(更高) */BaseType_t xReturn2=xTaskCreate(Led2_Task,"Led2Task",configMINIMAL_STACK_SIZE,NULL,2,/* 优先级 2 */NULL);/* 两个任务都创建成功则启动调度器 */if(xReturn1==pdPASS&&xReturn2==pdPASS){vTaskStartScheduler();}while(1){}/* 不会执行到这里 */}

现象:LED1 慢闪(1Hz),LED2 快闪(2.5Hz),互不干扰,这就是多任务并发的直观体现。


七、代码解析

1. 任务函数必须是无返回的死循环

每个任务都是一个独立执行的函数,不能return,否则会导致系统异常。

2.vTaskDelay()的作用

让任务进入阻塞状态,CPU 可以执行其他任务。单任务时 CPU 大部分空闲;多任务时两个任务轮流获得 CPU。

3.xTaskCreate()参数

参数含义
任务函数名任务入口函数
任务名称字符串,仅用于调试,最长configMAX_TASK_NAME_LEN
栈深度单位是字(4字节)configMINIMAL_STACK_SIZE=128即 512 字节
任务参数传给任务函数的指针,不需要传NULL
优先级数字越大优先级越高(0 为最低,configMAX_PRIORITIES-1为最高)
任务句柄用于后续操作任务(删除、挂起等),不需要时传NULL

4. 多任务与单任务的区别

多任务只是多次调用xTaskCreate()而已。每个任务拥有独立的栈和 TCB,调度器自动切换。注意:

  • 每个任务栈大小要足够,否则栈溢出;
  • 优先级可相同或不同,相同优先级会时间片轮转;
  • 任务数量受限于堆大小(configTOTAL_HEAP_SIZE)。

八、常见问题

1. 任务创建失败

  • 检查configSUPPORT_DYNAMIC_ALLOCATION是否为1
  • 增大configTOTAL_HEAP_SIZE

2. 编译报错SVC_Handler重复定义

  • 确保已注释stm32f10x_it.c中的SVC_HandlerPendSV_HandlerSysTick_Handler

3. 进入 HardFault

  • 中断优先级分组必须为NVIC_PriorityGroup_4
  • 确认FreeRTOSConfig.h中的中断映射正确。

4. 两个任务似乎不同步闪烁?

这是正常的,因为 FreeRTOS 调度器按时间片切换任务,LED 闪烁频率由各自的vTaskDelay决定,互不影响。


总结

本文使用标准库 + FreeRTOS 动态内存演示了单任务和多任务的创建方法。核心就是xTaskCreate()函数,多任务只是重复调用它。掌握这个流程后,复杂应用不过是创建更多任务并通过队列、信号量进行通信。

希望本文能帮你快速入门 FreeRTOS 的多任务编程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询