FreeRTOS【3】任务线程创建

1.开发背景

       基于上一篇指引,配置了常用配置后,开始操作系统的使用,其中最基础的就是任务线程的创建方法和系统启动。

2.开发需求

        创建任务线程并启动操作系统

3.开发环境

        window10 + MDK + STM32F429 + FreeRTOS10.3.1

4.实现步骤

        FreeRTOS 创建任务现成的方法有 2 种,动态创建和静态创建,动态创建交由系统统一管理内存,而静态创建则需要使用者指定线程使用的内存区域,创建方法更加原始和繁琐,不推荐使用

        下面是实现源码

#include "appTest.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "FreeRTOS.h"
#include "task.h"

#include "appLog.h"

typedef struct
{
    /* 动态创建任务 */
    TaskHandle_t taskDynamic;       // 句柄
    
    /* 静态创建任务 */
    StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];        // 空闲任务任务堆栈
    StaticTask_t IdleTaskTCB;                                   // 空闲任务控制块
    StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];   // 定时器服务任务堆栈
    StaticTask_t TimerTaskTCB;                                  // 定时器服务任务控制块
    StaticTask_t xTaskBuffer;       // TCP 控制块
    #define STACK_SIZE (256)
    StackType_t xStack[STACK_SIZE]; // 32bit 数组

    /* 动态创建多个任务 */
    #define TASK_LIST_SIZE  (5)
    TaskHandle_t taskList[TASK_LIST_SIZE];
    
}Ctrl_t;

static Ctrl_t s_ctrl = {0};
static Ctrl_t *p = &s_ctrl;

/* 指向空闲任务内存 */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, 
                                   StackType_t **ppxIdleTaskStackBuffer, 
                                   uint32_t *pulIdleTaskStackSize)
{
    *ppxIdleTaskTCBBuffer = &p->IdleTaskTCB;
    *ppxIdleTaskStackBuffer = p->IdleTaskStack;
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

/* 指向定时器任务内存 */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, 
                                    StackType_t **ppxTimerTaskStackBuffer, 
                                    uint32_t *pulTimerTaskStackSize)
{
    *ppxTimerTaskTCBBuffer = &p->TimerTaskTCB;
    *ppxTimerTaskStackBuffer = p->TimerTaskStack;
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}

/* 动态任务 */
static void TaskDynamic(void *pvParameters)
{
    for ( ; ; )
    {
        vTaskDelay(10000);
        
        static int count = 0;
        Log_Debug("%s count = %d\r\n", __func__, count++);
    }
}

/* 静态任务 */
static void TaskStatic(void *pvParameters)
{
    for ( ; ; )
    {
        vTaskDelay(10000);
        
        static int count = 0;
        Log_Debug("%s count = %d\r\n", __func__, count++);
    }
}

/* 动态任务组 */
static void TaskList(void *pvParameters)
{
    int whichTask = atoi(pvParameters);
    for ( ; ; )
    {
        vTaskDelay(10000);
        
        static int count = 0;
        Log_Debug("%s [%d] count = %d, 0x%.8X\r\n", __func__, whichTask, count++, &whichTask);
    }
}

/* 测试初始化 */
void aTest_Init(void)
{
    /* 创建动态任务 */
    xTaskCreate(TaskDynamic, "TaskDynamic", 500, NULL, 5, &p->taskDynamic);
    
    /* 创建静态任务 */
    xTaskCreateStatic(TaskStatic, "TaskStatic", 256, NULL, 5, p->xStack, &p->xTaskBuffer);
    
    /* 共用一个任务函数 创建多个任务 */
    static char whichTask[TASK_LIST_SIZE][3] = {0};
    for (int i = 0; i < TASK_LIST_SIZE; i++)
    {
        snprintf(whichTask[i], 2, "%d", i);
        xTaskCreate(TaskList, "TaskList", 500, (void*)whichTask[i], 5, &p->taskList[i]);
    }
}

main.c 中在创建线程的最后启动任务调度 vTaskStartScheduler();

/**
  ******************************************************************************
  * @file    main.c
  * @author  Yangjinghui
  * @version V1.0.0
  * @date    2021-10-01
  * @brief   程序入口文件 包括各个模块和应用程序初始化
  */
  
#include <stdio.h>

#include "stm32f4xx_hal.h"

/* MCU 驱动支持包 */
#include "mspAdc.h"
#include "mspCan.h"
#include "mspIic.h"
#include "mspSpi.h"
#include "mspDwt.h"
#include "mspDma.h"
#include "mspExti.h"
#include "mspUart.h"
#include "mspGpio.h"        // GPIO 相关的支持
#include "mspCore.h"
#include "mspTimer.h"
#include "mspFlash.h"

/* 板级支持包 */
#include "bspLed.h"

/* 模块加载 */
#include "modInfo.h"

/* 交互应用 */
#include "appLog.h"
#include "appTest.h"
#include "appRunLed.h"
#include "appMonitor.h"
#include "appFreeRtos.h"


/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    mspCore_Init();
    mspDwt_Init();              // DWT 初始化会触发一次软件复位
    mspGpio_Init();
    mspUart_Init();
    
    /* 板级支持文件 */
    bLed_Init();
    
    /* 加载应用程序初始化 */
    aLog_Init();
    aRunLed_Init();
    aMonitor_Init();
    
    /* 初始化测试 */
    aTest_Init();
    
    /* 启动操作系统 */
    vTaskStartScheduler();
    
    while (1)
    {
        mspDwt_DelayMs(100);
        bLed_OverTurn(eLed1);
    }
}

参考官方链接:This page describes the RTOS xTaskCreate() FreeRTOS API function which is part of the RTOS task control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.

4.1 静态创建任务线程

        1)配置相关宏,在 FreeRTOSConfig.h 配置 configSUPPORT_STATIC_ALLOCATION

#define configSUPPORT_STATIC_ALLOCATION          1

       2) 需要重新定义 vApplicationGetIdleTaskMemory 和 vApplicationGetTimerTaskMemory,如果没有使能定时器,可以不用理会 vApplicationGetTimerTaskMemory 接口,否则会报错显示这两个接口未定义。

       3) 调用接口 xTaskCreateStatic,接口使用和参数配置参考官方文档即可(上面的链接),需要注意的是最后 2 个参数,puxStackBuffer 和 pxTaskBuffer 需要常驻内存。

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                 const char * const pcName,
                                 const uint32_t ulStackDepth,
                                 void * const pvParameters,
                                 UBaseType_t uxPriority,
                                 StackType_t * const puxStackBuffer,
                                 StaticTask_t * const pxTaskBuffer );

4.2 动态创建任务线程

        相对于静态创建任务线程,动态创建的方式比较简单。

        1)配置相关宏,在 FreeRTOSConfig.h 配置 configSUPPORT_DYNAMIC_ALLOCATION

#define configSUPPORT_DYNAMIC_ALLOCATION         1

        2)调用接口 xTaskCreate,接口使用和参数配置参考官方文档即可(上面的链接),需要注意的是最后 1 个参数,pxCreatedTask这个是线程句柄,后续可以通过句柄实现线程的挂起和删除。

BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            const configSTACK_DEPTH_TYPE uxStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );

4.3 创建多个任务线程

        一般情况下掌握动态创建线程即可,但是有时候我们需要创建多个线程,而且线程的工作内容都基本相同的情况下,为了充分提炼相同代码,多个线程共用一个函数,可以通过传参来区分不同的线程,需要注意的是传参参数(pvParameters)需要常驻内存,否则后续线程访问指针会获取到错误信息。

xTaskCreate

        -> prvInitialiseNewTask

                -> pxPortInitialiseStack 

在 pxPortInitialiseStack 中只是设置了 *pxTopOfStack 指向 pvParameters,所以 pvParameters 不能是空指针。

/*
 * See header file for description.
 */
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */

	/* Offset added to account for the way the MCU uses the stack on entry/exit
	of interrupts, and to ensure alignment. */
	pxTopOfStack--;

	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	/* Save code space by skipping register initialisation. */
	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

	/* A save method is being used that requires each task to maintain its
	own exec return value. */
	pxTopOfStack--;
	*pxTopOfStack = portINITIAL_EXC_RETURN;

	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

4.4 启动系统调度

        当初始化准备好之后就可以开启任务调度了,接口 vTaskStartScheduler(); 在任务调度启用前都是裸机编程,如果系统运行成功,在 vTaskStartScheduler 后的所有语句代码都不会被执行,系统进入事件循环,绝大部分时间 PC 指针都在空闲线程中。

4.5 结果显示

系统剩余内存 45104 Bytes

5 注意事项

        创建线程需要注意 uxStackDepth 堆栈深度和 uxPriority 优先级,uxStackDepth 如果不够可能会导致线程跑飞甚至是系统崩溃。uxPriority 越高线程的优先级越高,在使能了抢占式内核条件下,高优先级线程会抢占低优先级线程的 CPU 使用权。

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在FreeRTOS创建线程,可以按照以下步骤进行操作: 1. 首先,确保已经在您的项目中包含了FreeRTOS库文件。 2. 在主函数或初始化函数中,调用 `xTaskCreate()` 函数来创建线程。该函数的原型如下: ```c BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, configSTACK_DEPTH_TYPE usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask); ``` 参数说明: - `pvTaskCode`:线程函数指针,指向您自定义的线程函数。 - `pcName`:线程名称,用于调试目的。 - `usStackDepth`:线程堆栈大小,以字节为单位。 - `pvParameters`:线程参数,可以传递给线程函数的任意类型值。 - `uxPriority`:线程优先级,数字越大优先级越高。 - `pxCreatedTask`:指向创建线程句柄的指针。 示例代码如下: ```c #include "FreeRTOS.h" #include "task.h" void vTaskFunction(void *pvParameters) { // 线程功能代码 } int main(void) { // 初始化系统 // 创建线程 xTaskCreate(vTaskFunction, "Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 启动FreeRTOS调度器 vTaskStartScheduler(); // 不会执行到这里 return 0; } ``` 在上面的示例中,`vTaskFunction` 是您自定义的线程函数,您可以在其中编写线程的功能代码。然后,通过调用 `xTaskCreate()` 函数来创建一个名为 "Task" 的线程,并指定线程的堆栈大小、优先级等参数。 最后,调用 `vTaskStartScheduler()` 函数来启动FreeRTOS调度器,开始执行线程任务。 请确保您已经正确配置了FreeRTOS的内存管理和调度器设置,以便使线程能够正常运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值