1 说明

本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正。

1.1 简介

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

1.2 测试工程说明

测试环境为在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

2 FreeRTOS任务基础知识

主要介绍任务管理。从以下几个方面进行介绍:多任务系统、任务状态、任务优先级、任务实现、任务控制块、任务堆栈。

2.1 多任务系统

FreeRTOS是一个抢占式的实时多任务系统。高优先级的任务可以打断低优先级的任务而取得CPU的使用权。

2.2 任务的特性

任务是独立的,每个任务都有自己的运行环境,不依赖于系统中的其他任务或者RTOS调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务由调度器决定。RTOS调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器、堆栈内容等)和任务上一次退出的时候相同。所以每个任务都有堆栈,当任务切换的时候将上下文环境存放在堆栈中,这样当任务再次执行的时候可以从堆栈中取出上下文环境,任务恢复运行。

任务有以下特性:简单、没有使用限制、支持抢占、支持优先级、任务必须有堆栈、使用抢占必须考虑重入的问题。

2.3 任务状态

FreeRTOS中的任务永远处在下面几种状态中的某一种。

1)运行态:当一个任务运行时,则该任务处于运行态,处于运行态的任务就是当前使用处理器的任务。单核处理器在任何时刻只有一个任务处于运行态。

2)就绪态:任务已经准备就绪可以运行,但还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。

3)阻塞态:任务当前正在等待某个外部事件,则任务处于阻塞态,比如说某个任务使用了vTaskDelay()函数的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个时间任务会退出阻塞态,即使所等待的事件还没有来临。

4)挂起态:任务进入挂起态后不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。

任务状态之间的转换如下图所示:

2.4 任务优先级

每个任务都可以分配一个从0~configMAX_PRIORITIES-1(定义在FreeRTOSConfig.h)的优先级。优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES-1的优先级最高。空闲任务的优先级最低,为0。

FreeRTOS调度器确保处于就绪态或运行态最高优先级的任务获取处理器使用权。当宏configUSE_TIME_SLICING(默认在FreeRTOS.h中定义)定义为1的时候多个任务可以共用一个优先级,数量不限。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。

2.5 任务控制块

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;

2.6 任务堆栈

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位处理器) 。

3 任务相关API函数

任务相关函数如下:

任务创建和删除API函数

任务创建和删除实验(动态方法)

任务创建和删除实验(静态方法)

任务挂起和恢复API函数

任务挂起和恢复实验

3.1 任务创建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: 任务创建失败,因为堆内存不足!

3.2 任务创建函数(静态方法)

函数原型:

#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为空。

其他值: 任务创建成功,返回任务的任务句柄。

3.3 任务删除API函数

函数原型:

void vTaskDelete( TaskHandle_t xTaskToDelete ) PRIVILEGED_FUNCTION;

描述:删除任务,删除之后的任务不再存在,也不能再使用此函数的句柄。如果任务使用的是xTaskCreate()创建的,此任务被删除后此任务之前申请的堆栈和控制块内存会在任务中被释放掉。

函数参数:xTaskToDelete要删除的任务的任务句柄。

返回值:

3.4 任务创建和删除实验(动态方法)

创建两个任务,这两个任务的功能如下:

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:

3.5 任务创建和删除实验(静态方法)

使用静态方式创建一个任务,该任务每个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);

3.6 vTaskDelay()

函数原型:

#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毫秒。

