Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ on:
pull_request:
branches:
- main
- dev
push:
branches:
- main
- dev
create:
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -189,15 +192,16 @@ jobs:
fail-fast: false
matrix:
boards: [
{boardname: "nrf52840dongle/nrf52840", fileformat: "hex", filename: "SlimeNRF_Nordic_eByte_Dongle_Receiver"},
{boardname: "holyiot_21017/nrf52840", fileformat: "hex", filename: "SlimeNRF_Holyiot_Dongle_Receiver"},
{boardname: "promicro_uf2/nrf52840", fileformat: "uf2", filename: "SlimeNRF_ProMicro_Receiver"},
{boardname: "xiao_ble/nrf52840", fileformat: "uf2", filename: "SlimeNRF_XIAO_Receiver"},
{boardname: "etee_dongle_uf2/nrf52840", fileformat: "uf2", filename: "SlimeNRF_etee_Receiver"},
{boardname: "nrf52840dk/nrf52840", fileformat: "hex", filename: "SlimeNRF_nRF52840dk_Receiver"},
{boardname: "butterfly_p1_uf2/nrf52833", fileformat: "uf2", filename: "SlimeNRF_Butterfly_P1_Receiver"},
{boardname: "butterfly_p2_uf2/nrf52833", fileformat: "uf2", filename: "SlimeNRF_Butterfly_P2_Receiver"},
{boardname: "butterfly_p3/nrf52820", fileformat: "hex", filename: "SlimeNRF_Butterfly_P3_Receiver"},
#{boardname: "nrf52840dongle/nrf52840", fileformat: "hex", filename: "SlimeNRF_Nordic_eByte_Dongle_Receiver"},
#{boardname: "holyiot_21017/nrf52840", fileformat: "hex", filename: "SlimeNRF_Holyiot_Dongle_Receiver"},
#{boardname: "promicro_uf2/nrf52840", fileformat: "uf2", filename: "SlimeNRF_ProMicro_Receiver"},
#{boardname: "xiao_ble/nrf52840", fileformat: "uf2", filename: "SlimeNRF_XIAO_Receiver"},
#{boardname: "etee_dongle_uf2/nrf52840", fileformat: "uf2", filename: "SlimeNRF_etee_Receiver"},
#{boardname: "nrf52840dk/nrf52840", fileformat: "hex", filename: "SlimeNRF_nRF52840dk_Receiver"},
#{boardname: "butterfly_p1_uf2/nrf52833", fileformat: "uf2", filename: "SlimeNRF_Butterfly_P1_Receiver"},
#{boardname: "butterfly_p2_uf2/nrf52833", fileformat: "uf2", filename: "SlimeNRF_Butterfly_P2_Receiver"},
#{boardname: "butterfly_p3/nrf52820", fileformat: "hex", filename: "SlimeNRF_Butterfly_P3_Receiver"},
{boardname: "zannendongle_uf2/nrf52840", fileformat: "uf2", filename: "SlimeNRF_ZannenDongle_Receiver"},
]
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 4 additions & 0 deletions boards/zannen/zannendongle_uf2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0

zephyr_library()
zephyr_library_sources(board.c)
5 changes: 5 additions & 0 deletions boards/zannen/zannendongle_uf2/Kconfig.zannendongle_uf2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (c) 2025 SlimeVR Contributors
# SPDX-License-Identifier: Apache-2.0

config BOARD_ZANNENDONGLE_UF2
select SOC_NRF52840_QIAA
58 changes: 58 additions & 0 deletions boards/zannen/zannendongle_uf2/board.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 SlimeVR Contributors.
*
* SPDX-License-Identifier: Apache-2.0
*
* Board initialization for ZannenDongle with RFX2401C FEM.
*
* The nRF52840's internal REG0 (LDO/DCDC stage for 1.8V radio supply)
* must be configured correctly for the RFX2401C FEM to operate.
* We set REG0 to 3.0V (VOUT_3V0) on first boot to ensure proper
* FEM supply voltage. This is written to UICR so it persists.
*/

#include <zephyr/init.h>
#include <hal/nrf_power.h>

