A major advantage to using a microcontroller today is that embedded software developers typically don’t have to write their own drivers anymore. Instead, it’s widespread 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. Today’s post will explore several best practices for writing your own external device drivers.
Best Practice #1 – Separate Implementation from Configuration
A key aspect of 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 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 change is needed to the configuration, it won’t impact the driver design or force other projects to either be out of sync or forced to accept new changes and go through a validation cycle.
Separating the implementation from the configuration allows the external hardware to be abstracted. The developer doesn’t need to fully understand what is happening in the hardware, just like 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 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
I often see developers try to do too much in their implementation when writing drivers. 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. First, the driver should 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 needed by the higher-level application code.
Best Practice #3 – Provide error detection
Unfortunately, many developers will write code that assumes that everything will always go alright. When writing the driver for an external device, we don’t have that luxury. Instead, 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? Can the function return whether the read was successful if a read operation is performed? What if a parity error occurs?
There are several ways to provide error and fault detection in the driver. First, every function could return an error code. This error code would be true if the operation is successful or false if a problem occurred. Second, if a problem did occur, there could be an addition to the device interface that would allow errors to be checked. Finally, I will sometimes include additional operations that:
- Return the driver error state
- Clear the driver error state
Again, this gives the driver flexibility and fault detection capabilities and will allow the application code to monitor whether driver operations were successful or not carefully.
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. Today’s post 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.