Skip to main content
Visitor II
November 24, 2022
Solved

Jumping to Sysmem bootloader behaves differently across two STM32F405RGT7 batches

  • November 24, 2022
  • 6 replies
  • 3291 views

The Question (since this is a little long): Is there any known interaction between the WWDG timer and the bootloader located in System Memory? Is there any known differences in the F405 revisions that could have an interaction between the WWDG and bootloader?

Hello, I'm working on a product that receives updates via the DFU bootloader. I have two batches of the same board received from the same manufacturer, and one batch is behaving strangely as I attempt to use this method to jump to the bootloader.

The code is based off of this tutorial, and works flawlessly on older batches of the board.

However, on a new batch, instead of jumping to system memory for DFU upload, the board will simply reset from the WWDG IRQ. The overall program feeds the WWDG every couple milliseconds, but by the time the program calls the SysMemBootJump() function, it should have disabled the RCC timers and the WWDG interrupt.

In order to demonstrate the issue, I'll attach a sample project I made in CubeIDE that produces the issue. The program will start with a GPIO set low, and after waiting for short period, will set it high, then jump to the bootloader. On the affected batch of boards (the bottom signal) the GPIO line will dip, indicating a hardware reset. The top signal is the a board operating normally, holding the GPIO high while it waits in system memory:

0693W00000WJSWaQAP.jpgThe only difference between these two boards is the batch number of the STM32F405RGT7 itself. The normal board has the following manufacturing information:

ID: STM32F405RGT7

Rev: y (0x100F)

Manufacturing information: GQ2CN VG CHN GQ 021 17

And the affected board:

ID: STM32F405RGT7

Rev: y (0x100F)

Manufacturing information: GQ2AD VG CHN GQ 213 18

It's pretty easy to dismiss this issue as a difference in the two batches of PCB, but I have been able to verify that the normal operations of this part are meeting expectations, it's just this WWDG/DFU situation.

Related Behaviours:

  • The affected board will boot to sysmem and allow DFU if the board is powered up and BOOT0 is pulled high
  • Explicitly disabling the WWDG peripheral clock (RCC->APB1RSTR &= ~RCC_APB1Periph_WWDG) before disabling interrupts via CPSID/__disable_irq() will allow the board to jump to sysmem

Other things I've checked:

  • Greymarket clones: chips sampled from the affected batch have unique chip ID values and internal serial numbers.
  • ARM revision: both affected and unaffected chips return 0x410fc241 from the SCB->CPUID register.
  • Wonky clocks/resonators: the boards are driven by a 25MHz external oscillator, and part of the program drives a PWM (on TIM1) signal at 533kHz. If there was a difference with the resonator the PWM signal should be affected, but it is not.
  • Running the same test code on an unrelated part with a similar processor (an STM32407VGTx): the F407 behaves the same as the "normal" F405

I'm truly at a loss here. This is a part that we've had in operation for several years with no revisions. But one batch of PCB's where the only visible change is a different batch of F405's is showing different behaviour that is completely reproducible across the entire batch. I really don't want to use the words "silicon error" but at this point I'm running out of ideas. I simply have no idea why the two chips are behaving differently. I couldn't find anything in the errata or AN2606 about WWDG and how it relates to the sysmem bootloader. My colleagues are similarly stumped.

PS, here's the main.c of the CubeMX project I mentioned. Running this on an F405RGT7 GQ2CN VG CHN GQ 021 17 will allow it to enter the bootloader, but running this same code on a GQ2AD VG CHN GQ 213 18 will result in the board rebooting.

#include "main.h"
 
WWDG_HandleTypeDef hwwdg;
 
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_WWDG_Init(void);
 
#define SYSMEM_ADDRESS (uint32_t)0x1FFF0000
void JumpToBootloader(void)
{
 void (*SysMemBootJump)(void);
 // Disable all interrupts
 __disable_irq();
 
 // Disable systick timer and reset it to default values
 SysTick->CTRL = 0;
 SysTick->LOAD = 0;
 SysTick->VAL = 0;
 
 // Disable RCC, set it to default (after reset) settings
 /* Set HSION bit */
 RCC->CR |= (uint32_t)0x00000001;
 
 /* Reset CFGR register */
 RCC->CFGR = 0x00000000;
 
 /* Reset HSEON, CSSON, PLLON, PLLI2S and PLLSAI(STM32F42/43xxx devices) bits */
 RCC->CR &= (uint32_t)0xEAF6FFFF;
 
 /* Reset PLLCFGR register */
 RCC->PLLCFGR = 0x24003010;
 
 /* Reset PLLI2SCFGR register */
 RCC->PLLI2SCFGR = 0x20003000;
 
 /* Reset HSEBYP bit */
 RCC->CR &= (uint32_t)0xFFFBFFFF;
 
 /* Disable all interrupts */
 RCC->CIR = 0x00000000;
 
 /**
 * Remap system memory to address 0x0000 0000 in address space
 * For STM32F4xx, MEMRMP register in SYSCFG is used (bits[1:0])
 */
 SYSCFG->MEMRMP = 0x01;
 // Set vector table offset to 0
 SCB->VTOR = 0;
 
 /**
 * Set jump memory location for system memory
 * Use address with 4 bytes offset which specifies jump location where program starts
 */
 SysMemBootJump = (void (*)(void)) (*((uint32_t *)(SYSMEM_ADDRESS + 4)));
 
 /**
 * Set main stack pointer.
 * This step must be done last otherwise local variables in this function
 * don't have proper value since stack pointer is located on different position
 *
 * Set direct address location which specifies stack pointer in SRAM location
 */
 __set_MSP(*(uint32_t *)(SYSMEM_ADDRESS));
 
 // Start system memory execution
 SysMemBootJump();
 
 while (1)
 {
 }
}
 
