Chapter 3: Design Patterns for Accessing Hardware – Design Patterns for Embedded Systems in C

Chapter 3 Design Patterns for Accessing Hardware

Chapter Outline

  1. Basic Hardware Access Concepts 81

  2. Hardware Proxy Pattern 85

    1. Abstract 85

    2. Problem 86

    3. Pattern Structure 86

    4. Collaboration Roles 86

      1. Hardware Device 86

      2. Hardware Proxy 86

      3. Proxy Client 88

    5. Consequences 88

    6. Implementation Strategies 89

    7. Related Patterns 89

    8. Example 89

  3. Hardware Adapter Pattern 96

    1. Abstract 96

    2. Problem 96

    3. Pattern Structure 96

    4. Collaboration Roles 96

      1. Adapter Client 96

      2. Hardware Adapter 96

      3. Hardware Interface to Client 97

      4. Hardware Device 97

      5. Hardware Proxy 97

    5. Consequences 97

    6. Implementation Strategies 98

    7. Related Patterns 98

    8. Example 98

  4. Mediator Pattern 100

    1. Abstract 100

    2. Problem 100

    3. Pattern Structure 101

    4. Collaboration Roles 101

      1. Collaborator Interface 101

      2. Mediator 101

      3. Specific Collaborator 102

    5. Consequences 102

    6. Implementation Strategies 102

    7. Related Patterns 102

    8. Example 103

  5. Observer Pattern 111

    1. Abstract 111

    2. Problem 112

    3. Pattern Structure 112

    4. Collaboration Roles 112

      1. AbstractClient Interface 112

      2. AbstractSubject Interface 113

      3. ConcreteClient 113

      4. ConcreteSubject 113

      5. Datum 114

      6. NotificationHandle 114

    5. Consequences 114

    6. Implementation Strategies 114

    7. Related Patterns 115

    8. Example 115

  6. Debouncing Pattern 122

    1. Abstract 123

    2. Problem 123

    3. Pattern Structure 123

    4. Collaboration Roles 123

      1. ApplicationClient 124

      2. BouncingDevice 124

      3. Debouncer 124

      4. DebouncingTimer 125

    5. Consequences 125

    6. Implementation Strategies 125

    7. Related Patterns 126

    8. Example 126

  7. Interrupt Pattern 130

    1. Abstract 130

    2. Problem 130

    3. Pattern Structure 131

    4. Collaboration Roles 131

      1. InterruptHandler 131

      2. InterruptVectorTable 132

      3. vectorPtr 132

    5. Consequences 132

    6. Implementation Strategies 134

    7. Related Patterns 135

    8. Example 135

  8. Polling Pattern 138

    1. Abstract 138

    2. Problem 138

    3. Pattern Structure 138

    4. Collaboration Roles 138

      1. ApplicationProcessingElement 139

      2. Device 139

      3. OpportunisticPoller 140

      4. PeriodicPoller 140

      5. PollDataClient 141

      6. PollTimer 141

    5. Consequences 141

    6. Implementation Strategies 141

    7. Related Patterns 142

    8. Example 142

  9. So, What Did We Learn? 147

Patterns in this chapter:

  • Hardware Proxy Pattern – Encapsulate the hardware into a class or struct

  • Hardware Adapter Pattern – Adapt between a required and a provided interface

  • Mediator Pattern – Coordinate complex interactions

  • Observer Pattern – Support efficient sensor data distribition

  • Debouncing Pattern – Reject intermittent hardware signals

  • Interrupt Pattern – Handle high-ugency hardware signals

  • Polling Pattern – Periodically check for new sensor data

Probably the most distinguishing property of embedded systems is that they must access hardware directly. The whole point of an embedded system is that the software is embedded in a “smart device” that provides some specific kind of service, and that requires accessing hardware. Broadly, software-accessible hardware can be categorized into four kinds – infrastructure, communications, sensors, and actuators.

3.1 Basic Hardware Access Concepts

Probably the most distinguishing property of embedded systems is that they must access hardware directly. The whole point of an embedded system is that the software is embedded in a “smart device” that provides some specific kind of service, and that requires accessing hardware. Broadly, software-accessible hardware can be categorized into four kinds – infrastructure, communications, sensors, and actuators.

Infrastructure hardware refers to the computing infrastructure and devices on which the software is executing. This includes not only the CPU and memory, but also storage devices, timers, input devices (such as keyboards, knobs, and buttons), user output devices (printers, displays, and lights), ports, and interrupts. Most of this hardware is not particularly application specific and may be located on the same motherboard, on daughterboards with high-speed access connections, or as other boards sharing a common backplane.

Communications hardware refers to hardware used to facilitate communication between different computing devices, such as standard (nonembedded) computers, other embedded systems, sensors, and actuators. Usually this means wired or wireless communications subsystems such as RS-232, RS-485, Ethernet, USB, 802.11x, or some other such facility. In embedded systems, DMA (Direct Memory Access) and multiported memory is sometimes used as well for this purpose, somewhat blurring the distinction between infrastructure and communications hardware.

Lastly, we have devices that monitor or manipulate the physical universe – these are the sensors and actuators. Sensors use electronic, mechanical, or chemical means for monitoring the status of physical phenomena, such as the rate of a heart beat, position of an aircraft, the mass of an object, or the concentration of a chemical. Actuators, on the other hand, change the physical state of some real-world element. Typical actuators are motors, heaters, generators, pumps, and switches.

All of these kinds of hardware are usually initialized, tested, and configured – tasks normally performed by the embedded software either on start-up or during execution, or both. All of these hardware elements are managed by the embedded software, provide commands or data to the software, or receive commands or data from the software.

This chapter will assume that you have enough hardware background to understand the hardware terms used in this book such as bus, CPU, RAM, ROM, EPROM, EEPROM, and Flash. If you want to know more, there any many excellent books on the topic, including books by David Simons1 , Michael Barr and Anthony Massa2 , Michael Pont3 , and Jack Ganssle4 . Instead, we will focus on common ways to develop embedded C applications to solve problems that commonly arise with the management and manipulation of such hardware elements. Some hardware-related concepts that we will talk briefly about are bit fields, ports, and interrupts.

It is exceedingly common to have hardware that uses bit fields to specify commands to the hardware or to return data. A bit field is a continuous block of bits (one or more) within an addressable memory element (e.g., a byte or word) that together has some semantic meaning to the hardware device. For example, an 8-bit byte might be mapped to a hardware device into four different fields:

0 0 0000 00

The bits represent the following information to or from the memory-mapped hardware device:

Bit Range Access Name Description
0 Write only Enable bit 0 = disable device, 1 = enable device
1 Read only Error status bit 0 = no error, 1 = error present
2–5 Write only Motor speed Range 0000 = no speed to 1111 (16d) top speed
6–7 Write only LED color 00 (0d) = OFF 01 (1d) = GREEN 10 (2d) = YELLOW 11 (3d) = RED

Bit fields are manipulated with C’s bit-wise operators & (bit-wise AND), | (bit-wise OR), ~ (bit-wise NOT), ^ (bit-wise XOR), >> (right shift), and << (left shift).

Here are some examples of the results of using these bit-wise operators with different masks on a specific value:

Code Listing

Value 01010101 Value 01010101   Value   01010101
Mask 11111111 Mask 00000000   Mask   00001111
ANDED 01010101 ANDED 00000000   ANDED   00000101
Value 01010101 Value 01010101   Value   01010101
Mask 11111111 Mask 00000000   Mask   00001111
ORED 11111111 ORED 01010101   ORED   01011111
Value 01010101 Value 01010101   Value   01010101
Mask 11111111 Mask 00000000   Mask   00001111
XORED 10101010 XORED 01010101   XORED   01011010

A common related C idiom is to use #define to create bit masks for ANDing and ORing and give them meaningful names. For example, we might create a set of bit masks for common manipulations of the memory mapped device above, such as:

Code Listing 3-1 Manipulating bit-oriented memory mapped hardware

#include <stdlib.h>

#include <stdio.h>

#define TURN_OFF (0x00)

#define INITIALIZE (0x61)

#define RUN (0x69)

#define CHECK_ERROR (0x02)

#define DEVICE_ADDRESS (0x01FFAFD0) void emergencyShutDown(void){

printf("OMG We're all gonna die!\n");

} int main() {

unsigned char* pDevice;

pDevice = (unsigned char *)DEVICE_ADDRESS; // pt to device

// for testing you can replace the above line with

// pDevice = malloc(1);

*pDevice = 0xFF; // start with all bits on

printf ("Device bits %X\n", *pDevice);

*pDevice = *pDevice & INITIALIZE; // and the bits into

printf ("Device bits %X\n", *pDevice);

if (*pDevice & CHECK_ERROR) { // system fail bit on?

emergencyShutDown();

abort();

}

else{

*pDevice = *pDevice & RUN;

printf (“Device bits %X\n”, *pDevice);

};

return 0;

};

The left and right shift operators are useful for setting or testing particular bits in isolation as well as for processing serial bit data. For example, the expression “1<<3” sets bit 3 resulting in the value 8. This is sometimes used in the definition of bits in a mask. For example, the mask CHECK_ERROR in Code Listing 3-1 could have been written:

#define CHECKERROR (1<<1)

Bit fields in C offer another way to represent bit-mapped fields in device interfaces. This syntax represents variable-length bit fields as fields within a struct. The ‘:’ operator separates the name of the field from its length in the declaration. For example, see Code Listing 3-2.

Code Listing 3-2 Bit fields in C

#include <stdlib.h>

#include <stdio.h>

int main() {

typedef struct _statusBits {

unsigned enable : 1;

unsigned errorStatus : 1;

unsigned motorSpeed : 4;

unsigned LEDColor : 2;

} statusBits;

statusBits status;

printf("size = %d\n",sizeof(status));

status.enable = 1;

status.errorStatus = 0;

status.motorSpeed = 3;

status.LEDColor = 2;

if (status.enable) printf("Enabled\n");

 else printf ("Disabled\n");

if (status.errorStatus) printf("ERROR!\n");

 else printf("No error\n");

printf ("Motor speed %d\n",status.motorSpeed);

printf ("Color %d\n",status.LEDColor);

return 0;

};

There are a couple of problems with bit fields in C. First, bit ordering is compiler- and processor- dependent. Further, the compiler may enforce byte padding rules, meaning that it may not map onto a memory mapped device as you expect. For example, the GNU C compiler returns size of status in Code Listing 3-2 to be 4 bytes even though the size of an unsigned char is only 1 byte.

Even worse, because most CPUs must write a byte or word at a time, bit fields may not be written in an atomic step, leading to thread safety issues if separate mutexes are used for different bit fields.

Another potential problem with using bit fields is that it is impossible to cast between a scalar and a user-defined struct. Thus, the following is disallowed5 :

Code Listings

Unsigned char f;

f = 0xF0;

status = (statusBits) f;

Let’s turn our attention now to a number of design patterns that have proven themselves useful for the manipulation of hardware. The Hardware Proxy Pattern, discussed next, is an archetypal pattern for the abstraction of hardware for the purpose of encapsulating details that are likely to change from the usage of the information provided to or by the hardware. The Hardware Adapter Pattern extends the Hardware Proxy Pattern to provide the ability to support different hardware interfaces. The Mediator Pattern supports coordination of multiple hardware devices to achieve a system level behavior. The Observer Pattern is a way of distributing sensed data to the software elements that need it. The Debouncing and Interrupt Patterns are simple reusable approaches to interface with hardware devices. The Timer Interrupt Pattern extends the Interrupt timer to provide accurate timing for embedded systems.

3.2 Hardware Proxy Pattern

The Hardware Proxy Pattern creates a software element responsible for access to a piece of hardware and encapsulation of hardware compression and coding implementation.

3.2.1 Abstract

The Hardware Proxy Pattern uses a class (or struct) to encapsulate all access to a hardware device, regardless of its physical interface. The hardware may be memory, port, or interrupt mapped, or may even be mapped via a serial connection, bus, network, or wireless link. The proxy publishes services that allow values to be read from and written to the device, as well as initialize, configure, and shut down the device as appropriate. The proxy provides an encoding and connection-independent interface for clients and so promotes easy modification should the nature of the device interface or connection change.

3.2.2 Problem

If every client accesses a hardware device directly, problems due to hardware changes are exacerbated. If the bit encoding, memory address, or connection technology changes, then every client must be tracked down and modified. By providing a proxy to sit between the clients and the actual hardware, the impact of hardware changes is greatly limited, easing such modifications. For easiest maintenance, the clients should be unaware of bit encoding, encryption, and compression used by the device; these details should be managed by the Hardware Proxy with internal private functions.

3.2.3 Pattern Structure

The pattern structure is quite simple as can be seen in Figure 3-1 6 . There may be many clients but a single Hardware Proxy per device being controlled. The proxy contains both public functions and private, encapsulated functions and data. The device is represented as a void* in the figure, but it should be appropriately typed for the device.

Figure 3-1 Hardware Proxy Pattern

3.2.4 Collaboration Roles

This section describes the roles for this pattern.

3.2.4.1 Hardware Device

This element represents the actual hardware. As such, you will not write C code for this element, but it is present just to aid with understanding the diagram. The association shown between the Hardware Device and the Hardware Proxy is realized via a software-addressable hardware interface, such as a port address, memory address, or interrupt.

3.2.4.2 Hardware Proxy

