天天看点

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

———————————————————————————————————————————

作者:soupllen

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

一、任务创建与删除

任务的创建

任务的删除

二、系统内核控制函数

三、其他任务 API 函数

四、时间管理(延迟)

五、消息队列

创建队列

发送队列消息

接收队列消息

六、信号量

定义信号量句柄

创建信号量

释放和等待互斥量

七、定时器

创建定时器

启动定时器

获取定时器ID,根据ID进行任务回调使用

八、 调 度 锁、临界段、任务控制

(一)调度锁

(二)临界段

(三)任务控制,挂起恢复

九、 事件标志组

创建事件标志组

设置事件标志组

等待事件标志

十、互斥量

定义互斥量句柄

创建互斥量

使用互斥量

一、任务创建与删除

任务的创建

在main前面声明宏定义和声明
#define START_TASK_PRIO 1 //任务优先级 (1)
#define START_STK_SIZE 128 //任务堆栈大小 (2)xxx
TaskHandle_t Task_Handler; //任务句柄 (3)
void start_task(void *pvParameters); //任务函数 (4)
           

创建新的任务并添加到任务队列中,准备运行

#include "task. h "
※:宏 configSUPPORT_DYNAMIC_ALLOCATION 必须为 1
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: 任务名字,一般用于追踪和调试。
※usStackDepth: 任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。
※pvParameters: 传递给任务函数的参数。
※uxPriotiry: 任务优先级,范围 0~ configMAX_PRIORITIES-1。
※pxCreatedTask: 任务控制块。

返回值:
pdPASS: 任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
           

任务的删除

        从RTOS实时内核管理中移除任务。要删除的任务将从就绪,封锁,挂起,事件列表中移除。 

#include "task. h "
void vTaskDelete( xTaskHandle pxTask );

参数:
※xTaskToDelete:要删除的任务的任务句柄。如果是NULL即从调度器删除调用此api的任务

返回值:
无
           

二、系统内核控制函数

        FreeRTOS 中有一些函数只供系统内核使用,用户应用程序一般不允许使用,这些 API 函数就是系统内核控制函数。文章后续再做说明。

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

三、其他任务 API 函数

        FreeRTOS 有很多与任务相关的 API 函数,不过这些 API函数大多都是辅助函数。

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

四、时间管理(延迟)

        当执行延时函数的时候就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。延时函数属于 FreeRTOS 的时间管理。在 FreeRTOS 中延时函数有相对模式和绝对模式,在 FreeRTOS 中不同的模式用的函数不同,其中函数 vTaskDelay()是相对模式(相对延时函数),函数 vTaskDelayUntil()是绝对模式(绝对延时函数)

1、vTaskDelay()

void vTaskDelay( const TickType_t xTicksToDelay )
{
    BaseType_t xAlreadyYielded = pdFALSE;
    //延时时间要大于 0。
    if( xTicksToDelay > ( TickType_t ) 0U ) (1)
    {
        configASSERT( uxSchedulerSuspended == 0 );
        vTaskSuspendAll(); (2) {
        traceTASK_DELAY();
        prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); (3) }
        xAlreadyYielded = xTaskResumeAll(); (4) 
    }
    else
    {
    mtCOVERAGE_TEST_MARKER();
    }
    if( xAlreadyYielded == pdFALSE ) (5) {
    portYIELD_WITHIN_API(); (6) }
    else
    {
    mtCOVERAGE_TEST_MARKER();
    }
}    

(1)、延时时间由参数 xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大于 0。
否则的话相当于直接调用函数 portYIELD()进行任务切换。
(2)、调用函数 vTaskSuspendAll()挂起任务调度器。
(3) 、 调 用 函 数 prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时
列 表pxDelayedTaskList 或 者 pxOverflowDelayedTaskList() 中 。 
(4)、调用函数 xTaskResumeAll()恢复任务调度器。
(5)、如果函数 xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度。
(6)、调用函数 portYIELD_WITHIN_API()进行一次任务调度。
           

2、vTaskDelayUntil()

        vTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数 vTaskDelayUntil()。

