From a6b6a0d779e99ea7fdcee1f17a22a7221660be8c Mon Sep 17 00:00:00 2001 From: cpq Date: Sat, 17 Oct 2026 13:12:04 +0100 Subject: [PATCH] Add frdm-mcxw71 support --- tutorials/nxp/frdm-mcxw71/minimal/Makefile | 43 +++++++ tutorials/nxp/frdm-mcxw71/minimal/hal.c | 109 +++++++++++++++++ tutorials/nxp/frdm-mcxw71/minimal/hal.h | 114 ++++++++++++++++++ tutorials/nxp/frdm-mcxw71/minimal/link.ld | 37 ++++++ tutorials/nxp/frdm-mcxw71/minimal/main.c | 33 +++++ .../nxp/frdm-mcxw71/minimal/mongoose_config.h | 7 ++ 6 files changed, 343 insertions(+) create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/Makefile create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/hal.c create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/hal.h create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/link.ld create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/main.c create mode 100644 tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h diff --git a/tutorials/nxp/frdm-mcxw71/minimal/Makefile b/tutorials/nxp/frdm-mcxw71/minimal/Makefile new file mode 100644 index 00000000..84c84ddb --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/Makefile @@ -0,0 +1,43 @@ +# Environment setup: https://mongoose.ws/documentation/tutorials/tools/ + +MGDIR = ../../../.. + +CFLAGS = -W -Wall -Wextra -Werror -Wundef -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 += -Icmsis_mcxw71/MCXW/MCXW716C -Icmsis_mcxw71/MCXW/periph2 +CFLAGS += -DCPU_MCXW716CMFTA +CFLAGS += -D__START=main -D__STARTUP_CLEAR_BSS # For startup_MCXW716C.S +CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections + +SOURCES = main.c hal.c $(MGDIR)/mongoose.c +SOURCES += cmsis_mcxw71/MCXW/MCXW716C/gcc/startup_MCXW716C.S + +all build: firmware.bin + +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 + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +# Device headers and startup code from MCUXpresso SDK, only MCXW716C files +cmsis_mcxw71: + git clone -q --depth 1 --filter=blob:none --sparse https://github.com/nxp-mcuxpresso/mcux-devices-mcx $@ + cd $@ && git sparse-checkout set MCXW/MCXW716C MCXW/periph2 + +# 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: firmware.bin + LinkServer flash MCXW716C:FRDM-MCXW71 load firmware.elf + +clean: + rm -rf firmware.* cmsis_* diff --git a/tutorials/nxp/frdm-mcxw71/minimal/hal.c b/tutorials/nxp/frdm-mcxw71/minimal/hal.c new file mode 100644 index 00000000..3fb9fa35 --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/hal.c @@ -0,0 +1,109 @@ +// 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; +} + +uint32_t SystemCoreClock; + +// Called by the startup code before .data and .bss are initialised, so it +// must not write any variables. Clock and SysTick setup is in main() +void SystemInit(void) { + hal_sys_init(); +} + +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) { +} diff --git a/tutorials/nxp/frdm-mcxw71/minimal/hal.h b/tutorials/nxp/frdm-mcxw71/minimal/hal.h new file mode 100644 index 00000000..adfd5ebe --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/hal.h @@ -0,0 +1,114 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// FRDM-MCXW71 hardware abstraction layer. MCXW716C reference: MCUXpresso SDK, +// https://github.com/nxp-mcuxpresso/mcux-devices-mcx, MCXW/MCXW716C + +#pragma once + +#include "MCXW716C.h" + +#include +#include +#include +#include + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) + +#define SIRC_FREQUENCY 6000000UL // Slow IRC (FRO6M), Hz + +extern uint32_t SystemCoreClock; // CPU clock, Hz. Set by hal_clock_init() + +// MRCC peripheral clock source, MRCC_xx[MUX], see fsl_clock.h +enum { MRCC_SRC_FRO6M = 2, MRCC_SRC_FRO192M = 3 }; + +// Fast IRC frequency, Hz, or 0 if FIRC is not running. See CLOCK_GetFircFreq() +static inline uint32_t hal_firc_freq(void) { + static const uint32_t freqs[] = {48000000, 64000000, 96000000, 192000000}; + if ((SCG0->FIRCCSR & SCG_FIRCCSR_FIRCVLD_MASK) == 0) return 0; + return freqs[SCG0->FIRCCFG & SCG_FIRCCFG_RANGE_MASK]; +} + +// Work out the CPU clock from the SCG clock source and core divider. The +// clock is left as configured at reset +static inline void hal_clock_init(void) { + uint32_t csr = SCG0->CSR, freq = 0; + uint32_t src = (csr & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT; + uint32_t div = ((csr & SCG_CSR_DIVCORE_MASK) >> SCG_CSR_DIVCORE_SHIFT) + 1; + if (src == 2) freq = SIRC_FREQUENCY; // Slow IRC + if (src == 3) freq = hal_firc_freq(); // Fast IRC + if (freq == 0) freq = SIRC_FREQUENCY; // Unknown source, assume reset value + SystemCoreClock = freq / div; + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +// Enable peripheral clock and take it out of reset, from given clock source. +// See CLOCK_SetIpSrc() and CLOCK_EnableClock() in fsl_clock.h +static inline void hal_periph_clock(volatile uint32_t *mrcc, uint32_t src) { + uint32_t v = (*mrcc & ~(MRCC_CC_MASK | MRCC_MUX_MASK | MRCC_DIV_MASK)) | + MRCC_MUX(src); + *mrcc = v; // Source, clock still off + *mrcc = v | 1U; // CC = 1: clock on + if (*mrcc & MRCC_PR_MASK) *mrcc |= MRCC_RSTB_MASK; // Release reset + __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) { + 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_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++) { + uint32_t sbr = (freq + osr * baud / 2) / (osr * baud), actual, err; + if (sbr == 0 || sbr > 0x1fff) continue; + actual = freq / (osr * sbr); + err = actual > baud ? actual - baud : baud - actual; + if (err < best_err) best_err = err, best_osr = osr, best_sbr = sbr; + } + uart->CTRL = 0; // Disable while configuring + uart->BAUD = LPUART_BAUD_OSR(best_osr - 1) | LPUART_BAUD_SBR(best_sbr) | + (best_osr < 8 ? LPUART_BAUD_BOTHEDGE_MASK : 0); + uart->CTRL = LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK; +} + +static inline void hal_uart_write_byte(LPUART_Type *uart, uint8_t byte) { + while ((uart->STAT & LPUART_STAT_TDRE_MASK) == 0) (void) 0; + uart->DATA = byte; +} + +static inline void hal_uart_write_buf(LPUART_Type *uart, const char *buf, + size_t len) { + while (len-- > 0) hal_uart_write_byte(uart, *(const uint8_t *) buf++); +} + +// WDOG0 runs after reset: disable it, as SystemInit() in system_MCXW716C.c +static inline void hal_wdog_disable(void) { + while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0; + if (WDOG0->CS & WDOG_CS_CMD32EN_MASK) { + WDOG0->CNT = 0xD928C520U; // Unlock sequence + } else { + WDOG0->CNT = 0xC520U; + WDOG0->CNT = 0xD928U; + } + while ((WDOG0->CS & WDOG_CS_ULK_MASK) == 0) (void) 0; + WDOG0->TOVAL = 0xFFFF; + WDOG0->CS = (WDOG0->CS & ~WDOG_CS_EN_MASK) | WDOG_CS_UPDATE_MASK; + while ((WDOG0->CS & WDOG_CS_RCS_MASK) == 0) (void) 0; +} + +static inline void hal_sys_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + hal_wdog_disable(); +} + +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); diff --git a/tutorials/nxp/frdm-mcxw71/minimal/link.ld b/tutorials/nxp/frdm-mcxw71/minimal/link.ld new file mode 100644 index 00000000..bdd815db --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/link.ld @@ -0,0 +1,37 @@ +/* MCXW716C memory map, see MCUXpresso SDK gcc/mcxw716_flash.ld */ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x00000000, LENGTH = 1024k + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 112k +} +__StackTop = ORIGIN(sram) + LENGTH(sram); /* Stack points here */ +__StackLimit = __StackTop - 8192; /* MSPLIM, see startup */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) . = ALIGN(4); } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) . = ALIGN(4); } > flash + + .data : { + __data_start__ = .; + _sdata = .; + *(.first_data) + *(.data SORT(.data.*)) + . = ALIGN(4); + __data_end__ = .; + _edata = .; + } > sram AT > flash + __etext = LOADADDR(.data); /* Startup copies .data from here */ + + .bss : { + __bss_start__ = .; + _sbss = .; + *(.bss SORT(.bss.*) COMMON) + . = ALIGN(4); + __bss_end__ = .; + _ebss = .; + } > sram + + . = ALIGN(8); + _end = .; /* Start of heap, see _sbrk() in hal.c */ +} diff --git a/tutorials/nxp/frdm-mcxw71/minimal/main.c b/tutorials/nxp/frdm-mcxw71/minimal/main.c new file mode 100644 index 00000000..b374c74b --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/main.c @@ -0,0 +1,33 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" +#include "mongoose.h" + +#define UART_DEBUG LPUART1 // MCU-Link USB serial port + +static void log_fn(char ch, void *param) { + hal_uart_write_buf(param, &ch, 1); +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +int main(void) { + hal_clock_init(); + hal_uart_init(UART_DEBUG, 115200); + mg_log_set_fn(log_fn, UART_DEBUG); + + MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000)); + MG_INFO(("RAM used: %zu, free: %zu", hal_ram_used(), hal_ram_free())); + + for (;;) { + static uint64_t timer; + if (hal_timer_expired(&timer, 1000, hal_get_tick())) { + MG_INFO(("Hi from Mongoose, tick %llu", hal_get_tick())); + } + } + + return 0; +} diff --git a/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h b/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h new file mode 100644 index 00000000..35e463c6 --- /dev/null +++ b/tutorials/nxp/frdm-mcxw71/minimal/mongoose_config.h @@ -0,0 +1,7 @@ +#pragma once + +// See https://mongoose.ws/documentation/#build-options +#define MG_ARCH MG_ARCH_ARMGCC + +#define MG_ENABLE_TCPIP 1 // Built-in TCP/IP stack. No network driver yet +#define MG_ENABLE_CUSTOM_MILLIS 1