Welcome to the Modern Embedded Systems Programming course.
My name is Miro Samek and in this lesson I'll finally tackle the subject of interrupts.
Today, you will learn what interrupts are, and how they work.
As usual, let's get started with making a copy of the previous "lesson15" project and
renaming it to "lesson16".
If you are just joining the course, you can download the previous projects from state-machine.com/quickstart.
Get inside the new "lesson16" directory and double-click on the workspace file to open
the IAR toolset.
If you don't have the IAR toolset, go back to "lesson0".
To quickly summarize what happened so far: in the couple of last lessons you were laying
the groundwork for adding interrupts to your program.
Today you are finally ready to actually start using interrupts to learn what they are and
how they work.
Let's start today with cleaning up the code from all the little experiments you've performed,
so that you get your blinky program back to its basic form.
To remind you how the blinky program works, your main() function starts off with initializing
the hardware.
Next it lights up the blue LED.
And finally it enters the endless wile (1) loop.
In this loop, your code turns the red LED on, and then calls the delay() function that
busy-waits for half a million iterations, which happens to take about half a second.
Next the program turns the red LED off, and again it busy-waits for half a million of
iterations.
When I say that the program busy-waits, I mean that the CPU is doing nothing else, but
constantly checking whether the iteration counter has dropped all the way to zero.
In programming this is called **polling** and is an approach, in which the program keeps
checking a for a certain condition to occur in order to do something in response.
There are many kinds of polling, some more clever than the others.
But the busy-wait polling for longer periods of time, like the delay() function, is one
of the most brain-dead ones, because it ties up the CPU completely and renders it unavailable
for any other work.
This is like you would spend all night counting every clock tick so that you won't oversleep
in the morning.
I mean, most people would set up an alarm clock to go off at the right time.
That way they would be able to do something else than constantly watching the clock.
It turns out that microprocessors also have a mechanism of setting up alarms, which are
called interrupts.
These interrupts are not just for timeouts, but for many other conditions, such as when
a user presses a button, when data arrives through a communication interface, when analog-to-digital
conversion completes, etc.
The name "interrupt" is actually quite suitable, because just like in real life, interrupt
disrupts an ongoing activity and forces you to start doing something else in response
to the interrupt.
So, let's stretch the analogy between alarms in real life and interrupts in a processor
a bit further and consider what's necessary for a person to use an alarm clock.
Well first, you need a special clock with an alarm hardware to make noise at the right
time.
And you also need a person to be able to hear the alarm.
We take it obviously for granted, but a person needs a special hardware (ears, auditory nerves,
etc.) to "listen to" the alarms.
With this picture in mind, I hope you begin to see that a microprocessor too needs special
hardware to handle interrupts.
Software alone, although also necessary, is not enough to do the job.
So, back your blinky program, you first need to find an appropriate alarm clock for your
microcontroller.
As it turns out TivaC MCU has many choices, one of them being the System Timer.
You can find it in the TivaC Datasheet by searching for SysTick.
The SysTick is a hardware peripheral meaning that it is a separate hardware block on the
MCU silicon.
It is clocked by the CPU clock and consists of three registers.
The STCURRENT register, which in software will be called SysTick->VAL, is a simple,
24-bit down-counter that decrements by one at every CPU clock cycle.
When the counter reaches zero, it can generate an alarm (i.e., an interrupt) to the CPU.
At this point, the counter automatically reloads from the STRELOAD register, which in software
will be called SysTick->LOAD, and starts decrementing again.
By writing a different value into the SysTick->LOAD register, you can set up a different time
between the interrupts.
The second piece of the puzzle is your CPU, and specifically, how it "listens" to the
interrupts coming in through the interrupt line.
Well, the CPU has a special built-in hardware that samples the status of the interrupt line
after every instruction.
As long as the interrupt line is low, the CPU fetches the next instruction in the pipeline.
However, when the interrupt line is high, the special CPU hardware forces the CPU to
execute the Interrupt Entry instruction.
This is called **PREEMPTION**.
Please note that while all the instructions are strictly synchronized to the CPU clock,
the interrupt line is generally not.
The interrupt line can change its status at any time and typically in the middle of an
instruction, completely **ASYNCHRONOUSLY** to the instruction execution.
That's why it is often said that interrupts are **ASYNCHRONOUS** to the executing program.
Another interesting observation is that the CPU has a special Interrupt Entry instruction,
which is often one of the longest in the instruction set.
For example, the ARM Cortex-M Interrupt Entry takes at least 12 cycles, whereas other simpler
instructions, such as MOV or ADD, can execute in just 1 clock cycle.
With all this new information, you are almost ready to get back to the code and use the
SysTick interrupt instead of the brain-dead polling.
But before you set up the interrupt, let's modify the polling code so that you have only
one delay in the loop.
This one delay would then directly correspond to the interval between the SysTick interrupts,
so the body of the loop, except the delay obviously, would be the exact code for the
interrupt handler.
This will become much clearer in a minute, but the point is to test this still with the
old polling approach, before venturing into something new like using interrupts.
So, instead of relying on the sequence LED-on - delay -- LED-off - delay, you can observe
that before each delay the state of the LED needs to toggle.
If the LED was OFF, it needs to go ON and vice versa.
It turns out that the C language has a nice coding idiom for toggling a bit, and that
is the bitwise exclusive-OR operator.
For example, to toggle the LED_RED bit, you use XOR-Equals operator, like this.
From the truth table of the exclusive-OR operator you can see that if the LED_RED bit was zero,
it will become 1, and if it was 1 it will become zero.
Finally, let me also comment out the code for lighting up of the blue LED, so that the
red LED will be the only one used.
When I press F7, the code compiles and builds error free.
The last logical thing to do is obviously to check if the code still runs.
As you can see, when I set a breakpoint at the exclusive-OR operator, the LED toggles
every time I hit the breakpoint.
When the program is run free, the LED blinks about once a second.
All right, so finally you are ready to configure the SysTick to generate interrupts every half
a second, instead of using the brain-dead delay() function.
For this, you need to write the correct values into the three registers comprising the SysTick.
As all other registers in your MCU, the SysTick registers are already mapped for you in the
"tm4c_cmsis.h" header file.
Specifically, the SysTick is specified the Cortex-Microcontroller Software Interface
Standard (CMSIS) header file "core_cm4.h", which is included in the "tm4c_cmsis.h" header
file.
It's a little bit annoying that CMSIS uses different register names than the datasheet
in this case, but with just three registers it's pretty easy to figure out how they match.
Actually, the only register that you really need to look up in the datasheet is the STCTRL
register, where you configure the SysTick timer.
In this register you would need to set bits 2, 1, and zero, for Clock-Source, Interrupt-Enable,
and Counter enable, respectively.
The SysTick counter value register is simple.
The datasheet says that it is a clear-on-write register, which means that it will be cleared
no matter what you write to it, so let's write a zero.
The LOAD register is the most interesting one, because this is where you determine the
interval between the interrupts.
To set this interval to half a second, you need to know the speed of the CPU clock in
terms of cycles per second.
As it turns out, the default speed of your TivaC is 16MHz out of the on-board crystal
oscillator.
If you have good eyesight, you can see it written on the crystal Y2.
By the way, you can make your Tiva MCU run much faster, by configuring the clock differently,
but for now let's just leave it at 16MHz.
The CPU clock frequency in Hertz is an important constant in your program, so let's define
it at the top of the file.
16MHz is 16 million cycles per second.
Finally you can set the SysTick LOAD register to half of the clock cycles per second minus
1.
This minus one accounts for the fact that SysTick counts through zero, so if you didn't
subtract one, you would count one clock too many.
One word of caution here is that for such a long timeout of half a second you need to
check that you don't overflow the dynamic range of the counter, which is only 24-bits.
You should check it with a calculator.
When you convert the value to hex, you can see that it just fits in three bytes (24-bits),
so you are OK.
At this point, you have the SysTick interrupt configured and enabled.
It will interrupt the CPU every half a second.
This interrupt, in turn, will cause calling the SysTick interrupt handler that you have
installed in the vector table in the last lesson.
In fact, you already have an empty body of this handler in your bsp.c module.
Today, the big question is, what should you put inside?
Well, just like your polling loop, your interrupt handler needs to toggle the red LED.
And that's all it needs to do, because the delay between the interrupts happens outside
the CPU, handled autonomously by the SysTick peripheral.
A slight problem at this point is that the code fails to compile, because the LED_RED
bit is defined only in main.c and is not known in bsp.c.
The right way to fix it is to put all such board-specific definitions, like LED pin numbers
and CPU clock frequency into the bsp.h header file.
I am going to adapt the delay.h file for this, because the busy-wait delay function becomes
obsolete at this point.
After the edits I save the file as bsp.h...
And I include bsp.h in main.c and in bsp.c
At this point I can remove delay.c from the project
When I build now, I get an error that the delay() function is not available.
This is fine, because I don't need it anymore, so I just delete it.
But note that I can't delete the whole while(1) loop, even though it is no longer doing anything.
This is because the CPU must spend its time somewhere between servicing the interrupts.
In the future, you can use this loop to do something useful, or to put the CPU to sleep,
but for now just keep it empty.
The code is almost ready to test, but you need to add one more thing, which is enabling
interrupts to the CPU.
In the previous discussion I showed you that the peripherals, like SysTick, connect directly
to the CPU's interrupt line.
This is a bit oversimplified, because there is a bit more happening in between.
For starters, all CPUs have a way to block the interrupt line in software.
ARM Cortex-M CPUs have a special bit called PRIMASK that must be cleared in software for
interrupts to reach the CPU.
So, as the last touch in your code, you need to add a call to the IAR intrinsic function
__enable_interrupt().
This function will clear the PRIMASK bit.
The code compiles and links cleanly, and I'm sure that you are hanging by the edge of your
seat to see how the code performs on the LauchPad board.
As you can see the red LED blinks every second.
When you break into the code, you find it spinning inside the empty while(1) loop.
And when you set a breakpoint inside the SysTick_Handler, you can see that the LED gets toggled every
time the breakpoint is hit.
This concludes this very gentle introduction to interrupts.
In the next lesson, I will step into the code and explain exactly the magic of preemption.
I will also show you how interrupts work in the MSP430 processor, which will help you
understand how ARM Cortex-M differs from all other processors.
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.