I’m making a bootloader for a SAMD51P20A, based on a previous one I made for a SAMD21. I have flash block erase and page writes working, and the application & bootloader use a few breadcrumbs in the last flash page to setup various bootloader operations. It all works well, I can erase the application code space, write application code to flash, then jump to the flash, and it runs.
The problem comes if I write a page of flash after erasing it, then try to read the value back from the newly written flash page. The value read back from a location in flash doesn’t reflect the newly written value, though the write does appear to have worked, as after the next reset, the written value is read correctly. I can step through in the debugger, and verify that the flash block erase and page writes work, by reading the flash directly, but in code, reading the same flash location shows a different value.
The minimal code to reproduce this is
ERROR_CODE_T FlashProgTestRead(uint32_t * bootcount)
{
// disable Flash read caches
NVMCTRL->CTRLA.bit.CACHEDIS0 = 1;
NVMCTRL->CTRLA.bit.CACHEDIS1 = 1;
/***************************************************************************/
/* ERASE BLOCK */
/***************************************************************************/
// Make sure the NVM is ready to accept a new command (NVMCTRL.STATUS).
while (NVMCTRL->STATUS.bit.READY != NVMCTRL_STATUS_READY );
// Execute "ER" Erase Row ( by passing in any page in the row )
NVMCTRL->ADDR.reg = BOOTCOUNT_ADDRESS;
// erase row
NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_EB;
// wait for erase to complete
while (NVMCTRL->INTFLAG.bit.DONE == 0);
// // check for any errors
if (NVMCTRL->INTFLAG.bit.ADDRE || NVMCTRL->INTFLAG.bit.PROGE || NVMCTRL->INTFLAG.bit.LOCKE
|| NVMCTRL->INTFLAG.bit.ECCSE || NVMCTRL->INTFLAG.bit.ECCDE || NVMCTRL->INTFLAG.bit.NVME) {
// read ECCERR to clear whatever error flag
uint32_t xxx = NVMCTRL->ECCERR.reg;
return ERR_FLASH_ERASE;
}
/***************************************************************************/
/* WRITE NEW VALUE */
/***************************************************************************/
// Disable automatic page write
NVMCTRL->CTRLA.bit.WMODE = 0;
// Make sure the NVM is ready to accept a new command (NVMCTRL.STATUS).
while (NVMCTRL->STATUS.bit.READY != NVMCTRL_STATUS_READY ) { }
// Execute "PBC" Page Buffer Clear
NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_PBC;
// Clear the DONE Flag (NVMCTRL.INTFLAG)
while (NVMCTRL->INTFLAG.bit.DONE == 0) { }
// 6. Write data to page buffer with 32-bit accesses at the Flash page address
uint32_t * p = (uint32_t *)FlashProg_GetPageAddress(BOOTCOUNT_ADDRESS);
for (int i = 0; i < (PAGE_SIZE / 4); i++) {
if (i != 126) {
*p = 0xffffffff;
} else {
// write special value to the bootcount address
*p = 0x12345678;
}
p++;
}
// Make sure the NVM is ready to accept a new command (NVMCTRL.STATUS).
while (NVMCTRL->STATUS.bit.READY != NVMCTRL_STATUS_READY ) { }
// Execute "WP" Write Page
NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMDEX_KEY | NVMCTRL_CTRLB_CMD_WP;
// Wait til write is done
while (NVMCTRL->INTFLAG.bit.DONE == 0);
// check for any errors during write
if (NVMCTRL->INTFLAG.bit.ADDRE || NVMCTRL->INTFLAG.bit.PROGE || NVMCTRL->INTFLAG.bit.LOCKE
|| NVMCTRL->INTFLAG.bit.ECCSE || NVMCTRL->INTFLAG.bit.ECCDE || NVMCTRL->INTFLAG.bit.NVME) {
// read ECCERR to clear whatever error flag
uint32_t xxx = NVMCTRL->ECCERR.reg;
return ERROR_FLASH_WRITE;
}
/***************************************************************************/
/* WRITE SUCCEEDED, TRY TO READ FLASH */
/***************************************************************************/
value = *((uint32_t *) 0xffff8/*BOOTCOUNT_ADDRESS*/);
*bootcount = value;
return ERR_OK;
}
Here’s a screen capture taken while running this in the debugger, with the correct flash value of 0x12345678 at location 0xffff8 (right pane), and the variable “value” in the locals pane on the left, just after it’s been read from the same flash location and showing the wrong, previous programmed value of 0xffffffff
The datasheet talks about two banks of flash, and being able to write to one bank and then switch in place but I’m not doing that. I feel like maybe it has to do with the read caching, but honestly I’ve been trying everything I can think of for more hours (read days) than I care to admit. I’ve looked at a couple other SAMD51 bootloaders, and I’m doing basically the same thing, and as I said, the bootloader actually works well, and in fact DOES correctly read the reset vector from the newly programmed application code in order to jump to the application.
Is there some dumb thing that I’m missing here to flush caches or force reads to work?
thanks kindly, Chris