实战篇

您所在的位置:网站首页 freertos中文网 实战篇

实战篇

2023-08-24 07:34| 来源: 网络整理| 查看: 265

ID:技术让梦想更伟大

作者:李肖遥

之前分享了很多关于freeRTOS的知识,那么我们怎么在实战中去写代码呢?本篇文章重在对基于freeRTOS的架构代码的解析。整个功能如下图:

为什么要用freeRTOS

在实际项目中,如果程序等待一个超时事件,传统的无RTOS情况下,就只能在原地等待而不能执行其它任务,如果使用RTOS,则可以很方便的将当前任务阻塞在该事件下,然后自动去执行别的任务,这样可以高效的利用CPU了。

一般使用情况

我们在开发的时候,我总是在main函数看到以下的代码,这让我感觉不是很爽

int 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;iID = 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; //这里自定义一些数据 makeEvent(&event,myId,IntrID_1,(void*)ptemp);//可以再自定一些事件ID如IntrID_1

然后再发送到这个事件到这个任务中,如下

测试

如上,我们构造一个事件,发送一些数据如下

Event event; int* ptemp = malloc(sizeof(int)*10); memset(ptemp,0x77,sizeof(int)*10); makeEvent(&event,myId,eventID_1,(void*)ptemp);

我们看到结果如下

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时候,则测试结果如下:

所以每个任务设置的时间,优先级,栈大小都是很重要的,具体的就需要在项目中调试了。

最后总结

本篇是属于代码实战篇,对于freeRTOS的具体讲解需要大家自己去领会,我这里是写了一个架构,帮助大家在项目中去更好的搭好架子,当我们有很多任务的时候,任务间又有很多交互通信的时候,就更需要理解这种架构了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3