STM32F411RET6的flash大小为512KB,可以从数据手册得知。
扇区的划分范围,可以从参考手册得知
c
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes */
Flash 存储器的特性:
- 擦除后:所有位变为 1,即每个字节为 0xFF
- 编程(写入):只能将 1 改为 0,不能将 0 改回 1
例如:
c
擦除后:0xFF (二进制 1111 1111)
编程写入 0x00:成功(1111 1111 → 0000 0000)
编程写入 0xAA:成功(1111 1111 → 1010 1010)
但如果想修改已写入的数据:
当前值:0x00 (0000 0000)
想改为:0x01 (0000 0001) → 需要将某些位从 0 改为 1,这是不可能的!
必须先擦除整个扇区(恢复为 0xFF),然后重新写入
重点记录几个函数
c
/**
* @brief 解锁闪存控制寄存器访问
* @param None
* @retval None
*/
void FLASH_Unlock(void);
/**
* @brief 锁定闪存控制寄存器访问
* @param None
* @retval None
*/
void FLASH_Lock(void);
/**
* @brief 清除闪存的挂起标志。
* @param Flash_FLAG:指定要清除的Flash标志。
* 此参数可以是以下值的任何组合:
* @arg FLASH_FLAG_EOP: FLASH操作结束标志
* @arg FLASH_FLAG_OPERR: FLASH操作错误标志
* @arg FLASH_FLAG_WRPERR: FLASH写保护错误标志
* @arg FLASH_FLAG_PGAERR: FLASH编程对齐错误标志
* @arg FLASH_FLAG_PGPERR: Flash编程并行错误标志
* @arg FLASH_FLAG_PGSERR: Flash编程序列错误标志
* @arg FLASH_FLAG_RDERR: Flash读保护错误标志 (STM32F42xx/43xxx and STM32F401xx/411xE devices)
* @retval None
*/
void FLASH_ClearFlag(uint32_t FLASH_FLAG);
/**
* @brief 清除指定的闪存扇区
* @param 电压范围:定义擦除并行性的设备电压范围。
* 此参数可以取以下值之一:
* @arg VoltageRange_1: 当设备电压范围为 1.8V 至 2.1V 时,操作将以字节(8 位)进行。
* @arg VoltageRange_2: 当设备电压范围为 2.1V 至 2.7V 时,操作将以半字(16 位)进行。
* @arg VoltageRange_3: 当设备电压范围为 2.7V 至 3.6V 时, 操作将以字(32 位)进行。
* @arg VoltageRange_4: 当设备电压范围为 2.7V 至 3.6V 加外部 Vpp 时,操作将以双字(64 位)进行。
*
* @retval FLASH Status: 返回的值可以是: FLASH_BUSY, FLASH_ERROR_PROGRAM,
* FLASH_ERROR_WRP, FLASH_ERROR_OPERATION or FLASH_COMPLETE.
*/
FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange);
/**
* @brief 在指定地址处对一个字节(8 位)进行编程。
* @note 该函数可在所有设备供电电压范围内使用。
*
* @note 注意:如果同时请求执行擦除操作和编程操作,则会先执行擦除操作,然后再进行编程操作。
*
* @param 地址:指定要进行编程的地址。
* 此参数可以是程序存储区域或 OTP 区域内的任意地址。
* @param 数据:指定要进行编程的数据。
* @retval FLASH Status: 返回值可以是:FLASH_BUSY, FLASH_ERROR_PROGRAM,
* FLASH_ERROR_WRP, FLASH_ERROR_OPERATION or FLASH_COMPLETE.
*/
FLASH_Status FLASH_ProgramByte(uint32_t Address, uint8_t Data);
在对 Flash 内容进行任何修改前,必须调用 FLASH_Unlock() 函数解锁,这是 Flash 编程的关键安全步骤。只有在成功解锁后,才能执行擦除、编程等操作。操作完成后,应立即使用 FLASH_Lock() 重新上锁。
1,首先需要找出要擦除的扇区。
c
/**
* @brief Gets the sector of a given address
* @param None
* @retval The sector of a given address
*/
static uint32_t GetSector(uint32_t Address)
{
uint32_t sector = 0;
if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
{
sector = FLASH_Sector_0;
}
else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
{
sector = FLASH_Sector_1;
}
else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
{
sector = FLASH_Sector_2;
}
else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
{
sector = FLASH_Sector_3;
}
else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
{
sector = FLASH_Sector_4;
}
else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
{
sector = FLASH_Sector_5;
}
else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
{
sector = FLASH_Sector_6;
}
else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))
{
sector = FLASH_Sector_7;
}
return sector;
}
2,此外,还需确定该扇区的尾部地址,因为扇区大小可能不一致。这样可以在写入操作时限定在单个扇区内,防止出现跨区操作。
c
/**
* @brief Gets the end address of a given sector
* @param None
* @retval The end address of a given sector
*/
static uint32_t GetSectorEnd(uint32_t sector)
{
switch(sector)
{
case FLASH_Sector_0: return ADDR_FLASH_SECTOR_1;
case FLASH_Sector_1: return ADDR_FLASH_SECTOR_2;
case FLASH_Sector_2: return ADDR_FLASH_SECTOR_3;
case FLASH_Sector_3: return ADDR_FLASH_SECTOR_4;
case FLASH_Sector_4: return ADDR_FLASH_SECTOR_5;
case FLASH_Sector_5: return ADDR_FLASH_SECTOR_6;
case FLASH_Sector_6: return ADDR_FLASH_SECTOR_7;
case FLASH_Sector_7: return ADDR_FLASH_SECTOR_8;
default: return 0;
}
}
3,执行写操作时,需遵循以下流程:首先解锁目标区域并清除相关标志位,随后将操作范围严格限制在单个扇区内完成写入。操作结束后立即重新上锁,以防止程序异常运行时发生意外的Flash擦写操作。
c
/**
* @brief Writes data to Flash
* @param WriteAddr: Flash write address, must be within the same sector
* @param pBuffer: Pointer to the data buffer to be written
* @param NumToWrite: Number of bytes to write
* @retval 0 on success, non-zero on failure
*/
uint8_t Flash_Write(uint32_t WriteAddr, uint8_t *pBuffer, uint32_t NumToWrite)
{
uint32_t sector = 0;
uint32_t sector_end = 0;
uint32_t i = 0;
// 获取要写入地址所在的扇区
sector = GetSector(WriteAddr);
sector_end = GetSectorEnd(sector); // 获取扇区结束地址
// 检查是否会跨扇区
if ((WriteAddr + NumToWrite) > sector_end)
{
// 跨扇区错误,返回失败
return 1;
}
// 解锁Flash
FLASH_Unlock();
// 清除所有可能的标志位
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |
FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
// 擦除扇区
if (FLASH_EraseSector(sector, VoltageRange_3) != FLASH_COMPLETE)
{
FLASH_Lock();
return 2;
}
// 写入数据
for(i = 0; i < NumToWrite; i++)
{
if(FLASH_ProgramByte(WriteAddr + i, pBuffer[i]) != FLASH_COMPLETE)
{
FLASH_Lock();
return 3;
}
}
// 锁定Flash
FLASH_Lock();
return 0;
}
4,读操作,这操作直接从地址上获取数据。
c
/**
* @brief Reads data from Flash
* @param ReadAddr: Flash read address
* @param pBuffer: Pointer to the data buffer to store the read data
* @param NumToRead: Number of bytes to read
* @retval 0 on success, non-zero on failure
*/
uint8_t Flash_Read(uint32_t ReadAddr, uint8_t *pBuffer, uint32_t NumToRead)
{
uint32_t i = 0;
for(i = 0; i < NumToRead; i++)
{
pBuffer[i] = *(__IO uint8_t*) (ReadAddr + i);
}
return 0;
}
在写入操作中选用FLASH_ProgramByte函数,主要基于数据通常以字节数组形式存储的考虑。
最后验证是否正常
c
void flash_test(void)
{
uint8_t write_data[16] = "Hello, Flash!";
uint8_t read_data[16] = {0};
uint32_t flash_address = ADDR_FLASH_SECTOR_7; // 选择一个扇区地址进行测试
// 写入数据到Flash
if(Flash_Write(flash_address, write_data, sizeof(write_data)) == 0)
{
printf("Flash write successful\r\n");
}
else
{
printf("Flash write failed\r\n");
return;
}
// 从Flash读取数据
if(Flash_Read(flash_address, read_data, sizeof(read_data)) == 0)
{
printf("Flash read successful: %s\r\n", read_data);
}
else
{
printf("Flash read failed\r\n");
return;
}
}
