My favorite line from the Matrix is that “there is no spoon”, and today that is a wise statement for embedded software developers to take to heart. Embedded software developers traditionally are hardware centric, believing that they can’t move forward without the hardware (sometimes this is true). Much like Neo, developers need to accept that “There is no hardware”! In today’s post, we are going to explore the mental paradigm shift developers need to make to develop their software without hardware.
Application Centric Development
The first shift in thinking that an embedded developer needs to make in today’s modern development cycles is that there is no hardware. We don’t start with the hardware and work our way up, but instead, we focus our attention and development on the application. Developers should be focusing on the application features, how they relate to each other and the data that they operate on.
Focusing on the application and not the hardware comes with several major advantages. First, it moves a developers focus to the application features. Second, with the focus on the application, developers can identify the data that the various features require as input and output along with how that data flows through the application. This focus on the data has the added benefit of abstracting and removing hardware dependencies! (We will explore this further in the next section). Finally, application centric development allows us to simulate, model and test our application code without the hardware. This opens up the ability for us to create automated regression tests, minimize on-target debug time and test our application in a PC environment, which is easier to work in then an on-target environment.
There is no Button!
Let’s look at a simple example. Embedded software developers often need to write a function that can debounce a switch. Traditionally, a developer is going to call their debounce function periodically and the function will read the GPIO line and then run the debounce algorithm. The problem here is that the debounce function is directly dependent upon the hardware! If the GPIO line changes, the function needs to be updated! Change hardware to a new microcontroller, the function needs to be updated with the new GPIO driver calls. Interested in unit testing the function? It’s far more difficult because the function is hardware dependent.
Obviously, this is not a great solution, even though we write our code like this all the time! Instead, we need to recognize that there is no button! There is only data that contains the state that represents the button. Instead of having the debounce switch directly access the GPIO hardware, the button debounce function should take a parameter that represents the current switch state. This decouples the function from the hardware and allows the function to be completely tested without the hardware! All one needs to do is pass in data to test that the switch debounce algorithm is working properly! This also means that we can build regression tests!
Designing Hardware-less Systems
In general, there are a few steps that developers can follow to write their application code so that they break their hardware dependencies.
First, identify the data that is generated or consumed by hardware. This data will become a parameter that is passed into the application feature function. For the debounce example, this is nothing more than the ButtonState. The function prototype might look something like the following:
bool Button_Debounce(ButtonState_t const ButtonState);
Next, create an abstraction that can be used to fetch the hardware state. This function when running on a PC will just link to a data generator that can simulate the hardware. When the hardware is present, the function links to a GPIO driver that returns the hardware state. This enables a developer to operate with or without hardware. This would then result in application code that looks something like the following:
bool ButtonDebounced = false;
ButtonState_t ButtonState = Button_StateGet();
ButtonDebounced = Button_Debounce(ButtonState);
Finally, an abstraction can also be made for posting data to the hardware. This is not necessary for a debounce algorithm, but it may be necessary if the application component is going to be controlling an LED, valve, motor, etc. This again, allows a developer to switch between running the application on a PC or the hardware.
Developers who are looking to improve the robustness of their code, decrease overall costs and time-to-market can greatly benefit from writing their application code without the hardware. This isn’t always possible or worthwhile, but for many modern systems this is a technique that can dramatically improve system design. It has the benefit of making the application easily testable and minimize the time spent debugging on-target. The question becomes, can you accept that there is no hardware?
Nice article. It was hard for me to change the mindset to develop firmware without hardware. This approach is specifically useful for unit testing and automation of testes for CI for example. It can same a lot of development time and resources.