Skip to content

Instantly share code, notes, and snippets.

@hidsh
Created August 16, 2025 06:57
Show Gist options
  • Save hidsh/bca78682210d877950dcb231f09bf962 to your computer and use it in GitHub Desktop.
Save hidsh/bca78682210d877950dcb231f09bf962 to your computer and use it in GitHub Desktop.
zephyr button w/ "dynamic" pinctrl example for xiao_ble (seeed xiao nrf52840)
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(button)
target_sources(app PRIVATE src/main.c src/remap.c)
# zephyr/samples/basic/button/boards/custom.conf
#
# "static" pinctrl to use external-uart breakout (AE-FT2232 / FTDI FT2232D) as /dev/ttyUSB0
# this file is to switch USB-CDC-ACM (/dev/ttyACM0) --> external uart (/dev/ttyUSB0)
# Disable USB CDC ACM
CONFIG_USB_DEVICE_STACK=n
CONFIG_USB_DEVICE_STACK_NEXT=n
CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n
# Enable UART console
CONFIG_UART_CONSOLE=y
CONFIG_CONSOLE=y
# based on zephyr/samples/boards/nordic/dynamic_pinctrl/Kconfig
#
# Copyright (c) 2021 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
mainmenu "Dynamic pin control sample"
config REMAP_INIT_PRIORITY
int "Remap routine initialization priority"
default 50
help
Initialization priority of the remap routine within the PRE_KERNEL1 level.
This priority must be greater than GPIO_INIT_PRIORITY and lower than
UART_INIT_PRIORITY.
source "Kconfig.zephyr"
CONFIG_GPIO=y
# ---------------------------------------------
# dynamic-pinctrl
# ---------------------------------------------
CONFIG_PINCTRL=y
CONFIG_PINCTRL_DYNAMIC=y
# configure serial and console to come after remap hook
CONFIG_SERIAL_INIT_PRIORITY=60
CONFIG_CONSOLE_INIT_PRIORITY=70
/*
* Copyright (c) 2016 Open-RnD Sp. z o.o.
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
* NOTE: If you are looking into an implementation of button events with
* debouncing, check out `input` subsystem and `samples/subsys/input/input_dump`
* example instead.
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <inttypes.h>
#define SLEEP_TIME_MS 1
/*
* Get button configuration from the devicetree sw0 alias. This is mandatory.
*/
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
{0});
#define SW1_NODE DT_ALIAS(sw1)
#if !DT_NODE_HAS_STATUS_OKAY(SW1_NODE)
#error "Unsupported board: sw1 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button1 = GPIO_DT_SPEC_GET_BY_IDX_OR(SW1_NODE, gpios,
0, // idx
{0});
static struct gpio_callback button_cb_data;
static struct gpio_callback button1_cb_data;
/*
* The led0 devicetree alias is optional. If present, we'll use it
* to turn on the LED whenever the button is pressed.
*/
static struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios,
{0});
void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
printk("Button pressed at %" PRIu32 " from pin %d\n", k_cycle_get_32(), pins);
}
int main(void)
{
int ret;
if (!gpio_is_ready_dt(&button)) {
printk("Error: button device %s is not ready\n",
button.port->name);
return 0;
}
if (!gpio_is_ready_dt(&button1)) {
printk("Error: button1 device %s is not ready\n",
button1.port->name);
return 0;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button.port->name, button.pin);
return 0;
}
ret = gpio_pin_configure_dt(&button1, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button1.port->name, button1.pin);
return 0;
}
ret = gpio_pin_interrupt_configure_dt(&button,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button.port->name, button.pin);
return 0;
}
ret = gpio_pin_interrupt_configure_dt(&button1,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button1.port->name, button1.pin);
return 0;
}
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
printk("Set up button at %s pin %d\n", button.port->name, button.pin);
gpio_init_callback(&button1_cb_data, button_pressed, BIT(button1.pin));
gpio_add_callback(button1.port, &button1_cb_data);
printk("Set up button at %s pin %d\n", button1.port->name, button1.pin);
if (led.port && !gpio_is_ready_dt(&led)) {
printk("Error %d: LED device %s is not ready; ignoring it\n",
ret, led.port->name);
led.port = NULL;
}
if (led.port) {
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);
if (ret != 0) {
printk("Error %d: failed to configure LED device %s pin %d\n",
ret, led.port->name, led.pin);
led.port = NULL;
} else {
printk("Set up LED at %s pin %d\n", led.port->name, led.pin);
}
}
printk("Press the button\n");
if (led.port) {
while (1) {
/* If we have an LED, match its state to the button's. */
int val = gpio_pin_get_dt(&button);
if (val >= 0) {
gpio_pin_set_dt(&led, val);
}
k_msleep(SLEEP_TIME_MS);
}
}
return 0;
}
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pinctrl.h>
/* make sure devices and remap hook are initialized in the correct order */
BUILD_ASSERT((CONFIG_GPIO_INIT_PRIORITY < CONFIG_REMAP_INIT_PRIORITY) &&
(CONFIG_REMAP_INIT_PRIORITY < CONFIG_SERIAL_INIT_PRIORITY),
"Device driver priorities are not set correctly");
PINCTRL_DT_DEV_CONFIG_DECLARE(DT_NODELABEL(uart0));
/* UART0 alternative configurations (default and sleep states) */
PINCTRL_DT_STATE_PINS_DEFINE(DT_PATH(zephyr_user), uart0_alt_default);
#ifdef CONFIG_PM_DEVICE
PINCTRL_DT_STATE_PINS_DEFINE(DT_PATH(zephyr_user), uart0_alt_sleep);
#endif
static const struct pinctrl_state uart0_alt[] = {
PINCTRL_DT_STATE_INIT(uart0_alt_default, PINCTRL_STATE_DEFAULT),
#ifdef CONFIG_PM_DEVICE
PINCTRL_DT_STATE_INIT(uart0_alt_sleep, PINCTRL_STATE_SLEEP),
#endif
};
#include <zephyr/kernel.h> // printk
static int remap_pins(void)
{
int ret;
printk("-- enter: remap_pins()\n");
const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw1),
gpios, {0});
if (!gpio_is_ready_dt(&button)) {
return -ENODEV;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) {
return ret;
}
/* remap UART0 pins if button is pressed */
if (gpio_pin_get_dt(&button)) {
struct pinctrl_dev_config *uart0_config =
PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL(uart0));
return pinctrl_update_states(uart0_config, uart0_alt,
ARRAY_SIZE(uart0_alt));
}
printk("-- exit: remap_pins()\n");
return 0;
}
SYS_INIT(remap_pins, PRE_KERNEL_1, CONFIG_REMAP_INIT_PRIORITY);
// zephyr/samples/basic/button/boards/xiao_ble.overlay
//
// adopt from zephyr/boards/nordic/nrf52dk/nrf52dk_nrf52810.dts
/ {
buttons {
compatible = "gpio-keys";
button0: button_0 {
label = "Push button switch 0";
gpios = <&gpio0 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; // P0.05 XIAO #6 D5 --> push switch
// zephyr,code = <INPUT_KEY_0>;
};
sw1: sw1 {
gpios = <&gpio0 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; // sw1: P0.04 (XIAO #5 D4)
label = "Select ttyUSB0/1";
// zephyr,code = <INPUT_KEY_1>; // optional, for input subsystem
};
};
aliases {
sw0 = &button0;
sw1 = &sw1;
uart-0 = &uart0; // Add this alias for the console
};
chosen {
zephyr,console = &uart0; // Explicitly set UART0 as console
};
};
// "static" pinctrl to use external-uart breakout (AE-FT2232 / FTDI FT2232D) as /dev/ttyUSB0
&pinctrl {
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 15)>; // P1.15 XIAO #11 D10 TX --> AE-FT2232 #39 RXD (cross)
};
group2 {
psels = <NRF_PSEL(UART_RX, 1, 14)>; // P1.14 XIAO #10 D9 RX --> AE-FT2232 #40 TXD (cross)
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 15)>,
<NRF_PSEL(UART_RX, 1, 14)>;
low-power-enable;
};
};
};
// Enable UART0
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
};
// ---------------------------------------------
// for dynamic-pinctl (to swap pins for uart0)
// ---------------------------------------------
#include <zephyr/dt-bindings/input/input-event-codes.h> // `zephyr,code`
/ {
zephyr,user {
uart0_alt_default = <&uart0_alt_default>;
uart0_alt_sleep = <&uart0_alt_sleep>;
};
};
// "dynamic" pinctrl to use external-uart breakout (AE-FT2232 / FTDI FT2232D) as /dev/ttyUSB1
// when pressed sw1
&pinctrl {
/* Alternative pin configuration for UART0 */
uart0_alt_default: uart0_alt_default {
group1 {
// psels = <NRF_PSEL(UART_TX, 0, 26)>; // TXD: P0.26 (XIAO led0:red)
psels = <NRF_PSEL(UART_TX, 1, 13)>; // TXD: P1.13 (XIAO #9 D8)
};
group2 {
psels = <NRF_PSEL(UART_RX, 1, 12)>; // RXD: P1.12 (XIAO #8 D7)
bias-pull-up;
};
};
uart0_alt_sleep: uart0_alt_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 13)>, // TXD: P1.13 (XIAO #9 D8)
<NRF_PSEL(UART_RX, 1, 12)>; // RXD: P1.12 (XIAO #8 D7)
low-power-enable;
};
};
};
/ {
// sw0: sw0 {
// compatible = "gpio-key";
// gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; // sw0: P0.05 (XIAO #6 D5)
// label = "Push Switch";
// zephyr,code = <INPUT_KEY_0>; // optional, for input subsystem
// };
// sw1: sw1 {
// compatible = "gpio-key";
// gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; // sw1: P0.04 (XIAO #5 D4)
// label = "Select ttyUSB0/1";
// zephyr,code = <INPUT_KEY_1>; // optional, for input subsystem
// };
// aliases {
// // sw0 = &sw0;
// sw1 = &sw1;
// };
};
@hidsh
Copy link
Author

