There are many benefits in having an organized software project when developing firmware. First, such projects make it easy to find modules and files when you need them rather than wasting time hunting around. Second, an organized project can help with project scalability by not requiring constant adjustments to a project make file or project properties. Third, if meticulously organized, developers should be able to easily integrate tools, rebuild their development environment and even port the code more easily. There are lots of ways to organize a code base but in today’s post, I’ll show you how I typically organize my projects.
When starting a new project, it’s good to have some sort of top-level organization to make it easy to find things. Over the years, I’ve adopted a simple but effective structure that I use at the start of every project, which then usually evolves slightly based on the needs of the project. The structure typically starts with the following:
While at first glance this structure may seem self-explanatory, it might be best if I explain what each of these elements does and how I typically handle them.
The Application directory is used to store all the application-specific code. This is custom code is written specifically for the product. A developer can consider this as the secret sauce or the code that differentiates the embedded system from every other one out there. This folder contains application modules along with any configuration modules that go with them that help make the code portable and scalable. One might want to create subdirectories to include source and configuration code, but I’ve often found that isn’t helpful and just requires adding more include paths to the project configuration. If I want to organize these further, do it in the actual Integrated Development Environment (IDE) software.
The documentation directory is used for several types of documentation. First, Doxygen generated manuals and files are all stored under this directory. Second, any additional documentation that is useful such as write-ups about user guides, CRC’s, input file formats, development specific data, and so forth.
The middleware directory stores all the relevant middleware and third-party libraries that are used in the project. I often link to Git submodules in this directory. There was once a best practice where teams would pull in a copy of everything they used, and they would keep it static so that it would never change, and they would always have a backup. This is a great practice, but I’ve found that today it’s more practical to include the Git submodule and then check-out the tagged revisions. With each official build for the project, the versions of each library used are documented. That way, to stay current, I can just pull from the submodule the latest revision versus exporting it, importing it into my repository, adding, committing, and so forth.
The Project directory stores the toolchain specific project information. It’s possible for several things to be in this directory, e.g., to have multiple versions of the same project in different toolchains. One might want to have or test their project in GCC, Keil, IAR, Visual Studio Code, or many other environments. Second, there may be different product SKUs that use a common code base and there are different projects defined for each product’s specific settings. In any event, keeping these things all together in one place keeps the project(s) structure from getting too messy.
The Tests folder is one that would not have existed in my organizational flow just a few years ago. I’ve been rapidly pushing myself to adopt a more Test-Driven Development (TDD) development process for firmware. All of my test case code is stored under this directory. The exact structure varies a little bit, but a good place to start is James Grenning’s cpputest-start-project on Github. Automated testing has become so important that every project should include this (and actively manage and maintain the tests).
Finally, there is the Utilities folder. Just like with the middleware folder, this is often a folder of Git submodules that provide access to various open-source tools. For example, I often link cpputest into this folder. That way, a new developer can check-out the project and initialize the submodules and be most of the way to being up and running. (I’ve also used Docker for this task but that is a story for another time). Sometimes I include the installer for various tools if they are not open source. Sometimes I’ll even place custom interface code written in Python in this folder as well.
Having a well-organized project can help developers easily find their way around the code as well as help them to easily maintain that code over time. Including folders like test also implies that developers should be writing test cases. There are certainly other directories that could be added to my list, e.g., a driver’s folder. These types of folders often fall under the middleware libraries category in most of today’s toolchains. In any event, I hope this helps give you an idea on how to easily organize your firmware projects along with some best practice processes.
Jacob Beningo is an embedded software consultant who currently works with clients in more than a dozen countries to dramatically transform their businesses by improving product quality, cost and time to market. He has published more than 200 articles on embedded software development techniques, is a sought-after speaker and technical trainer, and holds three degrees which include a Master of Engineering from the University of Michigan. Feel free to contact him at [email protected] at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter.