Skip to content

GPIO

The GPIO and pin multiplexing (pinctrl) of the F101 live in the same PIO module: 6 banks (PA…PF), 32 lines per bank, one interrupt per bank. In Zephyr each bank is a GPIO device (gpioa…gpiof), the interface is the standard <zephyr/drivers/gpio.h>, and the driver is drivers/gpio/gpio_sunxi.c.

ItemDescription
Devicegpioa, gpiob, gpioc, gpiod, gpioe, gpiof, 32 pins per bank
Directioninput, output, with optional internal pull-up or pull-down
Interruptsrising edge, falling edge, both edges, high level, low level
Not supportedopen drain, open source output (-ENOTSUP), gpio_pin_get_config

GPIO involves three files, from the bottom up:

FileRole
dts/bindings/gpio/allwinner,sunxi-gpio.yamlBinding: defines the properties of the allwinner,sunxi-gpio node
dts/riscv/allwinner/sun252i_f101.dtsiSoC devicetree: the nodes of the 6 banks, all okay by default
boards/yuzukihd/yuzukineko/yuzukineko.dtsBoard devicetree: references specific pins through properties such as gpio-leds and cd-gpios

Below is the node of the PB bank; the other 5 banks differ only in the register address, allwinner,bank and the interrupt number:

gpiob: gpio@2000030 {
/* Matches DT_DRV_COMPAT allwinner_sunxi_gpio in the driver gpio_sunxi.c */
compatible = "allwinner,sunxi-gpio";
/* The registers of this bank: base address 0x02000030, length 0x30 */
reg = <0x02000030 0x30>;
/* PIO register layout type. The F1xx series uses type 0: the bank stride is 0x30 bytes and the interrupt registers are after 0x200 */
allwinner,pio-hw-type = <ALLWINNER_PIO_HW_TYPE0>;
/* Bank number: PA = 0, PB = 1, ..., PF = 5. The driver uses it to work out the interrupt register location of this bank */
allwinner,bank = <1>;
/* This is a GPIO controller; a gpios property that references it has 2 cells: pin number, flags */
gpio-controller;
#gpio-cells = <2>;
/* Number of pins */
ngpios = <32>;
/* The interrupt of this bank: PLIC number 69, priority 1 */
interrupts = <69 1>;
interrupt-parent = <&plic>;
status = "okay";
};

The differences between the six banks:

NodeLabelRegistersallwinner,bankPLIC interrupt number
gpio@2000000gpioa0x02000000067
gpio@2000030gpiob0x02000030169
gpio@2000060gpioc0x02000060271
gpio@2000090gpiod0x02000090373
gpio@20000c0gpioe0x020000c0475
gpio@20000f0gpiof0x020000f0577

The board file uses GPIO in two places. One is the LED, the other is the TF card insertion detection:

/ {
aliases {
/* The application gets the LED with DT_ALIAS(led0) */
led0 = &led0;
};
leds {
compatible = "gpio-leds";
led0: led_0 {
/* &gpiod: the PD bank; 5: line 5, that is PD5; active high */
gpios = <&gpiod 5 GPIO_ACTIVE_HIGH>;
};
};
};
&smhc0 {
/* PF6 is TF card insertion detection, a low level means a card is present, so GPIO_ACTIVE_LOW */
cd-gpios = <&gpiof 6 GPIO_ACTIVE_LOW>;
};

In gpios = <&gpiod 5 GPIO_ACTIVE_HIGH>, the two numbers after &gpiod are the “pin number” and the “flags” prescribed by #gpio-cells = <2>. The bits available for the flags are in include/zephyr/dt-bindings/gpio/gpio.h: GPIO_ACTIVE_LOW, GPIO_PULL_UP, GPIO_PULL_DOWN and so on.

When a pin is not used as ordinary GPIO but is given to a peripheral, pinctrl sets its function. In yuzukineko.dts every peripheral has a pinctrl-0 group:

&pio {
/* UART1 console: PB00 is TX, PB01 is RX, multiplexing function 4 */
uart1_pins: uart1_pins {
uart1_tx_rx {
pinmux = <ALLWINNER_PINMUX(ALLWINNER_PIO_PB, 0, 4)
ALLWINNER_PINMUX(ALLWINNER_PIO_PB, 1, 4)>;
};
};
};
&uart1 {
pinctrl-0 = <&uart1_pins>;
pinctrl-names = "default";
};

