Tutorial: Building a Mainloop Application (Step 4)

Implementing Application Functionality

As our example application is still very simple, we can add all the code to the prepared class Application in src/Application.h (in a real-world application, the application layer code is certainly distributed across many different classes - see the VendingMachine example project that is shipped with the redBlocks Eval Package). Insert the code below, so it looks like this:

#ifndef APPLICATION_H_
#define APPLICATION_H_

#include "Platform.h"
#include <Core/Base/TGlobalPtrBase.h>
#include <Core/Log/Log.h>

/**
 * Application class
 */
class Application: public redBlocks::Core::TGlobalPtrBase<Application>
{
  public:
    /**
     * Constructor. Creates and initializes the application modules and associates them with each other.
     */
    Application() :
      mFlashTimer( this ),
      mCommandEvent( this ),
      mLedOn( true ),
      mFlashing( true )
    {
      // set initial Led state
      Platform::Led::setOutput( mLedOn );

      // start the timer for periodic callbacks
      mFlashTimer.startPeriodic( Platform::OS::MilliSec<500>::value );

      // callbacks are disabled by default and need to be enabled manually
      Platform::TerminalComInterface::enableRecvDataCallback();
    }

    /**
     * The application's mainloop: Sleeps until events are pending and processes them.
     */
    void run()
    {
      while ( true )
      {
        Platform::OS::processEvents();
        Platform::AwakeLed::setOutput( false );
        Platform::enterSleepMode();
        Platform::AwakeLed::setOutput( true );
      }
    }

    /**
     * Must be called each time, when a byte is received via the terminal
     * communication interface
     */
    void onCommandData()
    {
      while ( 0 != Platform::TerminalComInterface::getRecvData( &mCommandByte, 1 ) )
      {
        mCommandEvent.schedule();
      }
    }

  private:
    /** Convenience definition for our class */
    typedef Application ThisType;

    /**
     * This method is called each time mFlashTimer fires: It toggles the Led.
     */
    static void isrTimerCallback( ThisType* t )
    {
      t->mLedOn = !t->mLedOn;
      Platform::Led::setOutput( t->mLedOn );
    }

    /**
     * Event handler callback that is invoked when mCommandEvent is fired.
     */
    static void commandEventCallback( ThisType* t, u8 callCnt )
    {
      switch ( t->mCommandByte )
      {
        case '0':
        case '1':
          if ( t->mFlashing )
          {
            t->mFlashing = false;
            t->mFlashTimer.stop();
          }
          t->mLedOn = '1' == t->mCommandByte;
          Platform::Led::setOutput( t->mLedOn );
          RB_LOG_DEBUG( "Switching LED " << ( t->mLedOn ? "on" : "off" ) );
          break;
        case 'f':
          if ( !t->mFlashing )
          {
            t->mFlashing = true;
            t->mFlashTimer.startPeriodic( Platform::OS::MilliSec<500>::value );
          }
          RB_LOG_DEBUG( "Starting to flash LED" );
          break;
        default:
          RB_LOG_DEBUG( "Received invalid command" );
          break;
      }
    }

    /**
     * the timer that is used to flash the LED: we use an interrupt based timer,
     * in order to get constant flash intervals
     */
    Platform::OS::TIsrTimer<ThisType, &isrTimerCallback> mFlashTimer;

    /**
     * the event that is scheduled, when a new command byte has been received
     */
    Platform::OS::TEvent<ThisType, &commandEventCallback> mCommandEvent;

    /**
     * the current led state
     */
    bool mLedOn;
    /**
     * true while the LED is flashing
     */
    bool mFlashing;
    /**
     * the last received command
     */
    u8 mCommandByte;

};

#endif // APPLICATION_H_

The current state of our flashing Led is maintained in a boolean member variable mLedOn. It is initialized to false in the constructor. Upon each call of the method isrTimerCallback() it is changed and the corresponding output is updated

t->mLedOn = !t->mLedOn;
Platform::Led::setOutput(t->mLedOn);

The method isrTimerCallback() is invoked periodically by the IsrTimer mFlashTimer. It is associated with isrTimerCallback()

Platform::OS::TIsrTimer<ThisType, &isrTimerCallback> mFlashTimer;

and started to fire periodically:

mFlashTimer.startPeriodic(Platform::OS::MilliSec<500>::value);

The timer intervals need to be specified in system ticks. In order to be platform independent, the application determines the number of system ticks that correspond with the absolute time values via the class template MilliSec that is provided by the redBlocks Mainloop Scheduler.

Here is why: Depending on the platform's system tick interval, the absolute time value corresponds to a different number of ticks. With this approach, a clean compile time error will occur, if the desired time interval cannot be represented with the system tick interval (see documentation on TMainLoopScheduler for further details), which is much better than hoping that the error will be discovered during integration testing...

The method onCommandData() must be called when a receive interrupt occurs on the TerminalComInterface. It reads all data bytes from the hardware receive FIFO and (for simplicity reasons) stores only the last byte as an LED switching command in mCommandByte. After that, it schedules the event mCommandEvent.

mCommandEvent.schedule();

This event is a member of class Application and is associated with its callback method during template instantiation:

Platform::OS::TEvent<ThisType, &commandEventCallback> mCommandEvent;

By scheduling an event from within the interrupt context, we get around potential race conditions that might occur when multiple interrupts happen at the same time. Because all event handler methods are sequentially called (in the order they were scheduled) from within the method

Platform::OS::processEvents();

that is called from Application::run() (see Doxygen documentation of TMainLoopScheduler for further details).

In the event handler method we interpret the single-character-command:

  • 0 switches the LED off
  • 1 switches the LED permanently on
  • f starts the timer that makes the LED flash

In order to demonstrate how the redBlocks Mainloop Scheduler supports tickless operation and sends the processor to sleep mode during idle periods, the other LED (called AwakeLed) is switched on in the mainloop as long as the processor is not in sleep mode.

while (true)
{
  Platform::OS::processEvents();
  Platform::AwakeLed::setOutput(false);
  Platform::enterSleepMode();
  Platform::AwakeLed::setOutput(true);
}

With the application code in place there is only one thing left to do: We need to associate the receive callback of the TerminalComInterface with the method Application::onCommandData(). This is done by replacing this definition in file src/PlatformCallbacks.h (remember: we also need to change the pre-define name LogComInterface to TerminalComInterface here):

RB_CONNECT_ISR_CBK(Platform::LogComInterface, 
  Platform::LogComInterface::CBK_ON_RECV_DATA, 
  /* Empty */ )

with the following lines:

#include <Application.h>
RB_CONNECT_ISR_CBK(Platform::TerminalComInterface,
  Platform::TerminalComInterface::CBK_ON_RECV_DATA,
  Application::getInstanceRef().onCommandData())

Now everything is in place to start the example application in the simulation environment. Move on to the next step for that.