GPIO module reference

Header :
#include <gpio.h>
Makefile : gpio is already included by default

Description

Ports

The GPIO (General Purpose Input/Output) controller gives you access to and control over the physical pins of the microcontroller. We usually call these GPIO pins, or more simply GPIOs.

These GPIOs are divided into groups, called ports (each containing 32 or 16 pins) identified by letters : Port A, Port B and Port C. As explained in the general presentation the SAM4L microcontroller line offers 3 sizes of packages : 48-pin, 64-pin and 100-pin :

  • the 48-pin version only has Port A (32 GPIOs)
  • the 64-pin version, used by the Carbide, has Port A (32 GPIOs) and Port B (16 GPIOs)
  • the 100-pin version, used by the Xplained Pro kit, has all three Port A (32 GPIOs), Port B (16 GPIOs) and Port C (32 GPIOs)

A pin is therefore identified by its port and its number, such as PA28 or PB06. In the library, this is implemented using the Pin struct which combines the port and the pin number. For example, the PB06 pin can be defined like this :

  • GPIO::Pin PIN_LED = {GPIO::Port::B, 6};
There are also predefined constants to make things simpler, so for the same pin you can simply write :
  • GPIO::Pin PIN_LED = GPIO::PB06;
These constants can be used directly into the functions below if necessary :
  • GPIO::enableInput(GPIO::PB06);
However, it's always a good idea to define the pins explicitely at the top of the file, because you can more easily change the pin and PIN_LED/PIN_BUTTON are way more readable that PB06/PB07.

GPIO mode and peripheral mode

Each GPIO can operate in two modes :

  • In the "classic GPIO" mode, the pin operates in a digital fashion (it can only be in a high or low state) and can be either an input or an output. After calling enableInput() you can check the current state on the pin with get(), and after calling enableOutput() you can set the state on the pin with set().
  • In peripheral mode, the management of the pin is delegated to another peripheral. This mode is used by other modules to activate specific functions on the pins, such as the ADC which can be used to measure a voltage.

Peripheral function multiplexing

In peripheral mode, each pin can be mapped to one of eight peripheral functions. Most peripheral functions can be mapped to one of a few different pins. These mappings are defined in the chapter 3.2 Peripheral Multiplexing on I/O lines in the SAM4L's datasheet and are package-dependant. For example, for the 64-pin package :

  • PA11 can be mapped to USART0 RXD, TC0 B1, PEVC PAD EVT3, PARC PCDATA2, LCDCA COM1 or CATB SENSE7. Not all these functions are actually implemented in the library, but USART0 RXD means the Rx line of the USART controller n°0, and TC0 B1 means sub-channel 1 of channel B of the Timer/Counter controller 0.
  • The USART0 Rx line can be mapped to PA11, PA05, PB00 or PB14.
The pins_sam4l_XX.cpp mapping files (XX being 48, 64 or 100 according to the number of pins of the package you use, for example 64 for Carbide) provide the complete mapping options with sensible defaults and all available alternatives. However, you usually won't need to modify this file because most modules offer a setPin() function that you can use to change the mapping before enabling the module. When using this function, look into the mapping file for the alternative pins for the particular feature you need and give to setPin() the full definition of this pin, such as {GPIO::Port::A, 4, GPIO::Periph::A}.

Pin state

It is always a good idea to use precise types when programming, and this is why a pin state (either read or written) is always typed as PinState with the HIGH and LOW constants. However, you should know that this is strictly equivalent to using a simple bool with true and false. Therefore, it is perfectly fine to use PinState like a boolean expression, such as in the following examples :

  • if (GPIO::get(pin)) {} will enter the if block only if the pin is high
  • GPIO::set(pin, a == 3); will set the pin to high only if a equals 3

Polling

There are three functions that allow you to simply poll a pin in order to detect when the value changes : risingEdge(), fallingEdge() and changed(). These functions store internally the current state of the pin, compare it to the last stored state, and return true if the two are different. They are meant to be used inside a loop (probably your main loop) where they are called regularly.

