nucleo-f411re学习记录-13,flash的操作

代码仓库

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;
	}
}
相关推荐
晓梦林1 小时前
3170靶场学习笔记
笔记·学习
ErizJ2 小时前
Redis|学习笔记
redis·笔记·学习
加油20192 小时前
方法论:如何系统性的学习?
学习·学习方法·方法论
小t说说3 小时前
科学素养培养:男孩女孩的不同“方程式”,真的有分性别学习平台?
学习
xian_wwq3 小时前
【学习笔记】变电保护、测控、安自、自动化系统概述
笔记·学习·保护
lizhihai_993 小时前
股市学习心得—商业航天10大核心材料供应商
大数据·人工智能·学习
泰勒朗斯3 小时前
rootflight学习笔记
笔记·学习
知识分享小能手4 小时前
R语言入门学习教程,从入门到精通,R语言时间序列数据可视化(11)
学习·信息可视化·r语言
AI绘画哇哒哒5 小时前
RAG 系统中文档切分策略:如何选择合适的 chunk size?| 收藏这份实用指南,小白也能轻松上手大模型学习
人工智能·学习·ai·程序员·大模型·产品经理·转行