Design Article
Comment
Jacob_Beningo
Unfortunately not all processor vendors have consistent register mapping for a ...
one_armed_bandit
And you can write the code in C and use structs with the info. In other words, ...
Developing reusable device drivers for MCUs
Jacob Beningo
12/4/2012 12:32 PM EST
The rate at which society expects products to be released and refreshed has been steadily decreasing over the last two decades. The result has left development teams scrambling to implement the most basic product features before the product launch date. Designing a new product from scratch takes time, effort and money that is often unavailable.
Embedded software developers often look to chip manufacturers to provide example code and drivers for their processors to help accelerate the design cycle. Unfortunately the provided code often lacks a layered architecture that would allow the code to be easily reused. In addition, the code is often sparingly documented which makes fully understanding what is being done difficult. The result is poorly crafted code that is difficult to read and comprehend that offers no possibility of reuse with the next product. Time and effort is forced to focus on developing low level drivers rather than on implementing the product features at hand.
This paper will explore methods and techniques that can be used to develop reusable abstracted device drivers that will result in a sped up development cycle. A method for driver abstraction is examined in addition to a brief look at key C language features. A layered approach to software design will be explored with common driver design patterns for Timers, I/O, and SPI which can then be expanded upon to develop drivers for additional peripherals across a wide range of processor platforms.
Driver Code Organization
There are many different ways in which software can be organized. In fact, nearly every engineer has their own opinion on how things should be done. In this paper, with the intention of creating reusable drivers and reusable design patterns, the software will be broken up into layers which will include driver and application layers. The primary focus will be on the driver layer with the intent that the same basic principles can be applied to higher layers.
The driver layer will consist of peripheral interface code as one would expect; however, the drivers will attempt to remain generic to the peripheral. This will allow them to be used and configured for any range of applications. The driver layer can be compiled into a separate library that can then be dropped into any project. The configuration for each driver would be contained within configuration modules that would be part of its own layer. Each application can then uniquely configure the driver and application layers to match the requirements. Figure 1 shows how the configuration and driver code would be organized.

Figure 1 – Layered Organization
Application Programming Interface (API’s)
One of the most critical steps in developing a reusable driver framework is to define the Application Programming Interface (API’s). Properly defining the API’s allows for a standard interface to be used to access hardware across multiple platforms and projects. This is something that high level operating systems have done relatively well over the years.
There are many possible ways in which these API’s can be defined and is often dictated by programmer preferences. For this reason, the developed API’s should become part of the development teams’ software coding standard. The end goal is to define the API’s in a way that meets general requirements of the system but allows the power of each peripheral to be fully utilized.
There are software API’s available that can provide a starting point. It is possible to adopt formats used by the Linux kernel, Arduino libraries, AUTOSAR, or a custom driver API that is a mix. It really doesn’t matter provided that the format is well documented and used across all platforms and projects.
It is useful to define the API’s for common and useful features for each of the peripherals. Each peripheral will require an initialization function in addition to functions that allow the peripheral to perform its functions. For example, Listing 1 shows a possible interface for a Digital Input/Output driver. It consists of an initialization function, a read, write and toggle function.

Listing 1 – Digital Input/Output API
Next: Title-1


Francois Best
12/4/2012 2:15 PM EST
There might be a link missing there..
Sign in to Reply
Stephen.Evanczuk
12/5/2012 2:49 AM EST
It should be ok now. Thanks for noting that!
Sign in to Reply
rocketdog
12/5/2012 6:01 PM EST
There is significant runtime overhead with the simple case of two SPI peripherals. Imagine 5 UARTS? The ARM, Renesas RX, and Microchip 16 & 32 bit CPU have consistent register mapping for a given peripheral, and SPI1 and SPI2 differ only by the base address of the peripheral. Given this, it is easy to create a C+ (OOP in C) structure which will abstract all the port/pin/register to compile time constructors. No need for all this if/else overhead. The object contains it's own mapping. The object contains member functions to handle all the configuration, settings, and other CPU specific stuff. Only the details in the member functions need to be changed for each CPU. The interface can be the same.
Even better if one has a a C++ compiler because one can use "placement new" to create peripheral accessor cases that plop right down on top of the register map.
And modern compilers generate just as good of code in C++ as C.
Sign in to Reply
one_armed_bandit
12/5/2012 10:42 PM EST
And you can write the code in C and use structs with the info. In other words, I agree with @rocketdog in the general, just detest C++ in the specific. However, not trying to start a religious war over languages.
The simplest is to always abstract the API and (somehow) clump all of the per-device info (register addresses, status register bits, etc) in one struct per individual device.
You can even abstract the API to all devices - shucks, look at the *nix open/close/read/write/ioctl model. Works great, even if you need to separate char vs block devices.
Sign in to Reply
Jacob_Beningo
12/10/2012 6:14 PM EST
Unfortunately not all processor vendors have consistent register mapping for a given peripheral. The techniques described here work for parts that are not only consistent but also inconsistent! The run-time overhead only occurs the first time that the peripheral is setup and the overhead is minimal.
In an ideal world the use of C++ or OOP in C would be the optimal way to do this. But since architectures like the 8051 continue to be used throughout the industry these techniques explored a way to write drivers that could be considered "universal" (yes i know i agree i'm stretching it)
Some of the application code certainly could use optimization but the intent is not to provide the reader with perfect optimized code but instead demonstrate how with careful planning a framework (API's) could be developed that allows for easy reuse.
Thank you for your comments! Your feedback is greatly appreciated!
Sign in to Reply