以前にfreeRTOSについて多くの知識を共有したので、実際の戦闘でコードをどのように記述しますか?この記事では、freeRTOSベースのアーキテクチャコードの分析に焦点を当てています。全体の機能は次のとおりです。
freeRTOSを使用する理由
実際のプロジェクトでは、プログラムがタイムアウトイベントを待機する場合、RTOSがない従来のケースでは、その場で待機するだけで他のタスクを実行できません。RTOSを使用すると、イベントの下で現在のタスクを簡単にブロックできます。他のタスクを自動的に実行するため、CPUを効率的に使用できます。
一般的な使用法
開発中は、メイン関数に次のコードが常に表示されるため、不快に感じます
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);
}
次に、各タスクで、一般的なコードは次のように記述されます
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++ );
}
}
また、タスク間の通信もより面倒であり、一般的に言えば、コードの保守は容易ではなく、タスクを追加または削除するときに変更することが多すぎます。このため、タスクを簡単に増減できると同時に、イベントキューを介してタスク間で通信できるフレームワークを特別に設計しました。
デモ
タスク作成機能のカプセル化
最初に2つのタスクを定義し、すべてのタスク情報をカプセル化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関数ではタスクを1つずつ作成する必要がなくなり、このパッケージによるmain関数は次のようになります。
int main( void )
{
createTasks(taskRecord,taskInitPara,TASK_NUM);
/* Start the tasks and timer running. */
vTaskStartScheduler();
}
タスク間通信
最初に、2つのタスク間の通信のために何を知る必要があるかを考える必要があります。タスク1はデータをタスク2に送信する必要があるため、タスク2のIDを知る必要があり、データをパックする必要があります。タスク2は送信者を知る必要があり、次にタスク1自体を知る必要があります。 IDも知っておく必要があります。
これらの明確なことから、最初に次のようにタスクイベントIDを列挙します。
typedef enum {
eventID_1,
eventID_2,
eventID_3
}Event_ID;
次に、イベントID、送信者ID、および送信する構造またはデータを構造イベントにパッケージ化すると、コードは次のようになります。
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; //这里自定义一些数据
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の具体的な説明については、自分で理解する必要があります。ここでは、プロジェクトでより優れたシェルフを設定するのに役立つアーキテクチャを書いています。多くのタスクがある場合、タスク間インタラクティブなコミュニケーションが多い場合、このアーキテクチャを理解する必要があります。