freertos(第十一課,multi-task程序架構)

基於freertos的程序架構,可以結合front-middle-rear stage,進行任務劃分。
整個系統仍然是一個event driven system。但是由於有了RTOS進行任務管理,所以在任務劃分上,更加清晰,並且實時性更好。
主要考慮的是,進程通信,進程功能,事件處理。

對於外部事件,對應於IRQ。對於IRQ的響應過程,分爲了兩部分。類似於linux中的top half和bottom half。我們可以分爲IRQHandler和IRQTask兩部分。
則IRQHandler處於front stage,而IRQTask則處於middle stage。
TH和BH基於RTOS提供的進程通信機制進行通信。例如TaskNotify,Semaphore,Queue等。
IRQTask在任務上下文中對IRQACTION作出進一步的響應。
IRQTask另一個重要的功能,就是作爲聯繫front stage和rear stage的橋樑。它伺服於IRQHandler產生的event,另一方面,它也產生event,發送給rear stage。
rear stage則伺服於IRQTask產生的event。
另外,不同的middle stage之間,也能夠進行進程間通信。

對於時間事件,對應於TickCount。對於TickCount的響應過程,則簡單很多。只需要設置TickTask,利用TaskDelay進行時間同步即可。

對於傳輸事件,對應於msg。對於MSG的響應過程,只需要設置SendTask和ReceiveTask,利用Queue進行資源同步即可。

我們可以根據具體應用,將任務進行劃分。
分析出系統中需要設置哪些任務,他們屬於什麼角色,業務流程如何安排。
常見的Task,有如下幾種角色
1)IRQ Capturer。由IRQHandler來承擔。
在ISR中,進行最基本的操作,通常是產生event。
2)IRQ Processor。由IRQTask來承擔。
在Task中,等待event,當收到消息時,根據event做出對應的處理。
3)Timing Responser。由TickTask來承擔。
在Task中,等待TIMING,當Timing到達時,根據state做出對應的處理。
4)MSG Processor。由MSGReceiveTask來承擔。
在Task中,等待msg。當收到消息時,根據msg做出對應的處理。例如,TimerTask,也稱爲DaemonTask,就是典型的MSGProcessor。
5)Object Server。由ServerTask來承擔。通過查詢註冊到系統中的對象的屬性,判斷應該執行的操作,例如Callback,suspend等。
TimerTask,就是典型ObjectServer。
6)Deployer。由StartTask來承擔。
在Task中,創建系統所需要的常駐任務。常駐任務是無限循環的任務,而StartTask並不需要常駐在系統中,所以它的TaskFunction的最後一個工作,就是將自己從系統中刪除。
7)SystemManager。由ManagerTask來承擔。它主要負責系統內的資源管理,任務管理等。根據相應的條件,例如系統的state,產生的event,收到的msg,等等,執行對應的管理類操作。例如createtask,deletetask等。
大多數時候,系統中並沒有專門部署ManagerTask,而是由其他的Task臨時兼任。如果某個Task中,存在管理類的操作,那麼我們就認爲這個Task兼具管理者的職能。
例如,當DaemonTask調用了Callack時,而這個Callback含有管理類操作,我們就可以認爲,DaemonTask在此刻,兼具了管理者的職能。

可以看出,整個多進程系統的設計,最關鍵的就是進程通信的設計。
各個不同的任務角色,都是基於消息和其他進程協同工作。
唯一不同的就是Deployer。它在部署完系統後,就結束了任務生存週期。

我們來看一個簡單的helloworld。
如前所述,系統啓動時,要做一系列的初始化工作,然後創建第一個任務,即StartTask,然後啓動調度器,並讓主進程main進入無限循環。

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
/* Xilinx includes. */
#include "xil_printf.h"
#include "xparameters.h"

我們需要使用freertos提供的API,也需要standalone提供的API,所以,首先把這些H文件包含進來。

#define TIMER_ID	1
#define DELAY_10_SECONDS	10000UL
#define DELAY_1_SECOND		1000UL
#define TIMER_CHECK_THRESHOLD	9

我們需要使用一些常量,所以首先用宏把這些常數進行符號化處理,使它們具有明確的現實含義。

/* The Tx and Rx tasks as described at the top of this file. */
static void prvStartTask( void *pvParameters );
static void prvTxTask( void *pvParameters );
static void prvRxTask( void *pvParameters );

static void vTimerCallback( TimerHandle_t pxTimer );

我們需要用到的函數,在文件前部進行聲明。