Remember that if there is a quick blip in the signal between two calls of these functions, they won't be able to detect it. These polling functions are simple to use and work well for slow signals (e.g. the user pressing a button) but for a more reliable detection of signal change, use interrupts instead.

Interrupts

An interrupt is an event that is able to stop the current execution of the CPU in order to be handled as a priority, before resuming the normal execution. In the context of a GPIO, an interrupt can be triggered when a specified edge (rising, falling or any of the two) is detected in the pin. This will call a function of your choice, called the interrupt handler. It is a bit more complexe than polling but also a lot quicker and more efficient, because the event will be handled immediately when it happens, and you won't miss even a very short blip.

Interrupts are great, however, keep in mind two important points :

  • Keep the interrupt handlers as short and simple as possible, because the main execution will be stopped during this time and this might cause problems if it happens in the middle of a timing-sensitive code. If you need to make a heavy computation when an interrupt occurs, consider using the handler to simply toggle a boolean variable, and check this in your main loop to make the computation.
  • Be careful when writing global variables from within an interrupt handler : it means that the value of this variable can change unexpectedly during the execution of another portion of your code that accesses it. If this is what you want, also remember to declare the variable volatile, otherwise the compiler might optimize some things that will lead to strange behaviour (e.g. the value of the variable doesn't appear to have been modified in your main loop even though the handler has been executed).

Pulling resistors

In input mode, every GPIO has an optional internal pulling resistor that can be used to force a certain state when no other device is applying a state on the line (this is called high-impedance, or hi-Z) : a pull-up resistor pulls the state to high, a pull-down resistor pulls the state to low. Otherwise, when reading a pin in high impedance state, the value read is (kind of) random.

A common use for pulling resistor is for input buttons. Usually, a button is used to connect an input pin to GND when it is pressed and keeps the circuit open otherwise, leaving it in a high-impedance state. Configuring a pull-up resistor on the pin allows to read low when the button is pressed, and high when the button is not pressed.

There is also a special mode, called buskeeper, that is used to keep the last state applied on the pin even when it is disconnected. This is not commonly used but can prove useful in some circuits.

High-speed pins

The module offers functions for quick toggling of the pins PA00 and PB00. These pins don't actually have anything special compared to the others (see the Hacking section below), but the functions that toggle them have been optimised to operate with the minimum overhead possible. This is why they are "fixed" pins : it removes the overhead of looking for the correct register to modify, and moreover allows inlining (removing the function call overhead), leading to only a few clock cycles consumed by the call.

This is especially useful when you want to toggle a pin when an event happens, for example for debugging purposes with a logic analyzer, without altering the original timing too much with the debugging functions themselves.

Examples

API

Basic GPIO management

void enableInput(const Pin& pin, Pulling pulling=Pulling::NONE)
Enable the pin in input mode. State is read with get(). pulling can either be NONE, PULLUP, PULLDOWN or BUSKEEPER.
void enableOutput(const Pin& pin, PinState value)
Enable the pin in output mode. State is written with set().
void setPulling(const Pin& pin, Pulling pulling=Pulling::NONE)
Enable or disable a pulling resistor on an input pin. pulling can either beNONE, PULLUP, PULLDOWN or BUSKEEPER.
PinState get(const Pin& pin)
Read the state on the given pin, either HIGH or LOW.
void set(const Pin& pin, PinState value)
Set the state on the given pin, either HIGH or LOW.

Peripheral mode

void enablePeripheral(const Pin& pin)

Enable the pin in peripheral mode. The peripheral mode that is enabled is given as the third parameter in the Pin struct.

This function is called inside the other modules and should normally not be called by the user.

Helper functions

bool isHigh(const Pin& pin)
Return true if the pin is HIGH. This is equivalent to get(pin) == HIGH.
bool isLow(const Pin& pin)
Return true if the pin is LOW. This is equivalent to get(pin) == LOW.
void setHigh(const Pin& pin)
Set the pin to a HIGH state. This is equivalent to set(pin, HIGH).
void setLow(const Pin& pin)
Set the pin to a LOW state. This is equivalent to set(pin, LOW).
void blip(const Pin& pin)
Quickly turn the pin high then low, to produce a blip. This can be useful for debug purposes, especially with a logic analyzer.

Polling

bool risingEdge(const Pin& pin)
Return true if the pin is currently HIGH and it was LOW the last time a polling function was called.
bool fallingEdge(const Pin& pin)
Return true if the pin is currently LOW and it was HIGH the last time a polling function was called.
bool changed(const Pin& pin)
Return true if the state of the pin has changed since the last time a polling function was called.

Interrupts

void enableInterrupt(const Pin& pin, void (*handler)(), Trigger trigger=Trigger::RISING)
Enable interrupts on the given pin, calling the handler function when the event specified in trigger happens. trigger can either be CHANGE (any transition), RISING (low-to-high transition) or FALLING (high-to-low transition).
void disableInterrupt(const Pin& pin)
Disable interrupts on the given pin.
void enableInterrupt(const Pin& pin, Trigger trigger=Trigger::RISING)
Re-enable the interrupts on the given pin after it was disabled, or to change the trigger. Do not use this function without setting a handler first.

High-speed pins

void enablePA00()
Enable the PA00 pin to LOW for use in conjonction with setHighPA00() and setLowPA00().
void setHighPA00()
Set the PA00 pin to HIGH with the minimum overhead possible.
void setLowPA00()
Set the PA00 pin to LOW with the minimum overhead possible.
void enablePB00()
Enable the PB00 pin to LOW for use in conjonction with setHighPB00() and setLowPB00().
void setHighPB00()
Set the PB00 pin to HIGH with the minimum overhead possible.
void setLowPB00()
Set the PB00 pin to LOW with the minimum overhead possible.

Hacking

This module is based on the chapter 23. General-Purpose Input/Output Controller (GPIO) in the SAM4L's datasheet.

GPIO management is quite straightforward and there isn't much that can be modified. Mainly, slew-rate control and driving capabilities could be customized for niche applications. You can also add new high-speed pins by copy-pasting the inline definitions in the header in case PA00 and PB00 are already taken or not enough.

Code

Header

#ifndef _GPIO_H_
#define _GPIO_H_

#include <stdint.h>
#include "error.h"

// General Purpose Input Output
// This module controls the chip input/output signal pins
namespace GPIO {

    // Peripheral memory space base address
    const uint32_t GPIO_BASE = 0x400E1000;
    const uint32_t PORT_REG_SIZE = 0x0200;

    // GPIO base type
    typedef struct {
        volatile uint32_t RW;
        volatile uint32_t SET;
        volatile uint32_t CLEAR;
        volatile uint32_t TOGGLE;
    } RSCT_REG;

    // Registers addresses
    const uint32_t OFFSET_GPER =      0x000;
    const uint32_t OFFSET_PMR0 =      0x010;
    const uint32_t OFFSET_PMR1 =      0x020;
    const uint32_t OFFSET_PMR2 =      0x030;
    const uint32_t OFFSET_ODER =      0x040;
    const uint32_t OFFSET_OVR =       0x050;
    const uint32_t OFFSET_PVR =       0x060;
    const uint32_t OFFSET_PUER =      0x070;
    const uint32_t OFFSET_PDER =      0x080;
    const uint32_t OFFSET_IER =       0x090;
    const uint32_t OFFSET_IMR0 =      0x0A0;
    const uint32_t OFFSET_IMR1 =      0x0B0;
    const uint32_t OFFSET_GFER =      0x0C0;
    const uint32_t OFFSET_IFR =       0x0D0;
    const uint32_t OFFSET_ODCR0 =     0x100;
    const uint32_t OFFSET_ODCR1 =     0x110;
    const uint32_t OFFSET_OSRR0 =     0x130;
    const uint32_t OFFSET_STER =      0x160;
    const uint32_t OFFSET_EVER =      0x180;
    const uint32_t OFFSET_PARAMETER = 0x1F8;
    const uint32_t OFFSET_VERSION =   0x1FC;

    // Pins are divided in 3 ports : A, B and C
    // 48-pin packages only have port A, 64-pin packages have ports A and B,
    // and 100-pin packages have all A, B and C ports 
    const int N_PORTS = 3;
    enum class Port {A, B, C};

    // Each pin can either be controlled as a generic input/output (GPIO mode)
    // are be dedicated to one of up to 8 peripheral functions
    // See datasheet 3.2 Peripheral Multiplexing on I/O Lines for more details
    enum class Periph {A, B, C, D, E, F, G, H};    

    // Each pin has an optional pulling resistor, either pull-up or pull-down.
    // Buskeeper is a special mode where the pin keeps the last signal (high or low)
    // applied to it, even when this signal is disconnected.
    enum class Pulling {
        NONE,
        PULLUP,
        PULLDOWN,
        BUSKEEPER
    };
    
    // An interrupt can be generated by each pin on one of these signal change
    enum class Trigger {
        CHANGE,
        RISING,
        FALLING
    };


    // This is the main struct used to define a pin, this should be used in user space, e.g.
    // GPIO::Pin pinLed = {GPIO::Port::A, 2};
    // (function is unused for pins in basic GPIO mode and can be ommited)
    struct Pin {
        Port port;
        uint8_t number;
        Periph function;
    };

    // Pin name helpers
    const Pin PA00 = {Port::A,  0};
    const Pin PA01 = {Port::A,  1};
    const Pin PA02 = {Port::A,  2};
    const Pin PA03 = {Port::A,  3};
    const Pin PA04 = {Port::A,  4};
    const Pin PA05 = {Port::A,  5};
    const Pin PA06 = {Port::A,  6};
    const Pin PA07 = {Port::A,  7};
    const Pin PA08 = {Port::A,  8};
    const Pin PA09 = {Port::A,  9};
    const Pin PA10 = {Port::A, 10};
    const Pin PA11 = {Port::A, 11};
    const Pin PA12 = {Port::A, 12};
    const Pin PA13 = {Port::A, 13};
    const Pin PA14 = {Port::A, 14};
    const Pin PA15 = {Port::A, 15};
    const Pin PA16 = {Port::A, 16};
    const Pin PA17 = {Port::A, 17};
    const Pin PA18 = {Port::A, 18};
    const Pin PA19 = {Port::A, 19};
    const Pin PA20 = {Port::A, 20};
    const Pin PA21 = {Port::A, 21};
    const Pin PA22 = {Port::A, 22};
    const Pin PA23 = {Port::A, 23};
    const Pin PA24 = {Port::A, 24};
    const Pin PA25 = {Port::A, 25};
    const Pin PA26 = {Port::A, 26};
    const Pin PA27 = {Port::A, 27};
    const Pin PA28 = {Port::A, 28};
    const Pin PA29 = {Port::A, 29};
    const Pin PA30 = {Port::A, 30};
    const Pin PA31 = {Port::A, 31};
    const Pin PB00 = {Port::B,  0};
    const Pin PB01 = {Port::B,  1};
    const Pin PB02 = {Port::B,  2};
    const Pin PB03 = {Port::B,  3};
    const Pin PB04 = {Port::B,  4};
    const Pin PB05 = {Port::B,  5};
    const Pin PB06 = {Port::B,  6};
    const Pin PB07 = {Port::B,  7};
    const Pin PB08 = {Port::B,  8};
    const Pin PB09 = {Port::B,  9};
    const Pin PB10 = {Port::B, 10};
    const Pin PB11 = {Port::B, 11};
    const Pin PB12 = {Port::B, 12};
    const Pin PB13 = {Port::B, 13};
    const Pin PB14 = {Port::B, 14};
    const Pin PB15 = {Port::B, 15};
    const Pin PC00 = {Port::C,  0};
    const Pin PC01 = {Port::C,  1};
    const Pin PC02 = {Port::C,  2};
    const Pin PC03 = {Port::C,  3};
    const Pin PC04 = {Port::C,  4};
    const Pin PC05 = {Port::C,  5};
    const Pin PC06 = {Port::C,  6};
    const Pin PC07 = {Port::C,  7};
    const Pin PC08 = {Port::C,  8};
    const Pin PC09 = {Port::C,  9};
    const Pin PC10 = {Port::C, 10};
    const Pin PC11 = {Port::C, 11};
    const Pin PC12 = {Port::C, 12};
    const Pin PC13 = {Port::C, 13};
    const Pin PC14 = {Port::C, 14};
    const Pin PC15 = {Port::C, 15};
    const Pin PC16 = {Port::C, 16};
    const Pin PC17 = {Port::C, 17};
    const Pin PC18 = {Port::C, 18};
    const Pin PC19 = {Port::C, 19};
    const Pin PC20 = {Port::C, 20};
    const Pin PC21 = {Port::C, 21};
    const Pin PC22 = {Port::C, 22};
    const Pin PC23 = {Port::C, 23};
    const Pin PC24 = {Port::C, 24};
    const Pin PC25 = {Port::C, 25};
    const Pin PC26 = {Port::C, 26};
    const Pin PC27 = {Port::C, 27};
    const Pin PC28 = {Port::C, 28};
    const Pin PC29 = {Port::C, 29};
    const Pin PC30 = {Port::C, 30};
    const Pin PC31 = {Port::C, 31};

    // The PinState type can be used for clearer types, even though it's basically a boolean
    using PinState = bool;
    const PinState HIGH = true;
    const PinState LOW = false;

    // Interrupts handling
    const uint8_t N_GPIO_LINES = 96; // Max on the 100-pin package
    const uint8_t N_INTERRUPT_CHANNELS = N_GPIO_LINES / 8; // Each interrupt channel covers 8 GPIO lines
    extern uint32_t portsState[]; // Used for rising(), falling() and changed()
    extern uint32_t interruptHandlers[];

    // Error codes
    const Error::Code ERR_PIN_ALREADY_IN_USE = 1;
    const Error::Code ERR_HANDLER_NOT_DEFINED = 2;


    // Module API

    // Basic GPIO functions
    void enableInput(const Pin& pin, Pulling pulling=Pulling::NONE);
    void enableOutput(const Pin& pin, PinState value);
    void setPulling(const Pin& pin, Pulling pulling);
    PinState get(const Pin& pin);
    void set(const Pin& pin, PinState value);

    // Pin peripheral functions
    // This is usually called by other library modules, not by the user
    void enablePeripheral(const Pin& pin);
    void disablePeripheral(const Pin& pin);

    // Interrupts
    void enableInterrupt(const Pin& pin, void (*handler)(), Trigger trigger=Trigger::RISING);
    void enableInterrupt(const Pin& pin, Trigger trigger=Trigger::RISING);
    void disableInterrupt(const Pin& pin);

    // Helper functions
    inline bool isHigh(const Pin& pin) { return get(pin) == HIGH; };
    inline bool isLow(const Pin& pin) { return get(pin) == LOW; };
    inline void setHigh(const Pin& pin) { set(pin, HIGH); };
    inline void setLow(const Pin& pin) { set(pin, LOW); };
    void blip(const Pin& pin);

    // These functions monitor the pins state by polling and are a simple
    // way to check for a signal change, e.g. checking for a button press
    bool risingEdge(const Pin& pin);
    bool fallingEdge(const Pin& pin);
    bool changed(const Pin& pin);

    // PA00 and PB00 can be turned on and off quickly with these functions.
    // This is useful for debug purposes, when you want to switch a pin when something
    // has happened but you want to use as few clock cycles as possible
    inline void enablePA00() {enableOutput({GPIO::Port::A, 0}, GPIO::LOW);}
    inline void setHighPA00() {((volatile RSCT_REG*)(GPIO_BASE + OFFSET_OVR))->SET = 1;}
    inline void setLowPA00() {((volatile RSCT_REG*)(GPIO_BASE + OFFSET_OVR))->CLEAR = 1;}
    inline void enablePB00() {enableOutput({GPIO::Port::B, 0}, GPIO::LOW);}
    inline void setHighPB00() {((volatile RSCT_REG*)(GPIO_BASE + PORT_REG_SIZE + OFFSET_OVR))->SET = 1;}
    inline void setLowPB00() {((volatile RSCT_REG*)(GPIO_BASE + PORT_REG_SIZE + OFFSET_OVR))->CLEAR = 1;}

    // Internal initialization function. This is called in Core::init() and don't have to
    // be called by the user.
    void init();

}


#endif

Module

#include "gpio.h"
#include "core.h"
#include "error.h"
#include <string.h>

namespace GPIO {

    extern uint8_t INTERRUPT_PRIORITY;
    uint32_t _interruptHandlers[N_GPIO_LINES];
    uint32_t _portsState[N_PORTS];

    // Internal functions
    void interruptHandlerWrapper();


    // Internal initialization function. This is called in Core::init() and doesn't have to
    // be called by the user.
    void init() {
        memset(_interruptHandlers, 0, sizeof(_interruptHandlers));
        memset(_portsState, 0, sizeof(_portsState));
    }

    void enableInput(const Pin& pin, Pulling pulling) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // ODER (Output Driver Enable Register) : set the pin as input
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODER))->CLEAR = 1 << pin.number;

        // STER (Schmitt Trigger Enable Register) : enable the pin input Schmitt trigger (mandatory)
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_STER))->SET = 1 << pin.number;

        // Pulling
        setPulling(pin, pulling);

        // GPER (GPIO Enable Register) : set the pin as driven by the GPIO controller
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_GPER))->SET = 1 << pin.number;

        // Save the current state for polling functions
        PinState state = get(pin);
        if (state == HIGH) {
            _portsState[static_cast<uint8_t>(pin.port)] |= 1 << pin.number;
        } else {
            _portsState[static_cast<uint8_t>(pin.port)] &= ~(uint32_t)(1 << pin.number);
        }
    }

    void enableOutput(const Pin& pin, PinState value) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // OVR (Output Value Register) : set the pin output state
        if (value) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_OVR))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_OVR))->CLEAR = 1 << pin.number;
        }

        // ODER (Output Driver Enable Register) : set the pin as output
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODER))->SET = 1 << pin.number;

        // GPER (GPIO Enable Register) : set the pin as driven by the GPIO controller
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_GPER))->SET = 1 << pin.number;
    }

    void enablePeripheral(const Pin& pin) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // Check if the pin is already used by a peripheral
        if (!(((volatile RSCT_REG*)(REG_BASE + OFFSET_GPER))->RW & (1 << pin.number))) {
            Error::happened(Error::Module::GPIO, ERR_PIN_ALREADY_IN_USE, Error::Severity::CRITICAL);
            return;
        }

        // PMR (Peripheral Mux Register) : set the pin peripheral function
        if (static_cast<uint8_t>(pin.function) & 0b001) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR0))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR0))->CLEAR = 1 << pin.number;
        }
        if (static_cast<uint8_t>(pin.function) & 0b010) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR1))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR1))->CLEAR = 1 << pin.number;
        }
        if (static_cast<uint8_t>(pin.function) & 0b100) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR2))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PMR2))->CLEAR = 1 << pin.number;
        }

        // GPER (GPIO Enable Register) : set the pin as driven by the peripheral function
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_GPER))->CLEAR = 1 << pin.number;
    }

    void disablePeripheral(const Pin& pin) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // GPER (GPIO Enable Register) : set the pin as driven by the GPIO controller
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_GPER))->SET = 1 << pin.number;
    }

    void setPulling(const Pin& pin, Pulling pulling) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        if (pulling == Pulling::PULLUP) {
            // PDER (Pull-Down Enable Register) : disable the pin pull-down resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PDER))->CLEAR = 1 << pin.number;

            // PUER (Pull-Up Enable Register) : enable the pin pull-up resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PUER))->SET = 1 << pin.number;

        } else if (pulling == Pulling::PULLDOWN) {
            // PUER (Pull-Up Enable Register) : disable the pin pull-up resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PUER))->CLEAR = 1 << pin.number;

            // PDER (Pull-Down Enable Register) : enable the pin pull-down resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PDER))->SET = 1 << pin.number;

        } else if (pulling == Pulling::BUSKEEPER) {
            // See datasheet 23.7.10 Pull-down Enable Register
            // PUER (Pull-Up Enable Register) : enable the pin pull-up resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PUER))->SET = 1 << pin.number;

            // PDER (Pull-Down Enable Register) : enable the pin pull-down resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PDER))->SET = 1 << pin.number;

        } else { // Pulling::NONE
            // PUER (Pull-Up Enable Register) : disable the pin pull-up resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PUER))->CLEAR = 1 << pin.number;

            // PDER (Pull-Down Enable Register) : disable the pin pull-down resistor
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_PDER))->CLEAR = 1 << pin.number;
        }
    }

    void enableInterrupt(const Pin& pin, Trigger trigger) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // Select the trigger type : CHANGE = 00, RISING = 01, FALLING = 10
        if (trigger == Trigger::RISING) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_IMR0))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_IMR0))->CLEAR = 1 << pin.number;
        }
        if (trigger == Trigger::FALLING) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_IMR1))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_IMR1))->CLEAR = 1 << pin.number;
        }

        // Enable the interrupt at the GPIO Controller level
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_IER))->SET = 1 << pin.number;

        // Enable the interrupt at the Core level
        Core::enableInterrupt(static_cast<Core::Interrupt>(
                static_cast<int>(Core::Interrupt::GPIO0) 
                + static_cast<uint8_t>(pin.port) * 4 
                + pin.number / 8), INTERRUPT_PRIORITY);
    }

    void enableInterrupt(const Pin& pin, void (*handler)(), Trigger trigger) {
        // Set the interrupt handler
        _interruptHandlers[static_cast<uint8_t>(pin.port) * 32 + pin.number] = (uint32_t) handler;
        Core::setInterruptHandler(static_cast<Core::Interrupt>(
                static_cast<int>(Core::Interrupt::GPIO0)
                + static_cast<uint8_t>(pin.port) * 4 
                + pin.number / 8), &interruptHandlerWrapper);

        // Enable the interrupt with the function above
        enableInterrupt(pin, trigger);
    }

    void disableInterrupt(const Pin& pin) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // Disable the interrupt at the GPIO Controller level
        ((volatile RSCT_REG*)(REG_BASE + OFFSET_IER))->CLEAR = 1 << pin.number;

        // Disable the interrupt at the Core level
        Core::disableInterrupt(static_cast<Core::Interrupt>(
                static_cast<int>(Core::Interrupt::GPIO0)
                + static_cast<uint8_t>(pin.port) * 4
                + pin.number / 8));
    }

    PinState get(const Pin& pin) {
        // PVR (Pin Value Register) : get the pin state
        return ((volatile RSCT_REG*)(GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE + OFFSET_PVR))->RW & (1 << pin.number);
    }

    void set(const Pin& pin, PinState value) {
        const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE;

        // OVR (Output Value Register) : set the pin output state
        if (value) {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_OVR))->SET = 1 << pin.number;
        } else {
            ((volatile RSCT_REG*)(REG_BASE + OFFSET_OVR))->CLEAR = 1 << pin.number;
        }
    }

    void blip(const Pin& pin) {
        // Quickly turn the output pin high then low, to produce a blip
        // This can be useful for debug purposes, especially with a logic analyzer
        setHigh(pin);
        setLow(pin);
    }

    bool risingEdge(const Pin& pin) {
        bool result = false;

        // Check the current pin state
        PinState state = get(pin);
        if (state == HIGH) {
            // If it's HIGH, check the previous state
            if ((_portsState[static_cast<uint8_t>(pin.port)] & (1 << pin.number)) == 0) {
                // The previous state was LOW, therefore this is a rising edge
                result = true;
            }

            // Save the current state
            _portsState[static_cast<uint8_t>(pin.port)] |= 1 << pin.number;

        } else {
            // Save the current state
            _portsState[static_cast<uint8_t>(pin.port)] &= ~(uint32_t)(1 << pin.number);
        }

        return result;
    }

    bool fallingEdge(const Pin& pin) {
        bool result = false;

        // Check the current pin state
        PinState state = get(pin);
        if (state == LOW) {
            // If it's LOW, check the previous state
            if ((_portsState[static_cast<uint8_t>(pin.port)] & (1 << pin.number)) != 0) {
                // The previous state was HIGH, therefore this is a falling edge
                result = true;
            }

            // Save the current state
            _portsState[static_cast<uint8_t>(pin.port)] &= ~(uint32_t)(1 << pin.number);

        } else {
            // Save the current state
            _portsState[static_cast<uint8_t>(pin.port)] |= 1 << pin.number;
        }
        
        return result;
    }

    bool changed(const Pin& pin) {
        bool result = false;

        // Check the current pin state
        PinState state = get(pin);

        // Get the old state 
        PinState oldState = _portsState[static_cast<uint8_t>(pin.port)] & (1 << pin.number);
        if (oldState != state) {
            result = true;
        }

        // Save the current state
        if (state == HIGH) {
            _portsState[static_cast<uint8_t>(pin.port)] |= 1 << pin.number;
        } else {
            _portsState[static_cast<uint8_t>(pin.port)] &= ~(uint32_t)(1 << pin.number);
        }
        
        return result;
    }

    // This wrapper is called when any pin generates an interrupt, and calls
    // the user handler according to the current configuration in _interruptHandlers[]
    void interruptHandlerWrapper() {
        // Get the port and pin numbers which called this interrupt
        int channel = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::GPIO0);
        int port = channel / 4; // There are four 8-pin channels in each port. The division int truncate is voluntary.
        int subport = channel - 4 * port; // Equivalent to subport = channel % 4
        const uint32_t REG_BASE = GPIO_BASE + port * PORT_REG_SIZE;

        // For each of the 8 pins in this subport, call the handler if the interrupt is enabled (in IER) and pending (in IFR)
        uint32_t flag = ((volatile RSCT_REG*)(REG_BASE + OFFSET_IFR))->RW & ((volatile RSCT_REG*)(REG_BASE + OFFSET_IER))->RW;
        for (int pin = 0; pin < 8; pin++) {
            if (flag & (1 << (subport * 8 + pin))) {
                // Call the user handler for this interrupt
                void (*handler)() = (void (*)())_interruptHandlers[port * 32 + subport * 8 + pin];
                if (handler == nullptr) {
                    Error::happened(Error::Module::GPIO, ERR_HANDLER_NOT_DEFINED, Error::Severity::CRITICAL);
                } else {
                    handler();
                }

                // Clear the interrupt source
                ((volatile RSCT_REG*)(REG_BASE + OFFSET_IFR))->CLEAR = 1 << (subport * 8 + pin);
            }
        }
    }
}