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

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:

Collaboration

The interactions between the participants are shown in the figure below:

UML Class Diagram for Serial Port Pattern

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

  1. SerialPortManager's constructor installs the InterruptServiceRoutine().
  2. Serial Port's constructor initializes the interrupts so that the transmitter empty interrupt is disabled and the receiver ready interrupt is enabled.
  3. A message is enqueued to the SerialPort by invoking the HandleTxMessage() method.
  4. The method enqueues the message in the Transmit Queue and checks if this is the first message in the queue.
  5. Since this is the first message in the queue, the message is removed from the queue and copied into the transmission buffer.
  6. Then the transmitter empty interrupt is enabled.
  7. The device raises an interrupt as soon as it is enabled.
  8. The InterruptServiceRoutine() is invoked.
  9. The ISR polls the SerialPorts to select the interrupting device.
  10. The HandleInterrupt() method of the SerialPort is invoked.
  11. SerialPort checks the interrupt status register to determine the source of the interrupt.
  12. This is a transmit interrupt, so the HandleTxInterrupt() method is invoked.
  13. The byte to be transmitted is copied into the transmit data register of the device.
  14. The interrupt handling sequence presented above is repeated until all bytes have been transmitted.
  15. When the message transmission has been completed, a transmission complete event is sent to the task.
  16. This event is routed by the SerialPortManager to the SerialPort.
  17. SerialPort checks if the transmit queue has any more messages.
  18. 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

  1. When the first byte of a message is received, the SerialPort's receive interrupt handler interprets it as the length of the message.
  2. The interrupt handler keeps receiving the bytes until the complete message has been received.
  3. At this point a message receive complete event is dispatched to the task.
  4. The Serial Port's event handler allocates memory for the received message and writes the new message into the receive queue.
  5. 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.

Related Patterns