文章目录
FreeRTOS简介及API函数使用方法
一 FreeRTOS简介
1.1 FreeRTOS是什么
- Free Real Time Operating System :实时操作系统
- 对于常规单片机而言(一般都是单核),任一时刻只有一个任务或中断函数在独占CPU核,如果想要多个任务运行,就需要一个操作系统来指挥安排。操作系统中任务调度器的责任就是决定在某一时刻究竟运行哪个任务。任务调度在各个任务之间的切换非常快,就给人们造成了同一时刻有多个任务同时运行的错觉。
- FreeRTOS 就是一个十分小巧的操作系统,常用在资源有限的微控制器中运行,许多半导体厂商产品的 SDK(Software Development Kit—软件开发工具包) 包中就提供 FreeRTOS 作为其操作系统。
1.2 FreeRTOS 特点
- FreeRTOS 的内核支持抢占式,合作式和时间片调度。
- 系统的组件在创建时可以选择动态或者静态的 RAM,比如任务、消息队列、信号量、软件定时器等等。
- FreeRTOS-MPU 支持 Corex-M 系列中的 MPU 单元,如 STM32,NXP。
- FreeRTOS 系统简单、小巧、易用,通常情况下内核占用 4k-9k 字节的空间。
- 提供了一个用于低功耗的 Tickless 模式。
- 高可移植性,代码主要 C 语言编写。
- 高效的软件定时器。
- 强大的跟踪执行功能。
- 堆栈溢出检测功能。
- 任务数量不限。
- 任务优先级不限。
二 FreeRTOS任务基础
2.1 抢占式和非抢占式含义
操作系统调度任务时,可以分为:抢占式和非抢占式
- 非抢占式:每个任务都是轮流执行,没有轮到该任务运行的时候,不管该任务有多么的紧急,就只能等着,大家拥有一样的优先级
- 抢占式:每个任务都有自己的运行环境,CPU在任一时间点只能运行一个任务,但是任务调度器可以决定运行哪一个任务,如果任务紧急,即优先级高,就会先运行优先级高的任务。FreeRTOS就是一种抢占式的任务调度器。
2.2 FreeRTOS任务状态
FreeRTOS中的任务状态有运行态、就绪态、阻塞态、挂起态,每个任务只能处于这几种状态中的一个状态。
- 运行态
当一个任务正在CPU中执行时,该任务就处于运行态。对于单核CPU,只能有一个任务处于运行态。 - 就绪态
就绪态是指那些已经准备好了的任务,可以随时拿到CPU的使用权,进而进入运行态,但是此时此刻该状态的任务还没有执行,主要是因为当前有一个同优先级或者更高优先级的任务正在运行。 - 阻塞态
如果一个任务当前正在等待某一外部事件的发生所处于的状态,如:任务正在等待队列、信号量、事件组等都会进入阻塞态。任务进入阻塞态是有一定的时间限制,当超时等待时,该任务将退出阻塞态,进入就绪态,等待拿到CPU的使用权,进入运行态。 - 挂起态
任务进入挂起的状态和阻塞态一样,将不会被任务调度器所调用,但是出于挂起态的任务是没有超时的问题。
任务状态切换示意图如下图:
三 FreeRTOS API函数使用方法
许多半导体厂商产品的 SDK包中使用了 FreeRTOS 操作系统,并提供了API接口函数,只需要了解API函数的使用方法,就可以使用FreeRTOS 实现任务创建、删除,启动调度器、停止调度器、挂起所有任务、恢复运行等操作。同时还提供了任务杂项信息接口,比如获取任务状态、tick信息、调试、获取任务名等API接口。下面将介绍常用API函数的使用方法:
3.1 任务创建删除
任务创建和删除的主要函数如下:
函数 | 描述 |
---|---|
xTaskCreate( ) | 使用动态方法创建一个任务 |
xTaskDelete( ) | 删除一个任务 |
xEventGroupCreate() | 创建一个事件组,即创建事件控制块的内存空间 |
xQueueCreate() | 队列创建函数 |
xQueueGenericCreate() | 通用队列创建函数 |
- xTaskCreate( )函数
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务函数
const char * const pcName, //任务名字
const uint16_t usStackDepth, //任务堆栈大小
void * const pvParameters, //传递给任务函数的参数
UBaseType_t uxPriority, //任务优先级
TaskHandle_t * const pxCreatedTask ) //任务句柄
//pxTaskCode : 任务函数
//PCName : 任务名字,一般用于追踪和调试,名字长度不超过configMAX_TASK_LEN
//usStackDepth : 任务堆栈的大小,实际申请的大小是usStackDepth的4倍,空闲任务的任务堆栈大小是configMINIMAL_STACK_SIZE
//pvParameters : 传递给任务函数的参数
//uxPriority : 任务优先级,0~configMAX_PRIORITIES-1 ,数字越小,优先级越低
//pxCreatedTask : 任务句柄,任务创建成功后会返回此任务的任务句柄,
// 该句柄其实就是任务的堆栈,此参数用于保存这个任务句柄,其他的API函数会用到该句柄
//return : pdPASS:任务创建成功;errCOULD_NOT_ALLOCTE_REQUIRED_MEMORY:创建失败
- xTaskDelete( )函数
删除一个用动态或者静态创建的的任务,被删除的任务不再存在,任务调度器无法再调度该任务。当一个任务被删除后,其所对应的句柄就不能再使用了。删除一个使用动态创建的(XTaskCreate( ))任务,其所申请的堆栈和控制块内存将会被释放掉。有静态方法创建的任务,所使用的堆栈就需要用户自己去释放掉所对应的内存,否则会导致内存泄漏。此函数的原型如下:
void vTaskDelete( TaskHandle_t xTaskToDelete)
//参数:xTaskToDelete,需要删除的任务的任务句柄
- xEventGroupCreate( )函数
当创建一个事件时, 系统需要首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,xTaskCreate( )函数就用来创建一个事件组
EventGroupHandle_t xEventGroupCreate( void )
//返回值:成功建立事件组,则会返回事件组的句柄(指针)
// 如果内存堆没有足够的内存则会返回NULL(创建失败)
- xQueueCreate()
创建一个新的队列,为新的队列分配所需的存储内存,并返回一个队列处理。队列可以保存有限个具有确定长度的数据单元,队列可以保存的最大单元数目被称为队列的 “ 深度 ” ;在队列创建时需要设定其深度和每个单元的大小。
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
//uxQueueLength:队列项长度;即队列能够存储的最大消息数目,也称为队列深度
//uxItemSize:信息大小;即队列中每个消息数目的数据大小,以字节为单位
//返回参数: NULL:表示没有足够的堆空间分配给队列而导致创建失败 非NULL:表示队列创建成功
- xQueueGenericCreate()函数
通用队列创建函数,是队列创建函数xQueueCreate()的实际执行函数
QueueHandle_t xQueueGenericCreate(
const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
//uxQueueLength:队列的深度,也就是最多队列项的数目
//uxItemSize:每个队列项最多多大
//ucQueueType :队列的类型
3.2 任务控制
函数 | 描述 |
---|---|
vTaskSuspend( ) | 挂起一个任务,使其不再执行 |
vTaskResume( ) | 回复一个任务,使其进入就绪态 |
xTaskResumeFromISR( ) | 中断服务函数中恢复一个任务的运行 |
vTaskDelay( ) | 任务延迟 |
- vTaskSuspend( )函数
此函数用于将某一任务设置为挂起态,进入挂起态的任务将永远都不会进入运行态。退出挂起态的唯一方法是调用任务恢复函数vTaskResume( )或xTaskResumeFromISR( )。函数原型如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend);
//参数:xTaskToSuspend,需要挂起任务所对应的任务句柄。
- vTaskResume( )函数
将一个任务从挂起态恢复为就绪态,函数原型如下:
void vTaskResume(TaskHandle_t xTaskToResume);
//参数:xTaskToSuspend,需要挂起任务所对应的任务句柄。
- xTaskResumeFromISR( )函数
在中断服务函数中只能调用该函数将一个任务从挂起态恢复到就绪态。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume);
//返回值:
//pdTRUE: 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务)
// 这意味着在退出中断服务函数以后必须进行一次上下文切换。
//pdFALSE: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函
- vTaskDelay( )函数
延时时间函数,相当于告诉操作系统,该任务延时一段时间后再执行,这段时间内去执行其他任务,并在某个时间点将该任务重新唤醒。这个时间点就是函数的入参,一般都是以tick为单位。可以用于高优先级任务死循环执行时,设置任务睡眠,以执行低优先级的任务。
void vTaskDelay( const TickType_t xTicksToDelay )
//参数:延时时间,单位tick,时间片
3.3 调度器控制
函数 | 描述 |
---|---|
vTaskStartScheduler | 主要用于待用户任务创建好后,硬件初始化后,启动内核调度器 |
vTaskEndScheduler | 可用于停止内核调度器,一般很少用到,在一些安全相关的应用可能会在出故障时主动停止调度器 |
vTaskSuspendAll | 挂起所有任务,可以用用户逻辑主动挂起所有的任务 |
xTaskResumeAll | 恢复所有任务为就绪态 |
void vTaskStartScheduler( void )
void vTaskEndScheduler( void )
void vTaskSuspendAll( void )
BaseType_t xTaskResumeAll( void )
//返回:pdTRUE表示任务已经切换 pdFALSE表示任务未发生切换
3.4 软件定时器
FreeRTOS支持用户自定义创建定时器组,该定时器组的时基是基于系统时钟节拍实现的,但不需要使用任何硬件定时器,而且可以创建多个定时器,综合这些因素,这个功能就被称之为软件定时器组。在硬件定时器中,通过定时器中断来实现定时时间达到后需要执行的功能,而使用软件定时器时,需要通过创建指定软件定时器的回调函数,在回调函数中实现需要执行的功能。对应常用的API函数如下:
函数 | 描述 |
---|---|
xTimerCreate() | 创建软件定时器 |
xTimerStart () | 软件定时器启动 |
xTimerStop() | 软件定时器关闭 |
pvTimerGetTimerID () | 获取定时器ID |
- xTimerCreate( )函数
xTimerCreate函数通过计时、计时清零、函数调用的方式,建立一个需要不断运行的任务,该任务不被调度器打断,保证运行稳定。软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。
TimerHandle_t xTimerCreate
( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
//函数参数描述:
//pcTimerName:定时器名字,用于调试目的,方便识别不同的定时器。
//xTimerPeriod:单位系统时钟节拍。
//uxAutoReload:选择周期模式还是单次模式,若参数为pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式。
//pvTimerID:定时器ID,当创建不同的定时器,但使用相同的回调函数时,在回调函数中通过不同的ID号来区分不同的定时器。
//pxCallbackFunction:定时器回调函数。
//返回值:创建成功返回定时器的句柄,创建失败会返回NULL。
- xTimerStart()函数
软件定时器启动函数, 使用前一定要保证定时器组已经通过函数xTimerCreate创建了。 对于已经被激活的定时器,即调用过函数xTimerStart进行启动,再次调用此函数相当于调用了函数xTimerReset对定时器时间进行了复位。 如果在启动FreeRTOS调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate中设置的单次或者周期性延迟时间才会执行相应的回调函数。
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xBlockTime )
/*
函数参数描述:
xTimer:定时器句柄。
xBlockTime:成功启动定时器前的最大等待时间设置,单位系统时钟节拍;
定时器组的大部分API函数不是直接运行的,而是通过消息队列给定时器任务发消息来实现的
此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。
返回值:返回pdFAIL表示此函数向消息队列发送消息失败,返回pdPASS表示此函数向消息队列发送消息成功。
定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行;
如果是低优先级,就要等待其余高优先级任务释放CPU权才可以得到执行。
*/
- xTimerStop()函数
和软件定时器启动函数用法相同,该函数用于关闭定时器
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime )
/*
函数参数描述:
xTimer:定时器句柄。
xBlockTime:成功关闭定时器前的最大等待时间设置,单位系统时钟节拍;
返回值:返回pdFAIL表示此函数向消息队列发送消息失败,返回pdPASS表示此函数向消息队列发送消息成功。
*/
- pvTimerGetTimerID()函数
- 用于返回使用函数xTimerCreate创建的软件定时器ID,使用前一定要保证定时器组已经通过函数xTimerCreate创建了。 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。
void *pvTimerGetTimerID( TimerHandle_t xTimer );
//函数参数描述:
//xTimer:定时器句柄。
//返回值:返回定时器ID。
感谢阅读 若有错误 敬请见谅 !!!