void vTaskDelayUntil( 
TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement 
)

参数:
※pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用函数
vTaskDelayUntil 的话需要将 pxPreviousWakeTime 初始化进入任务的 while()循环
体的时间点值。
在以后的运行中函数 vTaskDelayUntil()会自动更新 pxPreviousWakeTime。
※xTimeIncrement:任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次
延时的节拍数)。(1)、挂起任务调度器。(2)、记录进入函数 vTaskDelayUntil()的时间
点值,并保存在 xConstTickCount 中。(3)、根据延时时间 xTimeIncrement 来计算任
务下一次要唤醒的时间点,并保存在xTimeToWake 中。可以看出这个延时时间是
相对于 pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。


※:用法:
void TestTask( void * pvParameters )
{
TickType_t PreviousWakeTime;
//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不
//能直接设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
PreviousWakeTime = xTaskGetTickCount();
//获取当前的系统节拍值
    for( ;; )
    {
    /******************************************************************/
    /*************************任务主体*********************************/
    /******************************************************************/
    
    //调用函数 vTaskDelayUntil 进行延时
    vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
    }
}
           

五、消息队列

        在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。

        队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。

工作如下:

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

        任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是3了。队列中发送消息是采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再次被使用,赋其他的值。

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

        任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长度为 2。

Freertos 实用API汇总一、任务创建与删除二、系统内核控制函数三、其他任务 API 函数四、时间管理(延迟)五、消息队列六、信号量七、定时器八、 调 度 锁、临界段、任务控制九、 事件标志组十、互斥量

        任务B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。

创建队列

        此函数本质上是一个宏,用来动态创建队列,此宏最终调用的是函xQueueGenericCreate()。

QueueHandle_t myxQueue1 = xQueueCreate(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize);

参数:
※uxQueueLength:第 1 个参数是消息队列支持的消息个数。
※uxItemSize:第 2 个参数是每个消息的大小,单位字节。 

返回值:
成功: 队列创捷成功以后返回的队列句柄!
失败:NULL
           

发送队列消息

普通发送xQueueSend()

        函数 xQueueSend 用于任务中消息发送。

aseType_t xQueueSend(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void * pvItemToQueue, /* 要传递数据地址 */
TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */
);

参数:
※uxQueueLength:第 1 个参数是消息队列句柄。
※uxItemSize:第 2 个参数要传递数据地址,每次发送都是将
消息队列创建函数 xQueueCreate 所指定的单个消息大小复制到消息队列空间中。 
※xTicksToWait :第 3 个参数是当消息队列已经满时,等待消息队列有空间时
的最大等待时间,单位系统时钟节拍。

返回值:
成功:pdTRUE
失败:NULL
           

中断发送xQueueSendFromISR()

        函数 xQueueSendFromISR 用于中断服务程序中消息发送。使用这个函数要注意以下问题:

        1. FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。

        2. 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xQueueSend。

        3. 消息队列还有两个函数 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR,函数xQueueSendToBackFromISR 实现的是 FIFO 方式的存取,函数 xQueueSendToFrontFromISR 实现的是 LIFO 方式的读写。函数 xQueueSendFromISR 等效于xQueueSendToBackFromISR,即实现的是 FIFO 方式的存取。

BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void *pvItemToQueue, /* 要传递数据地址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 *
);

参数:
※xQueue:第 1 个参数是消息队列句柄
※pvItemToQueue:第 2 个参数要传递数据地址,每次发送都是将消息队列创
建函数 xQueueCreate 所指定的单个消息大小复制到消息队列空间中
※pxHigherPriorityTaskWoken::第3个参数用于保存是否有高优先级任务准备就绪。
如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。

返回值:
成功:pdTRUE
失败:errQUEUE_FULL
           

接收队列消息

函数 xQueueReceive 用于接收消息队列中的数据。

使用这个函数要注意以下问题:

        1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xQueueReceiveFromISR。

        2. 如果消息队列为空且第三个参数为 0,那么此函数会立即返回。

        3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。

/* 设置最大等待时间为ms */
 const TickType_t xMaxBlockTime = pdMS_TO_TICKS(); 
 
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /* 消息队列句柄 */
void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */
);

