Embedded C Design Patterns Training
Learn powerful design patterns for writing clean, maintainable and reusable embedded C code.
Why you need design patterns
- Clean and maintainable: design patterns help us write clean, robust and maintainable code.
- Enhanced code reuse: patterns help you decouple dependencies and keep your code organized.
- Being proactive about bug prevention: design patterns give clear expectations and enable us to review and reason about large quantities of code more easily.
- Removes ambiguity: using the same patterns consistently removes ambiguity about code structure and behavior.
- Essential for effective DevOps: code that applies well known and understandable patterns is easy to review.
- Make details fall into place: design patterns help organize the code and make small implementation details fall into place more easily.
When we develop embedded software, we see time and again the same class of problems. Legacy code that is costly to maintain, changing one piece of code results in unexpected behavioral changes in other places, code that breaks in unexpected ways.
All of this results in source code that becomes exponentially more difficult to work with.
What this all comes down to is lack of clear architectural guidelines for writing the software. Things like "How do we define an object?" or "What basic methods should every object have?". These things aren’t enforced by compilers and probably will never be. This is something that we must enforce with clear guidelines and code review.
This is why we have a list of guidelines that we apply to every project and we make sure that all developers know about these guidelines and point out to each other during code review when these guidelines are not being followed. This results in code that is clean, easy to maintain, easy to test and easy to understand too.
We use design patterns to clarify software structure and the expectations that we place on that structure.
Design patterns are essential for implementing efficient devops because they directly decrease the time it takes to review new code. When everyone on the team uses design patterns and agrees on their use, everyone also knows what to expect from new code when it follows already known patterns.
Design patterns help us write clean, robust and maintainable code.
We are going to start with a short introduction where I explain to you how to follow this course and where to find additional resources and how each module is structured.
We will then cover
Creational Patterns which deal with construction of our
data objects. Creational patterns help us have standardized ways of creating
new objects and handling responsibility of memory ownership.
After that we dive into
Structural Patterns which deal with the structure of
our code. These patterns help us structure our code such that we can easily
extend it with new functionality without having to refactor it later.
Behavioral Patterns is a section concerned with code 'behavior' such as
return value pattern. These patterns help us with certainty of what to expect
from code in terms of behavior in different common situations.
Concurrency Patterns will give you an intuitive understanding of
concurrency on an embedded RTOS so that you can understand when to use which
concurrency pattern in order to achieve high responsiveness of your
- Introduction. In this section we look at useful information that you need before getting started, where to find code examples for this training, how to get additional help and where you can ask questions regarding this course. This is a brief introduction to this course.
- Object Pattern. This is a way to group data into objects that can be instantiated and destroyed. We also introduce concept of classes and member functions.
- Opaque Pattern. This pattern gives us three ways of making the implementation of the object private and exposing only a handle to the object. This can also be referred to as opaque objects pattern.
- Singleton Pattern. The singleton pattern is a design pattern used to ensure that a class has only one instance, while providing a global access point to this instance.
- Factory Pattern. The factory design pattern is a creational design pattern that provides an interface for creating objects in a super class, but allows sub-classes to alter the type of objects that will be created.
- Callback Pattern. Deals with object oriented callbacks that are bound to instances of objects. Allows callbacks to operate on specific instances of objects.
- Inheritance Pattern. Inheritance pattern is used for implementing inheritance relationships between objects and components in a C program. It is useful as a way to create a hierarchy of objects instead of having to manage a large application where all details are at the same level of abstraction.
- Virtual API Pattern. Virtual API pattern is a way of implementing virtual functions in C and making the handles to opaque objects also "smart". The virtual api pattern gives us polymorphism - where we can use the same interface for several implementations without knowing about these implementations.
- Bridge Pattern. This pattern builds upon the virtual api pattern and is the pattern to use when you need to bridge two distinct hierarchies of objects. We cover an example in Rust and in C where we look at how this pattern can be used in practice.
- Return Value Pattern. This pattern standardizes the way that function handle return values. This is valuable because return values in C are the primary way to signal status of an operation. Therefore we must have a clear way of communicating with caller through standardized return value.
- Concurrency Introduction. In this section we are going to look at concurrency itself as a pattern for software development as well as when and why we should consider concurrency as a valuable tool in our toolbox.
- Spinlock / Interrupt Masking Pattern. Masking interrupts is the simplest pattern for data integrity has to do with protecting shared data that is accessed by interrupts. A generic implementation of this is often done in the form of spinlock. Here we look at how we need to protect data from corruption by making sure interrupt never runs when we are modifying it.
- Semaphore Pattern. A semaphore is one level above a spinlock and outlines a pattern of signalling between interrupt handlers and application level threads (and also between multiple threads in our application). In this module we look at the actual implementation of a semaphore, its use cases and important considerations. A semaphore is the most basic, thread aware synchronization primitive in any RTOS system.
- Mutex Pattern. The mutex is slightly different from the semaphore in that it prevents starvation by means of priority inheritance. It is the primary pattern that should be used by an application thread for mutually exclusive access to a resource with respect to other threads. In this module we will look at an implementation of mutex, how it works and how it differs from implementations of other primitives.
- Conditional Variable Pattern. The conditional variable pattern builds upon the mutex pattern to implement a way of signaling and waking up one or more threads from another thread or from an interrupt handler. This pattern is useful for checking for arbitrarily complex conditions in response to an asynchronous event in a safe manner.
Who this is for
This training is for:
- Embedded Software Architects: even if you have been coding for many years, you may still find ideas in this content on how to do things better.
- Beginner Embedded Programmers: patterns help you organize your code and the best place to start is right from the start.
- Embedded Manager: it is very important that patterns are well understood and applied by the whole team because of how much impact they have on code structure.
Time to enroll
This training is available to paid members only.
Membership offers you additional benefits such as:
Get started today by registering a monthly membership account here on the site. Click to sign up