Welcome to the Embedded Systems Programming course. My name is Miro Samek
and in this lesson I'll introduce you to arrays and basic pointer arithmetic in C. You will learn how
to apply these concepts to take advantage of the more advanced features of the Stellaris
GPIO DATA registers, which I hope will answer some questions asked in the comments to this
video course posted on YouTube.
As usual, let's start with making a copy of the previous "lesson6" project and renaming
it to "lesson7". If you are just joining the course, you can download the previous projects
from state-machine.com/quickstart.
Get inside the new "lesson7" 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".
So, this is the program you created in "lesson6". Let's clean it up a bit and step into the
debugger.
As you can see, this program uses read-modify-write sequences to change the value of individual
bits of the GPIO register without disturbing the other bits. For example, to set bit-1
controlling the red-LED, the program first reads the current value of the GPIOF DATA
register with the LDR instruction. Next it uses the bit-wise OR to set the bit-1, and
finally it writes the modified value back with the STR instruction.
The read-modify-write sequence is necessary, because all the GPIO bits are co-located in
a single byte, with a single address. But imagine that each bit could be accessed separately
through its own unique address. Or better yet, every possible combination of GPIO bits
would have its own unique address. Then a single, atomic write operation to this particular
address would change the selected bits without disturbing any other GPIO bits.
In this lesson, I will show you how the Stellaris GPIO hardware allows you to do exactly this,
so that you can replace the typical read-modify-write sequence with a single and atomic write operation.
But before I explain HOW to do this, I think it's interesting to understand WHY bother.
After all, the read-modify-write sequence is fast enough for most cases.
Well, it turns out that it is not so much about the speed in this case, but rather to
give you the ability to manipulate the individual GPIO bits truly independently; from any part
of your code, including from interrupts.
Now, I realize that I haven't talked about interrupts yet, and I promise I will, because
it is a fascinating subject, especially in embedded systems. But just for now let me
only say that interrupts are a hardware-supported mechanism for a processor to abruptly change
the flow of control in your program. When an interrupt occurs, a special hardware in
the processor changes the value of the program counter register so that the processor suddenly
starts executing a different piece of code called the interrupt service routine or ISR,
which is typically quite short. When the ISR ends, the processor resumes the execution
of the original code as though nothing happened.
The interesting case is when the ISR changes some GPIO bits. If the interrupt happens to
come in the middle of the read-modify-write cycle, after the main code reads the GPIO
register, but before it writes the modified value back, any changes to the GPIO bits made
in the interrupt service routine will get lost. This is because the main code will still
use the previous value of the GPIO register, before the interrupt happened. This is the
inherent problem with the read-modify-write sequence.
So that's the main reason why the designers of the Stellaris GPIO hardware devised a way
to avoid the read-modify-write sequence and replace it with a single and atomic write
operation.
Here is how it works. The GPIO bits are connected to the CPU with the bunch of wires called
a bus. Each bit is connected to both a dedicated data line and a dedicated address line as
well. The bit can be changed only when the connected address line is 1, otherwise the
bit is unaffected, regardless of the value of the attached data line.
For example, to isolate the three GPIO bits connected to the LEDs, you need to write to
the address ending with 0000111000 binary. Please note that the two lowest-order address
lines A0 and A1 are not used, because the hardware requires all addresses to be divisible
by 4.
The data you write determine the state of the pins. For example, you can light up the
red-LED, extinguish the blue-LED and light-up the green-LED, all in a single write operation.
I hope it is becoming clear that this hardware design requires many registers with unique
addresses, because not only each GPIO bit has its own address-- for this you would need
only 8 registers. Each combination of bits has its own address in this scheme. To cover
all possible bit combinations of 8 GPIO bits, the Stellaris GPIO provides 256 32-bit DATA
registers starting with the address 0x40025000.
So far in the previous lessons, you have been using only the last of these registers called
GPIO_PORTF_DATA_R corresponding to the offset 1111111100 binary, which is 0x3FC hex. This
register obviously didn't isolate any bits, and enabled all 8 GPIO bits to be changed
with data lines. In this lesson we will use the other registers.
So, now the question is how to access all those GPIO registers in C.
One way is to hard-code the address directly, with the brute-force approach you learned
in lesson 3. For example, to isolate only the bit-1 corresponding to the red-LED, you
could manually compute the address by starting with the base address from the datasheet and
adding to it the LED_RED bit left-shifted by two to skip the two unused address bits.
You need to cast the synthesized address to a pointer and de-reference the pointer.
Remembering that this address isolates just a single bit, it matters only what you write
to this particular bit, and it is irrelevant what you write to the other bits. For the
sake of demonstration, let's write 1 to the LED_RED bit and 0 to all the other bits. Let's
check if this code compiles by pressing F7.
Now it is of course interesting to test this on the Launchpad board.
As you can see, the read-modify-write sequence is simplified to just the STR instruction
to the address in R2, which is the GPIO base address plus the offset 8. When you step through
the code, you see that the red-LED lights up, and the other LEDs stay unchanged, proving
that the code does exactly what you wanted.
So the code works, but is not very elegant. You can improve it significantly by applying
the concept of an array.
An array is a group of variables of the same type, occupying consecutive memory locations,
such as the group of 256 identical GPIO DATA registers.
In C you can declare an array by adding the number of elements in square brackets to a
variable. For example, this is an array of two counters, each being a volatile integer.
You can even initialize the whole array at once, by using the array initializer, like
this.
Now, you can use the array elements as normal variables by referring to the elements by
their numbers. The number in the brackets is called the array index and the first element
of an array in C has always 0. The second is 1 and so on.
Let's compile by pressing F7 to see if the compiler accepts the syntax.
Arrays in C are closely related to pointers. The C compiler treats an array as a pointer,
which points to the beginning of the array. To get a pointer to an element with index
i, you simply need to add i to the array pointer.
So instead of counter[1] you can write *(counter + 1). This is an example of a simple pointer
arithmetic.
The correspondence between arrays and pointers goes both ways, because every pointer can
also be viewed as an array. For example, the standard LM4F header file defines the pointer
GPIO_PORTF_DATA_BITS_R. This pointer can be used to access all 256 GPIO DATA registers
as an array.
So, for instance to access only the LED_RED bit, you can index into GPIO_PORTF_DATA_BITS_R
like this...
which is exactly equivalent to using the following pointer arithmetic...
Let's step into the debugger to see how these three options compare.
As you can see, all three implementation options write to the same address stored in the R4
register,
So this little experiment shows that indeed all the three alternatives are equivalent
and generate the exact same machine code.
Going back to the source code, let me point out the very important difference between
address arithmetic and pointer arithmetic. In the first case, you perform address arithmetic
first and only then you cast the raw address to the unsigned long pointer. In this case,
you had to left-shift the LED_RED value by two bits, to take into account the size of
the GPIO register, which is 4-byte wide.
In the second case, you use pointer arithmetic, because GPIO_PORTF_DATA_BITS_R is a pointer
to unsigned long. In pointer arithmetic you don't need to scale the offset by the size
of the element, because this is done automatically for you. This must be so, because of the equivalence
between pointer arithmetic and array indexing.
Of all these thee options, I think that array indexing looks the cleanest, so I am going
to leave this one and comment out the others.
Now, let's use the array indexing technique to clear the red-LED. As you remember from
the wiring diagram, this requires writing 0 to the LED_RED bit position.
Finally, I use the array indexing consistently for setting the blue-LED bit in the beginning.
So, this is the final program that uses the fast and interrupt-safe technique for manipulating
GPIO bits.
Let's test this program on the Launchpad board...
First, let's run full speed and watch the LEDs. As you can see the program works as
before.
When you break into the code, you can see that clearing the red-LED bit takes only one
STR instruction to the GPIO address in R0.
So, your program is about as good as it gets, except that, as it turns out, the Stellaris
LM4F microcontroller can do even better.
As you can find out in the Datasheet, the microcontroller has not one, but two peripheral
busses.
Advanced Peripheral Bus (APB) and Advanced High-Performance Bus (AHB). The GPIO ports
are connected to both.
The APB bus is the default and this is what you've been using so far. But APB is older
and slower than AHB, and is kept only for backwards compatibility. So in the remaining
minute of this lesson, I will show you how to switch to the faster and better AHB.
First, you need to find in the Datasheet how to switch GPIO from the default APB to AHB.
In the system control section, you find the GPIOHBCTL register, which does exactly this.
You note that PortF is controlled by bit number 5.
Next, you go to the LM4F header file and look for the GPIOHBCTL register.
You copy the register name and set bit 5 in it.
Finally, you need to change all GPIO addresses from the APB address range, which is called
APB aperture in the Datasheet, to the AHB aperture.
You search again the LM4F header file for GPIO_PORTF and you find a set of registers
with the _AHB suffix. So, you need to add this suffix to all the GPIO_PORTF registers
in your program.
Let's test this final version on the Launchpad board...
This concludes this lesson about arrays and pointer arithmetic in C. Now you are an expert
in Stellaris GPIO, so congratulations!
In the next lesson, I'll talk about C functions!
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.