GPIO
module reference#include <gpio.h>Makefile :
gpio
is already included by default
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 :
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};
GPIO::Pin PIN_LED = GPIO::PB06;
GPIO::enableInput(GPIO::PB06);
PIN_LED
/PIN_BUTTON
are way more readable that PB06
/PB07
.
Each GPIO can operate in two modes :
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, 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 :
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}
.
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 highGPIO::set(pin, a == 3);
will set the pin to high only if a equals 3There 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.
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 :
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).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.
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.
get()
. pulling
can either be NONE
, PULLUP
, PULLDOWN
or BUSKEEPER
.set()
.pulling
can either beNONE
, PULLUP
, PULLDOWN
or BUSKEEPER
.HIGH
or LOW
.HIGH
or LOW
.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.
true
if the pin is HIGH
. This is equivalent to get(pin) == HIGH
.true
if the pin is LOW
. This is equivalent to get(pin) == LOW
.HIGH
state. This is equivalent to set(pin, HIGH)
.LOW
state. This is equivalent to set(pin, LOW)
.true
if the pin is currently HIGH
and it was LOW
the last time a polling function was called.true
if the pin is currently LOW
and it was HIGH
the last time a polling function was called.true
if the state of the pin has changed since the last time a polling function was called.trigger
happens. trigger
can either be CHANGE
(any transition), RISING
(low-to-high transition) or FALLING
(high-to-low transition).LOW
for use in conjonction with setHighPA00()
and setLowPA00()
.HIGH
with the minimum overhead possible.LOW
with the minimum overhead possible.LOW
for use in conjonction with setHighPB00()
and setLowPB00()
.HIGH
with the minimum overhead possible.LOW
with the minimum overhead possible.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.
#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, UNDEFINED}; // 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}; const Pin UNDEFINED = {Port::UNDEFINED, 0}; // 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); void setDriveStrength(const Pin& pin, int strength); 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(); // Helpers used to compare two pin numbers inline bool operator==(const Pin& pin1, const Pin& pin2) { return pin1.port == pin2.port && pin1.number == pin2.number && pin1.function == pin2.function; } inline bool operator!=(const Pin& pin1, const Pin& pin2) { return !(pin1 == pin2); } } #endif
#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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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 setDriveStrength(const Pin& pin, int strength) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port const uint32_t REG_BASE = GPIO_BASE + static_cast<uint8_t>(pin.port) * PORT_REG_SIZE; // Check input if (strength < 0) { strength = 0; } else if (strength > 3) { strength = 3; } // ODCR (Output Driving Capability Register) : set the driving capability of this pin if (strength & 0b01) { ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODCR0))->SET = 1 << pin.number; } else { ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODCR0))->CLEAR = 1 << pin.number; } if (strength & 0b10) { ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODCR1))->SET = 1 << pin.number; } else { ((volatile RSCT_REG*)(REG_BASE + OFFSET_ODCR1))->CLEAR = 1 << pin.number; } } void enableInterrupt(const Pin& pin, Trigger trigger) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // Base register offset of this port 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) { if (pin.port == Port::UNDEFINED) { return; } // 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); } } } }