From c3dbc109196f014f924246f8ff2757a682d6d5c2 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 27 Feb 2026 13:43:01 +0100 Subject: [PATCH] Add target for the STM32WL3x For the STM32WL3x, stm32wb0.c is the right place because: - Both are single-core Cortex-M0+ wireless MCUs - Both share the same flash controller at 0x40001000 with identical register layout and command codes (0x11/0x22/0x33/0xBB) - Same flash size formula: ((SIZE[15:0] + 1) << 2) - Same erase granularity (2KB sectors) - Both have the same write-then-poll pattern --- src/target/stm32wb0.c | 117 ++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 34 deletions(-) diff --git a/src/target/stm32wb0.c b/src/target/stm32wb0.c index 401648a4f68..9905893f2c6 100644 --- a/src/target/stm32wb0.c +++ b/src/target/stm32wb0.c @@ -32,12 +32,17 @@ */ /* - * This file implements support for STM32WB0x series devices, providing - * memory maps and Flash programming routines. + * This file implements support for STM32WB0x and STM32WL3x series devices, + * providing memory maps and Flash programming routines. + * + * Both families share the same flash controller register layout and command + * set; the main differences are the flash bank base address, the debug + * infrastructure registers, and the write granularity. * * References: * RM0530 - STM32WB07xC and STM32WB06xC ultra-low power wireless 32-bit MCUs Arm®-based Cortex®-M0+ with Bluetooth® Low Energy and 2.4 GHz radio solution, Rev. 1 * - https://www.st.com/resource/en/reference_manual/rm0530--stm32wb07xc-and-stm32wb06xc-ultralow-power-wireless-32bit-mcus-armbased-cortexm0-with-bluetooth-low-energy-and-24-ghz-radio-solution-stmicroelectronics.pdf + * RM0505 - STM32WL33 ultra-low power sub-GHz wireless 32-bit MCU Arm®-based Cortex®-M0+, Rev. 1 */ #include "general.h" @@ -52,39 +57,60 @@ #define STM32WB0_SRAM_BASE 0x20000000U #define STM32WB0_SRAM_SIZE 0x00010000U +/* Memory map constants for STM32WL3x */ +#define STM32WL3_FLASH_BANK_BASE 0x10040000U + +/* Shared flash controller registers (same base and layout for both families) */ #define STM32WB0_FLASH_BASE 0x40001000U #define STM32WB0_FLASH_COMMAND (STM32WB0_FLASH_BASE + 0x000U) -#define STM32WB0_FLASH_STATUS (STM32WB0_FLASH_BASE + 0x008U) +#define STM32WB0_FLASH_STATUS (STM32WB0_FLASH_BASE + 0x008U) /* WB0x: masked interrupt status */ +#define STM32WL3_FLASH_IRQRAW (STM32WB0_FLASH_BASE + 0x010U) /* WL3x: raw interrupt status */ #define STM32WB0_FLASH_FLASH_SIZE (STM32WB0_FLASH_BASE + 0x014U) #define STM32WB0_FLASH_ADDRESS (STM32WB0_FLASH_BASE + 0x018U) #define STM32WB0_FLASH_DATA0 (STM32WB0_FLASH_BASE + 0x040U) -#define STM32WB0_FLASH_PAGE_SIZE 0x00000100U -#define STM32WB0_FLASH_SECTOR_SIZE 0x00000800U +#define STM32WB0_FLASH_PAGE_SIZE 0x00000100U /* WB0x write granularity */ +#define STM32WB0_FLASH_SECTOR_SIZE 0x00000800U /* erase granularity (both families) */ +/* Status/interrupt bits (same bit positions for both families) */ #define STM32WB0_FLASH_STATUS_CMDDONE (1U << 0U) #define STM32WB0_FLASH_STATUS_CMDSTART (1U << 1U) -#define STM32WB0_FLASH_STATUS_ERROR_MASK 0x0000001cU +#define STM32WB0_FLASH_STATUS_ERROR_MASK 0x0000001cU /* WB0x: bits [4:2] */ +#define STM32WL3_FLASH_IRQ_ERROR_MASK 0x0000000cU /* WL3x: bits [3:2] */ +/* Flash commands (shared between both families) */ #define STM32WB0_FLASH_COMMAND_SECTOR_ERASE 0x11U #define STM32WB0_FLASH_COMMAND_MASS_ERASE 0x22U #define STM32WB0_FLASH_COMMAND_WRITE 0x33U #define STM32WB0_FLASH_COMMAND_WAKEUP 0xbbU #define STM32WB0_FLASH_COMMAND_BURST_WRITE 0xccU -#define STM32WB0_PWRC_BASE 0x48500000U -#define STM32WB0_PWRC_DBGR (STM32WB0_PWRC_BASE + 0x084U) +/* STM32WB0x debug: power control register */ +#define STM32WB0_PWRC_BASE 0x48500000U +#define STM32WB0_PWRC_DBGR (STM32WB0_PWRC_BASE + 0x084U) +#define STM32WB0_PWRC_DBGR_DEEPSTOP2 (1U << 0U) + +/* STM32WL3x debug: DBGMCU registers */ +#define STM32WL3_DBGMCU_BASE 0x40008000U +#define STM32WL3_DBGMCU_CR (STM32WL3_DBGMCU_BASE + 0x000U) +#define STM32WL3_DBGMCU_APB0FZ (STM32WL3_DBGMCU_BASE + 0x004U) +#define STM32WL3_DBGMCU_CR_DBG_SLEEP (1U << 0U) /* Keep debug active in sleep */ +#define STM32WL3_DBGMCU_CR_DBG_STOP (1U << 1U) /* Keep debug active in stop */ +#define STM32WL3_DBGMCU_APB0FZ_IWDG (1U << 14U) /* Freeze IWDG in debug */ -#define STM32WB0_PWRC_DBGR_DEEPSTOP2 (1U << 0U) +/* STM32WL3x FLASH_SIZE register: bit 17 selects RAM size (0=16KB, 1=32KB) */ +#define STM32WL3_FLASH_SIZE_RAM_BIT (1U << 17U) #define ID_STM32WB0 0x01eU +#define ID_STM32WL3 0x027U static bool stm32wb0_enter_flash_mode(target_s *target); static bool stm32wb0_flash_erase(target_flash_s *flash, target_addr_t addr, size_t len); static bool stm32wb0_flash_write(target_flash_s *flash, target_addr_t dest, const void *src, size_t len); static bool stm32wb0_mass_erase(target_s *target, platform_timeout_s *print_progess); -static void stm32wb0_add_flash(target_s *const target, const size_t length) +static void stm32wb0_add_flash( + target_s *const target, const uint32_t start, const size_t length, const size_t writesize) { target_flash_s *flash = calloc(1, sizeof(*flash)); if (!flash) { /* calloc failed: heap exhaustion */ @@ -92,10 +118,10 @@ static void stm32wb0_add_flash(target_s *const target, const size_t length) return; } - flash->start = STM32WB0_FLASH_BANK_BASE; + flash->start = start; flash->length = length; flash->blocksize = STM32WB0_FLASH_SECTOR_SIZE; - flash->writesize = STM32WB0_FLASH_PAGE_SIZE; + flash->writesize = writesize; flash->erase = stm32wb0_flash_erase; flash->write = stm32wb0_flash_write; flash->erased = 0xffU; @@ -122,30 +148,52 @@ bool stm32wb0_probe(target_s *const target) { const adiv5_access_port_s *const ap = cortex_ap(target); /* Use the partno from the AP always to handle the difference between JTAG and SWD */ - if (ap->partno != ID_STM32WB0) + if (ap->partno != ID_STM32WB0 && ap->partno != ID_STM32WL3) return false; target->part_id = ap->partno; - /* Prevent deep sleeping from taking the debug link out */ - target_mem32_write16(target, STM32WB0_PWRC_DBGR, STM32WB0_PWRC_DBGR_DEEPSTOP2); - - target->driver = "STM32WB0"; target->mass_erase = stm32wb0_mass_erase; target->enter_flash_mode = stm32wb0_enter_flash_mode; const uint32_t signature = target_mem32_read32(target, STM32WB0_FLASH_FLASH_SIZE); - target_add_ram32(target, STM32WB0_SRAM_BASE, stm32wb0_ram_size(signature)); - stm32wb0_add_flash(target, ((signature & 0x0000ffffU) + 1U) << 2U); + const size_t flash_size = ((signature & 0x0000ffffU) + 1U) << 2U; + + if (ap->partno == ID_STM32WL3) { + /* STM32WL3x: use DBGMCU to keep debug active in sleep/stop and freeze IWDG */ + target_mem32_write32(target, STM32WL3_DBGMCU_CR, + STM32WL3_DBGMCU_CR_DBG_SLEEP | STM32WL3_DBGMCU_CR_DBG_STOP); + target_mem32_write32(target, STM32WL3_DBGMCU_APB0FZ, STM32WL3_DBGMCU_APB0FZ_IWDG); + + target->driver = "STM32WL3"; + /* RAM size: 32 KB if bit 17 is set, 16 KB otherwise */ + const size_t ram_size = (signature & STM32WL3_FLASH_SIZE_RAM_BIT) ? 0x8000U : 0x4000U; + target_add_ram32(target, STM32WB0_SRAM_BASE, ram_size); + /* WL3x writes one 32-bit word at a time */ + stm32wb0_add_flash(target, STM32WL3_FLASH_BANK_BASE, flash_size, 4U); + } else { + /* STM32WB0x: prevent deep sleeping from taking the debug link out */ + target_mem32_write16(target, STM32WB0_PWRC_DBGR, STM32WB0_PWRC_DBGR_DEEPSTOP2); + + target->driver = "STM32WB0"; + target_add_ram32(target, STM32WB0_SRAM_BASE, stm32wb0_ram_size(signature)); + stm32wb0_add_flash(target, STM32WB0_FLASH_BANK_BASE, flash_size, STM32WB0_FLASH_PAGE_SIZE); + } return true; } static bool stm32wb0_flash_wait_complete(target_s *const target, platform_timeout_s *const timeout) { + /* WL3x polls the raw interrupt status; WB0x polls the masked status register */ + const uint32_t status_reg = + (target->part_id == ID_STM32WL3) ? STM32WL3_FLASH_IRQRAW : STM32WB0_FLASH_STATUS; + const uint32_t error_mask = + (target->part_id == ID_STM32WL3) ? STM32WL3_FLASH_IRQ_ERROR_MASK : STM32WB0_FLASH_STATUS_ERROR_MASK; + uint32_t status = 0U; - /* Read the status register and poll for the command to have both started and completed */ + /* Poll until the command has both started and completed */ while ((status & (STM32WB0_FLASH_STATUS_CMDDONE | STM32WB0_FLASH_STATUS_CMDSTART)) != (STM32WB0_FLASH_STATUS_CMDDONE | STM32WB0_FLASH_STATUS_CMDSTART)) { - status = target_mem32_read32(target, STM32WB0_FLASH_STATUS); + status = target_mem32_read32(target, status_reg); if (target_check_error(target)) { DEBUG_ERROR("%s: error reading status\n", __func__); return false; @@ -153,21 +201,22 @@ static bool stm32wb0_flash_wait_complete(target_s *const target, platform_timeou if (timeout) target_print_progress(timeout); } - if (status & STM32WB0_FLASH_STATUS_ERROR_MASK) + if (status & error_mask) DEBUG_ERROR("%s: Flash error: %08" PRIx32 "\n", __func__, status); - /* Clear all error and status bits */ - target_mem32_write32(target, STM32WB0_FLASH_STATUS, status); - return !(status & STM32WB0_FLASH_STATUS_ERROR_MASK); + /* Clear all error and status bits by writing them back (write-1-clear) */ + target_mem32_write32(target, status_reg, status); + return !(status & error_mask); } static bool stm32wb0_enter_flash_mode(target_s *const target) { target_reset(target); - /* Make sure the Flash controller status bits are clear */ - target_mem32_write32(target, STM32WB0_FLASH_STATUS, target_mem32_read32(target, STM32WB0_FLASH_STATUS)); - /* Make sure the Flash controller is awake */ + /* Clear any pending status bits using the device-appropriate register */ + const uint32_t status_reg = + (target->part_id == ID_STM32WL3) ? STM32WL3_FLASH_IRQRAW : STM32WB0_FLASH_STATUS; + target_mem32_write32(target, status_reg, target_mem32_read32(target, status_reg)); + /* Wake the flash controller from sleep mode */ target_mem32_write32(target, STM32WB0_FLASH_COMMAND, STM32WB0_FLASH_COMMAND_WAKEUP); - /* Wait for the wakeup command to execute */ return stm32wb0_flash_wait_complete(target, NULL); } @@ -177,10 +226,10 @@ static bool stm32wb0_flash_erase(target_flash_s *const flash, const target_addr_ target_s *const target = flash->t; /* - * Take address bits [17:2] and put them in the controller address register as the start - * of the sector we want erased, the instruct the controller to start the erase + * Compute the word address relative to the flash bank base and write it to the + * address register, then instruct the controller to start the sector erase. */ - target_mem32_write32(target, STM32WB0_FLASH_ADDRESS, (addr - STM32WB0_FLASH_BANK_BASE) >> 2U); + target_mem32_write32(target, STM32WB0_FLASH_ADDRESS, (addr - flash->start) >> 2U); target_mem32_write32(target, STM32WB0_FLASH_COMMAND, STM32WB0_FLASH_COMMAND_SECTOR_ERASE); /* Wait for the operation to complete and report any errors */ return stm32wb0_flash_wait_complete(target, NULL); @@ -191,8 +240,8 @@ static bool stm32wb0_flash_write( { target_s *const target = flash->t; - /* Start by telling the controller the first address we want to program */ - target_mem32_write32(target, STM32WB0_FLASH_ADDRESS, (dest - STM32WB0_FLASH_BANK_BASE) >> 2U); + /* Start by telling the controller the first word address to program */ + target_mem32_write32(target, STM32WB0_FLASH_ADDRESS, (dest - flash->start) >> 2U); const uint32_t *const data = (const uint32_t *)src; /* Now loop through each location to write, 32 bits at a time */