int main(void)
{
 
 HAL_Init();
 
 SystemClock_Config();
 
 MX_GPIO_Init();
 MX_WWDG_Init();
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
 int i = 0;
 
 while (1)
 {
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET);
 HAL_WWDG_Refresh(&hwwdg);
 HAL_Delay(5);
 i++;
 if (i == 5) {
 JumpToBootloader();
 }
 }
}
 
void SystemClock_Config(void)
{
 RCC_OscInitTypeDef RCC_OscInitStruct = {0};
 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
 __HAL_RCC_PWR_CLK_ENABLE();
 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
 
 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
 RCC_OscInitStruct.HSEState = RCC_HSE_ON;
 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
 RCC_OscInitStruct.PLL.PLLM = 25;
 RCC_OscInitStruct.PLL.PLLN = 336;
 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
 RCC_OscInitStruct.PLL.PLLQ = 4;
 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
 {
 Error_Handler();
 }
 
 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
 
 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
 {
 Error_Handler();
 }
}
 
static void MX_WWDG_Init(void)
{
 
 hwwdg.Instance = WWDG;
 hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
 hwwdg.Init.Window = 127;
 hwwdg.Init.Counter = 127;
 hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
 if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
 {
 Error_Handler();
 }
}
 
static void MX_GPIO_Init(void)
{
 GPIO_InitTypeDef GPIO_InitStruct = {0};
 
 __HAL_RCC_GPIOH_CLK_ENABLE();
 __HAL_RCC_GPIOB_CLK_ENABLE();
 
 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET);
 
 GPIO_InitStruct.Pin = GPIO_PIN_10;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
 
