Practical articles | Multi-task event transmission demo based on freeRTOS (with code)

I shared a lot of knowledge about freeRTOS before, so how do we write code in actual combat? This article focuses on the analysis of freeRTOS-based architecture code. The whole function is as follows:

Why use freeRTOS

In actual projects, if the program waits for a timeout event, in the traditional case of no RTOS, it can only wait in place and cannot perform other tasks. If you use RTOS, you can easily block the current task under the event, and then Automatically perform other tasks, so you can efficiently use the CPU.

General usage

When we are developing, I always see the following code in the main function, which makes me feel uncomfortable

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);
}

Then in each task, the general code will be written like this

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++ );
  }
}

The communication between tasks is also more cumbersome. Generally speaking, the code is not easy to maintain, and there are too many things to change when adding or subtracting a task. For this reason, I specially designed a framework that can easily increase or decrease tasks, and at the same time communicate between tasks through event queues.

demo

Encapsulation of task creation function

We first define two tasks, encapsulate all task information in them taskRecord, and declare as follows:

#define TASK_NUM 2
//所有任务的信息
static TaskRecord taskRecord[TASK_NUM];

So TaskRecordhow to arrange it, we put all the task information in the structure. This includes task ID, task task function taskFucn, task name, stack size stackDep, priority prio, task handle taskHandle, and task queue 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;

Encapsulate some parameters in the task and place it in a structure TaskInitPara, which includes the task function taskFucn, task name, stack size stackDep, and priority prio.

typedef struct
{
  TaskFunction_t taskFucn;
  const char *  name;
  const configSTACK_DEPTH_TYPE stackDep;
  UBaseType_t prio;
} TaskInitPara;

After we have done this, we need to put the parameters in the structure into the task creation function, then the function createTaskscode is as follows:

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 ) );
 }
}

Among them numis the number of tasks, first put the task information in the initialization taskRecord, and then create the task with the information in it. Then the task creation function is done.

main function

Then, in our main function, there is no need to create tasks one by one. The main function according to this package is as follows:

int main( void )
{
 createTasks(taskRecord,taskInitPara,TASK_NUM);
 /* Start the tasks and timer running. */
 vTaskStartScheduler();
}

Inter-task communication

First of all, we need to think about what we need to know for communication between two tasks. Task1 wants to send some data to task2, so we need to know the ID of task2, we need to pack the data, task2 needs to know who sent it, then task1 itself You also need to know the ID.

According to these clear things, we first enumerate the task event ID as follows

typedef enum {
 eventID_1,
 eventID_2,
 eventID_3
}Event_ID;

Then package the event ID, sender ID, and the structure or data to be transmitted in the structure Event, the code is as follows:

typedef struct{
 Event_ID ID;
 int16_t src; //发送者ID
 void* pData; //传结构、数据
}Event;

Next, we need to construct an event and put all this information in this event. The code is as follows:

void makeEvent(Event* pEvent,int16_t myId,Event_ID evtId, const void* pData){
 pEvent->ID = evtId;
 pEvent->src = myId;
 pEvent->pData = (void*) pData;
}

Now we assume that task2 wants to send a series of data to task1, then in the task task, what we need to do is as follows, get the queue in task1 to see if it is empty.

 QueueHandle_t task1Queue;
 int16_t myId = pMyTaskRecord->Id;
 task1Queue = getTaskQueue(getTaskId("task1"));

Construction event

 Event event;
 int* ptemp; //这里自定义一些数据
 makeEvent(&event,myId,eventID_1,(void*)ptemp);

Then send the event out:

xQueueSendToBack( task1Queue, &event, 0);

For task1, see if the queue is empty, if there is a task event, get the event from the queue

 TaskRecord* pMyTaskRecord = (TaskRecord*)pPara;
 QueueHandle_t* evntQueue=pMyTaskRecord->queue;

When there is an event in the queue, receive the event

BaseType_t status = xQueueReceive( *evntQueue, &event, portMAX_DELAY );
if( status == pdPASS )
{
  task1HandleEvent(event);
}
else
{
  printf( "Task1 could not receive from the queue.\r\n" );}

Then we are task1HandleEventprocessing the received event, the code is as follows:

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;
 }
}

The above code means to judge which event is received according to the event ID, and then print out the event ID, data, etc.

External interruption communication

What if it is not communication between tasks, but an external interrupt trigger, and information exchange with a certain task is required, what should I do? For example, there is an Ethernet task. When the external network needs to send a data packet to this network task, then external communication is required. In the same way, we construct events in the Ethernet receiving function

 Event event;
 int* ptemp; //这里自定义一些数据
 makeEvent(&event,myId,IntrID_1,(void*)ptemp);//可以再自定一些事件ID如IntrID_1

Then send this event to this task, as follows

test

As above, we construct an event and send some data as follows

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

We see the result as follows

task1 receives data from task ID 0, event 1. The waiting time of each task can also be set here. The setting method is as follows:

/* 设置最大等待时间500ms */
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(500); 
BaseType_t status = xQueueReceive( *evntQueue, &event, xMaxBlockTime );

If the waiting time is portMAX_DELAY or 0, it means that a certain task is always active, such as task2. When the waiting time is portMAX_DELAY, the test results are as follows:

Therefore, the time, priority, and stack size set for each task are very important, and the specifics need to be debugged in the project.

Final summary

This article belongs to the actual code chapter. For the specific explanation of freeRTOS, you need to understand it yourself. Here I am writing an architecture to help you set up a better shelf in the project. When we have a lot of tasks, between tasks When there is a lot of interactive communication, it is even more necessary to understand this architecture.

Guess you like

Origin blog.csdn.net/u012846795/article/details/107995207