diff --git a/ports/zephyr/MeshCoreZephyr.cpp b/ports/zephyr/MeshCoreZephyr.cpp new file mode 100644 index 0000000000..a114931e4d --- /dev/null +++ b/ports/zephyr/MeshCoreZephyr.cpp @@ -0,0 +1,43 @@ +// +// Zephyr platform bindings for the MeshCore abstract interfaces. +// +#include + +#include +#include +#include + +namespace mesh_zephyr { + +// --- ZephyrMillisClock ----------------------------------------------------- +unsigned long ZephyrMillisClock::getMillis() { + return (unsigned long)k_uptime_get(); +} + +// --- ZephyrRTCClock -------------------------------------------------------- +ZephyrRTCClock::ZephyrRTCClock() { + base_time = 1715770351; // 15 May 2024, matches the Arduino VolatileRTCClock default + base_uptime_ms = (uint64_t)k_uptime_get(); +} + +uint32_t ZephyrRTCClock::getCurrentTime() { + uint64_t elapsed_ms = (uint64_t)k_uptime_get() - base_uptime_ms; + return base_time + (uint32_t)(elapsed_ms / 1000U); +} + +void ZephyrRTCClock::setCurrentTime(uint32_t time) { + base_time = time; + base_uptime_ms = (uint64_t)k_uptime_get(); +} + +// --- ZephyrRNG ------------------------------------------------------------- +void ZephyrRNG::random(uint8_t* dest, size_t sz) { + sys_rand_get(dest, sz); +} + +// --- ZephyrBoard ----------------------------------------------------------- +void ZephyrBoard::reboot() { + sys_reboot(SYS_REBOOT_COLD); +} + +} // namespace mesh_zephyr diff --git a/ports/zephyr/arduino_compat.cpp b/ports/zephyr/arduino_compat.cpp new file mode 100644 index 0000000000..7f23be8d73 --- /dev/null +++ b/ports/zephyr/arduino_compat.cpp @@ -0,0 +1,52 @@ +// +// Zephyr-backed implementation of the MeshCore Arduino compatibility shim. +// +#include + +#include +#include +#include + +// --- Timing --------------------------------------------------------------- +extern "C" unsigned long millis(void) { + return (unsigned long)k_uptime_get(); +} + +extern "C" unsigned long micros(void) { + return (unsigned long)(k_ticks_to_us_floor64(k_uptime_ticks())); +} + +extern "C" void delay(unsigned long ms) { + k_msleep((int32_t)ms); +} + +extern "C" void delayMicroseconds(unsigned int us) { + k_busy_wait(us); +} + +// --- Randomness ----------------------------------------------------------- +extern "C" void randomSeed(unsigned long /*seed*/) { + // No-op: entropy is provided by the Zephyr RNG. +} + +extern "C" long random(long max) { + if (max <= 0) return 0; + uint32_t v; + sys_rand_get(&v, sizeof(v)); + return (long)(v % (uint32_t)max); +} + +extern "C" long random(long min, long max) { + if (max <= min) return min; + return min + random(max - min); +} + +// --- Serial --------------------------------------------------------------- +// Routes byte output to Zephyr's console via printk. Buffered per-byte writes +// are fine for the low-volume debug/logging paths the core uses. +size_t ZephyrSerial::write(uint8_t b) { + printk("%c", (char)b); + return 1; +} + +ZephyrSerial Serial; diff --git a/ports/zephyr/include/Arduino.h b/ports/zephyr/include/Arduino.h new file mode 100644 index 0000000000..814b6fd185 --- /dev/null +++ b/ports/zephyr/include/Arduino.h @@ -0,0 +1,64 @@ +#pragma once +// +// Minimal Arduino compatibility header for building MeshCore on Zephyr RTOS. +// +// Provides only the free functions and the `Serial` object the MeshCore core +// relies on, implemented on top of the Zephyr kernel (see arduino_compat.cpp). +// This is a shim, not a full Arduino core: no digital/analog IO, no String, no +// SPI/Wire. Those belong to board/transport ports. + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// --- Timing --------------------------------------------------------------- +unsigned long millis(void); +unsigned long micros(void); +void delay(unsigned long ms); +void delayMicroseconds(unsigned int us); + +// --- Randomness ----------------------------------------------------------- +// randomSeed() is accepted for source-compat but ignored: the backing entropy +// comes from Zephyr's RNG, not a PRNG seed. +void randomSeed(unsigned long seed); +long random(long max); +long random(long min, long max); + +#ifdef __cplusplus +} +#endif + +// --- Serial --------------------------------------------------------------- +// A printk-backed Stream. Declared here, defined in arduino_compat.cpp. +#ifdef __cplusplus +class ZephyrSerial : public Stream { +public: + void begin(unsigned long /*baud*/) {} + size_t write(uint8_t b) override; + using Print::write; +}; +extern ZephyrSerial Serial; +#endif + +// --- Common Arduino helpers the tree occasionally expects ----------------- +#ifndef HIGH +#define HIGH 1 +#endif +#ifndef LOW +#define LOW 0 +#endif + +#ifdef __cplusplus +template static inline T mc_min(T a, T b) { return a < b ? a : b; } +template static inline T mc_max(T a, T b) { return a > b ? a : b; } +#ifndef min +#define min(a, b) mc_min(a, b) +#endif +#ifndef max +#define max(a, b) mc_max(a, b) +#endif +#endif diff --git a/ports/zephyr/include/MeshCoreZephyr.h b/ports/zephyr/include/MeshCoreZephyr.h new file mode 100644 index 0000000000..7db0902a30 --- /dev/null +++ b/ports/zephyr/include/MeshCoreZephyr.h @@ -0,0 +1,49 @@ +#pragma once +// +// Concrete MeshCore platform bindings for Zephyr RTOS. +// +// These implement the abstract interfaces the mesh core depends on +// (mesh::MillisecondClock, mesh::RTCClock, mesh::RNG, mesh::MainBoard) using +// Zephyr kernel APIs, so an application can bring up the engine without any +// Arduino board package. Radio and transport bindings are separate (see the +// RadioLib wrappers / RadioLibZephyrHal.h). + +#include +#include + +namespace mesh_zephyr { + +// Monotonic millisecond clock backed by k_uptime_get(). +class ZephyrMillisClock : public mesh::MillisecondClock { +public: + unsigned long getMillis() override; +}; + +// Volatile UNIX-epoch clock. Advances from a settable base using uptime; use a +// hardware RTC binding instead where wall-clock persistence is required. +class ZephyrRTCClock : public mesh::RTCClock { + uint32_t base_time; + uint64_t base_uptime_ms; +public: + ZephyrRTCClock(); + uint32_t getCurrentTime() override; + void setCurrentTime(uint32_t time) override; +}; + +// Hardware RNG via Zephyr's entropy/random subsystem. +class ZephyrRNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override; +}; + +// Minimal board: satisfies the pure-virtual mesh::MainBoard surface with sane +// defaults. Subclass and override for real battery/temperature/reboot support. +class ZephyrBoard : public mesh::MainBoard { +public: + uint16_t getBattMilliVolts() override { return 0; } + const char* getManufacturerName() const override { return "MeshCore/Zephyr"; } + uint8_t getStartupReason() const override { return 0; } + void reboot() override; +}; + +} // namespace mesh_zephyr diff --git a/ports/zephyr/include/RadioLibZephyrHal.h b/ports/zephyr/include/RadioLibZephyrHal.h new file mode 100644 index 0000000000..df481c731d --- /dev/null +++ b/ports/zephyr/include/RadioLibZephyrHal.h @@ -0,0 +1,62 @@ +#pragma once +// +// SCAFFOLD / TODO: RadioLib Hardware Abstraction Layer for Zephyr. +// +// MeshCore's radio bindings (src/helpers/radiolib/*) drive RadioLib. On Arduino +// RadioLib uses its built-in Arduino HAL; on Zephyr you must provide a HAL that +// maps RadioLib's GPIO/SPI/timing primitives onto Zephyr device drivers +// (gpio, spi, and the kernel clock). +// +// This header is a starting point only. It is NOT compiled by the baseline +// (CONFIG_MESHCORE_RADIOLIB is off by default). To finish the port: +// +// 1. Add RadioLib to the west manifest so and +// resolve. +// 2. Fill in the methods below using devicetree-declared spi/gpio nodes. +// 3. Set CONFIG_MESHCORE_RADIOLIB=y and select a MESHCORE_RADIO_* driver. +// +// Reference: RadioLib's examples/NonArduino/* HAL implementations. + +#if defined(CONFIG_MESHCORE_RADIOLIB) + +#include +#include +#include +#include + +class ZephyrHal : public RadioLibHal { +public: + ZephyrHal(const struct device* spi, + const struct gpio_dt_spec* nss, + const struct gpio_dt_spec* rst, + const struct gpio_dt_spec* busy, + const struct gpio_dt_spec* dio1); + + // --- RadioLibHal interface (see hal/RadioLibHal.h) ----------------------- + void init() override; + void term() override; + void pinMode(uint32_t pin, uint32_t mode) override; + void digitalWrite(uint32_t pin, uint32_t value) override; + uint32_t digitalRead(uint32_t pin) override; + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override; + void detachInterrupt(uint32_t interruptNum) override; + void delay(unsigned long ms) override; + void delayMicroseconds(unsigned long us) override; + unsigned long millis() override; + unsigned long micros() override; + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override; + void spiBegin() override; + void spiBeginTransaction() override; + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override; + void spiEndTransaction() override; + void spiEnd() override; + +private: + const struct device* _spi; + const struct gpio_dt_spec* _nss; + const struct gpio_dt_spec* _rst; + const struct gpio_dt_spec* _busy; + const struct gpio_dt_spec* _dio1; +}; + +#endif // CONFIG_MESHCORE_RADIOLIB diff --git a/ports/zephyr/include/Stream.h b/ports/zephyr/include/Stream.h new file mode 100644 index 0000000000..7e7924be4b --- /dev/null +++ b/ports/zephyr/include/Stream.h @@ -0,0 +1,64 @@ +#pragma once +// +// Minimal Arduino Print/Stream compatibility surface for MeshCore on Zephyr. +// +// The MeshCore *core* (src/*.cpp) only touches a tiny slice of the Arduino +// Stream API: print(char), print(const char*), printf(), println() and +// write(). This header provides exactly that, backed by whatever a concrete +// subclass implements in write(). See ports/zephyr/arduino_compat.cpp for the +// printk-backed `Serial` instance. +// +// This is intentionally NOT a full Arduino Stream implementation. Helpers that +// need String, Wire, SPI, or the full Stream read API still require porting. + +#include +#include +#include +#include +#include + +class Print { +public: + virtual ~Print() {} + + // The one method subclasses must provide. + virtual size_t write(uint8_t b) = 0; + + virtual size_t write(const uint8_t* buf, size_t len) { + size_t n = 0; + while (len--) n += write(*buf++); + return n; + } + size_t write(const char* s) { return s ? write((const uint8_t*)s, strlen(s)) : 0; } + + size_t print(char c) { return write((uint8_t)c); } + size_t print(const char* s) { return write(s); } + size_t print(int v) { return printf("%d", v); } + size_t print(unsigned v) { return printf("%u", v); } + size_t print(long v) { return printf("%ld", v); } + size_t print(unsigned long v) { return printf("%lu", v); } + + size_t println() { return write((uint8_t)'\n'); } + size_t println(const char* s) { size_t n = write(s); return n + println(); } + + size_t printf(const char* fmt, ...) { + char buf[192]; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (n < 0) return 0; + size_t len = (size_t)n < sizeof(buf) ? (size_t)n : sizeof(buf) - 1; + return write((const uint8_t*)buf, len); + } +}; + +// Stream adds the input side of the API. The core never reads from it, so these +// are stubbed; override in a real UART/BLE transport port. +class Stream : public Print { +public: + virtual int available() { return 0; } + virtual int read() { return -1; } + virtual int peek() { return -1; } + virtual void flush() {} +}; diff --git a/samples/zephyr/mesh_min/CMakeLists.txt b/samples/zephyr/mesh_min/CMakeLists.txt new file mode 100644 index 0000000000..97b7d2c7d3 --- /dev/null +++ b/samples/zephyr/mesh_min/CMakeLists.txt @@ -0,0 +1,13 @@ +# MeshCore "minimal" Zephyr sample. +# +# Brings up the mesh core with the Zephyr platform helpers and a loopback radio, +# proving the engine compiles and runs on a Zephyr target with no external +# radio hardware or extra dependencies. +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(meshcore_mesh_min) + +target_sources(app PRIVATE src/main.cpp) + +# The MeshCore module publishes its own include paths via zephyr_include_directories. diff --git a/samples/zephyr/mesh_min/README.md b/samples/zephyr/mesh_min/README.md new file mode 100644 index 0000000000..40dd933319 --- /dev/null +++ b/samples/zephyr/mesh_min/README.md @@ -0,0 +1,30 @@ +# MeshCore minimal sample + +Brings up the MeshCore mesh engine on Zephyr using the bundled platform helpers +and a loopback radio. No external LoRa hardware or extra west modules required — +this is the smoke test that the core builds and runs on a Zephyr target. + +## Build & run (native_sim) + +```console +west build -b native_sim samples/zephyr/mesh_min +west build -t run +``` + +Expected console output: + +``` +MeshCore minimal sample: mesh engine up +``` + +## What it does + +- Instantiates `mesh::Mesh` with: + - `LoopbackRadio` — accepts sends, never receives; + - `ZephyrMillisClock`, `ZephyrRNG`, `ZephyrRTCClock`, from + `ports/zephyr/MeshCoreZephyr.*`; + - a 16-slot `StaticPoolPacketManager` and `SimpleMeshTables`. +- Generates a random `LocalIdentity`, calls `begin()`, then runs `loop()`. + +See `../../../zephyr/README.md` for how to turn this into a real, radio-backed +role (repeater, room server, sensor, …). diff --git a/samples/zephyr/mesh_min/prj.conf b/samples/zephyr/mesh_min/prj.conf new file mode 100644 index 0000000000..3c80319c09 --- /dev/null +++ b/samples/zephyr/mesh_min/prj.conf @@ -0,0 +1,26 @@ +# Minimal MeshCore sample configuration. + +# Pull in and build the MeshCore library. +CONFIG_MESHCORE=y +CONFIG_MESHCORE_ARDUINO_COMPAT=y +CONFIG_MESHCORE_ZEPHYR_HELPERS=y + +# No external radio in this sample. +CONFIG_MESHCORE_RADIOLIB=n + +# C++ runtime. +CONFIG_CPP=y +CONFIG_STD_CPP17=y +CONFIG_REQUIRES_FULL_LIBCPP=y + +# Randomness for the RNG binding. +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y + +# Console / logging. +CONFIG_PRINTK=y +CONFIG_LOG=y + +# Reasonable stack/heap headroom for the mesh engine. +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_HEAP_MEM_POOL_SIZE=16384 diff --git a/samples/zephyr/mesh_min/sample.yaml b/samples/zephyr/mesh_min/sample.yaml new file mode 100644 index 0000000000..0b7bcd7315 --- /dev/null +++ b/samples/zephyr/mesh_min/sample.yaml @@ -0,0 +1,21 @@ +sample: + name: MeshCore minimal (loopback) + description: > + Brings up the MeshCore mesh engine on Zephyr with the Zephyr platform + helpers and a loopback radio. No external LoRa hardware required. + +common: + tags: meshcore lora mesh + integration_platforms: + - native_sim + harness: console + harness_config: + type: one_line + regex: + - "MeshCore minimal sample: mesh engine up" + +tests: + sample.meshcore.mesh_min: + platform_allow: + - native_sim + - qemu_cortex_m3 diff --git a/samples/zephyr/mesh_min/src/main.cpp b/samples/zephyr/mesh_min/src/main.cpp new file mode 100644 index 0000000000..6fa68b8cf5 --- /dev/null +++ b/samples/zephyr/mesh_min/src/main.cpp @@ -0,0 +1,61 @@ +// +// MeshCore minimal Zephyr sample. +// +// Instantiates the MeshCore engine on Zephyr using the bundled platform helpers +// (clock, RNG, RTC, board) and a loopback radio, then runs the dispatcher loop. +// This proves the core builds and runs on a Zephyr target with no external LoRa +// hardware. Swap LoopbackRadio for a RadioLib-backed driver (see +// ports/zephyr/RadioLibZephyrHal.h) to talk to real hardware. +// +#include +#include +#include +#include +#include + +#include + +// --- A do-nothing radio: accepts sends, never receives ------------------- +class LoopbackRadio : public mesh::Radio { +public: + int recvRaw(uint8_t*, int) override { return 0; } + uint32_t getEstAirtimeFor(int len_bytes) override { return len_bytes; /* ~1ms/byte */ } + float packetScore(float, int) override { return 0.0f; } + bool startSendRaw(const uint8_t*, int) override { return true; } + bool isSendComplete() override { return true; } + void onSendFinished() override {} + bool isInRecvMode() const override { return true; } +}; + +// --- Platform bindings (Zephyr-backed) ----------------------------------- +static LoopbackRadio radio; +static mesh_zephyr::ZephyrMillisClock ms_clock; +static mesh_zephyr::ZephyrRNG rng; +static mesh_zephyr::ZephyrRTCClock rtc; +static StaticPoolPacketManager pool(16); +static SimpleMeshTables tables; + +// --- The mesh engine ------------------------------------------------------ +class MinimalMesh : public mesh::Mesh { +public: + MinimalMesh() + : mesh::Mesh(radio, ms_clock, rng, rtc, pool, tables) {} +}; + +static MinimalMesh the_mesh; + +int main(void) { + Serial.begin(115200); + + // Give this node a fresh random identity, then start the dispatcher. + the_mesh.self_id = mesh::LocalIdentity(&rng); + the_mesh.begin(); + + Serial.println("MeshCore minimal sample: mesh engine up"); + + for (;;) { + the_mesh.loop(); + k_msleep(10); + } + return 0; +} diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt new file mode 100644 index 0000000000..f5f03834e4 --- /dev/null +++ b/zephyr/CMakeLists.txt @@ -0,0 +1,70 @@ +# MeshCore Zephyr module build glue. +# +# Only does anything when CONFIG_MESHCORE=y. Builds the mesh core plus the +# optional shim / helper / RadioLib layers selected in Kconfig, and forwards the +# LoRa RF parameters as compile definitions so the same source compiles that the +# PlatformIO build uses. + +if(CONFIG_MESHCORE) + + set(MC_ROOT ${ZEPHYR_CURRENT_MODULE_DIR}) + + zephyr_library_named(meshcore) + + # ---- Public include paths ------------------------------------------------ + zephyr_include_directories( + ${MC_ROOT}/src + ${MC_ROOT}/src/helpers + ) + + # ---- Core mesh engine (protocol, dispatcher, identity, packets) ---------- + file(GLOB MC_CORE_SRC ${MC_ROOT}/src/*.cpp) + zephyr_library_sources(${MC_CORE_SRC}) + + # ---- Arduino compatibility shim (Zephyr-backed) -------------------------- + if(CONFIG_MESHCORE_ARDUINO_COMPAT) + zephyr_include_directories(${MC_ROOT}/ports/zephyr/include) + zephyr_library_sources(${MC_ROOT}/ports/zephyr/arduino_compat.cpp) + endif() + + # ---- Zephyr platform helpers (clocks, RNG, board) ------------------------ + if(CONFIG_MESHCORE_ZEPHYR_HELPERS) + zephyr_library_sources(${MC_ROOT}/ports/zephyr/MeshCoreZephyr.cpp) + endif() + + # ---- RadioLib wrappers (optional, needs the RadioLib module) ------------- + if(CONFIG_MESHCORE_RADIOLIB) + zephyr_include_directories(${MC_ROOT}/src/helpers/radiolib) + zephyr_library_sources(${MC_ROOT}/src/helpers/radiolib/RadioLibWrappers.cpp) + + if(CONFIG_MESHCORE_RADIO_SX1262) + zephyr_library_compile_definitions(RADIO_CLASS=CustomSX1262 WRAPPER_CLASS=CustomSX1262Wrapper) + elseif(CONFIG_MESHCORE_RADIO_SX1276) + zephyr_library_compile_definitions(RADIO_CLASS=CustomSX1276 WRAPPER_CLASS=CustomSX1276Wrapper) + elseif(CONFIG_MESHCORE_RADIO_LR1110) + zephyr_library_compile_definitions(RADIO_CLASS=CustomLR1110 WRAPPER_CLASS=CustomLR1110Wrapper) + elseif(CONFIG_MESHCORE_RADIO_STM32WLX) + zephyr_library_compile_definitions(RADIO_CLASS=CustomSTM32WLx WRAPPER_CLASS=CustomSTM32WLxWrapper) + endif() + endif() + + # ---- LoRa RF parameters --> compile definitions -------------------------- + # Kconfig string values may arrive quoted; strip quotes so the value lands as + # a numeric literal (e.g. 869.618) rather than a string in the C++ TUs. + string(REPLACE "\"" "" MC_LORA_FREQ "${CONFIG_MESHCORE_LORA_FREQ}") + string(REPLACE "\"" "" MC_LORA_BW "${CONFIG_MESHCORE_LORA_BW}") + zephyr_library_compile_definitions( + LORA_FREQ=${MC_LORA_FREQ} + LORA_BW=${MC_LORA_BW} + LORA_SF=${CONFIG_MESHCORE_LORA_SF} + LORA_CR=${CONFIG_MESHCORE_LORA_CR} + LORA_TX_POWER=${CONFIG_MESHCORE_LORA_TX_POWER} + ) + + # RadioLib static-only knobs, matching platformio.ini's arduino_base flags. + zephyr_library_compile_definitions( + NDEBUG + RADIOLIB_STATIC_ONLY=1 + ) + +endif() diff --git a/zephyr/Kconfig b/zephyr/Kconfig new file mode 100644 index 0000000000..fdc3f43bc2 --- /dev/null +++ b/zephyr/Kconfig @@ -0,0 +1,131 @@ +# MeshCore Zephyr module Kconfig +# +# Sourced automatically by Zephyr's module machinery once the repo is a west +# project. Everything lives under the MESHCORE menu. + +menuconfig MESHCORE + bool "MeshCore LoRa mesh library" + select CPP + select CPP_EXCEPTIONS if MESHCORE_CPP_EXCEPTIONS + select REQUIRES_FULL_LIBCPP + select ENTROPY_GENERATOR if MESHCORE_ARDUINO_COMPAT + help + Build and link the MeshCore mesh-networking engine into the + application. This compiles the protocol core under src/ plus the + optional support layers selected below. + +if MESHCORE + +config MESHCORE_CPP_EXCEPTIONS + bool "Enable C++ exceptions for MeshCore" + default n + help + MeshCore itself does not require exceptions. Leave disabled unless an + application component needs them. + +config MESHCORE_ARDUINO_COMPAT + bool "Build the bundled Arduino compatibility shim" + default y + help + Provides the tiny Arduino surface the core relies on (millis(), + delay(), random(), and a printk-backed `Serial`/`Stream`) implemented + on top of the Zephyr kernel. Disable only if the application supplies + its own Arduino-compatible layer (e.g. the Zephyr Arduino module). + +config MESHCORE_ZEPHYR_HELPERS + bool "Build the Zephyr platform helpers (clocks, RNG, board)" + default y + depends on MESHCORE_ARDUINO_COMPAT + help + Concrete mesh::MillisecondClock / mesh::RTCClock / mesh::RNG / + mesh::MainBoard implementations backed by Zephyr kernel APIs. These + let an application bring up the mesh core without any Arduino board + package. + +config MESHCORE_RADIOLIB + bool "Build the RadioLib radio wrappers" + default n + help + Compile src/helpers/radiolib/*. Requires the RadioLib module to be + present in the west manifest and a working RadioLib HAL for Zephyr + (see ports/zephyr/RadioLibZephyrHal.h). Off by default so the baseline + builds with no extra dependencies. + +choice + prompt "LoRa radio driver" + default MESHCORE_RADIO_SX1262 + depends on MESHCORE_RADIOLIB + help + Selects which RadioLib Custom* wrapper is compiled in. + +config MESHCORE_RADIO_SX1262 + bool "Semtech SX1262" + +config MESHCORE_RADIO_SX1276 + bool "Semtech SX1276" + +config MESHCORE_RADIO_LR1110 + bool "Semtech LR1110" + +config MESHCORE_RADIO_STM32WLX + bool "STM32WLx integrated LoRa" + +endchoice + +menu "LoRa RF parameters" + +config MESHCORE_LORA_FREQ + string "Centre frequency (MHz)" + default "869.618" + help + Passed to the build as -DLORA_FREQ. String because Kconfig has no + float type; the value is used verbatim in C++ (e.g. 869.618). + +config MESHCORE_LORA_BW + string "Bandwidth (kHz)" + default "62.5" + +config MESHCORE_LORA_SF + int "Spreading factor" + range 5 12 + default 8 + +config MESHCORE_LORA_CR + int "Coding rate denominator (4/x)" + range 5 8 + default 5 + +config MESHCORE_LORA_TX_POWER + int "TX power (dBm)" + default 20 + +endmenu + +# Application role. Mirrors the PlatformIO examples/ folder; used by samples and +# by application CMakeLists to pull the right example sources. +choice + prompt "MeshCore application role" + default MESHCORE_ROLE_NONE + +config MESHCORE_ROLE_NONE + bool "None (library only)" + +config MESHCORE_ROLE_REPEATER + bool "Simple repeater" + +config MESHCORE_ROLE_ROOM_SERVER + bool "Simple room server" + +config MESHCORE_ROLE_COMPANION + bool "Companion radio" + +config MESHCORE_ROLE_SENSOR + bool "Simple sensor" + +endchoice + +module = MESHCORE +module-str = meshcore +source "subsys/logging/Kconfig.template.log_config" + +endif # MESHCORE diff --git a/zephyr/README.md b/zephyr/README.md new file mode 100644 index 0000000000..7e9c9efdc0 --- /dev/null +++ b/zephyr/README.md @@ -0,0 +1,160 @@ +# Building MeshCore with Zephyr RTOS + +This directory turns MeshCore into a **Zephyr module**, so the mesh engine can +be built and configured from a Zephyr application instead of (or alongside) the +existing PlatformIO/Arduino build. + +## Layout + +``` +zephyr/ + module.yml # declares MeshCore as a Zephyr module + Kconfig # MESHCORE_* options (features, radio, LoRa RF params) + CMakeLists.txt # builds the selected sources into a zephyr_library + README.md # this file +ports/zephyr/ + include/Arduino.h # tiny Arduino shim (millis/delay/random/Serial) on Zephyr + include/Stream.h # Print/Stream surface used by the core + include/MeshCoreZephyr.h # mesh::Clock/RNG/RTC/Board bound to Zephyr APIs + include/RadioLibZephyrHal.h# SCAFFOLD: RadioLib HAL for Zephyr (next step) + arduino_compat.cpp # shim implementation + MeshCoreZephyr.cpp # platform-binding implementation +samples/zephyr/ + mesh_min/ # buildable loopback sample (no radio HW needed) +``` + +## How the module is wired up + +`module.yml` points Zephyr at this directory's `CMakeLists.txt` and `Kconfig`. +Zephyr sources them automatically for every project listed as a module. The +CMake glue only compiles anything when `CONFIG_MESHCORE=y`, and it builds: + +- the mesh **core** (`src/*.cpp`) always; +- the **Arduino shim** (`CONFIG_MESHCORE_ARDUINO_COMPAT`, default y); +- the **Zephyr platform helpers** (`CONFIG_MESHCORE_ZEPHYR_HELPERS`, default y); +- the **RadioLib wrappers** (`CONFIG_MESHCORE_RADIOLIB`, default **n** — needs the + RadioLib module and a Zephyr HAL, see below). + +LoRa RF parameters (`MESHCORE_LORA_FREQ/BW/SF/CR/TX_POWER`) are forwarded as the +same `-DLORA_*` compile definitions the PlatformIO build uses. + +## Adding MeshCore to your Zephyr workspace (west submanifest) + +MeshCore is intended to be pulled in as a west **submanifest / project**. In the +top-level (application or Zephyr) `west.yml`: + +```yaml +manifest: + remotes: + - name: meshcore + url-base: https://github.com/meshcore-dev + projects: + - name: MeshCore + remote: meshcore + revision: feature/zephyr_sub_build + path: modules/lib/meshcore # any path inside the workspace +``` + +Then: + +```console +west update +``` + +Because `zephyr/module.yml` exists, Zephyr discovers the module with no extra +configuration. Verify with: + +```console +west list | grep -i meshcore +``` + +If MeshCore lives outside the manifest, point Zephyr at it explicitly instead: + +```console +west build -b -- -DEXTRA_ZEPHYR_MODULES=/abs/path/to/MeshCore +``` + +## Enabling it in an application + +In the application's `prj.conf`: + +```conf +CONFIG_MESHCORE=y +CONFIG_CPP=y +CONFIG_STD_CPP17=y +CONFIG_REQUIRES_FULL_LIBCPP=y +CONFIG_ENTROPY_GENERATOR=y +# RF parameters (optional, these are the defaults) +CONFIG_MESHCORE_LORA_FREQ="869.618" +CONFIG_MESHCORE_LORA_SF=8 +``` + +## Build the sample + +```console +west build -b native_sim samples/zephyr/mesh_min +west build -t run +``` + +Expected output: `MeshCore minimal sample: mesh engine up`. + +Run it under Twister: + +```console +west twister -T samples/zephyr/mesh_min -p native_sim +``` + +## How to add more build examples + +The PlatformIO tree already ships several roles under `examples/` +(`simple_repeater`, `simple_room_server`, `companion_radio`, `simple_sensor`, +…). Mirror them as Zephyr samples like this: + +1. **Copy the skeleton.** Duplicate `samples/zephyr/mesh_min` to + `samples/zephyr/` (each needs `CMakeLists.txt`, `prj.conf`, + `sample.yaml`, `src/`). + +2. **Pull in the role sources.** The example `.cpp`/`.h` live under + `examples/`. Add them to the sample's `CMakeLists.txt`: + + ```cmake + set(MC_EXAMPLE ${ZEPHYR_CURRENT_MODULE_DIR}/examples/simple_repeater) + target_sources(app PRIVATE ${MC_EXAMPLE}/main.cpp) + target_include_directories(app PRIVATE ${MC_EXAMPLE}) + ``` + + (`ZEPHYR_CURRENT_MODULE_DIR` resolves to the MeshCore checkout.) Select the + matching role in `prj.conf` with `CONFIG_MESHCORE_ROLE_REPEATER=y` so future + glue can key off it. + +3. **Provide a real radio.** Replace `LoopbackRadio` with a RadioLib driver: + - add RadioLib to the workspace `west.yml`; + - implement `ZephyrHal` in `ports/zephyr/RadioLibZephyrHal.h`; + - set `CONFIG_MESHCORE_RADIOLIB=y` and a `CONFIG_MESHCORE_RADIO_*` choice; + - describe the SX126x/SX127x wiring in a board `.overlay` (spi + nss/rst/busy/dio1 + GPIOs) and read it via `gpio_dt_spec`. + +4. **Storage / identity.** The Arduino examples persist identity via a + filesystem (`InternalFS`/LittleFS). On Zephyr use the `settings` subsystem or + an `fs`/LittleFS partition and adapt `IdentityStore`. + +5. **Register with CI.** `sample.yaml` makes Twister pick the sample up; add the + boards it should build for under `platform_allow`. + +## Current status / porting roadmap + +Working in this baseline: + +- Module discovery, Kconfig, CMake glue. +- Core mesh engine + Arduino shim + Zephyr clock/RNG/RTC/board compile and run. +- Loopback sample on `native_sim`. + +Still TODO (each is an independent, well-scoped step): + +- RadioLib `ZephyrHal` (GPIO/SPI/timing) — scaffolded in + `ports/zephyr/RadioLibZephyrHal.h`. +- Persistent identity/config storage (settings/LittleFS). +- Transports beyond LoRa (BLE/serial) — the Arduino `helpers/{esp32,nrf52}` + transports are not portable as-is. +- Displays / sensors under `helpers/ui` and `helpers/sensors`. +``` diff --git a/zephyr/module.yml b/zephyr/module.yml new file mode 100644 index 0000000000..a7cf760f0a --- /dev/null +++ b/zephyr/module.yml @@ -0,0 +1,15 @@ +# MeshCore Zephyr module descriptor. +# +# This turns the MeshCore repository into a Zephyr module. When the repo is +# pulled in as a west project (see zephyr/README.md), Zephyr automatically +# sources the Kconfig and CMakeLists.txt referenced below, so applications can +# enable the library with `CONFIG_MESHCORE=y`. +name: meshcore + +build: + # Directory (relative to the repo root) that holds the module's CMakeLists.txt. + cmake: zephyr + # Kconfig tree that exposes the MESHCORE_* options. + kconfig: zephyr/Kconfig + # NOTE: add a `settings:` block here (board_root / dts_root / soc_root) if and + # when MeshCore starts shipping Zephyr boards or SoC/DTS overlays.