In ALLWINNER_PINMUX(bank, pin number, multiplexing function) the multiplexing function is: 0 is input, 1 is output, 2…15 are peripheral functions, 14 is the external interrupt. For what each function of a pin is, see the pin multiplexing table. Properties that can be written in the same group:

PropertyMeaning
pinmuxThe pin and the multiplexing function, one pin per cell
bias-pull-upEnable the internal pull-up
bias-pull-downEnable the internal pull-down
bias-disableDisable the pull-up and pull-down
allwinner,drive-levelDrive strength, 0…3; if it is not written the reset value is kept
# Copyright (c) 2026 Yuzuki Tsuru
# SPDX-License-Identifier: Apache-2.0
description: |
Allwinner sunxi PIO GPIO controller.
Each DT gpio node covers one PIO bank (PA..PF), identified by the
'allwinner,bank' property. The 'allwinner,pio-hw-type' property selects
the PIO register layout used by the driver (see
include/zephyr/drivers/pinctrl/pinctrl_sunxi.h). Bank interrupts are
exposed as interrupt specifiers.
compatible: "allwinner,sunxi-gpio"
include: [gpio-controller.yaml, base.yaml]
properties:
reg:
required: true
interrupts:
required: true
allwinner,pio-hw-type:
required: true
type: int
description: |
PIO hardware layout type. See
include/zephyr/drivers/pinctrl/pinctrl_sunxi.h for the register
layout description of each type.
allwinner,bank:
required: true
type: int
description: |
PIO bank number of this controller (PA = 0, PB = 1, ...).
"#gpio-cells":
const: 2
gpio-cells:
- pin
- flags
PropertyRequiredMeaning
regyesThe registers of this bank
interruptsyesThe interrupt of this bank
allwinner,pio-hw-typeyesPIO register layout; for the F101 it is ALLWINNER_PIO_HW_TYPE0
allwinner,bankyesBank number, PA = 0 … PF = 5
ngpiosfrom gpio-controller.yamlNumber of pins, 32
#gpio-cellsyes, fixed at 2Pin number, flags
OptionDefaultMeaning
CONFIG_GPIOy in the board defconfigEnables the GPIO subsystem. In your own application it has to be written in prj.conf
CONFIG_GPIO_SUNXIy (depends on DT_HAS_ALLWINNER_SUNXI_GPIO_ENABLED)Builds this driver; there is no need to set it manually
CONFIG_GPIO_INIT_PRIORITYZephyr defaultThe initialization priority of the GPIO driver

Each interface below gets its own heading: the prototype, the behaviour of the driver, a complete example function and its output on the board. The examples come from the complete demo program at the end of the page, whose err() turns a return value into a name. The “levels” in the interfaces are all logical levels: for a pin declared GPIO_ACTIVE_LOW in the devicetree, gpio_pin_set_dt(spec, 1) outputs a low level. Interfaces with _raw do not look at this flag.

The demo uses 3 pins:

PinLocationUse
PD5the LED on the board (led0)output
PB2a floating pin on the headeroutput, driven with gpio_port_*
PB3a floating pin on the headerinput; the internal pull-up/pull-down changes the level and triggers the interrupt

PB2 and PB3 are referenced through the zephyr,user node in the demo’s app.overlay. This is the standard way for an application to give pins its own names without changing the board file.

int gpio_pin_configure_dt(const struct gpio_dt_spec *spec, gpio_flags_t extra_flags);

Configures the direction, the initial level and the pull-up/pull-down.

FlagDriver behaviour
GPIO_INPUTSets the multiplexing function to input
GPIO_OUTPUT, GPIO_OUTPUT_INACTIVE, GPIO_OUTPUT_ACTIVESets the multiplexing function to output; it writes the data register with the initial level first and only then switches to output, so there is no glitch on the output
GPIO_PULL_UP, GPIO_PULL_DOWNEnables the corresponding internal pull-up or pull-down; if neither is written the pull-up and pull-down are disabled
GPIO_OPEN_DRAIN, GPIO_OPEN_SOURCEReturns -ENOTSUP
/* gpio_pin_configure_dt: direction, initial level and pull resistor */
static void demo_pin_configure_dt(void)
{
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
printk("[configure] PD5 output, low = %s\n", err(ret));
ret = gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
printk("[configure] PB3 input with pull up = %s\n", err(ret));
ret = gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
printk("[configure] PB3 input with pull down = %s\n", err(ret));
ret = gpio_pin_configure_dt(&out, GPIO_OUTPUT | GPIO_OPEN_DRAIN);
printk("[configure] PB2 open drain output = %s\n", err(ret));
ret = gpio_pin_configure_dt(&out, GPIO_OUTPUT | GPIO_OPEN_SOURCE);
printk("[configure] PB2 open source output = %s\n", err(ret));
}
[configure] PD5 output, low = 0
[configure] PB3 input with pull up = 0
[configure] PB3 input with pull down = 0
[configure] PB2 open drain output = -ENOTSUP
[configure] PB2 open source output = -ENOTSUP
int gpio_pin_set_dt(const struct gpio_dt_spec *spec, int value);

