mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-06 15:51:25 +00:00
Add radio simulation
This commit is contained in:
1 parent
9337a78cd0
commit
d39519b92f
66 files changed
+5659
-1000
No files matched your search
@@ -3,8 +3,12 @@
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
#ifndef MQTT_SERVER_URL
|
||||
#define MQTT_SERVER_URL "mqtt://broker.hivemq.com:1883"
|
||||
#endif
|
||||
#ifndef MQTT_CLIENT_ID
|
||||
#define MQTT_CLIENT_ID "d3"
|
||||
#endif
|
||||
#define MQTT_USER MQTT_CLIENT_ID
|
||||
#define MQTT_PASS ""
|
||||
#define MQTT_PUBLISH_TOPIC "mg/" MQTT_CLIENT_ID "/tx"
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
|
||||
|
||||
MGDIR = ../../../..
|
||||
|
||||
# Flash over USB: the board's Adafruit UF2 bootloader does serial DFU. The
|
||||
# firmware resets into it on a 1200 baud touch, see usb.c. PORT: the board's
|
||||
# USB serial port. adafruit-nrfutil: pip install adafruit-nrfutil
|
||||
PORT ?= /dev/cu.usbmodem11301
|
||||
NRFUTIL ?= adafruit-nrfutil
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wshadow -Wdouble-promotion
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include
|
||||
CFLAGS += -Inrfx/mdk -Inrfx -Inrfx/hal -Inrfx/templates -Inrfx/drivers/src -Itinyusb/src
|
||||
CFLAGS += -Wno-cast-align -Wno-shadow -Wno-unused-parameter
|
||||
CFLAGS += -DNRF52840_XXAA -D__STARTUP_CLEAR_BSS -D__START=main -D__HEAP_SIZE=0
|
||||
CFLAGS += -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
|
||||
CFLAGS += $(CFLAGS_EXTRA)
|
||||
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c usb.c $(MGDIR)/mongoose.c
|
||||
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
|
||||
SOURCES += nrfx/mdk/system_nrf52840.c nrfx/mdk/gcc_startup_nrf52840.S
|
||||
|
||||
# TinyUSB, for the USB serial console: the board has no USB-UART chip
|
||||
SOURCES += tinyusb/src/tusb.c tinyusb/src/common/tusb_fifo.c
|
||||
SOURCES += tinyusb/src/device/usbd.c tinyusb/src/device/usbd_control.c
|
||||
SOURCES += tinyusb/src/class/cdc/cdc_device.c
|
||||
SOURCES += tinyusb/src/portable/nordic/nrf5x/dcd_nrf5x.c
|
||||
|
||||
all build: firmware.hex
|
||||
|
||||
firmware.hex: firmware.elf
|
||||
arm-none-eabi-objcopy -O ihex $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core nrfx tinyusb $(SOURCES) $(wildcard *.h) link.ld Makefile
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
firmware.zip: firmware.hex
|
||||
$(NRFUTIL) dfu genpkg --dev-type 0x0052 --sd-req 0xFFFE --application $< $@
|
||||
|
||||
flash: firmware.zip
|
||||
$(NRFUTIL) dfu serial --package $< -p $(PORT) -b 115200 --singlebank --touch 1200
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
# nRF52840 device headers and SystemInit(), nrf_* register helpers for TinyUSB
|
||||
nrfx:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b v2.11.0 https://github.com/NordicSemiconductor/nrfx $@
|
||||
|
||||
tinyusb:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 0.14.0 https://github.com/hathach/tinyusb $@
|
||||
|
||||
clean:
|
||||
rm -rf firmware.* *.dSYM tinyusb nrfx cmsis_core
|
||||
|
||||
.PHONY: all build flash clean
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
|
||||
(void) s, (void) line, (void) fn, (void) expr;
|
||||
for (;;) (void) 0; // or trigger the fault handler
|
||||
}
|
||||
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
|
||||
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
|
||||
if (period == 0) return false; // Avoid division by zero
|
||||
if (diff < period) return false; // Period has not elapsed yet
|
||||
*t += (diff / period) * period; // Preserve cadence, skip missed periods
|
||||
return true;
|
||||
}
|
||||
|
||||
static volatile uint64_t s_ticks; // Milliseconds since boot
|
||||
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
|
||||
s_ticks++;
|
||||
}
|
||||
|
||||
uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
};
|
||||
|
||||
struct stat;
|
||||
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
|
||||
(void) fd, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
|
||||
static unsigned char *s_current_heap_end = _end;
|
||||
|
||||
size_t hal_ram_used(void) {
|
||||
return (size_t) (s_current_heap_end - _end);
|
||||
}
|
||||
|
||||
size_t hal_ram_free(void) {
|
||||
unsigned char endofstack;
|
||||
return (size_t) (&endofstack - s_current_heap_end);
|
||||
}
|
||||
|
||||
void *_sbrk(int incr) {
|
||||
unsigned char *prev_heap;
|
||||
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
|
||||
prev_heap = s_current_heap_end;
|
||||
// Check how much space we got from the heap end to the stack end
|
||||
if (s_current_heap_end + incr > heap_end) return (void *) -1;
|
||||
s_current_heap_end += incr;
|
||||
return prev_heap;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _open(const char *path) {
|
||||
(void) path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _close(int fd) {
|
||||
(void) fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _isatty(int fd) {
|
||||
(void) fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
|
||||
(void) fd, (void) ptr, (void) dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _exit(int status) {
|
||||
(void) status;
|
||||
for (;;) asm volatile("BKPT #0");
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _kill(int pid, int sig) {
|
||||
(void) pid, (void) sig;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _getpid(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _link(const char *a, const char *b) {
|
||||
(void) a, (void) b;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _unlink(const char *a) {
|
||||
(void) a;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
|
||||
(void) path, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int mkdir(const char *path, int mode) {
|
||||
(void) path, (void) mode;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _init(void) {
|
||||
}
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "nrf.h"
|
||||
uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
void usb_init(void);
|
||||
void usb_poll(void);
|
||||
void usb_log(char ch, void *param);
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define PIN(port, num) (((port) << 5) | (num))
|
||||
#define PINNO(pin) ((pin) & 31)
|
||||
#define PINPORT(pin) ((pin) >> 5)
|
||||
|
||||
static inline NRF_GPIO_Type *hal_gpio_bank(uint16_t pin) {
|
||||
return PINPORT(pin) == 0 ? NRF_P0 : NRF_P1;
|
||||
}
|
||||
|
||||
static inline void hal_gpio_write(uint16_t pin, bool val) {
|
||||
NRF_GPIO_Type *gpio = hal_gpio_bank(pin);
|
||||
if (val) {
|
||||
gpio->OUTSET = BIT(PINNO(pin));
|
||||
} else {
|
||||
gpio->OUTCLR = BIT(PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
hal_gpio_write(pin, (hal_gpio_bank(pin)->OUT & BIT(PINNO(pin))) == 0);
|
||||
}
|
||||
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
hal_gpio_bank(pin)->PIN_CNF[PINNO(pin)] =
|
||||
(GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
|
||||
(GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos);
|
||||
}
|
||||
|
||||
static inline void hal_system_init(void) {
|
||||
extern uint32_t __isr_vector[];
|
||||
SCB->VTOR = (uint32_t) __isr_vector; // Application vectors above the bootloader
|
||||
__DSB();
|
||||
__ISB();
|
||||
__enable_irq();
|
||||
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
|
||||
NRF_CLOCK->TASKS_HFCLKSTART = 1;
|
||||
while (!NRF_CLOCK->EVENTS_HFCLKSTARTED) (void) 0;
|
||||
SystemCoreClockUpdate();
|
||||
SysTick_Config(SystemCoreClock / 1000);
|
||||
}
|
||||
|
||||
static inline void hal_rng_init(void) {
|
||||
NRF_RNG->CONFIG = 1;
|
||||
NRF_RNG->TASKS_START = 1;
|
||||
}
|
||||
|
||||
static inline uint32_t hal_rng_read(void) {
|
||||
uint32_t value = 0;
|
||||
for (unsigned i = 0; i < 4; i++) {
|
||||
while (!NRF_RNG->EVENTS_VALRDY) (void) 0;
|
||||
value |= (uint32_t) NRF_RNG->VALUE << (i * 8);
|
||||
NRF_RNG->EVENTS_VALRDY = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* nRF52840 on the XIAO nRF52840, with the Adafruit UF2 bootloader. Flash:
|
||||
* MBR and SoftDevice S140 below 0x27000, the bootloader from 0xED000 up: the
|
||||
* application in between. The SoftDevice is not enabled, so all RAM is ours,
|
||||
* except the first 24k it would take: kept free, as the Arduino core does */
|
||||
ENTRY(Reset_Handler);
|
||||
MEMORY {
|
||||
flash(rx) : ORIGIN = 0x00027000, LENGTH = 0xC6000
|
||||
sram(rwx) : ORIGIN = 0x20006000, LENGTH = 0x3A000
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
.vectors : { KEEP(*(.isr_vector)) . = ALIGN(4); } > flash
|
||||
.text : { *(.text* .text.*) } > flash
|
||||
.rodata : { *(.rodata*) . = ALIGN(4); } > flash
|
||||
|
||||
.data : {
|
||||
_sdata = .;
|
||||
*(.first_data)
|
||||
*(.iram .iram.*) /* Mongoose MG_IRAM functions */
|
||||
*(.data SORT(.data.*))
|
||||
. = ALIGN(4);
|
||||
_edata = .;
|
||||
} > sram AT > flash
|
||||
__data_start = _sdata;
|
||||
__data_end = _edata;
|
||||
__data_load_start = LOADADDR(.data);
|
||||
/* Unused data regions expected by Nordic's standard startup. */
|
||||
__sdata_start = _edata;
|
||||
__sdata_end = _edata;
|
||||
__sdata_load_start = _edata;
|
||||
__tdata_start = _edata;
|
||||
__tdata_end = _edata;
|
||||
__tdata_load_start = _edata;
|
||||
__fast_start = _edata;
|
||||
__fast_end = _edata;
|
||||
__fast_load_start = _edata;
|
||||
|
||||
.bss : {
|
||||
_sbss = .;
|
||||
*(.bss SORT(.bss.*) COMMON)
|
||||
. = ALIGN(4);
|
||||
_ebss = .;
|
||||
} > sram
|
||||
__bss_start__ = _sbss;
|
||||
__bss_end__ = _ebss;
|
||||
__tbss_start__ = _ebss;
|
||||
__tbss_end__ = _ebss;
|
||||
__sbss_start__ = _ebss;
|
||||
__sbss_end__ = _ebss;
|
||||
|
||||
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* Start of heap, see _sbrk() in hal.c */
|
||||
|
||||
/* Nordic startup reserves its default 16 KiB stack at the top of RAM. */
|
||||
.stack ORIGIN(sram) + LENGTH(sram) - 0x4000 (NOLOAD) : {
|
||||
KEEP(*(.stack))
|
||||
} > sram
|
||||
ASSERT(_end <= __StackLimit, "RAM sections overlap the stack")
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define LED1 PIN(0, 26) // P0.26, user LED, active low
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
usb_log(ch, param);
|
||||
}
|
||||
|
||||
static void blink_task(void) {
|
||||
static uint64_t blink_timer = 0;
|
||||
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
|
||||
hal_gpio_toggle(LED1);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||
uint32_t r = hal_rng_read();
|
||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
|
||||
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
|
||||
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
|
||||
#define TLS_CERT \
|
||||
"-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \
|
||||
"FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \
|
||||
"MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \
|
||||
"SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \
|
||||
"uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \
|
||||
"MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \
|
||||
"BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \
|
||||
"lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \
|
||||
"BCjX++LI\n" \
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
#define TLS_KEY \
|
||||
"-----BEGIN EC PRIVATE KEY-----\n" \
|
||||
"MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \
|
||||
"AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \
|
||||
"LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \
|
||||
"-----END EC PRIVATE KEY-----\n"
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
|
||||
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||
__asm volatile("udf #0"); // Trigger HardFault
|
||||
} else {
|
||||
mg_http_reply(c, 200, "",
|
||||
"Hi from Mongoose!\n"
|
||||
"tick: %llu\n"
|
||||
"ram used: %zu\n"
|
||||
"ram free: %zu\n",
|
||||
hal_get_tick(), hal_ram_used(), hal_ram_free());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
MG_HEALTH_INIT();
|
||||
|
||||
hal_system_init();
|
||||
usb_init();
|
||||
mg_log_set_fn(log_fn, NULL);
|
||||
hal_rng_init();
|
||||
hal_gpio_write(LED1, true);
|
||||
hal_gpio_output(LED1);
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_mqtt_init(&mgr);
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free()));
|
||||
MG_INFO(("Crash record: %M", mg_print_crash_record));
|
||||
|
||||
for (;;) {
|
||||
usb_poll();
|
||||
mg_mgr_poll(&mgr, 0);
|
||||
mg_mqtt_poll(&mgr);
|
||||
blink_task();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "nrf.h"
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_NRF52 1
|
||||
#define MG_ENABLE_TCPIP_TCPRTX 1
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
#define MG_ENABLE_CUSTOM_RANDOM 1
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MQTT_CLIENT_ID "xiao-nrf52840"
|
||||
|
||||
#ifndef MQTT_SERVER_URL
|
||||
#define MQTT_SERVER_URL "mqtt://broker.emqx.io:1883"
|
||||
#endif
|
||||
|
||||
// Stable, locally administered MAC derived from the factory device ID.
|
||||
#define MG_SET_MAC_ADDRESS(mac) \
|
||||
do { \
|
||||
uint32_t id_[2] = {NRF_FICR->DEVICEID[0], NRF_FICR->DEVICEID[1]}; \
|
||||
uint8_t hash_[32]; \
|
||||
mg_sha256(hash_, (uint8_t *) id_, sizeof(id_)); \
|
||||
memcpy(mac, hash_, 6); \
|
||||
(mac)[0] = ((mac)[0] & 0xfc) | 2; \
|
||||
} while (0)
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#define CFG_TUSB_MCU OPT_MCU_NRF5X
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#define CFG_TUD_CDC 1
|
||||
#define CFG_TUD_CDC_RX_BUFSIZE 256
|
||||
#define CFG_TUD_CDC_TX_BUFSIZE 4096
|
||||
#define CFG_TUD_CDC_EP_BUFSIZE 64
|
||||
@@ -0,0 +1,99 @@
|
||||
#include "hal.h"
|
||||
#include "tusb.h"
|
||||
extern void tusb_hal_nrf_power_event(uint32_t event);
|
||||
void USBD_IRQHandler(void) {
|
||||
tud_int_handler(0);
|
||||
}
|
||||
void POWER_CLOCK_IRQHandler(void) {
|
||||
if (NRF_POWER->EVENTS_USBDETECTED) {
|
||||
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||
tusb_hal_nrf_power_event(0);
|
||||
}
|
||||
if (NRF_POWER->EVENTS_USBREMOVED) {
|
||||
NRF_POWER->EVENTS_USBREMOVED = 0;
|
||||
tusb_hal_nrf_power_event(1);
|
||||
}
|
||||
if (NRF_POWER->EVENTS_USBPWRRDY) {
|
||||
NRF_POWER->EVENTS_USBPWRRDY = 0;
|
||||
tusb_hal_nrf_power_event(2);
|
||||
}
|
||||
}
|
||||
void usb_init(void) {
|
||||
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk |
|
||||
POWER_INTENSET_USBREMOVED_Msk |
|
||||
POWER_INTENSET_USBPWRRDY_Msk;
|
||||
NVIC_SetPriority(POWER_CLOCK_IRQn, 3);
|
||||
NVIC_SetPriority(USBD_IRQn, 3);
|
||||
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk)
|
||||
tusb_hal_nrf_power_event(0);
|
||||
if (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_OUTPUTRDY_Msk)
|
||||
tusb_hal_nrf_power_event(2);
|
||||
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||
NRF_POWER->EVENTS_USBPWRRDY = 0;
|
||||
NRF_POWER->EVENTS_USBREMOVED = 0;
|
||||
NVIC_ClearPendingIRQ(POWER_CLOCK_IRQn);
|
||||
tusb_init();
|
||||
NVIC_EnableIRQ(POWER_CLOCK_IRQn);
|
||||
}
|
||||
void usb_poll(void) {
|
||||
tud_task();
|
||||
tud_cdc_write_flush();
|
||||
}
|
||||
void usb_log(char ch, void *param) {
|
||||
(void) param;
|
||||
if (tud_cdc_connected()) tud_cdc_write_char(ch);
|
||||
}
|
||||
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) {
|
||||
cdc_line_coding_t lc;
|
||||
(void) rts;
|
||||
tud_cdc_n_get_line_coding(itf, &lc);
|
||||
if (!dtr && lc.bit_rate == 1200) {
|
||||
NRF_POWER->GPREGRET = 0x57;
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
}
|
||||
static const tusb_desc_device_t s_dev = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = TUSB_CLASS_MISC,
|
||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||
.bMaxPacketSize0 = 64,
|
||||
.idVendor = 0xcafe,
|
||||
.idProduct = 0x4001,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = 1,
|
||||
.iProduct = 2,
|
||||
.iSerialNumber = 3,
|
||||
.bNumConfigurations = 1};
|
||||
uint8_t const *tud_descriptor_device_cb(void) {
|
||||
return (const uint8_t *) &s_dev;
|
||||
}
|
||||
static const uint8_t s_cfg[] = {
|
||||
TUD_CONFIG_DESCRIPTOR(1, 2, 0, TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN, 0,
|
||||
100),
|
||||
TUD_CDC_DESCRIPTOR(0, 0, 0x81, 8, 0x02, 0x82, 64)};
|
||||
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
|
||||
(void) index;
|
||||
return s_cfg;
|
||||
}
|
||||
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||
static uint16_t buf[32];
|
||||
const char *strings[] = {"", "Cesanta", "XIAO nRF52840", "mongoose-nrf52840"};
|
||||
size_t n = 0;
|
||||
(void) langid;
|
||||
if (index == 0) {
|
||||
buf[1] = 0x0409;
|
||||
n = 1;
|
||||
} else if (index < 4) {
|
||||
const char *s = strings[index];
|
||||
while (s[n] && n < 31) {
|
||||
buf[n + 1] = s[n];
|
||||
n++;
|
||||
}
|
||||
} else
|
||||
return NULL;
|
||||
buf[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * n + 2));
|
||||
return buf;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_E
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c $(MGDIR)/mongoose.c
|
||||
|
||||
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
|
||||
SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S
|
||||
|
||||
@@ -23,9 +24,13 @@ firmware.bin: firmware.elf
|
||||
arm-none-eabi-objcopy -O binary $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) link.ld Makefile
|
||||
firmware.elf: cmsis_core cmsis_mcxw71 $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mesh-config
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Rebuild when overriding the board address or timing.
|
||||
.PHONY: mesh-config
|
||||
mesh-config:
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// 802.15.4 register setup. Then select the legacy 802.15.4 demodulator and
|
||||
// CCA from RX digital. See PhyHwInit() in NXP ieee_802_15_4 PHY sources,
|
||||
// PhyPacketProcessor.c
|
||||
bool hal_radio_init(void) {
|
||||
void hal_radio_init(void) {
|
||||
uint32_t stat = RFMC_RF2P4GHZ_STAT_RST_STAT_MASK |
|
||||
RFMC_RF2P4GHZ_STAT_LP_ACK_STAT_MASK;
|
||||
size_t i, n = 1000000;
|
||||
@@ -20,7 +20,10 @@ bool hal_radio_init(void) {
|
||||
RFMC_RF2P4GHZ_CTRL_RST_MASK); // Release radio reset, no low power
|
||||
while ((RFMC->RF2P4GHZ_STAT & stat) != 0 ||
|
||||
(RFMC->XO_STAT & RFMC_XO_STAT_XTAL_RDY_MASK) == 0) {
|
||||
if (--n == 0) return false; // Radio did not wake up
|
||||
if (--n == 0) {
|
||||
MG_ERROR(("Radio init failed")); // Radio did not wake up
|
||||
return;
|
||||
}
|
||||
}
|
||||
RADIO_CTRL->LL_CTRL = RADIO_CTRL_LL_CTRL_ACTIVE_LL_MASK;
|
||||
RFMC->RF2P4GHZ_CTRL |=
|
||||
@@ -31,17 +34,17 @@ bool hal_radio_init(void) {
|
||||
XCVR_MISC->XCVR_CTRL &= ~XCVR_MISC_XCVR_CTRL_DEMOD_SEL_MASK;
|
||||
XCVR_MISC->XCVR_CTRL |= XCVR_MISC_XCVR_CTRL_DEMOD_SEL(2);
|
||||
XCVR_ZBDEMOD->CCA_LQI_SRC |= XCVR_ZBDEMOD_CCA_LQI_SRC_CCA1_FROM_RX_DIG(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Random bytes for Mongoose, MG_ENABLE_CUSTOM_RANDOM. Entropy is radio noise:
|
||||
// receiver RSSI, sampled while the radio receives, mixed with SysTick by
|
||||
// SHA-256. Good enough for random addresses and nonces, not a certified TRNG
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
static uint32_t counter;
|
||||
uint8_t digest[32], *p = (uint8_t *) buf;
|
||||
// Random 32 bits, for mg_random(). Entropy is radio noise: receiver RSSI,
|
||||
// sampled while the radio receives, mixed with SysTick by SHA-256. One hash
|
||||
// fills a pool of 8 words. Good enough for random addresses and nonces, not
|
||||
// a certified TRNG
|
||||
uint32_t hal_rng_read(void) {
|
||||
static uint32_t counter, pool[8];
|
||||
static size_t avail;
|
||||
size_t i, n;
|
||||
while (len > 0) {
|
||||
if (avail == 0) {
|
||||
mg_sha256_ctx ctx;
|
||||
mg_sha256_init(&ctx);
|
||||
counter++;
|
||||
@@ -51,11 +54,10 @@ bool mg_random(void *buf, size_t len) {
|
||||
mg_sha256_update(&ctx, (uint8_t *) sample, sizeof(sample));
|
||||
for (n = 0; n < 50; n++) __NOP(); // Let the RSSI update
|
||||
}
|
||||
mg_sha256_final(digest, &ctx);
|
||||
n = len < sizeof(digest) ? len : sizeof(digest);
|
||||
memcpy(p, digest, n), p += n, len -= n;
|
||||
mg_sha256_final((uint8_t *) pool, &ctx);
|
||||
avail = sizeof(pool) / sizeof(pool[0]);
|
||||
}
|
||||
return true;
|
||||
return pool[--avail];
|
||||
}
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
@@ -81,6 +83,26 @@ uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
}
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
|
||||
uint32_t SystemCoreClock;
|
||||
|
||||
// Called by the startup code before .data and .bss are initialised, so it
|
||||
|
||||
@@ -17,11 +17,14 @@ uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
bool hal_radio_init(void); // Set up the 2.4 GHz transceiver for 802.15.4
|
||||
void hal_radio_init(void); // Set up the 2.4 GHz transceiver for 802.15.4
|
||||
uint32_t hal_rng_read(void); // Random 32 bits, radio noise. See hal.c
|
||||
extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init()
|
||||
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define PIN(port, num) ((((port) - 'A') << 8) | (num))
|
||||
#define PINNO(pin) (pin & 255)
|
||||
#define PINPORT(pin) (pin >> 8)
|
||||
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
|
||||
#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz
|
||||
|
||||
@@ -92,15 +95,53 @@ static inline void hal_periph_clock(volatile uint32_t *mrcc, uint32_t src) {
|
||||
__DSB();
|
||||
}
|
||||
|
||||
// LPUART1 is wired to the MCU-Link debug probe USB serial port.
|
||||
// TX = PTC3, RX = PTC2, both pin mux alternative 3. See board.h, pin_mux.c
|
||||
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
|
||||
// PORT, the pin multiplexer, of a pin. Ports A..C
|
||||
static inline PORT_Type *hal_pin_port(uint16_t pin) {
|
||||
PORT_Type *ports[] = {PORTA, PORTB, PORTC};
|
||||
return ports[PINPORT(pin)];
|
||||
}
|
||||
|
||||
// GPIO of a pin. Ports A..C
|
||||
static inline GPIO_Type *hal_pin_gpio(uint16_t pin) {
|
||||
GPIO_Type *gpios[] = {GPIOA, GPIOB, GPIOC};
|
||||
return gpios[PINPORT(pin)];
|
||||
}
|
||||
|
||||
// Route a pin to function mux: 1 GPIO, others see the datasheet pinout.
|
||||
// Clocks its PORT: MRCC_PORTA..C are consecutive
|
||||
static inline void hal_pin_mux(uint16_t pin, uint32_t mux) {
|
||||
hal_periph_clock(&MRCC->MRCC_PORTA + PINPORT(pin), 0);
|
||||
hal_pin_port(pin)->PCR[PINNO(pin)] = PORT_PCR_MUX(mux);
|
||||
}
|
||||
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
hal_pin_mux(pin, 1);
|
||||
hal_periph_clock(&MRCC->MRCC_GPIOA + PINPORT(pin), 0); // Consecutive, too
|
||||
hal_pin_gpio(pin)->PDDR |= BIT(PINNO(pin));
|
||||
}
|
||||
|
||||
static inline void hal_gpio_write(uint16_t pin, bool val) {
|
||||
if (val) {
|
||||
hal_pin_gpio(pin)->PSOR = BIT(PINNO(pin));
|
||||
} else {
|
||||
hal_pin_gpio(pin)->PCOR = BIT(PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
hal_pin_gpio(pin)->PTOR = BIT(PINNO(pin));
|
||||
}
|
||||
|
||||
// LPUART1, on tx and rx pins, pin mux alternative 3. On FRDM-MCXW71 it is
|
||||
// wired to the MCU-Link debug probe USB serial port: TX PTC3, RX PTC2, see
|
||||
// board.h, pin_mux.c
|
||||
static inline void hal_uart_init(LPUART_Type *uart, uint16_t tx, uint16_t rx,
|
||||
unsigned long baud) {
|
||||
uint32_t src = MRCC_SRC_FRO192M, freq = hal_firc_freq();
|
||||
uint32_t best_osr = 16, best_sbr = 1, best_err = UINT32_MAX, osr;
|
||||
if (freq == 0) src = MRCC_SRC_FRO6M, freq = SIRC_FREQUENCY; // FIRC off
|
||||
hal_periph_clock(&MRCC->MRCC_PORTC, 0);
|
||||
PORTC->PCR[2] = PORT_PCR_MUX(3); // RX
|
||||
PORTC->PCR[3] = PORT_PCR_MUX(3); // TX
|
||||
hal_pin_mux(rx, 3);
|
||||
hal_pin_mux(tx, 3);
|
||||
hal_periph_clock(&MRCC->MRCC_LPUART1, src);
|
||||
// Find the oversampling ratio (4..32) that gives the lowest baud rate error
|
||||
for (osr = 4; osr <= 32; osr++) {
|
||||
@@ -141,6 +182,11 @@ static inline void hal_wdog_disable(void) {
|
||||
while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0;
|
||||
}
|
||||
|
||||
// Random numbers come from radio noise, see hal_rng_read(): nothing to set
|
||||
// up here. The radio must be running, see hal_radio_init()
|
||||
static inline void hal_rng_init(void) {
|
||||
}
|
||||
|
||||
static inline void hal_sys_init(void) {
|
||||
SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU
|
||||
hal_wdog_disable();
|
||||
|
||||
@@ -33,6 +33,9 @@ SECTIONS {
|
||||
_ebss = .;
|
||||
} > sram
|
||||
|
||||
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* Start of heap, see _sbrk() in hal.c */
|
||||
}
|
||||
@@ -1,24 +1,42 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// IPv4 over IEEE 802.15.4 on FRDM-MCXW71, Mongoose built-in TCP/IP stack.
|
||||
// The radio carries Ethernet frames, channel 15, PAN ID 0xabcd. With the
|
||||
// FRDM-RW612 eth_radio_bridge tutorial, gets an address from the LAN's DHCP
|
||||
// server and is reachable from the LAN. Serves pages on ports 80 and 443
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#ifndef UART_DEBUG
|
||||
#define UART_DEBUG LPUART1 // MCU-Link USB serial port
|
||||
#define UART_DEBUG_TX_PIN PIN('C', 3)
|
||||
#define UART_DEBUG_RX_PIN PIN('C', 2)
|
||||
#endif
|
||||
|
||||
#define LED1 PIN('A', 19)
|
||||
#define LED2 PIN('A', 20)
|
||||
#define LED3 PIN('A', 21)
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
hal_uart_write_buf(param, &ch, 1);
|
||||
}
|
||||
|
||||
static void blink_task(void) {
|
||||
static uint64_t blink_timer = 0;
|
||||
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
|
||||
hal_gpio_toggle(LED2);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||
uint32_t r = hal_rng_read();
|
||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
|
||||
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
|
||||
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
|
||||
@@ -70,16 +88,21 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
hal_clock_init();
|
||||
hal_uart_init(UART_DEBUG, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
MG_HEALTH_INIT();
|
||||
|
||||
if (!hal_radio_init()) MG_ERROR(("Radio init failed"));
|
||||
hal_clock_init();
|
||||
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
hal_rng_init();
|
||||
hal_radio_init();
|
||||
hal_gpio_output(LED1), hal_gpio_write(LED1, true); // Active low
|
||||
hal_gpio_output(LED2), hal_gpio_write(LED2, true); // Active low
|
||||
hal_gpio_output(LED3), hal_gpio_write(LED3, true); // Active low
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0:80", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0:443", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_mqtt_init(&mgr);
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
@@ -89,6 +112,7 @@ int main(void) {
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 0);
|
||||
mg_mqtt_poll(&mgr);
|
||||
blink_task();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
#include "MCXW716C.h"
|
||||
|
||||
// See https://mongoose.ws/docs/getting-started/build-environment/
|
||||
// See https://mongoose.ws/docs/getting-started/build-options/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MQTT_CLIENT_ID "w71" // For tutorials/mqtt/mqtt-client
|
||||
#define MG_OTA MG_OTA_MCXW71
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
@@ -21,3 +22,12 @@
|
||||
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
|
||||
(mac)[4] = b_[4], (mac)[5] = b_[5]; \
|
||||
} while (0)
|
||||
|
||||
// Crash report support. Health record lives in the .mg_health region, see link.ld
|
||||
#define MG_ENABLE_HEALTH 1
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
|
||||
// mdash.net device management service support
|
||||
#define MG_ENABLE_MDASH 0
|
||||
#define MG_MDASH_KEY "..." // Set this - mDash device token
|
||||
#define MG_FIRMWARE_VERSION "w71.1.0.0"
|
||||
+7
-2
@@ -17,6 +17,7 @@ CFLAGS += -mcpu=cortex-m33+nodsp -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CF
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c radio.c startup.c flexspi.c flash_config.c $(MGDIR)/mongoose.c
|
||||
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_clock.c
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_trng.c
|
||||
SOURCES += cmsis_rw612/devices/RW612/drivers/fsl_reset.c
|
||||
@@ -35,9 +36,13 @@ firmware.bin: firmware.elf
|
||||
arm-none-eabi-objcopy -O binary $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) link.ld Makefile mongoose_config.h
|
||||
firmware.elf: cmsis_core cmsis_rw612 $(RADIO_FW) $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mongoose_config.h mesh-config
|
||||
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Rebuild when overriding the board address or timing.
|
||||
.PHONY: mesh-config
|
||||
mesh-config:
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
@@ -56,7 +61,7 @@ show_symbols_sorted_by_size: firmware.bin
|
||||
arm-none-eabi-readelf -sW firmware.elf | sed 1,3d | sed 's,^.*: ,,' | tr -s ' ' | cut -d ' ' -f 2,7 | sort -nk1 | grep -v '^0 '
|
||||
|
||||
flash: firmware.bin
|
||||
(echo 'loadfile firmware.elf'; echo 'reset'; echo 'exit';) | JLinkExe -device RW612 -if SWD -speed 4000 -autoconnect 1 -NoGui 1 -ExitOnError 1
|
||||
(echo 'loadfile firmware.elf'; echo 'reset'; echo 'go'; echo 'exit';) | JLinkExe -device RW612 -if SWD -speed 4000 -autoconnect 1 -NoGui 1 -ExitOnError 1
|
||||
# JRun --nogui --device RW612 --if SWD firmware.elf
|
||||
|
||||
clean:
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+20
@@ -26,6 +26,26 @@ uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
};
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
|
||||
uint32_t SystemCoreClock = SYS_FREQUENCY;
|
||||
int hal_radio_err; // Result of hal_radio_init()
|
||||
void SystemInit(void) { // Called automatically by startup code
|
||||
+12
-6
@@ -152,16 +152,22 @@ static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
|
||||
#endif
|
||||
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) {
|
||||
if (uart == USART0) {
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
|
||||
uint16_t tx_pin, uint16_t rx_pin) {
|
||||
if (uart == USART0 && tx_pin == PIN(0, 3) &&
|
||||
rx_pin == PIN(0, 2)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
|
||||
} else if (uart == USART1) {
|
||||
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
|
||||
rx_pin == PIN(0, 9)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
|
||||
} else if (uart == USART2) {
|
||||
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
|
||||
rx_pin == PIN(0, 13)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
|
||||
} else if (uart == USART3) {
|
||||
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
|
||||
rx_pin == PIN(0, 24)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
|
||||
} else if (uart == USART14) {
|
||||
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
|
||||
rx_pin == PIN(1, 25)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
|
||||
} else {
|
||||
return;
|
||||
File renamed without changes.
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define LED_RADIO PIN(0, 12) // Green, active low
|
||||
#define LED_LAN PIN(0, 0) // Blue, active low
|
||||
#define LED_RED PIN(0, 1) // Red, active low
|
||||
|
||||
static uint64_t s_radio_led_until;
|
||||
|
||||
// Cumulative totals. Up: radio -> LAN; down: LAN -> radio queue.
|
||||
static uint64_t s_frames_up, s_frames_down, s_bytes_up, s_bytes_down;
|
||||
static uint64_t s_radio_sent, s_radio_received;
|
||||
static size_t (*s_radio_tx)(struct mg_tcpip_if *, const void *, size_t);
|
||||
|
||||
static size_t radio_tx(struct mg_tcpip_if *ifp, const void *buf, size_t len) {
|
||||
size_t sent = s_radio_tx(ifp, buf, len);
|
||||
if (sent != 0) s_radio_sent++;
|
||||
return sent;
|
||||
}
|
||||
|
||||
static size_t eth_up(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
|
||||
size_t sent = mg_tcpip_driver_rw612.tx(buf, len, ifp);
|
||||
if (sent != 0) s_frames_up++, s_bytes_up += sent;
|
||||
return sent;
|
||||
}
|
||||
|
||||
static size_t eth_down(struct mg_mesh_node *node, const void *buf, size_t len) {
|
||||
size_t queued = mg_mesh_default.tx(node, buf, len);
|
||||
if (queued != 0) s_frames_down++, s_bytes_down += queued;
|
||||
return queued;
|
||||
}
|
||||
|
||||
static void radio_rx(struct mg_mesh_node *node, void *buf, size_t len) {
|
||||
s_radio_received++;
|
||||
s_radio_led_until = mg_millis() + 2;
|
||||
mg_mesh_default.rx(node, buf, len);
|
||||
}
|
||||
|
||||
static void log_fn(char ch, void *arg) {
|
||||
hal_uart_write_buf(arg, &ch, 1);
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
static struct mg_tcpip_driver_rw612_data eth_data;
|
||||
static struct mg_tcpip_if eth;
|
||||
static struct mg_tcpip_driver eth_driver;
|
||||
static char eth_queue[8192];
|
||||
static struct mg_tcpip_driver_rw612w_data radio_data;
|
||||
static struct mg_tcpip_if radio;
|
||||
static struct mg_mesh mesh;
|
||||
uint32_t lan_received = 0;
|
||||
uint64_t lan_led_until = 0;
|
||||
uint64_t link_timer = 0, stats_timer = 0;
|
||||
|
||||
hal_clock_init();
|
||||
hal_rng_init();
|
||||
hal_gpio_output(LED_RADIO);
|
||||
hal_gpio_output(LED_LAN);
|
||||
hal_gpio_output(LED_RED);
|
||||
hal_gpio_write(LED_RADIO, true);
|
||||
hal_gpio_write(LED_LAN, true);
|
||||
hal_gpio_write(LED_RED, true);
|
||||
hal_uart_init(USART3, 115200, PIN(0, 26), PIN(0, 24));
|
||||
mg_log_set_fn(log_fn, USART3);
|
||||
hal_ethernet_init();
|
||||
|
||||
mesh = mg_mesh_default;
|
||||
mesh.tx = eth_down;
|
||||
mesh.rx = radio_rx; // Observe radio reception without modifying the driver.
|
||||
radio_data.strategy = &mesh;
|
||||
radio.driver = &mg_tcpip_driver_rw612w;
|
||||
radio.driver_data = &radio_data;
|
||||
if (!radio.driver->init(&radio)) return 1;
|
||||
s_radio_tx = radio_data.mesh->tx;
|
||||
radio_data.mesh->tx = radio_tx;
|
||||
|
||||
eth_data.mdc_cr = MG_DRIVER_MDC_CR;
|
||||
eth_data.mdc_holdtime = MG_DRIVER_MDC_HOLDTIME;
|
||||
eth_data.phy_addr = MG_TCPIP_PHY_ADDR;
|
||||
eth_driver = mg_tcpip_driver_rw612;
|
||||
eth_driver.tx = eth_up;
|
||||
eth.driver = ð_driver;
|
||||
eth.driver_data = ð_data;
|
||||
memcpy(eth.mac, radio.mac, 6);
|
||||
mg_queue_init(ð.recv_queue, eth_queue, sizeof(eth_queue));
|
||||
if (!eth.driver->init(ð)) return 1;
|
||||
|
||||
ENET->RCR |= ENET_RCR_PROM_MASK; // Receive frames addressed to radio nodes.
|
||||
radio_data.mesh->lan = ð
|
||||
radio_data.mesh->next_tick = 0;
|
||||
|
||||
MG_INFO(("Ethernet/radio bridge, channel %u", MG_802154_CHANNEL));
|
||||
stats_timer = mg_millis() + 5000;
|
||||
for (;;) {
|
||||
uint64_t now = mg_millis();
|
||||
if (mg_timer_expired(&link_timer, 100, mg_millis())) {
|
||||
eth.state = eth.driver->poll(ð, true) ? MG_TCPIP_STATE_UP
|
||||
: MG_TCPIP_STATE_DOWN;
|
||||
}
|
||||
radio.driver->poll(&radio, false);
|
||||
if (mg_timer_expired(&stats_timer, 5000, now)) {
|
||||
MG_INFO(
|
||||
("ETH frames up/down %llu/%llu, bytes up/down "
|
||||
"%llu/%llu, radio frames sent/rcvd %llu/%llu",
|
||||
s_frames_up, s_frames_down, s_bytes_up, s_bytes_down, s_radio_sent,
|
||||
s_radio_received));
|
||||
}
|
||||
if (eth.nrecv != lan_received) {
|
||||
lan_received = eth.nrecv;
|
||||
lan_led_until = now + 2;
|
||||
}
|
||||
hal_gpio_write(LED_RADIO, now >= s_radio_led_until);
|
||||
hal_gpio_write(LED_LAN, now >= lan_led_until);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
// See https://mongoose.ws/docs/getting-started/build-environment/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_RW612 1 // Ethernet
|
||||
#define MG_ENABLE_DRIVER_RW612W 1 // Mesh bridge over raw 802.15.4
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
|
||||
|
||||
// The bridge initializes and polls its two L2 interfaces directly.
|
||||
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,101 +0,0 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Ethernet <-> IEEE 802.15.4 radio bridge. Radio frames carry Ethernet frames
|
||||
// split into pieces, see mg_l2_802154_tx(). Radio nodes, e.g. the
|
||||
// FRDM-MCXW71 minimal tutorial, are on the Ethernet LAN: they get IPv4
|
||||
// addresses from its DHCP server. The bridge itself has no IP address.
|
||||
// Everything from the radio goes to Ethernet. From Ethernet, only broadcasts
|
||||
// and frames to radio nodes go to the radio, it is much slower
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#define UART_DEBUG USART3
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
hal_uart_write_buf(param, &ch, 1);
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
// MAC addresses of radio nodes, learned from frames they send
|
||||
static uint8_t s_nodes[8][6];
|
||||
static size_t s_next;
|
||||
|
||||
// Remember a radio node's MAC address, if new
|
||||
static void learn(const uint8_t *mac) {
|
||||
size_t i, n = sizeof(s_nodes) / sizeof(s_nodes[0]);
|
||||
for (i = 0; i < n && memcmp(s_nodes[i], mac, 6) != 0; i++) (void) 0;
|
||||
if (i < n) return;
|
||||
memcpy(s_nodes[s_next], mac, 6);
|
||||
s_next = (s_next + 1) % n;
|
||||
MG_INFO(("Radio node %M", mg_print_mac, mac));
|
||||
}
|
||||
|
||||
// Is an Ethernet frame for the radio: broadcast, or to a radio node
|
||||
static bool for_radio(const uint8_t *dst) {
|
||||
size_t i, n = sizeof(s_nodes) / sizeof(s_nodes[0]);
|
||||
if (memcmp(dst, "\xff\xff\xff\xff\xff\xff", 6) == 0) return true;
|
||||
for (i = 0; i < n && memcmp(s_nodes[i], dst, 6) != 0; i++) (void) 0;
|
||||
return i < n;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
static struct mg_tcpip_driver_rw612_data eth_data = {
|
||||
.mdc_cr = MG_DRIVER_MDC_CR, .phy_addr = MG_TCPIP_PHY_ADDR};
|
||||
static struct mg_l2_802154_data radio_data = {
|
||||
.channel = MG_L2_802154_CHANNEL, .pan_id = MG_L2_802154_PAN_ID};
|
||||
static struct mg_tcpip_if eth = {.driver = &mg_tcpip_driver_rw612,
|
||||
.driver_data = ð_data};
|
||||
static struct mg_tcpip_if radio = {.driver = &mg_tcpip_driver_rw612_154,
|
||||
.driver_data = &radio_data};
|
||||
static char eth_queue[8192]; // Filled by the Ethernet IRQ handler
|
||||
static uint8_t frame[1540];
|
||||
uint8_t uid[FSL_OCOTP_UID_LENGTH], digest[32];
|
||||
uint32_t uid_len = sizeof(uid);
|
||||
uint64_t timer = 0;
|
||||
char *p;
|
||||
size_t n;
|
||||
|
||||
hal_clock_init();
|
||||
hal_uart_init(UART_DEBUG, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
if (hal_radio_err) {
|
||||
MG_ERROR(("Radio firmware failed to start, step %d", hal_radio_err));
|
||||
}
|
||||
|
||||
// MAC addresses made from the chip unique ID
|
||||
memset(uid, 0, sizeof(uid));
|
||||
OCOTP_ReadUniqueID(uid, &uid_len);
|
||||
mg_sha256(digest, uid, sizeof(uid));
|
||||
memcpy(eth.mac, digest, 6);
|
||||
memcpy(radio.mac, digest + 6, 6);
|
||||
eth.mac[0] = radio.mac[0] = 0x02; // Locally administered, unicast
|
||||
|
||||
hal_ethernet_init();
|
||||
mg_queue_init(ð.recv_queue, eth_queue, sizeof(eth_queue));
|
||||
if (!eth.driver->init(ð)) MG_ERROR(("Ethernet init failed"));
|
||||
if (!radio.driver->init(&radio)) MG_ERROR(("Radio init failed"));
|
||||
ENET->RCR |= ENET_RCR_PROM_MASK; // Receive frames for radio nodes, too
|
||||
MG_INFO(("Bridge started, radio channel %d", radio_data.channel));
|
||||
|
||||
for (;;) {
|
||||
if ((n = mg_queue_next(ð.recv_queue, &p)) > 0) { // Ethernet -> radio
|
||||
if (n >= 14 && for_radio((uint8_t *) p)) radio.driver->tx(p, n, &radio);
|
||||
mg_queue_del(ð.recv_queue, n);
|
||||
}
|
||||
if ((n = radio.driver->rx(frame, sizeof(frame), &radio)) >= 14) {
|
||||
learn(frame + 6); // Radio -> Ethernet
|
||||
eth.driver->tx(frame, n, ð);
|
||||
}
|
||||
if (mg_timer_expired(&timer, 1000, mg_millis())) { // Ethernet link
|
||||
bool up = eth.driver->poll(ð, true);
|
||||
eth.state = up ? MG_TCPIP_STATE_UP : MG_TCPIP_STATE_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// See https://mongoose.ws/docs/getting-started/build-environment/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_RW612 1 // Ethernet
|
||||
#define MG_ENABLE_DRIVER_RW612_154 1 // 802.15.4 radio
|
||||
#define MG_ENABLE_TCPIP_DRIVER_INIT 0 // main.c drives both, no TCP/IP stack
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
@@ -147,16 +147,22 @@ static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
#define MG_HAL_SYSTICK MG_HAL_SYSTICK_NONE
|
||||
#endif
|
||||
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud) {
|
||||
if (uart == USART0) {
|
||||
static inline void hal_uart_init(USART_Type *uart, unsigned long baud,
|
||||
uint16_t tx_pin, uint16_t rx_pin) {
|
||||
if (uart == USART0 && tx_pin == PIN(0, 3) &&
|
||||
rx_pin == PIN(0, 2)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC0_USART_DATA);
|
||||
} else if (uart == USART1) {
|
||||
} else if (uart == USART1 && tx_pin == PIN(0, 8) &&
|
||||
rx_pin == PIN(0, 9)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC1_USART_DATA);
|
||||
} else if (uart == USART2) {
|
||||
} else if (uart == USART2 && tx_pin == PIN(0, 14) &&
|
||||
rx_pin == PIN(0, 13)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC2_USART_DATA);
|
||||
} else if (uart == USART3) {
|
||||
} else if (uart == USART3 && tx_pin == PIN(0, 26) &&
|
||||
rx_pin == PIN(0, 24)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC3_USART_DATA);
|
||||
} else if (uart == USART14) {
|
||||
} else if (uart == USART14 && tx_pin == PIN(1, 24) &&
|
||||
rx_pin == PIN(1, 25)) {
|
||||
IO_MUX_SetPinMux(IO_MUX_FC14_USART_DATA);
|
||||
} else {
|
||||
return;
|
||||
|
||||
@@ -96,7 +96,7 @@ int main(void) {
|
||||
hal_gpio_write(LED2, true);
|
||||
hal_gpio_output(LED3);
|
||||
hal_gpio_write(LED3, true);
|
||||
hal_uart_init(UART_DEBUG, 115200);
|
||||
hal_uart_init(UART_DEBUG, 115200, PIN(0, 26), PIN(0, 24));
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
|
||||
hal_ethernet_init();
|
||||
|
||||
@@ -31,5 +31,5 @@ clean:
|
||||
$(RM) build pico_sdk_import.cmake $(SDK_PATH)
|
||||
|
||||
$(SDK_PATH):
|
||||
git clone -c advice.detachedHead=false --quiet --depth 1 -b 2.1.1 https://github.com/raspberrypi/pico-sdk $@
|
||||
git clone -c advice.detachedHead=false --quiet --depth 1 -b 2.2.0 https://github.com/raspberrypi/pico-sdk $@
|
||||
cd $@ && git submodule update --quiet --init
|
||||
@@ -0,0 +1,73 @@
|
||||
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
|
||||
|
||||
MGDIR = ../../../..
|
||||
SDK = silabs_sdk/platform
|
||||
RAIL = $(SDK)/radio/rail_lib
|
||||
|
||||
CFLAGS = -W -Wall -Wextra -Werror -Wshadow # No -Wundef: RAIL headers
|
||||
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
|
||||
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
|
||||
CFLAGS += -I. -I$(MGDIR) -Icmsis_core/CMSIS/Core/Include
|
||||
CFLAGS += -I$(SDK)/Device/SiliconLabs/EFR32MG24/Include -I$(SDK)/common/inc
|
||||
CFLAGS += -I$(RAIL)/common -I$(RAIL)/protocol/ieee802154
|
||||
CFLAGS += -I$(RAIL)/chip/efr32/efr32xg2x
|
||||
CFLAGS += -DEFR32MG24B220F1536IM48
|
||||
CFLAGS += -D__START=main # For startup_efr32mg24.c
|
||||
CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 -mcmse
|
||||
CFLAGS += $(CFLAGS_EXTRA)
|
||||
|
||||
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lm -lgcc -Wl,--gc-sections
|
||||
|
||||
SOURCES = main.c hal.c $(MGDIR)/mongoose.c
|
||||
|
||||
SOURCES += $(MGDIR)/tutorials/mqtt/mqtt-client/mongoose_mqtt.c
|
||||
SOURCES += $(SDK)/Device/SiliconLabs/EFR32MG24/Source/startup_efr32mg24.c
|
||||
SOURCES += $(SDK)/Device/SiliconLabs/EFR32MG24/Source/system_efr32mg24.c
|
||||
|
||||
# RAIL radio library, a binary blob. Simplicity SDK keeps it in Git LFS,
|
||||
# served by Silicon Labs. The oid is the file's sha256, see the LFS pointer
|
||||
# platform/radio/rail_lib/autogen/librail_release/librail_efr32xg24_gcc_release.a
|
||||
SDK_VERSION = v2025.6.3
|
||||
LIBRAIL = librail_efr32xg24_gcc_release.a
|
||||
LIBRAIL_SHA256 = 381b123649b66e9e3bbe6d1363c31ee0457b3274207b837ea49685a3a0a2c387
|
||||
LIBRAIL_URL = https://artifacts.silabs.net/api/lfs/gsdk/object/$(LIBRAIL_SHA256)
|
||||
|
||||
all build: firmware.bin
|
||||
|
||||
firmware.bin: firmware.elf
|
||||
arm-none-eabi-objcopy -O binary $< $@
|
||||
arm-none-eabi-size --format=berkeley $<
|
||||
|
||||
firmware.elf: cmsis_core silabs_sdk $(LIBRAIL) $(SOURCES) $(wildcard *.h) $(MGDIR)/mongoose.h link.ld Makefile mesh-config
|
||||
arm-none-eabi-gcc $(SOURCES) $(LIBRAIL) $(CFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Rebuild when overriding the board address or timing.
|
||||
.PHONY: mesh-config
|
||||
mesh-config:
|
||||
|
||||
cmsis_core:
|
||||
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
|
||||
|
||||
# Device headers, startup code and RAIL headers from Simplicity SDK
|
||||
silabs_sdk:
|
||||
GIT_LFS_SKIP_SMUDGE=1 git clone -q -c advice.detachedHead=false --depth 1 -b $(SDK_VERSION) --filter=blob:none --no-checkout https://github.com/SiliconLabs/simplicity_sdk $@
|
||||
cd $@ && git sparse-checkout set --no-cone /platform/Device/SiliconLabs/EFR32MG24/ /platform/common/inc/ /platform/radio/rail_lib/common/ /platform/radio/rail_lib/protocol/ieee802154/ /platform/radio/rail_lib/chip/efr32/efr32xg2x/
|
||||
cd $@ && GIT_LFS_SKIP_SMUDGE=1 git checkout -q
|
||||
|
||||
$(LIBRAIL):
|
||||
curl -sfL $(LIBRAIL_URL) -o $@.tmp
|
||||
echo "$(LIBRAIL_SHA256) $@.tmp" | shasum -a 256 -c -s
|
||||
mv $@.tmp $@
|
||||
|
||||
# Show all symbols sorted by size
|
||||
show_symbols_sorted_by_size: firmware.bin
|
||||
arm-none-eabi-readelf -sW firmware.elf | sed 1,3d | sed 's,^.*: ,,' | tr -s ' ' | cut -d ' ' -f 2,7 | sort -nk1 | grep -v '^0 '
|
||||
|
||||
# Flash over the on-board CMSIS-DAP probe. pyocd needs the EFR32MG24 pack:
|
||||
# pyocd pack install efr32mg24b220f1536im48
|
||||
# With several probes attached, choose one: make flash PYOCD_FLAGS="-u ID"
|
||||
flash: firmware.bin
|
||||
pyocd flash -t efr32mg24b220f1536im48 $(PYOCD_FLAGS) firmware.elf
|
||||
|
||||
clean:
|
||||
rm -rf firmware.* cmsis_* silabs_* librail_*
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
// Secure Engine data descriptor, sli_se_datatransfer_t in Simplicity SDK
|
||||
struct se_dt {
|
||||
volatile void *volatile data; // Data buffer
|
||||
volatile void *volatile next; // Next descriptor, or 1: last one
|
||||
volatile uint32_t length; // Length in bytes, plus flags
|
||||
};
|
||||
|
||||
#define SE_CMD_TRNG_GET_RANDOM 0x07000000UL // SE mailbox commands
|
||||
#define SE_DT_STOP 1UL // Descriptor flags
|
||||
#define SE_DT_REALIGN 0x20000000UL
|
||||
#define SE_RESPONSE_MASK 0x000F0000UL // RX_HEADER response code, 0: OK
|
||||
|
||||
// Fill buf with n random bytes, n a multiple of 4, from the Secure Engine
|
||||
// TRNG (RM section 11.2.5), through the SE mailbox, as sl_se_get_random()
|
||||
// and sli_se_mailbox_execute_command() in Simplicity SDK. buf and the
|
||||
// descriptor are static: on the stack, at the top of RAM, the SE reported a
|
||||
// bus error. Return false on SE error
|
||||
static bool se_random(uint32_t *buf, uint32_t n) {
|
||||
static struct se_dt out;
|
||||
out.data = buf, out.next = (void *) SE_DT_STOP;
|
||||
out.length = n | SE_DT_REALIGN;
|
||||
while ((SEMAILBOX_HOST->TX_STATUS & SEMAILBOX_TX_STATUS_TXINT) == 0) (void) 0;
|
||||
SEMAILBOX_HOST->TX_HEADER = sizeof(uint32_t) * 5; // 4 words + 1 parameter
|
||||
SEMAILBOX_HOST->FIFO = SE_CMD_TRNG_GET_RANDOM;
|
||||
SEMAILBOX_HOST->FIFO = 0; // No input data
|
||||
SEMAILBOX_HOST->FIFO = (uint32_t) &out; // Output data
|
||||
SEMAILBOX_HOST->FIFO = n; // Parameter: number of bytes
|
||||
while ((SEMAILBOX_HOST->RX_STATUS & SEMAILBOX_RX_STATUS_RXINT) == 0) (void) 0;
|
||||
return (SEMAILBOX_HOST->RX_HEADER & SE_RESPONSE_MASK) == 0;
|
||||
}
|
||||
|
||||
// Random 32 bits, for mg_random(). One SE request fills a pool of 16 words.
|
||||
// If the SE fails 3 times in a row, the pool is zeros: see se_random()
|
||||
uint32_t hal_rng_read(void) {
|
||||
static uint32_t pool[16];
|
||||
static size_t avail;
|
||||
if (avail == 0) {
|
||||
int i;
|
||||
for (i = 0; i < 3 && !se_random(pool, sizeof(pool)); i++) (void) 0;
|
||||
if (i == 3) memset(pool, 0, sizeof(pool));
|
||||
avail = sizeof(pool) / sizeof(pool[0]);
|
||||
}
|
||||
return pool[--avail];
|
||||
}
|
||||
|
||||
// RAIL library needs these Simplicity SDK functions. Implemented as in the
|
||||
// SDK: sl_core_cortexm.c, em_cmu.c, em_emu.c, em_system.c, sl_gpio.c,
|
||||
// pa_conversions_efr32.c. Atomic sections mask IRQs with priority number 3
|
||||
// and above, see hal_radio_init() for the IRQ priorities
|
||||
#define CORE_ATOMIC_BASE_PRIORITY_LEVEL 3
|
||||
|
||||
uint32_t CORE_EnterCritical(void) {
|
||||
uint32_t state = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
return state;
|
||||
}
|
||||
|
||||
void CORE_ExitCritical(uint32_t state) {
|
||||
if (state == 0) __enable_irq(), __ISB();
|
||||
}
|
||||
|
||||
uint32_t CORE_EnterAtomic(void) {
|
||||
uint32_t state = __get_BASEPRI();
|
||||
__set_BASEPRI(CORE_ATOMIC_BASE_PRIORITY_LEVEL << (8U - __NVIC_PRIO_BITS));
|
||||
return state;
|
||||
}
|
||||
|
||||
void CORE_ExitAtomic(uint32_t state) {
|
||||
__set_BASEPRI(state);
|
||||
__ISB();
|
||||
}
|
||||
|
||||
// Clock IDs: bits 0..4 bit number, bits 5..6 register, 1 CLKEN0, 2 CLKEN1
|
||||
void CMU_ClockEnable(uint32_t clock, bool enable) {
|
||||
volatile uint32_t *reg = (clock >> 5) == 1 ? &CMU->CLKEN0
|
||||
: (clock >> 5) == 2 ? &CMU->CLKEN1
|
||||
: NULL;
|
||||
if (reg == NULL) return;
|
||||
if (enable) {
|
||||
*reg |= BIT(clock & 31);
|
||||
} else {
|
||||
*reg &= ~BIT(clock & 31);
|
||||
}
|
||||
}
|
||||
|
||||
void EMU_DCDCSetPFMXTimeoutMaxCtrl(uint32_t value) {
|
||||
bool clk_was_on = (CMU->CLKEN0 & CMU_CLKEN0_DCDC) != 0;
|
||||
bool was_locked;
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_DCDC;
|
||||
was_locked = (DCDC->LOCKSTATUS & DCDC_LOCKSTATUS_LOCK) != 0;
|
||||
DCDC->LOCK = DCDC_LOCK_LOCKKEY_UNLOCKKEY;
|
||||
while (DCDC->SYNCBUSY & DCDC_SYNCBUSY_PFMXCTRL) (void) 0;
|
||||
DCDC->PFMXCTRL = (DCDC->PFMXCTRL & ~_DCDC_PFMXCTRL_IPKTMAXCTRL_MASK) |
|
||||
(value << _DCDC_PFMXCTRL_IPKTMAXCTRL_SHIFT);
|
||||
if (was_locked) DCDC->LOCK = ~DCDC_LOCK_LOCKKEY_UNLOCKKEY;
|
||||
if (!clk_was_on) CMU->CLKEN0_CLR = CMU_CLKEN0_DCDC;
|
||||
}
|
||||
|
||||
// SYSTEM_ChipRevision_TypeDef, em_system.h
|
||||
struct chip_rev {
|
||||
uint8_t minor; // Minor revision number
|
||||
uint8_t major; // Major revision number
|
||||
uint8_t family; // Device family number
|
||||
};
|
||||
|
||||
void SYSTEM_ChipRevisionGet(struct chip_rev *rev) {
|
||||
uint32_t chiprev;
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_SYSCFG;
|
||||
chiprev = SYSCFG->CHIPREV;
|
||||
rev->family = (chiprev & _SYSCFG_CHIPREV_FAMILY_MASK) >>
|
||||
_SYSCFG_CHIPREV_FAMILY_SHIFT;
|
||||
rev->major = (chiprev & _SYSCFG_CHIPREV_MAJOR_MASK) >>
|
||||
_SYSCFG_CHIPREV_MAJOR_SHIFT;
|
||||
rev->minor = (chiprev & _SYSCFG_CHIPREV_MINOR_MASK) >>
|
||||
_SYSCFG_CHIPREV_MINOR_SHIFT;
|
||||
}
|
||||
|
||||
// sl_gpio_t, sl_device_gpio.h
|
||||
struct sl_gpio {
|
||||
uint8_t port; // GPIO port number
|
||||
uint8_t pin; // GPIO pin number
|
||||
};
|
||||
|
||||
uint32_t sl_gpio_set_pin(const struct sl_gpio *gpio) {
|
||||
GPIO->P_SET[gpio->port].DOUT = BIT(gpio->pin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t sl_gpio_clear_pin(const struct sl_gpio *gpio) {
|
||||
GPIO->P_CLR[gpio->port].DOUT = BIT(gpio->pin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We set TX power as a raw PA level and load no PA curves. RAIL calls this to
|
||||
// check per-channel dBm limits: RAIL_TX_POWER_MAX means no limit, 255. Without
|
||||
// curves the SDK returns 0, see RAIL_ConvertDbmToRaw(), pa_conversions_efr32.c
|
||||
uint8_t RAIL_ConvertDbmToRaw(void *rail, int mode, int16_t power) {
|
||||
(void) rail, (void) mode;
|
||||
return power >= 0x7fff ? 255 : 0; // 0x7fff: RAIL_TX_POWER_MAX
|
||||
}
|
||||
|
||||
// Added to minimise the bin size: avoid pulling printf and friends
|
||||
void __assert_func(const char *s, int line, const char *fn, const char *expr) {
|
||||
(void) s, (void) line, (void) fn, (void) expr;
|
||||
for (;;) (void) 0; // or trigger the fault handler
|
||||
}
|
||||
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
|
||||
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
|
||||
if (period == 0) return false; // Avoid division by zero
|
||||
if (diff < period) return false; // Period has not elapsed yet
|
||||
*t += (diff / period) * period; // Preserve cadence, skip missed periods
|
||||
return true;
|
||||
}
|
||||
|
||||
static volatile uint64_t s_ticks; // Milliseconds since boot
|
||||
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
|
||||
s_ticks++;
|
||||
}
|
||||
|
||||
uint64_t hal_get_tick(void) {
|
||||
return s_ticks;
|
||||
}
|
||||
|
||||
uint64_t mg_micros(void);
|
||||
// SysTick runs at the core clock and reloads every millisecond. Sample
|
||||
// the tick counter and the sub-millisecond counter in one critical section.
|
||||
uint64_t mg_micros(void) {
|
||||
uint32_t primask = __get_PRIMASK(), val, pending;
|
||||
uint64_t ms;
|
||||
__disable_irq();
|
||||
ms = hal_get_tick();
|
||||
pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
if (pending != (SCB->ICSR & SCB_ICSR_PENDSTSET_Msk)) {
|
||||
pending = SCB_ICSR_PENDSTSET_Msk;
|
||||
val = SysTick->VAL;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
if (pending != 0 && val != 0) ms++;
|
||||
return ms * 1000 + ((uint64_t) (SysTick->LOAD - val) * 1000) /
|
||||
(SysTick->LOAD + 1);
|
||||
}
|
||||
|
||||
struct stat;
|
||||
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
|
||||
(void) fd, (void) st;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
|
||||
static unsigned char *s_current_heap_end = _end;
|
||||
|
||||
size_t hal_ram_used(void) {
|
||||
return (size_t) (s_current_heap_end - _end);
|
||||
}
|
||||
|
||||
size_t hal_ram_free(void) {
|
||||
unsigned char endofstack;
|
||||
return (size_t) (&endofstack - s_current_heap_end);
|
||||
}
|
||||
|
||||
void *_sbrk(int incr) {
|
||||
unsigned char *prev_heap;
|
||||
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
|
||||
prev_heap = s_current_heap_end;
|
||||
// Check how much space we got from the heap end to the stack end
|
||||
if (s_current_heap_end + incr > heap_end) return (void *) -1;
|
||||
s_current_heap_end += incr;
|
||||
return prev_heap;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _open(const char *path) {
|
||||
(void) path;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _close(int fd) {
|
||||
(void) fd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _isatty(int fd) {
|
||||
(void) fd;
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
|
||||
(void) fd, (void) ptr, (void) dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _exit(int status) {
|
||||
(void) status;
|
||||
for (;;) asm volatile("BKPT #0");
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _kill(int pid, int sig) {
|
||||
(void) pid, (void) sig;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _getpid(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
|
||||
(void) fd, (void) ptr, (void) len;
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((weak)) void _init(void) {
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// Seeed Studio XIAO MG24 hardware abstraction layer. EFR32MG24B220F1536IM48
|
||||
// reference: Simplicity SDK, https://github.com/SiliconLabs/simplicity_sdk,
|
||||
// platform/Device/SiliconLabs/EFR32MG24. Board: Seeed XIAO MG24 schematic,
|
||||
// pin names from Silicon Labs Arduino core, variants/xiao_mg24
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "em_device.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
uint64_t hal_get_tick(void);
|
||||
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
|
||||
size_t hal_ram_used(void);
|
||||
size_t hal_ram_free(void);
|
||||
uint32_t hal_rng_read(void); // Random 32 bits, Secure Engine TRNG. See hal.c
|
||||
|
||||
#define BIT(x) (1UL << (x))
|
||||
#define PIN(port, num) ((((port) - 'A') << 8) | (num))
|
||||
#define PINNO(pin) (pin & 255)
|
||||
#define PINPORT(pin) (pin >> 8)
|
||||
|
||||
#define RF_SW_PIN PIN('B', 4) // Antenna switch VCTL: low onboard, high UFL
|
||||
#define RF_SW_PWR_PIN PIN('B', 5) // Antenna switch power
|
||||
|
||||
// HFXO crystal tuning, XIAO MG24 board value: Silicon Labs Arduino core,
|
||||
// sl_device_init_hfxo_config.h. XO gets +40, see ctuneDelta in em_cmu.c
|
||||
#define HFXO_CTUNE 95
|
||||
#define HFXO_CTUNE_XO_DELTA 40
|
||||
|
||||
enum { GPIO_MODE_DISABLED = 0, GPIO_MODE_INPUT = 1, GPIO_MODE_PUSHPULL = 4 };
|
||||
|
||||
static inline void gpio_set_mode(uint16_t pin, uint32_t mode) {
|
||||
volatile uint32_t *r = PINNO(pin) < 8 ? &GPIO->P[PINPORT(pin)].MODEL
|
||||
: &GPIO->P[PINPORT(pin)].MODEH;
|
||||
uint32_t shift = (PINNO(pin) & 7) * 4;
|
||||
*r = (*r & ~(15UL << shift)) | (mode << shift);
|
||||
}
|
||||
|
||||
static inline void gpio_write(uint16_t pin, bool val) {
|
||||
if (val) {
|
||||
GPIO->P_SET[PINPORT(pin)].DOUT = BIT(PINNO(pin));
|
||||
} else {
|
||||
GPIO->P_CLR[PINPORT(pin)].DOUT = BIT(PINNO(pin));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void gpio_output(uint16_t pin, bool val) {
|
||||
gpio_write(pin, val);
|
||||
gpio_set_mode(pin, GPIO_MODE_PUSHPULL);
|
||||
}
|
||||
|
||||
// Start the HFXO crystal oscillator, 39 MHz, and wait until it is ready.
|
||||
// Steps and default values as in CMU_HFXOInit() and CMU_HFXOINIT_DEFAULT,
|
||||
// emlib em_cmu.c. The radio needs HFXO, and RAIL expects it configured
|
||||
static inline void hal_hfxo_init(void) {
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_HFXO0;
|
||||
HFXO0->LOCK = HFXO_LOCK_LOCKKEY_UNLOCK;
|
||||
HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND | HFXO_CTRL_DISONDEMANDBUFOUT;
|
||||
HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
|
||||
while (HFXO0->STATUS & HFXO_STATUS_ENS) (void) 0; // Wait until disabled
|
||||
while (HFXO0->STATUS & HFXO_STATUS_SYNCBUSY) (void) 0;
|
||||
HFXO0->XTALCFG = HFXO_XTALCFG_TIMEOUTCBLSB_T416US |
|
||||
HFXO_XTALCFG_TIMEOUTSTEADY_T833US | // First lock
|
||||
(32U << _HFXO_XTALCFG_COREBIASSTARTUP_SHIFT) |
|
||||
(32U << _HFXO_XTALCFG_COREBIASSTARTUPI_SHIFT);
|
||||
HFXO0->XTALCTRL =
|
||||
(HFXO0->XTALCTRL & _HFXO_XTALCTRL_SKIPCOREBIASOPT_MASK) |
|
||||
HFXO_XTALCTRL_CTUNEFIXANA_XO | // 20 dBm PA parts, see em_cmu.h
|
||||
((uint32_t) (HFXO_CTUNE + HFXO_CTUNE_XO_DELTA)
|
||||
<< _HFXO_XTALCTRL_CTUNEXOANA_SHIFT) |
|
||||
((uint32_t) HFXO_CTUNE << _HFXO_XTALCTRL_CTUNEXIANA_SHIFT) |
|
||||
(60U << _HFXO_XTALCTRL_COREBIASANA_SHIFT);
|
||||
HFXO0->CFG &= ~(_HFXO_CFG_SQBUFSCHTRGANA_MASK | _HFXO_CFG_ENXIDCBIASANA_MASK |
|
||||
_HFXO_CFG_MODE_MASK); // Crystal mode
|
||||
HFXO0->CTRL = (HFXO0->CTRL & ~(_HFXO_CTRL_FORCEXO2GNDANA_MASK |
|
||||
_HFXO_CTRL_FORCEXI2GNDANA_MASK |
|
||||
_HFXO_CTRL_DISONDEMAND_MASK |
|
||||
_HFXO_CTRL_EM23ONDEMAND_MASK)) |
|
||||
HFXO_CTRL_FORCEEN;
|
||||
uint32_t ready = HFXO_STATUS_RDY | HFXO_STATUS_COREBIASOPTRDY |
|
||||
HFXO_STATUS_ENS;
|
||||
while ((HFXO0->STATUS & ready) != ready) (void) 0;
|
||||
HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;
|
||||
HFXO0->XTALCFG = (HFXO0->XTALCFG & ~_HFXO_XTALCFG_TIMEOUTSTEADY_MASK) |
|
||||
HFXO_XTALCFG_TIMEOUTSTEADY_T83US; // Subsequent locks
|
||||
HFXO0->XTALCTRL_SET = HFXO_XTALCTRL_SKIPCOREBIASOPT;
|
||||
HFXO0->CTRL_CLR = HFXO_CTRL_DISONDEMAND; // Radio can request it on demand
|
||||
}
|
||||
|
||||
// Lock HFRCODPLL to HFXO at 78 MHz: 39 MHz * (N + 1) / (M + 1), N 3839,
|
||||
// M 1919, phase lock, falling edge, auto recover. Values and steps as in
|
||||
// Simplicity SDK CMU_DPLLLock() and the xG24 clock manager configuration.
|
||||
// HFRCO band 72.6..80 MHz, tuned with its factory calibration from DEVINFO
|
||||
static inline bool hal_dpll_78mhz(void) {
|
||||
uint32_t lock = DPLL_IF_LOCK | DPLL_IF_LOCKFAILLOW | DPLL_IF_LOCKFAILHIGH;
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_DPLL0 | CMU_CLKEN0_HFRCO0;
|
||||
DPLL0->EN_CLR = DPLL_EN_EN;
|
||||
while (DPLL0->EN & DPLL_EN_DISABLING) (void) 0;
|
||||
while (HFRCO0->STATUS & (HFRCO_STATUS_SYNCBUSY | HFRCO_STATUS_FREQBSY))
|
||||
(void) 0;
|
||||
HFRCO0->CAL = 0xCCF09F00UL | (DEVINFO->HFRCODPLLCAL[16].HFRCODPLLCAL &
|
||||
_HFRCO_CAL_TUNING_MASK);
|
||||
DPLL0->CFG1 = (3839UL << _DPLL_CFG1_N_SHIFT) | (1919UL << _DPLL_CFG1_M_SHIFT);
|
||||
CMU->DPLLREFCLKCTRL = CMU_DPLLREFCLKCTRL_CLKSEL_HFXO;
|
||||
DPLL0->CFG = (1UL << _DPLL_CFG_AUTORECOVER_SHIFT) | // Edge: falling, 0
|
||||
(_DPLL_CFG_MODE_PLL << _DPLL_CFG_MODE_SHIFT);
|
||||
DPLL0->IF_CLR = lock;
|
||||
DPLL0->EN_SET = DPLL_EN_EN;
|
||||
while ((DPLL0->IF & lock) == 0) (void) 0;
|
||||
return (DPLL0->IF & lock) == DPLL_IF_LOCK;
|
||||
}
|
||||
|
||||
// Run the CPU at its maximum, 78 MHz, from HFRCODPLL locked to HFXO. First
|
||||
// set the limits for 78 MHz, datasheet table 4.2 and em_cmu.c: flash 1 wait
|
||||
// state above 40 MHz; PCLK max 50 MHz, so SYSCLK / 2; radio HCLK max 40 MHz,
|
||||
// so SYSCLK / 2. Voltage scaling is VSCALE2 after reset, fine for 78 MHz.
|
||||
// If the DPLL does not lock, stay on HFXO, 39 MHz
|
||||
static inline void hal_clock_init(void) {
|
||||
hal_hfxo_init();
|
||||
CMU->CLKEN1_SET = CMU_CLKEN1_MSC;
|
||||
MSC->LOCK = MSC_LOCK_LOCKKEY_UNLOCK;
|
||||
MSC->READCTRL = (MSC->READCTRL & ~_MSC_READCTRL_MODE_MASK) |
|
||||
MSC_READCTRL_MODE_WS1;
|
||||
CMU->SYSCLKCTRL_SET = CMU_SYSCLKCTRL_PCLKPRESC | CMU_SYSCLKCTRL_RHCLKPRESC;
|
||||
if (hal_dpll_78mhz()) {
|
||||
SystemHFRCODPLLClockSet(78000000);
|
||||
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) |
|
||||
CMU_SYSCLKCTRL_CLKSEL_HFRCODPLL;
|
||||
} else {
|
||||
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) |
|
||||
CMU_SYSCLKCTRL_CLKSEL_HFXO;
|
||||
}
|
||||
SystemCoreClock = SystemSYSCLKGet(); // HCLK = SYSCLK, no prescaler
|
||||
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
|
||||
}
|
||||
|
||||
// PCLK, the peripheral clock: SYSCLK, divided by 2 if PCLKPRESC is set
|
||||
static inline uint32_t hal_pclk(void) {
|
||||
bool half = (CMU->SYSCLKCTRL & CMU_SYSCLKCTRL_PCLKPRESC) != 0;
|
||||
return half ? SystemCoreClock / 2 : SystemCoreClock;
|
||||
}
|
||||
|
||||
static inline void hal_gpio_output(uint16_t pin) {
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO;
|
||||
gpio_set_mode(pin, GPIO_MODE_PUSHPULL);
|
||||
}
|
||||
|
||||
static inline void hal_gpio_toggle(uint16_t pin) {
|
||||
GPIO->P_TGL[PINPORT(pin)].DOUT = BIT(PINNO(pin));
|
||||
}
|
||||
|
||||
// USART0, 8N1, on tx and rx pins: any pin, through GPIO routing. On XIAO
|
||||
// MG24, PA8 TX and PA9 RX go to the SAMD11 USB serial bridge. USART0 is
|
||||
// clocked by PCLK
|
||||
static inline void hal_uart_init(USART_TypeDef *uart, uint16_t tx, uint16_t rx,
|
||||
unsigned long baud) {
|
||||
uint32_t ovs = 16, div, pclk = hal_pclk();
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO | CMU_CLKEN0_USART0;
|
||||
gpio_output(tx, 1);
|
||||
gpio_set_mode(rx, GPIO_MODE_INPUT);
|
||||
GPIO->USARTROUTE[0].TXROUTE =
|
||||
((uint32_t) PINPORT(tx) << _GPIO_USART_TXROUTE_PORT_SHIFT) |
|
||||
((uint32_t) PINNO(tx) << _GPIO_USART_TXROUTE_PIN_SHIFT);
|
||||
GPIO->USARTROUTE[0].RXROUTE =
|
||||
((uint32_t) PINPORT(rx) << _GPIO_USART_RXROUTE_PORT_SHIFT) |
|
||||
((uint32_t) PINNO(rx) << _GPIO_USART_RXROUTE_PIN_SHIFT);
|
||||
GPIO->USARTROUTE[0].ROUTEEN =
|
||||
GPIO_USART_ROUTEEN_TXPEN | GPIO_USART_ROUTEEN_RXPEN;
|
||||
uart->EN = USART_EN_EN;
|
||||
uart->CTRL = 0; // Async, 16x oversampling
|
||||
uart->FRAME = USART_FRAME_DATABITS_EIGHT | USART_FRAME_STOPBITS_ONE |
|
||||
USART_FRAME_PARITY_NONE;
|
||||
// CLKDIV = 256 * (fPCLK / (ovs * baud) - 1), see USART_BaudrateAsyncSet()
|
||||
div = (32 * pclk + (ovs * baud) / 2) / (ovs * baud) - 32;
|
||||
uart->CLKDIV = (div * 8) & _USART_CLKDIV_DIV_MASK;
|
||||
uart->CMD = USART_CMD_TXEN | USART_CMD_RXEN;
|
||||
}
|
||||
|
||||
static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) {
|
||||
while ((uart->STATUS & USART_STATUS_TXBL) == 0) (void) 0;
|
||||
uart->TXDATA = byte;
|
||||
}
|
||||
|
||||
static inline void hal_uart_write_buf(USART_TypeDef *uart, const char *buf,
|
||||
size_t len) {
|
||||
while (len-- > 0) hal_uart_write_byte(uart, *(const uint8_t *) buf++);
|
||||
}
|
||||
|
||||
// Random numbers come from the Secure Engine TRNG, see hal_rng_read(): clock
|
||||
// its mailbox
|
||||
static inline void hal_rng_init(void) {
|
||||
CMU->CLKEN1_SET = CMU_CLKEN1_SEMAILBOXHOST;
|
||||
}
|
||||
|
||||
// Prepare for RAIL. Set all IRQs to priority 5, as Silicon Labs
|
||||
// sl_interrupt_manager_init() does: RAIL atomic sections mask priority
|
||||
// numbers 3 and above. Route the radio to the onboard ceramic antenna.
|
||||
// FM8625H switch: VCTL low connects ANT to RF1, the chip antenna; high RF2,
|
||||
// the U.FL socket
|
||||
static inline void hal_radio_init(void) {
|
||||
for (int i = 0; i < EXT_IRQ_COUNT; i++) NVIC_SetPriority((IRQn_Type) i, 5);
|
||||
CMU->CLKEN0_SET = CMU_CLKEN0_GPIO;
|
||||
gpio_output(RF_SW_PWR_PIN, 1);
|
||||
gpio_output(RF_SW_PIN, 0);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* EFR32MG24B220F1536IM48 memory map, see Simplicity SDK linkerfile_base.ld */
|
||||
ENTRY(Reset_Handler);
|
||||
MEMORY {
|
||||
flash(rx) : ORIGIN = 0x08000000, LENGTH = 1536k
|
||||
sram(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||
}
|
||||
__StackTop = ORIGIN(sram) + LENGTH(sram); /* Stack points here */
|
||||
__StackLimit = __StackTop - 8192; /* MSPLIM, see startup */
|
||||
|
||||
SECTIONS {
|
||||
.vectors : { KEEP(*(.vectors)) . = ALIGN(4); } > flash
|
||||
.text : { *(.text* .text.* text_*) } > flash /* text_*: SDK code */
|
||||
.rodata : { *(.rodata*) . = ALIGN(4); } > flash
|
||||
|
||||
/* RAM functions, copied by CopyToRam() in the startup code */
|
||||
.ramfuncs : {
|
||||
. = ALIGN(4);
|
||||
__ramfuncs_start__ = .;
|
||||
*(.ram .ram.*)
|
||||
. = ALIGN(4);
|
||||
__ramfuncs_end__ = .;
|
||||
} > sram AT > flash
|
||||
__lma_ramfuncs_start__ = LOADADDR(.ramfuncs);
|
||||
__lma_ramfuncs_end__ = LOADADDR(.ramfuncs) + SIZEOF(.ramfuncs);
|
||||
|
||||
.data : {
|
||||
__data_start__ = .;
|
||||
*(.first_data)
|
||||
*(.iram .iram.*) /* Mongoose MG_IRAM functions */
|
||||
*(.data SORT(.data.*))
|
||||
. = ALIGN(4);
|
||||
__data_end__ = .;
|
||||
} > sram AT > flash
|
||||
__etext = LOADADDR(.data); /* Startup copies .data from here */
|
||||
|
||||
.bss : {
|
||||
__bss_start__ = .;
|
||||
*(.bss SORT(.bss.*) COMMON)
|
||||
. = ALIGN(4);
|
||||
__bss_end__ = .;
|
||||
} > sram
|
||||
|
||||
/* Health record, see MG_HEALTH_RAM: not cleared, survives a warm reset */
|
||||
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > sram
|
||||
|
||||
. = ALIGN(8);
|
||||
_end = .; /* Start of heap, see _sbrk() in hal.c */
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2026 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include "hal.h"
|
||||
#include "mongoose.h"
|
||||
|
||||
#ifndef UART_DEBUG
|
||||
#define UART_DEBUG USART0 // SAMD11 USB serial port
|
||||
#define UART_DEBUG_TX_PIN PIN('A', 8)
|
||||
#define UART_DEBUG_RX_PIN PIN('A', 9)
|
||||
#else
|
||||
#define UART_DEBUG_TX_PIN PIN('C', 6) // D6, D7 header pins
|
||||
#define UART_DEBUG_RX_PIN PIN('C', 7)
|
||||
#endif
|
||||
|
||||
#define LED1 PIN('A', 7) // User LED, active low
|
||||
|
||||
static void log_fn(char ch, void *param) {
|
||||
hal_uart_write_buf(param, &ch, 1);
|
||||
}
|
||||
|
||||
static void blink_task(void) {
|
||||
static uint64_t blink_timer = 0;
|
||||
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
|
||||
hal_gpio_toggle(LED1);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t mg_millis(void) {
|
||||
return hal_get_tick();
|
||||
}
|
||||
|
||||
bool mg_random(void *buf, size_t len) {
|
||||
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
|
||||
uint32_t r = hal_rng_read();
|
||||
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Self-signed P-256 certificate and key for the HTTPS listener. Created by:
|
||||
// openssl ecparam -genkey -name prime256v1 -noout -out key.pem
|
||||
// openssl req -x509 -key key.pem -out cert.pem -days 3650 -subj /CN=mg
|
||||
#define TLS_CERT \
|
||||
"-----BEGIN CERTIFICATE-----\n" \
|
||||
"MIIBgjCCASegAwIBAgIUIJOq77Qn7lHbH6WseMemeqorR50wCgYIKoZIzj0EAwIw\n" \
|
||||
"FjEUMBIGA1UEAwwLbnVjbGVvLWg3MjMwHhcNMjYxMDEyMTUwMjE5WhcNMzYxMDA5\n" \
|
||||
"MTUwMjE5WjAWMRQwEgYDVQQDDAtudWNsZW8taDcyMzBZMBMGByqGSM49AgEGCCqG\n" \
|
||||
"SM49AwEHA0IABB6IQJNmzrsdQL+2BnZYS7GYOaj7sz8f6cjBmz3n8D9Em19XodlU\n" \
|
||||
"uy4ybIvyhr766lL0ERoTmMUivb/lGmzx9C+jUzBRMB0GA1UdDgQWBBQqxMuc4gj7\n" \
|
||||
"MTMzreWlRMNI3QMqTzAfBgNVHSMEGDAWgBQqxMuc4gj7MTMzreWlRMNI3QMqTzAP\n" \
|
||||
"BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDY4xaJpuxq6b3rHK3b\n" \
|
||||
"lOIQyfkQX+orCRqQUkseplqpqwIhAKswZFNn+2zUNRRNY8+cHmdIwmdpsmxtoWYP\n" \
|
||||
"BCjX++LI\n" \
|
||||
"-----END CERTIFICATE-----\n"
|
||||
|
||||
#define TLS_KEY \
|
||||
"-----BEGIN EC PRIVATE KEY-----\n" \
|
||||
"MHcCAQEEIJ/GBl0TlD52q9PMg1KWaNIdXqp8b/HyIsABmBpqC+O1oAoGCCqGSM49\n" \
|
||||
"AwEHoUQDQgAEHohAk2bOux1Av7YGdlhLsZg5qPuzPx/pyMGbPefwP0SbX1eh2VS7\n" \
|
||||
"LjJsi/KGvvrqUvQRGhOYxSK9v+UabPH0Lw==\n" \
|
||||
"-----END EC PRIVATE KEY-----\n"
|
||||
|
||||
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_ACCEPT && c->is_tls) { // Accepted on the https:// listener
|
||||
struct mg_tls_opts opts = {.cert = mg_str(TLS_CERT),
|
||||
.key = mg_str(TLS_KEY)};
|
||||
mg_tls_init(c, &opts);
|
||||
} else if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
|
||||
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||
__asm volatile("udf #0"); // Trigger HardFault
|
||||
} else {
|
||||
mg_http_reply(c, 200, "",
|
||||
"Hi from Mongoose!\n"
|
||||
"tick: %llu\n"
|
||||
"ram used: %zu\n"
|
||||
"ram free: %zu\n",
|
||||
hal_get_tick(), hal_ram_used(), hal_ram_free());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
MG_HEALTH_INIT();
|
||||
|
||||
hal_clock_init();
|
||||
hal_uart_init(UART_DEBUG, UART_DEBUG_TX_PIN, UART_DEBUG_RX_PIN, 115200);
|
||||
mg_log_set_fn(log_fn, UART_DEBUG);
|
||||
hal_rng_init();
|
||||
hal_radio_init();
|
||||
hal_gpio_output(LED1);
|
||||
|
||||
struct mg_mgr mgr;
|
||||
mg_mgr_init(&mgr);
|
||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_http_listen(&mgr, "https://0.0.0.0", http_ev_handler, NULL);
|
||||
mg_mqtt_init(&mgr);
|
||||
|
||||
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||
MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free()));
|
||||
MG_INFO(("Crash record: %M", mg_print_crash_record));
|
||||
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 0);
|
||||
mg_mqtt_poll(&mgr);
|
||||
blink_task();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "em_device.h"
|
||||
|
||||
// See https://mongoose.ws/docs/getting-started/build-options/
|
||||
#define MG_ARCH MG_ARCH_ARMGCC
|
||||
#define MG_TLS MG_TLS_BUILTIN
|
||||
#define MQTT_CLIENT_ID "mg24" // For tutorials/mqtt/mqtt-client
|
||||
|
||||
#define MG_ENABLE_TCPIP 1
|
||||
#define MG_ENABLE_DRIVER_MG24 1
|
||||
#define MG_ENABLE_CUSTOM_MILLIS 1
|
||||
#define MG_ENABLE_CUSTOM_RANDOM 1 // Secure Engine TRNG, see hal.c
|
||||
#define MG_ENABLE_TCPIP_TCPRTX 1 // Link can be lossy, enable retransmit
|
||||
|
||||
// Stable MAC, so the DHCP address stays the same across reboots: a hash of
|
||||
// the factory EUI-64, DEVINFO, locally administered
|
||||
#define MG_SET_MAC_ADDRESS(mac) \
|
||||
do { \
|
||||
uint32_t eui_[2] = {DEVINFO->EUI64L, DEVINFO->EUI64H}; \
|
||||
uint8_t b_[32]; \
|
||||
mg_sha256(b_, (uint8_t *) eui_, sizeof(eui_)); \
|
||||
(mac)[0] = 2, (mac)[1] = b_[1], (mac)[2] = b_[2], (mac)[3] = b_[3]; \
|
||||
(mac)[4] = b_[4], (mac)[5] = b_[5]; \
|
||||
} while (0)
|
||||
|
||||
// Crash report support. Health record lives in the .mg_health region, see link.ld
|
||||
#define MG_ENABLE_HEALTH 1
|
||||
#define MG_HEALTH_RAM __attribute__((section(".mg_health")))
|
||||
|
||||
// mdash.net device management service support
|
||||
#define MG_ENABLE_MDASH 0
|
||||
#define MG_MDASH_KEY "..." // Set this - mDash device token
|
||||
#define MG_FIRMWARE_VERSION "mg24.1.0.0"
|
||||
|
||||
Reference in new issue
Block a user