I have compiled a simple list of DOs and DO NOTs. They should help developing solid software for small scale Embedded Systems. I also plan to write a more elaborate article or even book about this subject. It actually contains a couple of issues and ideas that I collected over the last 3 years since leaving university.
DO
- Design your software to use independent software modules
- Design software interfaces to be portable and movable (e.q. allow a different module to implement it without having to change too much code)
- Design the software to be hardware independent
- Hide hardware definitions behind a module that can be exchanged as a whole (a module LCD and a module OLED which both implement a “Display” subsystem)
- Document all interfaces between module
- Typedef all structs and enums in use
- Use meaningful status and return codes
- Use state machines whenever useful
- Use enum types for everything that is not a continuous measurement including status codes
- Use structured data and organize all data that belongs together into structs
- Limit code per function/method to fit onscreen
- Design all software modules to use a common API and call structure (e.q. Module_Init(), Module_Cyclic() and Module_GetXYZ())
- Document the assumed timing for a task or process
- Use C99 types for data
- Hide compiler specifics behind macros
- Use the keyword static
- Use the assert macro while developing for a SIL or unit test
- Prototype the software independent of the actual target hardware and test it on the PC before trying it on actual hardware
- Document every behavioural aspect of the software
- Follow a generic code design pattern that defines nomenclature of values and functions, source code style and usage of keywords e.q. the Embedded C Coding Standard
- Use static code checking tools like PC-Lint often
- Separate code and data – defined default data should reside in a separate data module, e.q. device definitions
DO NOT
- Mix multiple functionalities into one software module, write one module per defined functionality
- Use ANSI C default types unless necessary (e.q. calls of the stdlib)
- Expect the software to behave correctly, assume fault states when ever possible
- Use global variables, provide access functions instead
- Write for a specific microcontroller or target board – expect that the hardware might change
- Use fix values for controlling timing, allow the user to configure any timing requirements
- Use magic numbers, provide symbolic configuration constants instead
- Assume that the source code is enough for documentation
- Rely on any state and introduce failsafe states wherever possible
- Ignore compiler warnings
- Use master include files, only make interfaces available to a module that are necessary
- Omit testing