Last time, we got an LED to blink, but the application code was extremely ugly because we were writing to registers in the application code. This makes the code hard to read, maintain and build upon. Therefore, let’s make a hardware abstraction layer for GPIOs!
Let’s start off simple: setting a pin as an input or an output, reading what the pin value is, and writing the pin value (setting it high or low). Fortunately, each of these functions have a register associated with them:

So the datasheet says we use DDRxn to make the pin input or output, PORTxn to write the pin value, and PINxn to read the pin value. The x denotes port, while n denotes pin number. For example, last time one of the LEDs was on PE6 (Pin on port E, pin number 6), so we’ll need to manipulate bit 6 in registers DDRE, PORTE and PINE. So let’s make some helper functions to set and clear these pins.
As a quick aside, I’m going to be writing in C++ because the object oriented nature of classes supported by C++ goes very well with hardware abstraction, and makes scaling, reusing and maintaining code very easy. I won’t be going into how classes and pointers work in C++, as that is outside the scope of this post.
Let’s work on the constructor for the GPIO class. The constructor, when given a port and pin number, should figure out what registers it should be concerned with. Here’s my code below:

Let’s walk through the code. First, the constructor records what pin we’re using in gpio_pin. It also creates a bit mask in gpio_pin_m, which is useful for setting and clearing bits in registers. Then comes the meat of the constructor, the switch statement. When provided with a port, the switch statement sets up pointers to the relevant PORTx, DDRx and PINx (it also figures out whether the pin is capable of generating interrupts). For example, when provided with PORTE, gpio_PORT_r points to PORTE, gpio_DDR_r points to DDRE, and gpio_PIN_r points to PINE. Now that the pin and port for the GPIO are all figured out, the constructor sets the value for the pin (HIGH or LOW) and then sets the direction of the pin (input or output).
The main point of the constructor in this case is to set-up the private variables that will be used by the rest of the method members of the class, primarily the register pointers. Let’s look at some examples:

Above shows the method class members, AKA helper functions, that allow you to write and read the pin. gpio_clear, for example, sets the correct bit in the correct port register low. For example, for PE6, gpio_clear will set bit 6 in PORTE low. Likewise, gpio_set sets bit 6 in PORTE high. gpio_get, meanwhile, returns the relevant bit in the relevant PORT register. For PE6, gpio_get will return bit 6 in PINE.

The code above shows how the class sets and gets pin direction. Let’s say we’re looking at PE6 again. Thanks to the constructor, gpio_DDR_r points to the correct data direction register, DDRE, and gpio_pin_m is set-up to be usable right away. Therefore, if the user wants the pin to be an input, then direction_set writes a 0 to bit 6 in DDRE. If the pin is made an output, then the method writes a 1 to bit 6 in DDRE. If the user wants to see if the pin is an output, then is_output returns bit 6 in DDRE.
I hope the examples above show how useful the constructor is in setting up pointers and generating bit-masks. Thanks to the constructor, the methods are very simple!
Speaking of simple, now let’s write an example application code and see how clean it is.

main.cpp on the right
main.h is also very important to hardware abstraction. Besides containing all the libraries to import (again we’re using avr/io and util/delay, but we’re also using the GPIO class defined in GPIO.h and GPIO.cpp), it also includes definitions that keep main.cpp clean. For example, main.cpp doesn’t care what the GPIO ports and pin numbers are, it just wants to turn them on and off. Therefore, the port, pin number and pin direction can be written in main.h instead, making the code clean and legible. Furthermore, you can change TP2’s port, pin number and direction without laying a finger on main.cpp, so this is good practice.
Let’s walk through main.cpp. Here, 4 instances of the GPIO class are created: TP1, TP2, TP3 and TP4. These classes are constructed using the definitions in main.h, which defines what the port, pin number and pin directions should be. Then, inside the while loop, TP1, TP2, TP3 and TP4 are driven high or low. Pretty straight forward!
The strengths of hardware abstraction are evident here. Firstly, main.cpp doesn’t have to know anything about reading and writing registers in order to set pins high or low. Secondly, main.cpp is schematic independent; if I respin the board, and make TP2 connect to PD3 instead of PF0, then all I have to do is edit a couple lines of main.h! Thirdly, look how much cleaner the code is compared to the previous variant (second to last image in the post).
Now that I’ve addressed the fundamentals, let me elaborate on the class set-up.

At the top are the enums. For ATMEGA32U4, there are only 5 ports: PORTB through PORTF. By defining the GPIO_port enum, I make sure whoever uses the constructor will only be able to call ports that exist. The same idea is applied for the GPIO_pin enum; each port can only have pins 0 through 7, so the constructor can only be called with legitimate pin number. The same for GPIO_dir as well. Enums provide a way to limit the programmer to only using legitimate values, as well as keep the code readable by giving names to values (for example, GPIO_LOW rather than 0b00).
There’s more to GPIOs than reading and setting pins or changing direction of the pins. For ATMEGA32U4, GPIOs can also have pull-ups on them, and some of them can trigger interrupts. So let’s code those too!
Firstly, enums are defined for the interrupts. GPIO_INT enum is used to record what kind of interrupt the pin can generate. Some pins cannot generate interrupts, some generate pin change interrupts, and some generate external interrupts (pin change interrupts are more limited than external interrupts, but external interrupts are fewer). Therefore, the enum defines GPIO_NONE, GPIO_PCINT, GPIO_EXTINT respectively. GPIO_EXTINT6 is a special case of GPIO_EXTINT, but they’re basically the same for the most part.
Secondly, enums are defined for how the external interrupt is triggered. While pin change interrupts cannot be configured, and they just fire whenever a pin value is changed, external interrupts are more complex: they can fire only when a rising edge occurs on that pin, a falling edge occurs, any edge occurs (which basically makes this like a pin change interrupt), or when the pin has a value of LOW. In order to make sure the external interrupt is configured to a legit value, and to make the program human readable, this enum is defined based on the table provided in the datasheet:

Let’s look at the interrupt enable and disable code:

When the constructor for the pin is called, int_type is setup. The constructor determines, based on the port and pin number of the GPIO, whether it is a pin capable of generating interrupts, and if so, what kind of interrupt. Therefore, when gpio_interrupt_enable is called, it sets the appropriate bit in the appropriate register. Likewise, gpio_interrupt_disable clears the appropriate bit in the appropriate register. In this case, PCMSK0 is manipulated for pin change interrupt pins, and EIMSK is manipulated for external interrupt pins. If the pin is not capable of generating interrupts, the function returns a -1.

External interrupts can also be configured to trigger on rising edge, falling edge, any edge, or low value on the pin. Therefore, gpio_extint_config will setup the pin to behave as desired. This is where the distinction between GPIO_EXTINT and GPIO_EXTINT6 is most relevant: EXTINT0 through 3 are set-up in EICRA, while EXTINT6 is set-up in EICRB (EXTINT6 also has some special clocking requirements). But the point is that this function will set-up the pin to only trigger when the specified trigger occurs.
That’s pretty much it for enabling, disabling and configuring interrupts. Now, let’s take a look at pull-ups.

From ATMEGA32U4 datasheet
The pull-up is very simple and limited for this microcontroller. They’re either enabled for all pins, or disabled for all pins. Bummer, since a lot of other microcontrollers allow you to control pull-ups on a port-by-port or pin-by-pin basis. Oh well. At least the coding is simple; just set or clear PUD in MCUCR!

Above shows clearing and setting PUD in MCUCR, as well as reporting whether or not pull-up is enabled or disabled. As the comments say, pull-up is only enabled on a pin if it is an input AND the corresponding bit in the PORT pin is a high (as long as pull-up is enabled, of course):

Two quick asides here. First, note that the pointers to the memory mapped I/O, gpio_DDR_r, gpio_PORT_r, and gpio_PIN_r, are declared as pointers to volatile variables. This is very important. Volatile means that the registers can change value without the CPU doing anything; for example, the PIN register can change value if the input to one of the pins for that register changes from low to high or vice versa. Often times, if the compiler doesn’t expect a variable to change, then it doesn’t bother to read that variable from memory and just uses a local copy in one of its internal registers. However, by marking that variable as volatile, the compiler knows that the register value can change at any point, so it makes sure to read the memory mapped I/O register (that is, the PIN register) instead of using a local copy of the register. Secondly, here is how I have my Atmel Studio 7 set-up. In order for the code to compile, the project must have main.cpp and GPIO.cpp:

Also make sure you have ATMEGA32U4 selected as your target device!

I hope you enjoyed reading about the HAL for GPIOs. Next, we’ll be taking a look at the microcontroller’s timers and counters.