基于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的职能。