FreeRTOS 创建单任务与多任务(SRAM 动态内存)

一、前言

在 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 中做中断映射:

#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_HandlerPendSV_HandlerSysTick_Handler

3. 进入 HardFault

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

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

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


总结

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

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

内容概要:本文系统研究了在有限控制集约束下,三相并网逆变器中电流功率双模态模型预测控制(MPC)的等效机理及其性能边界。通过构建精确的预测模型,设计合理的代价函数,并结合Simulink仿真Matlab代码实现,深入分析了电流预测控制功率预测控制两种策略在动态响应速度、稳态精度、谐波抑制能力和抗扰性等方面的差异内在联系。研究揭示了在特定系统参数和运行条件下,两种控制模式之间的等效转化机制,并界定了各自的适用范围性能极限。同时,探讨了多模态控制的切换逻辑、实时性优化及预测模型不确定性对控制性能的影响,旨在提升逆变器在复杂电网环境下的综合控制品质鲁棒性。; 适合人群:具备电力电子、自动控制或新能源并网等相关专业背景,熟悉Matlab/Simulink仿真环境,从事研究生及以上层次科研或从事高端电力电子装备研发的工程技术人员。; 使用场景及目标:①深入理解模型预测控制在并网逆变器中的具体实现方法理论基础;②掌握电流功率双模态MPC控制器的设计、仿真建模性能对比评估流程;③为高动态、高精度并网控制系统的方案选型、参数优化工程化应用提供坚实的理论依据和技术参考。; 阅读建议:建议结合所提供的Simulink仿真模型Matlab源代码进行同步实验验证,重点关注预测模型的建立过程、控制律的数学推导以及不同工况下的仿真结果对比分析,宜配合现代控制理论、电力电子变换技术及并网标准等相关资料进行系统性学习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

捷瑞电子工坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值