- Reading and Writing on STM32 MCU Flash Memory
Every microcontroller had an allocated flash-memory in which the firmware are stored. The advantage of the flash-memory is that it retains its data even the power is disconnected. It is ideal for constants to be stored which vital for the firmware's operation.
On this example we will use the development board called "blue pill" and will use STM32CubeIDE as text editor. The blue pill use STM32F103C8 MCU.
STM32F103C8 belongs to the medium density devices of ST. The table below shows that STM32F103C8 had 128 pages of 1 Kbyte memory block starting from 0x0800 0000 to 0x0801 FFFF.
Block | Name | Base adresses | Size (bytes) |
---|---|---|---|
Main memory |
Page 0 | 0x0800 0000 - 0x0800 03FF | 1 Kbyte |
^ | Page 1 | 0x0800 0400 - 0x0800 07FF | 1 Kbyte |
^ | Page 2 | 0x0800 0800 - 0x0800 0BFF | 1 Kbyte |
^ | Page 3 | 0x0800 0C00 - 0x0800 0FFF | 1 Kbyte |
^ | Page 4 | 0x0800 1000 - 0x0800 13FF | 1 Kbyte |
^ | - - - |
- - - |
- - - |
^ | Page 127 | 0x0801 FC00 - 0x0801 FFFF | 1 Kbyte |
If we want to save variable/constants into the flash-memory, other than the main firmware, it is best to start at the very last page 0x0801 FC00 going up in order to avoid overwritting the firmware itself.
This function writes 32-bit of data into a specified flash memory address. It has two arguments which are as shown on the code snippet below:
uint32_t Flash_Write_Data (uint32_t StartPageAddress, uint32_t * data){
...
return 0;
}
- StartPageAddress - the starting address of the page in flash-memory which where you want to write.
- data - the address of the 32-bit data to be written into the flash memory.
The flow chart below shows the process the function performs when it is called.
graph LR;
A([Start]) --> B[/Get Number of Words/];
B[/Get Number of Words/] --> C[/Unlock Flash/];
C[/Unlock Flash/] --> D[/Erase Flash/];
D[/Erase Flash/] --> E[/Program Flash/];
E[/Program Flash/] --> F[/Lock Flash/];
F[/Lock Flash/] --> G([End]);
The function first computes how many words the data have. The snippet below computes the number word by adding the qoutient of the lenght data(using strlen(data) function) divided by four , and added by one, if the length of the data has a remainder (divided by four) and zero if none.
...
int numberofwords = (strlen(data)/4) + ((strlen(data) % 4) != 0);
For example a "Hello World" string that has character length of 11, would have a three words. This is because 11/4 = 2, with the "rld" as a remainder, which would lead to 2 + 1 = 3. The table below maps how many word the example string have.
Word | > | > | > | 1 | > | > | > | 2 | > | > | > | 3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
data[ ] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
char | H | e | l | l | o | _ | W | o | r | l | d | _ |
Next we must unlocks the flash memory for modification by using the code below.
HAL_FLASH_Unlock();
After the flash-memory has been unlock, the function erases the area unto which the data will be written. First we have to declare the FLASH_EraseInitTypeDef.
static FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t PAGEError;
...
//Declare structure Variables
uint32_t StartPage = GetPage(StartPageAddress);
uint32_t EndPageAdress = StartPageAddress + numberofwords*4;
uint32_t EndPage = GetPage(EndPageAdress);
uint32_t NumberOfPages = ((EndPage - StartPage)/FLASH_PAGE_SIZE) +1;
//Fill EraseInit structure
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = StartPage;
EraseInitStruct.NbPages = NumberOfPages;
//capture error
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK){
return HAL_FLASH_GetError ();
}
...
EraseInit Structure Variable Declaration
- StartPage - is the page name or number in the memory block in which we want to store the data. This can be computed with the GetPage() function. The GetPage() function is another function within the library. In our example the start page address of 0x0801 FC00 would have a page number of 127.
- EndPageAddress - is the after page address on the memory on which the data will be written grouped in fours. The EndPageAddress is computed by adding the StartPageAddress with the numberofwords multiplied by four(4). In our example, the last page page in which the "Hello World" would be written is 0x0801 FC0B(plus with the nullafter the character 'd'). The end address would be 0x0801 FC00 + (3*4) = 0x0801 FC0C.
- **EndPage** - is the page name or number in the memory block in which the last bit of data will be stored. This can be computed also with the GetPage() function.
EraseInit Structure the following structures are required on erasing the flash-memory.
- TypeErase - Mass erase or page erase. On our example we are using page erase, so the value would be FLASH_TYPEERASE_PAGES.
- PageAddress - The initial flash page address to be erased. This has been computed and saved in our StartPage variable.
- NbPages - Number of pages to be erased. On our example, this was computed by adding the quotient of (EndPage - StartPage) and FLASH_PAGE_SIZE(1024) by one (1).
After the page area has been erased, we can now write the data into the flash memory. The snippet below writes the data into the flash-memory, word by word. this is determined by the HAL_FLASH_Program's argument FLASH_TYPEPROGRAM_WORD. If the writing process is OK, we increment the ctr, and add 4 to the StartPageAddress, else an error is captured.
int ctr = 0;
while (ctr < numberofwords){
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartPageAddress, data[ctr]) == HAL_OK){
StartPageAddress += 4;
ctr++;
}
else{
return HAL_FLASH_GetError ();
}
}
After the writing process, we must lock the flash by running the code below to protect the flash from unwanted operation.
...
HAL_FLASH_Lock();
This function reads data from the flash memory and has the following arguments.
void Flash_Read_Data (uint32_t StartPageAddress, __IO uint32_t * data){
...
}
- StartPageAddress - the starting address of the page in flash-memory which where we want to read data.
- data - the address of the 32-bit variable where we want to store the data.
while (1){
*var = *(__IO uint32_t *)StartPageAddress;
if (*var == 0xffffffff){
*var = '\0';
break;
}
StartPageAddress += 4;
var++;
}
The program will store the value into the *var word by word, determined by incrementing StartPageAddress by 4, until value is equal to 0xFFFFFFFF, in which it replaces it with \0.
Afterwhich it exits the function. The value of StartPageAddress now is tored on the address value of *var.
Some ST's MCU are mapped with sectors instead of pages. These sectors differs in sizes. On this example we are using Nucleo F401RE with STM32F401RE as its core MCU. The table below shows the flash-memory mapping of the said MCU.
Block | Name | Block base addresses | Size |
---|---|---|---|
Main memory | Sector 0 | 0x0800 0000 - 0x0800 3FFF | 16 Kbytes |
^ | Sector 1 | 0x0800 4000 - 0x0800 7FFF | 16 Kbytes |
^ | Sector 2 | 0x0800 8000 - 0x0800 BFFF | 16 Kbytes |
^ | Sector 3 | 0x0800 C000 - 0x0800 FFFF | 16 Kbytes |
^ | Sector 4 | 0x0801 0000 - 0x0801 FFFF | 64 Kbytes |
^ | Sector 5 | 0x0802 0000 - 0x0803 FFFF | 128 Kbytes |
^ | Sector 6 | 0x0804 0000 - 0x0805 FFFF | 128 Kbytes |
^ | Sector 7 | 0x0806 0000 - 0x0807 FFFF | 128 Kbytes |
> | System memory | 0x1FFF 0000 - 0x1FFF 77FF | 30 Kbytes |
> | OTP Area | 0x1FFF 7800 - 0x1FFF 7A0F | 528 bytes |
> | Option bytes | 0x1FFF C000 - 0x1FFF C00F | 16 bytes |
This function writes a data into the specified sector address on flash memory. The structure is the same as former but takes sector address instead of page address.
uint32_t Flash_Write_Data (uint32_t StartSectorAddress, uint32_t * data){
...
return 0;
}
The function has two arguments which are both are addresses in the memory.
- StartSectorAddress - the starting address of the sector into which the data will be written.
- data - the address of the data to be stored in the flash-memory. The flow of the function is the same as the former function but only differs on the erasing of flash and programming of flash, which will only be discussed on this section. Refer to the previous section for the full function flow.
The flow of the function is the same as the flow of the function on writing on a memory divided by pages stated above. I will just point out the difference in this section.
...
static FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t SECTORError;
int ctr = 0;
uint32_t StartSector = GetSector(StartSectorAddress);
uint32_t EndSectorAddress = StartSectorAddress + numberofwords*4;
uint32_t EndSector = GetSector(EndSectorAddress);
uint32_t NumberOfSectors = (EndSector - StartSector) + 1;
//Fill EraseInit structure
EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
EraseInitStruct.Sector = StartSector;
EraseInitStruct.NbSectors = NumberOfSector
if (HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK){
return HAL_FLASH_GetError ();
}
The difference between the functions are, first is on the variable declaration, the "Page" was replaced with "Sectors" like StartPageAddress was replaced with StartSectorAddress. Then the EraseInit requires an additional VoltageRange structure for devices STM32F4xx series. This voltage range is the device voltage range which defines the erase parallelism with the following must have values below.
Since the nucleo's mcu operates on 3.3 volt range, the voltage range that must be selected FLASH_VOLTAGE_RANGE_3 which has an operating range of 2.7V to 3.6V.
#define FLASH_VOLTAGE_RANGE_1 0x00000000U /*!< Device operating range: 1.8V to 2.1V */
#define FLASH_VOLTAGE_RANGE_2 0x00000001U /*!< Device operating range: 2.1V to 2.7V */
#define FLASH_VOLTAGE_RANGE_3 0x00000002U /*!< Device operating range: 2.7V to 3.6V */
#define FLASH_VOLTAGE_RANGE_4 0x00000003U /*!< Device operating range: 2.7V to 3.6V + External Vpp */
This process is the same as with the former function except for the second argument on the HAL_FLASH_Program() function which takes in the StartSectorAddress instead of the StartPageAddress.
...
while (ctr < numberofwords){
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, StartSectorAddress, data[ctr]) == HAL_OK){
StartSectorAddress += 4;
ctr++;
}
else{
return HAL_FLASH_GetError ();
}
}