参数:
※xQueue:第 1 个参数是消息队列句柄
※ 第 2 个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于
等于消息队列创建函数 xQueueCreate 所指定的单个消息大小,否则取出的数据无
法全部存储到缓冲区,从而造成内存溢出。
※xTicksToWait :: 3 个参数是消息队列为空时,等待消息队列有数据的最大等
待时间,单位系统时钟节拍。

返回值:
成功:pdTRUE
失败:pdFALSE
           

六、信号量

定义信号量句柄

#include "semphr.h"
static SemaphoreHandle_t MYxSemaphore = NULL;
           

创建信号量

SemaphoreHandle_t MYxSemaphore= xSemaphoreCreateCounting(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )

参数:
※uxMaxCount:第 1 个参数计数信号量最大计数值,当信号量值等于此
              值的时候释放信号量就会失败。
※xTicksToWait :第 2 个参数是计数信号量初始值。 

返回值:
NULL:计数型信号量创建失败。
其他值:计数型信号量创建成功,返回计数型信号量句柄。
           

释放和等待信号量

xSemaphoreGive()

函数 xSemaphoreGive 用于在任务代码中释放信号量。

        1. 此函数是基于消息队列函数 xQueueGenericSend 实现的:

#define xSemaphoreGive( xSemaphore )  xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

        2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR。

        3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。

        4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。

xSemaphoreGive(SemaphoreHandle_t MYxSemaphore);

参数:
※SemaphoreHandle_t :第 1 个参数是信号量句柄

返回值:
成功:pdTRUE
失败:pdFALSE
           

xSemaphoreTake()

        函数 xSemaphoreTake 用于在任务代码中获取信号量。

        此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成。

xSemaphoreTake(
 SemaphoreHandle_t xSemaphore,/* 信号量句柄 */
TickType_t xTicksToWait  /* 等待信号量可用的最大等待时间 */
); 

参数:
※xSemaphore:第 1 个参数是信号量句柄
※xTicksToWait :第 2 个参数是没有信号量可用时,等待信号量可
用的最大等待时间,单位系统时钟节拍。如果是0则立刻返回。

返回值:
成功:pdTRUE
失败:pdFALSE
           

七、定时器

 为了更好的管理 FreeRTOS 的定时器组件,专门创建了一个定时器任务,或者称之为 Daemon 任务。

定义定时器句柄和定义时间

static TimerHandle_t xTimers = NULL;//定时器句柄
const TickType_t xTimerPer = 100;//单位系统时钟节拍
           

创建定时器

xTimerCreate()

函数 xTimerCreate 用于创建软件定时器。

1. 在 FreeRTOSConfig.h 文件中使能宏定义:#define configUSE_TIMERS      1

TimerHandle_t xTimers = xTimerCreate(
const char * const pcTimerName,       /* 定时器名字 */
const TickType_t xTimerPeriod,         /* 定时器周期,单位系统时钟节拍 */
const UBaseType_t uxAutoReload,       /* 选择单次模式或者周期模式 */
void * const pvTimerID,           /* 定时器 ID */
TimerCallbackFunction_t pxCallbackFunction/* 定时器回调函数 */
); 

参数:
※pcTimerName:第 1 个参数是定时器名字,用于调试目的;方便识别不同的定时器。
※xTimerPeriod:第 2 个参数是定时器周期,单位系统时钟节拍。
※uxAutoReload:第 3 个参数是选择周期模式还是单次模式,
若参数为 pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式。
※pvTimerID:参数是定时器 ID,当创建不同的定时器,但使用相同的回调函数时,
在回调函数中通过不同的ID 号来区分不同的定时器。
※pxCallbackFunction :第 5 个参数是定时器回调函数

返回值:
成功:创建成功返回定时器的句柄
失败:由于 FreeRTOSCongfig.h 文件中 heap 空间不足,或者定时器周期设置为 0,
会返回 NULL。
           

启动定时器

xTimerStart 