This is the primary class (or function) in the system. It has both data and functions that are customized to the device at hand, although usually each one has an initialize(), configure(), and disable() function. The other public functions provide read access to values from the device or set values sent to the device. Although the pattern structure has a single access() and mutate() function, it is common to have several such functions, each bearing the name of the value being read or set.

The key features7 of the class include:

access()

This public function returns a particular value from the device. In most cases, the class will contain a separate function for each separately-identifiable piece of information desired from the device. This operation usually calls the unmarshal() function prior to returning the retrieved values to the client.

configure()

This public function provides a means by which the device may be configured. Although in the general pattern no parameter list can be provided, this function almost always takes a list of parameters that specify the proper operating state of the device.

disable()

This public function provides a means by which the device may be safely turned off or disabled. It may or may not have parameters depending upon the device characteristics.

deviceAddr

This private variable provides low-level direct access to the hardware. In the pattern it is shown as a void*but it may be a memory mapped integer (int*) or other primitive type, a port-mapped device port number, or some other location identified. If more complex means are used to access the device, such as an RS232 serial port or an Ethernet connection, then this data type and its access method will be more complex. In any event, the public functions provided by the Hardware Proxy completely hide how the proxy connects to the actual device. This variable is not directly accessible by the client.

initialize()

This public function enables and initializes the device prior to first use. It is common for this function to have no parameters but in some cases it may need values provided by the client.

marshal()

This private function takes parameters from various other functions and performs any required encryption, compression, or bit-packing required to send the data to the device. This ensures that the peculiarities of the device interface are hidden from the client. Data in the format required by the actual device are known as “marshaled data” or are said to be in “native format.” Data that are in a form that is easily manipulated by software are said to be “unmarshaled” or in “presentation format.” This function is not accessible by the client since native format is hidden from the clients.

mutate()

This public function writes data values to the device. This function always has one or more input parameters. It usually calls the marshal() operation prior to writing the value to the device.

unmarshal()

This private function performs any necessary unpacking, decryption, and decompression of the data retrieved from the device prior to returning them to the client in presentation format. This ensures that the peculiarities of the device interface are hidden from the client. This function is not accessible by the client.

3.2.4.3 Proxy Client

The proxy client “knows about” the Hardware Proxy and invokes its services to access the hardware device.

3.2.5 Consequences

The pattern is extremely common and provides all the benefits of encapsulation of the hardware interface and encoding details. It provides flexibility for the actual hardware interface to change radically with absolutely no changes in the clients. This is because the hardware details are all encapsulated within the Hardware Proxy. This means that the proxy clients are usually unaware of the native format of the data and manipulate them only in presentation format.

This can, however, have a negative impact on run-time performance. It may sometimes be more time efficient for the clients to know the encoding details and manipulate the data in their native format. However, this degrades the maintainability of the system because now the clients need to be modified should the hardware interface or encoding change.

3.2.6 Implementation Strategies

As discussed in Chapter 1 classes may be implemented in different ways in C ranging from simple files to using struct to store class attributes, to using virtual function tables that support true polymorphism. All of those approaches can be used to implement this very simple pattern.

Usually, a Hardware Proxy supports all features of a specific device and a different Hardware Proxy is used for each separate device, but this isn’t an absolute rule. Separation into different devices means that the devices can follow independent maintenance paths and so is very flexible for the future.

In general, it is preferable to hide the bit-encoding, encryption, and data compression of the actual hardware away from the application software8 . However, it is possible to implement the pattern without the marshal() and unmarshal() functions by making the native format visible to the clients.

3.2.7 Related Patterns

The simple implementation of this pattern doesn’t implement anything for thread safety. It can be combined with the Critical Region, Guarded Call, or Queuing patterns to provide thread safety. For deadlock avoidance, it can be combined with the Ordered Locking or Simultaneous Locking patterns.

3.2.8 Example

The example, shown in Figure 3-2, is of a system with a motor with a memory-mapped interface. The interface is 16 bits wide and is detailed in the figure comment. In addition to the motor proxy, there are two clients in the example. The first of these is a motor controller than makes high-level decisions about what to do with the motor direction, speed, and how to handle errors. The motor display client is concerned with providing user feedback of the speed, direction, and status of the motor. Most of the action, of course, is in the Hardware Proxy per se.

Figure 3-2 Hardware Proxy example

The intention of the motor proxy is to provide the services to access the hardware in a hardware interface-independent fashion so that the two clients needn’t worry about – or even be aware of – the specific encoding of the bits. To this end, several types of services are offered by the motor proxy:

Motor Management Functions

  • configure() – this function sets up the memory-mapped address of the motor and the length of the rotary arm; this function must be called first

  • disable() – this function turns off the motor but keeps the set values intact

  • enable() – this function turns the motor on with the current set values

  • initialize() – this function turns on the motor to default set values (off)

Motor Status Functions

  • accessMotorDirection() – this function returns the current motor direction (off, forward, or reverse)

  • accessMotorSpeed() – this function returns the speed of motor

Motor Control Functions

  • writeMotorSpeed() – this function sets the speed of the motor and adjusts for the length of the rotary arm (if set)

Motor Error Management Functions

  • clearErrorStatus() – this function clears all error bits

  • accessMotorState() – this function returns the error status

Internal Data Formatting Functions (private)

These functions aren’t provided to the clients, but are used internally to exchange the data between native and presentation formats.

  • marshal() – converts presentation (client) data format to native (motor) format

  • unmarshal() – converts native (motor) data format to presentation (client) format

The C source code for the data types and MotorProxy class are shown in the next several code segments. Code Listing 3-3 shows the data typing for the DirectionType while Code Listing 3-4 shows the presentation client format for MotorData.

Code Listing 3-3 HardwareProxyExample.h

#ifndef HWProxyExample_H

#define HWProxyExample_H

struct MotorController;

struct MotorData;

struct MotorDisplay;

struct MotorProxy;

typedef enum DirectionType {

NO_DIRECTION,

FORWARD,

REVERSE

} DirectionType;

#endif

Code Listing 3-4 MotorData.h

#ifndef MotorData_H

#define MotorData_H

#include "HWProxyExample.h"

typedef struct MotorData MotorData;

struct MotorData {

unsigned char on_off;

DirectionType direction;

unsigned int speed;

unsigned char errorStatus;

unsigned char noPowerError;

unsigned char noTorqueError;

unsigned char BITError;

unsigned char overTemperatureError;

unsigned char reservedError1;

unsigned char reservedError2;

unsigned char unknownError;

};

#endif

The next two code listings show the header and implementation files for the MotorProxy class itself including both the data structure and the member functions.

Code Listing 3-5 MotorProxy.h

#ifndef MotorProxy_H

#define MotorProxy_H

#include "HWProxyExample.h"

#include "MotorData.h"

/* class MotorProxy */

typedef struct MotorProxy MotorProxy;

/* This is the proxy for the motor hardware. */

/* Note that the speed of the motor is adjusted for the length of the rotary arm */

/* to keep a constant speed at the end of the arm. */

struct MotorProxy {

unsigned int* motorAddr;

unsigned int rotaryArmLength;

};

void MotorProxy_Init(MotorProxy* const me);

void MotorProxy_Cleanup(MotorProxy* const me);

DirectionType* MotorProxy_accessMotorDirection(MotorProxy* const me);

unsigned int MotorProxy_accessMotorSpeed(MotorProxy* const me);

unsigned int MotorProxy_aceessMotorState(MotorProxy* const me);

/* keep all settings the same but clear error bits */ void MotorProxy_clearErrorStatus(MotorProxy* const me);

/* Configure must be called first, since it sets up the */

/* address of the device. */

void MotorProxy_configure(MotorProxy* const me, unsigned int length, unsigned int* location);

/* turn motor off but keep original settings */

void MotorProxy_disable(MotorProxy* const me);

/* Start up the hardware but leave all other settings of the */ /* hardware alone */ void MotorProxy_enable(MotorProxy* const me);

/* precondition: must be called AFTER configure() function. */ /* turn on the hardware to a known default state. */ void MotorProxy_initialize(MotorProxy* const me);

/* update the speed and direction of the motor together */

void MotorProxy_writeMotorSpeed(MotorProxy* const me, const DirectionType* direction, unsigned int speed);

MotorProxy * MotorProxy_Create(void); void MotorProxy_Destroy(MotorProxy* const me);

#endif

Code Listing 3-6 MotorProxy.c

#include "MotorProxy.h"

/* class MotorProxy */

/* This function takes a MotorData structure and creates */

/* a device-specific unsigned int in device native format. */

static unsigned int marshal(MotorProxy* const me, const struct MotorData* mData);

static struct MotorData* unmarshal(MotorProxy* const me, unsigned int encodedMData);

void MotorProxy_Init(MotorProxy* const me) { me->motorAddr = NULL;

}

void MotorProxy_Cleanup(MotorProxy* const me) {

}

DirectionType* MotorProxy_accessMotorDirection(MotorProxy* const me) {

MotorData mData;

if (!me->motorData) return 0;

mData = unmarshall(*me->motorAddr);

return mData.direction;

}

unsigned int MotorProxy_accessMotorSpeed(MotorProxy* const me) {

MotorData mData;

if (!me->motorData) return 0;

mData = unmarshall(*me->motorAddr);

return mData.speed;

}

unsigned int MotorProxy_aceessMotorState(MotorProxy* const me) {

MotorData mData;

if (!me->motorData) return 0;

mData = unmarshall(*me->motorAddr);

return mData.errorStatus;

}

void MotorProxy_clearErrorStatus(MotorProxy* const me) {

if (!me->motorData) return;

*me->motorAddr &= 0xFF;

}

void MotorProxy_configure(MotorProxy* const me, unsigned int length, unsigned int* location) {

me->rotaryArmLength = length;

me->motorAddr = location;

}

void MotorProxy_disable(MotorProxy* const me) {

// and with all bits set except for the enable bit

if (!me->motorData) return;

me->MotorAddr & = 0xFFFE;

}

void MotorProxy_enable(MotorProxy* const me) {

if (!me->motorData) return;

*me->motorAddr |= 1;

}

void MotorProxy_initialize(MotorProxy* const me) {

MotorData mData;

if (!me->motorData) return;

mData.on_off = 1;

mData.direction = 0;

mData.speed = 0;

mData.errorStatus = 0;

mData.noPowerError) = 0;

mData.noTorqueError) = 0;

mData.BITError) = 0;

mData.overTemperatureError) = 0;

mData.reservedError1) = 0;

mData.reservedError2) = 0;

Data.unknownError) = 0;

*me->motorAddr = marshall(mData);

}

void MotorProxy_writeMotorSpeed(MotorProxy* const me, const DirectionType* direction, unsigned int speed) {

MotorData mData

double dPi, dArmLength, dSpeed, dAdjSpeed;

if (!me->motorData) return; mData = unmarshall(*me->motorAddr); mData.direction = direction;

// ok, let’s do some math to adjust for

// the length of the rotary arm times 10

if (me->rotaryArmLength > 0) {

dSpeed = speed;

dArmLength = me->rotaryArmLength;

dAdjSpeed = dSpeed / 2.0 / 3.14159 / dArmLength * 10.0;

mData.speed = (int)dAdjSpeed;

}

else

mData.speed = speed;

*me->motorData = marshal(mData);

return;

}

static unsigned int marshal(MotorProxy* const me, const struct MotorData* mData) {

unsigned int deviceCmd;

deviceCmd = 0; // set all bits to zero

if (mData.on_off) deviceCmd |= 1; // OR in the appropriate bit

if (mData.direction == FORWARD)

deviceCmd |= (2 << 1);

else if (mData.direction == REVERSE)

deviceCmd |= (1 << 1);

if (mData.speed < 32 && mData.speed >= 0)

deviceCmd |= mData.speed << 3;

if (mData.errorStatus) deviceCmd |= 1 << 8;

if (mData.noPowerError) deviceCmd |= 1 << 9;

if (mData.noTorqueError) deviceCmd |= 1 << 10;

if (mData.BITError) deviceCmd |= 1 << 11;

if (mData.overTemperatureError) deviceCmd |= 1 << 12;

if (mData.reservedError1) deviceCmd |= 1 << 13;

if (mData.reservedError2) deviceCmd |= 1 << 14;

if (mData.unknownError) deviceCmd |= 1 << 15;

return deviceCmd;

}

static struct MotorData* unmarshal(MotorProxy* const me, unsigned int encodedMData) {

MotorData mData

int temp;

mData.on_off = encodedMData & 1;

temp = (encodedMData & (3 << 1)) >> 1; if (temp == 1)

mData.direction = REVERSE;

else if (temp == 2)

mData.direction = FORWARD;

else

mData.direction = NO_DIRECTION;

mData.speed = encodedMData & (31 << 3);

mData.errorStatus = encodedMData & (1 << 8);mData.noPowerError) = encodedMData & (1 << 9);mData.noTorqueError) = encodedMData & (1 << 10); mData.BITError) = encodedMData & (1 << 11); mData.overTemperatureError) = encodedMData & (1 << 12); mData.reservedError1) = encodedMData & (1 << 13); mData.reservedError2) = encodedMData & (1 << 14);

Data.unknownError) = encodedMData & (1 << 15);

return mData;

}

