GPIO (General Purpose I/O)
GPIO allows you to control digital pins for input, output, and interrupt-driven operations.
GPIO Concepts
flowchart LR
subgraph MCU
GPIO[GPIO Controller]
end
subgraph Output
LED[LED]
Relay[Relay]
end
subgraph Input
Button[Button]
Sensor[Digital Sensor]
end
GPIO -->|Output| LED
GPIO -->|Output| Relay
Button -->|Input| GPIO
Sensor -->|Input| GPIO
Basic Setup
Using Devicetree Spec (Recommended)
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
/* Get GPIO spec from devicetree alias */
#define LED_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE, gpios);
void main(void)
{
int ret;
/* Check device ready */
if (!gpio_is_ready_dt(&led)) {
printk("LED device not ready\n");
return;
}
/* Configure as output */
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
printk("Failed to configure LED: %d\n", ret);
return;
}
/* Blink forever */
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(500);
}
}
Manual Configuration
#include <zephyr/drivers/gpio.h>
const struct device *gpio = DEVICE_DT_GET(DT_NODELABEL(gpio0));
void main(void)
{
if (!device_is_ready(gpio)) {
return;
}
/* Configure pin 13 as output, initial low */
gpio_pin_configure(gpio, 13, GPIO_OUTPUT_INACTIVE);
/* Set high */
gpio_pin_set(gpio, 13, 1);
/* Set low */
gpio_pin_set(gpio, 13, 0);
/* Toggle */
gpio_pin_toggle(gpio, 13);
}
Output Modes
Configuration Flags
/* Basic output */
gpio_pin_configure_dt(&pin, GPIO_OUTPUT);
/* Output with initial state */
gpio_pin_configure_dt(&pin, GPIO_OUTPUT_ACTIVE); /* Initially on */
gpio_pin_configure_dt(&pin, GPIO_OUTPUT_INACTIVE); /* Initially off */
/* Open-drain (needs external pull-up) */
gpio_pin_configure_dt(&pin, GPIO_OUTPUT | GPIO_OPEN_DRAIN);
/* Open-source (needs external pull-down) */
gpio_pin_configure_dt(&pin, GPIO_OUTPUT | GPIO_OPEN_SOURCE);
Setting Output Values
/* Set to logical 1 (respects ACTIVE_LOW flag) */
gpio_pin_set_dt(&led, 1);
/* Set to logical 0 */
gpio_pin_set_dt(&led, 0);
/* Toggle current state */
gpio_pin_toggle_dt(&led);
/* Set raw value (ignores ACTIVE_LOW) */
gpio_pin_set_raw(gpio, pin, 1);
Input Modes
Configuration
/* Basic input */
gpio_pin_configure_dt(&button, GPIO_INPUT);
/* Input with pull-up */
gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_PULL_UP);
/* Input with pull-down */
gpio_pin_configure_dt(&button, GPIO_INPUT | GPIO_PULL_DOWN);
Reading Input
/* Read logical value (respects ACTIVE_LOW) */
int state = gpio_pin_get_dt(&button);
if (state > 0) {
printk("Button pressed\n");
} else if (state == 0) {
printk("Button released\n");
} else {
printk("Error: %d\n", state);
}
/* Read raw value (ignores ACTIVE_LOW) */
int raw = gpio_pin_get_raw(gpio, pin);
GPIO Interrupts
Interrupt Flow
sequenceDiagram
participant HW as Hardware Pin
participant GPIO as GPIO Controller
participant ISR as Interrupt Handler
participant CB as Callback Function
participant App as Application
HW->>GPIO: Pin state changes
GPIO->>ISR: Trigger interrupt
ISR->>CB: Call registered callback
CB->>App: Signal or queue work
Setting Up Interrupts
#include <zephyr/drivers/gpio.h>
#define BUTTON_NODE DT_ALIAS(sw0)
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios);
static struct gpio_callback button_cb;
/* Callback runs in ISR context! */
void button_handler(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
printk("Button interrupt on pins: 0x%08x\n", pins);
/* Don't do heavy processing here - use work queue instead */
}
void setup_button_interrupt(void)
{
int ret;
if (!gpio_is_ready_dt(&button)) {
return;
}
/* Configure as input */
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) {
return;
}
/* Configure interrupt trigger */
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
return;
}
/* Initialize and add callback */
gpio_init_callback(&button_cb, button_handler, BIT(button.pin));
gpio_add_callback(button.port, &button_cb);
}
Interrupt Trigger Modes
/* Edge triggers */
GPIO_INT_EDGE_RISING /* Low to high transition */
GPIO_INT_EDGE_FALLING /* High to low transition */
GPIO_INT_EDGE_BOTH /* Any transition */
GPIO_INT_EDGE_TO_ACTIVE /* To active state (respects ACTIVE_LOW) */
GPIO_INT_EDGE_TO_INACTIVE /* To inactive state */
/* Level triggers */
GPIO_INT_LEVEL_LOW /* While low */
GPIO_INT_LEVEL_HIGH /* While high */
GPIO_INT_LEVEL_ACTIVE /* While active (respects ACTIVE_LOW) */
GPIO_INT_LEVEL_INACTIVE /* While inactive */
/* Disable */
GPIO_INT_DISABLE /* No interrupt */
Interrupt with Work Queue
static K_WORK_DEFINE(button_work, button_work_handler);
void button_isr(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
/* Submit work to system workqueue */
k_work_submit(&button_work);
}
void button_work_handler(struct k_work *work)
{
/* Safe to do heavy processing here */
printk("Processing button press in thread context\n");
/* Read sensors, update state, etc. */
}
Port Operations
Operate on multiple pins simultaneously:
/* Read entire port */
gpio_port_value_t value;
gpio_port_get(gpio, &value);
/* Set multiple pins */
gpio_port_set_masked(gpio, 0xFF, 0x55); /* Mask, value */
/* Set bits (OR with current) */
gpio_port_set_bits(gpio, BIT(0) | BIT(2));
/* Clear bits (AND with ~mask) */
gpio_port_clear_bits(gpio, BIT(1) | BIT(3));
/* Toggle bits */
gpio_port_toggle_bits(gpio, BIT(4) | BIT(5));
Multiple LEDs Example
#define LED0_NODE DT_ALIAS(led0)
#define LED1_NODE DT_ALIAS(led1)
#define LED2_NODE DT_ALIAS(led2)
static const struct gpio_dt_spec leds[] = {
GPIO_DT_SPEC_GET(LED0_NODE, gpios),
GPIO_DT_SPEC_GET(LED1_NODE, gpios),
GPIO_DT_SPEC_GET(LED2_NODE, gpios),
};
void init_leds(void)
{
for (int i = 0; i < ARRAY_SIZE(leds); i++) {
if (!gpio_is_ready_dt(&leds[i])) {
printk("LED %d not ready\n", i);
continue;
}
gpio_pin_configure_dt(&leds[i], GPIO_OUTPUT_INACTIVE);
}
}
void set_led(int index, bool on)
{
if (index < ARRAY_SIZE(leds)) {
gpio_pin_set_dt(&leds[index], on ? 1 : 0);
}
}
void led_pattern(uint8_t pattern)
{
for (int i = 0; i < ARRAY_SIZE(leds); i++) {
gpio_pin_set_dt(&leds[i], (pattern >> i) & 1);
}
}
Debouncing
Hardware doesn’t debounce - implement in software:
static int64_t last_press_time;
#define DEBOUNCE_MS 50
void button_handler(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
int64_t now = k_uptime_get();
if ((now - last_press_time) > DEBOUNCE_MS) {
last_press_time = now;
k_work_submit(&button_work);
}
}
Or use a timer:
static struct k_timer debounce_timer;
static bool button_stable = true;
void debounce_expired(struct k_timer *timer)
{
button_stable = true;
}
void button_handler(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
if (button_stable) {
button_stable = false;
k_timer_start(&debounce_timer, K_MSEC(50), K_NO_WAIT);
k_work_submit(&button_work);
}
}
void init(void)
{
k_timer_init(&debounce_timer, debounce_expired, NULL);
}
API Reference
/* Configuration */
int gpio_pin_configure(const struct device *port, gpio_pin_t pin,
gpio_flags_t flags);
int gpio_pin_configure_dt(const struct gpio_dt_spec *spec,
gpio_flags_t extra_flags);
/* Output */
int gpio_pin_set(const struct device *port, gpio_pin_t pin, int value);
int gpio_pin_set_dt(const struct gpio_dt_spec *spec, int value);
int gpio_pin_toggle(const struct device *port, gpio_pin_t pin);
int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec);
/* Input */
int gpio_pin_get(const struct device *port, gpio_pin_t pin);
int gpio_pin_get_dt(const struct gpio_dt_spec *spec);
/* Interrupts */
int gpio_pin_interrupt_configure(const struct device *port, gpio_pin_t pin,
gpio_flags_t flags);
int gpio_pin_interrupt_configure_dt(const struct gpio_dt_spec *spec,
gpio_flags_t flags);
void gpio_init_callback(struct gpio_callback *callback,
gpio_callback_handler_t handler,
gpio_port_pins_t pin_mask);
int gpio_add_callback(const struct device *port, struct gpio_callback *callback);
int gpio_remove_callback(const struct device *port,
struct gpio_callback *callback);
Best Practices
- Use DT spec structures -
GPIO_DT_SPEC_GET()for cleaner code - Check device_is_ready() - Before any GPIO operations
- Use ACTIVE_LOW in devicetree - Let the framework handle inversion
- Keep ISR callbacks short - Use work queues for processing
- Implement debouncing - For mechanical switches
- Use port operations - When setting multiple pins simultaneously
Example Code
See the complete GPIO Example demonstrating LED control and button input with interrupts.
Next Steps
Learn about I2C for communicating with peripherals.