I have finished my boot loader and here is some code which somebody may find handy. It is of course based on the HAL code but is stripped down, because a lot of the error conditions are probably impossible unless the silicon is defective, and in any case there is no way to deal with most of them in a typical embedded system.
Some basic routines:
/**
*
* @param Sector FLASH sector to erase 0-11
* Device voltage range is 2.7V to 3.6V, and the operation will be done by word (32-bit)
* @retval None
* Waits for previous operation to complete.
*
*/
static void L_FLASH_Erase_Sector(uint32_t Sector)
{
if (Sector<=11)
{
while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET);
CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE); // Clears bits 9,8
FLASH->CR |= FLASH_PSIZE_WORD; // sets them to 10
CLEAR_BIT(FLASH->CR, FLASH_CR_SNB); // Clears bits 6-3
FLASH->CR |= FLASH_CR_SER | (Sector << 3); // SER=1 & load sector # into 6-3
FLASH->CR |= FLASH_CR_STRT;
}
}
/**
* @brief Locks the FLASH control register access
* @retval
*/
static void L_HAL_FLASH_Lock(void)
{
// Wait for any flash operation to finish
while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET);
// Set the LOCK Bit to lock the FLASH Registers access
FLASH->CR |= FLASH_CR_LOCK;
}
/**
* @brief Unlock the FLASH control register access
* Issues the unlok codes
*/
static void L_HAL_FLASH_Unlock(void)
{
// Wait for any flash operation to finish
while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET);
// Write the two unlock codes
WRITE_REG(FLASH->KEYR, FLASH_KEY1);
WRITE_REG(FLASH->KEYR, FLASH_KEY2);
}
/**
* @brief Program word (32-bit) at a specified address.
* @note This function must be used when the device voltage range is from
* 2.7V to 3.6V.
*
* @note If an erase and a program operations are requested simultaneously,
* the erase operation is performed before the program one.
*
* @param Address specifies the address to be programmed.
* @param Data specifies the data to be programmed.
* @retval None
* Waits for previous operation to finish
*
*/
static void L_FLASH_Program_Word(uint32_t Address, uint32_t Data)
{
// wait for any previous op to finish
while(__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET);
// clear program size bits
CLEAR_BIT(FLASH->CR, FLASH_CR_PSIZE);
// reload program size bits
FLASH->CR |= FLASH_PSIZE_WORD;
// enable programming
FLASH->CR |= FLASH_CR_PG;
// write the data in
*(volatile uint32_t*)Address = Data;
}
I start the boot loader with a bit which tests the uppermost flash sector (sector 11, size 128k) by erasing it, verifying the erasure, programming it with varying data, and verifying that, before proceeding with the rest
// ===== Make sure we can erase and program sector 11 - the top one =====
// If that works, the rest should work :)
uint32_t error=0;
uint32_t data;
uint32_t address;
// Erase sector
L_HAL_FLASH_Unlock();
L_FLASH_Erase_Sector(11);
L_HAL_FLASH_Lock();
// Check it is all FFs
for (address=0x080e0000; address<0x080fffff; address+=4)
{
data=*(volatile uint32_t*)address;
if ( data != 0xffffffff )
error++;
}
// Erase sector again
L_HAL_FLASH_Unlock();
L_FLASH_Erase_Sector(11);
// Fill it with data
for (uint32_t i=0; i<(128*1024); i+=4)
{
L_FLASH_Program_Word(i+0x080e0000, i);
}
// Probably always best to lock the flash again before reading it
L_HAL_FLASH_Lock();
// Check the data we have just written
data=0;
for (address=0x080e0000; address<0x080fffff; address+=4)
{
if ( (*(volatile uint32_t*)address) != data )
error++;
data+=4;
}
// If any errors, set up a code on LEDs and allow override with a button
if (error!=0)
and here is another piece of code
// Erase whole 1MB except boot block
// This is done in one go, rather than inside the loop below, to avoid complicated
// coding around the variable size sectors (16k to 128k). Exec time 9 seconds.
L_HAL_FLASH_Unlock();
for (uint32_t i=2;i<=11;i++)
{
L_FLASH_Erase_Sector(i);
}
L_HAL_FLASH_Lock();
// Check it is all FFs
error=0;
for (address=0x08080000; address<0x080fffff; address+=4)
{
data=*(volatile uint32_t*)address;
if ( data != 0xffffffff )
error++;
}
pagebase=4100+64; // 4100-5119 factory image in serial FLASH pages
cpubase=0x08008000; // base of CPU FLASH, after boot block
// Program entire image in one go
// Loop which always does the whole 512k (16 32k blocks) except block 0
for (uint32_t block32k=1; block32k<16; block32k++) // 1-15
{
// Read each 32k block into buffer1
buffer1idx=0;
for ( uint32_t page=pagebase; page<(pagebase+64); page++ )
{
AT45dbxx_ReadPage(&buffer1[buffer1idx],512,page*512); // reads 512 bytes at a time, from serial FLASH address of page*512
buffer1idx+=512;
}
// Program 32k block
L_HAL_FLASH_Unlock();
for (uint32_t i=0; i<(32*1024); i+=4)
{
uint32_t data=buffer1[i]|(buffer1[i+1]<<8)|(buffer1[i+2]<<16)|(buffer1[i+3]<<24);
L_FLASH_Program_Word(i+cpubase, data);
}
L_HAL_FLASH_Lock();
// Verify 32k block against buffer1
for (uint32_t i=0; i<(32*1024); i+=4)
{
uint32_t data=buffer1[i]|(buffer1[i+1]<<8)|(buffer1[i+2]<<16)|(buffer1[i+3]<<24);
if ( (*(volatile uint32_t*) (i+cpubase)) != data ) error++;
}
cpubase+=(32*1024);
pagebase+=64;
block32k++;
}
attempt++;
}
while ( (attempt<NUM_ATTEMPTS) && (error!=0) );
I am allowing up to 5 attempts but in reality I never saw any errors.
You can see above that erasing most of the 1MB FLASH takes 9 seconds. Erasing and programming the two bottom blocks (2x16k) takes 0.6 second (I measured that carefully) which extrapolates to 15-20 seconds for erasing and writing the whole 1MB. That includes reading the data from a serial FLASH over SPI running at 21mbps, so the raw time would be nearer to 15s. One could perhaps speed this up a little by interleaving data fetching with the FLASH programming cycle (the above code is blocking so it waits).