2024-05-01 10:47 PM - edited 2024-05-01 11:05 PM
Heyho FLASH experts!
within the last few weeks I was able to get an ethernet bootloader running on a H735 discovery kit.
Internal flash is partitioned into area starting with the bootloader (256kB, including ETH), then comes the application (rest: 768kB).
No OS, (almost) no HAL. A kind of state machine / simple task scheduler is used in main.
Bootloader (BL) and application offer STM32*.bin File download via http POST into HyperRAM, file gets checked there, then written into (octal) SPI flash.
Then from app jump to BL is possible, or BL finds new file in flash at next start and updates the internal app flash from SPI flash, checks and jumps into app.
So far so good, everything's working smoothly.
Here comes the BUT...
I had the idea that the app should be able to update the bootloader, so I use the same internal flash write functions and flash write state machine as I use in the bootloader.
As soon as the app starts writing internal flash, the CPU hangs.
I have a hard fault handler that saves all relevant registers to no-init SRAM, after reset these show BFARVALID and PERCISERR, but with some crazy addresses which don't make sense at all.
Debugging: I cannot get it to work, the IDE debugger can't handle the split flash with my settings...
What's worrying me is that it works 100% in bootloader, but the same functions hang in the app, so that I'm afraid that the same problem might occur later on in the bootloader.
The big difference between bootloader and app:
-> the app is using many more peripherals with DMA and interrupts (ADC, TIM, SAI, I2S, CAN)
Here's the flash write function that's killing it, at first it was without the interrupt disable - which did not help:
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* FLASH write page
*/
/**
* @brief write page / word of 256 bits = 8x 32bit words
* waiting for programming page done in state machine
* only writing BOOTLOADER flash from application
* u8WrBlock wait here for finish
* write settings elsewhere,
* for now fixed page buffer is used
* @retval 0 = ok
*/
uint8_t FlashIntWrPage(uint8_t u8WrBlock)
{
uint8_t u8RetVal = 0;
/* check source pointer */
if( NULL == sFlashIntCtl.pu8WrBuf )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): pu8WrBuf = NULL\n\r");
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* check flash destination address */
if( sFlashIntCtl.u32WrAddr < FLASH_INT_ADDR_START_BOOT ||
sFlashIntCtl.u32WrAddr >= FLASH_INT_ADDR_END_BOOT )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): u32WrAddr out of range\n\r");
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* check LOCK bit */
if( FLASH->CR1 & FLASH_CR_LOCK )
{
if( FlashIntUnlock() != HAL_OK )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): FlashIntUnlock()\n\r");
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
}
/* check if last operation done */
if( FlashIntWaitLastOp(FLASH_INT_TO_BUSY_MS) != HAL_OK )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): QW\n\r");
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* wait for BUSY release to write registers */
if( FlashIntCheckBusyTo(FLASH_INT_TO_BUSY_MS) != HAL_OK )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): BUSY\n\r");
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* reset some error flags */
/* get error flags */
uint32_t u32RegVal = (FLASH->SR1 & FLASH_INT_SR_ERR_ALL);
/* in case of error reported in Flash SR1 */
if( u32RegVal != 0 )
{
/* save error code*/
sFlashIntCtl.u32SrErrors |= u32RegVal;
/* clear error programming flags */
FLASH->CCR1 = u32RegVal;
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): SR errors = %08lX\n\r", sFlashIntCtl.u32SrErrors);
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* check SR1 once more */
u32RegVal = FLASH->SR1;
if( u32RegVal != 0 )
{
#if DEBUG_FLASH_INT
uart_printf("# ERR: FlashIntWrPage(): SR = %08lX\n\r", u32RegVal);
#endif /* DEBUG_FLASH_INT */
return HAL_ERROR;
}
/* +++++++++++++++++++++++++++++++++++++++++++ */
/* start programming */
__disable_irq();
__IO uint32_t *pu32SrcAddr = (__IO uint32_t *)sFlashIntCtl.pu8WrBuf;
__IO uint32_t *pu32DstAddr = (__IO uint32_t *)sFlashIntCtl.u32WrAddr;
uint8_t u8RowIndex = FLASH_NB_32BITWORD_IN_FLASHWORD; /* = 8 -> * 32= 256 */
/* set PG program enable bit */
FLASH->CR1 |= FLASH_CR_PG;
__ISB();
__DSB();
/* program the flash word */
do
{
*pu32DstAddr = *pu32SrcAddr;
pu32DstAddr++;
pu32SrcAddr++;
u8RowIndex--;
} while( u8RowIndex != 0 );
__ISB();
__DSB();
__enable_irq();
/* wait in state machine ? */
#if( 1 )
if( FLASH_INT_WRITE_BLOCK == u8WrBlock )
{
/* wait for last operation to be completed */
u8RetVal = FlashIntWaitLastOp(FLASH_INT_TO_PROG_PAGE);
/* if the program operation is completed, disable PG bit */
FLASH->CR1 &= ~FLASH_CR_PG;
}
#endif
return u8RetVal;
}
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/**
* @brief wait for a FLASH operation to complete
* u32timeout flash operation timeout
* @retval 0 = okay
*/
uint8_t FlashIntWaitLastOp(uint32_t u32Timeout)
{
uint32_t u32TickStart = HAL_GetTick();
/* wait for the FLASH operation to complete by polling on QW flag to be reset.
* even if the FLASH operation fails, the QW flag will be reset and an error
* flag will be set
*/
while( FLASH->SR1 & FLASH_SR_QW )
{
if( ((HAL_GetTick() - u32TickStart) > u32Timeout) || (0 == u32Timeout) )
{
return HAL_TIMEOUT;
}
}
/* get error flags */
uint32_t u32SrErrors = (FLASH->SR1 & FLASH_INT_SR_ERR_ALL);
/* in case of error reported in Flash SR1 */
if( u32SrErrors != 0 )
{
/* save error code*/
sFlashIntCtl.u32SrErrors |= u32SrErrors;
/* clear error programming flags */
FLASH->CCR1 = u32SrErrors;
return HAL_ERROR;
}
if( FLASH->SR1 & FLASH_SR_EOP )
{
/* clear FLASH End of Operation bit */
FLASH->CCR1 = FLASH_CCR_CLR_EOP;
}
return HAL_OK;
}
void FlashIntWrDone(void)
{
if( FLASH->SR1 & FLASH_SR_EOP )
{
/* clear FLASH End of Operation pending bit */
FLASH->CCR1 = FLASH_CCR_CLR_EOP;
}
/* disable PG bit */
FLASH->CR1 &= ~FLASH_CR_PG;
}
The same functions are used in the bootloader, so it must be somehow the combination of the state machine with the many other peripherals running in the application.
In main is a state machine calling the flash update control function FlashIntUpdCtrl() regularly, which uses another state machine basically working like this, until the complete SPI flash image is written to internal flash:
FLINT_STATE_WRITE:
FlashIntWrPage() okay -> u8FlashIntState = FLINT_STATE_WR_WAIT
FLINT_STATE_WR_WAIT:
FlashIntCheckLastOp() okay -> u8FlashIntState = FLINT_STATE_WRITE
FlashIntCheckLastOp() is like the wait function above, but without any while, just checking the flash flags, then returning.
Anybody any ideas?
Thanks in advance!
2024-05-01 10:50 PM
I forgot to mention:
When I do not use the flash state machine, but use a function to write the internal flash in on go within one blocking while loop (using the same write / wait functions as above), then it's working.
2024-05-02 12:46 AM
Vector Table and SCB VTOR pointed at somewhere safe and not erased in process?
Flash block sizes accounted for?
Will look over later when on computer.
2024-05-02 02:58 AM
Thanks for your reply.
Yes, VTOR moved accordingly and block sizes in control (for erase only).
Basically almost everything is working, here again the short form:
I'd like to find out why the state machine scheme doesn't work in app, using the same functions - but with much more stuff going on in the app.