函数 xTimerStart 用于启动软件定时器。

        1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。

        2. 在 FreeRTOSConfig.h 文件中使能宏定义:#define configUSE_TIMERS      1

        3. 对于已经被激活的定时器,即调用过函数 xTimerStart 进行启动,再次调用此函数相当于调用了函数xTimerReset 对定时器时间进行了复位。

        4. 如果在启动 FreeRTOS 调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了 FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate 中设置的单次或者周期性延迟时间才会执行相应的回调函数。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。

BaseType_t xTimerStart( 
TimerHandle_t xTimer,  /* 定时器句柄 */
TickType_t xBlockTime /* 成功启动定时器前的最大等待时间设置,单位系统时钟节拍 */
);   

参数:
※xTimer:第 1 个参数是定时器句柄。
※xBlockTime :第 2 个参数是成功启动定时器前的最大等待时间设置

返回值:
成功:pdPASS
失败:pdFAIL 
           

获取定时器ID,根据ID进行任务回调使用

pvTimerGetTimerID()

函数 pvTimerGetTimerID 用于返回使用函数 xTimerCreate 创建的软件定时器。

        1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。

        2. 在 FreeRTOSConfig.h 文件中使能宏定义:#define configUSE_TIMERS    1

        3. 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。

void *pvTimerGetTimerID( 
TimerHandle_t  xTimer  /* 定时器句柄 */
) ; 

参数:
※xTimer:第 1 个参数是定时器句柄。

返回值:
返回定时器 ID--ulTimerID 

例子:
********************************************
*  函 数 名: vTimerCallback
*  功能说明: 定时器回调函数
*********************************************
static void vTimerCallback(xTimerHandle pxTimer)
{
    uint32_t ulTimerID;
    ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
    /* 处理定时器 0 任务 */
  if(ulTimerID == 0)
  {
     xxx;
  }
  /* 处理定时器 1 任务 */
  if(ulTimerID == 1)
  {
   xxx;
  }
}
           

八、 调 度 锁、临界段、任务控制

(一)调度锁

vTaskSuspendAll()

函数 vTaskSuspendAll 用于实现 FreeRTOS 调度锁开启。

xTaskResumeAll()

xTaskResumeAll函数可以实现 FreeRTOS 的调度锁关闭。

        1. 调度锁函数只是禁止了任务调度,并没有关闭任何中断。

        2. 调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 一定要成对使用。

        3. 切不可在调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 之间调用任何会引起任务切换的 API,比如 vTaskDelayUntil、vTaskDelay、xQueueSend 等

vTaskSuspendAll();/* 挂起调度器 */      

xxxx;

xTaskResumeAll();/* 恢复调度器 */      
           

(二)临界段

        代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。

taskENTER_CRITICAL();   /* 进入临界区 */      
 
 xxxx
 
 taskEXIT_CRITICAL();      /* 退出临界区 */
           

(三)任务控制,挂起恢复

xTaskSuspend()

        函数xTaskSuspend可以实现 FreeRTOS 的任务挂起

void xTaskSuspend(
TaskHandle_t  xTaskToSuspend/* 任务句柄 */
);

参数:
※xTaskToSuspend:第 1 个参数是任务句柄,如果写NULL为本身
           

xTaskResume()

        函数可以实现 FreeRTOS 的任务恢复。

void xTaskResume(
TaskHandle_t  xTaskToSuspend/* 任务句柄 */
);

参数:
※xTaskToSuspend:第 1 个参数是任务句柄,如果写NULL为本身
           

九、 事件标志组

        事件标志组是实现多任务同步的有效机制之一。使用全局变量的确比较方便,但是在加上 RTOS 后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:  

  • 使用事件标志组可以让 RTOS 内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
  • 使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
  • 使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。

根据用户在 FreeRTOSConfig.h 文件中的配置:

  1. #define configUSE_16_BIT_TICKS 1:配置宏定义 configUSE_16_BIT_TICKS 为 1 时,每创建一个事件标志组,用户可以使用的事件标志是8 个。
  2. #define configUSE_16_BIT_TICKS 0:配置宏定义 configUSE_16_BIT_TICKS 为 0 时,每创建一个事件标志组,用户可以使用的事件标志是24个。

