Add wolfBoot support for STM32G4

pull/789/head
David Garske 2026-05-12 09:49:29 -07:00 committed by Daniele Lacamera
parent 0cb6bd42ac
commit c075549ea2
10 changed files with 755 additions and 0 deletions

View File

@ -458,6 +458,12 @@ jobs:
arch: arm
config-file: ./config/examples/stm32g0.config
stm32g4_test:
uses: ./.github/workflows/test-build.yml
with:
arch: arm
config-file: ./config/examples/stm32g4.config
stm32h5_test:
uses: ./.github/workflows/test-build.yml
with:

View File

@ -167,6 +167,10 @@ ifeq ($(ARCH),ARM)
ARCH_FLASH_OFFSET=0x08000000
endif
ifeq ($(TARGET),stm32g4)
ARCH_FLASH_OFFSET=0x08000000
endif
ifeq ($(TARGET),stm32f1)
CORTEX_M3=1
NO_ARM_ASM=1

View File

@ -0,0 +1,24 @@
ARCH?=ARM
TARGET?=stm32g4
SIGN?=ECC256
HASH?=SHA256
DEBUG?=0
VTOR?=1
NO_ASM?=0
EXT_FLASH?=0
SPI_FLASH?=0
ALLOW_DOWNGRADE?=0
NVM_FLASH_WRITEONCE?=1
WOLFBOOT_VERSION?=0
V?=0
SPMATH?=1
RAM_CODE?=1
DUALBANK_SWAP?=0
# Single-bank 512KB flash, 2KB pages.
# Layout: 32K boot, 232K app, 232K update, 16K swap.
WOLFBOOT_SECTOR_SIZE?=0x800
WOLFBOOT_PARTITION_SIZE?=0x3A000
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08008000
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x08042000
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0807C000

View File

