Skip to content

cpu/stm32: fix STM32G4 flash writes failing after motor driver init #22151

@mlecriva

Description

@mlecriva

Description

On STM32G4, flash write and erase operations fail silently after
hardware timer peripherals (e.g. PWM for motor control) have been
initialized. The flash status register (FLASH->SR) retains sticky
error flags from motor_driver_init()pwm_init(), and _wait_for_pending_operations()
does not clear them before proceeding.

Note: Other STM32 families are likely affected by the same issue but the current fix is scoped to STM32G4 only, as I don't have the hardware to test on other targets.

Steps to reproduce the issue

  1. Build and flash the following minimal test on an STM32G4-based
    board (e.g. nucleo-g474re):
#include <stdio.h>
#include <string.h>

#include "board.h"
#include "motor_driver.h"
#include "periph/flashpage.h"

#define TEST_PAGE    (FLASHPAGE_NUMOF - 1)
#define TEST_PATTERN 0xBB

static const motor_driver_params_t motion_motors_params = {
    .mode = MOTOR_DRIVER_1_DIR_BRAKE,
    .pwm_dev = 0,
    .pwm_mode = PWM_LEFT,
    .pwm_frequency = 20000U,
    .pwm_resolution = 500U,
    .brake_inverted = true,
    .enable_inverted = false,
    .nb_motors = 2,
    .motors = {
        { .pwm_channel = 0, .gpio_enable = GPIO_PIN(PORT_A, 10),
          .gpio_dir0 = GPIO_PIN(PORT_C, 6),
          .gpio_brake = GPIO_PIN(PORT_C, 8), .gpio_dir_reverse = 1 },
        { .pwm_channel = 1, .gpio_enable = GPIO_PIN(PORT_B, 1),
          .gpio_dir0 = GPIO_PIN(PORT_B, 10),
          .gpio_brake = GPIO_PIN(PORT_B, 2), .gpio_dir_reverse = 0 },
    },
    .motor_set_post_cb = NULL,
};

static motor_driver_t motor_driver;
static uint8_t buf[FLASHPAGE_SIZE]
    __attribute__((aligned(FLASHPAGE_WRITE_BLOCK_ALIGNMENT)));

int main(void)
{
    printf("FLASH->SR before init:    0x%08lX\n", (unsigned long)FLASH->SR);

    motor_driver_init(&motor_driver, &motion_motors_params);

    printf("FLASH->SR after PWM init: 0x%08lX\n", (unsigned long)FLASH->SR);

    memset(buf, TEST_PATTERN, sizeof(buf));
    int rc = flashpage_write_and_verify(TEST_PAGE, buf);
    printf("write_and_verify: %s\n", (rc == FLASHPAGE_OK) ? "OK" : "FAIL");

    printf("FLASH->SR after flash op: 0x%08lX\n", (unsigned long)FLASH->SR);

    memset(buf, 0x00, sizeof(buf));
    flashpage_read(TEST_PAGE, buf);
    printf("first byte: 0x%02X (expected 0x%02X)\n", buf[0], TEST_PATTERN);

    return 0;
}
  1. Without the fix:
FLASH->SR before init:    0x00000000
FLASH->SR after PWM init: 0x000000A0  <-- sticky error flags set
write_and_verify: FAIL
FLASH->SR after flash op: 0x000000A0  <-- flags still present
first byte: 0x00 (expected 0xBB)      <-- stale data
  1. With the fix applied:
FLASH->SR before init:    0x00000000
FLASH->SR after PWM init: 0x000000A0  <-- flags set by PWM init
write_and_verify: OK
FLASH->SR after flash op: 0x00000000  <-- flags cleared
first byte: 0xBB (expected 0xBB)      <-- OK

Expected results

Flash write/erase operations succeed and persist data correctly,
regardless of which peripherals were initialized beforehand.

Actual results

Flash operations fail and flashpage_write_and_verify correctly
reports the error. However, the failure is unrecoverable: FLASH->SR
contains stale error flags (e.g. PGAERR, PGSERR) set as a side
effect of timer peripheral initialization, and because these sticky
flags are never cleared, all subsequent flash operations
keep failing permanently

Versions

  • Board: STM32G4-based (e.g. nucleo-g474re)
  • RIOT: master
  • Affected file: cpu/stm32/periph/flash_common.c

Note: the example code was generated by an LLM (Claude). The English content of this issue was proofread/corrected by an LLM. The bug investigation and fix in the attached MR were done manually, inspired by the ST HAL implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: cpuArea: CPU/MCU portsType: bugThe issue reports a bug / The PR fixes a bug (including spelling errors)

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions