下载 FreeRTOS
 

出色的 RTOS & 嵌入式软件

内核
最新资讯
简化任何设备的身份验证云连接。
利用 CoAP 设计节能型云连接 IoT 解决方案。
11.0.0 版 FreeRTOS 内核简介:
FreeRTOS 路线图和代码贡献流程。
使用 FreeRTOS 实现 OPC-UA over TSN。

解决方案 #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() 的执行时间可以忽略不计,但 当提供页面时,执行时间相当长。

  • 它不是很容易维护 - 它依赖于在最大时间内执行的每个函数。

  • 通信缓冲区每个周期仅维护一次,因此其长度必须大于 其他必要长度。



替代结构

两个可确定因素限制了迄今为止描述的简单循环结构的适用性。
  1. 每个函数调用的长度

    允许每个函数完全执行需要很长时间。 这可以通过将每个函数拆分为 多个状态来防止。 每次调用仅执行一个状态。 以控制函数为示例:

    // 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;           
        }
    }
    
    此函数现在在结构上更加复杂,并引入了进一步的调度问题。 随着额外状态的添加,代码本身将 变得难以理解 - 例如 处理超时和错误条件。

  2. 定时器的粒度

    更短的定时器间隔将提供更大的灵活性。

    将控制函数实现为 状态机(这样做会使每次调用更短)可允许从定时器中断调用它。 此 定时器间隔必须足够短,确保以满足其计时要求的频率调用函数 。 此选项充满了定时和维护问题。

    或者,可以修改无限循环解决方案以调用每个循环上的不同函数 - 更频繁地调用高优先级控制函数:

    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.