Flash 又称为闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。
STM32 的 Flash 接口可管理 CPU 通过 AHB I-Code 和 D-Code 对 Flash 进行的访问。该接口可针对 Flash 执行擦除和编程操作,并实施读写保护机制。Flash 接口通过指令预取和缓存机制加速代码执行。
STM32F407ZG 的具体分区如下:
主存储器,存放代码和数据常数(如const 类型的数据)。从上图可以看出主存储器的起始地址就是 0X08000000 。当 B0 、 B1 都接 GND 的时候,就是从 0X08000000 开始运行代码的。
系统存储器,主要用来存放STM32F4 的 bootloader 代码,此代码是出厂的时候就固化
在 STM32F4 里面了,专门来给主存储器下载代码的。当 B0 接 V3.3 B1 接 GND 的时候,从
该存储器启动(即进入串口下载模式)。
OTP 区域,即一次性可编程区域,共 528 字节,被分成两个部分,前面 512 字节( 32 字节为 1 块,分成 16 块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面 16 字节,用于锁定对应块。这里的一次性是指写入一次后,再次写入的话是前后相与的值,两次不相同则会置零。
选项字节,用于配置读保护、BOR 级别、软件 硬件看门狗以及器件处于待机或停止模式下的复位。
STM32F4 可通过内部的 I Code 指令总线或 D Code 数据总线访问内置闪存模块。为了准确读取 Flash 数据,必须根据 CPU 时钟 (HCLK) 频率和器件电源电压在 Flash 存取控 制寄存器 (FLASH_ACR) 中正确地编程等待周期数 (LATENCY)。当电源电压低于 2.1 V 时,必须关闭预取缓冲器。
于是一般正常工作时(168MHz,3.3V),应设置 LATENCY 为 5 WS。
Flash 读取,即对 Flash 某个地址读一个字(32位),只要知道地址即可通过如下语句读取:
data = * (vu32 * )addr; //volatile unsigned int 32,每次读取需要重新取,不能直接读寄存器的值。
执行任何 Flash 编程操作(擦除或编程)时,CPU 时钟频率 (HCLK) 不能低于 1 MHz。如果 在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。
在对 STM32F4xx 的 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从 Flash 中执行代码或数据获取操作。
闪存的擦除和编程是通过设置寄存器的值来控制的。
编程步骤
注意写入操作必须要在保证写入地址已被擦除。
擦除步骤
用于使能/失能 数据缓存、指令缓存、预取 相关的功能,最重要的是在于低三位用于设置 LATENCY 的值。
CR 有两种,分别对应 stm32f405xxx/407xxx/415xxx/417xxx 和 42xxx/43xxx 。这里贴出前者。
此寄存器用于解锁 CR ,有固定的两个设置值,否则会将 CR 锁定。
包括 BSY 繁忙标志 、各种错误标志、EOP 操作结束标志 。具体见官方手册。
一般来说只有使能了相关错误中断,这些错误标志位才有意义。
对于OPT的控制,具体见官方手册。
定时读写Flash,开机读取Flash计数值,每10秒钟计数加1并重新写入Flash保存。
使用通用定时器 TIM3 设定重装值使定时为10s,每次自动重装时转入中断函数读取 Flash 计数值自增,并重新擦除写入。
tim3 的配置详情见我的另一篇文章: STM32F4ZG TIM
tim3 的中断函数:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) //判断发生中断
{
if(first_flag == 0) //首次中断仅打印计数值初始值
{
first_flag = 1;
printf("%d\r\n", counter);
}
else
{
u32 load[1];
LED1 = !LED1;
counter++;
load[0] = counter;
stmflash_write(ADDR_COUNTER, load, 1);
stmflash_read(ADDR_COUNTER, load, 1);
printf("%d\r\n", load[0]);
}
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除更新中断标志位
}
stmflash.h
#ifndef STMFLASH_H__
#define STMFLASH_H__
//FLASH 扇区的起始地址
#define ADDR_FLASH_SECTOR_0 ((u32)0x08000000) //扇区0起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((u32)0x08004000) //扇区1起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((u32)0x08008000) //扇区2起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((u32)0x0800C000) //扇区3起始地址, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((u32)0x08010000) //扇区4起始地址, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((u32)0x08020000) //扇区5起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_6 ((u32)0x08040000) //扇区6起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_7 ((u32)0x08060000) //扇区7起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_8 ((u32)0x08080000) //扇区8起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_9 ((u32)0x080A0000) //扇区9起始地址, 128 Kbytes
#define ADDR_FLASH_SECTOR_10 ((u32)0x080C0000) //扇区10起始地址,128 Kbytes
#define ADDR_FLASH_SECTOR_11 ((u32)0x080E0000) //扇区11起始地址,128 Kbytes
#define ADDR_COUNTER ((u32)0x080E0000)
extern u32 counter;
void load_counter(void);
u16 stmflash_get_sector(u32 addr);
void stmflash_read(u32 addr, u32* pbuffer, u32 num);
void stmflash_write(u32 addr, u32* pbuffer, u32 num);
#endif
stmflash.c
#include "stm32f4xx.h" // Device header
#include "stmflash.h"
u32 stmflash_read_word(u32 faddr)
{
return *(vu32*)faddr;
}
//******实验需要********
u32 counter = 0;
void load_counter(void)
{
counter = stmflash_read_word(ADDR_COUNTER);
}
//*********************
u16 stmflash_get_sector(u32 addr)
{
if(addr < ADDR_FLASH_SECTOR_1)return FLASH_Sector_0;
else if(addr < ADDR_FLASH_SECTOR_2)return FLASH_Sector_1;
else if(addr < ADDR_FLASH_SECTOR_3)return FLASH_Sector_2;
else if(addr < ADDR_FLASH_SECTOR_4)return FLASH_Sector_3;
else if(addr < ADDR_FLASH_SECTOR_5)return FLASH_Sector_4;
else if(addr < ADDR_FLASH_SECTOR_6)return FLASH_Sector_5;
else if(addr < ADDR_FLASH_SECTOR_7)return FLASH_Sector_6;
else if(addr < ADDR_FLASH_SECTOR_8)return FLASH_Sector_7;
else if(addr < ADDR_FLASH_SECTOR_9)return FLASH_Sector_8;
else if(addr < ADDR_FLASH_SECTOR_10)return FLASH_Sector_9;
else if(addr < ADDR_FLASH_SECTOR_11)return FLASH_Sector_10;
return FLASH_Sector_11;
}
void stmflash_read(u32 addr, u32* pbuffer, u32 num)
{
u32 i;
for(i = 0; i < num; i++)
{
pbuffer[i] = stmflash_read_word(addr);
addr += 4;
}
}
void stmflash_write(u32 addr, u32* pbuffer, u32 num)
{
FLASH_Status status =FLASH_COMPLETE;
u32 start_addr = 0;
u32 end_addr = 0;
if(addr < FLASH_BASE || addr % 4)return; //地址非法
FLASH_Unlock();
FLASH_DataCacheCmd(DISABLE); //FLASH擦除期间,必须禁止数据缓存
start_addr = addr;
end_addr = addr + num * 4;
if(start_addr < 0X1FFF0000) //主存储区才需要擦除
{
//若要写的区域有数据,需要把所在整个扇区擦除
while(start_addr < end_addr)
{
if(stmflash_read_word(start_addr) != 0xFFFFFFFF)
{
status = FLASH_EraseSector(stmflash_get_sector(start_addr), VoltageRange_3);
if(status != FLASH_COMPLETE)break; //擦除异常
}
else
{
start_addr += 4;
}
}
}
//写数据
if(status == FLASH_COMPLETE)
{
while(addr<end_addr)
{
if(FLASH_ProgramWord(addr,*pbuffer) != FLASH_COMPLETE)//写入数据
{
break; //写入异常
}
addr+=4;
pbuffer++;
}
}
FLASH_DataCacheCmd(ENABLE); //写入结束开启数据缓存
FLASH_Lock();
}
main 函数比较简单,记得在开启定时器前 load 一下 counter 就行。
int main(void)
{
uint16_t times = 0;
load_counter();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //延时初始化
usart_init(); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
LED1 = 0;
//开始定时,定时时间 = (psc+1) * (arr+1) / clk
// 20000 * 42000 / 84000000 = 10s
tim3_init(20000 - 1, 42000 - 1);
while(1)
{
times++;
if(times % 30 == 0) LED0 =! LED0; //闪烁LED0,提示系统正在运行
delay_ms(10);
}
}