/* The queue used by the Tx and Rx tasks, as described at the top of this
file. */
static TaskHandle_t xStartTask;
static TaskHandle_t xTxTask;
static TaskHandle_t xRxTask;
static QueueHandle_t xQueue = NULL;
static TimerHandle_t xTimer = NULL;
char HWstring[15] = "Hello World";
long RxtaskCntr = 0;

我們需要用到全局變量,在文件頭部進行實例化。

int main( void )
{
	xil_printf( "Hello from Freertos example main\r\n" );

	xTaskCreate( prvStartTask,
				 ( const char * ) "ST",
				 configMINIMAL_STACK_SIZE,
				 NULL,
				 tskIDLE_PRIORITY + 1,
				 &xStartTask );
				 
	/* Start the tasks and timer running. */
	vTaskStartScheduler();
	for( ;; );
}

static void prvStartTask( void *pvParameters )
{
	const TickType_t x10seconds = pdMS_TO_TICKS( DELAY_10_SECONDS );
	
	xTaskCreate( 	prvTxTask, 					/* The function that implements the task. */
					( const char * ) "Tx", 		/* Text name for the task, provided to assist debugging only. */
					configMINIMAL_STACK_SIZE, 	/* The stack allocated to the task. */
					NULL, 						/* The task parameter is not used, so set to NULL. */
					tskIDLE_PRIORITY,			/* The task runs at the idle priority. */
					&xTxTask );

	xTaskCreate( prvRxTask,
				 ( const char * ) "GB",
				 configMINIMAL_STACK_SIZE,
				 NULL,
				 tskIDLE_PRIORITY + 1,
				 &xRxTask );

	xQueue = xQueueCreate( 	1,						/* There is only one space in the queue. */
							sizeof( HWstring ) );	/* Each space in the queue is large enough to hold a uint32_t. */

	/* Check the queue was created. */
	configASSERT( xQueue );

	xTimer = xTimerCreate( (const char *) "Timer",
							x10seconds,
							pdFALSE,
							(void *) TIMER_ID,
							vTimerCallback);
	/* Check the timer was created. */
	configASSERT( xTimer );

	xTimerStart( xTimer, 0 );
	
	vTaskDelete(NULL);
}


static void prvTxTask( void *pvParameters )
{
	const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );

	for( ;; )
	{
		/* Delay for 1 second. */
		vTaskDelay( x1second );

		xQueueSend( xQueue,			/* The queue being written to. */
					HWstring, /* The address of the data being sent. */
					0UL );			/* The block time. */
	}
}
static void prvRxTask( void *pvParameters )
{
	char Recdstring[15] = "";

	for( ;; )
	{
		xQueueReceive( 	xQueue,				/* The queue being read. */
						Recdstring,	/* Data is read into this address. */
						portMAX_DELAY );	/* Wait without a timeout for data. */

		/* Print the received data. */
		xil_printf( "Rx task received string from Tx task: %s\r\n", Recdstring );
		RxtaskCntr++;
	}
}

static void vTimerCallback( TimerHandle_t pxTimer )
{
	long lTimerId;
	configASSERT( pxTimer );

	lTimerId = ( long ) pvTimerGetTimerID( pxTimer );

	if (lTimerId != TIMER_ID) {
		xil_printf("FreeRTOS Hello World Example FAILED");
	}

	if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
		xil_printf("FreeRTOS Hello World Example PASSED");
	} else {
		xil_printf("FreeRTOS Hello World Example FAILED");
	}

	vTaskDelete( xRxTask );
	vTaskDelete( xTxTask );
}

從中可以看到,
有一個Deployer,即starttask。它部署了兩個常駐task,然後部署了公共資源queue,然後部署了Timer,提交給TimerTask。
TimerTask是一個IRQProcessor,它在每個Tick被喚醒運行,同時它也是一個ObjectServer,它被喚醒運行時,會查詢註冊的Timer的timestamp,如果條件滿足,則調用Callback。
TimerCallback並不是一個獨立的任務,它只是被DaemonTask在適當的時候調用,所以它是運行在DaemonTask的進程上下文中的。我們在編寫時,需要清楚Callback是oneshot的。雖然有periodical的Timer,但是實質上,它已經是另一個Timer了,只不過,在當前Timer被刪除時,系統又新創建了一個TickCount晚一個週期的NewTimer。

TxTask是典型的TimingResponser。所以它的循環體的首句,是一個TimingSync。當它被喚醒後,繼續執行,所做的工作,是發送一個msg。
RxTask是典型的MSGProcessor。所以它的循環體的首句,是一個MessageSync。當它被喚醒後,繼續執行,所做的工作,是將接收到的msg發送到stdout。

TimerCallback中,由於使用了taskdelete,所以當Callback被調用執行時,DaemonTask兼具了Manager的職能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章