一、前言
在 FreeRTOS 中,任务创建有两种方式:动态内存创建和静态内存创建。本文使用最常见的动态内存创建方式,通过 STM32F103 标准库 + FreeRTOS 演示:
- 创建单任务:让一个 LED 闪烁;
- 创建多任务:让两个 LED 以不同频率闪烁。
硬件:STM32F103C8T6,LED1 接 PC13,LED2 接 PA0(若你的板子不同,修改引脚即可)。
软件:Keil MDK5、标准外设库 V3.5、FreeRTOS V10.x。
二、动态内存创建任务原理
调用 xTaskCreate() 时,FreeRTOS 会从堆中动态申请两块内存:
- 任务控制块(TCB):保存任务状态、优先级、栈指针等信息;
- 任务栈:任务运行时局部变量和函数调用使用。
因此必须满足:
configSUPPORT_DYNAMIC_ALLOCATION为1;configTOTAL_HEAP_SIZE足够大(例如 8KB)。
三、工程准备(标准库移植注意)
- 将 FreeRTOS 源码加入工程,并添加头文件路径;
- 准备一个标准库点灯工程;
- 重要:注释掉
stm32f10x_it.c中的以下三个中断函数,避免与 FreeRTOS 冲突:
// stm32f10x_it.c 中注释掉下面三个函数
// void SVC_Handler(void) {}
// void PendSV_Handler(void) {}
// void SysTick_Handler(void) {}
然后在 FreeRTOSConfig.h 中做中断映射:
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
四、FreeRTOSConfig.h 关键配置
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ (72000000UL)
#define configTICK_RATE_HZ (1000UL)
#define configMAX_PRIORITIES (5)
#define configMINIMAL_STACK_SIZE ((unsigned short)128)
#define configTOTAL_HEAP_SIZE (8 * 1024)
#define configMAX_TASK_NAME_LEN (16)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 0
#define configQUEUE_REGISTRY_SIZE 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configGENERATE_RUN_TIME_STATS 0
/* 动态内存创建必须为 1 */
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSUPPORT_STATIC_ALLOCATION 0
/* 使能 vTaskDelay 等 API */
#define INCLUDE_vTaskDelay 1
#define INCLUDE_vTaskDelete 1
/* 中断函数映射 */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
五、创建单任务(完整代码)
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
/* LED1 定义 */
#define LED1_PORT GPIOC
#define LED1_PIN GPIO_Pin_13
/* 任务函数声明 */
static void Led1_Task(void *pvParameters);
/* LED1 初始化 */
static void LED1_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 闪烁任务 */
static void Led1_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 */
}
}
int main(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 定义 */
#define LED1_PORT GPIOC
#define LED1_PIN GPIO_Pin_13
#define LED2_PORT GPIOA
#define LED2_PIN GPIO_Pin_0
/* 任务函数声明 */
static void Led1_Task(void *pvParameters);
static void Led2_Task(void *pvParameters);
/* LED 初始化 */
static void LED_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 闪烁任务(慢闪) */
static void Led1_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 闪烁任务(快闪) */
static void Led2_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);
}
}
int main(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_Handler、PendSV_Handler、SysTick_Handler。
3. 进入 HardFault
- 中断优先级分组必须为
NVIC_PriorityGroup_4; - 确认
FreeRTOSConfig.h中的中断映射正确。
4. 两个任务似乎不同步闪烁?
这是正常的,因为 FreeRTOS 调度器按时间片切换任务,LED 闪烁频率由各自的 vTaskDelay 决定,互不影响。
总结
本文使用 标准库 + FreeRTOS 动态内存 演示了单任务和多任务的创建方法。核心就是 xTaskCreate() 函数,多任务只是重复调用它。掌握这个流程后,复杂应用不过是创建更多任务并通过队列、信号量进行通信。
希望本文能帮你快速入门 FreeRTOS 的多任务编程。
&spm=1001.2101.3001.5002&articleId=163862624&d=1&t=3&u=dbc33c5c61c5427c834d0c89ec463abc)
7246

被折叠的 条评论
为什么被折叠?



