2022-11-24 12:38 PM
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:
Other things I've checked:
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 */
Solved! Go to Solution.
2022-11-28 08:28 AM
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.
2022-11-27 05:46 AM
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.
2022-11-27 06:38 AM
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.
2022-11-28 06:56 AM
@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:
@Uwe Bonnes That sounds like a more sustainable solution than what we're doing now. I can investigate that too.
2022-11-28 07:29 AM
@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?
2022-11-28 08:28 AM
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.
2023-01-09 06:40 AM
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).
2023-01-09 06:41 AM
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).