Add radio simulation

This commit is contained in:
cpq committed 2026-11-05 22:23:06 +00:00
1 parent 9337a78cd0
commit d39519b92f
66 files changed
+5659 -1000

No files matched your search

@@ -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_*
+262
View File
@@ -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) {
}
+212
View File
@@ -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 */
}
+113
View File
@@ -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"