void Error_Handler(void)
{
 __disable_irq();
 while (1)
 {
 }
}
 
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */

    This topic has been closed for replies.
    Best answer by Tesla DeLorean

    The processor doesn't normally start with the interrupts disabled/masked, and the code similarly doesn't assume that they are

    SYSCFG clock is enabled via AHB2ENR, the memory won't remap without it.

    This is the method I used on F2 and F4 boards a decade ago, resets and vectors into ROM, in reset conditions

    ;*****************************************************************************
     
    startup.s
     
    Reset_Handler PROC
     EXPORT Reset_Handler
     IMPORT SystemInit
     IMPORT __main
     
     LDR R0, =0x2001FFFC ; TOP OF RAM
     LDR R1, =0xDEADBEEF
     LDR R2, [R0, #0]
     CMP R1, R2
     BNE NotDfuSe
     STR R0, [R0, #0] ; Invalidate
     LDR R0, =0x40023844 ; RCC_APB2ENR
     LDR R1, =0x00004000 ; ENABLE SYSCFG CLOCK
     STR R1, [R0, #0]
     LDR R0, =0x40013800 ; SYSCFG_MEMRMP
     LDR R1, =0x00000001 ; MAP ROM AT ZERO
     STR R1, [R0, #0]
     LDR R0, =0x1FFF0000 ; ROM BASE
     LDR SP,[R0, #0]
     LDR R0,[R0, #4]
     BX R0 ; BOOT ROM
    NotDfuSe
     
     LDR R0, =SystemInit
     BLX R0
     
     LDR R0, =__main
     BX R0
     
     ENDP ; sourcer32@gmail.com
     
    ;*****************************************************************************
     
    C Code
     
     printf("Entering Boot Loader..\r\n");
     
     *((unsigned long *)TOP_OF_RAM) = 0xDEADBEEF;
     
     __DSB();
     
     NVIC_SystemReset();
     
    ;*****************************************************************************

    Perhaps the SPI and I2C pins being monitored now are moving in your design, or new USART were added.

    6 replies

    Graduate II
    November 27, 2022

    I have no direct answer to your question. But I strongly suggest you set some marker in a memory that is not initilized by reset. Then you call NVIC_System reset and in the startup code you look up that marker and start the bootloader there.

    Graduate II
    November 27, 2022

    Don't use __disable_irq(); there isn't a counter party to this in the loader to enable, and the task is to TURN OFF the interrupts YOU are using, at the peripheral level.

    Check the version of the LOADER, I think STM32 Cube Programmer reports this now

    SYSCFG has a clock it needs to be enabled to work

    Watch what GNU/GCC generates with the stack frame, changing the stack pointer mid-function can be problematic depending on whether registers or stack has been used for auto/local variables.

    TWall.2Author
    Visitor II
    November 28, 2022

    @Community member​  Considering the same code results in different behaviour across two batches of boards I'm hesitant to agree it's a GNU/GCC issue. I'll take a look at the loader, but I have a couple other questions:

    • "SYSCFG has a clock it needs to be enabled to work"
      • Is there a source I can find on this? Looking through the clock tree and SYSCFG chapters in the TRM, this statement leaves me a little perplexed. Is it derived from the SysClck?
    • "Don't use __disable_irq();"
      • Agreed, we should be disabling the interrupts we use directly.
      • (this is just a curiosity question) I'm not sure what you meant " there isn't a counter party to this in the loader to enable". That if __disable_irq() is used, then the bootloader doesn't re-enable any interrupts (even for use internally)?

    @Uwe Bonnes​ That sounds like a more sustainable solution than what we're doing now. I can investigate that too.

    TWall.2Author
    Visitor II
    November 28, 2022

    @Community member​ The boards use two different versions of the bootloader. The older F405 uses bootloader version 0x31 and the newer (non-cooperative) one uses 0x91. The only difference between the two according to AN2606 is that 0x91 supports SPI and I2C which we aren't using anyway, so it's moot. Are there any other differences that you could be aware of?

    Graduate II
    November 28, 2022

    The processor doesn't normally start with the interrupts disabled/masked, and the code similarly doesn't assume that they are

    SYSCFG clock is enabled via AHB2ENR, the memory won't remap without it.

    This is the method I used on F2 and F4 boards a decade ago, resets and vectors into ROM, in reset conditions

    ;*****************************************************************************
     
    startup.s
     
    Reset_Handler PROC
     EXPORT Reset_Handler
     IMPORT SystemInit
     IMPORT __main
     
     LDR R0, =0x2001FFFC ; TOP OF RAM
     LDR R1, =0xDEADBEEF
     LDR R2, [R0, #0]
     CMP R1, R2
     BNE NotDfuSe
     STR R0, [R0, #0] ; Invalidate
     LDR R0, =0x40023844 ; RCC_APB2ENR
     LDR R1, =0x00004000 ; ENABLE SYSCFG CLOCK
     STR R1, [R0, #0]
     LDR R0, =0x40013800 ; SYSCFG_MEMRMP
     LDR R1, =0x00000001 ; MAP ROM AT ZERO
     STR R1, [R0, #0]
     LDR R0, =0x1FFF0000 ; ROM BASE
     LDR SP,[R0, #0]
     LDR R0,[R0, #4]
     BX R0 ; BOOT ROM
    NotDfuSe
     
     LDR R0, =SystemInit
     BLX R0
     
     LDR R0, =__main
     BX R0
     
     ENDP ; sourcer32@gmail.com
     
    ;*****************************************************************************
     
    C Code
     
     printf("Entering Boot Loader..\r\n");
     
     *((unsigned long *)TOP_OF_RAM) = 0xDEADBEEF;
     
     __DSB();
     
     NVIC_SystemReset();
     
    ;*****************************************************************************

    Perhaps the SPI and I2C pins being monitored now are moving in your design, or new USART were added.

    TWall.2Author
    Visitor II
    January 9, 2023

    Again for future readers: this wasn't the actual solution, but it is better than the original method I was using (linked in the first post in this thread).

    TWall.2Author
    Visitor II
    January 9, 2023

    I suppose I should update this! I hate finding forum posts that don't end in a resolution or just "I fixed it myself" with no context.

    I tried the methods outlined above on the newer batch of chips to no success. Which still strikes me as odd, but either way, on we go. I also swapped a newer F405 that exhibits this issue with an older F405 on the same PCB just to make sure it wasn't a quirk from a particular batch of PCB's. The issue was still manifesting with the newer chip, and was no longer present on the PCB that had the older chip swapped onto it. We're investigating the supply chain for these newer chips, but I'm 99% certain they aren't counterfeit to begin with. Either way, I do have the hacky solution where I manually disable the WWDG clock before proceeding with the rest of the shutdown, which is what we had to continue with due to timeline pressure.

    Thanks for the pointers everyone. I'll be implementing the more correct bootloader jump sequence in future designs (and hope I never run into something like this ever again).