之前分享了很多关于freeRTOS的知识,那么我们怎么在实战中去写代码呢?本篇文章重在对基于freeRTOS的架构代码的解析。整个功能如下图:
为什么要用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 );
-
-
-
-
-
-
然后在每个task中,一般代码会这样写
-
void vTask1( void *pvParameters )
-
-
volatile unsigned long ul;
-
-
-
xQueueSend( USART1_MSGQ,
"task 1 !\n",portMAX_DELAY);
-
for( ul =
0; ul < mainDELAY_LOOP_COUNT; ul++ );
-
-
而任务之间的通信也是比较繁琐,总体来说,代码不易维护,增减一个任务的话要改的东西太多了。为此我特意设计一个框架,可以很方便的增减任务,同时任务之间通过事件队列来通信。
demo
任务创建函数的封装
我们首先定义两个任务,把所有任务信息封装在taskRecord
里,并且申明如下:
-
-
-
static TaskRecord taskRecord[TASK_NUM];
那么TaskRecord
怎么安排呢,我们把所有的任务信息都放在结构体里。其中包括任务ID,任务任务函数taskFucn
,任务名字,栈的大小stackDep
,还有优先级prio
,任务句柄taskHandle
,任务队列queue
。
-
-
-
-
-
-
configSTACK_DEPTH_TYPE stackDep;
-
-
-
-
-
把任务中的一些参数封装起来,放在结构体TaskInitPara
,其中包括了任务函数taskFucn
,任务名字,栈的大小stackDep
,还有优先级prio
。
-
-
-
-
-
const configSTACK_DEPTH_TYPE stackDep;
-
-
我们做好了这些之后,就需要把结构体中的参数放到创建任务函数中,那么这个函数createTasks
代码如下:
-
void createTasks(TaskRecord* taskRecord,
const TaskInitPara* taskIniPara,
int num){
-
-
-
-
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].parameters,
-
-
&taskRecord[i].taskHandle );
-
-
taskRecord[i].queue = xQueueCreate(
100, sizeof( Event ) );
-
-
其中num
为任务数量,先把任务信息放到初始化的taskRecord
中,再把其中的信息创建任务。那么任务创建函数就做好了。
main函数
接着,在我们的main函数中,就不需要那么繁琐的一个一个的创建任务了,按照这个封装的main函数如下:
-
-
-
createTasks(taskRecord,taskInitPara,TASK_NUM);
-
/* Start the tasks and timer running. */
-
-
任务间通信
首先我们想想,两个任务之间通信需要知道什么,task1想往task2的发送一些数据,那么需要知道task2的ID吧,需要把数据打包吧,task2需要知道是谁发的,那么task1本身的ID也需要知道吧。
按照这几个明确的东西,我们首先把任务事件ID枚举如下
-
-
-
-
-
然后把事件ID,发送者ID,以及要传输的结构或者数据打包封装在结构体Event中,代码如下:
-
-
-
-
-
接着,我们需要构造一个事件,把这些信息都放在这个事件中,代码如下:
-
void makeEvent(Event* pEvent,int16_t myId,Event_ID evtId,
const void* pData){
-
-
-
pEvent->pData = (void*) pData;
-
现在我们假设task2要往task1发送一系列数据,那么task任务中,我们需要做的事如下,获取task1中队列,看是否为空。
-
QueueHandle_t task1Queue;
-
int16_t myId = pMyTaskRecord->Id;
-
task1Queue = getTaskQueue(getTaskId(
"task1"));
构造事件
-
-
-
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 );
-
-
-
-
-
-
-
printf(
"Task1 could not receive from the queue.\r\n" );}
然后我们在task1HandleEvent
处理接收到的事件,代码如下:
-
void task1HandleEvent(Event event){
-
xil_printf(
"Task1 is processing event...\r\n" );
-
-
-
-
-
xil_printf(
"ID=%d From: %d data=%d\r\n",event.ID, event.src,p[
7]);
-
-
-
-
-
-
-
-
上面代码表示根据事件ID来判断接收的是哪个事件,再把事件ID,数据等等打印出来。
外部中断通信
如果不是任务间的通信,而是有外部中断触发,需要与某个任务进行信息交互,怎么办?例如有一个以太网任务,当外部网络需要发送一个数据包到这个网络任务的时候,那么就需要进行外部通信了。同样我们这样做,在以太网接收函数中,构造事件
-
-
-
makeEvent(&event,myId,IntrID_1,(void*)ptemp);
//可以再自定一些事件ID如IntrID_1
然后再发送到这个事件到这个任务中,如下
测试
如上,我们构造一个事件,发送一些数据如下
-
-
int* ptemp = malloc(sizeof(
int)*
10);
-
memset(ptemp,
0x77,sizeof(
int)*
10);
-
makeEvent(&event,myId,eventID_1,(void*)ptemp);
我们看到结果如下
task1接到来自任务ID为0,事件1的数据。这里每个任务的等待时间也是可以设置的,设置方法如下:
-
-
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(
500);
-
BaseType_t status = xQueueReceive( *evntQueue, &event, xMaxBlockTime );
如果等待时间为portMAX_DELAY或者0的话,说明某个任务一直处于激活状态,比如task2,当等待时间为portMAX_DELAY时候,则测试结果如下:
所以每个任务设置的时间,优先级,栈大小都是很重要的,具体的就需要在项目中调试了。
最后总结
本篇是属于代码实战篇,对于freeRTOS的具体讲解需要大家自己去领会,我这里是写了一个架构,帮助大家在项目中去更好的搭好架子,当我们有很多任务的时候,任务间又有很多交互通信的时候,就更需要理解这种架构了。