Skip to content
Draft
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
9 changes: 9 additions & 0 deletions ports/nordic/boards/muzi_base_duo/board.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Álvaro Figueroa
//
// SPDX-License-Identifier: MIT

#include "supervisor/board.h"

// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here.
53 changes: 53 additions & 0 deletions ports/nordic/boards/muzi_base_duo/mpconfigboard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Álvaro Figueroa
//
// SPDX-License-Identifier: MIT

#pragma once

#include "nrfx/hal/nrf_gpio.h"

#define MICROPY_HW_BOARD_NAME "muzi works Base Duo"
#define MICROPY_HW_MCU_NAME "nRF52840"

// Blue LED. Cathode is driven by the MCU, anode sits on 3V3, so this is active low.
#define MICROPY_HW_LED_STATUS (&pin_P1_04)
#define MICROPY_HW_LED_STATUS_INVERTED (1)

// Y1 32.768 kHz crystal on P0.00 / P0.01.
#define BOARD_HAS_32KHZ_XTAL (1)

// User button, active low with a 100k pull-up.
#define CIRCUITPY_BOOT_BUTTON (&pin_P0_10)

#if QSPI_FLASH_FILESYSTEM
#define MICROPY_QSPI_DATA0 NRF_GPIO_PIN_MAP(0, 30)
#define MICROPY_QSPI_DATA1 NRF_GPIO_PIN_MAP(0, 29)
#define MICROPY_QSPI_DATA2 NRF_GPIO_PIN_MAP(0, 28)
#define MICROPY_QSPI_DATA3 NRF_GPIO_PIN_MAP(0, 2)
#define MICROPY_QSPI_SCK NRF_GPIO_PIN_MAP(0, 3)
#define MICROPY_QSPI_CS NRF_GPIO_PIN_MAP(0, 26)
#endif

#define CIRCUITPY_INTERNAL_NVM_SIZE (4096)

#define BOARD_FLASH_SIZE (FLASH_SIZE - 0x4000 - CIRCUITPY_INTERNAL_NVM_SIZE)

// 0: Qwiic / STEMMA QT and the castellated I2C pads.
// 1: the OLED / display header pads.
#define CIRCUITPY_BOARD_I2C (2)
#define CIRCUITPY_BOARD_I2C_PIN { \
{.scl = &pin_P0_06, .sda = &pin_P0_04}, \
{.scl = &pin_P0_25, .sda = &pin_P0_24}, \
}

// The only SPI bus is the module-internal one going to the LR1121.
#define CIRCUITPY_BOARD_SPI (1)
#define CIRCUITPY_BOARD_SPI_PIN { \
{.clock = &pin_P1_13, .mosi = &pin_P1_14, .miso = &pin_P1_15}, \
}

// Castellated UART. Net names on the silkscreen are from the peripheral's point of view.
#define DEFAULT_UART_BUS_TX (&pin_P0_19)
#define DEFAULT_UART_BUS_RX (&pin_P0_20)
11 changes: 11 additions & 0 deletions ports/nordic/boards/muzi_base_duo/mpconfigboard.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
USB_VID = 0x239A
USB_PID = 0x0081
USB_PRODUCT = "Base Duo"
USB_MANUFACTURER = "muzi works"

MCU_CHIP = nrf52840

QSPI_FLASH_FILESYSTEM = 1
# Rev01 ships a W25Q128JV; the Meshtastic BASE variant documents a W25Q32JV.
# List both families and let the JEDEC ID decide at runtime.
EXTERNAL_FLASH_DEVICES = "W25Q128JVxQ, W25Q64JVxQ, W25Q32JVxQ, W25Q32JVxM"
134 changes: 134 additions & 0 deletions ports/nordic/boards/muzi_base_duo/pins.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Álvaro Figueroa
//
// SPDX-License-Identifier: MIT

#include "shared-bindings/board/__init__.h"

// Instance 0 is the Qwiic / STEMMA QT connector, instance 1 is the display header.
CIRCUITPY_BOARD_BUS_SINGLETON(stemma_i2c, i2c, 0)
CIRCUITPY_BOARD_BUS_SINGLETON(display_i2c, i2c, 1)

// The LR1121 hangs off the only SPI bus, inside the nRFLR1121 module.
CIRCUITPY_BOARD_BUS_SINGLETON(lora_spi, spi, 0)

static const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS

// Castellated I/O, bottom edge (JC4-JC9, JC13).
// The silkscreen names in parentheses are the trackball positions muzi uses
// on its own carrier boards.
{ MP_ROM_QSTR(MP_QSTR_IO1), MP_ROM_PTR(&pin_P1_05) },
{ MP_ROM_QSTR(MP_QSTR_BTN_L), MP_ROM_PTR(&pin_P1_05) },
{ MP_ROM_QSTR(MP_QSTR_P1_05), MP_ROM_PTR(&pin_P1_05) },

{ MP_ROM_QSTR(MP_QSTR_IO2), MP_ROM_PTR(&pin_P0_21) },
{ MP_ROM_QSTR(MP_QSTR_BTN_U), MP_ROM_PTR(&pin_P0_21) },
{ MP_ROM_QSTR(MP_QSTR_P0_21), MP_ROM_PTR(&pin_P0_21) },

{ MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_P0_17) },
{ MP_ROM_QSTR(MP_QSTR_BTN_D), MP_ROM_PTR(&pin_P0_17) },
{ MP_ROM_QSTR(MP_QSTR_P0_17), MP_ROM_PTR(&pin_P0_17) },

{ MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_P0_16) },
{ MP_ROM_QSTR(MP_QSTR_BTN_R), MP_ROM_PTR(&pin_P0_16) },
{ MP_ROM_QSTR(MP_QSTR_P0_16), MP_ROM_PTR(&pin_P0_16) },

{ MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_P0_15) },
{ MP_ROM_QSTR(MP_QSTR_BTN_B), MP_ROM_PTR(&pin_P0_15) },
{ MP_ROM_QSTR(MP_QSTR_P0_15), MP_ROM_PTR(&pin_P0_15) },

{ MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_P1_00) },
{ MP_ROM_QSTR(MP_QSTR_P1_00), MP_ROM_PTR(&pin_P1_00) },

// Castellated I/O, top edge (JC19, JC20). Labelled SWITCH 1 / SWITCH 2.
{ MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_P1_09) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH1), MP_ROM_PTR(&pin_P1_09) },
{ MP_ROM_QSTR(MP_QSTR_P1_09), MP_ROM_PTR(&pin_P1_09) },

{ MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_P0_12) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH2), MP_ROM_PTR(&pin_P0_12) },
{ MP_ROM_QSTR(MP_QSTR_P0_12), MP_ROM_PTR(&pin_P0_12) },

// On-board user button (JC6). Active low, 100k pull-up. Also NFC2.
{ MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_P0_10) },
{ MP_ROM_QSTR(MP_QSTR_USER_BTN), MP_ROM_PTR(&pin_P0_10) },
{ MP_ROM_QSTR(MP_QSTR_P0_10), MP_ROM_PTR(&pin_P0_10) },

// Qwiic / STEMMA QT and the JC10 / JC11 castellations. 5.1k pull-ups on board.
{ MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_P0_04) },
{ MP_ROM_QSTR(MP_QSTR_P0_04), MP_ROM_PTR(&pin_P0_04) },
{ MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_P0_06) },
{ MP_ROM_QSTR(MP_QSTR_P0_06), MP_ROM_PTR(&pin_P0_06) },

// Display header (JC23-JC25). Second I2C bus, also with 5.1k pull-ups.
{ MP_ROM_QSTR(MP_QSTR_OLED_SDA), MP_ROM_PTR(&pin_P0_24) },
{ MP_ROM_QSTR(MP_QSTR_P0_24), MP_ROM_PTR(&pin_P0_24) },
{ MP_ROM_QSTR(MP_QSTR_OLED_SCL), MP_ROM_PTR(&pin_P0_25) },
{ MP_ROM_QSTR(MP_QSTR_P0_25), MP_ROM_PTR(&pin_P0_25) },
{ MP_ROM_QSTR(MP_QSTR_OLED_12V_EN), MP_ROM_PTR(&pin_P0_23) },
{ MP_ROM_QSTR(MP_QSTR_P0_23), MP_ROM_PTR(&pin_P0_23) },

// Castellated UART (JC17 / JC18). The silkscreen labels these from the
// point of view of the attached peripheral, so JC17 (UART_GPS_RX) is our TX.
{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_P0_19) },
{ MP_ROM_QSTR(MP_QSTR_P0_19), MP_ROM_PTR(&pin_P0_19) },
{ MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_P0_20) },
{ MP_ROM_QSTR(MP_QSTR_P0_20), MP_ROM_PTR(&pin_P0_20) },

// Switched 3V3 rails brought out on JC14 / JC15, 500 mA each.
{ MP_ROM_QSTR(MP_QSTR_PWR_IO1_EN), MP_ROM_PTR(&pin_P0_22) },
{ MP_ROM_QSTR(MP_QSTR_BUZZER_EN), MP_ROM_PTR(&pin_P0_22) },
{ MP_ROM_QSTR(MP_QSTR_P0_22), MP_ROM_PTR(&pin_P0_22) },
{ MP_ROM_QSTR(MP_QSTR_PWR_IO2_EN), MP_ROM_PTR(&pin_P1_01) },
{ MP_ROM_QSTR(MP_QSTR_GPS_EN), MP_ROM_PTR(&pin_P1_01) },
{ MP_ROM_QSTR(MP_QSTR_P1_01), MP_ROM_PTR(&pin_P1_01) },