static int board_zannendongle_uf2_init(void)
{

/*
* The RFX2401C FEM is typically powered from the nRF52840's
* VDD_nRF (REG0 output) or an external LDO at 3.0V-3.3V.
*
* If the main regulator is in HIGH mode and REGOUT0 is still
* at its POR default (1.8V), we reprogram it to 3.0V and
* trigger a system reset so the change takes effect.
*
* This is a one-time initialization; after the reset,
* REGOUT0 will already be at 3.0V and this code becomes a no-op.
*/
if ((nrf_power_mainregstatus_get(NRF_POWER) ==
NRF_POWER_MAINREGSTATUS_HIGH) &&
((NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) ==
(UICR_REGOUT0_VOUT_DEFAULT << UICR_REGOUT0_VOUT_Pos))) {

NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
;
}

NRF_UICR->REGOUT0 =
(NRF_UICR->REGOUT0 & ~((uint32_t)UICR_REGOUT0_VOUT_Msk)) |
(UICR_REGOUT0_VOUT_3V0 << UICR_REGOUT0_VOUT_Pos);

NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
;
}

/* A reset is required for the REGOUT0 change to take effect */
NVIC_SystemReset();
}

return 0;
}

SYS_INIT(board_zannendongle_uf2_init, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
9 changes: 9 additions & 0 deletions boards/zannen/zannendongle_uf2/board.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2025 SlimeVR Contributors
# SPDX-License-Identifier: Apache-2.0

board_runner_args(jlink "--device=nrf52840_xxaa" "--speed=4000")
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")

include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
8 changes: 8 additions & 0 deletions boards/zannen/zannendongle_uf2/board.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2025 SlimeVR Contributors
# SPDX-License-Identifier: Apache-2.0

board:
name: zannendongle_uf2
vendor: zannen
socs:
- name: nrf52840
126 changes: 126 additions & 0 deletions boards/zannen/zannendongle_uf2/zannendongle_uf2.dts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) 2025 SlimeVR Contributors
// SPDX-License-Identifier: Apache-2.0

/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>

// PWM pin assignment for RGB status LED
// Red: P0.12, Green: P0.08, Blue: P1.09
&pinctrl {
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 12)>,
<NRF_PSEL(PWM_OUT1, 0, 8)>,
<NRF_PSEL(PWM_OUT2, 1, 9)>;
nordic,drive-mode = <NRF_DRIVE_D0S1>;
nordic,invert;
};
};

pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 12)>,
<NRF_PSEL(PWM_OUT1, 0, 8)>,
<NRF_PSEL(PWM_OUT2, 1, 9)>;
low-power-enable;
};
};
};

/ {
model = "ZannenDongle_uf2";
compatible = "zannendongle_uf2";

chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,console = &cdc_acm_uart0;
zephyr,shell-uart = &cdc_acm_uart0;
};

// RFX2401C FEM: simple 2-control-pin front-end module
// Uses nRF21540 GPIO-only driver via MPSL for TX/RX switching.
// The nRF21540 driver is MPSL-native and correctly handles ESB
// radio events to toggle TXEN/RXEN automatically.
// TXEN = P1.00, RXEN = P0.24
nrf_radio_fem: rfx2401c_fem {
compatible = "nordic,nrf21540-fem";
tx-en-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; // RFX2401C TXEN (P1.00)
rx-en-gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>; // RFX2401C RXEN (P0.24)
};

// PWM-driven RGB LED
pwmleds {
compatible = "pwm-leds";
pwm_led0: pwm_led_0 {
pwms = <&pwm0 0 PWM_MSEC(1) PWM_POLARITY_INVERTED>;
};
pwm_led1: pwm_led_1 {
pwms = <&pwm0 1 PWM_MSEC(1) PWM_POLARITY_INVERTED>;
};
pwm_led2: pwm_led_2 {
pwms = <&pwm0 2 PWM_MSEC(1) PWM_POLARITY_INVERTED>;
};
};

aliases {
pwm-led0 = &pwm_led0;
pwm-led1 = &pwm_led1;
pwm-led2 = &pwm_led2;
};

// Simple GPIO LED (fallback indicator on Red channel)
zephyr,user {
led-gpios = <&gpio0 12 (GPIO_ACTIVE_LOW | GPIO_OPEN_DRAIN)>;
};
};

// nRF52840 internal main regulator (REG1) — use LDO mode by default.
// DCDC mode requires external LC components; use LDO for guaranteed stability.
// If the PCB includes the DCDC inductor/capacitor, change to NRF5X_REG_MODE_DCDC.
&reg1 {
regulator-initial-mode = <NRF5X_REG_MODE_LDO>;
};

&gpio0 {
status = "okay";
};

&gpio1 {
status = "okay";
};

&gpiote {
status = "okay";
};

&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};

// USB peripheral with CDC ACM UART console
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};

// CryptoCell not needed for receiver firmware
&cryptocell {
status = "disabled";
};

// NFC not used, disable to free GPIO pins
&nfct {
status = "disabled";
};

