首页 > 其他分享 >STM32_HAL_FLASH 模拟 EEPROM

STM32_HAL_FLASH 模拟 EEPROM

时间:2024-05-27 15:32:08浏览次数:27  
标签:SECTOR HAL ADDR FLASH 扇区 STM32 地址 uint32

1. STM32 FLASH简介

STM32F407ZGT6 的 FLASH 容量为1024K 字节, STM32F40xx/41xx 的闪存模块组织如图

STM32F4 的闪存模块由主存储器、系统存储器、 OPT 区域和选项字节等 4 部分组成。

        主存储器,该部分用来存放代码和数据常数(如 const 类型的数据)。分为 12 个扇区,前 4个扇区为 16KB 大小,扇区 4 为 64KB 大小,扇区 5~11 为 128KB 大小,不同容量的 STM32F4,拥有的扇区数不一样,比如我们的 STM32F407ZGT6,拥有 12 个扇区。从表 46.1 可以看出,主存储器的起始地址为 0x08000000, B0、 B1 都接 GND 的时候,就是从 0x08000000 开始运行代码。

        系统存储器,主要用来存放 STM32F4 的 bootloader 代码,此代码在出厂的时候就固化在STM32F4 里面了,专门用来给主存储器下载代码的。当 B0 接 V3.3, B1 接 GND 的时候,从该存储器启动(即进入串口下载模式)。

        OTP 区域,即一次性可编程区域,总共 528 字节大小,被分成两个部分,前面 512 字节(32字节为 1 块,分成 16 块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面 16 字节,用于锁定对应块。

        选项字节,用于配置读保护、 BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。

        闪存存储器接口寄存器,该部分用于控制闪存读写等,是整个闪存模块的控制结构。

        在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行。既在进行写或擦除操作时,不能进行代码或数据的读取操作。

1.1 闪存的读取

        STM32F4 可以通过内部的 I-Code 指令总线或 D-Code 数据总线访问内置闪存模块,本章主要讲解的数据读写,即通过 D-Code 数据总线来访问内部闪存模块。 为了准确读取 Flash 数据,必须根据 CPU 时钟(HCLK)频率和器件电源电压在 Flash 存取控制寄存器(FLASH_ACR) 中正确地设置等待周期数(LATENCY)。当电源电压低于 2.1V 时,必须关闭预取缓冲器。 Flash等待周期与 CPU 时钟频率之间的对应关系,如表

        等待周期通过 FLASH_ACR 寄存器的 LATENCY[2:0]三个位设置。系统复位后, CPU 时钟频率为内部 16 M RC 振荡器(HIS), LATENCY 默认是 0,即 1 个等待周期。 供电电压,我们一般是 3.3V,所以,在我们设置 168 MHz 频率作为 CPU 时钟之前,必须先设置 LATENCY 为5,否则 FLASH 读写可能出错,导致死机。

        正常工作时(168 MHz),虽然 FLASH 需要 6 个 CPU 等待周期,但是由于 STM32F4 具有自适应实时存储器加速器(ART Accelerator),通过指令缓存存储器,预取指令,实现相当于 0 FLASH 等待的运行速度。关于自适应实时存储器加速器的详细介绍,请大家参考《STM32F4xx参考手册_V4(中文版) .pdf》 3.4.2 节。 STM23F4 的 FLASH 读取是很简单的。例如,我们要从地址 addr,读取一个字(字节为 8 位, 半字为 16 位,字为 32 位),可以通过如下的语句读取:

data = *(volatile uint32_t *)addr;

        将 addr 强制转换为 volatile uint32_t 指针,然后取该指针所指向的地址的值,即得到了 addr地址的值。类似的,将上面的 volatile uint32_t 改为 volatile uint16_t,即可读取指定地址的一个半字。相对 FLASH 读取来说, STM32F4 FLASH 的写就复杂一点了,下面我们介绍 STM32F4闪存的编程和擦除。

 1.2 闪存的编程和擦除

        执行任何 Flash 编程操作(擦除或编程)时, CPU 时钟频率(HCLK)不能低于 1 MHz。如果在 Flash 操作期间发生器件复位,无法保证 Flash 中的内容。

        在对 STM32F4 的 Flash 执行写入或擦除操作期间,任何读取 Flash 的尝试都会导致总线阻塞。只有在完成编程操作后,才能正确处理读操作。这意味着,写/擦除操作进行期间不能从 Flash中执行代码或数据获取操作。

STM32F4 用户闪存的编程一般由 6 个 32 位寄存器控制,他们分别是:

⚫ FLASH 访问控制寄存器(FLASH_ACR)

⚫ FLASH 秘钥寄存器(FLASH_KEYR)

⚫ FLASH 选项秘钥寄存器(FLASH_OPTKEYR)

⚫ FLASH 状态寄存器(FLASH_SR)

⚫ FLASH 控制寄存器(FLASH_CR)

⚫ FLASH 选项控制寄存器(FLASH_OPTCR)

        STM32F4 复位后, FLASH 编程操作是被保护的,不能写入 FLASH_CR 寄存器;通过写入特定的序列(0x45670123 和 0xCDEF89AB)到 FLASH_KEYR 寄存器才可解除写保护,只有在写保护被解除后,我们才能操作相关寄存器。FLASH_CR 的解锁序列为:

(1) 写 0x45670123 到 FLASH_KEYR

(2)写 0xCDEF89AB 到 FLASH_KEYR

        通过这两个步骤,即可解锁 FLASH_CR,如果写入错误,那么 FLASH_CR 将被锁定,直到下次复位后才可以再次解锁。

STM32F4 闪存的编程位数可以通过 FLASH_CR 的 PSIZE 字段配置, PSIZE 的设置必须和电源电压匹配,见表

        由于我们开发板用的电压是 3.3V,所以 PSIZE 必须设置为 10,即 32 位并行位数。擦除或者编程,都必须以 32 位为基础进行。

FLASH 配置步骤

        STM32F4 的 FLASH 在编程的时候,也必须要求其写入地址的 FLASH 是被擦除了的(也就是其值必须是 0xFFFFFFFF),无法写入。 STM32F4 的标准编程步骤如图

从上图可以得到闪存的编程顺序如下:

1,检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁

2,检查 FLASH_SR 寄存器的 BSY 位,以确认没有其他正在进行的编程操作

3,设置 FLASH_CR 寄存器的 PG 位为‘1’

4,在指定的地址写入数据(一次写入 32 字节,不能超过 32 字节)

5,等待 BSY 位变为‘0’

6,读出写入地址并验证数据

前面提到,我们在 STM32 的 FLASH 编程的时候,要先判断缩写地址是否被擦除了,所以,我们有必要再介绍一下 STM32 的闪存擦除, STM32 的闪存擦除分为两种:页擦除和整片擦除。页擦除过程如图

1,检查 FLASH_CR 的 LOCK 是否解锁,如果没有则先解锁

2,检查 FLASH_SR 寄存器中的 BSY 位,确保当前未执行任何 FLASH 操作

3,在 FLASH_CR 寄存器中,将 SER 位置 1,并设置 SNB=0(只有 1 个扇区,扇区 0)

4,将 FLASH_CR 寄存器中的 START 位置 1,触发擦除操作5,等待 BSY 位清零

经过以上五步,就可以擦除某个扇区。

 1.3 FLASH 寄存器

⚫ Flash 访问控制寄存器(FLASH_ACR)

⚫ FLASH 密钥寄存器(FLASH_KEYR)

 ⚫ FLASH 控制寄存器(FLASH_CR)

LOCK 位,该位用于指示 FLASH_CR 寄存器是否被锁住,该位在检测到正确的解锁序列后,硬件将其清零。在一次不成功的解锁操作后,在下次系统复位之前,该位将不再改变。

STRT 位,该位用于开始一次擦除操作。在该为写入 1,将执行一次擦除操作。PSIZE[1:0]位,用于设置编程宽度,我们一般设置 PSIZE = 2 即可(32 位)。SNB[3:0]位,这 4 个位用于选择要擦除的扇区编号,取值范围为 0~1。

SER 位,该位用于选择扇区擦除操作,在扇区擦除的时候,需要将该位置 1。PG 位,该位用于选择编程操作,在往 FLASH 写数据的时候,该位需要置 1。

⚫ FLASH 状态寄存器(FLASH_SR)

该寄存器我们主要用了 BSY 位:表示 BANK 当前正在执行编程操作,当该位为 1 时,表示正在执行 FLASH 操作,当该位为 0 时,表示当前未执行 FLASH 操作。 

2. 程序设计

flash.h

#ifndef __STMFLASH_H
#define __STMFLASH_H

#include "./SYSTEM/sys/sys.h"


/* FLASH起始地址 */
#define STM32_FLASH_SIZE        0x100000        /* STM32 FLASH 总大小 */
#define STM32_FLASH_BASE        0x08000000      /* STM32 FLASH 起始地址 */
#define FLASH_WAITETIME         50000           /* FLASH等待超时时间 */

/* FLASH 扇区的起始地址 */
#define ADDR_FLASH_SECTOR_0     ((uint32_t )0x08000000)     /* 扇区0起始地址, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t )0x08004000)     /* 扇区1起始地址, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t )0x08008000)     /* 扇区2起始地址, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t )0x0800C000)     /* 扇区3起始地址, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t )0x08010000)     /* 扇区4起始地址, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t )0x08020000)     /* 扇区5起始地址, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t )0x08040000)     /* 扇区6起始地址, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t )0x08060000)     /* 扇区7起始地址, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8     ((uint32_t )0x08080000)     /* 扇区8起始地址, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9     ((uint32_t )0x080A0000)     /* 扇区9起始地址, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10    ((uint32_t )0x080C0000)     /* 扇区10起始地址,128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t )0x080E0000)     /* 扇区11起始地址,128 Kbytes */

uint32_t stmflash_read_word(uint32_t faddr);                             /* 读出字 */
void stmflash_write(uint32_t waddr, uint32_t *pbuf, uint32_t length);    /* 从指定地址开始写入指定长度的数据 */
void stmflash_read(uint32_t raddr, uint32_t *pbuf, uint32_t length);     /* 从指定地址开始读出指定长度的数据 */

void test_write(uint32_t waddr, uint32_t wdata);                         /* 测试写入 */

#endif

flash.c

#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/STMFLASH/stmflash.h"

/**
 * @brief       从指定地址读取一个字 (32位数据)
 * @param       faddr   : 读取地址 (此地址必须为4倍数!!)
 * @retval      读取到的数据 (32位)
 */
uint32_t stmflash_read_word(uint32_t faddr)
{
    return *(volatile uint32_t *)faddr;
}

/**
 * @brief       获取某个地址所在的flash扇区
 * @param       addr    : lash地址
 * @retval      0~11,即addr所在的扇区
 */
uint8_t  stmflash_get_flash_sector(uint32_t 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;
}

/**
 * @brief       在FLASH 指定位置, 写入指定长度的数据(自动擦除)
 *   @note      因为STM32F4的扇区实在太大,没办法本地保存扇区数据,所以本函数写地址如果非0XFF
 *              ,那么会先擦除整个扇区且不保存扇区数据.所以写非0XFF的地址,将导致整个扇区数据丢失.
 *              建议写之前确保扇区里没有重要数据,最好是整个扇区先擦除了,然后慢慢往后写.
 *              该函数对OTP区域也有效!可以用来写OTP区!
 *              OTP区域地址范围:0X1FFF7800~0X1FFF7A0F(注意:最后16字节,用于OTP数据块锁定,别乱写!!)
 * @param       waddr   : 起始地址 (此地址必须为4的倍数!!,否则写入出错!)
 * @param       pbuf    : 数据指针
 * @param       length  : 要写入的 字(32位)数(就是要写入的32位数据的个数)
 * @retval      无
 */
void stmflash_write(uint32_t waddr, uint32_t *pbuf, uint32_t length)
{
    FLASH_EraseInitTypeDef flasheraseinit;
    HAL_StatusTypeDef FlashStatus=HAL_OK;

    uint32_t addrx = 0;
    uint32_t endaddr = 0;
    uint32_t sectorerror=0;
    
    if (waddr < STM32_FLASH_BASE || waddr % 4 ||        /* 写入地址小于 STM32_FLASH_BASE, 或不是4的整数倍, 非法. */
        waddr > (STM32_FLASH_BASE + STM32_FLASH_SIZE))  /* 写入地址大于 STM32_FLASH_BASE + STM32_FLASH_SIZE, 非法. */
    {
        return;
    }

    HAL_FLASH_Unlock();             /* 解锁 */
    FLASH->ACR &= ~(1 << 10);       /* FLASH擦除期间,必须禁止数据缓存!!! */

    addrx = waddr;                  /* 写入的起始地址 */
    endaddr = waddr + length * 4;   /* 写入的结束地址 */

    if (addrx < 0X1FFF0000)         /* 只有主存储区,才需要执行擦除操作!! */
    {
        while (addrx < endaddr)     /* 扫清一切障碍.(对非FFFFFFFF的地方,先擦除) */
        {
            if (stmflash_read_word(addrx) != 0XFFFFFFFF)    /* 有非0XFFFFFFFF的地方,要擦除这个扇区 */
            {
                flasheraseinit.TypeErase=FLASH_TYPEERASE_SECTORS;       /* 擦除类型,扇区擦除 */
                flasheraseinit.Sector=stmflash_get_flash_sector(addrx); /* 要擦除的扇区 */
                flasheraseinit.NbSectors=1;                             /* 一次只擦除一个扇区 */
                flasheraseinit.VoltageRange=FLASH_VOLTAGE_RANGE_3;      /* 电压范围,VCC=2.7~3.6V之间!! */

                if(HAL_FLASHEx_Erase(&flasheraseinit, &sectorerror) != HAL_OK) 
                {
                    break;/* 发生错误了 */
                }

            }
            else
            {
                addrx += 4;
            }
            FLASH_WaitForLastOperation(FLASH_WAITETIME);                 /* 等待上次操作完成 */
        }
    }

    FlashStatus=FLASH_WaitForLastOperation(FLASH_WAITETIME);             /* 等待上次操作完成 */

    if (FlashStatus==HAL_OK)
    {
        while (waddr < endaddr)     /* 写数据 */
        {
            if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, waddr, *pbuf) != HAL_OK)  /* 写入数据 */
            {
                break;              /* 写入异常 */
            }

            waddr += 4;
            pbuf++;
        }
    }
    
    FLASH->ACR |= 1 << 10;          /* FLASH擦除结束,开启数据fetch */

    HAL_FLASH_Lock();               /* 上锁 */
}

