主页

方案 #1
为什么使用 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();
        }
    }
}

 

 

人机接口函数

包括了键盘、LCD、RS232 通信和嵌入式 WEB 服务器。

下面伪代码表示了一个简单的无限循环来控制这些接口。

int main( void )
{
    Initialise();
    
    for( ;; )
    {
        ScanKeypad();
        UpdateLCD();
        ProcessRS232Characters();
        ProcessHTTPRequests();   
    }

    // Should never get here.
    return 0;
}

这里做出了两个假定:首先通信数据是由中断服务程序缓存的,所以不需要轮询外设;其次,每个函数的执行时间足够快,可以满足最大定时的要求。


调用设备控制函数

控制函数的长度意味着它不能简单的在 10ms 定时中断里调用。

添加它到无限循环需要引入一些临时控制。例如 ... :

// 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;
}
... 但是这不是一个可以接受的方案:

 


另外一种结构

两个因素确定了简单循环结构的限制。
  1. 每个函数调用的深度

    允许每个函数执行很长时间。这可以防止分割每个函数成为若干个状态,每次调用只有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: 一个完整的占先式系统


翻译:   邵子扬
EMail:   shaoziyang@126.com
Blog:    http://blog.ednchina.com/shaoziyang
2008年10月

 

Site Meter