@ -45,6 +45,7 @@ This README describes configuration of supported targets.
* [STM32F4](#stm32f4)
* [STM32F7](#stm32f7)
* [STM32G0](#stm32g0)
* [STM32G4](#stm32g4)
* [STM32H5](#stm32h5)
* [STM32N6](#stm32n6)
* [STM32H7](#stm32h7)
@ -529,6 +530,79 @@ add-symbol-file test-app/image.elf 0x08008100
mon reset init
```
## STM32G4
Supports STM32G4 single-bank Category 3 parts (verified on NUCLEO-G491RE,
STM32G491RET6: 512KB flash, 96KB SRAM, Cortex-M4F).
The HAL boots at 170 MHz using HSI16 + PLL with PWR Range 1 Boost mode
(RM0440 6.1.4), 4 flash wait states, prefetch + I/D-cache enabled.
Example 512KB partitioning on STM32G491RE:
- Sector size: 2KB
- wolfBoot partition size: 32KB
- Application partition size: 232KB
```C
#define WOLFBOOT_SECTOR_SIZE 0x800 /* 2 KB */
#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x08008000
#define WOLFBOOT_PARTITION_SIZE 0x3A000 /* 232 KB */
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x08042000
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x0807C000 /* 16 KB swap */
```
### Building STM32G4
Reference configuration (see [/config/examples/stm32g4.config](/config/examples/stm32g4.config)).
You can copy this to wolfBoot root as `.config`: `cp ./config/examples/stm32g4.config .config`.
To build you can use `make`.
The TARGET for this is `stm32g4`: `make TARGET=stm32g4`.
Cortex-M4F is the default ARM core for this target.
The option `NVM_FLASH_WRITEONCE=1` is mandatory: the IAP driver writes one
64-bit doubleword per location and cannot rewrite without an erase first.
The default signing scheme is ECC256 with SHA256.
NUCLEO-G491RE has no HSE, so HSI16 is used as the PLL source. The ST-LINK
virtual COM port is wired to LPUART1 on PA2 (TX) / PA3 (RX) via AF12.
Optional boot logs over LPUART1 at 115200 8N1 are enabled with
`CFLAGS_EXTRA+=-DDEBUG_UART`.
This target is single-bank only -- keep `DUALBANK_SWAP=0`.
### STM32G4 Programming
Compile requirements: `make TARGET=stm32g4 NVM_FLASH_WRITEONCE=1`
The output is a single `factory.bin` that includes `wolfboot.bin` and
`test-app/image_v1_signed.bin` combined together. This should be programmed
to the flash start address `0x08000000`.
Flash using the STM32CubeProgrammer CLI:
```
STM32_Programmer_CLI -c port=swd -d factory.bin 0x08000000
```
### STM32G4 Debugging
Use `make DEBUG=1` and program firmware again.
Start GDB server on port 3333:
```
ST-LINK_gdbserver -d -e -r 1 -p 3333
OR
st-util -p 3333
```
```
arm-none-eabi-gdb
add-symbol-file test-app/image.elf 0x08008100
mon reset init
```
## STM32C0
Supports STM32C0x0/STM32C0x1. Instructions are for the STM Nucleo-C031C6 dev board.

284
hal/stm32g4.c 100644
View File

@ -0,0 +1,284 @@
/* stm32g4.c
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <stdint.h>
#include <image.h>
#include "stm32g4.h"
#ifndef NVM_FLASH_WRITEONCE
# error "wolfBoot STM32G4 HAL: no WRITEONCE support detected. Please define NVM_FLASH_WRITEONCE"
#endif
static RAMFUNCTION void flash_wait_complete(void)
{
while ((FLASH_SR & FLASH_SR_BSY) == FLASH_SR_BSY)
;
}
static void RAMFUNCTION flash_clear_errors(void)
{
FLASH_SR |= (FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR |
FLASH_SR_PGAERR | FLASH_SR_SIZERR | FLASH_SR_PGSERR |
FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR |
FLASH_SR_OPTVERR);
}
int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len)
{
int i = 0;
uint32_t *src, *dst;
flash_clear_errors();
FLASH_CR |= FLASH_CR_PG;
while (i < len) {
flash_clear_errors();
if ((len - i > 3) && ((((address + i) & 0x07) == 0) &&
((((uint32_t)data) + i) & 0x07) == 0)) {
src = (uint32_t *)data;
dst = (uint32_t *)address;
flash_wait_complete();
dst[i >> 2] = src[i >> 2];
dst[(i >> 2) + 1] = src[(i >> 2) + 1];
flash_wait_complete();
i += 8;
} else {
uint32_t val[2];
uint8_t *vbytes = (uint8_t *)(val);
int off = (address + i) - (((address + i) >> 3) << 3);
uint32_t base_addr = address & (~0x07); /* aligned to 64 bit */
int u32_idx = (i >> 2);
dst = (uint32_t *)(base_addr);
val[0] = dst[u32_idx];
val[1] = dst[u32_idx + 1];
while ((off < 8) && (i < len))
vbytes[off++] = data[i++];
dst[u32_idx] = val[0];
dst[u32_idx + 1] = val[1];
flash_wait_complete();
}
}
if ((FLASH_SR & FLASH_SR_EOP) == FLASH_SR_EOP)
FLASH_SR |= FLASH_SR_EOP;
FLASH_CR &= ~FLASH_CR_PG;
return 0;
}
void RAMFUNCTION hal_flash_unlock(void)
{
flash_wait_complete();
if ((FLASH_CR & FLASH_CR_LOCK) != 0) {
FLASH_KEY = FLASH_KEY1;
DMB();
FLASH_KEY = FLASH_KEY2;
DMB();
while ((FLASH_CR & FLASH_CR_LOCK) != 0)
;
}
}
void RAMFUNCTION hal_flash_lock(void)
{
flash_wait_complete();
if ((FLASH_CR & FLASH_CR_LOCK) == 0)
FLASH_CR |= FLASH_CR_LOCK;
}
int RAMFUNCTION hal_flash_erase(uint32_t address, int len)
{
uint32_t end_address;
uint32_t p;
uint32_t page_number;
uint32_t reg;
if (len == 0)
return -1;
address -= FLASHMEM_ADDRESS_SPACE;
end_address = address + len - 1;
for (p = address; p < end_address; p += FLASH_PAGE_SIZE) {
flash_wait_complete();
flash_clear_errors();
page_number = (p >> 11) & FLASH_CR_PNB_MASK;
reg = FLASH_CR & ~(FLASH_CR_PNB_MASK << FLASH_CR_PNB_SHIFT);
FLASH_CR = reg | (page_number << FLASH_CR_PNB_SHIFT) | FLASH_CR_PER;
DMB();
FLASH_CR |= FLASH_CR_STRT;
flash_wait_complete();
FLASH_CR &= ~FLASH_CR_PER;
}
return 0;
}
static void clock_pll_off(void)
{
uint32_t reg32;
/* Switch back to HSI16 as SYSCLK source before turning off the PLL. */
reg32 = RCC_CFGR;
reg32 &= ~RCC_CFGR_SW_MASK;
RCC_CFGR = (reg32 | RCC_CFGR_SW_HSI);
DMB();
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SW_MASK)
!= RCC_CFGR_SW_HSI)
;
RCC_CR &= ~RCC_CR_PLLON;
DMB();
while ((RCC_CR & RCC_CR_PLLRDY) != 0)
;
}
/* Bring SYSCLK up to 170 MHz from HSI16 + PLL with Range 1 Boost mode.
* Sequence per RM0440 - 6.1.4: PWR boost must precede flash wait states
* and PLL enable. */
static void clock_pll_on(int powersave)
{
uint32_t reg32;
uint32_t pllm = 4;
uint32_t plln = 85;
uint32_t pllr = 2;
uint32_t flash_waitstates = FLASH_ACR_LATENCY_4WS;
(void)powersave;
/* Enable PWR clock so VOS/R1MODE writes take effect. */
RCC_APB1ENR1 |= RCC_APB1ENR1_PWREN;
(void)RCC_APB1ENR1;
/* Voltage scaling Range 1 (boost-capable). */
reg32 = PWR_CR1;
reg32 = (reg32 & ~PWR_CR1_VOS_MASK) | PWR_CR1_VOS_RANGE1;
PWR_CR1 = reg32;
while ((PWR_SR2 & PWR_SR2_VOSF) != 0)
;
/* Clear R1MODE to enable Boost (required above 150 MHz). */
PWR_CR5 &= ~PWR_CR5_R1MODE;
/* HSI16 already on at reset; be explicit. */
RCC_CR |= RCC_CR_HSION;
DMB();
while ((RCC_CR & RCC_CR_HSIRDY) == 0)
;
/* Set flash wait states + caches before raising CPU frequency. */
FLASH_ACR = (FLASH_ACR & ~FLASH_ACR_LATENCY_MASK)
| flash_waitstates
| FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;
while ((FLASH_ACR & FLASH_ACR_LATENCY_MASK) != flash_waitstates)
;
/* PLL must be off before reprogramming PLLCFGR. */
RCC_CR &= ~RCC_CR_PLLON;
while ((RCC_CR & RCC_CR_PLLRDY) != 0)
;
/* PLL: SRC=HSI16, M=4 (raw 3), N=85, R=2 (raw 0), enable R output.
* VCO = 16 MHz / 4 * 85 = 340 MHz. SYSCLK = 340 / 2 = 170 MHz. */
reg32 = RCC_PLLCFGR_PLLSRC_HSI16;
reg32 |= ((pllm - 1) << RCC_PLLCFGR_PLLM_SHIFT);
reg32 |= (plln << RCC_PLLCFGR_PLLN_SHIFT);
reg32 |= (((pllr >> 1) - 1) << RCC_PLLCFGR_PLLR_SHIFT);
reg32 |= RCC_PLLCFGR_PLLREN;
RCC_PLLCFGR = reg32;
DMB();
RCC_CR |= RCC_CR_PLLON;
while ((RCC_CR & RCC_CR_PLLRDY) == 0)
;
/* Switch SYSCLK source to PLL R. */
reg32 = RCC_CFGR;
reg32 = (reg32 & ~RCC_CFGR_SW_MASK) | RCC_CFGR_SW_PLL;
RCC_CFGR = reg32;
DMB();
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SW_MASK)
!= RCC_CFGR_SW_PLL)
;
/* SYSCFG clock for any post-init configuration. */
RCC_APB2ENR |= RCC_APB2ENR_SYSCFGEN;
}
/* Optional debug UART on NUCLEO-G491RE ST-LINK VCP: LPUART1 PA2/PA3 AF12.
* Available when: wolfBoot with DEBUG_UART, or test-app (always). */
#if defined(DEBUG_UART) || !defined(__WOLFBOOT)
void uart_init(void)
{
uint32_t reg;
const uint32_t tx_pin = NUCLEO_G491_UART_TX_PIN;
const uint32_t rx_pin = NUCLEO_G491_UART_RX_PIN;
RCC_AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
(void)RCC_AHB2ENR;
RCC_APB1ENR2 |= RCC_APB1ENR2_LPUART1EN;
(void)RCC_APB1ENR2;
/* PA2, PA3 -> alternate function (MODER = 10b). */
reg = GPIOA_MODER & ~((0x3u << (tx_pin * 2)) | (0x3u << (rx_pin * 2)));
GPIOA_MODER = reg | ((0x2u << (tx_pin * 2)) | (0x2u << (rx_pin * 2)));
/* High speed (11b) so 115200 edges are clean even at 170 MHz. */
GPIOA_OSPEEDR |= (0x3u << (tx_pin * 2)) | (0x3u << (rx_pin * 2));
/* AF12 (LPUART1) on PA2 and PA3 - AFRL bits [11:8] and [15:12]. */
reg = GPIOA_AFRL & ~((0xFu << (tx_pin * 4)) | (0xFu << (rx_pin * 4)));
GPIOA_AFRL = reg | ((NUCLEO_G491_UART_AF << (tx_pin * 4))
| (NUCLEO_G491_UART_AF << (rx_pin * 4)));
LPUART1_CR1 = 0;
LPUART1_BRR = STM32G4_LPUART1_BRR(STM32G4_UART_BAUD);
LPUART1_CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
while ((LPUART1_ISR & USART_ISR_TEACK) == 0)
;
}
void uart_write(const char *buf, unsigned int len)
{
unsigned int i;
for (i = 0; i < len; i++) {
while ((LPUART1_ISR & USART_ISR_TXE) == 0)
;
LPUART1_TDR = (uint32_t)buf[i];
}
}
#endif /* DEBUG_UART || !__WOLFBOOT */
void hal_init(void)
{
clock_pll_on(0);
#if defined(DEBUG_UART) && defined(__WOLFBOOT)
uart_init();
#endif
}
void RAMFUNCTION hal_prepare_boot(void)
{
#ifdef SPI_FLASH
spi_flash_release();
#endif
#ifdef WOLFBOOT_RESTORE_CLOCK
clock_pll_off();
#endif
}

177
hal/stm32g4.h 100644
View File

@ -0,0 +1,177 @@
/* stm32g4.h
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* STM32G4 family (e.g. STM32G491RE on NUCLEO-G491RE).
* Cortex-M4F, no TrustZone. Single-bank 512 KB flash, 2 KB pages.
* Reference: RM0440. Shared register map for the wolfBoot HAL and
* the test-app -- both pull in the same definitions so wire-up
* doesn't drift between them. */
#ifndef _STM32G4_H_
#define _STM32G4_H_
#include <stdint.h>
/* Assembly helpers */
#define DMB() __asm__ volatile ("dmb")
#define ISB() __asm__ volatile ("isb")
#define DSB() __asm__ volatile ("dsb")
/* -------- RCC (AHB1 + 0x1000 = 0x40021000) -------- RM0440 - 7.4 -------- */
#define RCC_BASE (0x40021000)
#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR (*(volatile uint32_t *)(RCC_BASE + 0x08))
#define RCC_PLLCFGR (*(volatile uint32_t *)(RCC_BASE + 0x0C))
#define RCC_CRRCR (*(volatile uint32_t *)(RCC_BASE + 0x98))
#define RCC_CCIPR (*(volatile uint32_t *)(RCC_BASE + 0x88))
#define RCC_AHB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x4C))
#define RCC_APB1ENR1 (*(volatile uint32_t *)(RCC_BASE + 0x58))
#define RCC_APB1ENR2 (*(volatile uint32_t *)(RCC_BASE + 0x5C))
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x60))
#define RCC_CR_PLLRDY (1 << 25)
#define RCC_CR_PLLON (1 << 24)
#define RCC_CR_HSIRDY (1 << 10)
#define RCC_CR_HSION (1 << 8)
#define RCC_CFGR_SW_HSI 0x1
#define RCC_CFGR_SW_PLL 0x3
#define RCC_CFGR_SW_MASK 0x3
#define RCC_CFGR_SWS_SHIFT 2
#define RCC_PLLCFGR_PLLREN (1 << 24)
#define RCC_PLLCFGR_PLLR_SHIFT 25
#define RCC_PLLCFGR_PLLN_SHIFT 8
#define RCC_PLLCFGR_PLLM_SHIFT 4
#define RCC_PLLCFGR_PLLSRC_HSI16 0x2
#define RCC_CRRCR_HSI48ON (1 << 0)
#define RCC_CRRCR_HSI48RDY (1 << 1)
#define RCC_AHB2ENR_GPIOAEN (1 << 0)
#define RCC_APB1ENR1_PWREN (1 << 28)
#define RCC_APB1ENR2_LPUART1EN (1 << 0)
#define RCC_APB2ENR_SYSCFGEN (1 << 0)
/* -------- PWR (APB1 = 0x40007000) -------- RM0440 - 6.4 -------- */
#define PWR_BASE (0x40007000)
#define PWR_CR1 (*(volatile uint32_t *)(PWR_BASE + 0x00))
#define PWR_CR5 (*(volatile uint32_t *)(PWR_BASE + 0x80))
#define PWR_SR2 (*(volatile uint32_t *)(PWR_BASE + 0x14))
#define PWR_CR1_VOS_SHIFT 9
#define PWR_CR1_VOS_MASK (0x3 << PWR_CR1_VOS_SHIFT)
#define PWR_CR1_VOS_RANGE1 (0x1 << PWR_CR1_VOS_SHIFT)
#define PWR_CR5_R1MODE (1 << 0) /* 0 = Range 1 Boost */
#define PWR_SR2_VOSF (1 << 10)
/* -------- FLASH (AHB1 + 0x2000 = 0x40022000) -------- RM0440 - 3.7 -------- */
#define FLASH_BASE (0x40022000)
#define FLASH_ACR (*(volatile uint32_t *)(FLASH_BASE + 0x00))
#define FLASH_KEY (*(volatile uint32_t *)(FLASH_BASE + 0x08))
#define FLASH_SR (*(volatile uint32_t *)(FLASH_BASE + 0x10))
#define FLASH_CR (*(volatile uint32_t *)(FLASH_BASE + 0x14))
#define FLASHMEM_ADDRESS_SPACE (0x08000000)
#define FLASH_PAGE_SIZE (0x800) /* 2KB */
#define FLASH_ACR_LATENCY_MASK (0x7)
#define FLASH_ACR_PRFTEN (1 << 8)
#define FLASH_ACR_ICEN (1 << 9)
#define FLASH_ACR_DCEN (1 << 10)
#define FLASH_ACR_LATENCY_4WS (0x4)
/* G4 has a single BSY at bit 16 (no BSY1/BSY2 like G0). */
#define FLASH_SR_BSY (1 << 16)
#define FLASH_SR_OPTVERR (1 << 15)
#define FLASH_SR_RDERR (1 << 14)
#define FLASH_SR_FASTERR (1 << 9)
#define FLASH_SR_MISERR (1 << 8)
#define FLASH_SR_PGSERR (1 << 7)
#define FLASH_SR_SIZERR (1 << 6)
#define FLASH_SR_PGAERR (1 << 5)
#define FLASH_SR_WRPERR (1 << 4)
#define FLASH_SR_PROGERR (1 << 3)
#define FLASH_SR_OPERR (1 << 1)
#define FLASH_SR_EOP (1 << 0)
/* G491 (Cat 3) is single-bank: PNB is 8 bits at [10:3], no BKER. */
#define FLASH_CR_LOCK (1UL << 31)
#define FLASH_CR_STRT (1 << 16)
#define FLASH_CR_PER (1 << 1)
#define FLASH_CR_PG (1 << 0)
#define FLASH_CR_PNB_SHIFT 3
#define FLASH_CR_PNB_MASK 0xFF
#define FLASH_KEY1 (0x45670123)
#define FLASH_KEY2 (0xCDEF89AB)
/* -------- GPIOA (AHB2 = 0x48000000) -------- RM0440 - 9.4 -------- */
#define GPIOA_BASE (0x48000000)
#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_OTYPER (*(volatile uint32_t *)(GPIOA_BASE + 0x04))
#define GPIOA_OSPEEDR (*(volatile uint32_t *)(GPIOA_BASE + 0x08))
#define GPIOA_PUPDR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))
#define GPIOA_ODR (*(volatile uint32_t *)(GPIOA_BASE + 0x14))
#define GPIOA_BSRR (*(volatile uint32_t *)(GPIOA_BASE + 0x18))
#define GPIOA_AFRL (*(volatile uint32_t *)(GPIOA_BASE + 0x20))
#define GPIOA_AFRH (*(volatile uint32_t *)(GPIOA_BASE + 0x24))
/* -------- LPUART1 (APB1 = 0x40008000) -------- RM0440 - 38.7 -------- */
#define LPUART1_BASE (0x40008000)
#define LPUART1_CR1 (*(volatile uint32_t *)(LPUART1_BASE + 0x00))
#define LPUART1_BRR (*(volatile uint32_t *)(LPUART1_BASE + 0x0C))
#define LPUART1_ISR (*(volatile uint32_t *)(LPUART1_BASE + 0x1C))
#define LPUART1_TDR (*(volatile uint32_t *)(LPUART1_BASE + 0x28))
#define USART_CR1_UE (1 << 0)
#define USART_CR1_RE (1 << 2)
#define USART_CR1_TE (1 << 3)
#define USART_ISR_TEACK (1 << 21)
#define USART_ISR_TXE (1 << 7)
/* -------- SYSCLK / boot defaults -------- */
#define STM32G4_SYSCLK_HZ (170000000UL)
#define STM32G4_UART_BAUD (115200UL)
/* LPUART_BRR = (256 * f_LPUART) / baud. PCLK1 = SYSCLK on default prescalers. */
#define STM32G4_LPUART1_BRR(baud) \
((uint32_t)(((uint64_t)256u * STM32G4_SYSCLK_HZ) / (baud)))
/* -------- NUCLEO-G491RE board specifics -------- */
#define NUCLEO_G491_LED_LD2_PIN (5) /* PA5 */
#define NUCLEO_G491_UART_TX_PIN (2) /* PA2 - LPUART1_TX */
#define NUCLEO_G491_UART_RX_PIN (3) /* PA3 - LPUART1_RX */
#define NUCLEO_G491_UART_AF (12)
/* Public helpers implemented in hal/stm32g4.c. Available to wolfBoot when
* DEBUG_UART is set, and to the test-app at all times. */
void uart_init(void);
void uart_write(const char *buf, unsigned int len);
#endif /* _STM32G4_H_ */

50
hal/stm32g4.ld 100644
View File

@ -0,0 +1,50 @@
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00018000 /* 96 KB on G491RE */
}
SECTIONS
{
.text :
{
_start_text = .;
KEEP(*(.isr_vector))
*(.text*)
*(.rodata*)
. = ALIGN(4);
_end_text = .;
} > FLASH
.edidx :
{
. = ALIGN(4);
*(.ARM.exidx*)
} > FLASH
_stored_data = .;
.data : AT (_stored_data)
{
_start_data = .;
KEEP(*(.data*))
. = ALIGN(4);
KEEP(*(.ramcode))
. = ALIGN(4);
_end_data = .;
} > RAM
.bss (NOLOAD) :
{
_start_bss = .;
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_end_bss = .;
__bss_end__ = .;
_end = .;
} > RAM
. = ALIGN(4);
}
END_STACK = ORIGIN(RAM) + LENGTH(RAM);

View File

@ -919,6 +919,23 @@ endif
ifeq ($(TARGET),stm32g0)
CFLAGS+=-DNVM_FLASH_WRITEONCE=1
endif
ifeq ($(TARGET),stm32g4)
CFLAGS+=-DNVM_FLASH_WRITEONCE=1
CFLAGS+=-I../hal
LDFLAGS+=-Wl,-gc-sections
# Test app uses printf via syscalls.o (routes _write -> uart_write).
# syscalls.c references uart_write/uart_vprintf from string.o; string.o
# is already pulled in when DEBUG_UART=1. Pull it in unconditionally so
# printf is available even when DEBUG_UART is off.
ifeq ($(DEBUG_UART),)
APP_OBJS+=../src/string.o
endif
APP_OBJS+=syscalls.o
# _Min_Heap_Size is consumed by syscalls.c _sbrk(); ARM.ld does not
# define a heap, so anchor it at zero. _sbrk is gc'd when malloc is
# unused, but the symbol must still resolve.
LDFLAGS+=-Wl,--defsym=_Min_Heap_Size=0
endif
ifeq ($(TARGET),stm32c0)
CFLAGS+=-DNVM_FLASH_WRITEONCE=1
endif

View File

@ -0,0 +1,94 @@
/* app_stm32g4.c
*
* Test bare-metal application for the STM32G4 (NUCLEO-G491RE).
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <stdint.h>
#include <stdio.h>
#include "hal.h"
#include "stm32g4.h"
#include "wolfboot/wolfboot.h"
#include "target.h"
#ifdef TARGET_stm32g4
static void led_init(void)
{
uint32_t reg;
RCC_AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
reg = RCC_AHB2ENR;
(void)reg;
reg = GPIOA_MODER & ~(0x3u << (NUCLEO_G491_LED_LD2_PIN * 2));
GPIOA_MODER = reg | (0x1u << (NUCLEO_G491_LED_LD2_PIN * 2));
GPIOA_PUPDR &= ~(0x3u << (NUCLEO_G491_LED_LD2_PIN * 2));
}
static void led_on(void)
{
GPIOA_BSRR = (1u << NUCLEO_G491_LED_LD2_PIN);
}
static void led_off(void)
{
GPIOA_BSRR = (1u << (NUCLEO_G491_LED_LD2_PIN + 16));
}
static void busy_delay(uint32_t count)
{
volatile uint32_t i;
for (i = 0; i < count; i++)
__asm__ volatile ("nop");
}
void main(void)
{
uint32_t version;
uint32_t on_ticks, off_ticks;
hal_init();
led_init();
uart_init();
version = wolfBoot_current_firmware_version();
printf("TEST APP\r\n");
printf("App version: %lu\r\n", (unsigned long)version);
/* v1: slow blink. v2+: fast blink + mark update successful. */
if (version >= 2) {
wolfBoot_success();
printf("Update confirmed.\r\n");
on_ticks = 200000;
off_ticks = 200000;
} else {
on_ticks = 600000;
off_ticks = 600000;
}
while (1) {
led_on();
busy_delay(on_ticks);
led_off();
busy_delay(off_ticks);
}
}
#endif /* TARGET_stm32g4 */

View File

@ -230,3 +230,28 @@ void boot_led_off(void)
}
#endif /* TARGET_stm32l4 */
#ifdef TARGET_stm32g4
#include "stm32g4.h"
/* NUCLEO-G491RE user LED LD2 on PA5. */
void boot_led_on(void)
{
uint32_t reg;
const uint32_t pin = NUCLEO_G491_LED_LD2_PIN;
RCC_AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
reg = RCC_AHB2ENR; /* read-back so GPIOA clock is live before MODER write */
(void)reg;
reg = GPIOA_MODER & ~(0x03 << (pin * 2));
GPIOA_MODER = reg | (1 << (pin * 2));
reg = GPIOA_PUPDR & ~(0x03 << (pin * 2));
GPIOA_PUPDR = reg | (1 << (pin * 2));
GPIOA_BSRR = (1 << pin); /* BSRR is write-only; bare assignment, not |= */
}
void boot_led_off(void)
{
GPIOA_BSRR = (1 << (NUCLEO_G491_LED_LD2_PIN + 16));
}
#endif /* TARGET_stm32g4 */