uCR proper does not operate devices, or expect any to be present, but ancillary libraries include classes that drive various devices. The application may add more device classes simply by writing the code needed to manipulate device registers, and putting that code into classes.
Devices make good objects. Programmers seem to understand and respond well to this technique. As an example, a uCR library includes classes for communicating with a host through a PCI bus. The class ``BUS'' has a subtype BUS::Device that is the abstract type of a bus interface device. The PLX9060 class is derived from BUS::Device and drives the PLX Technology PCI9060 [11] interface chip. The I960RP class is also derived from BUS::Device and drives the ATU function unit of the Intel i960rp [6] microprocessor.
Figure 2: BUS Class hierarchy
The BUS class in Figure uses BUS::Device objects to implement a channel protocol with the host processor. The abstract BUS::Device class has a minimum set of methods used by the BUS class for sending packets to the host and getting packets back.
The classes derived from BUS::Device implement the minimum methods (which are pure virtual) and others that the device can support in addition to the minimum requirements. This is a fairly classic object oriented design. A similar hierarchy exists for timers (in case you choose to use them) where the abstract Ticker class provides a common interface for a generic clock and the derived classes implement the virtual methods as necessary for the specific hardware available.
Figure 3: Ticker Class hierarchy
The Ticker class hierarchy in Figure is another example of an object oriented device driver design, also taken from the uCR libraries. The Ticker base class provides the methods of a generic interval timer, that portable code may use. The concrete class derived from the Ticker drives the real hardware timer to implement the behavior of the abstract class.
Even when inheritance does not make sense, device driver code fits well into classes. For example, the XC4000 class has the method device_configure() for programming the device, but does not specifically support or require any derivation.
Device classes can be templates, too. The uCR libraries include a template class LED that is a driver for light-emitting diodes. This template is also useful for controlling general purpose output bits, and other miscellaneous jobs.
template <class RT> class LED { public: explicit LED(volatile RT*base); void set(unsigned idx); void clr(unsigned idx); [...] };
Often, LEDS are connected to a register somewhere in the address space of the processor. The register may be a word, or a byte, or whatever the hardware costs and design allow. The programmer uses as the template parameter the integer type needed to access the word (for example ``char'' or ``unsigned long'') and passes to the constructor the address of the register. Individual bits of the register can then be set or cleared with the ``set()'' and ``clr()'' methods.
The nice feature of this template is that the programmer can use custom types for RT that for example store its value in memory outside the address space, such as I/O space.