Welcome to the Modern Embedded Systems Programming course.
I'm sorry for my long absence since posting lesson 20, but I'd like to make it up to you
by announcing a new group of lessons about the architecture and design of embedded software.
Today I am going to introduce the ubiquitous foreground-background architecture, also known
as the super-loop or main+interrupts.
Apart from being interesting in its own right, foreground/background is the starting point
for all embedded software architectures and, among others, is an important stepping stone
to understanding a Real-Time Operating System (RTOS), which was perhaps the most requested
subject for me to explain.
So, you can view this lesson as a prerequisite for the upcoming lessons about the RTOS as
well as other architectures.
To follow along today's lesson you need to download two pieces of code from the companion
website to this course at state-machine.com/quickstart.
The first is the lesson 21 project that I prepared in advance, and the second piece is the 'qpc'
framework, which you will use in most of the upcoming lessons in this group.
You need to unzip these two downloads into the embedded-programming directory on your
disk.
This lesson also uses the ARM-Keil MDK toolset from the ARM company instead the Eclipse-based
Code Composer Studio from Texas Instruments.
The reason for this change is that I ran into problems with the latest CCS 7.3, which was
simply blocked by my anti-virus software.
I made all sorts of attempts to reconcile the CCS with my anti-virus, but I ultimately failed.
This is, among others, one reason why this group of lessons took longer to prepare.
In the end, I decided to treat this setback as an opportunity to introduce you to the
third, very popular and excellent ARM-Keil MDK toolset, which is just as good as the
IAR toolset, and in my option easier to use than Eclipse.
To get Keil MDK, you can start from ARM.com and choose the "Development Tools -> Microcontroller
and Embedded Development" menu.
Alternatively, you can simply google for "Keil MDK".
Either way, you will land on this main MDK page.
As you can see in the comparison of available MDK editions, there is a free, code-size limited
Lite version, which will be perfectly adequate for all projects in this video course.
Click on the Download button and fill out the usual download form.
Click the Submit button.
While you install the Keil MDK toolset, you might want to familiarize yourself with the documentation
The toolset comes with the Getting Started videos and a guide in PDF.
So, assuming that you have successfully installed ARM Keil MDK toolset, you can go to the lesson21
directory you have just downloaded and click on the provided uVision project file.
When you run the Keil uVision IDE for the first time, you might need to register the
license, which in my case is MDK-Lite Evaluation Version.
You can get this license online by clicking on "Get LIC via Internet".
Also, the first time you open a project for the TivaC LaunchPad board, you might need
to install the so called "Software Pack".
You open the Pack Installer
and select the Texas Instruments, TivaC Series, TM4C123x Series pack, which includes your
specific microcontroller.
OK, so finally you can get to the code for today's lesson that is actually very similar
to what you had back in lesson 8 in that it simply blinks the green LED on your TivaC
LauchPad board.
The only difference from the previous version is the delay() function, now called BSP_delay(),
because it is defined in the Board Support Package (BSP), which you first encountered
in lesson-15.
In this lesson you will see how to take the concept of the Board Support Package to the
next level.
Before you go any further, let's just build this version of the Blinky program and check
that it still works by opening it in the micro-Vision debugger.
When you run the program, you can see that the Green LED blinks by staying on for about
a quarter of a second and off for about three quarters of a second.
Now, going back to the editing mode, let me explain a few things about the BSP_delay()
function.
Unlike the previous crude delay() implementation from lesson-8, BSP_delay() is based on the
SysTick interrupt, which delivers more precise timing, because it does not depend on the
speed of the compiler-generated code.
In this new implementation, the SysTick interrupt is programmed to fire at a rate of BSP_TICKS_PER_SEC,
which is defined as a hundred times per second in the bsp.h header file.
This BSP_TICKS_PER_SEC constant is subsequently used to configure the SysTick interrupt.
The SysTick interrupt handler simply increments the local l_tickCtr variable, which is declared
both static and volatile.
The 'volatilie' qualifier has been introduced back in lesson-5, but here let me quickly
remind you that when you declare a variable to be "volatile", you are telling the compiler
that the variable might change unexpectedly even though no currently performed program
instructions change it, which is exactly what can happen when a variable is modified in
an interrupt.
The BSP_tickCtr() function simply reads and returns the current value of l_tickCtr variable.
But as you surely remember from the last lesson-20, to avoid any race conditions between the SysTick
interrupt and the code that calls BSP_tickCtr(), the access to such a variable must occur in
a critical section, that is, with interrupts disabled.
Finally, the BSP_delay() function first reads the tick counter and stores it in the automatic
variable 'start'.
Next, it enters a polling while-loop, which constantly reads the tick counter value and
computes the difference from the start.
The loop continues as long as the difference is smaller than the specified number of clock
ticks.
Please note that the 2's complement arithmetic, which I discussed in lesson 2, handles properly
the discontinuity when the tick counter rolls over from all-Fs to 0.
At the end of the day, however, the BSP_delay() implementation, just like the previous, crude
delay() function, is based on the same primitive idea of polling, so it ends up wasting all
the CPU cycles until the specified number of clock ticks elapse.
However, for this lesson, the most important point is that the software structure of the
Blinky program has all the characteristics of the so called *foreground/background architecture*
also known as "main+ISRs", which is very common in smaller embedded systems.
As the name suggests, the architecture consists of two main parts: the endless *background*
loop inside the main() function and the interrupt handlers, like your SysTick_Handler() and
possibly others, comprising the *foreground*.
The interrupts running in the foreground preempt the background loop, but they always return
to the point of preemption.
The two parts of the system communicate with each other by means of shared variables, like
l_tickCtr.
To avoid race conditions due to preemption of the background loop by the foreground interrupts,
these shared variables should be defined as volatile and must be protected by briefly
disabling interrupts around any access to them from the background.
The timing of the execution of the various functions called from the background loop
is not well defined, because it depends on the time spent inside the loop that typically
varies from one pass through the loop to another due to conditional branching in the code and
interrupt activity.
For these reasons, any operations with strict time constraints cannot be reliably performed
from the background loop and must be pushed to the interrupt level running in the foreground.
However, this tends to make the interrupts longer and they might start to interfere with
the background loop and with each other.
Still, due to its simplicity, the foreground/background architecture is very popular in high-volume
embedded applications, such as consumer electronics, home appliances, toys, remote controllers,
and countless others.
Foreground/background is also exactly the architecture used in various maker platforms,
such as Arduino.
Here, for example is the Arduino version of the Blinky program.
At the first glance, you might not recognize it as a foreground/background, because Arduino
hides it inside its library.
But if you take a look at the main function inside the Arduino library, you should immediately
recognize the familiar background architecture consisting of initialization, the setup()
function, and the endless loop, repetitively, calling the loop() function.
Note that the for-loop with empty control is equivalent to the while(1) loop and is
a C-language idiom that means for-ever.
Arduino has, of course, also the foreground level consisting of interrupt handlers.
Here, for example, is the system clock tick Interrupt Service Routine (ISR), which increments
some counters, just like your SysTick handler.
There is also, of course, the matching polling delay(), which is perhaps the most frequently
used function in Arduino programs.
Now, let's go back to your Blinky background code and notice that you can still organize
it a bit better.
For example, the initialization part is board-specific, so it logically belongs to the Board Support
Package (BSP).
So, let's make a BSP_init() function inside the BSP module, place all the initialization
code there, and call it from main.
Next, notice that switching the LEDs on and off is also board-specific, meaning that the
code will need to change if you used a different board with LEDs attached to different pins.
Therefore, you should move this code to the BSP as well.
Specifically, inside the BSP module you can create BSP functions to turn LEDs of various
colors on and off.
Once the functions are defined, you can simply call them from the background loop.
Of course, you need to add the prototypes of all the new BSP functions to the BSP header
file.
An finally, notice that now you can now remove all board-specific stuff from the main code,
such as the MCU header file, the LEDs pin numbers, etc.
You can move all this stuff into bsp.c, because your background loop no longer depends on
any of these details.
As you can see the code compiles and links error-free.
The end effect is that your main code is completely insulated from the board.
The background code only specifies WHAT needs to be done, while the BSP code specifies HOW
to do it.
This way of separating concerns (the WHAT from the HOW) has many advantages.
First, your main code is smaller and self-explanatory.
You really don't need comments to understand what's going on.
The second advantage is that you could run this main code on a different board or you
could use a different development toolchain.
Of course, you would need to provide a different BSP implementation in the bsp.c file, but
you don't need to change a single line of your main application code.
It is even possible to run your main application on a desktop PC, which is not an embedded
board at all.
Now, let's check experimentally where your Blinky program spends the most of its time
by simply breaking into the running code.
As you can see, the program stops in BSP_tickCtr() function.
When you inspect the call stack view, you can see that BSP_tickCtr() is called from
BSP_delay(), which in turn is called from main at the shown location.
In fact, you have almost no chance at all to find this program doing anything else than
delaying its execution.
When you draw a flowchart of this background code you can see arrows going *backwards*
the flow of control.
These are the pooling loops where the code spends the most of its time, and therefore
they are shown with thick lines.
I will call this type of code both *blocking* and *sequential*.
The code is *blocking*, because it waits for events (such as a timeout event after expiration
of a delay) in-line and does not progress until the expected event arrives.
Once the event arrives, however, the code naturally progresses directly to handling
the event because the code downstream the blocking call provides the right context for
the expected event.
The code is *sequential*, because the sequence of expected events is hard-coded in the sequence
of instructions.
For example, the Blinky program expects a timeout event of a duration of 1/4 second
after turning the Green LED on, and another timeout event of duration 3/4 of a second
after turning the Green LED off.
But it is also possible to arrange the background code differently in a non-blocking fashion,
without the polling loops that busy-wait for specific events.
To show this alternative, I make the sequential implementation inactive by surrounding it
with #if 0 ... #endif.
Now, I add the new non-blocking version of the background loop.
When you build and debug this version, you can see that it blinks exactly as before.
But when you break into the code, you can see that it always stops inside the main loop
rather than inside some other function.
When you look at the flowchart of the non-blocking background code, you can see that no arrows
in the flowchart go backwards.
Instead, all arrows point forwards without blocking the main background loop, so the
program spends the most of its time right in the main loop, as indicated by the heavy
lines.
Compared to the sequential and blocking version, the non-blocking main loop spins hundreds
of thousand times per second instead of only once per second in the sequential code.
This means that the main loop can handle events as soon as they arrive in the order in which
they arrive.
In other words, the non-blocking code is driven by events, and therefore I will call it *event-driven*.
Of course, there is a price to pay for this increased flexibility and timeliness of such
non-blocking code, and that is the apparent higher complexity.
The event-driven code is more complex, because the sequence of events this code can accept
is no longer hard-coded in the sequence of instructions.
By the way, the non-blocking code is structured here as a polling state machine.
This is unfortunately not the norm.
In the majority of real-life projects, you will see rather convoluted and deeply nested
IF-THEN-ELSE branching based on the value of many global variables, also known as the
"spaghetti code" or a "big ball of mud".
I cannot go into details of state machine in this lesson about foreground/background
systems.
But I promise to come back to state machines, in several upcoming lessons, actually, as
they are fundamentally important in embedded systems programming.
This concludes this lesson about the foreground/background architecture, which is fundamental to understanding
all other architectures used in embedded software.
You saw that foreground/background can be implemented either using the sequential paradigm
with blocking, or with the event-driven and non-blocking paradigm.
In the upcoming lessons I will explore all these options, starting with the introduction
to Real-Time Operating Systems in the very next lesson!
If you like this channel, please subscribe to stay tuned.
You can also visit state-machine.com/quickstart for the class notes and project file downloads.