MotorProxy * MotorProxy_Create(void) {

MotorProxy* me = (MotorProxy *) malloc(sizeof(MotorProxy));

if(me!=NULL)

{

MotorProxy_Init(me);

}

return me;

}

void MotorProxy_Destroy(MotorProxy* const me) {

if(me!=NULL)

{

MotorProxy_Cleanup(me);

}

free(me);

}

3.3 Hardware Adapter Pattern

The Hardware Adapter Pattern provides a way of adapting an existing hardware interface into the expectations of the application. This pattern is a straightforward derivative of the Adapter Pattern.

3.3.1 Abstract

The Hardware Adapter Pattern is useful when the application requires or uses one interface, but the actual hardware provides another. The pattern creates an element that converts between the two interfaces.

3.3.2 Problem

While hardware that performs similar functions tend to have similar interfaces, often the information they need and the set of services differ. Rather than rewrite the clients of the hardware device to use the provided interface, an adapter is created that provides the expected interface to the clients while converting the requests to and from the actual hardware interface.

The Hardware Adapter Pattern is useful when you have hardware that meets the semantic need of the system but that has an incompatible interface. The goal of the pattern is to minimize the reworking of code when one hardware design or implementation is replaced with another.

3.3.3 Pattern Structure

The pattern structure is shown in Figure 3-3. The pattern extends the Hardware Proxy Pattern by adding a Hardware Adapter and explicitly showing the interface the client expects the hardware to support.

Figure 3-3 Hardware Adapter Pattern

3.3.4 Collaboration Roles

This section describes the roles for this pattern.

3.3.4.1 Adapter Client

The adapter client expects to be invoking the services of a software element that represents the hardware, as specified by the Hardware Interface to Client interface. It is an element in the application system that expects to control, monitor, or use the hardware device.

3.3.4.2 Hardware Adapter

The Hardware Adapter performs “impedance matching” between the client and the Hardware Proxy. That is, service requests made by the Adapter Client are converted into a sequence of services that are available from the Hardware Proxy. This potentially includes both factoring of the service invocations and reformatting and restructuring the data.

3.3.4.3 Hardware Interface to Client

This interface represents the set of services and parameter lists that the client expects the Hardware Proxy to provide. As an interface, it is a collection of service specifications and has no implementation. The implementation in this case is provided by the Hardware Adapter Class.

3.3.4.4 Hardware Device

This element is described in the Hardware Proxy Pattern. See Section 3.2.4.1.

3.3.4.5 Hardware Proxy

This element is described in the Hardware Proxy Pattern. See Section 3.2.4.2.

3.3.5 Consequences

The use of this pattern allows various Hardware Proxies and their related hardware devices to be used as-is in different applications, while at the same time allowing existing applications to use different hardware devices without change. The key is that the adapter provides the connective glue to match the Hardware Proxy to the application. This means that it will be easier, less error-prone, and faster to change hardware devices for an application or to reuse an existing hardware device in a new application.

The cost of using the pattern is that it adds a level of indirection and therefore decreases run-time performance slightly.

3.3.6 Implementation Strategies

The classic Design Patterns 9 book by Gamma, et. al., identifies two forms for this pattern. The object adapter form is as shown in Figure 3-3. In this form, the adapter subclasses along one interface and delegates to the other element (although we use realization in this case rather than inheritance). In the class adapter form, the Hardware Adapter subclasses from both interfaces and reimplements the client services in terms of the proxy services that it inherits. This requires a more elaborate implementation of classes than we’ve been using so far in this book since it requires the creation of a virtual function table to implement true polymorphism (see Section, 1.2.3). The tradeoffs for the two different implementations are subtly different. The class adapter form is a bit more reusable but requires a more complex implementation in C.

3.3.7 Related Patterns

The Hardware Adapter extends the Hardware Proxy Pattern. The latter pattern encapsulates hardware interface detail but does not translate service requests into radically different ones. The Hardware Adapter Pattern adds a level of indirection between the client and the Hardware Proxy. This allows the clients to be unchanged in the application while at the same time permitting reuse of existing Hardware Proxies that may also have been created for other systems. The implementation of the Hardware Proxy and the Hardware Adapter can be merged, but that undermines the reusability of the Hardware Proxy.

3.3.8 Example

The example is shown graphically in Figure 3-4. This example shows a system that tracks both oxygen concentration and oxygen flow rates, such as might be found in a medical ventilator. Two clients are shown. The Gas Display client displays these data for the attending medical staff. The Gas Mixer client uses these data for the closed loop control of gas delivery. Both are implemented to use the interface as shown in the iO2Sensor interface. The two services the clients expect to see are gimmeO2Conc(), which returns an integer in the range of 0–100, and gimmeO2Flow(),which returns an integer type with units of cc/min.

Figure 3-4 Hardware Adapter example

The system can be delivered with either of two physical sensors. These sensors do much the same job but they provide different programmatic interfaces. The Acme O2 Sensor Proxy provides two services. getO2Conc() returns the oxygen concentration in a range of 0–100 as an unsigned integer. getO2Flow() returns the flow oxygen flow as a scaled integer of 100* cc/sec. The Ultimate O2 sensor has two similar functions. accessO2Conc() returns concentration as a double in the range of 0.000 to 1.000 (3 digits of accuracy) and the accessGasFlow() returns the total gas flow (of which oxygen is just a part) as a double in liters/hour.

Each Hardware Proxy requires its own adapter to convert to the expected client interface.

The previous pattern showed code for the proxy classes, so this section will only show code for the adapters. The header files aren’t very interesting, so we will concentrate solely on the implementation files of the two adapters. The implementation of the Acme O2 Adapter is shown in Code Listing 3-7 and the implementation for the Ultimate O2 Adapter is given in Code Listing 3-8.

Code Listing 3-7 AcmeO2Adapter.c

int AcmeO2Adapter_gimmeO2Conc(AcmeO2Adapter* const me) {

return me->itsAcmeO2SensorProxy->getO2Conc();

}

