Serial Port Design Pattern
Embedded software has to interact with hardware devices of various types. In this article we will consider a design pattern for handling hardware interfaces for a serial port. A serial port interface drives serial links like HDLC, RS-232, RS-422 etc.
Intent
The Serial Port design pattern defines a generic interface with a serial port device. The main intention here is to completely encapsulate the interface with the serial port hardware device. All classes interfacing with the serial port will not be impacted by change in the hardware device.
Also Known As
- Asynchronous Communication Adaptor
- Serial Interface
- Serial Device
Motivation
The main motivation for development of this design pattern is to minimize dependency on hardware. Very often the hardware team decides to change the interface devices due to cost, end-of-life or functionality improvements. This involves a costly software porting exercise. Serial port design pattern encapsulates the register and interrupt handling specific to a device. Change in the device will just result in changes to just the classes involved in implementing this design pattern.
Applicability
This pattern is applicable to all serial devices that involve direct byte transfers to and from the device using program instructions. In such devices, serial transmission is implemented by the device interrupting the processor for data bytes. When data is received on the serial link, the device interrupts the processor to transfer data.
Structure
Serial Port is implemented with the SerialPort and SerialPortManager classes. The SerialPortManager maintains an array of SerialPort objects. Each SerialPort object manages the transmit and receive buffers. The SerialPortManager class also implements the interrupt service routine.
Participants
The key participants in this pattern are:
- Serial Port Manager: Manages all the Serial Ports on the board.
- Interrupt Service Routine: Implemented as a static method in Serial Port Manager.
- Serial Port: Handles the interface with a single serial port device. This class also contains the transmit and receive buffers.
- Transmit Queue: This queue contains messages awaiting transmission on the serial port.
- Receive Queue: Messages received on the serial link are stored in this queue.
Collaboration
The interactions between the participants are shown in the figure below:
Consequences
Implementing the Serial Port design pattern keeps the hardware dependent code confined to a few classes in the system. This simplifies the software port to new hardware.
Implementation
The implementation of this design pattern is explained in terms of handling of message transmission and reception. The important point to note here is that the code executing in the context of the ISR is kept to the minimum. All the CPU intensive operations are carried out at the task level.
Transmitting a Message
- SerialPortManager's constructor installs the InterruptServiceRoutine().
- Serial Port's constructor initializes the interrupts so that the transmitter empty interrupt is disabled and the receiver ready interrupt is enabled.
- A message is enqueued to the SerialPort by invoking the HandleTxMessage() method.
- The method enqueues the message in the Transmit Queue and checks if this is the first message in the queue.
- Since this is the first message in the queue, the message is removed from the queue and copied into the transmission buffer.
- Then the transmitter empty interrupt is enabled.
- The device raises an interrupt as soon as it is enabled.
- The InterruptServiceRoutine() is invoked.
- The ISR polls the SerialPorts to select the interrupting device.
- The HandleInterrupt() method of the SerialPort is invoked.
- SerialPort checks the interrupt status register to determine the source of the interrupt.
- This is a transmit interrupt, so the HandeTxInterrupt() method is invoked.
- The byte to be transmitted is copied into the transmit data register of the device.
- The interrupt handling sequence presented above is repeated until all bytes have been transmitted.
- When the message transmission has been completed, a transmission complete event is sent to the task.
- This event is routed by the SerialPortManager to the SerialPort.
- SerialPort checks if the transmit queue has any more messages.
- If a message is found, message transmission of the new message is initiated. If no message is found, the transmitter empty interrupt is disabled.
Receiving a Message
- When the first byte of a message is received, the SerialPort's receive interrupt handler interprets it as the length of the message.
- The interrupt handler keeps receiving the bytes until the complete message has been received.
- At this point a message receive complete event is dispatched to the task.
- The Serial Port's event handler allocates memory for the received message and writes the new message into the receive queue.
- Then it cleans up the receive buffer for the next message.
Sample Code and Usage
Here is the code for a typical implementation of this pattern:
Serial Port
Known Uses
This pattern can be used to implement serial interfaces where data handling is handled in interrupt service routines. It is not suitable for direct memory access (DMA) based serial devices.