/**
 * @brief       从指定地址开始读出指定长度的数据
 * @param       raddr : 起始地址
 * @param       pbuf  : 数据指针
 * @param       length: 要读取的字(32)数,即4个字节的整数倍
 * @retval      无
 */
void stmflash_read(uint32_t raddr, uint32_t *pbuf, uint32_t length)
{
    uint32_t i;

    for (i = 0; i < length; i++)
    {
        pbuf[i] = stmflash_read_word(raddr);    /* 读取4个字节. */
        raddr += 4; /* 偏移4个字节. */
    }
}

/******************************************************************************************/
/* 测试用代码 */

/**
 * @brief       测试写数据(写1个字)
 * @param       waddr : 起始地址
 * @param       wdata : 要写入的数据
 * @retval      读取到的数据
 */
void test_write(uint32_t waddr, uint32_t wdata)
{
    stmflash_write(waddr, &wdata, 1);   /* 写入一个字 */
}

标签:SECTOR,HAL,ADDR,FLASH,扇区,STM32,地址,uint32
From: https://blog.csdn.net/banchengl/article/details/139173124

相关文章

  • STM32F4平台使用SerialPort串口库教程
    这一期教程的内容主要是在STM32F4平台使用SerialPort串口库和上位机通信,上位机(例如串口调试助手)向下位机发送字符串,下位机接收到字符串后,经过加工处理再发送给上位机。SerialPort串口库使用C++编写,用户只需在外部实现4个中断回调函数即可完成串口DMA收发,然后使用read函数读......
  • JINGWHALE ABCDE 概念模型系统设计建模法,用户画像进行场景化业务需求分析与归纳,帮你规
    JINGWHALE对此论文相关未知以及已知概念、定理、公式、图片等内容的感悟、分析、创新、创造等拥有作品著作权。未经JINGWHALE授权,禁止转载与商业使用。《一种基于概念模型思想的ABCDE系统设计建模法的研究与应用》张云龙(JINGWHALE数字科学艺术创新中心,浙江杭州,310......
  • 嵌入式实时操作系统笔记3:FreeRTOS移植(STM32F407)_编写简单的FreeRTOS任务例程
    上文讲到UC/OSIII系统的移植,那篇文章是失败了的,网络上的资料真是层次不清,多有遗漏步骤,导致单片机连操作系统的初始化都卡在那,这次换个赛道,学FreeRTOS吧......今日任务如标题所示:FreeRTOS移植(STM32F407)_编写简单的FreeRTOS任务例程文章提供测试代码讲解、完整工程下载、测......
  • STM32 F1系列 全中文HAL&LL库使用手册 中英双语对照 GPT机翻 共1208页、约40万字
    STM32F1系列全中文HAL_LL库使用手册,中英文双语对照阅读。内容、格式对照官方原文,含标签导航及目录跳转。全文GPT机翻,除人工翻译外,相对更加贴合原文原意,双语版防止翻译错误方便对照。全文:1208页,约40万字。*******下有更多展示图片********由于本汉化不改变官方文档的内容......
  • STM32G0x0系列 全中文汉化参考手册 中英双语对照 GPT机翻 共989页、约50万字
    STM32G0x0系列全中文参考手册,中英文双语对照阅读。适用于:STM32G030、STM32G050、STM32G070、STM32G0B0内容、格式对照官方原文,含标签导航及目录跳转。全文GPT机翻,除人工翻译外,相对更加贴合原文原意,双语版防止翻译错误方便对照。全文:989页,约50万字。*******下有更多展示......
  • 【STM32】串口不定长接收 保姆级教程
    为什么要使用串口不定长接收正常的接收函数HAL_UART_Receive(UART_HandleTypeDef*huart,uint8_t*pData,uint16_tSize,uint32_tTimeout)huart:句柄pData:盛放接收数据的变量Size:接收数据的大小中断接收函数HAL_UART_Receive_IT(UART_HandleTypeDef*huart,uint......
  • 毕业设计项目 stm32的人体健康状态检测系统(项目开源)
    文章目录0前言1硬件电路2软件设计3跌倒检测算法4软件部分MLX90614红外温度传感器5关键代码6最后0前言......
  • STM32F1之I2C通信·软件I2C代码编写
    目录1. 软件I2C代码编写 1.1 I2C起始方法一方法二方法三方法四1.2 I2C终止1.3 发送一个字节 1.4 接收一个字节1.5 发送应答1.6 接收应答1. 软件I2C代码编写     由于软件I2C不受引脚限制,随便找两个普通的GPIO口就可以使用,首先我们......
  • STM32F1之SPI通信·软件SPI代码编写
    目录1. 简介2. 硬件电路移位示意图3. SPI时序基本单元3.1  起始条件3.2 终止条件3.3  交换一个字节(模式0)3.4 交换一个字节(模式1)3.5 交换一个字节(模式2)3.6 交换一个字节(模式3)4. 代码编写4.1 引脚初始化4.2 引脚置高低电平封装4.2.1  S......
  • stm32嵌入式系统与应用习题 2
    目录一、选择题二、填空题三、简答题四、程序设计题一、选择题1.每个IO引脚都有一个复用器,该复用器采用(D)路复用功能输入。A.8B.16C.32D.42.固件库中的功能状态(FunctionalState)类型被赋予以下两个值(A)。A.ENABLE或者DISABLEB.SET......