FreeRTOS-00-基础知识+任务创建删除的更多相关文章

  1. MySQL基础知识:创建MySQL数据库和表

    虚构一个微型在线书店的数据库和数据,作为后续MySQL脚本的执行源,方便后续MySQL和SQL的练习. 在虚构这个库的过程中,主要涉及的是如何使用命令行管理 MySQL数据库对象:数据库.表.索引.外 ...

  2. FreeRTOS学习笔记——FreeRTOS 任务基础知识

    RTOS 系统的核心就是任务管理,FreeRTOS 也不例外,而且大多数学习RTOS 系统的工程师或者学生主要就是为了使用RTOS 的多任务处理功能,初步上手RTOS 系统首先必须掌握的也是任务的创建 ...

  3. Android学习之基础知识五—创建自定义控件

    下面是控件和布局的继承关系: 从上面我们看到: 1.所有控件都是直接或间接继承View,所有的布局都是直接或间接继承ViewGroup 2.View是Android中最基本的UI组件,各种组件其实就是 ...

  4. OpenCV 基础知识------图像创建、访问、转换

    cvCreateImage函数-- Cxcore数组操作 创建头并分配数据 IplImage* cvCreateImage( CvSize size, int depth, int channels ...

  5. Java并发(基础知识)—— 创建、运行以及停止一个线程

    在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行.如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的:如果计算机只有一个核心处理器那么就只是表面现象. 现代所有的 ...

  6. Oracle数据库基础知识

    oracle数据库plsql developer   目录(?)[-] 一     SQL基础知识 创建删除数据库 创建删除修改表 添加修改删除列 oracle cascade用法 添加删除约束主键外 ...

  7. ElasticSearch(四):关于es的一些基础知识讲解

    上一篇博客更新完之后,我发现一个问题:在我创建索引的时候依旧无法准确的理解每个字段的意义,所以就有了这个. 1. 关于索引 1.1 关于索引的一些基础知识 在创建标准化索引的时候,我们传入的请求体如下 ...

  8. Java并发(基础知识)—— Executor框架及线程池

    在Java并发(基础知识)—— 创建.运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有 ...

  9. FreeRTOS基础知识

    前面一篇文章介绍了一些命名规范之类的基础知识,但是我觉得还缺少一定前言知识,就是裸机和操作系统有什么区别,为什么我们需要学freertos,因为招聘要求?那么为什么招聘网又会有这个要求呢?所以我们为什 ...

随机推荐

  1. 90%的人都不知道的Node.js 依赖关系管理(下)

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/node-dependency-manage ...

  2. C# 强行锁定 第三方 外部 应用程序窗体窗口的分辨率尺寸大小 禁止鼠标拖拽改变窗口大小

    我们也许会有一些奇怪的需求,比如说禁止一个外部程序的窗口大小更改. 如果我们没法修改外部程序的代码,那要怎么做呢? 当然,我们可以通过DLL注入目标程序的方式去Hook或registry一个事件来检测 ...

  3. leetcode:在 D 天内送达包裹的能力

    链接:https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/ 我是按照这个思路来做的. 如果随便给一个船的运 ...

  4. fiddler概念及原理

    一.什么是fiddler? fiddler是位于客户端与服务器端的HTTP代理,它能够记录客户端与服务器之间所有的HTTP请求,可以针对特定的HTTP请求,分析请求数据,设置断点,调试WEB应用,修改 ...

  5. 性能工具之Jmeter小白入门系列之一

    一.简单了解 Apache JMeter The Apache JMeter application is open source software, a 100% pure Java applica ...

  6. 【NX二次开发】 获取体的面 UF_MODL_ask_body_faces

    获取体的面 1 extern DllExport void ufsta(char *param, int *returnCode, int rlen) 2 { 3 UF_initialize(); 4 ...

  7. 02:database 配置(可能出现严格模式配置问题)

    DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'day42', 'USER': 'root', 'P ...

  8. Java @FunctionalInterface注解-6

    在学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口.@FunctionalInterface 就是用来指定某 ...

  9. VsCode中添加tasks.json

    选中项目文件夹,按ctrl+shift+p,输入tasks 选择之后,继续选择 然后选择 选中就可以了

  10. .NET 云原生架构师训练营(设计原则与模式)--学习笔记

    在复杂系统的架构设计中引入设计原则与模式,能够极大降低复杂系统开发.和维护的成本 目录 几个问题 为什么要学习设计模式 优良架构设计的具体指标 理解复杂系统 面向对象思想(指导复杂系统的分析.设计.实 ...