int AcmeO2Adapter_gimmeO2Flow(AcmeO2Adapter* const me) {

return (me->itsAcmeO2SensorProxy->getO2Flow()*60)/100;

Code Listing 3-8 UltimateO2Adapter.c

int UltimateO2Adapter_gimmeO2Conc(UltimateO2Adapter* const me) {

return int(me->getItsUltimateO2SensorProxy

->accessO2Conc()*100);

}

int UltimateO2Adapter_gimmeO2Flow(UltimateO2Adapter* const me) {

double totalFlow;

// convert from liters/hr to cc/min

totalFlow = me->itsUltimateO2SensorProxy->accessGasFlow() *

1000.0/60.0;

// now return the portion of the flow due to oxygen

// and return it as an integer

return (int)(totalFlow *

me->itsUltimateO2SensorProxy->accessO2Conc());

}

3.4 Mediator Pattern

The Mediator Pattern provides a means of coordinating a complex interaction among a set of elements.

3.4.1 Abstract

The Mediator Pattern is particularly useful for managing different hardware elements when their behavior must be coordinated in well-defined but complex ways. It is particularly useful for C applications because it doesn’t require a lot of specialization (subclassing), which can introduce its own complexities into the implementation.

3.4.2 Problem

Many embedded applications control sets of actuators that must work in concert to achieve the desired effect. For example, to achieve a coordinated movement of a multi-joint robot arm, all the motors must work together to provide the desired arm movement. Similarly, using reaction wheels or thrusters in a spacecraft in three dimensions requires many different such devices acting at precisely the right time and with the right amount of force to achieve attitude stabilization.

3.4.3 Pattern Structure

The Mediator Pattern uses a mediator class to coordinate the actions of a set of collaborating devices to achieve the desired overall effect. The Mediator class coordinates the control of the set of multiple Specific Collaborators (their number is indicated by the ‘*’ multiplicity on the association between the Mediator and the Specific Collaborator in Figure 3-5). Each Specific Collaborator must be able to contact the Mediator when an event of interest occurs.

Figure 3-5 Mediator Pattern

3.4.4 Collaboration Roles

This section describes the roles for this pattern.

3.4.4.1 Collaborator Interface

The Collaborator Interface is a specification of a set of services common to all Specific Collaborators that may be invoked by the Mediator. For example, it is common to have reset(), shutdown, initialize() style operations for all the hardware devices to facilitate bringing them all to a known initial, recovery and/or shutdown state. Each of the common services must be implemented in each Specific Collaborator (although, of course, the implementation will usually be unique to each collaborator). If there are no services common to all Specific Collaborators, then this interface may be omitted.

3.4.4.2 Mediator

The Mediator class coordinates the Specific Collaborators in the pattern. It has a link to each Specific Collaborator so that it can send messages to it. In addition, each Specific Collaborator must be able to send the Mediator messages when events of interest occur. The Mediator provides the coordination logic that would otherwise require Specific Collaborators to coordinate among themselves.

3.4.4.3 Specific Collaborator

The Specific Collaborator represents one device and so may be a device driver or Hardware Proxy. It receives command messages from the Mediator and also sends messages to the Mediator when events of interest occur.

3.4.5 Consequences

This pattern creates a mediator that coordinates the set of collaborating actuators but without requiring direct coupling of those devices. This greatly simplifies the overall design by minimizing the points of coupling and encapsulating the coordination within a single element. Whenever the Collaborator would have directly contacted another Collaborator, instead it notifies the Mediator who can decide how to respond as a collective collaborative whole.

Since many embedded systems must react with high time fidelity, delays between the actions may result in unstable or undesirable effects. It is important that the mediator class can react within those time constraints. This is of a particular concern when there is two-way collaboration between the actuators and the mediator.

3.4.6 Implementation Strategies

The Mediator must be able to link to each Specific Collaborator. This can be done with an array of pointers (the obvious implementation strategy), a separate pointer for each Specific Collaborator, or a combination of the two. A common rule of thumb is that if a set of Specific Collaborators are indistinguishable in terms of their use other than their position in the list (e.g., they provide a common interface), then an array of pointers is best. If the Specific Collaborators serve different purposes, then use a separate link for each such Specific Collaborator. The example in Section 3.4.8 shows the use of both an array and of specific pointers.

It is even possible to have grouped sets of different Specific Collaborators in which each class within a group provides a common interface.

3.4.7 Related Patterns

This pattern is similar to the Strategy Pattern from the classic Design Patterns book10 and the architectural Rendezvous Pattern from my Real-Time Design Patterns book11 . In this case we are focusing on its use for detailed hardware coordination.

The Observer Pattern may be used instead of a direct link to the Mediator when there are multiple clients of the Specific Collaborator or when you wish to hide the Mediator from the Specific Collaborator.

3.4.8 Example

In this example of a robot system (See Figure 3-6), the Robot Arm Manager receives requests to grasp objects at specific points in space (x, y, z) and time (t). It first computes an arm trajectory via the computeTrajectory() function and produces a set of nSteps actions (up to 100). Each action is composed of a set of commands to each of the seven servos. If the goal is achievable, then Robot Arm Manager calls executeStep() function nSteps times to step through the computed action sequence. At each step, any command can return an error code (non-zero) which causes the graspAt() function to abort with an error code. Note that the computation of the arm path via the sum of the movements of the various arm joints is a complex exercise in geometry which, while interesting, is outside the scope of concern for this book. In addition, many paths may be disallowed for safety reasons (Rule 1: You Can’t Hit the Operator)12 , because they may require a path through a physical object, or may not even be physically possible.

Figure 3-6 Mediator Example

The point of the example is to illustrate the value of the Mediator (RobotArmManager). Without its coordinating influences, all of the actuators would have to collaborate directly with their peers. The computation of allowable versus illegal movement paths of the arm would be divided up among the collaborators and would be very difficult to manage.

Figure 3-7 shows a flowchart for the graspAt() function. This is the primary function that clients of the RobotArmManager will invoke when they want the arm to move to a position and grasp (or let go of) something.

Figure 3-7 Flowchart for graspAt() function

The code for the RobotArmManager is given in Code Listing 3-9 and Code Listing 3-10. You can see that the RobotArmManager coordinates the various servos to achieve the desired arm actions.

Code Listing 3-9 RobotArmManager.h

#include "GraspingManipulator.h"

#include "RotatingArmJoint.h"

#include "SlidingArmJoint.h"

#include "Action.h"

/*## class RobotArmManager */

typedef struct RobotArmManager RobotArmManager;

struct RobotArmManager {

unsigned int currentStep;

unsigned int nSteps;

struct GraspingManipulator* itsGraspingManipulator;

struct RotatingArmJoint *itsRotatingArmJoint[4];

struct SlidingArmJoint *itsSlidingArmJoint[2];

struct Action *itsAction[100];

int status;

};

/* Constructors and destructors:*/

void RobotArmManager_Init(RobotArmManager* const me);

void RobotArmManager_Cleanup(RobotArmManager* const me);

/* Operations */

void RobotArmManager_computeTrajectory(RobotArmManager* const me, int x, int y, int z, int t);

int RobotArmManager_executeStep(RobotArmManager* const me);

int RobotArmManager_graspAt(RobotArmManager* const me, int x, int y, int z, int t);

int RobotArmManager_zero(RobotArmManager* const me);

struct GraspingManipulator* RobotArmManager_getItsGraspingManipulator(const RobotArmManager* const me);

void RobotArmManager_setItsGraspingManipulator(RobotArmManager* const me, struct GraspingManipulator* p_GraspingManipulator);

int RobotArmManager_getItsRotatingArmJoint(const RobotArmManager* const me);

void RobotArmManager_addItsRotatingArmJoint(RobotArmManager* const me, struct RotatingArmJoint * p_RotatingArmJoint);

void RobotArmManager_removeItsRotatingArmJoint(RobotArmManager* const me, struct RotatingArmJoint * p_RotatingArmJoint);

void RobotArmManager_clearItsRotatingArmJoint(RobotArmManager* const me);

int RobotArmManager_getItsSlidingArmJoint(const RobotArmManager* const me);

void RobotArmManager_addItsSlidingArmJoint(RobotArmManager* const me, struct SlidingArmJoint * p_SlidingArmJoint);

void RobotArmManager_removeItsSlidingArmJoint(RobotArmManager* const me, struct SlidingArmJoint * p_SlidingArmJoint);

void RobotArmManager_clearItsSlidingArmJoint(RobotArmManager* const me);

RobotArmManager * RobotArmManager_Create(void);

void RobotArmManager_Destroy(RobotArmManager* const me);

int RobotArmManager_getItsAction(const RobotArmManager* const me);

void RobotArmManager_addItsAction(RobotArmManager* const me, struct Action * p_Action);

void RobotArmManager_removeItsAction(RobotArmManager* const me, struct Action * p_Action);

void RobotArmManager_clearItsAction(RobotArmManager* const me);

#endif

Code Listing 3-10 RobotArmManager.c

#include "RobotArmManager.h"

static void cleanUpRelations(RobotArmManager* const me);

void RobotArmManager_Init(RobotArmManager* const me) {

int pos;

for(pos = 0; pos < 100; ++pos) {

me->itsAction[pos] = NULL;

}

me->itsGraspingManipulator = NULL;

for(pos = 0; pos < 4; ++pos) {

me->itsRotatingArmJoint[pos] = NULL;

}

for(pos = 0; pos < 2; ++pos) {

me->itsSlidingArmJoint[pos] = NULL;

}

}

void RobotArmManager_Cleanup(RobotArmManager* const me) {

cleanUpRelations(me);

}

/* operation computeTrajectory(x,y,z,t)

This function computes a path for the robot arm to follow to position the manipulator at

the desired end point. It produces a set of up to 100 actions, each action is a set of

commands to the various servos to which the RobotArmManager connects. In actual practice

this is a complex job. The implementation here is just a placeholder.

*/

void RobotArmManager_computeTrajectory(RobotArmManager* const me, int x, int y, int z, int t) {

Action* ap;

int j;

me->nSteps = 0;

RobotArmManager_clearItsAction(me);

/* move the arm to the position with manipulator open*/

ap = Action_Create();

RobotArmManager_addItsAction(me, ap);

ap->rotatingArmJoint1=1;

ap->rotatingArmJoint2=2;

ap->rotatingArmJoint3=3;

ap->rotatingArmJoint4=4;

ap->slidingArmJoint1=10;

ap->slidingArmJoint2=20;

ap->manipulatorForce=0;

ap->manipulatorOpen=1;

/* grab the object */

ap = Action_Create();

RobotArmManager_addItsAction(me, ap);

ap->rotatingArmJoint1=1;

ap->rotatingArmJoint2=2;

ap->rotatingArmJoint3=3;

ap->rotatingArmJoint4=4;

ap->slidingArmJoint1=10;

ap->slidingArmJoint2=20;

ap->manipulatorForce=10;

ap->manipulatorOpen=0;

/* return to zero position */

ap = Action_Create();

RobotArmManager_addItsAction(me, ap);

ap->rotatingArmJoint1=0;

ap->rotatingArmJoint2=0;

ap->rotatingArmJoint3=0;

ap->rotatingArmJoint4=0;

ap->slidingArmJoint1=0;

ap->slidingArmJoint2=0;

ap->manipulatorForce=10;

ap->manipulatorOpen=0;

me->nSteps = 3;

}

/* operation executeStep()

This operation executes a single step in the chain of actions by

executing all of the commands within the current action

*/

int RobotArmManager_executeStep(RobotArmManager* const me) {

int actionValue = 0;

int step = me->currentStep;

int status = 0;

if (me->itsAction[step]) {

actionValue = me->itsAction[step]->rotatingArmJoint1;

status = RotatingArmJoint_rotate(me->itsRotatingArmJoint[0],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->rotatingArmJoint2;

status = RotatingArmJoint_rotate(me->itsRotatingArmJoint[1],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->rotatingArmJoint3;

status = RotatingArmJoint_rotate(me->itsRotatingArmJoint[2],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->rotatingArmJoint4;

status = RotatingArmJoint_rotate(me->itsRotatingArmJoint[3],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->slidingArmJoint1;

status = SlidingArmJoint_setLength(me->itsSlidingArmJoint[0],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->rotatingArmJoint2;

status = SlidingArmJoint_setLength(me->itsSlidingArmJoint[0],actionValue);

if (status) return status;

actionValue = me->itsAction[step]->manipulatorForce;

status = GraspingManipulator_setMaxForce(me->itsGraspingManipulator,actionValue);

if (status) return status;

if (me->itsAction[step]->manipulatorOpen)

status = GraspingManipulator_open(me->itsGraspingManipulator);

else

status = GraspingManipulator_close(me->itsGraspingManipulator);

};

return status;

}

/* operation graspAt(x,y,z,t) is the main function called by clients of the

RobotArmManager. This operation:

1. zeros the servos

2. computes the trajectory with a call to computeTrajectory()

3. executes each step in the constructed action list

*/

int RobotArmManager_graspAt(RobotArmManager* const me, int x, int y, int z, int t) {

me->currentStep = −1;

me->nSteps = 0;

RobotArmManager_zero(me);

RobotArmManager_computeTrajectory(me,x,y,z,t);

if ( me->nSteps == 0 ) {

me->status = −1;

}

else {

do {

me->currentStep++;

me->status = RobotArmManager_executeStep(me);

}

while (me->status ==0 && me->currentStep < me->nSteps);

}

return me->status;

}

/* operation zero()

This operation returns all servos to a starting default position

*/

int RobotArmManager_zero(RobotArmManager* const me) {

/* zero all devices */

int j;

for (j=0; j<4; j++){

if (me->itsRotatingArmJoint[j] == NULL) return -1;

if (RotatingArmJoint_zero(me->itsRotatingArmJoint[j])) return -1;

}

for (j=0; j<2; j++) {

if (me->itsSlidingArmJoint[j] == NULL) return -1;

if (SlidingArmJoint_zero(me->itsSlidingArmJoint[j])) return -1;

}

if (me->itsGraspingManipulator == NULL) return -1;

if (GraspingManipulator_open(me->itsGraspingManipulator)) return -1;

return 0;

}

struct GraspingManipulator* RobotArmManager_getItsGraspingManipulator(const RobotArmManager* const me) {

return (struct GraspingManipulator*)me->itsGraspingManipulator;

}

void RobotArmManager_setItsGraspingManipulator(RobotArmManager* const me, struct GraspingManipulator* p_GraspingManipulator) {

me->itsGraspingManipulator = p_GraspingManipulator;

}

int RobotArmManager_getItsRotatingArmJoint(const RobotArmManager* const me) {

int iter = 0;

return iter;

}

void RobotArmManager_addItsRotatingArmJoint(RobotArmManager* const me, struct RotatingArmJoint * p_RotatingArmJoint) {

int pos;

for(pos = 0; pos < 4; ++pos) {

if (!me->itsRotatingArmJoint[pos]) {

 me->itsRotatingArmJoint[pos] = p_RotatingArmJoint;

 break;

}

}

}

void RobotArmManager_removeItsRotatingArmJoint(RobotArmManager* const me, struct RotatingArmJoint * p_RotatingArmJoint) {

int pos;

for(pos = 0; pos < 4; ++pos) {

if (me->itsRotatingArmJoint[pos] == p_RotatingArmJoint) {

me->itsRotatingArmJoint[pos] = NULL;

}

}

}

void RobotArmManager_clearItsRotatingArmJoint(RobotArmManager* const me) {

{

int pos;

for(pos = 0; pos < 4; ++pos)

{

me->itsRotatingArmJoint[pos] = NULL;

}

}

}

int RobotArmManager_getItsSlidingArmJoint(const RobotArmManager* const me) {

int iter = 0;

return iter;

}

void RobotArmManager_addItsSlidingArmJoint(RobotArmManager* const me, struct SlidingArmJoint * p_SlidingArmJoint) {

int pos;

for(pos = 0; pos < 2; ++pos) {

if (!me->itsSlidingArmJoint[pos]) {

me->itsSlidingArmJoint[pos] = p_SlidingArmJoint;

break;

}

}

}

void RobotArmManager_removeItsSlidingArmJoint(RobotArmManager* const me, struct SlidingArmJoint * p_SlidingArmJoint) {

int pos;

for(pos = 0; pos < 2; ++pos) {

if (me->itsSlidingArmJoint[pos] == p_SlidingArmJoint) {

me->itsSlidingArmJoint[pos] = NULL;

}

}

}

void RobotArmManager_clearItsSlidingArmJoint(RobotArmManager* const me) {

{

int pos;

for(pos = 0; pos < 2; ++pos)

{

me->itsSlidingArmJoint[pos] = NULL;

}

}

}

RobotArmManager * RobotArmManager_Create(void) {

RobotArmManager* me = (RobotArmManager *) malloc(sizeof(RobotArmManager));

if(me!=NULL)

{

RobotArmManager_Init(me);

}

return me;

}

void RobotArmManager_Destroy(RobotArmManager* const me) {

if(me!=NULL) {

RobotArmManager_Cleanup(me);

}

free(me);

}

static void cleanUpRelations(RobotArmManager* const me) {

if(me->itsGraspingManipulator != NULL) {

me->itsGraspingManipulator = NULL;

}

}

int RobotArmManager_getItsAction(const RobotArmManager* const me) {

int iter = 0;

return iter;

}

void RobotArmManager_addItsAction(RobotArmManager* const me, struct Action * p_Action) {

int pos;

for(pos = 0; pos < 100; ++pos) {

if (!me->itsAction[pos]) {

me->itsAction[pos] = p_Action;

break;

}

}

}

void RobotArmManager_removeItsAction(RobotArmManager* const me, struct Action * p_Action) {

int pos;

for(pos = 0; pos < 100; ++pos) {

if (me->itsAction[pos] == p_Action) {

me->itsAction[pos] = NULL;

}

}

}

void RobotArmManager_clearItsAction(RobotArmManager* const me) {

 int pos;

for(pos = 0; pos < 100; ++pos) {

me->itsAction[pos] = NULL;

}

}

3.5 Observer Pattern

The Observer Pattern is one of the most common patterns around. When present, it provides a means for objects to “listen in” on others while requiring no modifications whatsoever to the data servers. In the embedded domain, this means that sensor data can be easily shared to elements that may not even exist when the sensor proxies are written.

3.5.1 Abstract

The Observer Pattern (also known as the “Publish-Subscribe Pattern”) provides notification to a set of interested clients that relevant data have changed. It does this without requiring the data server to have any a priori knowledge about its clients. Instead, the clients simply offer a subscription function that allows clients to dynamically add (and remove) themselves to the notification list. The data server can then enforce whatever notification policy it desires. Most commonly, data are sent whenever new data arrive, but clients can also be updated periodically, or with a minimum or maximum frequency. This reduces the computational burden on the clients to ensure that they have timely data.

3.5.2 Problem

In a naïve situation, each client can request data periodically from a data server in case the data have changed, but that is wasteful of compute and communication resources as the clients generally cannot know when new data are available. If the data server pushes the data out, then it must know who all of its clients are, breaking the basic rule of client-server relations13 requiring changes to the server to add new clients.

The Observer Pattern addresses this concern by adding subscription and unsubscription services to the data server. Thus a client can dynamically add itself to the notification list without a priori knowledge of the client on the part of the server. On the server side, the server can enforce the appropriate update policy to the notification of its interested clients. In addition, the pattern allows dynamic modification of subscriber lists and so adds a great deal of flexibility to the software.

3.5.3 Pattern Structure

Figure 3-8 shows the basic structure of the pattern. The AbstractSubject is the data server plus the machinery to maintain a list of interested subscribers. A client adds itself to the notification list by passing a pointer to an accept(Datum) function and removes itself by calling unsubscribe with the same pointer. When the AbstractSubject decides to notify its clients, the notify() function walks through the client list, calling the pointed-to function and passing the relevant data. The AbstractClient provides the accept(Datum) function to receive and process the incoming data.

Figure 3-8 Observer Pattern

3.5.4 Collaboration Roles

This section describes the roles for this pattern.

3.5.4.1 AbstractClient Interface

The AbstractClient associates with the AbstractSubject so that it can invoke the latter’s various services. It contains an accept(Datum) function to be called when the AbstractClient subscribes as well as whenever the AbstractSubject thinks it is appropriate to send data. The AbstractClient is abstract in the sense that while it specifies the functions, it does not provide any implementation. It is realized by a set of ConcreteClients that provide implementation that do things with the received data.

In addition to the accept(Datum) function, the AbstractClient associates with the data. This is normally realized with a pointer but it can also be a stack, global, or static variable.

3.5.4.2 AbstractSubject Interface

The AbstractSubject is the data server in this pattern. Relevant to this pattern it provides three services. The subscribe(acceptPtr) service adds the pointer to the accept function to the notification list. Its return value is zero if the add is successful or non-zero if not. The unsubscribe(acceptPtr) function removes the accept function from the notification list, again returning zero if successful. Finally, the notify() function walks the notification list to notify the subscribed clients. This is done by invoking the function pointed to by each notification handle.

The AbstractSubject also contains a link to the Datum to be passed as well as a list of NotificationHandles. In the representation here, the NotificationHandle is implemented as an array of pointers, but it can also be a linked list or other data structure. Similarly, the Datum can be on the heap, stack, or static as appropriate.

3.5.4.3 ConcreteClient

The ConcreteClient is a concrete realization of the AbstractClient interface. That is, it provides an implementation of the acceptPtr(Datum) function as well as other functionality not relevant to the pattern per se.

3.5.4.4 ConcreteSubject

The ConcreteSubject is a concrete realization of the AbstractSubject interface. It provides implementation of the functions but also provides means to acquire and manage the data it distributes. The ConcreteSubject is often a HardwareProxy in addition to an AbstractSubject.

3.5.4.5 Datum

This element is a stand-in for the data of interest to the clients and subjects. It may be a simple primitive type such as an int or may be a complex struct.

3.5.4.6 NotificationHandle

The NotificationHandle is a representation of a means to invoke a client’s accept(Datum) function. By far, the most common implementation is as a function pointer, but other implementations can be used as well.

3.5.5 Consequences

The Observer Pattern simplifies the process of distributing data to a set of clients who may not be known at design-time, and dynamically managing lists of interested clients during run-time. This pattern maintains the fundamental client-server relation while providing run-time flexibility through its subscription mechanism. Compute efficiency is maintained because clients are only updated when appropriate; the most common policy is that the clients are updated when the data change, but any appropriate policy can be implemented.

3.5.6 Implementation Strategies

The only complex aspects of this pattern are the implementation of the notification handle and the management of the notification handle list. The notification handle itself is almost always a function callback, that is, a pointer to a function with the right signature. Of course, this signature varies with the data being returned, but it also depends on the implementation of the software “classes” as discussed in Chapter 1. If using the struct approach with the me pointers, this will be the first parameter of the accept function. If you don’t use the approach but write a more traditional C, then the realization of the ConcreteClients is more manual but you won’t have to include the me pointer in the accept function parameter list. In the example later in this section, we shall use the former approach.

The easiest approach for the notification list is to declare an array big enough to hold all potential clients. This wastes memory in highly dynamic systems with many potential clients. An alternative is to construct the system as a linked list; that is, add another variable to each notification handle which is a pointer to the next one. Obviously, the last element in the list has a NULL next pointer value.

3.5.7 Related Patterns

This pattern can be freely mixed with the previous patterns in this chapter since its concerns are orthogonal. In embedded systems it is very common to add Observer functionality to a Hardware Proxy or Hardware Adapter, for example.

3.5.8 Example

Figure 3-9 provides a straightforward example of this pattern. In this example, the GasSensor is the ConcreteClient of the pattern; it provides the subscribe() and unsubscribe functions and manages the array of GasNotificationHandles. Because we implemented the pattern with the usage of our pseudo-object–oriented approach, all the operations have a first parameter that identifies the instance data. These “hidden parameters” are not shown in Figure 3-9 but they do show up in the code. This impacts the GasNotificationHandle in that not only do we need the function pointer for the acceptor function, we also need its instance data point to pass as its first argument.

Figure 3-9 Observer Pattern example

In addition to the subscribe() and unsubscribe() operations, the notify() function walks the list of registered subscribers and invokes the accept() operation using the simple statement

me->itsGasNH[pos]->acceptorPtr(me->itsGasNH[pos]->instancePtr, me->itsGasData);

In this way, the function being pointed to is accessed via the function pointer, where the first parameter is the instance data of the client and the second parameter is a pointer to the new data.

The GasSensor class also includes a newData() function that calls the notify() function and a dumpList() function that prints out a list of the currently subscribed clients. The code for the GasSensor is shown in Code Listing 3-11 and Code Listing 3-12.

Code Listing 3-11 GasSensor.h

#ifndef GasSensor_H

#define GasSensor_H

#include "GasData.h"

/* the function pointer type

The first value of the function pointer is to the instance

data of the class. The second is a ptr to the new gas data

*/

typedef void (*gasDataAcceptorPtr)(void *, struct GasData*);

struct GasNotificationHandle;

/* class GasSensor */

typedef struct GasSensor GasSensor;

struct GasSensor {

struct GasData* itsGasData;

struct GasNotificationHandle *itsGasNH[100];

};

/* Constructors and destructors:*/

void GasSensor_Init(GasSensor* const me);

void GasSensor_Cleanup(GasSensor* const me);

/* Operations */

void GasSensor_dumpList(GasSensor* const me);

void GasSensor_newData(GasSensor* const me, unsigned int flow, unsigned int n2, unsigned int o2);

void GasSensor_notify(GasSensor* const me);

void GasSensor_subscribe(GasSensor* const me, void * instancePtr, const gasDataAcceptorPtr* aPtr);

void GasSensor_unsubscribe(GasSensor* const me, const gasDataAcceptorPtr* aPtr);

struct GasData* GasSensor_getItsGasData(const GasSensor* const me);

void GasSensor_setItsGasData(GasSensor* const me, struct GasData* p_GasData);

int GasSensor_getItsGasNH(const GasSensor* const me);

void GasSensor_addItsGasNH(GasSensor* const me, struct GasNotificationHandle * p_GasNotificationHandle);

void GasSensor_removeItsGasNH(GasSensor* const me, struct GasNotificationHandle * p_GasNotificationHandle);

void GasSensor_clearItsGasNH(GasSensor* const me);

GasSensor * GasSensor_Create(void);

void GasSensor_Destroy(GasSensor* const me);

#endif

Code Listing 3-12 GasSensor.c

#include "GasSensor.h"

#include "GasData.h"

#include "GasNotificationHandle.h"

static void cleanUpRelations(GasSensor* const me);

void GasSensor_Init(GasSensor* const me) {

me->itsGasData = NULL;

int pos;

for(pos = 0; pos < 100; ++pos) {

me->itsGasNH[pos] = NULL;

}

}

void GasSensor_Cleanup(GasSensor* const me) {

cleanUpRelations(me);

}

void GasSensor_dumpList(GasSensor* const me) {

int pos;

char s[100];

printf(“Dumping registered elements/n”);

for (pos=0; pos<100; pos++)

if (me->itsGasNH[pos])

if (me->itsGasNH[pos]->acceptorPtr) {

printf(“Client %d: InstancePtr=%p, acceptPtr=%p/n”,

pos,me->itsGasNH[pos]->instancePtr, me->itsGasNH[pos]->acceptorPtr);

};

}

void GasSensor_newData(GasSensor* const me, unsigned int flow, unsigned int n2, unsigned int o2) {

if (!me->itsGasData)

me->itsGasData = GasData_Create();

me->itsGasData->flowRate = flow;

me->itsGasData->N2Conc = n2;

me->itsGasData->O2Conc = o2;

GasSensor_notify(me);

}

void GasSensor_notify(GasSensor* const me) {

int pos;

char s[100];

for (pos=0; pos<100; pos++)

if (me->itsGasNH[pos])

if (me->itsGasNH[pos]->acceptorPtr)

me->itsGasNH[pos]->acceptorPtr(me->itsGasNH[pos]->instancePtr,me->itsGasData);

}

void GasSensor_subscribe(GasSensor* const me, void * instancePtr, const gasDataAcceptorPtr* aPtr) {

struct GasNotificationHandle* gnh;

gnh = GasNotificationHandle_Create();

gnh->instancePtr = instancePtr;

gnh->acceptorPtr = aPtr;

GasSensor_addItsGasNH(me, gnh);

}

void GasSensor_unsubscribe(GasSensor* const me, const gasDataAcceptorPtr* aPtr) {

int pos;

for(pos = 0; pos < 100; ++pos) {

if (me->itsGasNH[pos])

if (me->itsGasNH[pos]->acceptorPtr == aPtr) {

GasNotificationHandle_Destroy(me->itsGasNH[pos]);

me->itsGasNH[pos] = NULL;

}

}

}

struct GasData* GasSensor_getItsGasData(const GasSensor* const me) {

return (struct GasData*)me->itsGasData;

}

void GasSensor_setItsGasData(GasSensor* const me, struct GasData* p_GasData) {

me->itsGasData = p_GasData;

}

int GasSensor_getItsGasNH(const GasSensor* const me) {

int iter = 0;

return iter;

}

void GasSensor_addItsGasNH(GasSensor* const me, struct GasNotificationHandle * p_GasNotificationHandle) {

int pos;

for(pos = 0; pos < 100; ++pos) {

if (!me->itsGasNH[pos]) {

me->itsGasNH[pos] = p_GasNotificationHandle;

break;

}

}

}

void GasSensor_removeItsGasNH(GasSensor* const me, struct GasNotificationHandle * p_GasNotificationHandle) {

int pos;

for(pos = 0; pos < 100; ++pos) {

if (me->itsGasNH[pos] == p_GasNotificationHandle) {

me->itsGasNH[pos] = NULL;

}

}

}

void GasSensor_clearItsGasNH(GasSensor* const me) {

int pos;

for(pos = 0; pos < 100; ++pos)

{

me->itsGasNH[pos] = NULL;

}

}

GasSensor * GasSensor_Create(void) {

GasSensor* me = (GasSensor *) malloc(sizeof(GasSensor));

if(me!=NULL) {

GasSensor_Init(me);

}

return me;

}

void GasSensor_Destroy(GasSensor* const me) {

if(me!=NULL) {

GasSensor_Cleanup(me);

}

free(me);

}

static void cleanUpRelations(GasSensor* const me) {

if(me->itsGasData != NULL) {

me->itsGasData = NULL;

}

}

Figure 3-9 shows that our example system has three clients – the DisplayClient, the GasMixerClient, and the SafetyMonitorClient. In addition to the accept() function, the DisplayMonitorClient class includes a show() function that displays the data when its accept() function is called, an alarm function (invoked by the SafetyMonitorClient when appropriate), and a register() function that calls the subscribe function of the GasSensor. The code for the DisplayClient is given in Code Listing 3-13 and Code Listing 3-14.

Code Listing 3-13 DisplayClient.h

#ifndef DisplayClient_H

#define DisplayClient_H

#include "GasSensor.h"

typedef struct DisplayClient DisplayClient;

struct DisplayClient {

struct GasData* itsGasData;

struct GasSensor* itsGasSensor;

};

/* Constructors and destructors:*/

void DisplayClient_Init(DisplayClient* const me);

void DisplayClient_Cleanup(DisplayClient* const me);

/* Operations */

void DisplayClient_accept(DisplayClient* const me, struct GasData* g);

void DisplayClient_alarm(DisplayClient* const me, char* alarmMsg);

void DisplayClient_register(DisplayClient* const me);

void DisplayClient_show(DisplayClient* const me);

struct GasData* DisplayClient_getItsGasData(const DisplayClient* const me);

void DisplayClient_setItsGasData(DisplayClient* const me, struct GasData* p_GasData);

struct GasSensor* DisplayClient_getItsGasSensor(const DisplayClient* const me);

void DisplayClient_setItsGasSensor(DisplayClient* const me, struct GasSensor* p_GasSensor);

DisplayClient * DisplayClient_Create(void);

void DisplayClient_Destroy(DisplayClient* const me);

#endif

Code Listing 3-14 DisplayClient.c

#include “DisplayClient.h”

#include “GasData.h”

#include “GasSensor.h”

static void cleanUpRelations(DisplayClient* const me);

void DisplayClient_Init(DisplayClient* const me) { me->itsGasData = NULL; me->itsGasSensor = NULL;

}

void DisplayClient_Cleanup(DisplayClient* const me) {

cleanUpRelations(me);

}

void DisplayClient_accept(DisplayClient* const me, struct GasData* g) {

if (!me->itsGasData)

me->itsGasData = GasData_Create();

if (me->itsGasData) {

me->itsGasData->flowRate = g->flowRate;

me->itsGasData->N2Conc = g->N2Conc;

me->itsGasData->O2Conc = g->O2Conc;

DisplayClient_show(me);

};

}

void DisplayClient_alarm(DisplayClient* const me, char* alarmMsg) {

printf(“ALERT! ”);

printf(alarmMsg);

printf(“/n/n”);

}

void DisplayClient_register(DisplayClient* const me) {

if (me->itsGasSensor)

GasSensor_subscribe(me->itsGasSensor, me,&DisplayClient_accept);

}

void DisplayClient_show(DisplayClient* const me) {

if (me->itsGasData) {

printf("Gas Flow Rate = %5d/n", me->itsGasData->flowRate);

printf("O2 Concentration = %2d/n", me->itsGasData->N2Conc);

printf("N2 Concentration = %2d/n/n", me->itsGasData->N2Conc);

}

else

printf("No data available/n/n");

}

struct GasData* DisplayClient_getItsGasData(const DisplayClient* const me) {

return (struct GasData*)me->itsGasData;

}

void DisplayClient_setItsGasData(DisplayClient* const me, struct GasData* p_GasData) {

me->itsGasData = p_GasData;

}

struct GasSensor* DisplayClient_getItsGasSensor(const DisplayClient* const me) {

return (struct GasSensor*)me->itsGasSensor;

}

void DisplayClient_setItsGasSensor(DisplayClient* const me, struct GasSensor* p_GasSensor) {

me->itsGasSensor = p_GasSensor;

}

DisplayClient * DisplayClient_Create(void) {

DisplayClient* me = (DisplayClient *)

malloc(sizeof(DisplayClient));

if(me!=NULL) {

DisplayClient_Init(me);

}

return me;

}

void DisplayClient_Destroy(DisplayClient* const me) {

if(me!=NULL) {

DisplayClient_Cleanup(me);

}

free(me);

}

static void cleanUpRelations(DisplayClient* const me) {

if(me->itsGasData != NULL) {

me->itsGasData = NULL;

}

if(me->itsGasSensor != NULL) {

me->itsGasSensor = NULL;

}

}

Lastly, let’s look at the code for the GasData class. This is a very straightforward data class with just a few helper functions, as seen in Code Listing 3-15 and Code Listing 3-16.

Code Listing 3-15 GasData.h

ifndef GasData_H

#define GasData_H

typedef struct GasData GasData;

struct GasData {

unsigned short N2Conc;

unsigned short O2Conc;

unsigned int flowRate;

};

/* Constructors and destructors:*/

oid GasData_Init(GasData* const me);

void GasData_Cleanup(GasData* const me);

GasData * GasData_Create(void);

void GasData_Destroy(GasData* const me);

#endif

Code Listing 3-16 GasData.c

#include "GasData.h"

void GasData_Init(GasData* const me) {

}

void GasData_Cleanup(GasData* const me) {

}

GasData * GasData_Create(void) {

GasData* me = (GasData *) malloc(sizeof(GasData));

if(me!=NULL) {

GasData_Init(me);

}

return me;

}

void GasData_Destroy(GasData* const me) {

if(me!=NULL) {

GasData_Cleanup(me);

}

free(me);

}

3.6 Debouncing Pattern

This simple pattern is used to reject multiple false events arising from intermittent contact of metal surfaces.

3.6.1 Abstract

Push buttons, toggle switches, and electromechanical relays are input devices for digital systems that share a common problem – as metal connections make contact, the metal deforms or “bounces”, producing intermittent connections during switch open or closure. Since this happens very slowly (order of milliseconds) compared to the response speed of embedded systems (order of microseconds or faster), this results in multiple electronic signals to the control system. This pattern addresses this concern by reducing the multiple signals into a single one by waiting a period of time after the initial signal and then checking the state.

3.6.2 Problem

Many input devices for embedded systems use metal-on-metal contact to indicate events of interest, such as button presses, switch movement, and activating or deactivating relays. As the metal moves into contact, physical deformation occurs resulting in an intermittent bouncing contact until the vibrations dampen down. This results in an intermediate contact profile such as that shown in Figure 3-10.

Figure 3-10 Bouncing electrical contacts

3.6.3 Pattern Structure

The basic solution is to accept the initial event, wait for the vibrations to dampen out, and then sample the data source for its state. See Figure 3-11 for the pattern structure.

Figure 3-11 Debouncing Pattern

3.6.4 Collaboration Roles

This section describes the roles for this pattern.

3.6.4.1 ApplicationClient

This element is the ultimate recipient of the debounced event. Its operation deviceEventReceive() is only activated when the event is real (i.e., results in a changed device state).

3.6.4.2 BouncingDevice

The BouncingDevice represents the hardware for the device itself. The most common implementation of this device is completely in hardware; in the case, the sendEvent() operation is simply an activation of an interrupt in the interrupt vector table of the embedded processor and the getState operation is implemented via a read of a memory location or IO port. DeviceState is normally a bivalued attribute, ON or OFF.

3.6.4.3 Debouncer

The Debouncer is the software element that processes the incoming event, debounces it, and makes sure that it represents an actual device state change. Its eventReceive() function is activated by the sendEvent() service of the BouncingDevice. In turn, it sets the delay timer (disabling interrupts from the device if necessary) and then checks the device state. If, after the debouncing time, the state is different then the event must have been real, so it sends the appropriate message to the ApplicationClient. The old state of the button is stored in its variable oldState; this variable is updated whenever there is a detected change of state of the device.

3.6.4.4 DebouncingTimer

This timer provides a nonbusy wait via its delay() service. This is often done with an OS call but might be done with specialty timer hardware as well.

3.6.5 Consequences

This pattern is not necessary for hardware that debounces before announcing events. In my experience, however, it often falls on the software to perform this task. This is a simple pattern that performs the debouncing so that the application need only concern itself with true events resulting from a change in device state.

3.6.6 Implementation Strategies

It is common for the device to use an interrupt vector to contact its client. Indeed, it is common for the BouncingDevice to be entirely implemented in hardware so that when the event occurs, a hardware interrupt is created, and the device state must be verified by reading a memory location or port address. When the event interface is via an interrupt, the address of the DeviceClient::eventReceive() operation must be installed in the proper location in the interrupt vector table when the DeviceClient is initiated, and the address must be removed if the DeviceClient is deactivated. Care must be taken to ensure that the eventReceive operation has no parameters (not even hidden ones!) so that the Return from Interrupt instruction will work properly. If the hidden me parameter is used as in the other patterns here, then making the operation static will ensure there is no hidden parameter because static operations are class-wide and not instance-wide functions (that is, they use no instance data and so don’t require a me pointer).

If the interrupt vector table is not used, this pattern can be mixed with the Observer Pattern to distribute the event signal to multiple clients, if desired.

The DebouncingTimer may use special timer hardware such as a programmable 555 timer. If the RTOS (Real-Time Operating System) timer is used, care must be taken with the time unit resolution. Windows, for example, has a standard timer resolution of between 10 and 25 milliseconds. You can only get a delay time as a multiple of the basic timer resolution so if you want a 45 ms delay, you will have to use the closest timer resolution that is greater than or equal to your desired time. For debouncing applications, this is usually fine but your needs may vary with your specific hardware and application.

If you don’t mind fully occupying your embedded processor while you wait for the debounce timeout, it is a simple matter, as shown in Code Listing 3-17.

Code Listing 3-17 Busy wait timing

/* LOOPS_PER_MS is the # of loops in the delay() function

required to hit 1 ms, so it is processor and

compiler-dependent

*/

#define LOOPS_PER_MS 1000

void delay(unsigned int ms) {

long nLoops = ms * LOOPS_PER_MS;

do {

while (nLoops--);

}

3.6.7 Related Patterns

As mentioned, the pattern is often used in conjunction with the Interrupt Pattern discussed elsewhere in this chapter. Timeouts are often shown within a tm() event on the state machine.

3.6.8 Example

In the example shown in Figure 3-12, we are modeling a toggle button for a microwave oven; press it once to turn on the microwave emitter and press it again to turn it off. This means that we need to call different operations in the MicrowaveEmitter depending if this is an even-numbered press of the button or an odd-numbered press. In addition, we have to debounce both the press and release of the button, although we only act on a button release.

Code Listing 3-18 ButtonDriver.h

The code for the button driver is given in Code Listing 18 and Code Listing 19. #ifndef ButtonDriver_H

#define ButtonDriver_H

#define LOOPS_PER_MS (1000)

#define DEBOUNCE_TIME (40)

struct Button;

struct MicrowaveEmitter;

struct Timer;

typedef struct ButtonDriver ButtonDriver;

struct ButtonDriver {

unsigned char oldState;

unsigned char toggleOn;

struct Button* itsButton;

struct MicrowaveEmitter* itsMicrowaveEmitter;

struct Timer* itsTimer;

};

void ButtonDriver_Init(ButtonDriver* const me);

void ButtonDriver_Cleanup(ButtonDriver* const me);

/* Operations */ void ButtonDriver_eventReceive(ButtonDriver* const me);

struct Button* ButtonDriver_getItsButton(const ButtonDriver* const me);

void ButtonDriver_setItsButton(ButtonDriver* const me, struct Button* p_Button);

struct MicrowaveEmitter* ButtonDriver_getItsMicrowaveEmitter(const ButtonDriver* const me);

void ButtonDriver_setItsMicrowaveEmitter(ButtonDriver* const me, struct MicrowaveEmitter* p_MicrowaveEmitter);

struct Timer* ButtonDriver_getItsTimer(const ButtonDriver* const me);

void ButtonDriver_setItsTimer(ButtonDriver* const me, struct Timer* p_Timer);

ButtonDriver * ButtonDriver_Create(void);

void ButtonDriver_Destroy(ButtonDriver* const me);

void ButtonDriver___setItsButton(ButtonDriver* const me, struct Button* p_Button);

void ButtonDriver__setItsButton(ButtonDriver* const me, struct Button* p_Button);

void ButtonDriver__clearItsButton(ButtonDriver* const me);

#endif

Code Listing 3-19 ButtonDriver.c

#include "ButtonDriver.h"

#include "Button.h"

#include "MicrowaveEmitter.h"

#include "Timer.h"

static void cleanUpRelations(ButtonDriver* const me);

void ButtonDriver_Init(ButtonDriver* const me) {

me->oldState = 0;

me->toggleOn = 0;

me->itsButton = NULL;

me->itsMicrowaveEmitter = NULL;

me->itsTimer = NULL;

}

void ButtonDriver_Cleanup(ButtonDriver* const me) {

cleanUpRelations(me);

}

void ButtonDriver_eventReceive(ButtonDriver* const me) {

Timer_delay(me->itsTimer, DEBOUNCE_TIME);

if (Button_getState(me->itsButton) != me->oldState) {

/* must be a valid button event */

me->oldState = me->itsButton->deviceState;

if (!me->oldState) {

/* must be a button release, so update toggle value */

if (me->toggleOn) {

me->toggleOn = 0; /* toggle it off */

Button_backlight(me->itsButton, 0);

MicrowaveEmitter_stopEmitting(me->itsMicrowaveEmitter);

}

else {

me->toggleOn = 1; /* toggle it on */

Button_backlight(me->itsButton, 1);

MicrowaveEmitter_startEmitting(me->itsMicrowaveEmitter);

}

}

/* if it’s not a button release, then it must

be a button push, which we ignore.

*/

}

}

struct Button* ButtonDriver_getItsButton(const ButtonDriver* const me) {

return (struct Button*)me->itsButton;

}

void ButtonDriver_setItsButton(ButtonDriver* const me, struct Button* p_Button) {

if(p_Button != NULL)

{

Button__setItsButtonDriver(p_Button, me);

}

ButtonDriver__setItsButton(me, p_Button);

}

struct MicrowaveEmitter* ButtonDriver_getItsMicrowaveEmitter(const ButtonDriver* const me) {

return (struct MicrowaveEmitter*)me->itsMicrowaveEmitter;

}

void ButtonDriver_setItsMicrowaveEmitter(ButtonDriver* const me, struct MicrowaveEmitter* p_MicrowaveEmitter) {

me->itsMicrowaveEmitter = p_MicrowaveEmitter;

}

struct Timer* ButtonDriver_getItsTimer(const ButtonDriver* const me) {

return (struct Timer*)me->itsTimer;

}

void ButtonDriver_setItsTimer(ButtonDriver* const me, struct Timer* p_Timer) {

me->itsTimer = p_Timer;

}

ButtonDriver * ButtonDriver_Create(void) {

ButtonDriver* me = (ButtonDriver *) malloc(sizeof(ButtonDriver));

if(me!=NULL)

{

ButtonDriver_Init(me);

}

return me;

}

void ButtonDriver_Destroy(ButtonDriver* const me) {

if(me!=NULL)

{

ButtonDriver_Cleanup(me);

}

free(me);

}

static void cleanUpRelations(ButtonDriver* const me) {

if(me->itsButton != NULL)

{

struct ButtonDriver* p_ButtonDriver = Button_getItsButtonDriver(me->itsButton);

if(p_ButtonDriver != NULL)

{

Button___setItsButtonDriver(me->itsButton, NULL);

}

me->itsButton = NULL;

}

if(me->itsMicrowaveEmitter != NULL)

{

me->itsMicrowaveEmitter = NULL;

}

if(me->itsTimer != NULL)

{

me->itsTimer = NULL;

}

}

void ButtonDriver___setItsButton(ButtonDriver* const me, struct Button* p_Button) {

me->itsButton = p_Button;

}

void ButtonDriver__setItsButton(ButtonDriver* const me, struct Button* p_Button) {

if(me->itsButton != NULL)

{

Button___setItsButtonDriver(me->itsButton, NULL);

}

ButtonDriver___setItsButton(me, p_Button);

}

void ButtonDriver__clearItsButton(ButtonDriver* const me) {

me->itsButton = NULL;

}

Figure 3-12 Debounce example

3.7 Interrupt Pattern

The physical world is fundamentally both concurrent and asynchronous; it's nonlinear too, but that's a different story. Things happen when they happen and if your embedded system isn’t paying attention, those occurrences may be lost. Interrupt handlers (a.k.a. Interrupt Service Routines, or ISRs) are a useful way to be notified when an event of interest occurs even if your embedded system is off doing other processing.

3.7.1 Abstract

The Interrupt Pattern is a way of structuring the system to respond appropriately to incoming events. It does require some processor- and compiler-specific services but most embedded compilers, and even some operating systems provide services for just this purpose. Once initialized, an interrupt will pause its normal processing, handle the incoming event, and then return the system to its original computations.

3.7.2 Problem

In many systems, events have different levels of urgency14 . Most embedded systems have at least some events with a high urgency that must be handled even when the system is busy doing other processing. The Polling Pattern, discussed elsewhere in this chapter, looks for events of interest when it is convenient for the system. While this has the advantage that primary processing can proceed uninterrupted, it has the disadvantage that high urgency and high frequency events may not be handled in a timely fashion, or may be missed altogether. The Interrupt Pattern addresses this problem by immediately pausing the current processing, handling the incoming event, and then returning to the original computation.

3.7.3 Pattern Structure

Figure 3-13 shows the basic structure of the pattern. Because it is important to ensure that the interrupt handlers are parameterless, I’ve used the «File» stereotype to indicate that this pattern isn’t using the class-structured approach with the me pointer.

Figure 3-13 Interrupt Pattern

3.7.4 Collaboration Roles

This section describes the roles for this pattern.

3.7.4.1 InterruptHandler

The interruptHandler is the only element that has behavior in the pattern. It has functions to install or deinstall an interrupt vector and the interrupt service routines themselves.

The install() function takes the interrupt number as an incoming parameter. When run, it copies the existing vector into the vector table and then replaces it with the address of the appropriate interrupt service routine. The deinstall() function does the reverse, restoring the original vector. Each handleIinterrupt_x() function handles a specific interrupt, completing with a Return From Interrupt (RTI) statement. This statement is compiler and processor dependent. As mentioned, it is crucial that the interrupt service routines have no parameters because otherwise when they attempt to return, the wrong value will be popped off the CPU stack.

3.7.4.2 InterruptVectorTable

The InterruptVectorTable is nothing more than an array of addresses to interrupt service routines. It is located in a specific memory location that is processor dependent. When interrupt number x occurs, the CPU suspends the current processing and indirectly invokes the address corresponding to the x-th index in this table. Upon an RTI, the CPU restores execution of the suspending computation.

3.7.4.3 vectorPtr

The vectorPtr is a data type; specifically it is a pointer to a function that takes no parameters and returns no values.

3.7.5 Consequences

This pattern allows for highly responsive processing of events of interest. It interrupts normal processing (provided that interrupts have not been disabled) and so should be used carefully when time-critical processing is going on. Normally, interrupts are disabled while an interrupt service routine is executing; this means that interrupt service routines must execute very quickly to ensure that other interrupts are not missed.

Because the interrupt service routines must be short, one must take care when using them to invoke other system services. To share data signaled by an interrupt, for example, the ISR might need to queue the data and quickly RTI; at some point in the future, the application software will discover the data in the queue. This mechanism is useful when the actual acquisition of the data is more urgent than its processing. In this way, a long interrupt handle can be broken up into two parts; the urgent part, done via the ISR per se, and the processing part done via a second function that periodically checks for waiting data or signals.

Problems arise with this pattern when the ISR processing takes too long, when an implementation mistake leaves interrupts disabled, or race conditions or deadlocks occur on shared resources. The last of these concerns is the most insidious.

A race condition is a computational situation in which the result depends on the order of execution of statements but that order isn’t known or knowable. A deadlock is a situation in which two elements are waiting on a condition that cannot in principle occur. These are common problems that will be discussed in more detail in the next chapter. In this context, a variable or data structure (such as a queue) shared between an interrupt service routine and an application service is a resource with potential race and deadlock conditions because you can never know exactly when the ISR will be executed.

Figure 3-14 shows a typical structure when the data acquired as a part of an ISR must be shared with normal system processing. The race condition arises when the interrupt occurs when ApplicationElement is accessing the SharedResource. Imagining that the ApplicationElement is halfway through reading the data when the interrupt occurs – unless the read takes place as an atomic uninterruptible access – the ISR will pause the ApplicationElement mid-read, modify the data, and then return. The ApplicationElement will see corrupted data – partially new data and partially old data.

Figure 3-14 ISR potential race condition

There are a number of solutions to this, but they all involve serializing the access to the shared resource. One way is to disable interrupts in the ApplicationElement just prior to reading the data and to reenable interrupt just after access is complete. The primary downside to that approach is the discipline necessary to ensure that this is always done. Another approach is to use a mutex semaphore to guard the data, as shown in Figure 3-15.

Figure 3-15 ISR potential deadlock condition

In this figure, the SharedResource is protected with a mutex semaphore; this results in a lock when either the getData() or setData() functions are called and in the removal of the lock when the functions are complete. Deadlock can occur if the ISR waits on the semaphore lock when it tries to access the data. Since the ISR has interrupted the ApplicationElement that owns the lock, if the ISR waits, the ApplicationElement will never have the opportunity to remove the lock. The solution of course, is that the ISR cannot wait on the lock. The new data can be discarded or two shared resources can be made with the proviso that the ISR or ApplicationElement can only lock one of the resources at a time. This latter solution is sometimes referred to as a “ping pong buffer.”

3.7.6 Implementation Strategies

An ISR is different than a normal C function in that as long as interrupts are enabled, it can pretty much interrupt between any pair of CPU instructions. That means that the CPU registers must be saved and restored before the ISR statements can execute.

In fact, each interrupt service routine must:

  • save the CPU registers, including the CPU instruction pointer and any processor flags, such as carry, parity and zero

  • clear the interrupt bit

  • perform the appropriate processing

  • restore the CPU registers

  • return

In “standard C,” it is necessary to use assembly language keyword asm to save and restore registers, something like15 :

Code Listing

void isr (void) {

asm {

DI; disable interrupts

PUSH AF ; save registers

PUSH BC

PUSH DE

PUSH HL

PUSH IX

PUSH IY

}

/* normal C code here */

asm {

POP IY

POP IX

POP HL

POP DE

POP BC

POP AF

EI; enable interrupts

RETI ; return from interrupt

}

}

Some compilers support this by using a special interrupt keyword. When available, the keyword is used in the function declaration such as

Code Listing

interrupt void isr(void) {

/* normal C code here */

};

and the compiler will insert the necessary instructions for you.

The GNU C Compiler (GCC) uses __attribute__ specification to indicate an interrupt handler using the syntax

Code Listing

void isr(void) __attribute__ ((interrupt (“IRQ”));

void isr(void) {

/* normal C code here */

}

but again this is processor dependent.

The interrupt keyword tells the compiler to generate register-saving instructions before the body of the function is executed and to restore the registers just before the ISRs return.

Many RTOSs provide functions for installing the vectors and some CPUs only have a single vector, requiring the ISR to figure out which interrupt occurred by checking the hardware.

3.7.7 Related Patterns

An alternative means to get hardware signals and data is to periodically check for them via the Polling Pattern.

3.7.8 Example

In the example shown in Figure 3-16, a button is the hardware device. One interrupt (index 0) is generated for the push and another (index 1) is generated for the release. The ButtonHandler is the interrupt handler for the interrupts and its install() functions set up the vectors for both pointers. The main() routine does the following:

  • initializes the Button and LED classes

  • sets all the elements of the RobotInterruptVectorTable and ButtonHandler::oldVectors to NULL

  • sets the pointer from the ButtonHandler to the LED

  • calls the install() function to set up the vectors to the two ISRs

as you can see in Code Listing 20

Code Listing 20 Interupt example main() code

int j;

Button itsButton;

LED itsLED;

itsButton = Button_Create();

itsLED = LED_Create();

for (j=0;j<9;j++) {

ISRAddress[j] = NULL;

oldVectors[j] = NULL;

};

ButtonHandler_setItsLED(&itsLED);

install(); /* install interrupt vectors */

/* normal system execution stuff */

Figure 3-16 Interrupt Example

The interesting part is the ButtonHandler itself. It installs the address of the two ISRs into positions 0 and 1 of the interrupt vector table so that when the interrupts occur, the appropriate ISR is invoked. In the example, when the button is pushed, the ISR lights the LED and turns off the light when the button is released.

Note that this code uses the interrupt keyword to indicate to the compiler the interrupt service routines; you will have to use the appropriate syntax provided by your compiler. To execute this on standard desktop hardware, simply remove the interrupt keyword from the ISR's function headers and then the interrupt vector table will operate as a table of normal function pointers.

Code Listing 21 ButtonHandler.h

#ifndef ButtonHandler_H

#define ButtonHandler_H

typedef void (*ButtonVectorPtr)(void);

struct LED;

extern ButtonVectorPtr oldVectors[10];

/* Operations */

void install(void);

void deinstall(void);

interrupt void handleButtonPushInterrupt(void);

interrupt void handleButtonReleaseInterrupt(void);

struct LED* ButtonHandler_getItsLED(void); void ButtonHandler_setItsLED(struct LED* p_LED);

#endif

Code Listing 22 ButtonHandler.c

#include "ButtonHandler.h"

#include "LED.h"

#include "RobotInterruptVectorTable.h"

ButtonVectorPtr oldVectors[10];

static struct LED* itsLED;

void deinstall(void) {

ISRAddress[0] = oldVectors[0];

ISRAddress[1] = oldVectors[1];

}

interrupt void handleButtonPushInterrupt(void) {

LED_LightOn(itsLED);

}

interrupt void handleButtonReleaseInterrupt(void) {

LED_LightOff(itsLED);

}

void install(void) {

oldVectors[0] = ISRAddress[0];

oldVectors[1] = ISRAddress[1];

ISRAddress[0] = handleButtonPushInterrupt;

ISRAddress[1] = handleButtonReleaseInterrupt;

}

struct LED* ButtonHandler_getItsLED(void) {

return (struct LED*)itsLED;

}

void ButtonHandler_setItsLED(struct LED* p_LED) {

itsLED = p_LED;

}

3.8 Polling Pattern

Another common pattern for getting sensor data or signals from hardware is to check periodically, a process known as polling. Polling is useful when the data or signals are not so urgent that they cannot wait until the next polling period to be received or when the hardware isn’t capable of generating interrupts when data or signals become available.

3.8.1 Abstract

The Polling Pattern is the simplest way to check for new data or signals from the hardware. Polling can be periodic or opportunistic; periodic polling uses a timer to indicate when the hardware should be sampled whereas opportunistic polling is done when it is convenient for the system, such as between major system functions or at some point in a repeated execution cycle. Opportunistic polling is, by definition, less regular but has less impact on the timeliness of other activities in which the system may be engaged.

3.8.2 Problem

The Polling Pattern addresses the concern of getting new sensor data or hardware signals into the system as it runs when the data or events are not highly urgent and the time between data sampling can be guaranteed to be fast enough.

3.8.3 Pattern Structure

This pattern comes in two flavors. Figure 3-17 shows the pattern structure for opportunistic polling while Figure 3-18 shows the pattern for periodic polling. The difference is that in the former pattern the applicationFunction will embed calls to poll() when convenient, and in the latter, a timer is created to start polling.

Figure 3-17 Opportunistic Polling Pattern

Figure 3-18 Periodic Polling Pattern

3.8.4 Collaboration Roles

This section describes the roles for both variants of this pattern.

3.8.4.1 ApplicationProcessingElement

This elements has the applicationFunction that has a loop in which it invokes the poll() operation, such as

Code Listing

while (processing) {

action1();

action2();

action3();

OpportunityPoller_poll(me->itsOpportunisticPoller);

}

3.8.4.2 Device

The device provides data and/or device state information via accessible functions. This element can be a device driver or can simply read from memory- or port-mapped devices. This class provides two functions, one to retrieve data (typed as deviceData in the example) and one to retrieve device state (typed as an unsigned int in the example).

In the example, there are MAX_POLL_DEVICES connected to the polling element so that the poll() function scans them all and notifies their respective clients.

3.8.4.3 OpportunisticPoller

This element has the poll() function that scans the attached devices for data and device state and passes this information on to the appropriate client for each. The difference between the OpportunisticPoller and the PeriodicPoller is that the latter has timer initialization and shutdown functionality in addition to polling for the data.

3.8.4.4 PeriodicPoller

Like the OpportunisticPoller, the PeriodicPoller has the poll() function that scans the attached devices for data and device state and passes this information on to the appropriate client for each. In addition, it has a variable for the poll time (settable in its setPollTime(t) function) and services to start and stop polling. The installInterruptTimer() function inserts the address of the ISR into the interrupt vector table while the removeInterruptHandler() restores the original vector. The startPolling() function initializes the timer. The stopPolling() function stops the timer but leaves the vector in the interrupt vector table.

3.8.4.5 PollDataClient

This element is the client for data and state information from one or more of the devices. In some cases, there may be a single client for the data from all devices, but in general each device will have its own client.

3.8.4.6 PollTimer

The PollTimer element represents a timer and the services associated with using it. The startTimer() method installs the handleTimerInterrupt() interrupt service routine in the interrupt vector table and creates and initializes a timer with the pollTime attribute. Once the timer is started, it will set the hardware timer so that its ISR handleTimerInterrupt() will be invoked, which first resets the timer count and then calls the poll() function.

3.8.5 Consequences

Polling is simpler than the setup and use of the Interrupt Service Routines, although periodic polling is usually implemented with an ISR tied to a poll timer.

Polling can check many different devices at the same time for status changes but is usually less timely than interrupts. For this reason, care must be taken that if there are deadlines associated with the data or signals that the poll time plus the response time is always less than the deadlines involved. If data arrive faster than the poll time, then data will be lost. This is not a problem in many applications but is fatal (sometimes literally so) in others.

3.8.6 Implementation Strategies

The simplest implementation is to insert the hardware check in the middle of a main processing loop that executes as long as the system operates. This is called “symmetric opportunistic polling” because it operates the same way all the time, even though the length of time the processing loops take may vary widely. Asymmetric opportunistic polling refers to the practice of placing checks for new data at convenient but unrelated points throughout the processing. This approach allows for better tuning (if you need to be more responsive, add some more checks) but has a larger impact on the primary flow and is harder to maintain.

Periodic polling is polling that takes place with a regular time interval (known as the period) with a bounded variation (known as the jitter). To achieve this regularity, the checks for new data are initiated by a timer tied to an interrupt. Thus the periodic variant of the Polling Pattern is simply a special case of the Interrupt Pattern.

3.8.7 Related Patterns

Periodic polling is a special case of the Interrupt Pattern. In addition, the hardware checks may be done by invoking data acquisition services of Hardware Proxies or Hardware Adapters. In addition, the Observer Pattern can be merged in as well, with the polling element (OpportunisticPoller or PeriodicPoller) serving as the data server and the PollDataClients serving as the data clients.

3.8.8 Example

For the example shown in Figure 3-19, I decided to use the Periodic Polling Pattern to check for three breathing circuit sensors. The first of these sensors monitors the oxygen concentration in the breathing circuit, the second monitors the gas pressure (and holds the gas flow state), and the third monitors the pressure in the circuit. When the BEPeriodicPoller is created, its initializer (BCPeriodicPoller_Init()) calls the BCTimer::installInterruptHandler() service; similarly, the clean-up operation done when the BCPeriodicPoller is destroyed calls BCTimer_stopTiming() and then BCTimer_removeInterruptHandler().

Figure 3-19 Polling example

At some point, the BCPeriodicPoller is commanded to start polling via a call to BCPeriodicPoller_startPolling(). This function calls BC_Timer_startTimer(timeout) service, passing the desired poll period. From then on, until commanded otherwise, the timer will fire periodically and invoke the timer interrupt handler BCTimer_handleTimeInterrupt(), which resets the timer and calls BCPeriodicPoller_poll(). The poll() function then polls the data and state information from the three devices and sends them to the MedicalDisplay.

Code Listing 3-23 BCPeriodicPoller.h

#ifndef BCPeriodicPoller_H

#define BCPeriodicPoller_H

typedef int deviceData;

typedef void (*timerVectorPtr)(void);

#define MAX_POLL_DEVICES (10)

#define DEFAULT_POLL_TIME (1000)

struct BCTimer;

struct BreathingCircuitSensor;

struct MedicalDisplay;

typedef struct BCPeriodicPoller BCPeriodicPoller;

struct BCPeriodicPoller {

unsigned long pollTime;

struct BCTimer* itsBCTimer;

struct BreathingCircuitSensor *itsBreathingCircuitSensor[3];

struct MedicalDisplay* itsMedicalDisplay;

};

void BCPeriodicPoller_Init(BCPeriodicPoller* const me);

void BCPeriodicPoller_Cleanup(BCPeriodicPoller* const me);

/* Operations */ void BCPeriodicPoller_poll(BCPeriodicPoller* const me);

void BCPeriodicPoller_setPollTime(BCPeriodicPoller* const me, unsigned long t);

void BCPeriodicPoller_startPolling(BCPeriodicPoller* const me);

void BCPeriodicPoller_stopPolling(BCPeriodicPoller* const me);

struct BCTimer* BCPeriodicPoller_getItsBCTimer(const BCPeriodicPoller* const me);

void BCPeriodicPoller_setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer);

int BCPeriodicPoller_getItsBreathingCircuitSensor(const BCPeriodicPoller* const me);

void BCPeriodicPoller_addItsBreathingCircuitSensor(BCPeriodicPoller* const me, struct BreathingCircuitSensor * p_BreathingCircuitSensor);

void BCPeriodicPoller_removeItsBreathingCircuitSensor(BCPeriodicPoller* const me, struct BreathingCircuitSensor * p_BreathingCircuitSensor);

void BCPeriodicPoller_clearItsBreathingCircuitSensor(BCPeriodicPoller* const me);

struct MedicalDisplay* BCPeriodicPoller_getItsMedicalDisplay(const BCPeriodicPoller* const me);

void BCPeriodicPoller_setItsMedicalDisplay(BCPeriodicPoller* const me, struct MedicalDisplay* p_MedicalDisplay);

BCPeriodicPoller * BCPeriodicPoller_Create(void);

void BCPeriodicPoller_Destroy(BCPeriodicPoller* const me);

void BCPeriodicPoller___setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer);

void BCPeriodicPoller__setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer);

void BCPeriodicPoller__clearItsBCTimer(BCPeriodicPoller* const me);

#endif

Code Listing 3-24 BCPeriodicPoller.c

#include "BCPeriodicPoller.h"

#include "BCTimer.h"

#include "BreathingCircuitSensor.h"

#include "MedicalDisplay.h"

static void cleanUpRelations(BCPeriodicPoller* const me);

void BCPeriodicPoller_Init(BCPeriodicPoller* const me) { me->pollTime = DEFAULT_POLL_TIME; me->itsBCTimer = NULL;

int pos;

for(pos = 0; pos < 3; ++pos) {

me->itsBreathingCircuitSensor[pos] = NULL;

me->itsMedicalDisplay = NULL;

BCTimer_installInterruptHandler(me->itsBCTimer);

me->pollTime = DEFAULT_POLL_TIME;

}

void BCPeriodicPoller_Cleanup(BCPeriodicPoller* const me) {

BCTimer_stopTimer(me->itsBCTimer);

BCTimer_removeInterruptHandler(me->itsBCTimer);

cleanUpRelations(me);

}

void BCPeriodicPoller_poll(BCPeriodicPoller* const me) {

int state, data;

data = BreathingCircuitSensor_getData(me->itsBreathingCircuitSensor[0]);

MedicalDisplay_showO2Concentration(me->itsMedicalDisplay, data);

data = BreathingCircuitSensor_getData(me->itsBreathingCircuitSensor[1]);

state = BreathingCircuitSensor_getState(me->itsBreathingCircuitSensor[1]);

MedicalDisplay_showGasFlow(me->itsMedicalDisplay, data);

MedicalDisplay_showGasFlowStatus(me->itsMedicalDisplay, state);

data = BreathingCircuitSensor_getData(me->itsBreathingCircuitSensor[2]);

MedicalDisplay_showCircuitPressure(me->itsMedicalDisplay, data);

}

void BCPeriodicPoller_setPollTime(BCPeriodicPoller* const me, unsigned long t) {

me->pollTime = t;

}

void BCPeriodicPoller_startPolling(BCPeriodicPoller* const me) {

BCTimer_startTimer(me->itsBCTimer, me->pollTime);

}

void BCPeriodicPoller_stopPolling(BCPeriodicPoller* const me) {

BCTimer_stopTimer(me->itsBCTimer);

}

struct BCTimer* BCPeriodicPoller_getItsBCTimer(const BCPeriodicPoller* const me) {

return (struct BCTimer*)me->itsBCTimer;

}

void BCPeriodicPoller_setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer) {

if(p_BCTimer != NULL)

{

BCTimer__setItsBCPeriodicPoller(p_BCTimer, me);

}

BCPeriodicPoller__setItsBCTimer(me, p_BCTimer);

}

int BCPeriodicPoller_getItsBreathingCircuitSensor(const BCPeriodicPoller* const me) {

int iter = 0;

return iter;

}

void BCPeriodicPoller_addItsBreathingCircuitSensor(BCPeriodicPoller* const me, struct BreathingCircuitSensor * p_BreathingCircuitSensor) {

int pos;

for(pos = 0; pos < 3; ++pos) {

if (!me->itsBreathingCircuitSensor[pos]) {

me->itsBreathingCircuitSensor[pos] = p_BreathingCircuitSensor;

break;

}

}

}

void BCPeriodicPoller_removeItsBreathingCircuitSensor(BCPeriodicPoller* const me, struct BreathingCircuitSensor * p_BreathingCircuitSensor) {

int pos;

for(pos = 0; pos < 3; ++pos) {

if (me->itsBreathingCircuitSensor[pos] == p_BreathingCircuitSensor) {

me->itsBreathingCircuitSensor[pos] = NULL;

}

}

}

void BCPeriodicPoller_clearItsBreathingCircuitSensor(BCPeriodicPoller* const me) {

{

int pos;

for(pos = 0; pos < 3; ++pos)

{

me->itsBreathingCircuitSensor[pos] = NULL;

}

}

}

struct MedicalDisplay* BCPeriodicPoller_getItsMedicalDisplay(const BCPeriodicPoller* const me) {

return (struct MedicalDisplay*)me->itsMedicalDisplay;

}

void BCPeriodicPoller_setItsMedicalDisplay(BCPeriodicPoller* const me, struct MedicalDisplay* p_MedicalDisplay) {

me->itsMedicalDisplay = p_MedicalDisplay;

}

BCPeriodicPoller * BCPeriodicPoller_Create(void) {

BCPeriodicPoller* me = (BCPeriodicPoller *) malloc(sizeof(BCPeriodicPoller));

if(me!=NULL)

{

BCPeriodicPoller_Init(me);

}

return me;

}

void BCPeriodicPoller_Destroy(BCPeriodicPoller* const me) {

if(me!=NULL)

{

BCPeriodicPoller_Cleanup(me);

}

free(me);

}

static void cleanUpRelations(BCPeriodicPoller* const me) {

if(me->itsBCTimer != NULL)

{

struct BCPeriodicPoller* p_BCPeriodicPoller = BCTimer_getItsBCPeriodicPoller(me->itsBCTimer);

if(p_BCPeriodicPoller != NULL)

{

BCTimer___setItsBCPeriodicPoller(me->itsBCTimer, NULL);

}

me->itsBCTimer = NULL;

}

if(me->itsMedicalDisplay != NULL)

{

me->itsMedicalDisplay = NULL;

}

}

void BCPeriodicPoller___setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer) {

me->itsBCTimer = p_BCTimer;

}

void BCPeriodicPoller__setItsBCTimer(BCPeriodicPoller* const me, struct BCTimer* p_BCTimer) {

if(me->itsBCTimer != NULL)

{

BCTimer___setItsBCPeriodicPoller(me->itsBCTimer, NULL);

}

BCPeriodicPoller___setItsBCTimer(me, p_BCTimer);

}

void BCPeriodicPoller__clearItsBCTimer(BCPeriodicPoller* const me) {

me->itsBCTimer = NULL;

}

3.9 So, What Did We Learn?

In this chapter, we’ve looked at a number of patterns useful for manipulating hardware. The Hardware Proxy Pattern focuses on encapsulation of hardware-specific details while the Hardware Adapter Pattern provides a straightforward means to adapt different but similar devices for the needs of the system. Many times, complex interactions of hardware are necessary for proper system functions, a benefit of the Mediator Pattern. The Observer Pattern supports dynamic addition and removal of clients for hardware data, such as from sensors.

The last three patterns provide different means for interfacing to hardware. The Debouncing Pattern handles the common problem of hardware that produces intermittent contact during open and closure of metal contact switches. The Interrupt Pattern supports the highly timely response to events of interest. The Polling Pattern repeatedly polls the hardware for new sensor data and device state information.

These are basic patterns in the sense that they all address low-level concerns about interfacing with the hardware. The more difficult design issues have to do with transforming the commands and data within the system to achieve the system goals. The next chapter provides patterns that deal with issues of concurrent execution, from basic concepts to scheduling execution, avoiding race conditions and deadlock, and synchronizing tasks when necessary.

Chapter 5 deals with finite state machines – their implementation and usage; while in Chapter 6 we look at safety and reliability.

1 Simon, D., 1999. An Embedded Software Primer. Addison-Wesley.

2 Barr, M. Massa, A., 2007. Programming Embedded Systems with C and GNU Development Tools. O’Reilly.

3 Pont, M.J., 2002. Embedded C. Addison-Wesley.

4 Ganssle, J., 2007. Embedded Systems: World Class Designs. Newnes.

5 Although you can get around that by doing something as circuitous asstatus = *(statusBits*)&f(that is, casting the address of f (unsigned char) to be an address of the structure type statusBits) but I can hardly recommend that!

6 Each of these patterns will be shown as a UML diagram and code will be provided for the examples. The Appendix provides a short overview of the UML notation.

7 By the term feature, I am referring primarily to data elements (attributes or variables), operations and functions, states, and event receptions.

8 It may not be in high-security applications however. In those cases, it may be necessary to always have the data stored in encrypted formats to make it more difficult to violate data security protocols.

9 Gamma, E., Helm, R., Johnson, R., Vlissides, J., 1995. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.

10 Gamma, E., Helm, R., Johnson, R., Vlissides, J., 1995. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.

11 Douglass, B.P., 2003. Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems. Addison-Wesley.

12 An entirely different Rule #1 than in Zombieland. See www.zombieland.com (Sony Pictures, 2009), possibly the most awesome zombie movie ever.

13 That is, the clients know about the server but the server doesn’t know who its clients are.

14 Urgency is a measure of the rapidity with which an event must be handled, usually expressed as the nearness of a deadline. This topic will be discussed in more detail in Chapter 4.

15 This is Z80 assembly language (my personal favorite). Your processor mileage may vary.