FreeRTOS 事件标志组 ——提高篇

时间:2023-03-09 15:04:52
FreeRTOS 事件标志组  ——提高篇

假设你已经看过FreeRTOS 事件标志组这篇随笔了。

之前的基础篇,真的就只是简单了解一下,相当于大学实验室的实验,但是,我们实际公司项目中,需要更多地思考,就算我们之前只是学习了基础概念以及基础语法,只要我们勤加思考,就能灵活的运用基础知识了,基础是内功,基础打好了,功力自然上升。

事件标志组的概念就不再解释了,直接来正题。

你一定和我刚开始接触FreeRTOS一样,知道了事件标志组,也调用过API函数,并且也实现了开发板上历程的功能,不过开发板历程仅仅是介绍了某些API函数的用法,仅仅使用法而已。是否有和我当初一样的想法,到底事件标志组用来干什么,在我的程序设计中,我到底什么时候需要使用事件标志组?

在基础篇说到,事件标志组,相当于我们裸机开发中最常用的标志位flag。由于在os上跑,全局变量要谨慎使用。

事件标志组有自己的超时等待,也有同步线程的作用。

现在举例说明:

网络通信模块,SIM868,这个我已经使用裸机编写了第一代代码,切实体会到超时等待的优势,如果你没有足够的裸机编程经验,确实很难从教程中体会到超时等待,比如这个SIM868,我发送AT指令,有些指令1s就返回,有些指令几十秒甚至上分钟才返回,裸机开发中,要么一个阻塞延时等待(第一代代码采用的这种方式),要么定时器中断不断查询,这就出现一个问题,比如,我采取发送一个AT指令,延时3s之后,再去读模块返回的数据,在网络好的时候,可能只需要1s就可以读到模块返回的数据,这样我们功能正常,只是多花了2s,但是在网络不好的时候,可能需要5s模块才返回,这样的话,我们的裸机程序就会因为超时没有接收到数据需要重新发送或者其他什么处理,这就有很大弊端。所以,现在采取操作系统的方式。

其优势在于,创建两个任务(线程),一个发送,一个接收。发送任务只管发送,接收任务接收到模块返回之后立即和发送任务通信,实现消息同步。我们的网络通信模块SIM868,就是发送一条AT指令,收到返回数据之后,解析数据,再发送下一条。

好的,那么问题就来了,这,恰恰就是我们事件标志组的用途了啊。

首先说裸机,接收到SIM868返回数据的时候,我们可以立即解析,也可以设置标志,通过这个标志,在其他函数中做处理;

os,通过事件标志组,在接收任务中解析数据,解析完成之后,设置一个事件标志,发送任务等待这个事件的触发,然后周期性执行。

eg:

声明和定义:

static void AppTaskCreate (void);
static TaskHandle_t xHandleTaskSIM868send = NULL;
static TaskHandle_t xHandleTaskSIM868recive = NULL;
static EventGroupHandle_t xCreatedEventGroup = NULL; #define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)

main函数:

    AppTaskCreate();

        /* 创建任务通信机制 */
AppObjCreate(); /* 启动调度,开始执行任务 */
vTaskStartScheduler();

其他函数:

