STM32F407ZG FLASH + 定时读写FLASH计数值

闪存 FLASH

简介

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。

提高CPU频率


降低CPU频率


Flash 读取,即对 Flash 某个地址读一个字(32位),只要知道地址即可通过如下语句读取:
data = * (vu32 * )addr; //volatile unsigned int 32,每次读取需要重新取,不能直接读寄存器的值。

闪存的编程和擦除

执行任何 Flash 编程操作(擦除或编程)时,CPU 时钟频率 (HCLK) 不能低于 1 MHz。如果 在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。
在对 STM32F4xx 的 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从 Flash 中执行代码或数据获取操作。
闪存的擦除和编程是通过设置寄存器的值来控制的。

编程步骤

  1. 检查 FLASH_SR 中的 BSY 位,确保当前未执行任何 FLASH 操作。
  2. 将 FLASH_CR 寄存器中的 PG 位置 1 ,激活 FLASH 编程。
  3. 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作:
    — 并行位数为 x8 时 按字节写入( PSIZE = 00 )
    — 并行位数为 x16 时按半字写入( PSIZE = 01 )
    — 并行位数为 x32 时按字写入( PSIZE = 02 )
    — 并行位数为 x64 时按双字写入( PSIZE = 03 )
  4. 等待 BSY 位清零,完成一次编程。

注意写入操作必须要在保证写入地址已被擦除。

擦除步骤

  1. 检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁
  2. 检查 FLASH_SR 寄存器中的 BSY 位,确保当前未执行任何 FLASH 操作
  3. 在 FLASH_CR 寄存器中,将 SER 位置 1 ,并从主存储块的 12 个扇区中选择要擦除的扇区(SNB)
  4. 将 FLASH_CR 寄存器中的 STRT 位置 1 ,触发擦除操作
  5. 等待 BSY 位清零

寄存器

访问控制寄存器 (FLASH_ACR)

用于使能/失能 数据缓存、指令缓存、预取 相关的功能,最重要的是在于低三位用于设置 LATENCY 的值。

在这里插入图片描述

控制寄存器 (FLASH_CR)

CR 有两种,分别对应 stm32f405xxx/407xxx/415xxx/417xxx 和 42xxx/43xxx 。这里贴出前者。

在这里插入图片描述


在这里插入图片描述

秘钥寄存器 (FLASH_KEYR)

此寄存器用于解锁 CR ,有固定的两个设置值,否则会将 CR 锁定。

在这里插入图片描述

状态寄存器 (FLASH_SR)

包括 BSY 繁忙标志各种错误标志EOP 操作结束标志 。具体见官方手册。
一般来说只有使能了相关错误中断,这些错误标志位才有意义。

选项控制寄存器 (FLASH_OPTCR)

对于OPT的控制,具体见官方手册。

选项秘钥寄存器 (FLASH_OPTKEYR)

与 KEYR 类似,用于解锁 OPTCR 。

在这里插入图片描述

定时读写 FLASH 计数值

需求

定时读写Flash,开机读取Flash计数值,每10秒钟计数加1并重新写入Flash保存。

思路

使用通用定时器 TIM3 设定重装值使定时为10s,每次自动重装时转入中断函数读取 Flash 计数值自增,并重新擦除写入。

实验过程中的一些问题

TIM 相关

  1. 实验中发现 TIM 初始化时,最开始也会进入一次中断,因此会导致还没开始计时就自增了一次。解决方法是使用了一个标志位,首次中断不做处理。
  2. TIM 触发中断后,会自动重装计数值,且中断函数的执行和计数互不影响。也就是说虽然对 Flash 的操作会有一定延迟,但并不影响计时,也不影响下一次中断(经实验擦写大概需要1s)。

代码

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);
	}
}

实验结果

每 10s 自增一次,串口显示,同时反转 LED1 的值。LED0 闪烁表示程序正在运行。

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340