Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

The SPARK Real-Time Kernel


May99: The SPARK Real-Time Kernel

Anatoly is a senior software engineer for Auto Image ID and can be contacted at [email protected].


In many real-world scenarios, embedded-system developers must squeeze powerful functionality into limited memory spaces. To do this, they often have to turn away from true multitasking operating systems. This is the situation we faced at Auto Image ID (http://www .autoimageid.com/), the company I work for, when building a fixed-position video bar-code scanner. To address the problem, I ended up writing a real-time kernel called SPARK, short for "Small Portable Adjustable Real-time Kernel." We subsequently spun off a startup company called Real-Time Microsystems (http://www.realtimemicrosystems.com/) to continue SPARK development. In this article, I'll present SPARK by describing how I used it in the implementation of the Auto Image ID ID4100 video bar-code scanner; see Figure 1.

SPARK is a royalty-free, fast, tiny, portable real-time kernel. Leaving interrupt handling to you, SPARK doesn't include anything that real-time embedded systems developers would consider redundant or platform specific. On the other hand, SPARK's event- and state-oriented nature and table-driven, modular architecture provide flexibility and reusability.

The SPARK kernel supports five main service functions:

  • sparkExec() lets an application start up a new task. The task ID is passed to the function in the first input parameter. The third input parameter of the function is a pointer to the null-terminated string of characters, which SPARK passes on to the task as the task's command line of arguments. (The second parameter of the sparkExec() function and the only parameter of the sparkKill() function specifies an exit code for the completed task. SPARK does not do any processing of the task exit codes. However, the exit code can be checked by the sparkPostProcess() hook if any specific action on the particular exit code of the particular task must be done.) SPARK provides nonpreemptive multitasking. When a new task is initiated, the currently running task gets closed. When no task is running, the system goes to the SPARK idle loop in which it does nothing but wait for any event to occur.
  • sparkKill() kills the currently running task and puts the system into the SPARK idle loop.

  • sparkSetAppState() sets a new state of the application. The state ID is passed to this function as an input parameter.

  • sparkGetAppState() returns the ID of the current state of the application.

  • sparkPostEvent() lets an application trigger a reaction on the particular event. The event ID is passed to the function in the first input parameter. The second input parameter of this function is a pointer to the data that can be passed on to the event-processing function called an "event function." (In the sparkPostEvent() function prototype, the pointer to the data is defined as a pointer to void. It's up to you to define the actual structure of data being passed to the particular event function.)

The logic of the sparkPostEvent() function is simple. First, it tries to find and call the event function responsible for processing the event at the current state of the application. If the event function is not provided, then the check is made whether or not the event is system wide. If it is, the event function responsible for processing the event at any state of the application is called; otherwise, the event is ignored and control is passed back to the interrupted task. When found and called, the event function may decide either to start a new task by calling sparkExec(), kill the current task by calling sparkKill(), or do something (or nothing), and return to the current task.

The tasks, states, and events are referred to using their unique respective IDs. This adds flexibility to the system. All associations between the tasks, states, events, and called functions are made at the application-configuration level and can easily be reviewed or changed. Moreover, SPARK treats the task and state IDs as direct indices to the application-specific table of tasks and table of states, respectively, so that there is no time wasted in looking up the task function to start when sparkExec() is called or the list of events and their corresponding event functions in the table of states when sparkPostEvent() is called. It makes the system reaction to the events and task switching extremely fast.

SPARK also provides a set of callback functions, called "SPARK hooks," which are called by the kernel when the state of the system changes. By overriding the hooks, an application can change the default behavior of the system and take full control over transitions of the system and application states.

Some of the hooks are:

  • sparkStartup(), called on system start-up. You can override the hook to do any application-specific initializations here.
  • sparkReadyToRun(), called to get the application confirmation before starting a new task. The function should return 1 if it's okay to close the currently running task and start the new one; otherwise, the function returns 0. The default of this hook does nothing and returns 1. You can override the hook to perform application-specific checking prior to allowing SPARK to start a new task.

  • sparkPreProcess(), called just before a new task is started. You can override the hook to do any application-specific initializations before the new task is started.

  • sparkPostProcess(), called just after the new task is finished. You can override the hook to perform any application-specific actions after the task is finished.

  • sparkIdle(), called between idle loops. You can override the hook to perform application-specific actions when the system is idle.

  • sparkSetIntSystemStatus(), called whenever the interrupt system should be turned on or off. You should override the hook to provide a proper manipulation with the platform-specific interrupt system.

  • sparkGetIntSystemStatus(), called whenever the current status of the interrupt system is inquired. You should override the hook to provide a proper manipulation with the platform-specific interrupt system.

Developing SPARK-based Applications

When developing SPARK-based applications, the most important thing you need to do is determine the tasks, states, and events of the embedded system and describe them in your application-configuration file using the SPARK configuration macrocommands. You then use SPARKCNF, the SPARK Configuration Tool, to generate the application configuration .c and .h files where the tables used by SPARK are automatically created. The command line for running SPARKCNF on UNIX or Windows host computers is:

sparkcnf.exe -i inp_file -e event_file -o

outp_file

where inp_file is an application-configuration file written using the SPARK configuration macrocommands; event_ file is the name of the file (normally, with the extension .h) that contains definitions of all event IDs specified in the inp_file; and outp_file is the root name of the generated .c and .h files.

Listing One is excerpted from the ID4100 fixed-position video bar-code scanner's application-configuration file. Although it's only a fragment of the real configuration file, it demonstrates the most important elements of any SPARK-based application.

In the TASKS section, each task is described by its ID followed by the task function name. For example, the task that provides the scanner main menu to users has the ID TASK_MAIN_MENU, and its function name is taskMainMenu.

In the STATES section, each application state is described by its ID and, optionally, by the immediately following EVENTS section. In the EVENTS section, every event that should be processed at the specified state of the application is described by the event ID followed by the event-function name. Similarly, in the SYSTEM_WIDE_ EVENTS section, the events that may apply to any state of the application are described. For example, if the video input request event (SYSEVENT_VIDEOINP_REQUEST) is posted when the system is idle (application state STATE_ IDLE), the function evRunScanner() gets called. If the same event is posted when the system is already in the process of getting video data (application state STATE_GET_VIDEO) or decoding data (application state STATE_DECODE), the function evRerunScanner() gets called, and so forth.

SPARKCNF automatically generates the actual values of the task and state IDs in the outp_file.h file. However, you need to provide the values of the event IDs in event_file. (The event IDs can also be generated automatically, and event_file is optional. I found it more helpful for complex embedded systems such as the video bar-code scanner to have a separate header file that properly enumerates the system events. This way, I can include this file in the source code of any reusable and separately compilable level of the application, BIOS for example, and post proper events from there when necessary without having to rely on generated event IDs.) Listing Two shows the definitions of event IDs used by the ID4100 video bar-code scanner. There are several event IDs that are defined internally by SPARK in spark.h (EVENT_ POWER_UP is one of them). The application-specific event IDs begin from the value LAST_SPARK_SYSTEM_EVENT + 1.

For each task described in the configuration file, you should provide the task function, which must have the prototype:

typedef EXIT_CODE (*TASK)(char

* cmd_line);

where cmd_line is a pointer to the null-terminated string of parameters passed to the task via sparkExec() call.

For each event described in the configuration file, you should provide the event function, which must have the prototype:

typedef int (*EVENT_FUNC)(void * pPar);

where pPar is a pointer to the data that can be passed to the event function via the sparkPostEvent() call. The event function returns 1 to signal that the event has been processed successfully and the corresponding event function from the SYSTEM_WIDE_EVENTS should not be called (if this event has an entry in the SYSTEM_WIDE_EVENTS section as well); otherwise, the event function should return 0. This behavior lets you provide the default action for certain system-wide events and override the default action or simply adjust it, depending on the state of the application.

To complete an application, you write all other application-specific functions, including interrupt-handling routines. Use the SPARK service functions to set application states, to post events, to start tasks, and so on. If necessary, you can override any SPARK hook to provide proper functionality of your embedded system. Compile the generated configuration .c file along with all of your application-specific modules and overridden SPARK hooks and link them with the SPARK library (which should be built for the platform of your embedded system) and the C run-time library that supports your CPU. This builds your application. Use the debugger and other tools available to debug your application and, perhaps, burn the ROM to make the application available for use in your embedded system.

A Closer Look at the Bar-Code Scanner Application

To better illustrate how a real-time application can be built with SPARK, I'll take a closer look at the bar-code scanner application.

When the system starts up, SPARK fires the EVENT_POWER_UP event. This is described as a system-wide event in the configuration file (see Listing One) and is processed by evPowerUp() (see Listing Three). This function sets an application state STATE_POWER_UP, does all necessary ID4100 scanner initializations by calling ID4100SystemInit(), and puts the system into an idle loop by calling sparkKill(0).

In between the idle loops, the spark-Idle() hook is called. The bar-code scanner application overrides this hook to set an application state to STATE_IDLE.

When an object containing a bar-code is positioned at the field of view of the scanner, the scanner's hardware generates a trigger interrupt. The trigger interrupt handling routine then posts the SYS-EVENT_ VIDEOINP_REQUEST event by calling sparkPostEvent(SYSEVENT_VID- EOINP_REQUEST, (void *) 0). At the STATE_IDLE application state, this event is processed by evRunScanner(), which starts the video input and decoding process by calling sparkExec(TASK_RUN_ SCANNER, 0, (char *) 0). While getting the video data, the application is in the STATE_GET_VIDEO state. When in the decoding process, the application state is set to STATE_DECODE. Upon return from the decoding task, the system comes back to the idle loop.

If the SYSEVENT_VIDEOINP_REQUEST event occurs while the application is in the STATE_GET_VIDEO or the STATE_DECODE state (meaning a real-time error situation), the event is processed by evRerunScanner(), which sends a real-time error message to the user prior to initiating the new video input and decoding process.

The decoding process may also be interrupted by the SYSEVENT_READ_ TIMEOUT event generated by the system timer when the time allowed for decoding expires. The event function evScannerRead-TimeOut(), called when the event occurs, sends a "No Read" message to the user and kills the decoding task, putting the system back into the idle loop.

Of course, this is only a fractional part of the real bar-code scanner functionality. The SYSEVENT_NOWAIT_IN system-wide event is fired by the serial port interrupt-handling routine when it receives the data not expected by the application. The SYS-EVENT_BREAK event is triggered when a task should be stopped due to the user request. This allows the bar-code scanner to switch back and forth from the online running mode to the offline menu mode.

DDJ

Listing One

TASKS
{
        TASK_MAIN_MENU: taskMainMenu
        TASK_HELP: taskHelp
        TASK_LIST_DECODERS: taskListDecoders
        TASK_ENABLE_DECODER: taskEnableDecoder
        TASK_DISABLE_DECODER: taskDisableDecoder
        TASK_DECODE: taskDecode
        TASK_RUN_SCANNER: taskRunScanner
} 
STATES
{
        STATE_POWER_UP
        STATE_IDLE
        EVENTS
        {
                SYSEVENT_VIDEOINP_REQUEST: evRunScanner
        }
        STATE_GET_VIDEO
        EVENTS
        {
                SYSEVENT_VIDEOINP_REQUEST: evRerunScanner
        }
        STATE_DECODE
        EVENTS
        {
                SYSEVENT_VIDEOINP_REQUEST: evRerunScanner
                SYSEVENT_READ_TIMEOUT: evScannerReadTimeOut
        }
        STATE_MMENU
}
SYSTEM_WIDE_EVENTS
{
        EVENT_POWER_UP: evPowerUp
        SYSEVENT_NOWAIT_IN: evNoWaitIn
        SYSEVENT_BREAK: evBreak
}

Back to Article

Listing Two

#ifndef SYSEVENT_H
#define SYSEVENT_H

#include "spark.h"
#define FIRST_SYSEVENT        LAST_SPARK_SYSTEM_EVENT + 1
enum {
        SYSEVENT_NOWAIT_IN = FIRST_SYSEVENT,
        SYSEVENT_BREAK,
        SYSEVENT_VIDEOINP_REQUEST,
        SYSEVENT_READ_TIMEOUT, 
        LAST_SYSEVENT = SYSEVENT_READ_TIMEOUT
};
#endif /* SYSEVENT_H */

Back to Article

Listing Three

#include "spark.h"
#include "sparkhks.h"
#include "id4100.h"
/*****************************************************************\
 EVENT_POWER_UP event-function.
\*****************************************************************/
int
evPowerUp(void * par)
{
        sparkSetAppState(STATE_POWER_UP);
        ID4100SystemInit();
        sparkKill(0);
        return 1;
}
/*****************************************************************\
   This is an override of the sparkIdle() SPARK hook.
\*****************************************************************/
void
sparkIdle(int first_call)
{
        if (first_call)
        {
                sparkSetAppState(STATE_IDLE);
        }
}
/*****************************************************************\
   This function handles the "Video Input Request" event when the
   system is in the idle loop.
\*****************************************************************/
int
evRunScanner(void * par)
{
        sparkExec(TASK_RUN_SCANNER, 0, (char *) 0);
        return 1;
}
EXIT_CODE
taskRunScanner(char * cmd_line)
{
        sparkSetAppState(STATE_GET_VIDEO);
        ID4100GetVideo();
        sparkExec(TASK_DECODE, 0, (char *) 0);
        return 0;
}
/*****************************************************************\
   This function handles the "Video Input Request" event when the
   system is busy decoding the previously entered data.
\*****************************************************************/
int
evRerunScanner(void * par)
{
        ID4100RTEMsg();
        sparkExec(TASK_RUN_SCANNER, 0, (char *) 0);
        return 1;
}
/*****************************************************************\
   This function handles the "Read Time Out" event when the
   system is decoding the previously entered data.
\*****************************************************************/
int
evScannerReadTimeOut(void * par)
{
        ID4100NoReadMsg();
        sparkKill(ERR_READ_TIMEOUT);
        return 1;
}

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.