解决方案 #1
为什么使用 RTOS 内核?
<<< | >>>
另请参阅常见问题项目“为什么使用 RTOS?”。
概要
许多应用程序可以在不使用 RTOS 内核的情况下生成,本页介绍了可能采取的方法。
尽管在这种情况下,应用程序可能过于复杂,以至于无法采用这种方法,但本页不仅着重介绍
潜在的问题,而且为以下基于 RTOS 的软件设计提供对比。
实现
此解决方案使用传统的无限循环方法,即应用程序的每个组件都由一个
执行到完成的函数表示。
理想情况下,将使用硬件定时器调度时间关键型设备控制函数。 然而,由于必须等待数据到达并
执行的复杂计算,因此控制函数不适于在中断服务程序内执行。
操作理念
在无限循环内调用组件的频率和顺序可以加以修改,以引入一些
优先级。 下面的示例中提供了几种此类排序替代方案。
调度器配置
未使用 RTOS 调度器。
评估
 |
代码大小较小。 |
 |
不依赖第三方源代码。 |
 |
没有 RTOS RAM、ROM 或处理开销。 |
 |
难以满足复杂的定时要求。 |
 |
如果不大幅增加复杂性,就不能很好地扩展。 |
 |
由于不同函数之间的相互依存关系,很难评估或维持定时。 |
结论
简单的循环方法非常适合小型应用和具有灵活定时要求的应用 - 但
如果扩展到更大的系统,可能会变得复杂、难以分析和难以维护。
示例
此示例是先前介绍的假设应用程序的部分实现。
设备控制函数
控制函数可由以下伪代码表示:
void PlantControlCycle( void )
{
TransmitRequest();
WaitForFirstSensorResponse();
if( Got data from first sensor )
{
WaitForSecondSensorResponse();
if( Got data from second sensor )
{
PerformControlAlgorithm();
TransmitResults();
}
}
}
人机界面函数
这包括键盘、液晶屏、RS232 通信和嵌入式 Web 服务器。
以下伪代码表示用于控制这些接口的简单无限循环结构。
int main( void )
{
Initialise();
for( ;; )
{
ScanKeypad();
UpdateLCD();
ProcessRS232Characters();
ProcessHTTPRequests();
}
// Should never get here.
return 0;
}
这假设了
两件事: 首先,通信 IO 被中断服务程序缓冲,因此外围设备不需要
轮询。 其次,
循环内单个函数调用执行速度足够快,可以满足所有最大定时要求。
调度设备控制函数
控制函数的长度意味着不能简单地从 10 毫秒定时器中断调用它。
将它添加到无限循环中需要引入一些时间控制。 例如……:
// Flag used to mark the time at which a
// control cycle should start (mutual exclusion
// issues being ignored for this example).
int TimerExpired;
// Service routine for a timer interrupt. This
// is configured to execute every 10ms.
void TimerInterrupt( void )
{
TimerExpired = true;
}
// Main() still contains the infinite loop -
// within which a call to the plant control
// function has been added.
int main( void )
{
Initialise();
for( ;; )
{
// Spin until it is time for the next
// cycle.
if( TimerExpired )
{
PlantControlCycle();
TimerExpired = false;
ScanKeypad();
UpdateLCD();
// The LEDs could use a count of
// the number of interrupts, or a
// different timer.
ProcessLEDs();
// Comms buffers must be large
// enough to hold 10ms worth of
// data.
ProcessRS232Characters();
ProcessHTTPRequests();
}
// The processor can be put to sleep
// here provided it is woken by any
// interrupt.
}
// Should never get here.
return 0;
}
…但这不是一个可接受的解决方案:
- 现场总线上的延迟或故障
会导致设备控制函数的执行时间增加。接口函数的定时要求
很有可能被违反。
- 每个周期执行所有函数也可能导致违反控制周期定时。
- 执行时间的抖动可能导致错过周期。 例如,
当没有收到 HTTP 请求时,ProcessHTTPRequests() 的执行时间可以忽略不计,但
当提供页面时,执行时间相当长。
- 它不是很容易维护 - 它依赖于在最大时间内执行的每个函数。
- 通信缓冲区每个周期仅维护一次,因此其长度必须大于
其他必要长度。
替代结构
两个可确定因素限制了迄今为止描述的简单循环结构的适用性。
- 每个函数调用的长度
允许每个函数完全执行需要很长时间。 这可以通过将每个函数拆分为
多个状态来防止。 每次调用仅执行一个状态。 以控制函数为示例:
// Define the states for the control cycle function.
typdef enum eCONTROL_STATES
{
eStart, // Start new cycle.
eWait1, // Wait for the first sensor response.
eWait2 // Wait for the second sensor response.
} eControlStates;
void PlantControlCycle( void )
{
static eControlState eState = eStart;
switch( eState )
{
case eStart :
TransmitRequest();
eState = eWait1;
break;
case eWait1;
if( Got data from first sensor )
{
eState = eWait2;
}
// How are time outs to be handled?
break;
case eWait2;
if( Got data from first sensor )
{
PerformControlAlgorithm();
TransmitResults();
eState = eStart;
}
// How are time outs to be handled?
break;
}
}
此函数现在在结构上更加复杂,并引入了进一步的调度问题。 随着额外状态的添加,代码本身将
变得难以理解 - 例如 处理超时和错误条件。
- 定时器的粒度
更短的定时器间隔将提供更大的灵活性。
将控制函数实现为
状态机(这样做会使每次调用更短)可允许从定时器中断调用它。 此
定时器间隔必须足够短,确保以满足其计时要求的频率调用函数
。 此选项充满了定时和维护问题。
或者,可以修改无限循环解决方案以调用每个循环上的不同函数 -
更频繁地调用高优先级控制函数:
int main( void )
{
int Counter = -1;
Initialise();
// Each function is implemented as a state
// machine so is guaranteed to execute
// quickly - but must be called often.
// Note the timer frequency has been raised.
for( ;; )
{
if( TimerExpired )
{
Counter++;
switch( Counter )
{
case 0 : ControlCycle();
ScanKeypad();
break;
case 1 : UpdateLCD();
break;
case 2 : ControlCycle();
ProcessRS232Characters();
break;
case 3 : ProcessHTTPRequests();
// Go back to start
Counter = -1;
break;
}
TimerExpired = false;
}
}
// Should never get here.
return 0;
}
可通过事件计数器引入更多智能,从而仅在
发生需要服务的事件时调用较低优先级的功能:
for( ;; )
{
if( TimerExpired )
{
Counter++;
// Process the control cycle every other loop.
switch( Counter )
{
case 0 : ControlCycle();
break;
case 1 : Counter = -1;
break;
}
// Process just one of the other functions. Only process
// a function if there is something to do. EventStatus()
// checks for events since the last iteration.
switch( EventStatus() )
{
case EVENT_KEY : ScanKeypad();
UpdateLCD();
break;
case EVENT_232 : ProcessRS232Characters();
break;
case EVENT_TCP : ProcessHTTPRequests();
break;
}
TimerExpired = false;
}
}
以这种方式处理事件将减少浪费的 CPU 周期,但设计仍将
在控制周期执行的频率上表现出抖动。
下一篇 >>> 解决方案 # 2: 完全抢占式系统
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.