zedboard第二十三课(standalone, Event driven System , RTOS)

在standaloneOS的应用中,只有一个主线程,就是main函数。
它是后台进程的入口函数。而各种中断服务函数ISR,就是前台进程的函数。
我们知道,Callback是运行在前台的,而main是云心在后台的。
这里,简单介绍前后台程序架构。
front and rear stage,即前后台结构。
front stage 负责的是event response。
rear stage 负责的是task process。
从这个角度讲,ISR是front stage,因为中断负责响应事件,具体的行为就是setup Events。
main是rear stage,因为main负责轮询调用各个task,它用一个while循环调用各个task。例如

while(1){
	task1(&event1);
	task1(&event2);
	task1(&event3);
	task1(&event4);
	task1(&event5);
	...
}

如果有中断触发,那么ISR就负责修改这些event结构体对象的内容。
当ISR返回后,CPU执行权重新回到main中,main再依次调用各个task,并传入各自需要使用的event对象句柄。

对于简单系统,时间要求宽松的系统,这种Event Driven System是可以胜任的。但是它的最大问题就是,后台是一个单一进程。所以event的对应的task被执行的时机是不确定的。
一旦系统更加复杂,或者需要实时响应的时候,EDS就出现性能瓶颈了。

解决方案就是,RTOS。
Xilinx的SDK提供对FreeRTOS的支持。但是无论是UCOS还是FreeRTOS,其设计思想都是一样的。

最重要的变化,就是Multi processing。多进程。
多进程使得CPU被复用,从宏观上看,好像每个进程都拥有自己的CPU一样。
当进程被分配了不同的优先级,并针对不同的优先级,分配了权重不同的CPU时间片后,每个进程就拥有了不同算力分配。
进程使用了时间片之后,每个进程的启动时机都是在创建之后很短的延时内出现的。这就是被称为RT的原因。
但是宏观上看,由于CPU被复用,所以每个进程获取的算力,都不会是100%。而且由于优先级的不同,每个进程获取的算力也并不相等。
但这并不影响RT特性,因为进程的启动时机是延迟很小的,只不过,由于有效算力的减弱,进程的执行过程被拉长了。
对于多任务的系统,这个特性再好不过了。
对于small task,它能够实时启动,算力减弱并不影响多少。
对于big task,它也能够实时启动,但是由于算力减弱,它需要花费更多的时间,但是它通常并不是那么紧急。

SDK中移植的FreeRTOS,是基于standalone的,层次上,位于BSP和APP之间。提供了中间件的服务功能。
RTOS需要使用BSP所提供的服务功能。所不同的是,在metal方式下,使用RAW API直接是应用程序,现在变成应用程序使用RTOS的API,而RTOS再使用RAWAPI。从而形成层次化调用。
所以,之前的基于BSP的应用程序,稍加改造,就可以成为基于RTOS的应用程序。

和METAL方式下的执行过程一样,
当系统上电后,首先进入ExceptionVector,然后跳转到_boot标号的位置执行,这个代码在boot.S中。然后跳转到_start标号的位置执行,这个代码在xil_ctr0.S中,然后跳转到main标号的位置执行,这个代码在应用程序的main.c中。

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

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 )
{
	const TickType_t x10seconds = pdMS_TO_TICKS( DELAY_10_SECONDS );

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

	/* Create the two tasks.  The Tx task is given a lower priority than the
	Rx task, so the Rx task will leave the Blocked state and pre-empt the Tx
	task as soon as the Tx task places an item in the queue. */
	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 );

	/* Create the queue used by the tasks.  The Rx task has a higher priority
	than the Tx task, so will preempt the Tx task and remove values from the
	queue as soon as the Tx task writes to the queue - therefore the queue can
	never have more than one item in it. */
	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 );

	/* Create a timer with a timer expiry of 10 seconds. The timer would expire
	 after 10 seconds and the timer call back would get called. In the timer call back
	 checks are done to ensure that the tasks have been running properly till then.
	 The tasks are deleted in the timer call back and a message is printed to convey that
	 the example has run successfully.
	 The timer expiry is set to 10 seconds and the timer set to not auto reload. */
	xTimer = xTimerCreate( (const char *) "Timer",
							x10seconds,
							pdFALSE,
							(void *) TIMER_ID,
							vTimerCallback);
	/* Check the timer was created. */
	configASSERT( xTimer );

	/* start the timer with a block time of 0 ticks. This means as soon
	   as the schedule starts the timer will start running and will expire after
	   10 seconds */
	xTimerStart( xTimer, 0 );

	/* Start the tasks and timer running. */
	vTaskStartScheduler();

	/* If all is well, the scheduler will now be running, and the following line
	will never be reached.  If the following line does execute, then there was
	insufficient FreeRTOS heap memory available for the idle and/or timer tasks
	to be created.  See the memory management section on the FreeRTOS web site
	for more details. */
	for( ;; );
}

