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
43 changes: 43 additions & 0 deletions ports/zephyr/MeshCoreZephyr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Zephyr platform bindings for the MeshCore abstract interfaces.
//
#include <MeshCoreZephyr.h>

#include <zephyr/kernel.h>
#include <zephyr/random/random.h>
#include <zephyr/sys/reboot.h>

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
52 changes: 52 additions & 0 deletions ports/zephyr/arduino_compat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Zephyr-backed implementation of the MeshCore Arduino compatibility shim.
//
#include <Arduino.h>

#include <zephyr/kernel.h>
#include <zephyr/random/random.h>
#include <zephyr/sys/printk.h>

// --- 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;
64 changes: 64 additions & 0 deletions ports/zephyr/include/Arduino.h
Original file line number Diff line number Diff line change
@@ -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 <stddef.h>
#include <stdint.h>
#include <Stream.h>

#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 <typename T> static inline T mc_min(T a, T b) { return a < b ? a : b; }
template <typename T> 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
49 changes: 49 additions & 0 deletions ports/zephyr/include/MeshCoreZephyr.h
Original file line number Diff line number Diff line change
@@ -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 <Mesh.h>
#include <Utils.h>

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
62 changes: 62 additions & 0 deletions ports/zephyr/include/RadioLibZephyrHal.h
Original file line number Diff line number Diff line change
@@ -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 <RadioLib.h> and <hal/RadioLibHal.h>
// 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 <RadioLib.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>

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
64 changes: 64 additions & 0 deletions ports/zephyr/include/Stream.h
Original file line number Diff line number Diff line change
@@ -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 <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

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() {}
};
13 changes: 13 additions & 0 deletions samples/zephyr/mesh_min/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 30 additions & 0 deletions samples/zephyr/mesh_min/README.md
Original file line number Diff line number Diff line change
@@ -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, …).
Loading