定义事件标志组句柄

static EventGroupHandle_t xEventGroup = NULL;//事件标志组句柄
           

创建事件标志组

xEventGroupCreate()

此函数用于创建一个事件标志组。

EventGroupHandle_t xEventGroup =xEventGroupCreate();

返回值:
NULL:事件标志组创建失败。
成功:创建成功的事件标志组句柄。
           

设置事件标志组

xEventGroupSetBits()

        函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。

        1. 使用前一定要保证事件标志组已经通过函数 xEventGroupCreate 创建了。

        2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xEventGroupSetBitsFromISR

        3. 用户通过参数 uxBitsToSet 设置的标志位并不一定会保留到此函数的返回值中,下面举两种情况:a. 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。b. 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数xEventGroupWaitBits 清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits 的返回值已经被修改。

EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToSet  /* 事件标志位设置 */
);

参数:
※xEventGroup:第 1 个参数是事件标志组句柄。
※uxBitsToSet:第 2 个参数表示 24 个可设置的事件标志位,=EventBits_t 
是定义的 32 位变量,低 24 位用于事件标志设置。变量 uxBitsToSet 的低 24 
位的某个位设置为 1,那么被设置的事件标志组的相应位就设置为 1。
变量 uxBitsToSet 设置为 0 的位对事件标志相应位没有影响。
比如设置变量 uxBitsToSet = 0x0003 就表示将事件标志的位 0 和
位 1 设置为 1,其余位没有变化。

返回值:当前的事件标志组数值.
           

例子:

#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
static EventGroupHandle_t xCreatedEventGroup = NULL;
/*
*********************************************************************************************************
*  函 数 名: vTaskTaskUserIF
*  功能说明: 接口消息处理。
*  形 参: pvParameters 是在创建该任务时传递的形参
*  返 回 值: 无
* 优 先 级: 1 (数值越小优先级越低,这个跟 uCOS 相反)
*********************************************************************************************************
*/
static void vTaskTaskUserIF(void *pvParameters)
{
  uint8_t ucKeyCode;
  EventBits_t uxBits;
  while(1)
  {
    ucKeyCode = bsp_GetKey();
    if (ucKeyCode != KEY_NONE)
    {
      switch (ucKeyCode)
      { 
        /* K2 键按下,直接发送事件标志给任务 vTaskMsgPro,设置 bit0 */
        case KEY_DOWN_K2:
        /* 设置事件标志组的 bit0 */
        uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0);
        if((uxBits & BIT_0) != 0)
        {
          printf("K2 键按下,事件标志的 bit0 被设置\r\n");
        }
        else
        {
          printf("K2 键按下,事件标志的 bit0 被清除,说明任务 vTaskMsgPro 已经接受到 bit0 和
          bit1 被设置的情况\r\n");
        }
        break;
        
        
        /* K3 键按下,直接发送事件标志给任务 vTaskMsgPro,设置 bit1 */
        case KEY_DOWN_K3:
        /* 设置事件标志组的 bit1 */
        uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_1);
        if((uxBits & BIT_1) != 0)
        {
          printf("K3 键按下,事件标志的 bit1 被设置\r\n");
        }
        else
        {
          printf("K3 键按下,事件标志的 bit1 被清除,说明任务 vTaskMsgPro 已经接受到 bit0 和
          bit1 被设置的情况\r\n");
        }
      break;
    /* 其他的键值不处理 */
      default:
      break;
      }
    }
   vTaskDelay(20);
  }}
           

等待事件标志

xEventGroupWaitBits()

函数 xEventGroupWaitBits 等待事件标志被设置。

        使用这个函数要注意以下问题:

        1. 此函数切不可在中断服务程序中调用。

        2. 这里再着重说明下这个函数的返回值,通过返回值用户可以检测是哪个事件标志位被置 1 了。

EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToWaitFor, /* 等待被设置的事件标志位 */
const BaseType_t xClearOnExit, /* 选择是否清零被置位的事件标志位 */
const BaseType_t xWaitForAllBits, /* 选择是否等待所有标志位都被设置 */
TickType_t xTicksToWait /* 设置等待时间 */
); 

参数:
※xEventGroup:第 1 个参数是事件标志组句柄。
※uxBitsToWaitFor:第 2 个参数表示等待 24 个事件标志位中的指定标志,
EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。比如设置
变量 uxBitsToWaitFor = 0x0003 就表示等待事件标志的位 0 和位 1 设
置为 1。此参数切不可设置为 0。
※xClearOnExit:第 3 个参数选择是否清除已经被置位的事件标志,如果这个
参数设置为 pdTRUE,且函数xEventGroupWaitBits 在参数 xTicksToWait 设置
的溢出时间内返回,那么相应被设置的事件标志位会被清零。如果这个参数设置
为 pdFALSE,对已经被设置的事件标志位没有影响。
※xWaitForAllBits:第 4 个参数选择是否等待所有的标志位都被设置,如果这个
参数设置为 pdTRUE,要等待第 2 个参数 uxBitsToWaitFor 所指定的标志位全部
被置 1,函数才可以返回。当然,超出了在参数xTicksToWait 设置的溢出时间也是
会返回的。如果这个参数设置为 pdFALSE,第 2 个参数uxBitsToWaitFor 所指定的
任何标志位被置 1,函数都会返回,超出溢出时间也会返回。
※第 5 个参数设置等待时间,单位时钟节拍周期。如果设
置为 portMAX_DELAY,表示永久等待。

返回值:由于设置的时间超时或者指定的事件标志位
被置 1,导致函数退出时返回的事件标志组数值。
           

例子:

#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
static EventGroupHandle_t xCreatedEventGroup = NULL;
/*
*********************************************************************************************************
*  函 数 名: vTaskMsgPro
*  功能说明: 消息处理,使用函数 xEventGroupWaitBits 接收任务 vTaskTaskUserIF 发送的事件标志
*  形 参: pvParameters 是在创建该任务时传递的形参
*  返 回 值: 无
* 优 先 级: 3
*********************************************************************************************************
*/
static void vTaskMsgPro(void *pvParameters)
{
  EventBits_t uxBits;
   /* 最大延迟 100ms */
  const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;
  while(1)
  {
      /* 等 K2 按键按下设置 bit0 和 K3 按键按下设置 bit1 */
      uxBits = xEventGroupWaitBits(
             xCreatedEventGroup, /* 事件标志组句柄 */
         BIT_ALL, /* 等待 bit0 和 bit1 被设置 */
         pdTRUE, /* 退出前 bit0 和 bit1 被清除,这里是 bit0 和 bit1
         都被设置才表示“退出”*/
         pdTRUE, /* 设置为 pdTRUE表示等待 bit1和 bit0 都被设置*/
         xTicksToWait); /* 等待延迟时间 */
      if((uxBits & BIT_ALL) == BIT_ALL)
      {
        /* 接收到 bit1 和 bit0 都被设置的消息 */
        printf("接收到 bit0 和 bit1 都被设置的消息\r\n");
      }
      else
      {
        /* 超时,另外注意仅接收到一个按键按下的消息时,变量 uxBits
             的相应 bit 也是被设置的 */
        bsp_LedToggle(3);
      }
  }
}
           

十、互斥量

        互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。互斥信号量可以防止优先级翻转,而二值信号量不支持

定义互斥量句柄

static SemaphoreHandle_t xSemaphore = NULL;
           

创建互斥量

xSemaphore = xSemaphoreCreateBinary();
if(xSemaphore == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
}
/* 先释放一次,将初始值改为 1,利用二值信号量实现互斥功能 */
xSemaphoreGive(xSemaphore);
           

使用互斥量

xSemaphoreTake(xSemaphore, portMAX_DELAY);//等待互斥量
/*******共享设备或资源********/
xSemaphoreGive(xSemaphore);//释放互斥量
           

———————————————————————————————————————————

继续阅读