这是传统的占先式多任务方案。它完整的使用了 RTOS 服务而没有考虑内存和进程管理。包括一些部分功能简化的独立任务。
系统的每个部分,或者有特别定时要求的部分,创建一个单独的任务。
任务将被阻塞,直到有需要处理的事件发生。事件既可以外部事件(如有键按下),也可以是内部的(如定时器超时)。
优先级将按照任务的定时需求分配,高精度定时的要求分配高优先级。
这个调度将自动进行,无需在源代码中使用特别的知识、结构或命令。给任务分配合适的优先级是程序设计者的责任。
当没有任务执行时将执行空闲任务(idle task)。在空闲任务中可以使处理器休眠节约能源。
调度管理配置为占先方式。内核节拍频率设置为需要时间间隔的最慢值。
![]() |
简单、分段、灵活、可维护的设计以及基本不相互关联。 |
![]() |
处理器自动从一个任务切换到另一个,而不需要源代码中加入其他操作。 |
![]() |
可以在空闲任务中加入节能指令(休眠)减少功率的消耗,但是系统节拍中断经常不必要的唤醒 CPU 会造成浪费。 |
![]() |
核心功能使用了处理器资源,范围和内核节拍频率相关。 |
![]() |
这个方案需要许多任务,每个任务有自己的堆栈,当发生事件时一些任务还需要队列。这个方案需要很多 RAM。 |
![]() |
频繁的在相同优先级的任务间切换将浪费处理器时间。 |
在 RAM 和 处理器性能足够时这是一个好的方案。系统划分为任务,并根据需要分配任务的优先级。
#define CYCLE_RATE_MS 10 #define MAX_COMMS_DELAY 2 void PlantControlTask( void *pvParameters ) { portTickType xLastWakeTime; DataType Data1, Data2; InitialiseTheQueue(); // A xLastWakeTime = xTaskGetTickCount(); // B for( ;; ) { // C vTaskDelayUntil( &xLastWakeTime, CYCLE_RATE_MS ); // Request data from the sensors. TransmitRequest(); // D if( xQueueReceive( xFieldBusQueue, &Data1, MAX_COMMS_DELAY ) ) { // E if( xQueueReceive( xFieldBusQueue, &Data2, MAX_COMMS_DELAY ) ) { PerformControlAlgorithm(); TransmitResults(); } } } // Will never get here! }上面代码中标签的说明:
xLastWakeTime 初始化,这个变量用于 vTaskDelayUntil() API 函数,决定控制函数的调用频率。
这个函数作为独立的任务,因此从不退出。
vTaskDelayUntil() 告诉内核这个任务将在保存在 xLastWakeTime 的时间后精确的 10ms 执行。到达这个时间后设备控制任务将进行任务阻塞。因为它是最高优先级任务,因此在时间到达后它将再次运行。它会抢占其他低优先级任务。
在数据请求和数据到达之间的时间间隔是有限的。从现场总线接收到的数据在中断服务程序中存放到 xFieldBusQueue,设备控制任务可以在队列中进行阻塞调用,等待数据到达。和以前一样,因为它是系统中最高优先级任务,所以数据到达后它将立即执行。
类似 'D', 等待第二个传感器的数据。
void WebServerTask( void *pvParameters ) { DataTypeA Data; for( ;; ) { // Block until data arrives. xEthernetQueue is filled by the // Ethernet interrupt service routine. if( xQueueReceive( xEthernetQueue, &Data, MAX_DELAY ) ) { ProcessHTTPData( Data ); } } }
void RS232Task( void *pvParameters ) { DataTypeB Data; for( ;; ) { // Block until data arrives. xRS232Queue is filled by the // RS232 interrupt service routine. if( xQueueReceive( xRS232Queue, &Data, MAX_DELAY ) ) { ProcessSerialCharacters( Data ); } } }
设置的定时周期比指定的门限高很多。这是因为发出请求后可能不能马上进行处理 - 可能被设备控制任务抢占。
如果把所有空闲的系统时间都用于这个任务,那么可以设置它为优先级最低的任务,这样完全可以删除 vTaskDelayUntil() 函数。键盘扫描函数将连续执行直到被其他高优先级任务阻塞 - 相当于空闲任务(idle task)。
下一节 >>> 方案 #3: 减少 RAM 使用率 翻译: 邵子扬
#define DELAY_PERIOD 4
void KeyScanTask( void *pvParmeters )
{
char Key;
portTickType xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, DELAY_PERIOD );
// Scan the keyboard.
if( KeyPressed( &Key ) )
{
UpdateDisplay( Key );
}
}
}
LED 任务
这是所有任务中最简单的一个。
#define DELAY_PERIOD 1000
void LEDTask( void *pvParmeters )
{
portTickType xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, DELAY_PERIOD );
// Flash the appropriate LED.
if( SystemIsHealthy() )
{
FlashLED( GREEN );
}
else
{
FlashLED( RED );
}
}
}
EMail: shaoziyang@126.com
Blog: http://blog.ednchina.com/shaoziyang
2008年10月