// Semtech LR1121 dual-band LoRa radio. Wired inside the nRFLR1121 module,
// except for IRQ which leaves the module and comes back in on P1.08.
{ MP_ROM_QSTR(MP_QSTR_LORA_SCK), MP_ROM_PTR(&pin_P1_13) },
{ MP_ROM_QSTR(MP_QSTR_P1_13), MP_ROM_PTR(&pin_P1_13) },
{ MP_ROM_QSTR(MP_QSTR_LORA_MOSI), MP_ROM_PTR(&pin_P1_14) },
{ MP_ROM_QSTR(MP_QSTR_P1_14), MP_ROM_PTR(&pin_P1_14) },
{ MP_ROM_QSTR(MP_QSTR_LORA_MISO), MP_ROM_PTR(&pin_P1_15) },
{ MP_ROM_QSTR(MP_QSTR_P1_15), MP_ROM_PTR(&pin_P1_15) },
{ MP_ROM_QSTR(MP_QSTR_LORA_CS), MP_ROM_PTR(&pin_P1_12) },
{ MP_ROM_QSTR(MP_QSTR_P1_12), MP_ROM_PTR(&pin_P1_12) },
{ MP_ROM_QSTR(MP_QSTR_LORA_BUSY), MP_ROM_PTR(&pin_P1_11) },
{ MP_ROM_QSTR(MP_QSTR_P1_11), MP_ROM_PTR(&pin_P1_11) },
{ MP_ROM_QSTR(MP_QSTR_LORA_RESET), MP_ROM_PTR(&pin_P1_10) },
{ MP_ROM_QSTR(MP_QSTR_P1_10), MP_ROM_PTR(&pin_P1_10) },
{ MP_ROM_QSTR(MP_QSTR_LORA_IRQ), MP_ROM_PTR(&pin_P1_08) },
{ MP_ROM_QSTR(MP_QSTR_LORA_DIO9), MP_ROM_PTR(&pin_P1_08) },
{ MP_ROM_QSTR(MP_QSTR_P1_08), MP_ROM_PTR(&pin_P1_08) },

// Status LEDs. Anodes are tied to 3V3, so drive these low to light them.
{ MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&pin_P1_04) },
{ MP_ROM_QSTR(MP_QSTR_BLUE_LED), MP_ROM_PTR(&pin_P1_04) },
{ MP_ROM_QSTR(MP_QSTR_P1_04), MP_ROM_PTR(&pin_P1_04) },
{ MP_ROM_QSTR(MP_QSTR_GREEN_LED), MP_ROM_PTR(&pin_P1_03) },
{ MP_ROM_QSTR(MP_QSTR_P1_03), MP_ROM_PTR(&pin_P1_03) },

// BQ25185 charger status. CHARGE_STATUS also sinks the red LED, so it reads
// low while charging. Both are open-drain outputs of the charger: inputs only.
{ MP_ROM_QSTR(MP_QSTR_CHARGE_STATUS), MP_ROM_PTR(&pin_P1_02) },
{ MP_ROM_QSTR(MP_QSTR_P1_02), MP_ROM_PTR(&pin_P1_02) },
{ MP_ROM_QSTR(MP_QSTR_CHARGE_STAT1), MP_ROM_PTR(&pin_P0_27) },
{ MP_ROM_QSTR(MP_QSTR_P0_27), MP_ROM_PTR(&pin_P0_27) },

// Battery sense on AIN7, through an 806k / 1.5M divider.
// VBAT = analog_in.value / 65535 * 3.3 * 1.5373
{ MP_ROM_QSTR(MP_QSTR_VOLTAGE_MONITOR), MP_ROM_PTR(&pin_P0_31) },
{ MP_ROM_QSTR(MP_QSTR_BATTERY), MP_ROM_PTR(&pin_P0_31) },
{ MP_ROM_QSTR(MP_QSTR_P0_31), MP_ROM_PTR(&pin_P0_31) },

{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_STEMMA_I2C), MP_ROM_PTR(&board_stemma_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_I2C), MP_ROM_PTR(&board_display_i2c_obj) },
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) },
{ MP_ROM_QSTR(MP_QSTR_LORA_SPI), MP_ROM_PTR(&board_lora_spi_obj) },
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) },
};

MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);
Loading