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:
#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:
#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.
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.
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.
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.