static void AppObjCreate (void)
{
/* 创建事件标志组 */
xCreatedEventGroup = xEventGroupCreate(); if(xCreatedEventGroup == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
printf("creak event failure!\r\n");
}
}
static void vTaskSIM868send(void *pvParameters)
{ EventBits_t uxBits;
const TickType_t xTicksToWait =; /* 最大延迟10s */
SIM868_PowerReset();
SIM868_instruction();
while()
{
comSendBuf(COM4,(uint8_t *)(SendCommand_Init.Echo_Off),strlen(SendCommand_Init.Echo_Off)); uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */
BIT_0, /* 等待bit0被设置 */
pdTRUE, /* 退出前bit0被清除,这里是bit0被设置才表示“退出”*/
pdTRUE, /* 设置为pdTRUE表示等待bit0被设置*/
xTicksToWait); /* 等待延迟时间 */ if((uxBits & BIT_ALL) == BIT_0)
{
/* 接收到bit0都被设置的消息 */
printf("接收到bit0被设置的消息\r\n");
}
else
{ printf("没接收到bit0被设置的消息\r\n");
}
} }
static void vTaskSIM868recive(void *pvParameters)
{ uint8_t read;
int count =;
char buf[]={};
char buf1[];
EventBits_t uxBits;
while()
{
for(int i=;i<;i++)
{
while(comGetChar(COM4, &read))
{
sprintf(buf1, "%c", read);
if((read!='\r')&&( read !='\n'))//不存放这两个特殊字符
{
buf[count++]=read;
} vTaskDelay();
}
count=;
switch(buf[])
{
case 'A':
if(!strncmp(buf,"ATE0OK",))
{
comClearRxFifo(COM4);//清除缓
printf("去除回显\r\n");
memset(buf,,);
uxBits = xEventGroupSetBits(xCreatedEventGroup, BIT_0);
if((uxBits & BIT_0) != )
{
printf("收到ok返回并且事件标志置位");
}
else
{
printf("noready\r\n");
}
break;
}
break;
}
} }
}
static void AppTaskCreate (void)
{
xTaskCreate( vTaskSIM868send, /* 任务函数 */
"vTaskSIM868send", /* 任务名 */
, /* 任务栈大小,单位word,也就是4字节 */
NULL, /* 任务参数 */
, /* 任务优先级*/
&xHandleTaskSIM868send ); /* 任务句柄 */ xTaskCreate( vTaskSIM868recive, /* 任务函数 */
"vTaskSIM868recive", /* 任务名 */
, /* 任务栈大小,单位word,也就是4字节 */
NULL, /* 任务参数 */
, /* 任务优先级*/
&xHandleTaskSIM868recive ); /* 任务句柄 */ }

在启动调度之前,我们先创建了事件标志组,采用24bit的方式,这个在基础篇已有说明。

现在,创建两个任务,一个发送,一个接收。

发送函数,首先,通过串口给SIM868发送去回显指令,然后就进入阻塞态,因为wait函数会让其阻塞,这里设置的最大等待时间是10s,一般的指令,10s内都返回了,特殊的关系到网络问题的指令,时间再根据需要更改。

然后,接收任务中,就在串口里面读取SIM868返回的数据,解析数据之后,调用set函数,此时高优先级的发送任务立即退出阻塞态,打断低优先级的接收任务,执行后面的指令,当然这里仅仅举例,因为我这里就一条指令,以后有机会分享不涉及公司的demo。

这样的好处在于,第一,通过os的方式,独立发送和接收,发送只管发,接收到了就把相应的事件标志位置位,通知wait的任务,为了立即响应,wait任务优先级设置比set任务的高,这样在10s超时等待时间内,如果网络好,我1s就可返回,网络不好,等待5s也无所谓,特殊需要长时间等待的指令再特殊处理。这样,就会让系统运行更加高效,尤其是低功耗类产品。其次,在裸机开发中,我之前使用阻塞延时方式,当多个指令都返回的是ok的时候,我不能很方便的打印出到底是哪个指令返回的ok,而os使用事件标志组之后,我一定能知道是哪个触发的。最后,一个任务通知另外一个,标志着某个动作完成或者异常时,就是我们使用事件标志组的时候。

在这里,因为当初很是困惑,开发板厂商的代码大多换汤不换药,一个调调:

FreeRTOS 事件标志组  ——提高篇

这里,由于接收任务优先级低于发送,所以if条件不会满足,为什么呢?因为事件标志置位,让wait的高优先级任务返回了,会清除这个标志,所以,打印else的内容。在最初开开发板教程的时候,我只觉得if满足的条件才证明是事件被置位了,可是它依托于wait函数,这个在基础教程中也说到了。

那么,我们实际项目中,其实就应该只判断else的内容,满足else分支,证明执行wait函数的任务已经返回(当然这是建立在执行wait函数的任务优先级高于执行set函数的任务的前提下,这个也是比较推荐的方式),这说明了什么?说明我们不能死板地学习教程,开发板教程仅仅是熟悉,需要自己思考,自己去官网,论坛,Google查询资料,并且一定要有自己独立思考地过程,这样,基础有了,进阶就会容易得多。