Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 83 additions & 34 deletions src/target/stm32wb0.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -52,50 +57,71 @@
#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 */
DEBUG_ERROR("calloc: failed in %s\n", __func__);
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;
Expand All @@ -122,52 +148,75 @@ 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;
}
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);
}

Expand All @@ -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);
Expand All @@ -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 */
Expand Down