A more complex program

Now that we know, how the basic structure of a Circle application looks like, we want to add some more often used classes and thus functionality. The following program is based on the sample/04-timer. You will need a HDMI display or a serial terminal, connected to your Raspberry Pi, to try it out.

First create a new subdirectory below app/ and copy the files main.cpp and Makefile from the previously discussed program. These files remain unchanged. Only our CKernel class will be modified and extended. The class definition looks now as follows:

kernel.h
#ifndef _kernel_h
#define _kernel_h

#include <circle/actled.h>
#include <circle/koptions.h>
#include <circle/devicenameservice.h>
#include <circle/screen.h>
#include <circle/serial.h>
#include <circle/exceptionhandler.h>
#include <circle/interrupt.h>
#include <circle/timer.h>
#include <circle/logger.h>
#include <circle/types.h>

enum TShutdownMode
{
        ShutdownNone,
        ShutdownHalt,
        ShutdownReboot
};

class CKernel
{
public:
        CKernel (void);
        ~CKernel (void);

        boolean Initialize (void);

        TShutdownMode Run (void);

private:
        static void TimerHandler (TKernelTimerHandle hTimer,
                                  void *pParam, void *pContext);

private:
        CActLED                 m_ActLED;
        CKernelOptions          m_Options;
        CDeviceNameService      m_DeviceNameService;
        CScreenDevice           m_Screen;
        CSerialDevice           m_Serial;
        CExceptionHandler       m_ExceptionHandler;
        CInterruptSystem        m_Interrupt;
        CTimer                  m_Timer;
        CLogger                 m_Logger;
};

#endif

We add the following classes as member objects to CKernel:

Class

Purpose

CKernelOptions

Provides command line options from cmdline.txt

CDeviceNameService

Maps device names to a pointer to the device object

CScreenDevice

Access to the HDMI display (screen)

CSerialDevice

Access to the serial interface (UART)

CExceptionHandler

Reports system faults (abort exceptions) for debugging

CInterruptSystem

Interrupt (IRQ and FIQ) handling

CTimer

Provides several time services

CLogger

System logging facility

Furthermore a private static TimerHandler() callback function is added, which is used to show the function of kernel timers, implemented by the CTimer class. The file kernel.cpp has been updated like this:

kernel.cpp
#include "kernel.h"

static const char FromKernel[] = "kernel";

CKernel::CKernel (void)
:       m_Screen (m_Options.GetWidth (), m_Options.GetHeight ()),
        m_Timer (&m_Interrupt),
        m_Logger (m_Options.GetLogLevel (), &m_Timer)
{
        m_ActLED.Blink (5);
}

CKernel::~CKernel (void)
{
}

In the constructor of CKernel the CScreenDevice member is explicitly initialized using the display width and height from the configuration file cmdline.txt on the SD card. The display resolution can be selected in the first line of this file for example like this: width=640 height=480. The CTimer member uses interrupts (IRQ) to implement a system tick of 100 Hz and hence gets a pointer to the CInterruptSystem member object.

Note

All Circle options for cmdline.txt are listed in doc/cmdline.txt. All options must be specified in the first line, separated with a space.

The system logging facility CLogger is initialized with the wanted logging level and a pointer to the timer, so that it can log the system time. The logging level can be set in cmdline.txt by adding loglevel=N, where N is a number between 0 (panic) and 4 (debug, default). Only the log messages with a severity of smaller or equal then this value will be logged.

kernel.cpp (continued)
boolean CKernel::Initialize (void)
{
        boolean bOK = TRUE;

        if (bOK)
        {
                bOK = m_Screen.Initialize ();
        }

        if (bOK)
        {
                bOK = m_Serial.Initialize (115200);
        }

        if (bOK)
        {
                CDevice *pTarget = m_DeviceNameService.GetDevice (
                                        m_Options.GetLogDevice (), FALSE);
                if (pTarget == 0)
                {
                        pTarget = &m_Screen;
                }

                bOK = m_Logger.Initialize (pTarget);
        }

        if (bOK)
        {
                bOK = m_Interrupt.Initialize ();
        }

        if (bOK)
        {
                bOK = m_Timer.Initialize ();
        }

        return bOK;
}

In the Initialize() method the second step of the class member initialization is done. The call to m_Logger.Initialize() gets a pointer to the logging device as a parameter, which is &m_Screen by default. If you add logdev=ttyS1 to cmdline.txt you can read the messages on a connected serial terminal. The mapping from device name to device object pointer takes place in m_DeviceNameService.GetDevice(), which returns 0, if the device name is not found.

Important

The order of initialization is important. The same applies to the constructor and the order of member objects in the class definition in kernel.h.

kernel.cpp (continued)
TShutdownMode CKernel::Run (void)
{
        m_Logger.Write (FromKernel, LogNotice,
                        "An exception will occur after 15 seconds from now");

        m_Timer.StartKernelTimer (15 * HZ, TimerHandler);

        unsigned nTime = m_Timer.GetTime ();
        while (1)
        {
                while (nTime == m_Timer.GetTime ())
                {
                        // just wait a second
                }

                nTime = m_Timer.GetTime ();

                m_Logger.Write (FromKernel, LogNotice, "Time is %u", nTime);
        }

        return ShutdownHalt;
}

m_Logger.Write() writes a message of the given severity to the system log. FromKernel names the source of the message (see definition above). m_Timer.StartKernelTimer() triggers, that the TimerHandler() gets called after 15 seconds. m_Timer.GetTime() returns the current local system time in seconds since 1970-01-01 00:00:00. Because we do not use a real-time clock, the actual time is equal to the uptime of the system. The program generates a log message every second on the screen or serial terminal, if it is selected as logging device.

kernel.cpp (continued)
void CKernel::TimerHandler (TKernelTimerHandle hTimer,
                            void *pParam, void *pContext)
{
        void (*pInvalid) (void) = (void (*) (void)) 0x500000;

        (*pInvalid) ();
}

After 15 seconds the TimerHandler() is called and generates a “Prefetch abort” exception by jumping to the address 0x500000, because the memory region at this address is marked as “not executable”.

The Appendix Analyzing exceptions explains using this program, how the information can be analyzed, which is displayed, when an abort exception occurs.