解决方案 #4
减少处理器开销
<<< | >>>
注意:自 FreeRTOS V4.0.0 推出以来,这些页面一直没有更新。 V4.0.0 引入了协程的概念,
可为本教程所介绍的设计提供另一种新颖解决方案。 更多信息,请参阅任务和协程文档
。
概要
解决方案 #2 演示了如何通过充分利用 RTOS 功能来生成干净的应用程序。
解决方案 #3 演示了
如何将其应用于 RAM 资源有限的嵌入式计算机。 解决方案 #4 进行了进一步的修改,
目的是减少 RTOS 处理开销。
通过配置内核进行协作调度,创建混合调度算法(既不是完全抢占式,也不是完全协作式),
然后从事件中断服务程序内执行上下文切换。
实现
解决方案 #4 函数任务和优先级
同样,关键设备控制功能通过高优先级任务实现,
但使用协作式调度器需要对其实现进行修改。 之前,
使用 vTaskDelayUntil() API 函数维护定时。 使用抢占式调度器时,
为控制任务分配最高优先级可确保在指定的时间开始执行。 而现在使用的是协作式调度器,
只有在应用程序源代码明确请求时才会发生任务切换,
因此保证的定时功能丢失。
解决方案 #4 使用外设定时器的中断,确保以控制任务所需的确切频率
请求上下文切换。 调度器确保每个请求的上下文切换
都会切换到可运行的最高优先级任务。
键盘扫描函数也在由定时器中断触发的任务中执行,
因此同样需要定期处理器时间。 可以很容易地评估这项任务的定时;
控制函数最坏的情况处理时间由错误情况给出,
如果没有来自网络传感器的数据,则控制函数将超时。 键盘扫描函数的
执行时间基本固定。 因此,我们可以确定,以这种方式链接其功能永远不会导致控制周期频率的抖动,
或者更糟糕的是,
不会导致错过控制周期。
RS232 任务将由 RS232 中断服务程序调度。
LED 功能的灵活定时要求意味着,可以将嵌入式 Web 服务器任务加入到
空闲任务钩子。
如果还不行的话,也可以移到高优先级任务。
操作理念
仅在显式请求上下文切换时,协作调度器才会执行上下文切换。 这很大程度上
降低了 RTOS 产生的处理器开销[除了空闲任务不能再将处理器置于节能模式之外?]。
空闲任务(包括嵌入式 Web 服务器功能)将在
没有任何不必要的内核中断的情况下执行。
来自 RS232 或定时器外设的中断将导致上下文切换,
并且仅在必要时进行切换。 这样,
RS232 任务的优先级仍然高于空闲任务,并且自身仍可被设备控制任务抢占,
以维持优先级较高的系统功能。
调度器配置
调度器配置为协作式操作。 内核滴答仅用于维护实时滴答值。
评估
 |
只创建两个应用程序任务,因此使用的 RAM 少于解决方案 #2。 |
 |
RTOS 上下文切换开销降至最低,尽管空闲任务可能会使用更多的 CPU 周期,
因为这些任务无法再使用节能模式。 |
 |
仅使用 RTOS 功能的子集。 这需要在应用程序源代码层面更多地考虑定时和执行环境,
但仍然可以显著简化设计
(与解决方案 #1 相比)。 |
 |
依赖处理器外围设备, 不可移植。
|
 |
仍需考虑解决方案 #1 中确定的模块之间的分析和相互依赖性问题,
尽管程度要低得多。
|
 |
如果应用程序过大,设计可能无法扩展 |
结论
RTOS 内核的功能只需很少的开销,即使在处理器和内存限制无法实现完全抢占式解决方案的系统上,
也可以实现简化的设计。
示例
此示例是先前介绍的假设应用程序的部分实现。 使用
FreeRTOS API。
高优先级任务
高优先级任务由周期性中断服务程序“给定”的信号量触发:
void vTimerInterrupt( void )
{
// 'Give' the semaphore. This will wake the high priority task.
xSemaphoreGiveFromISR( xTimingSemaphore );
// The high priority task will now be able to execute but as
// the cooperative scheduler is being used it will not start
// to execute until we explicitly cause a context switch.
taskYIELD();
}
请注意,用于从 ISR 内强制上下文切换的语法因移植而异。
不要直接复制此示例,而是检查您使用的移植文档。
高优先级任务包含设备控制和键盘功能。 首先调用 PlantControlCycle(),
以确保定时一致。
void HighPriorityTaskTask( void *pvParameters )
{
// Start by obtaining the semaphore.
xSemaphoreTake( xSemaphore, DONT_BLOCK );
for( ;; )
{
// Another call to take the semaphore will now fail until
// the timer interrupt has called xSemaphoreGiveFromISR().
// We use a very long block time as the timing is controlled
// by the frequency of the timer.
if( xSemaphoreTake( xSemaphore, VERY_LONG_TIME ) == pdTRUE )
{
// We unblocked because the semaphore became available.
// It must be time to execute the control algorithm.
PlantControlCycle();
// Followed by the keyscan.
if( KeyPressed( &Key ) )
{
UpdateDisplay( Key );
}
}
// Now we go back and block again until the next timer interrupt.
}
}
RS232 任务
RS232 任务只在等待数据到达的队列上阻塞。 RS232 中断服务程序必须
将数据发布到队列中(使任务处于就绪态),然后强制执行上下文切换。 这项机制就如同
上面给出的定时器中断伪码。
因此, RS232 任务可以由以下伪代码表示:
void vRS232Task( void *pvParameters )
{
DataType Data;
for( ;; )
{
if( cQueueReceive( xRS232Queue, &Data, MAX_DELAY ) )
{
ProcessRS232Data( Data );
}
}
}
嵌入式 Web 服务器和 LED 功能
其他的系统功能放置在空闲任务钩子中。 这只是由空闲任务
每个循环调用的函数。
void IdleTaskHook( void )
{
static TickType_t LastFlashTime = 0;
ProcessHTTPRequests();
// Check the tick count value to see if it is time to flash the LED
// again.
if( ( xTaskGetTickCount() - LastFlashTime ) > FLASH_RATE )
{
UpdateLED();
// Remember the time now so we know when the next flash is due.
LastFlashTime = xTaskGetTickCount();
}
}
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.