微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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 闪烁表示程序正在运行。

在这里插入图片描述

原文地址:https://www.jb51.cc/wenti/3284676.html

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

相关推荐