Sets the logical level of an output; 1 is the active level.

/* gpio_pin_set_dt: the logical level of an output */
static void demo_pin_set_dt(void)
{
int ret;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
ret = gpio_pin_set_dt(&led, 1);
printk("[set] gpio_pin_set_dt(PD5, 1) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
ret = gpio_pin_set_dt(&led, 0);
printk("[set] gpio_pin_set_dt(PD5, 0) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
}
[set] gpio_pin_set_dt(PD5, 1) = 0, register bit 1
[set] gpio_pin_set_dt(PD5, 0) = 0, register bit 0
int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec);

Inverts the level of an output.

/* gpio_pin_toggle_dt: invert an output; six calls make the LED blink three times */
static void demo_pin_toggle_dt(void)
{
int ret = gpio_pin_toggle_dt(&led);
printk("[toggle] gpio_pin_toggle_dt(PD5) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
for (int i = 0; i < 5; i++) {
k_msleep(100);
gpio_pin_toggle_dt(&led);
}
printk("[toggle] blinked the LED 3 times\n");
}
[toggle] gpio_pin_toggle_dt(PD5) = 0, register bit 1
[toggle] blinked the LED 3 times

The last line corresponds to the LED blinking 3 times.

int gpio_pin_get_dt(const struct gpio_dt_spec *spec);

Reads the logical level of a pin. For an output pin it reads back the value that was written, not the actual level on the pin.

/* gpio_pin_get_dt: the logical level of a pin. For an output pin it is the level that was written. */
static void demo_pin_get_dt(void)
{
int level;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
level = gpio_pin_get_dt(&led);
printk("[get] PD5 written active, gpio_pin_get_dt = %d\n", level);
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
level = gpio_pin_get_dt(&led);
printk("[get] PD5 written inactive, gpio_pin_get_dt = %d\n", level);
}
[get] PD5 written active, gpio_pin_get_dt = 1
[get] PD5 written inactive, gpio_pin_get_dt = 0
int gpio_pin_get_raw(const struct device *port, gpio_pin_t pin);

Reads the actual level on the pin, without looking at GPIO_ACTIVE_LOW. PB3 is floating, so with the pull-up enabled it reads 1 and with the pull-down enabled it reads 0; this is also how the interrupt demo further down creates an edge.

/* gpio_pin_get_raw: the level of the pin, not looking at GPIO_ACTIVE_LOW. PB3 is not connected,
* so the pull resistor decides.
*/
static void demo_pin_get_raw(void)
{
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
k_msleep(1);
printk("[get_raw] PB3 with pull up = %d\n", gpio_pin_get_raw(in.port, in.pin));
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
k_msleep(1);
printk("[get_raw] PB3 with pull down = %d\n", gpio_pin_get_raw(in.port, in.pin));
}
[get_raw] PB3 with pull up = 1
[get_raw] PB3 with pull down = 0
int gpio_port_get_raw(const struct device *port, gpio_port_value_t *value);

Reads all 32 lines of the bank at once; each bit of value corresponds to one pin.

/* gpio_port_get_raw: all 32 pins of the bank at once */
static void demo_port_get_raw(void)
{
gpio_port_value_t v;
int ret;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
k_msleep(1);
ret = gpio_port_get_raw(gpiob, &v);
printk("[port_get] gpio_port_get_raw(PB) = %s, PB2 %d, PB3 %d\n", err(ret),
!!(v & BIT(out.pin)), !!(v & BIT(in.pin)));
}
[port_get] gpio_port_get_raw(PB) = 0, PB2 0, PB3 1
int gpio_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins);

Drives every pin in the mask high, in a single register write.

/* gpio_port_set_bits_raw: drive the pins of the mask high */
static void demo_port_set_bits_raw(void)
{
gpio_port_value_t v;
int ret;
gpio_pin_configure_dt(&out, GPIO_OUTPUT_INACTIVE);
ret = gpio_port_set_bits_raw(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[set_bits] gpio_port_set_bits_raw(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
[set_bits] gpio_port_set_bits_raw(PB2) = 0, PB2 1
int gpio_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins);

Drives every pin in the mask low.

/* gpio_port_clear_bits_raw: drive the pins of the mask low */
static void demo_port_clear_bits_raw(void)
{
gpio_port_value_t v;
int ret = gpio_port_clear_bits_raw(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[clear_bits] gpio_port_clear_bits_raw(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
[clear_bits] gpio_port_clear_bits_raw(PB2) = 0, PB2 0
int gpio_port_toggle_bits(const struct device *port, gpio_port_pins_t pins);

Inverts every pin in the mask.

/* gpio_port_toggle_bits: invert the pins of the mask */
static void demo_port_toggle_bits(void)
{
gpio_port_value_t v;
int ret = gpio_port_toggle_bits(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[toggle_bits] gpio_port_toggle_bits(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
[toggle_bits] gpio_port_toggle_bits(PB2) = 0, PB2 1
int gpio_port_set_masked_raw(const struct device *port, gpio_port_pins_t mask, gpio_port_value_t value);

Sets the pins in the mask to the corresponding bits of value; the other pins are left unchanged.

/* gpio_port_set_masked_raw: set the pins of the mask to the matching bits of value, leave the others */
static void demo_port_set_masked_raw(void)
{
gpio_port_value_t v;
int ret = gpio_port_set_masked_raw(gpiob, BIT(out.pin), 0);
gpio_port_get_raw(gpiob, &v);
printk("[set_masked] gpio_port_set_masked_raw(PB2, 0) = %s, PB2 %d\n", err(ret),
!!(v & BIT(out.pin)));
}
[set_masked] gpio_port_set_masked_raw(PB2, 0) = 0, PB2 0
void gpio_init_callback(struct gpio_callback *callback, gpio_callback_handler_t handler, gpio_port_pins_t pin_mask);

Binds the handler and the pin mask it cares about to a struct gpio_callback. The callback runs in the PLIC interrupt, and the pins argument is the bitmask of all pins pending on this bank this time; do not do anything time-consuming inside the callback. The variables and the callback function shared by the three demos below:

static struct gpio_callback cb;
static volatile int fired;
static volatile uint32_t last_pins;
static void on_edge(const struct device *port, struct gpio_callback *callback, uint32_t pins)
{
fired++;
last_pins = pins;
}
/* gpio_init_callback: bind a handler and the mask of the pins it wants */
static void demo_init_callback(void)
{
gpio_init_callback(&cb, on_edge, BIT(in.pin));
printk("[init_callback] handler for pin mask 0x%x\n", (unsigned int)cb.pin_mask);
}
[init_callback] handler for pin mask 0x8
int gpio_add_callback(const struct device *port, struct gpio_callback *callback);

Registers the callback with the bank.

/* gpio_add_callback: register it with the bank */
static void demo_add_callback(void)
{
int ret = gpio_add_callback(in.port, &cb);
printk("[add_callback] gpio_add_callback = %s\n", err(ret));
}
[add_callback] gpio_add_callback = 0
int gpio_pin_interrupt_configure_dt(const struct gpio_dt_spec *spec, gpio_flags_t flags);
flagsDriver behaviour
GPIO_INT_EDGE_RISING, GPIO_INT_EDGE_FALLING, GPIO_INT_EDGE_BOTHEdge triggered
GPIO_INT_LEVEL_HIGH, GPIO_INT_LEVEL_LOWLevel triggered
GPIO_INT_DISABLEDisables the interrupt of this pin; if the pin is still on the interrupt function, restores the multiplexing function to the value it had before the interrupt was enabled

Demo: PB3 is pulled down first and the rising edge is armed; changing it to pull-up creates a rising edge and the callback fires 1 time; changing it back to pull-down is a falling edge, not the armed rising edge, so the callback count is 0.

/* gpio_pin_interrupt_configure_dt: arm the pin. The pull resistor is part of the pin configuration,
* so after every gpio_pin_configure the interrupt is armed again.
*/
static void demo_pin_interrupt_configure_dt(void)
{
int ret;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
k_msleep(1);
ret = gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
printk("[interrupt] arm EDGE_RISING = %s\n", err(ret));
fired = 0;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
k_msleep(20);
printk("[interrupt] pull up after arming rising : %d callback(s), pins 0x%x\n", fired, last_pins);
fired = 0;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
k_msleep(20);
printk("[interrupt] pull down after arming rising : %d callback(s)\n", fired);
ret = gpio_pin_interrupt_configure_dt(&in, GPIO_INT_DISABLE);
printk("[interrupt] GPIO_INT_DISABLE = %s\n", err(ret));
}
[interrupt] arm EDGE_RISING = 0
[interrupt] pull up after arming rising : 1 callback(s), pins 0x8
[interrupt] pull down after arming rising : 0 callback(s)
[interrupt] GPIO_INT_DISABLE = 0

pins 0x8 is PB3 (bit 3).

int gpio_get_pending_int(const struct device *dev);

Reads the interrupt status register of this bank, one bit per pin. The interrupt handler clears the bits it has handled, so it reads 0 when there is no pending interrupt.

/* gpio_get_pending_int: the interrupt status register of the bank, one bit per pin */
static void demo_get_pending_int(void)
{
printk("[pending] gpio_get_pending_int(PB) = 0x%x\n", gpio_get_pending_int(gpiob));
}
[pending] gpio_get_pending_int(PB) = 0x0
int gpio_remove_callback(const struct device *port, struct gpio_callback *callback);

Takes the callback off the bank.

/* gpio_remove_callback: take the handler out again */
static void demo_remove_callback(void)
{
int ret = gpio_remove_callback(in.port, &cb);
printk("[remove_callback] gpio_remove_callback = %s\n", err(ret));
}
[remove_callback] gpio_remove_callback = 0

All the examples and logs above come from this single program. Create a directory, put the files below in it, and it builds like any other Zephyr application.

gpio_api/
├── CMakeLists.txt
├── prj.conf
├── app.overlay
└── src/
└── main.c
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(gpio_api)
target_sources(app PRIVATE src/main.c)
CONFIG_GPIO=y
CONFIG_PRINTK=y
CONFIG_SHELL=n
CONFIG_DEVMEM_SHELL=n
CONFIG_LOG_PRINTK=n
CONFIG_MAIN_STACK_SIZE=4096

CONFIG_MAIN_STACK_SIZE=4096 cannot be left out: the default main thread stack is only 1024 bytes, printk will use it up, and the program will stop with an Instruction Access fault while the display is being initialized.

/* PB2 and PB3 are free pins of the board (the UART1 flow control signals, not used) */
/ {
zephyr,user {
out-gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
in-gpios = <&gpiob 3 GPIO_ACTIVE_HIGH>;
};
};
/*
* Calls the functions of <zephyr/drivers/gpio.h> that the sunxi GPIO driver
* implements, one function per call, and prints what each call returns.
*/
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
/* PD5, the LED of the board (alias led0 in yuzukineko.dts) */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
/* PB2 and PB3, from the zephyr,user node of app.overlay */
static const struct gpio_dt_spec out = GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), out_gpios);
static const struct gpio_dt_spec in = GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), in_gpios);
static const struct device *const gpiob = DEVICE_DT_GET(DT_NODELABEL(gpiob));
static const char *err(int ret)
{
switch (ret) {
case 0: return "0";
case -ENOTSUP: return "-ENOTSUP";
case -EINVAL: return "-EINVAL";
case -EIO: return "-EIO";
default: return "?";
}
}
/* gpio_pin_configure_dt: direction, initial level and pull resistor */
static void demo_pin_configure_dt(void)
{
int ret;
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
printk("[configure] PD5 output, low = %s\n", err(ret));
ret = gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
printk("[configure] PB3 input with pull up = %s\n", err(ret));
ret = gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
printk("[configure] PB3 input with pull down = %s\n", err(ret));
ret = gpio_pin_configure_dt(&out, GPIO_OUTPUT | GPIO_OPEN_DRAIN);
printk("[configure] PB2 open drain output = %s\n", err(ret));
ret = gpio_pin_configure_dt(&out, GPIO_OUTPUT | GPIO_OPEN_SOURCE);
printk("[configure] PB2 open source output = %s\n", err(ret));
}
/* gpio_pin_set_dt: the logical level of an output */
static void demo_pin_set_dt(void)
{
int ret;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
ret = gpio_pin_set_dt(&led, 1);
printk("[set] gpio_pin_set_dt(PD5, 1) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
ret = gpio_pin_set_dt(&led, 0);
printk("[set] gpio_pin_set_dt(PD5, 0) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
}
/* gpio_pin_toggle_dt: invert an output; six calls make the LED blink three times */
static void demo_pin_toggle_dt(void)
{
int ret = gpio_pin_toggle_dt(&led);
printk("[toggle] gpio_pin_toggle_dt(PD5) = %s, register bit %d\n", err(ret),
gpio_pin_get_raw(led.port, led.pin));
for (int i = 0; i < 5; i++) {
k_msleep(100);
gpio_pin_toggle_dt(&led);
}
printk("[toggle] blinked the LED 3 times\n");
}
/* gpio_pin_get_dt: the logical level of a pin. For an output pin it is the level that was written. */
static void demo_pin_get_dt(void)
{
int level;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
level = gpio_pin_get_dt(&led);
printk("[get] PD5 written active, gpio_pin_get_dt = %d\n", level);
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
level = gpio_pin_get_dt(&led);
printk("[get] PD5 written inactive, gpio_pin_get_dt = %d\n", level);
}
/* gpio_pin_get_raw: the level of the pin, not looking at GPIO_ACTIVE_LOW. PB3 is not connected,
* so the pull resistor decides.
*/
static void demo_pin_get_raw(void)
{
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
k_msleep(1);
printk("[get_raw] PB3 with pull up = %d\n", gpio_pin_get_raw(in.port, in.pin));
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
k_msleep(1);
printk("[get_raw] PB3 with pull down = %d\n", gpio_pin_get_raw(in.port, in.pin));
}
/* gpio_port_get_raw: all 32 pins of the bank at once */
static void demo_port_get_raw(void)
{
gpio_port_value_t v;
int ret;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
k_msleep(1);
ret = gpio_port_get_raw(gpiob, &v);
printk("[port_get] gpio_port_get_raw(PB) = %s, PB2 %d, PB3 %d\n", err(ret),
!!(v & BIT(out.pin)), !!(v & BIT(in.pin)));
}
/* gpio_port_set_bits_raw: drive the pins of the mask high */
static void demo_port_set_bits_raw(void)
{
gpio_port_value_t v;
int ret;
gpio_pin_configure_dt(&out, GPIO_OUTPUT_INACTIVE);
ret = gpio_port_set_bits_raw(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[set_bits] gpio_port_set_bits_raw(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
/* gpio_port_clear_bits_raw: drive the pins of the mask low */
static void demo_port_clear_bits_raw(void)
{
gpio_port_value_t v;
int ret = gpio_port_clear_bits_raw(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[clear_bits] gpio_port_clear_bits_raw(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
/* gpio_port_toggle_bits: invert the pins of the mask */
static void demo_port_toggle_bits(void)
{
gpio_port_value_t v;
int ret = gpio_port_toggle_bits(gpiob, BIT(out.pin));
gpio_port_get_raw(gpiob, &v);
printk("[toggle_bits] gpio_port_toggle_bits(PB2) = %s, PB2 %d\n", err(ret), !!(v & BIT(out.pin)));
}
/* gpio_port_set_masked_raw: set the pins of the mask to the matching bits of value, leave the others */
static void demo_port_set_masked_raw(void)
{
gpio_port_value_t v;
int ret = gpio_port_set_masked_raw(gpiob, BIT(out.pin), 0);
gpio_port_get_raw(gpiob, &v);
printk("[set_masked] gpio_port_set_masked_raw(PB2, 0) = %s, PB2 %d\n", err(ret),
!!(v & BIT(out.pin)));
}
/* interrupts. The edge is made by switching the pull resistor of the unconnected pin PB3. */
static struct gpio_callback cb;
static volatile int fired;
static volatile uint32_t last_pins;
static void on_edge(const struct device *port, struct gpio_callback *callback, uint32_t pins)
{
fired++;
last_pins = pins;
}
/* gpio_init_callback: bind a handler and the mask of the pins it wants */
static void demo_init_callback(void)
{
gpio_init_callback(&cb, on_edge, BIT(in.pin));
printk("[init_callback] handler for pin mask 0x%x\n", (unsigned int)cb.pin_mask);
}
/* gpio_add_callback: register it with the bank */
static void demo_add_callback(void)
{
int ret = gpio_add_callback(in.port, &cb);
printk("[add_callback] gpio_add_callback = %s\n", err(ret));
}
/* gpio_pin_interrupt_configure_dt: arm the pin. The pull resistor is part of the pin configuration,
* so after every gpio_pin_configure the interrupt is armed again.
*/
static void demo_pin_interrupt_configure_dt(void)
{
int ret;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
k_msleep(1);
ret = gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
printk("[interrupt] arm EDGE_RISING = %s\n", err(ret));
fired = 0;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_UP);
gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
k_msleep(20);
printk("[interrupt] pull up after arming rising : %d callback(s), pins 0x%x\n", fired, last_pins);
fired = 0;
gpio_pin_configure_dt(&in, GPIO_INPUT | GPIO_PULL_DOWN);
gpio_pin_interrupt_configure_dt(&in, GPIO_INT_EDGE_RISING);
k_msleep(20);
printk("[interrupt] pull down after arming rising : %d callback(s)\n", fired);
ret = gpio_pin_interrupt_configure_dt(&in, GPIO_INT_DISABLE);
printk("[interrupt] GPIO_INT_DISABLE = %s\n", err(ret));
}
/* gpio_get_pending_int: the interrupt status register of the bank, one bit per pin */
static void demo_get_pending_int(void)
{
printk("[pending] gpio_get_pending_int(PB) = 0x%x\n", gpio_get_pending_int(gpiob));
}
/* gpio_remove_callback: take the handler out again */
static void demo_remove_callback(void)
{
int ret = gpio_remove_callback(in.port, &cb);
printk("[remove_callback] gpio_remove_callback = %s\n", err(ret));
}
int main(void)
{
if (!gpio_is_ready_dt(&led) || !gpio_is_ready_dt(&out) || !gpio_is_ready_dt(&in)) {
printk("gpio not ready\n");
return 0;
}
demo_pin_configure_dt();
demo_pin_set_dt();
demo_pin_toggle_dt();
demo_pin_get_dt();
demo_pin_get_raw();
demo_port_get_raw();
demo_port_set_bits_raw();
demo_port_clear_bits_raw();
demo_port_toggle_bits();
demo_port_set_masked_raw();
demo_init_callback();
demo_add_callback();
demo_pin_interrupt_configure_dt();
demo_get_pending_int();
demo_remove_callback();
printk("gpio_api done\n");
return 0;
}
Terminal window
. ~/f101-zephyr/env.sh
cd ~/f101-zephyr
west build -b yuzukineko -d build/gpio-api gpio_api

For how to download and run it, see Build and run. The complete output on the UART1 console:

[configure] PD5 output, low = 0
[configure] PB3 input with pull up = 0
[configure] PB3 input with pull down = 0
[configure] PB2 open drain output = -ENOTSUP
[configure] PB2 open source output = -ENOTSUP
[set] gpio_pin_set_dt(PD5, 1) = 0, register bit 1
[set] gpio_pin_set_dt(PD5, 0) = 0, register bit 0
[toggle] gpio_pin_toggle_dt(PD5) = 0, register bit 1
[toggle] blinked the LED 3 times
[get] PD5 written active, gpio_pin_get_dt = 1
[get] PD5 written inactive, gpio_pin_get_dt = 0
[get_raw] PB3 with pull up = 1
[get_raw] PB3 with pull down = 0
[port_get] gpio_port_get_raw(PB) = 0, PB2 0, PB3 1
[set_bits] gpio_port_set_bits_raw(PB2) = 0, PB2 1
[clear_bits] gpio_port_clear_bits_raw(PB2) = 0, PB2 0
[toggle_bits] gpio_port_toggle_bits(PB2) = 0, PB2 1
[set_masked] gpio_port_set_masked_raw(PB2, 0) = 0, PB2 0
[init_callback] handler for pin mask 0x8
[add_callback] gpio_add_callback = 0
[interrupt] arm EDGE_RISING = 0
[interrupt] pull up after arming rising : 1 callback(s), pins 0x8
[interrupt] pull down after arming rising : 0 callback(s)
[interrupt] GPIO_INT_DISABLE = 0
[pending] gpio_get_pending_int(PB) = 0x0
[remove_callback] gpio_remove_callback = 0
gpio_api done