No Drama Embedded Software Development
Developing embedded software is hard. It gets even more “complicated” when requirements, hardware, schedule, or team composition change. Change, by definition, implies additional work—and the drama comes from how to complete that work without pushing the schedule or adding resources. Put another way, too many changes can put software on the critical path and the development team under a microscope. This session covers strategies for taming inevitable changes and keeping your software off the critical path.
Topics covered will include:
- Dependency management
- BSP: Encapsulating the MCU datasheet, compiler, and board
- Abstract interfaces and other decoupling techniques
- Decoupling drivers from the hardware
- Design for testability and automated off-target unit testing
- Functional simulator
- OCP principle: Software entities should be open for extension, but closed for modification
- Main Pattern: An application is a collection of “wired-together” widgets
What this presentation is about and why it matters
How do you keep embedded software moving when requirements, hardware, and schedules keep shifting under you? John Taylor approaches that tension with a practical, opinionated walkthrough built from decades in embedded roles across several industries. He uses a concrete architecture case study, then layers in patterns, binding techniques, and test strategy to show how change tolerance is built into the design, not added later. If you work on firmware that has to survive board changes, variant growth, or late hardware arrival, this talk gives you a useful way to think about the problem.
Who will benefit the most from this presentation
- Embedded software engineer dealing with changing requirements or late hardware availability
- Firmware architect trying to reduce transitive dependencies in a growing codebase
- Test engineer or tech lead who wants off-target unit tests to drive better design
- Developer supporting multiple boards, BSPs, or RTOS options in one product line
- Engineer looking for a practical alternative to heavy
#ifbased variant management
What you need to know
A basic working knowledge of embedded software development will help. The talk assumes familiarity with a few common ideas:
- C or C++ source and header relationships
- Board support packages and vendor SDKs
- Unit testing and CI pipelines
- RTOS-based embedded projects
Glossary (terms used in this talk)
- RTOS (Real-Time Operating System): An operating system designed to provide predictable timing behavior for real-time applications.
- Binding time: The point at which a name is associated with a specific implementation or piece of data. Different binding times, such as compile time, link time, or runtime, shape how flexible and interchangeable a system can be.
- Late binding: A design approach where the exact implementation is selected later than compile time, often to reduce coupling between callers and concrete implementations. It is commonly used to make platform-specific behavior swappable.
- Functional simulator: A host-side program that runs production logic on a PC and emulates hardware behavior at a functional level, without trying to match real-time performance. It is useful for starting development before target hardware is available.
- Data model architecture pattern: An architectural pattern where modules communicate through shared data instances rather than direct module-to-module dependencies. The data object becomes the source of truth, which can simplify coupling and variant management.
- Model point: A typed data object that can be read and written through defined operations, often with validity state and change notification behavior. It serves as a shared interface between producers and consumers of data.
- Software architecture: The high-level structure of a software system, including its major components, dependencies, and interaction boundaries. Good architecture makes it easier to change parts of a system without rippling changes everywhere.
Toolbox (mentioned in this talk)
- FreeRTOS: A widely used real-time operating system for embedded systems. It is commonly integrated as a library that the application initializes and then starts scheduling tasks.
- Zephyr RTOS: A scalable real-time operating system for connected and resource-constrained devices. It often owns much of the system startup flow, including hardware initialization and thread setup.
- QEMU: An emulator that can run target software on a host machine while exposing introspection for testing and debugging. It is commonly used when hardware is unavailable or when desktop-based analysis is easier.
- Linux: A family of open-source operating systems widely used as a host environment for embedded development, servers, and development tools. It provides a broad base of runtime services, libraries, and utilities for building and running software.
- STM32Cube: STMicroelectronics' software development ecosystem for STM32 microcontrollers, including peripheral configuration and generated project code.
- PuTTY: A terminal emulator and serial client commonly used to connect to embedded devices over UART or similar interfaces.
Final thoughts
Practical and architecture-focused, this talk gives you a vocabulary for talking about coupling, variant handling, and testability without drifting into theory for theory's sake. The value is less in a checklist than in a way of seeing your codebase, especially when hardware and requirements are both moving. It will help firmware engineers, tech leads, and test-minded developers who need to make change less chaotic. The overall feeling is calm, disciplined engineering applied to a messy real-world problem.
This overview is AI-generated from the session transcript. Spot an issue? Let us know.
Great question. And yes I have and opinions/preferred approach for managing and sharing code across repositories. I have yet another open source project - Outcast (https://github.com/johnttaylor/Outcast) - which is a python based tool that I use to have a mono-repository (without git sub-modules) for my project - and still retain the ability to share code from other repositories. I have been using Outcast in my day jobs for over 6 years now.
Outcast is specifically targeted to allow code sharing while put the priorities of "my project" first and not getting slowed down by the change management processes of the shared repos. Take a look and the links below and you are welcome to email me with follow up questions: john.t.taylor@gmail.com
Here is link to some introduction documentation for Outcast: https://github.com/johnttaylor/Outcast/blob/master/start_here.html
Here is snippet from the above web page:
Outcast is a proof of concept for implementing a hybrid Mono-Consuming-Poly Repo (MCP) paradigm to provide a solution to the problem: how to manage, in a team environment, a scalable reusable code base?
A Mono Repo paradigm is a software development strategy where code for many projects is stored in the same repository. A Poly Repo paradigm separates different projects, libraries, sub-systems, etc. into individual independent repositories (e.g. using git submodules). There are pros and cons to both approaches (just google mono vs multi repo). However, in my experience neither of these approaches provided a solution that is both scalable and low overhead.
The Mono-Consuming-Poly Repo (MCP) strategy is a my project first approach with the benefits of reusing and/or consuming packages from other repositories. The my project first means that when consuming other repositories the day-to-day workflow should have no or minimal overhead over a Monorepo strategy and fully isolate my project from the change management of the external repositories.
Amazing! Yes, I imagined something like your approach. Will definitely ping you for a follow up after I give it a try. Thanks John!
Thanks a lot for these best practices, we are applying most of these with great success. We could probably do better at dependency management still, so your talk was also a "call for action" for us! I take the liberty to add some additional best practices we found useful in this context: (1) complement unit testing with design-by-contract assertions in the production code, (2) in unit tests, use parametrized test functions to increase tests' conclusiveness (but not necessarily code coverage) (3) to make unit test code readable, we found it helpful to use what we call "AAA" / "AAAC" pattern (arrange, act, assert, and I added cleanup at the end). So test code would often have // arrange: xyz and // act: abc type of comments.
I can attest to the effectiveness of these methods. Last year I purchased both of John's books and absorbed as much of this info as I could. Our existing code base was a hot mess, I will spare the details. Following the EPC book I created all of the documentation before starting to code. I used the data model for the architecture. The results were greatly improved customer satisfaction, lower scrap costs in the warranty department, and one code base that easily supported all product variations instead of a code repository for each product variant. It was a huge success. Thanks John!
Glad the Embedded Project Cookbook help! I had a similar experience - when I start my latest gig (just as EPC was being published) I was tasked with standing up the FW development processes and architecture for building a code base that could run on any hardware from microcontrollers to soft cores in FPGAs. I literal started with the example processes and Word documents from EPC (along with the EPC source code). It knocked off 3-4 months from the front end of schedule and gave us a solid foundation for the rest of the project.








Hi John, great talk! I'm wondering how you would apply the decoupling concept when it comes to repository-level coupling.
If your application/project repo abstracts from the platform/OS, where would you place and link each port? Does it make sense to have a full reference (e.g.: FreeRTOS repo as a submodule) sitting there, maybe unused due to other port being used?
Thanks!