Welcome to the Modern Embedded Systems Programming course.
My name is Miro Samek and in this lesson I'll finally tackle the subject of race conditions.
Today you will learn what race conditions are, why they are dangerous, and how to avoid
them.
To remind you quickly what happened so far: in the last lesson this course switched the
development toolset to the Eclipse-based Code Composer Studio.
The project directory structure established in the last lesson 19 looks as follows:
The CCS sub-directory contains the Eclipse workspaces for the lessons of this video course.
This directory was created by the Code Composer Studio Eclipse-based IDE.
The CMSIS sub-directory contains the Cortex Microcontroller Software Interface Standard
header files.
If you have not done it yet, you need to create this directory by unzipping the CMSIS archive,
which you can get from the project downloads for this video course.
Finally, you should have the lesson 19 directory with all the code and the CCS project files,
which you also get from the project downloads for this video course.
Of course, you can have any other lessons in this directory as well, but for today you
only need the three directories described so far.
To create a new project for this lesson, make a copy of the previous "lesson19" directory
and rename it to "lesson20".
With Eclipse-based IDEs, you typically cannot simply open the project by double clicking
on it.
Instead, you need to first launch the Eclipse IDE, and then import the new project, as I
will show you in a minute.
While launching Code Composer Studio, make sure that the selected workspace is CCS that
I described before.
After Eclipse finally opens, it should contain one project called "lesson", which you created
in the last lesson 19.
Before importing the new project for lesson 20, you need to delete the old one from the
workspace, because the new project will also have the same copied name "lesson" and Eclipse
does not accept two projects with the same name.
When you delete the project, do not check the box "delete project contents on disk".
Now you can finally import the copied project for lesson20.
Choose File - Import menu and select Existing Projects into Workspace.
Click Next and browse for the lesson20 directory.
You should see the "lesson" project in the lesson20 directory, click "Select All", and
the Finish button.
At the end of all this, you should have the "lesson" project open in Eclipse, but this
time this is the new one corresponding to lesson 20.
Let's first open the two source modules you have here, which are main.c and bsp.c and
put them conveniently side-by-side.
The main code consists of the customary endless while(1) loop, in which the Green LED is turned
on and off.
The BSP code contains the SysTick_Handler interrupt service routine, which is set up
to fire twice a second to toggle the Blue LED.
For this lesson, let's modify the main code by commenting out the two lines, which turn
the green LED on and off by means of the special DATA_Bits array of GPIO registers that I have
explained in Lesson 7.
Instead of using the DATA_Bits array, today you will simply use the DATA register, which
controls all 8 GPIO lines attached to the bits 0 through 7.
Specifically, to set a desired bit, you will read the DATA register, logically OR it with
the LED_GREEN bit, and then write the result back to the DATA register.
You are doing all this not to disturb the other 7 bits of the DATA register which control
other things than your Green LED.
By the way, if you don't remember how the bitwise operators work, please go back to
lesson 6.
To clear a desired bit, you will read the DATA register, logically AND it with the inverted
LED_GREEN bit, and write the result back to the DATA register.
I specifically used the lengthy way of writing these expressions, to show you that a GPIO
bit is set or cleared by a read-modify-write sequence of operations.
But these expressions can also be written more succinctly by means of the OR-equal and
the AND-equal C operators.
Please remember, though, that these short forms perform exactly the same read-modify-write
sequence of operations.
When you build and load your program to the TivaC LaunchPad board, you can see that it
still blinks the LEDs as before.
When you break into the code and single step through the main loop, you can see that the
Green LED is switched on and off, exactly as you programmed it.
Also, when you set a breakpoint in the SysTick_Handler, you can see that it is still toggles the Blue
LED every time it is called.
So, your program seems to work as before, and an inspection of individual elements doesn't
show any problems.
What's here not to like?
But, if you watch the board for a while, you can notice that the blinking of the Blue LED
is no longer as regular as before.
Specifically, sometimes the LED seems to pause for the whole second or even longer.
To find out why it happens, let's set a breakpoint at the line that turns the Green LED off and
open a few new debugger views, such as disassembly and registers.
Now let's single step through the code in assembly.
First the address of the GPIO-F DATA register is loaded into r3 and r2, and then the value
of the DATA register is loaded again into r2.
Finally, the BIC instruction clears the Green LED bit (encoded as the immediate argument
8) in the r2 register as well.
Please notice that all these machine instructions are generated from one short line of your
C code.
But before you execute the BIC instruction, let's trigger the SysTick interrupt.
I mean, an interrupt can happen at any time, so why not here?
To trigger SysTick, open the NVIC register set, scroll to NVIC_INT_CTRL, write one to
the PENDSTSET field, and press return.
Before you run the code, restore the Core Registers view, and set some breakpoints.
Place the first breakpoint immediately after the BIC instruction, and the second in the
SysTick interrupt handler.
That way you will know which piece of code executed first.
You should be already familiar with this trick from the previous lessons about interrupts.
Finally, you are ready to run the code by clicking the Resume button.
You cannot single-step, because it will disable the interrupt.
Well, as you can see, the first breakpoint hit is inside the SysTick handler, so it must
have executed before your other breakpoint after the BIC instruction.
This means that the SysTick handler has preempted the previous code at this exact point.
So now, when you step through the SysTick handler code, you can see that it turns the
Blue LED on, as expected, and then it returns back to the BIC instruction.
Now the BIC instruction clears the Green LED bit in r2 and then stores the r2 in the GPIO
DATA register.
Ooops!
This last store instruction extinguishes both the Green and the Blue LEDs.
This is a problem, because the code was supposed to turn only the Green LED off and exactly
not change any other GPIO bits.
I hope you start to get a sense of what just happened here.
The code that turns the Green LED off is a sequence of read, modify, write operations.
But the sequence was interrupted after the read but before the write.
The interrupt changed the state of the GPIO DATA register, by turning the Blue LED on.
However, the interrupted code was unaware of this change and still used the previous
value of the GPIO DATA register stored in r2.
The problem that you have just experienced is called a RACE CONDITION.
It occurs when two or more pieces of code that can preempt each other access a shared
resource in such a way that the result depends on the sequence of execution of these pieces
of code.
Of course, in a toy Blinky example such a Race Condition is not a big deal, but it can
be, in fact, a deadly bug.
Imagine, for example, that instead of just toggling the Blue LED, the SysTick interrupt
switches on a cooling system in a nuclear reactor to prevent the reactor from overheating.
In this case, the SysTick interrupt has just turned on the cooling system, but the main
loop turned it off again just a fraction of a microsecond later.
The result is that the cooling system remains off, while the reactor melts down.
With this in mind, I hope you start to see why Race Conditions can be a problem.
But I'm not sure that you fully appreciate how nasty they can be.
The problem is that Race Conditions seem to defy logic in the sense that each individual
piece of code works correctly.
The problem only occurs when the pieces of code are executing together and preempt each
other in various places, over which you have no control.
This results in bugs that tend to be intermittent, hard to reproduce, and hard to isolate, as
typically there are only narrow time windows when preemption leads to bugs.
This means that you might be testing your system for hours or weeks, never noticing
any problems.
Still, some catastrophic Race Condition bugs might escape to the final product.
All this makes Race Conditions the worst possible kind of bugs you ever want to deal with.
They are the direct consequence and a huge price for using interrupts.
It seems that nothing good in life, not even interrupts, comes for free.
So now, that I sufficiently scared you, I hope, I'd like to discuss two main strategies
of eliminating race conditions.
The first strategy is based on the concept of mutual exclusion, which means that you
make sure that only one piece of concurrent code can execute while accessing a shared
resource.
In the case of your Blinky program, you can implement mutual exclusion by simply disabling
interrupts around turning the Blue LED on and around turning it off.
This will preclude preemption by the SysTick interrupt, and will eliminate the race condition.
Let's quickly take a look at this modified version in the debugger.
First, the intrinsic function __disable_irq() generates just one machine instruction "CPSID
i".
There is no function call overhead here, which is the beauty of using intrinsic functions.
Similarly, __enable_irq() generates also one instruction "CPSIE i".
Both these instructions execute in just one clock cycle, so the additional overhead of
disabling interrupts really small.
BTW, the section of code between interrupts disabling and enabling is called a "Critical
Section".
So, now let's repeat exactly the previous test of triggering the SysTick interrupt and
setting the breakpoints to find out the order of code execution.
Single step up to the BIC instruction.
Trigger the SysTick interrupt in the NVIC
Set a breakpoint just after the BIC instruction
and another one in the SysTick handler.
Run the application by clicking the Resume button.
As you can see, this time, the SysTick did not preempt the main code, but instead the
breakpoint at the STR instruction was hit.
However, the SysTick interrupt is not lost.
It fires as soon as interrupts get enabled.
When you single step through the the interrupt code, you can see that it turns the Blue LED
on.
The interrupt then returns back to the main code at the top of the loop.
In summary, the introduction of critical sections serialized the access to the shared resource,
the GPIOF->DATA register in this case, and made it atomic, meaning indivisible.
With critical sections in place, the three pieces of code: the SysTick interrupt and
the two critical sections, can run either before or after each other, but not in the
middle.
This is what mutually exclusive access means.
But even better than mutual exclusion is to avoid race conditions by not sharing any resources
in the first place.
So, let me comment out today's code, to leave it there for you to experiment, and revert
to the original code.
This original code does not use the DATA register, but instead it uses the DATA_Bits array of
255 GPIO registers.
I have devoted almost entire lesson 7 to explaining how this array of GPIO registers works, so
I won't repeat it here.
But the main point for today is that the array provides a separate register for every possible
combination of the 8 GPIO bits, so the register DATA_Bits[LED_GREEN] is different than the
register DATA_Bits[LED_BLUE], for example.
This means that there is no sharing of the common DATA register, and, as you will see
in a minute in disassembly, there is no need for the read-modify-write sequence either.
Setting a given bit is accomplished by a simple atomic write to the dedicated DATA_Bits register
and so is clearing of a given bit.
So now you finally understand why the hardware engineers at Texas Instruments have designed
the GPIO registers in this peculiar and complex way.
They did it exactly to separate the various GPIO bits, to avoid the need of sharing them
and thus eliminating potential race conditions in the software.
Those folks did the heavy lifting in hardware, so that your life, as the software designer
can be easier.
This concludes the lesson about race conditions.
In the next lesson I will talk about prioritizing interrupts and about other ways of disabling
interrupts on the ARM Cortex-M processor.
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.