Jumping to Sysmem bootloader behaves differently across two STM32F405RGT7 batches
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:
The 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 */