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 );
	
	taskENTER_CRITICAL();
	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);

	taskEXIT_CRITICAL();
}


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。由于它会创建任务,而新创建的任务,有可能具有较高的优先级,会抢占CPU,从而导致starttask的后续工作被延迟。为了防止这个情况出现,有两种解决办法,
一是将starttask的任务优先级设置为最高,例如,TIMERTASK就是最高优先级。

#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)

二是使用临界代码段保护。

taskENTER_CRITICAL();
taskEXIT_CRITICAL();

在这里,我们使用了第二种方式。

它部署了两个常驻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的职能。

你可能感兴趣的:(freertos)