In the Hello World tutorial we glossed over a lot of detail about what we were writing to and how the hardware access works. For this tutorial, we’re going to revisit our blinking LED and go into more detail about the hardware involved.
A register is a memory address that is tied to hardware, effectively the hardware either listens to what is written to a register, or updates the contents of a register for us to read and act on.
A peripheral, like say a USART or a timer, has a collection of registers that makes up the capabilities of that peripheral. On the Kakapo, those collections are represented by C structs, with different instances of the same peripheral using the same base struct. These are pulled into our code with the include near the top of the hello world example:
In our Hello World example, we were accessing a collection called “PORTE”. PORTE is an instance of a digital IO port. Digital IO ports have a bunch of registers associated with them, to define the direction of each pin, the output state, the input being sensed on the pin, as well as more advanced features such as interrupts and pin behaviour.
(For more information about C structs, you may wish to Google them.)
There are similar instances called PORTA, PORTB, PORTC, and PORTD. All of them have the same collection of registers associated with them.
When we write to, say, the OUT register of a port, this has the immediate effect of changing the output of one or more pins on this port. Similarly, if we read the IN register of a port, we get the current sense level of each of the pins. There is a direction register (DIR) for whether a pin is an input or output. Lastly, as noted in the Hello World example, OUT and DIR have bitmask access registers as well.
Try attaching an LED to another IO pin, and modifying Hello World to blink it instead. The IO pins are all marked on the board with their port and pin number (eg, PC1 is Port C, pin 1), and you can use any of them as digital IO.
Not all registers are used this way, however. Some registers have many different functions or settings on specific bits of the register. This is where the friendly names for parts of a register come into play.
Our Hello World code used “PIN3_bm” to refer to pin 3 of a digital IO port. But what is this actually doing?
The OUT register is just a number, 1 bit for each pin. We could have written a literal number to the register like this:
PORTE.OUTSET = 0x08
But this is much less clear about what it means. To help us, the io.h we included also has many useful values as friendly names. “PIN3_bm” is an example of a friendly name.
These are used to make it more obvious what we are fiddling with in a register. For example, to set a output pin mode as “wired AND”, we can use the following:
PORTC.PIN3CTRL = ((PORTC.PIN3CTRL & ~(PORT_OPC_gm)) | PORT_OPC_WIREDAND_gc);
Because we don’t have bitmask access, we have to set this feature by first clearing the OPC bits from the existing register value (“PORTC.PIN3CTRL & ~(PORT_OPC_gm)”), and then applying the bits for the value we want.
As you can see, while this is not the easiest thing to read, it is much easier than:
PORTC.PIN3CTRL = ((PORTC.PIN3CTRL & ~(0x38)) | 0x28);
You can find many of these friendly names by reading the appropriate headers (avr/iox64d4.h for the Kakapo) and by reading the datasheet for the ATXMEGA64D4.