本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正。
FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式、合作式和时间片调度。适用于微处理器或小型微处理器的实时应用。
本文档使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
参考文档:《FreeRTOS_Reference_Manual_V10.0.0.pdf》、《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》、《STM32F4 FreeRTOS开发手册_V1.1.pdf》
参考视频:正点原子FreeRTOS手把手教学-基于STM32_哔哩哔哩_bilibili
测试环境为在Linux进行仿真跑FreeRTOS,关于如何实现在Linux下的FreeRTOS仿真说明如下:
网上在linux下跑FreeRTOS的教程很多,这里我直接check了一份别人已经创建了的代码。
代码路径:freertos-simulator: FreeRTOS simulator on linux platform (gitee.com)
或者去官网,按照官网说明创建也可以。
官网:FreeRTOS simulator for Posix/Linux
编译之前需要先安装pcap开发包,不同的系统安装方式也不同,官网也给出了安装方式。
To install on ubuntu run
$ sudo apt-get install libpcap-dev
To install on rpm based system run
$ sudo yum install libpcap-devel
or
$ sudo dnf install libpcap-devel
To install on MacOS run
$ brew install libpcap
编译方式:
进入simulator目录,直接执行make即可。
运行方式:
$ ./build/freertos-simulator
主要介绍任务管理。从以下几个方面进行介绍:多任务系统、任务状态、任务优先级、任务实现、任务控制块、任务堆栈。
FreeRTOS是一个抢占式的实时多任务系统。高优先级的任务可以打断低优先级的任务而取得CPU的使用权。
任务是独立的,每个任务都有自己的运行环境,不依赖于系统中的其他任务或者RTOS调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务由调度器决定。RTOS调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器、堆栈内容等)和任务上一次退出的时候相同。所以每个任务都有堆栈,当任务切换的时候将上下文环境存放在堆栈中,这样当任务再次执行的时候可以从堆栈中取出上下文环境,任务恢复运行。
任务有以下特性:简单、没有使用限制、支持抢占、支持优先级、任务必须有堆栈、使用抢占必须考虑重入的问题。
FreeRTOS中的任务永远处在下面几种状态中的某一种。
1)运行态:当一个任务运行时,则该任务处于运行态,处于运行态的任务就是当前使用处理器的任务。单核处理器在任何时刻只有一个任务处于运行态。
2)就绪态:任务已经准备就绪可以运行,但还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。
3)阻塞态:任务当前正在等待某个外部事件,则任务处于阻塞态,比如说某个任务使用了vTaskDelay()函数的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个时间任务会退出阻塞态,即使所等待的事件还没有来临。
4)挂起态:任务进入挂起态后不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。
任务状态之间的转换如下图所示:
每个任务都可以分配一个从0~configMAX_PRIORITIES-1(定义在FreeRTOSConfig.h)的优先级。优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES-1的优先级最高。空闲任务的优先级最低,为0。
FreeRTOS调度器确保处于就绪态或运行态最高优先级的任务获取处理器使用权。当宏configUSE_TIME_SLICING(默认在FreeRTOS.h中定义)定义为1的时候多个任务可以共用一个优先级,数量不限。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。
FreeRTOS的每个任务都有一些属性需要存储,这些属性集合在一个结构体来表示,这个结构体就叫任务控制块:TCB_t,这个结构体定义在task.c,如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if ( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调
度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,
等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着
从上次中断的地方开始运行。
创建任务的时候需要给任务指定堆栈,如果使用的函数 xTaskCreate()创建任务(动态方法)
的话那么任务堆栈就会由函数 xTaskCreate()自动创建,后面分析 xTaskCreate()的时候会讲解 。
堆栈大小:任务堆栈的数据类型为 StackType_t, StackType_t 本质上是 unsigned long,在 portmacro.h 中有定义。
#define portSTACK_TYPE unsigned long
typedef portSTACK_TYPE StackType_t;
可以看出 StackType_t 类型的变量为 4 个字节,那么任务的实际堆栈大小就应该是我们所定义的 4 倍(32位处理器)或8倍(64位处理器) 。
任务相关函数如下:
任务创建和删除API函数
任务创建和删除实验(动态方法)
任务创建和删除实验(静态方法)
任务挂起和恢复API函数
任务挂起和恢复实验
函数原型:
#include "FreeRTOS.h"
#include "task.h"
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask );
描述:使用动态方法创建一个任务,任务控制块和任务堆栈在函数内创建。最新创建的任务初始化为就绪态,如果当前没有更高优先级的任务运行,则立刻变为运行态。
函数参数说明:
参数名
说明
pxTaskCode
任务函数,通常为一个无限循环。
pcName
任务名字,名称长度有限制,在FreeRTOSConfig.h中有定义configMAX_TASK_NAME_LEN。
usStackDepth
任务堆栈大小,实际申请到的堆栈是usStackDepth的4倍。configMINIMAL_STACK_SIZE定义的是空闲任务堆栈大小。
pvParameters
传递给任务函数的参数
uxPriority
任务优先级,范围0~configMAX_PRIORITIES-1。
pxCreatedTask
任务句柄,任务创建成功以后会返回此任务的任务句柄, 这个句柄其实就是 任务的任务堆栈。 此参数就用来保存这个任务句柄。其他 API 函数可能会使 用到这个句柄。如果任务句柄不需要使用,可以被设置为NULL。
返回值:
pdPASS:任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
函数原型:
#include "FreeRTOS.h"
#include "task.h"
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 );
描述:使用静态方法创建一个任务。任务所需要的RAM需要用户来提供。
函数参数说明:
参数名
说明
pxTaskCode
任务函数,通常为一个无限循环。
pcName
任务名字,名称长度有限制,在FreeRTOSConfig.h中有定义configMAX_TASK_NAME_LEN。
usStackDepth
任务堆栈大小,静态创建任务的堆栈由用户给出,通常为一个数组,这个参数就是数组的大小。
pvParameters
传递给任务函数的参数
uxPriority
任务优先级,范围0~configMAX_PRIORITIES-1。
puxStackBuffer
任务堆栈,一般为数组,数组类型为StackType_t类型。
pxTaskBuffer
任务控制块
返回值:
NULL:任务创建失败,puxStackBuffer或pxTaskBuffer为空。
其他值: 任务创建成功,返回任务的任务句柄。
函数原型:
void vTaskDelete( TaskHandle_t xTaskToDelete ) PRIVILEGED_FUNCTION;
描述:删除任务,删除之后的任务不再存在,也不能再使用此函数的句柄。如果任务使用的是xTaskCreate()创建的,此任务被删除后此任务之前申请的堆栈和控制块内存会在任务中被释放掉。
函数参数:xTaskToDelete要删除的任务的任务句柄。
返回值:无
创建两个任务,这两个任务的功能如下:
task00:此任务每个1000ms打印一次字符串,调用5次之后调用vTaskDelete()函数删除task01。
task01:此任务为普通任务,间隔500ms打印一次字符串,和task00同样的任务优先级。
task00任务创建代码:
configSTACK_DEPTH_TYPE Task00_STACK_SIZE = 5;
UBaseType_t Task00_Priority = 1;
TaskHandle_t Task00_xHandle;
void vTask00_Code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task00 cnt %u...", cnt);
if (cnt == 4)
vTaskDelete(Task01_xHandle);
cnt++;
vTaskDelay(1000);
}
}
xTaskCreate(vTask00_Code, "task00", Task00_STACK_SIZE, NULL, Task00_Priority, &Task00_xHandle);
task01任务创建代码:
configSTACK_DEPTH_TYPE Task01_STACK_SIZE = 5;
UBaseType_t Task01_Priority = 1;
TaskHandle_t Task01_xHandle;
void vTask01_Code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task01 cnt %u...", cnt);
cnt++;
vTaskDelay(500);
}
}
xTaskCreate(vTask01_Code, "task01", Task01_STACK_SIZE, NULL, Task01_Priority, &Task01_xHandle);
编译,运行,测试结果符合预期,task00运行5秒之后删除了task01:
使用静态方式创建一个任务,该任务每个1秒打印一个字符串:
#define STATIC_STACK_SIZE 5
UBaseType_t Static_Task_Priority = 1;
StaticTask_t Static_xTaskBuffer;
StackType_t Static_xStack[STATIC_STACK_SIZE];
TaskHandle_t Static_xhandle = NULL; //任务句柄
void static_task_code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" static task cnt %u...", cnt);
cnt++;
vTaskDelay(1000);
}
}
Static_xhandle = xTaskCreateStatic (static_task_code,
"static task",
STATIC_STACK_SIZE,
NULL,
Static_Task_Priority,
Static_xStack,
&Static_xTaskBuffer);
函数原型:
#include "FreeRTOS.h"
#include "task.h"
void vTaskDelay( const TickType_t xTicksToDelay );
描述:调用该函数的任务将进入阻塞态,中断一段固定的时钟周期。
函数参数:xTicksToDelay表示调用函数的任务的阻塞态保持时间。延时达到之后将进入就绪态。例如:当时钟计数到10000时,函数调用了vTaskDelay(100),然后任务进入阻塞态,并且保持阻塞态直到时钟计数到10100。
宏pdMS_TO_TICKS()可以被使用来延时毫秒。例如:调用vTaskDelay( pdMS_TO_TICKS(100) ),任务将进入阻塞态100毫秒。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章