hidsh commented Aug 16, 2025

build

❯❯ zephyr/samples/basic/button
❯❯ west build -p -b xiao_ble . -- -DOVERLAY_CONFIG=custom.conf
-- west build: making build dir /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build pristine
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
-- Application: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button
-- CMake version: 4.0.1-dirty
-- Found Python3: /home/g/mise-work/zephyr-latest/.venv/bin/python (found suitable version "3.12.7", minimum required is "3.10") found components: Interpreter
-- Cache files will be written to: /home/g/.cache/zephyr
-- Zephyr version: 4.2.99 (/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr)
-- Found west (found suitable version "1.4.0", minimum required is "0.14.0")
-- Board: xiao_ble, qualifiers: nrf52840
-- ZEPHYR_TOOLCHAIN_VARIANT not set, trying to locate Zephyr SDK
-- Found host-tools: zephyr 0.16.3 (/home/g/zephyr-sdk-0.16.3)
-- Found toolchain: zephyr 0.16.3 (/home/g/zephyr-sdk-0.16.3)
-- Found Dtc: /home/g/zephyr-sdk-0.16.3/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6")
-- Found BOARD.dts: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/boards/seeed/xiao_ble/xiao_ble.dts
-- Found devicetree overlay: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/boards/xiao_ble.overlay
-- Generated zephyr.dts: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/zephyr.dts
-- Generated pickled edt: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/edt.pickle
-- Generated devicetree_generated.h: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/include/generated/zephyr/devicetree_generated.h
Parsing /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/Kconfig
Loaded configuration '/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/boards/seeed/xiao_ble/xiao_ble_defconfig'
Merged configuration '/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/prj.conf'
Merged configuration '/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/custom.conf'
Configuration saved to '/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/.config'
Kconfig header saved to '/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/include/generated/zephyr/autoconf.h'
-- Found GnuLd: /home/g/zephyr-sdk-0.16.3/arm-zephyr-eabi/arm-zephyr-eabi/bin/ld.bfd (found version "2.38")
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/g/zephyr-sdk-0.16.3/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc
-- Found gen_kobject_list: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/scripts/build/gen_kobject_list.py
-- Configuring done (5.3s)
-- Generating done (0.1s)
-- Build files have been written to: /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build
-- west build: building application
[1/146] Preparing syscall dependency handling

[2/146] Generating include/generated/zephyr/version.h
-- Zephyr version: 4.2.99 (/home/g/mise-work/zephyr-latest/zephyr-proj/zephyr), build: v4.2.0-1442-g78ad454dc986
[146/146] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       26892 B       788 KB      3.33%
             RAM:        4544 B       256 KB      1.73%
        IDT_LIST:          0 GB        32 KB      0.00%
Generating files from /home/g/mise-work/zephyr-latest/zephyr-proj/zephyr/samples/basic/button/build/zephyr/zephyr.elf for board: xiao_ble
Converted to uf2, output size: 54272, start address: 0x27000
Wrote 54272 bytes to zephyr.uf2

run

❯❯ tio /dev/ttyUSB1
[15:26:47.032] tio c43d2f6
[15:26:47.032] Press ctrl-t q to quit
[15:26:47.037] Connected to /dev/ttyUSB1
Button pressed at 1041151 from pin 32
Button pressed at 1057918 from pin 32
Button pressed at 1061214 from pin 32
Button pressed at 1069968 from pin 32
Button pressed at 1073216 from pin 32
Button pressed at 1133709 from pin 16
Button pressed at 1137778 from pin 16
Button pressed at 1143252 from pin 16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment