ESE101: Three Simple Steps to a Better Microcontroller
Last time, I introduced the microcontroller and its instructions, registers, and memory. I started with a simple fictional microcontroller and ran through a few examples of how its instructions worked. The microcontroller ran instructions when I said “let’s run this ADD instruction now.” But of course a real microcontroller doesn’t have someone feeding it instructions as it runs, so it has to get the instructions from somewhere. This post will make our fictional microcontroller more realistic by answering the question, “Where does a microcontroller get instructions from?”
The short answer is: That somewhere is memory. Read on to learn more!
When a microcontroller powers up it looks in a defined memory location for the first instruction to run. It runs that first instruction, and then looks at the next memory location for the second instruction, runs it, and so on.
Let’s make this more concrete using our ADD, SUB, and MOVE instructions from last time.
For this example we'll assume that each instruction is two bytes long. So if an ADD instruction starts at memory location zero, it occupies memory locations zero and one. For example, memory might look like this:
Memory locations zero and one are taken up by the ADD R0, R3 instruction. SUB R1, R2 is stored in memory locations two and three. The MOVE R3, [6] instruction is the last instruction in our example, and it is stored in memory locations four and five. I’ve put some arbitrary values in the last couple of memory locations.
Now we have some instructions in memory, along with some data. How does the microcontroller know where to find the instructions? It uses a special register called a Program Counter (PC) to keep track of where in memory it’s looking for instructions. When a microcontroller powers up or is reset, the PC is automatically set to some known starting address so it always starts at the same place. This starting address is called the reset address or reset vector.
A microcontroller does its job by following three simple steps:
- Read the instruction at the memory address in the PC register.
- Run the instruction.
- Update the PC register to point to the next instruction in memory.
These three steps are repeated forever, or until the power is turned off.
Let’s go through an example. Our microcontroller will use a reset address of zero, meaning that our PC will be set to zero initially. Normally all register and memory locations are set to zero at reset, but I’ll set the register (other than the PC) and memory locations to non-zero values so our examples have more interesting instruction arguments than all zeroes.
The starting state of our microcontroller looks like this:
You can see that we’ve added the PC to the four registers we had last time, and that it’s been set to zero since that’s the reset address for our microcontroller. We'll begin with PC=0 and start at Step 1.
PC=0, Step 1: Read the instruction at the memory address in the PC register.
When our microcontroller powers up, it looks at its PC, finds a zero there, and then reads the instruction stored at memory location zero. Our microcontroller knows that each instruction is two bytes long (because we defined it that way), and so it reads memory locations zero and one to get the complete instruction from memory.
The instruction it finds is:
ADD R0, R3
PC=0, Step 2: Run the instruction.
The ADD instruction does R3 = R0 + R3. Memory is unchanged, but the registers change to look like this, with the changed value written in red:
PC=0, Step 3: Update the PC register to point to the next instruction in memory.
After the ADD instruction is done, the microcontroller updates the PC register to be the memory address of the next instruction; it simply adds two to the current PC value. Since each instruction is two bytes long, adding two sets the PC so it now has the memory location of the next instruction. The registers now look like this:
And now we go back to Step 1 of the simple microcontroller execution loop. Let’s walk through the loop again, this time with PC=2.
PC=2, Step 1: Read the instruction at the memory address in the PC register.
The microcontroller looks at its PC register, sees that it’s two and then reads the instruction stored at memory location two. Remember, our microcontroller knows that each instruction is two bytes long and so it reads memory locations two and three to get the complete instruction from memory.
It finds this instruction:
SUB R1, R2
PC=2, Step 2: Run the instruction.
This instruction is R2 = R2 - R1, which changes the R2 register. The registers now look like this:
PC=2, Step 3: Update the PC register to point to the next instruction in memory.
The microcontroller adds two to the PC to point to the next instruction in memory. The registers now look like this:
And now we once again go back to Step 1.
PC=4, Step 1: Read the instruction at the memory address in the PC register.
The PC is four, and the instruction at memory locations four and five (remember, each instruction is two bytes long) is:
MOVE R3, [6]
PC=4, Step 2: Run the instruction.
This MOVE instruction copies the value in R3 into memory location six, and so memory now looks like this:
PC=4, Step 3: Update the PC register to point to the next instruction in memory.
The PC is incremented by two and so the registers look like:
And that brings us to the end of the example. Note that if you continued running the the example and used the current PC (=6) to find the next instruction, things would get confusing: memory locations six and seven are where the next instruction would be, but the drawing of memory shows two normal numbers there (43 and 12), not instructions! What would the microcontroller do? We’ll talk about that in a future post, but for now feel free to guess in in the comments below.
We’ve learned how a microcontroller follows three simple steps to find and run instructions. There are plenty of details left to fill in, of course, but if you understand the three steps and how memory and registers are used to store and run instructions, then you’ve got a good microcontroller foundation that we’ll continue to build on.
We’ll end here for today. Come back next week when we introduce a couple of new instructions that make running code a lot more interesting (and useful)!
And as always, if something doesn’t make sense or if you have any questions, please add a comment below and I’ll try to help!
This post is part of a series. Check out the complete Embedded Software Engineering 101 series here.