// Link the FEM to the radio peripheral
&radio {
fem = <&nrf_radio_fem>;
};
14 changes: 14 additions & 0 deletions boards/zannen/zannendongle_uf2/zannendongle_uf2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright (c) 2025 SlimeVR Contributors
# SPDX-License-Identifier: Apache-2.0

identifier: zannendongle_uf2
name: ZannenDongle_uf2
vendor: zannen
type: mcu
arch: arm
ram: 256
flash: 1024
toolchain:
- zephyr
- gnuarmemb
- xtools
15 changes: 15 additions & 0 deletions boards/zannen/zannendongle_uf2/zannendongle_uf2_defconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2025 SlimeVR Contributors
# SPDX-License-Identifier: Apache-2.0

CONFIG_ARM_MPU=y
CONFIG_HW_STACK_PROTECTION=y

CONFIG_USB_DEVICE_MANUFACTURER="Zannen"
CONFIG_USB_DEVICE_PRODUCT="SlimeNRF Receiver ZannenDongle"

# RGB LED color mode
CONFIG_LED_RGB_COLOR=y

# Generate UF2 file for Adafruit UF2 bootloader flashing
CONFIG_BUILD_OUTPUT_UF2=y
CONFIG_FLASH_LOAD_OFFSET=0x1000
7 changes: 7 additions & 0 deletions boards/zannendongle_uf2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ZannenDongle_uf2 board-specific Kconfig fragment (applied on top of defconfig)

CONFIG_USB_DEVICE_MANUFACTURER="Zannen"
CONFIG_USB_DEVICE_PRODUCT="SlimeNRF Receiver ZannenDongle"

# RGB LED color mode
CONFIG_LED_RGB_COLOR=y
12 changes: 12 additions & 0 deletions pm_static_zannendongle_uf2_nrf52840.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Partition Manager static configuration for ZannenDongle_uf2 (nRF52840)
# Uses Adafruit UF2 bootloader at 0x1000 offset
bootloader:
address: 0x0
end_address: 0x1000
region: flash_primary
size: 0x1000
uf2_bootloader:
address: 0xf4000
end_address: 0x100000
region: flash_primary
size: 0xc000
1 change: 1 addition & 0 deletions sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ tests:
- nrf52840dk/nrf52840
- nrf52840dongle/nrf52840
- xiao_ble/nrf52840
- zannendongle_uf2/nrf52840
sysbuild: true
12 changes: 6 additions & 6 deletions src/connection/esb.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,9 @@ static void esb_deinitialize(void)
esb_initialized = false;
LOG_INF("Deinitializing ESB");
k_msleep(10); // wait for pending transmissions
if (esb_initialized)
{
LOG_INF("ESB denitialize cancelled");
return;
}
// Always call esb_disable() — do NOT re-check esb_initialized.
// The re-check races with esb_pair()'s hot loop which
// calls esb_initialize() during the 10ms sleep window.
esb_disable();
}
esb_initialized = false;
Expand Down Expand Up @@ -492,7 +490,9 @@ void esb_pair(void)

void esb_reset_pair(void)
{
esb_deinitialize(); // make sure esb is off
esb_pairing = false; // Signal current pairing loop to exit
k_msleep(20); // Wait for loop exit + its internal esb_deinitialize()
esb_deinitialize(); // Now safe: no concurrent ESB operations
esb_paired = false;
}

Expand Down
27 changes: 14 additions & 13 deletions src/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,22 @@ static inline void strtolower(char *str) {

static void print_help(void)
{
printk("\nhelp Display this help text\n");

printk("\ninfo Get device information\n");
printk("uptime Get device uptime\n");
printk("list Get paired devices\n");
printk("reboot Soft reset the device\n");
printk("\nadd <address> Manually add a device\n");
printk("remove Remove last device\n");
printk("pair Enter pairing mode\n");
printk("exit Exit pairing mode\n");
printk("clear Clear stored devices\n");
printk(
"\nhelp Display this help text\n"
"\ninfo Get device information\n"
"uptime Get device uptime\n"
"list Get paired devices\n"
"reboot Soft reset the device\n"
"\nadd <address> Manually add a device\n"
"remove Remove last device\n"
"pair Enter pairing mode\n"
"exit Exit pairing mode\n"
"clear Clear stored devices\n"
#if DFU_EXISTS
printk("\ndfu Enter DFU bootloader\n");
"\ndfu Enter DFU bootloader\n"
#endif
printk("\nmeow Meow!\n");
"\nmeow Meow!\n"
);
}

static void console_thread(void)
Expand Down