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);
}
}
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 使用权。