基于STM32F407VET6
Flash 结构如下:
— 主存储器块,分为 4 个 16 KB 扇区、1 个 64 KB 扇区和 7 个 128 KB 扇区
— 系统存储器,器件在系统存储器自举模式下从该存储器启动
— 512 字节 OTP(一次性可编程),用于存储用户数据 OTP 区域还有 16 个额外字节,用于锁定对应的 OTP 数据块。
— 选项字节,用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或 停止模式下的复位。

一、CPU 时钟频率与 Flash 读取时间之间的关系
为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地编程等待周期数 (LATENCY)。
在本次实验中,我的HCLK为168MHz

二、需要提高 CPU 频率时的操作步骤
(1)将新的等待周期数编程到 FLASH_ACR 寄存器中的 LATENCY 位
(2)通过读取 FLASH_ACR 寄存器,检查新的等待周期是否设置成功
(3)通过改写 RCC_CFGR 寄存器中的 SW 位来修改 CPU 时钟源
(4)如有需要,可通过改写 RCC_CFGR 中的 HPRE 位来修改 CPU 时钟预分频器
(5)通过读取 RCC_CFGR 寄存器中相应的时钟源状态(SWS 位)和/或 AHB 预分频值 (HPRE 位),检查新的 CPU 时钟源和/或新的 CPU 时钟预分频值是否设置成功

DCRST:数据缓存复位
ICRST:指令缓存复位
DCEN:数据缓存使能
ICEN:指令缓存使能
LATENCY:延迟 (周期数)
000:零等待周期
001:一个等待周期
010:两个等待周期
011:三个等待周期
100:四个等待周期
101:五个等待周期
110:六个等待周期
111:七个等待周期
对应代码如下:
/*设置Flash周期数*/
void Flash_Set_Cycle()
{
FLASH->ACR &= ~(FLASH_ACR_ICEN | FLASH_ACR_DCEN); //禁用缓存
FLASH->ACR |= FLASH_ACR_ICRST; // 复位指令缓存
FLASH->ACR |= FLASH_ACR_DCRST; // 复位数据缓存
__NOP(); __NOP(); __NOP(); // 短暂延时
FLASH->ACR |= FLASH_ACR_ICEN | FLASH_ACR_DCEN; // 使能缓存
FLASH->ACR |= 0x05; // 设置五个等待周期
}
三、Flash 控制寄存器解锁
复位后,Flash 控制寄存器 (FLASH_CR) 不允许执行写操作,以防因电气干扰等原因出现对 Flash 的意外操作。此寄存器的解锁顺序如下:
- 在 Flash 密钥寄存器 (FLASH_KEYR) 中写入 KEY1 = 0x45670123
- 在 Flash 密钥寄存器 (FLASH_KEYR) 中写入 KEY2 = 0xCDEF89AB

对应代码如下:
四、上锁
/*解锁*/
void Flash_Unlock()
{
FLASH->KEYR = FLASH_KEY1;
__NOP(); __NOP(); __NOP(); // 短暂延时
FLASH->KEYR = FLASH_KEY2;
}
给FLASH的CR寄存器上的LOCK写1即可

/*上锁*/
void Flash_Lock()
{
FLASH->CR |= FLASH_CR_LOCK;
}
五、擦除
把 Flash 的单元从“1”写为“0”时,无需执行擦除操作即可进行连续写操作。
把 Flash 的 单元从“0”写为“1”时,则需要执行 Flash 擦除操作。
如果同时发出擦除和编程操作请求,首先执行擦除操作。
扇区擦除
扇区擦除的具体步骤如下:
(1)检查 FLASH_SR 寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作
(2)在 FLASH_CR 寄存器中,将 SER 位置 1,并从主存储块的 12 个(基于STM32F407) 扇区中选择要擦除 的扇区 (SNB)
(3)将 FLASH_CR 寄存器中的 STRT 位置 1
(4)等待 BSY 位清零

/*擦除扇区*/
void Flash_Erase_Sector(uint32_t Sector)
{
if((FLASH->SR & 0x10000) == 0) //判断当前有没有执行Flash 操作
{
FLASH->CR |= 0x02; // 在 FLASH_CR 寄存器中,将SER位置 1
FLASH->CR &= ~(0x78); // 清除SNB位
FLASH->CR |= Sector; // 选择要擦除的扇区
FLASH->CR |= FLASH_CR_STRT; // 将 FLASH_CR 寄存器中的 STRT 位置 1
while((FLASH->SR & 0x10000) != 0); // 等待 BSY 位清零
FLASH->CR &= ~(0x02); // 在擦除完成,将SER位置0
}
六、写入
Flash 编程顺序如下:
(1)检查 FLASH_SR 寄存器中的 BSY 位,以确认当前未执行任何 Flash 操作
(2)将 FLASH_CR 寄存器中的 PG 位置 1。
(3)针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作:
并行位数为 x8 时按字节写入
并行位数为 x16 时按半字写入
并行位数为 x32 时按字写入
并行位数为 x64 时按双字写入
(4)等待 BSY 位清零
/*写入*/
void Flash_Write_DataByte(uint32_t address, uint8_t data)
{
cli(); //关闭总中断,防止打断数据写入
Flash_Unlock();
if((FLASH->SR & 0x10000) == 0) //判断当前有没有执行Flash 操作
{
FLASH->CR |= 0x01; // 在 FLASH_CR 寄存器中,将PG位置 1
FLASH->CR &= ~(0x300); // 设置并行位数为8 字节写入
*(__IO uint8_t*)address = data; //写入数据
while((FLASH->SR & 0x10000) != 0); // 等待 BSY 位清零
FLASH->CR &= ~(0x01); // 写入完成,将PG位置0
}
Flash_Lock();
sei(); //开启总中断
}
7、读取
读取很简单,读取指定的地址即可
uint8_t Flash_Read_DataByte(uint32_t address)
{
return *(volatile uint8_t*)address;
}








