|
We have already looked at classes that provide a high level interface to the
underlying hardware (e.g. Serial Port).
Here we will look at the design of classes corresponding to the individual
devices. The main objective is to keep all register programming information at
one place.
Intent
The Hardware Device Design Pattern encapsulates the actual hardware device
being programmed. The main idea is to encapsulate device register programming
and bit manipulation into a single class dealing with the device.
Also Known As
- Device
- Hardware Interface
- Peripheral Interface
- Peripheral
Motivation
Very often the lowest level of code that interfaces with the hardware is
difficult to understand and maintain. One of the main reasons for this is the idiosyncrasies
of register level programming model of hardware devices. Very often
devices require registers to be accessed in a certain sequence. Defining a class
to represent the device can go a long way in simplifying the code by decoupling
the low level code and register manipulation.
Another motivation for this design pattern is skill sets. Often details about
intricacies of register programming in devices are understood only by the
persons familiar with the hardware design. Many times other low level code might
be written by software engineers with just basic understanding of hardware.
Also note that separating the device programming and logic simplifies porting
of the code to a different hardware platform.
Applicability
This pattern can be used to represent any hardware device.
Structure
The structure of class in this design pattern largely depends upon the
register programming model of the device being programmed. In most cases, this
design pattern would be implemented as a single class representing the device.
In case of complex devices, the device might be modeled as a main device class
and other subclasses modeling different parts of the device.
Participants
This design pattern generally interfaces with other classes that need to
access hardware registers.
Collaboration
Collaboration between the device class and other classes would largely depend
upon the purpose for which the device is being used. For example, a hardware
device that provides timers, serial ports and a DMA controller might be modeled
just as a serial device if other device functionality is not being used.
Consequences
The device involved in low level hardware programming is simplified as
details about register manipulation have been hidden within the class. Thus the
code accessing the hardware device can focus on the logic of the operation being
performed. As noted earlier, porting of hardware dependent software is also
simplified.
Implementation
We will study the implementation of this pattern by working with an imaginary
serial device with the following register set:
- Status Register (STAT): This read only register contains the following
status bits:
- Bit 0: Transmit Buffer Has Empty Space
- Bit 1: Receive Buffer Has Data
- Bit 2: Transmit under run
- Bit 3: Receive overrun
- Action Register (ACT): Bits in this write only register correspond to the
bits in the status register. A condition in the status register can be
cleared by writing the corresponding bit as 1. Note that bit 0 automatically
gets cleared when writes are performed to the transmit buffer. Bit 1 is
cleared automatically when reads are performed from the receive buffer. Bit
2 and 3 however need to be cleared explicitly.
- Transmit Buffer (TXBUF): Write only buffer in which bytes meant for
transmission should be written.
- Receive Buffer (RXBUF): Read only buffer in which received bytes are
stored.
Sample Code and Usage
Here is the code for the Serial_Device class:
|
Serial_Device |
class Serial_Device
{
enum Register_Offsets
{
STAT_REG_OFFSET = 0,
ACT_REG_OFFSET = 0,
TXBUF_OFFSET = 1,
RXBUF_OFFSET = 2
};
enum Status_Register_Bits
{
TX_EMPTY,
RX_DATA,
TX_UNDERRUN,
RX_OVERRUN
};
const long m_status_Register;
const long m_action_Register;
const long m_transmit_Register;
const long m_receive_Register;
public:
Serial_Device(long baseAddress) : m_status_Register(baseAddress + STAT_REG_OFFSET),
m_action_Register(baseAddress + ACT_REG_OFFSET),
m_transmit_Register(baseAddress + TXBUF_OFFSET),
m_receive_Register(baseAddress + RXBUF_OFFSET)
{
}
// Use this method to determine if a transmit interrupt might be
// pending.
bool Transmitter_Has_Space() const
{ return ((io_read(m_status_Register) & TX_EMPTY) == TX_EMPTY); }
// This method returns true if a receive interrupt is pending
bool Receiver_Has_Data() const
{ return ((io_read(m_status_Register) & RX_DATA) == RX_DATA); }
// Returns true if transmit error interrupt is active
bool Transmitter_Has_Error() const
{ return ((io_read(m_status_Register) & TX_UNDERRUN) == TX_UNDERRUN); }
// Returns true if receive error interrupt is active
bool Receiver_Has_Error() const
{ return ((io_read(m_status_Register) & RX_OVERRUN) == RX_OVERRUN); }
// Clear all the error conditions
void Clear_Errors() const
{ io_write(m_action_Register, TX_UNDERRUN | TX_OVERRUN); }
// Write_Data transmits the specified number of bytes. All bytes
// may not be transmitted due to transmit buffer space. The total
// number of transmitted bytes is returned.
int Write_Data(const char *pData, int byteCount) const
{
// Keep writing transmit bytes until all the bytes
// have been transmitted or transmit buffer has no space
for (int txCount=0; i < byteCount; txCount++)
{
if (!Transmitter_Has_Space())
{
break;
}
// Write the byte to the transmit buffer for transmission
io_write(m_transmit_Register, pData[txCount]);
}
// Return the count of transmitted bytes
return txCount;
}
// Read_Data reads the bytes from the device. The maximum number
// of bytes to be read can be specified. The actual number
// of received bytes is returned.
int Read_Data(char *pData, int byteLimit) const
{
// Keep reading received bytes until all the received bytes
// have been copied or specified limit has been reached
for (int rxCount=0; i < byteLimit; rxCount++)
{
if (!Receiver_Has_Data())
{
break;
}
pData[rxCount] = io_read(m_receive_Register);
}
// Return count of received bytes
return rxCount;
}
};
|
Known Uses
This pattern is used to decouple the logical device handling and device
register manipulation.
Related Patterns
|