Embedded application software will at times access the lowest level firmware and perform some hardware control. The design and implementation of drivers are crucial to ensure that a system meets its real-time requirements. The following 5 tips are considerations that every developer should keep in mind when designing drivers. Let’s follow the editor of “Microcontrollers and Embedded Systems Applications” to explore these design methods.
1. Use Design Patterns
Design patterns are solutions to problems that frequently occur in software. Developers can waste valuable time and budget reinventing a solution from scratch or choose the most suitable solution from their toolbox to address the issue at hand. Since low-level drivers have matured since the advent of microprocessors, why not leverage existing mature solutions?
Driver design patterns can be broadly categorized into the following four types: Bit bang, polling, interrupt-driven, and direct memory access (DMA).
Bit bang mode: When a microcontroller has no peripherals to perform functions or when all peripherals are already in use and a new request arises, developers should opt for the Bit bang design pattern. Bit bang solutions are efficient but often require significant software overhead to ensure their implementation capability. The Bit bang mode allows developers to manually execute communication protocols or external behaviors.
Polling mode is used to simply monitor events in a polling scheduling manner. Polling mode is suitable for very simple systems, but many modern applications require interrupts.
Interrupts allow developers to handle events as they occur without waiting for the code to manually check.
DMA (Direct Memory Access) mode allows other peripherals to handle data transfer requirements without the intervention of drivers.
2. Understand Real-Time Behavior
A real-time system’s ability to meet real-time requirements depends on its drivers. Poorly written drivers are inefficient and may cause uninformed developers to compromise system performance. Designers need to consider two characteristics of drivers: blocking and non-blocking. A blocking driver prevents any other software from executing until it completes its work. For example, a USART driver may load a character into the transmission buffer and wait for the transmission end flag before proceeding to the next operation.
On the other hand, non-blocking drivers generally use interrupts to achieve their functionality. The use of interrupts can prevent the driver from intercepting the execution of other software while waiting for an event to occur. A USART driver can load a character into the transmission buffer and then wait for the main program to issue the next command. The setting of the transmission end flag will trigger an interrupt to allow the driver to proceed to the next operation.
Regardless of the type, to maintain real-time performance and prevent failures in the system, developers must understand the average execution time of the driver and the worst-case execution time. A complete system could face greater safety issues due to a potential risk.
3. Reuse Design
Why reinvent the wheel when time and budget are tight? In driver development, reuse, portability, and maintainability are key requirements. Many of these features can be illustrated through the design and use of a hardware abstraction layer (HAL).
The hardware abstraction layer (HAL) provides developers with a way to create a standard interface to control the peripherals of a microcontroller. Abstraction hides implementation details and instead provides visual functions, such as Usart_Init and Usart_Transmit. This method allows any USART, SPI, PWM, or other peripherals to possess common characteristics supported by all microcontrollers. By using HAL, the underlying specific device details are hidden, allowing application developers to focus on application needs rather than how the underlying hardware works. At the same time, HAL provides a container for reuse.
4. Refer to Data Sheets
Microcontrollers have become increasingly complex over the past few years. Previously, understanding a microcontroller fully required mastering a single data sheet of about 500 pages. Nowadays, a 32-bit microcontroller typically consists of partial data sheets, documentation for the entire microcontroller series, hundreds of documents for each peripheral, and all errata combined into one data sheet. Developers need to understand thousands of pages of documents to fully grasp this content.
Unfortunately, all these data sheets are necessary for a driver to be implemented reasonably. Developers must collect and sort the information contained in each data sheet from the very beginning. Often, each of them needs to be accessed to get the peripherals started and running. Key information is scattered (or hidden) across various types of data sheets.
5. Beware of Peripheral Failures
Recently, I had the opportunity to port a series of microcontroller drivers to other microprocessors. The manufacturer and data sheets indicated that the PWM peripherals were the same between these two series of microcontrollers. However, the reality was that there were significant differences when running the PWM driver. The driver only worked on the original microcontroller and was ineffective on the new series of microcontrollers.
After repeatedly reviewing the data sheets, I discovered in a completely unrelated footnote that the PWM peripheral would be in a fault state upon power-up and required clearing a flag hidden in a register. At the start of driver implementation, confirm potential faults in peripherals and check other seemingly unrelated register errors.