Microcontrollers are fast. Our MSP430 runs at 25MHz. Running at 25MHz means that the microcontroller is running about 25 million instructions each second. That means an instruction runs every 1/25,000,000 seconds = 40 nanoseconds. That’s fast.
And many microcontrollers run faster than 25MHz: 100MHz+ is common. This is nothing near the 2GHz+ processor in your laptop or phone, but it’s still pretty dang fast.
Speed is great when you want to get something done fast. Speed is not so great when you want to get something done slow. When do you want to do something slow? It turns that people don’t process information as fast as computers, so if you want your embedded system to work with people then you’ll have to do some things at human-speed (slow!) instead of microcontroller-speed (fast!).
A good example is the blinky light from last time. That last post had an infinite loop where the MSP430’s LED was turned on and off as fast as the microcontroller could run. When I run the code on my MSP430 dev board the LED looks like it’s always on, even though I know the code is really turning it on and off very quickly.
How quickly? Let’s take a closer look.
First let’s simplify the code a tiny bit. The code from last time turned the LED on and off twice; let’s remove the second on/off so it looks like this:
; Set GPIO P1.0 to be an output BIS.B #1, &PADIR_L MainLoop: ; Set output on GPIO P1.0 to high voltage BIS.B #1, &PAOUT_L ; Set output on GPIO P1.0 to low voltage BIC.B #1, &PAOUT_L JMP MainLoop
A brief review: This code sets GPIO P1.0 to be an output, then starts the MainLoop, where it turns the LED on, then off, and then repeats MainLoop forever. When it runs on my MSP430 dev board the LED appears to be always on. Is the LED actually being turned on and off? I’m going to use a tool called a logic analyzer to find out.
A logic analyzer is a tool that records and displays voltage levels for digital signals. I use the Logic Pro 8 logic analyzer from Saleae. It’s a nice 8-channel logic analyzer that runs on Mac, Windows, and Linux. “8-channel” means it has 8 wires that can monitor 8 different digital signals at once, which is enough for most everyday debugging. For measuring a single LED I only need one channel: a single wire hooks up to the LED. (And a second wire hooks up to ground, because measuring a voltage always requires two connections.)
Here’s a picture of the logic analyzer setup:
The black clip at the bottom of the picture connects the logic analyzer to the LED, and the wire in the middle-right of the MSP430 dev board connects the logic analyzer to ground.
Here’s a picture of the measurement while the blinky infinite loop is running:
The logic analyzer software shows us the LED voltage is high for about 3.78 us (that’s microseconds), and then low for about 6.6 us. (Technically microseconds should be abbreviated using with the Greek mu symbol like “μs”, but figuring out how to insert the mu character is annoying and so it’s μsμally and lazily abbreviated with a regular old ‘u’).
This logic analyzer display shows us that the LED is toggling on and off as expected, but it’s happening so fast that our eyes can’t see the light actually turning on and off.
Okay, so we’ve established that the LED is turning off quickly, at microcontroller speeds. This is too fast for our eyes to see. How can we slow it down?
The simplest way is to add a delay after turning on the LED, and another delay after turning it off. The simplest delay is having the microcontroller do busy work for a set amount of time. This is also called busy looping or busy waiting or spinning.
Here’s a busy loop that we’ll use to delay the blinking LED:
MOV #50000, R4 ; works out to a delay of about 0.2 seconds DelayOn: SUB #1, R4 ; subtract 1 from R4 CMP #0, R4 ; compare R4 to 0 JNZ DelayOn ; while R4 != 0, keep running DelayOn loop
This busy loop code sets R4=50000, then subtracts 1 from R4 until R4 is equal to 0. This takes about 0.2 seconds on my MSP430 dev board as measured on the Saleae logic analyzer..
Let’s break the five assembly instructions down:
The MOV instruction stores the immediate value 50000 into the register R4. This sets up the busy work that will happen next. 50000 is an arbitrary number: I played around with different values and found that 50000 gives about a 0.2 second delay.
The “DelayOn:” label is the start of the actual busy loop.
The SUB instruction subtracts 1 from R4. R4 decreases by 1 each time this instruction runs.
The CMP instruction compares the value in R4 to 0; it does “R4 - 0”. CMP doesn’t change the value in R4; remember that CMP instructions only change the status register SR. Specifically, the SR register’s Z (“Zero”) bit will be set if the subtraction “R4 - 0” is equal to 0.
The JNZ instruction (“Jump if Not Zero”) looks at the SR register’s Zero bit, and if SR.Z is not set then it jumps to the label DelayOn, which runs another iteration of the busy loop. If SR.Z is set then then the next instruction after the JNZ is run.
Let’s add two busy loops: one after the LED is turned on, and one after the LED is turned off. We’ll call the first busy loop DelayOn and the second busy loop DelayOff. The new code looks like this:
; Set GPIO P1.0 to be an output BIS.B #1, &PADIR_L MainLoop: ; Set output on GPIO P1.0 to high voltage BIS.B #1, &PAOUT_L MOV #50000, R4 ; works out to a delay of about 0.2 seconds DelayOn: SUB #1, R4 ; subtract 1 from R4 CMP #0, R4 ; compare R4 to 0 JNZ DelayOn ; while R4 != 0, keep running DelayOn loop ; Set output on GPIO P1.0 to low voltage BIC.B #1, &PAOUT_L MOV #50000, R4 DelayOff: SUB #1, R4 CMP #0, R4 JNZ DelayOff JMP MainLoop
When I run this code I see the LED blinking a few times a second - the delays must have worked! Here’s the logic analyzer’s view of the LED voltage:
The LED has a high voltage (on) for about 0.189 seconds, and a low voltage (off) for about the same time.
We’ve used a busy wait loop to successfully slow down the blinking LED from microcontroller-fast (on/off every few microseconds) to human-slow (on/off every 200 milliseconds or so).
Delaying using a busy wait is simple and effective, but it has its downsides too. Busy waiting is crude, inefficient, and not portable:
- Crude: I had to experiment with the value to count down from in R4 to get a blink rate I was happy with. 50000 happened to work, but it took a bunch of trial and error to dial it in. Also, this simple example has nothing else going on. If interrupts were enabled (we’ll discuss them later) or higher priority code was running, the busy wait loop may be interrupted, giving seemingly random different delay times.
- Inefficient: It’s called busy waiting because the microcontroller is busy while it’s waiting: it’s using power and fully occupied waiting.
- Not portable: If you change the clock speed of your microcontroller the delay value will need to change because it will busy wait faster or slower. If you add or remove higher priority code the delay time will change as well.
Next week I’ll start discussing a peripheral called a timer. A timer keeps time - surprise! Like a GPIO, a timer is a ubiquitous microcontroller peripheral that is used in pretty much every embedded system. I’ll explore how to use a timer to create a better delay in the coming weeks.
This post is part of a series. Check out the complete Embedded Software Engineering 101 series here.