在RTOS中,将进程命名为TASK,是为了使逻辑上更符合RT的应用场景。
在main中,进行了一系列初始化和创建工作后,启动任务调度。
之后,main转入rear stage,是一个for(;😉 循环,使整个main进程处于IDLE状态。
系统将调度之前创建的其他任务进入执行状态,获得CPU时间片资源。如果系统中始终有任务进程没有消亡,那么理论上,IDLE进程将始终得不到执行。
从代码中我们看到,main进程在转入rear stage之前,创建了多个对象实体。
两个Task对象实体,这是后面要执行的任务控制块。
一个Queue对象实体,这是两个进程通信的公共资源的资源描述块。
一个TIMER对象实体,用来配置一个TIMER EVENT。

这些结构体都是在main之外定义的静态全局变量。所以可以在main进程外使用。

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

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

		/* Send the next value on the queue.  The queue should always be
		empty at this point so a block time of 0 is used. */
		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( ;; )
	{
		/* Block to wait for data arriving on the queue. */
		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++;
	}
}

定义了两个任务所需要执行的函数。即taskfunction。
可以看到,这两个任务都是无限循环的,如果放在同一个进程中,那么另一个将永远无法获得CPU时间片。但是有了RTOS的调度,两个任务可以穿插执行。
两个任务进程间通信,利用公共资源Queue来完成,并利用RTOS API来操作公共资源。
RX具有比TX更高的优先级,所以在调度时,只要条件满足,RX总是会抢占CPU执行,除非被阻塞。而能够阻塞RX的,就是Queue。

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 the RxtaskCntr is updated every time the Rx task is called. The
	 Rx task is called every time the Tx task sends a message. The Tx task
	 sends a message every 1 second.
	 The timer expires after 10 seconds. We expect the RxtaskCntr to at least
	 have a value of 9 (TIMER_CHECK_THRESHOLD) when the timer expires. */
	if (RxtaskCntr >= TIMER_CHECK_THRESHOLD) {
		xil_printf("FreeRTOS Hello World Example PASSED");
	} else {
		xil_printf("FreeRTOS Hello World Example FAILED");
	}

	vTaskDelete( xRxTask );
	vTaskDelete( xTxTask );
}

定义了TIMER EVENT的Callback。
当RTOS的TICKNUM达到TIMER指定的TICKNUM时,将创建一个最高优先级的tasklet,将Callback作为taskfunction配置给tasklet。从而使tasklet在调度时,被优先执行。
我们看到,tasklet中,利用RTOS API,删除了两个任务。从而使得下次调度时,这两个任务不会再被调度执行了。

这里,涉及到编程思想的转变。最主要的编程思想,就是task separate。
front stage所对应的event的Callback,被装进tasklet,并由RTOS负责调度执行,由于tasklet具有高优先级,所以tasklet会被很快的调度执行,这和在ISR中执行Callback,几乎没有什么差别。
rear stage所对应的task,每个taskfunction被装进不同的task,并有RTOS负责调度执行,这和在main中执行taskfunction,变成了穿插执行,宏观上看,相当于是并行执行。这样,每个taskfunction,都可以使用无限循环,而且也不用担心其他task无法获取CPU时间片的问题。
task分离,使应用工程师只需要关注于taskfunction的功能性问题,因为宏观上看,就好像每个task都有一个自己的CPU一样。
由于分时复用和阻塞机制的存在,task的执行流程并不会错乱,而只是等效算力减弱。从用户的角度看,就好像某个函数的执行时间变的更长甚至不确定多长。但执行时间并不是静态代码编写时的考虑因素,所以对于编写某一个task的应用工程师而言,不再需要像前后台一样,要考虑整个轮询机中,如何安排各个taskfunction的执行流程,而仅仅只考虑单一的taskfunction的功能性代码即可。

整个程序的划分,就可以分派给多个的应用工程师来完成设计任务。
从角色上分,大致可以分为如下几个:
1)母程序,即main函数。
init==>create==>start==>idle
main函数的静态代码,主要完成初始化工作,以及各种对象实体的创建工作,在完成初始化和创建工作之后,它需要完成start工作,包括各种对象实体的start,最后是scheduler的start。在完成了start工作之后,main进程转入IDLE。

2)task函数。
task函数的静态代码,主要完成本任务所需要完成的工作。
task对应于rear stage中的常规任务。它有几个特征:
第一,紧急度不高。所以优先级不高。
第二,可以不依赖于event。所以它是系统的默认需要完成的工作。
第三,可以使用无限循环。

3)taskletCallback。
tasklet的Callback,主要完成ISR所需要完成的工作。
tasklet对应于front stage产生的event。
由于tasklet对应于特定的event,所以它是依赖于event的。一个event对应一个tasklet。
它有几个特征:
第一,紧急度高。所以优先级高。
第二,依赖event。所以它是典型的event driven task。
第三,不能使用无限循环。所以如果有需要持续处理的任务,要在tasklet中创建task,来继续处理。

基于RTOS的程序设计中,除了能够直接使用BSP API之外,还能够使用RTOS API。
RTOS API的使用,是更推荐的做法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值