A major advantage to using a microcontroller today is that embedded software developers typically don’t have to write their own drivers anymore. It’s very common for the microcontroller vendor to provide software frameworks that abstract the hardware and allow developers to make simple function calls to initialize, read and write to peripherals such as SPI, UART, analog to digital converters and so on. However, developers still often need to write drivers to interact with external integrated circuits that could be sensors, actuators, motor controllers and so forth. In today’s post, we will explore several best practices for writing your own external device drivers.
Best Practice #1 – Separate Implementation from Configuration
A key aspect to writing any driver is to separate the implementation from the configuration. The separation helps to ensure that the driver will be reusable and flexible. For example, the driver could easily be compiled into an object file so that a developer can’t see the insides and so it could be used on multiple projects. The developer would still have access to a configuration module that they can use to configure the driver for their specific application needs. If a chance is needed to the configuration, it won’t impact the driver design or force other projects using the driver to either be out of sync or forced to accept new changes and go through a validation cycle.
Separating the implemenation from the configuration also allows the external hardware to be abstracted so that the developer doesn’t need to fully understand what is happening in the hardware, just like on the microcontroller. I have often wished that integrated circuit providers would stop providing configuration tool GUI’s and instead spend their effort writing a reusable and portable driver for their devices. Every customer of theirs struggles to write a driver and understand their module by reading ~100 page datasheets when they could say here is a configuration tool, the software driver and a five page paper on how to interact with the driver.
Best Practice #2 – Create a simple low-level interface
When writing drivers, I often see developers try to do too much in their implementation. The driver becomes a mix of a driver and application code. The interface for the driver should contain a simple interface that includes:
- An initialization function
- A write function
- A read function
Anything more than that is really starting to get into the application domain! My logic behind this is quite simple. The driver should simply provide the ability to communicate with the device and allow read and write operations. An application module would then access these read and write functions to build out reusable application components that are needed by the higher-level application code.
Best Practice #3 – Provide error detection
Unfortunately, a lot of developers will write code that just assumes that everything will always go alright. When it comes to writing the driver for an external device, we don’t have that luxury. The device driver should consider potential errors and faults. For example, what happens if the bus goes down? Can the driver time-out and provide an error? If a read operation is performed, can the function return whether the read was successful? What if a parity error occurs?
There are several different ways to provide error and fault detection in the driver. First, every function could return an error code. This error code would simply be true if the operation is successful or false if a problem occurred. Second, if a problem did occur, then there could be an addition to the device interface that would allow errors to be checked. I will sometimes include additional operations that:
- Return the driver error state
- Clear the driver error state
Again, this gives flexibility and fault detection capabilities to the driver and will allow the application code to carefully monitor whether driver operations were successful or not.
Writing device drivers for integrated circuits outside the microcontroller is one of the last frontiers for embedded developers where we still must write our own drivers. From where I sit, I don’t see this changing anytime soon which means developers still need to be comfortable with driver development. In today’s post we examined several best practices for writing drivers for external integrated circuits. These best practices will help the reader develop scalable and reusable drivers that can detect faults and allow the application code to respond appropriately.