电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
实战篇 | 基于freeRTOS的多任务事件传输demo(附代码)
分 享
扫描二维码分享
实战篇 | 基于freeRTOS的多任务事件传输demo(附代码)
FreeRTOS
嵌入式
C语言
李肖遥
关注
发布时间: 2020-08-14
丨
阅读: 1418
## 为什么要用freeRTOS 在实际项目中,如果程序等待一个超时事件,传统的无RTOS情况下,要么在原地一直等待而不能执行其它任务,如果使用RTOS,则可以很方便的将当前任务阻塞在该事件下,然后自动去执行别的任务,这显然更方便,并且可以高效的利用CPU。 ## 一般使用情况 我们在开发的时候,我总是在main函数看到以下的代码,这让我感觉不是很爽 ``` xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL ); xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL ); xTaskCreate( vTask3, "Task 3", 1000, NULL, 2, NULL ); vTaskStartScheduler(); while(1); ``` 然后在每个task中,一般代码会这样写 ``` void vTask1( void *pvParameters ) { volatile unsigned long ul; for( ;; ) { xQueueSend( USART1_MSGQ, "task 1 !\n",portMAX_DELAY); for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ); } } ``` 而任务之间的通信也是比较繁琐,总体来说,代码不易维护,增减一个任务的话要改的东西太多了。为此我特意设计一个框架,可以很方便的增减任务,同时任务之间通过事件队列来通信。 ## demo ### 任务创建函数的封装 我们首先定义两个任务,把所有任务信息封装在`taskRecord`里,并且申明如下: ``` #define TASK_NUM 2 //所有任务的信息 static TaskRecord taskRecord[TASK_NUM]; ``` 那么`TaskRecord`怎么安排呢,我们把所有的任务信息都放在结构体里。其中包括任务ID,任务任务函数`taskFucn`,任务名字,栈的大小`stackDep`,还有优先级`prio`,任务句柄`taskHandle`,任务队列`queue`。 ``` typedef struct { int16_t Id; TaskFunction_t taskFucn; const char * name; configSTACK_DEPTH_TYPE stackDep; void * parameters; UBaseType_t prio; TaskHandle_t taskHandle; QueueHandle_t queue; } TaskRecord; ``` 把任务中的一些参数封装起来,放在结构体`TaskInitPara`,其中包括了任务函数`taskFucn`,任务名字,栈的大小`stackDep`,还有优先级`prio`。 ``` typedef struct { TaskFunction_t taskFucn; const char * name; const configSTACK_DEPTH_TYPE stackDep; UBaseType_t prio; } TaskInitPara; ``` 我们做好了这些之后,就需要把结构体中的参数放到创建任务函数中,那么这个函数`createTasks`代码如下: ``` void createTasks(TaskRecord* taskRecord, const TaskInitPara* taskIniPara, int num){ int i; for(i=0;i<num;i++){ taskRecord[i].Id = i; taskRecord[i].taskFucn = taskIniPara[i].taskFucn; taskRecord[i].name = taskIniPara[i].name; taskRecord[i].stackDep = taskIniPara[i].stackDep; taskRecord[i].parameters = &taskRecord[i]; taskRecord[i].prio = taskIniPara[i].prio; xTaskCreate( taskRecord[i].taskFucn, taskRecord[i].name, taskRecord[i].stackDep, taskRecord[i].parameters, taskRecord[i].prio, &taskRecord[i].taskHandle ); taskRecord[i].queue = xQueueCreate( 100, sizeof( Event ) ); } ``` 其中`num`为任务数量,先把任务信息放到初始化的`taskRecord`中,再把其中的信息创建任务。那么任务创建函数就做好了。 ### main函数 接着,在我们的main函数中,就不需要那么繁琐的一个一个的创建任务了,按照这个封装的main函数如下: ``` int main( void ) { createTasks(taskRecord,taskInitPara,TASK_NUM); /@@* Start the tasks and timer running. */ vTaskStartScheduler(); } ``` ### 任务间通信 首先我们想想,两个任务之间通信需要知道什么,task1想往task2的发送一些数据,那么需要知道task2的ID吧,需要把数据打包吧,task2需要知道是谁发的,那么task1本身的ID也需要知道吧。 按照这几个明确的东西,我们首先把任务事件ID枚举如下 ``` typedef enum { eventID_1, eventID_2, eventID_3 }Event_ID; ``` 然后把事件ID,发送者ID,以及要传输的结构或者数据打包封装在结构体Event中,代码如下: ``` typedef struct{ Event_ID ID; int16_t src; //发送者ID void* pData; //传结构、数据 }Event; ``` 接着,我们需要构造一个事件,把这些信息都放在这个事件中,代码如下: ``` void makeEvent(Event* pEvent,int16_t myId,Event_ID evtId, const void* pData){ pEvent->ID = evtId; pEvent->src = myId; pEvent->pData = (void*) pData; } ``` 现在我们假设task2要往task1发送一系列数据,那么task任务中,我们需要做的事如下,获取task1中队列,看是否为空 ``` QueueHandle_t task1Queue; int16_t myId = pMyTaskRecord->Id; task1Queue = getTaskQueue(getTaskId("task1")); ``` 构造事件 ``` Event event; int* ptemp; //这里自定义一些数据 makeEvent(&event,myId,eventID_1,(void*)ptemp); ``` 然后把事件发送出去: ``` xQueueSendToBack( task1Queue, &event, 0); ``` 对于task1来说,看队列中是否为空,如果有任务事件来,从队列中获取事件 ``` TaskRecord* pMyTaskRecord = (TaskRecord*)pPara; QueueHandle_t* evntQueue=pMyTaskRecord->queue; ``` 当队列中确实有事件时,接收事件 ``` BaseType_t status = xQueueReceive( *evntQueue, &event, portMAX_DELAY ); if( status == pdPASS ) { task1HandleEvent(event); } else { printf( "Task1 could not receive from the queue.\r\n" );} ``` 然后我们在`task1HandleEvent`处理接收到的事件,代码如下: ``` void task1HandleEvent(Event event){ xil_printf( "Task1 is processing event...\r\n" ); int* p; switch(event.ID){ case eventID_1: p= (int*) event.pData; xil_printf("ID=%d From: %d data=%d\r\n",event.ID, event.src,p[7]); free(event.pData); break; case eventID_2: break; default: break; } } ``` 上面代码表示根据事件ID来判断接收的是哪个事件,再把事件ID,数据等等打印出来。 ## 测试 如上,我们构造一个事件,发送一些数据如下 ``` Event event; int* ptemp = malloc(sizeof(int)*10); memset(ptemp,0x77,sizeof(int)*10); makeEvent(&event,myId,eventID_1,(void*)ptemp); ``` 我们看到结果如下 ![](https://cf04.ickimg.com/bbsimages/202008/9cb6a8640ed00c1a8d9c61b49693fede.jpg) task1接到来自任务ID为0,事件1的数据。这里每个任务的等待时间也是可以设置的,设置方法如下: ``` /@@* 设置最大等待时间500ms */ const TickType_t xMaxBlockTime = pdMS_TO_TICKS(500); BaseType_t status = xQueueReceive( *evntQueue, &event, xMaxBlockTime ); ``` 如果等待时间为portMAX_DELAY或者0的话,说明某个任务一直处于激活状态,比如task2,当等待时间为portMAX_DELAY时候,则测试结果如下: ![](https://cf04.ickimg.com/bbsimages/202008/99436a4a1e39fb73a8b75cb2a8e781e2.png) 所以每个任务设置的时间,优先级,栈大小都是很重要的,具体的就需要在项目中调试了。 ## 最后总结 本篇是属于代码实战篇,对于freeRTOS的具体讲解需要大家自己去领会,我这里是写了一个架构,帮助大家在项目中去更好的搭好架子,当我们有很多任务的时候,任务间又有很多交互通信的时候,就更需要理解这种架构了。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
李肖遥
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交