zoukankan      html  css  js  c++  java
  • STM32内部Flash读写操作

    STM32内部Flash读写操作

    硬件平台:以STM32F103C8T6为例

    固件库SDK版本:HAL V1.8.3

    1、内存映射介绍

    (1)stm32的flash地址起始于0x0800 0000,结束地址是0x0800 0000加上芯片实际的flash大小,不同的芯片flash大小不同。

    (2)RAM起始地址是0x2000 0000,结束地址是0x2000 0000加上芯片的RAM大小。不同的芯片RAM也不同。

    Flash中的内容一般用来存储代码和一些定义为const的数据,断电不丢失, RAM可以理解为内存,用来存储代码运行时的数据,变量等等。掉电数据丢失。STM32将外设等都映射为地址的形式,对地址的操作就是对外设的操作。

    stm32的外设地址从0x4000 0000开始,可以看到在库文件中,是通过基于0x4000 0000地址的偏移量来操作寄存器以及外设的。

    一般情况下,程序文件是从 0x0800 0000 地址写入,这个是STM32开始执行的地方,0x0800 0004是STM32的中断向量表的起始地址。
    在使用keil进行编写程序时,其编程地址的设置如下图:

    程序的写入地址从0x08000000开始的,其大小为0x10000也就是64KB的空间,换句话说就是告诉编译器flash的空间是从0x08000000-0x08010000,RAM的地址从0x20000000开始,大小为0x05000也就是20KB的RAM。这与STM32的内存地址映射关系是对应的。

    M3复位后,从0x08000004取出复位中断的地址,并且跳转到复位中断程序,中断执行完之后会跳到我们的main函数,main函数里边一般是一个死循环,进去后就不会再退出,当有中断发生的时候,M3将PC指针强制跳转回中断向量表,然后根据中断源进入对应的中断函数,执行完中断函数之后,再次返回main函数中。大致的流程就是这样。


    2、Flash分布介绍

    flash由主存储区、系统存储区、选项字节区域,部分型号还有OTP(one time program)区域组成,地址分布如下图:

    image-20210801235137513

    image-20210801235201855

    主存储器:一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域它是存储用户应用程序的空间,芯片型号说明中的 1M FLASH、 2M FLASH 都是指这个区域的大小。与其它 FLASH 一样,在写入数据前,要先按扇区擦除。

    系统存储区:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB 以及 CAN 等 ISP 烧录功能。

    OTP 区域:OTP(One Time Program),指的是只能写入一次的存储区域,容量为 512 字节,写入后数据就无法再更改, OTP 常用于存储应用程序的加密密钥。

    选项字节区域:选项字节用于配置 FLASH 的读写保护、电源管理中的 BOR 级别、软件/硬件看门狗等功能,这部分共 32 字节。可以通过修改 FLASH 的选项控制寄存器修改。


    3、读写flash操作流程

    1. 解锁 (固定的KEY值)
      (1) 往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
      (2) 再往 Flash 密钥寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB

    1. 数据操作位数
      最大操作位数会影响擦除和写入的速度,其中 64 位宽度的操作除了配置寄存器位外,还需要在 Vpp 引脚外加一个电压源,且其供电间不得超过一小时,否则 FLASH可能损坏,所以 64 位宽度的操作一般是在量产时对 FLASH 写入应用程序时才使用,大部分应用场合都是用 32 位的宽度。

    2. 擦除扇区
      在写入新的数据前,需要先擦除存储区域, STM32 提供了扇区擦除指令和整个FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
      扇区擦除的过程如下:
      (1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
      (2) 在 FLASH_CR 寄存器中,将“激活扇区擦除寄存器位 SER ”置 1,并设置“扇 区编号寄存器位 SNB”,选择要擦除的扇区;
      (3) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
      (4) 等待 BSY 位被清零时,表示擦除完成。

    3. 写入数据
      擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:
      (1) 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
      (2) 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;
      (3) 针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作;
      (4) 等待 BSY 位被清零时,表示写入完成。

    4. 写flash操作流程图

    读flash操作流程

    4、代码实现

    官方库函数提供了几个接口函数,可对flash进行解锁、上锁、读、写、擦除等操作

    stm32f1xx_hal_flash.h

    stm32f1xx_hal_flash.c

    stm32f1xx_hal_flash_ex.h

    stm32f1xx_hal_flash_ex.c

    1. 接口函数介绍
    // 解锁操作函数
    HAL_StatusTypeDef HAL_FLASH_Unlock(void);
    HAL_StatusTypeDef HAL_FLASH_Lock(void);
    
    // 写操作函数
    /**
      * @brief  Program halfword, word or double word at a specified address
      * @note   The function HAL_FLASH_Unlock() should be called before to unlock the FLASH interface
      *         The function HAL_FLASH_Lock() should be called after to lock the FLASH interface
      *
      * @note   If an erase and a program operations are requested simultaneously,    
      *         the erase operation is performed before the program one.
      *  
      * @note   FLASH should be previously erased before new programmation (only exception to this 
      *         is when 0x0000 is programmed)
      *
      * @param  TypeProgram:  Indicate the way to program at a specified address.
      *                       This parameter can be a value of @ref FLASH_Type_Program
      * @param  Address:      Specifies the address to be programmed.
      * @param  Data:         Specifies the data to be programmed
      * 
      * @retval HAL_StatusTypeDef HAL Status
      */
    HAL_StatusTypeDef HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
    
    /**
      * @brief  Program halfword, word or double word at a specified address  with interrupt enabled.
      * @note   The function HAL_FLASH_Unlock() should be called before to unlock the FLASH interface
      *         The function HAL_FLASH_Lock() should be called after to lock the FLASH interface
      *
      * @note   If an erase and a program operations are requested simultaneously,    
      *         the erase operation is performed before the program one.
      *
      * @param  TypeProgram: Indicate the way to program at a specified address.
      *                      This parameter can be a value of @ref FLASH_Type_Program
      * @param  Address:     Specifies the address to be programmed.
      * @param  Data:        Specifies the data to be programmed
      * 
      * @retval HAL_StatusTypeDef HAL Status
      */
    HAL_StatusTypeDef HAL_FLASH_Program_IT(uint32_t TypeProgram, uint32_t Address, uint64_t Data);
    
    // 擦除操作函数
    /**
      * @brief  Perform a mass erase or erase the specified FLASH memory pages
      * @note   To correctly run this function, the @ref HAL_FLASH_Unlock() function
      *         must be called before.
      *         Call the @ref HAL_FLASH_Lock() to disable the flash memory access 
      *         (recommended to protect the FLASH memory against possible unwanted operation)
      * @param[in]  pEraseInit pointer to an FLASH_EraseInitTypeDef structure that
      *         contains the configuration information for the erasing.
      *
      * @param[out]  PageError pointer to variable  that
      *         contains the configuration information on faulty page in case of error
      *         (0xFFFFFFFF means that all the pages have been correctly erased)
      *
      * @retval HAL_StatusTypeDef HAL Status
      */
    HAL_StatusTypeDef  HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError);
    HAL_StatusTypeDef  HAL_FLASHEx_Erase_IT(FLASH_EraseInitTypeDef *pEraseInit);
    
    1. 用户接口实现

    flash.h

    #ifndef __FLASH_H__
    #define __FLASH_H__
    
    #include "main.h"
    
    #define FLASH_BASE_ADDR		0x08000000UL		/* Flash基地址 */
    #define SECTOR_SIZE			1024				/* 块大小 */
    #define	FLASH_SIZE			(1*64*1024)			/* Flash 容量 */
    #define UserFlashAddress    ((uint32_t)(FLASH_BASE_ADDR | 0xFC00)) // 用户读写起始地址(内部flash的主存储块地址从0x080FC000开始)
    
    #endif /* __FLASH_H__ */
    

    flash.c

    #include "flash.h"
    
    //从指定地址开始写入多个数据(16位)
    void FLASH_WriteHalfWordData(uint32_t startAddress, uint16_t *writeData, uint16_t countToWrite)
    {
        uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 计算去掉0X08000000后的实际偏移地址
        uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 计算扇区地址,对于STM32F103VET6为0~255
        uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 对应扇区的首地址
        uint16_t dataIndex;
    
        if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 2) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
        {
            return; // 非法地址
        }
        FLASH_Unlock(); // 解锁写保护
    
        FLASH_ErasePage(sectorStartAddress); // 擦除这个扇区
    
        for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
        {
            HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, startAddress + dataIndex * 2, writeData[dataIndex]);
        }
    
        FLASH_Lock(); // 上锁写保护
    }
    
    //从指定地址开始写入多个数据(32位)
    void FLASH_WriteWordData(uint32_t startAddress, uint32_t *writeData, uint16_t countToWrite)
    {
        uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 计算去掉0X08000000后的实际偏移地址
        uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 计算扇区地址,对于STM32F103VET6为0~255
        uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 对应扇区的首地址
        uint16_t dataIndex;
    
        if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 4) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
        {
            return; // 非法地址
        }
        FLASH_Unlock(); // 解锁写保护
    
        FLASH_ErasePage(sectorStartAddress); // 擦除这个扇区
    
        for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
        {
            HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, startAddress + dataIndex * 4, writeData[dataIndex]);
        }
    
        FLASH_Lock(); // 上锁写保护
    }
    
    //从指定地址开始写入多个数据(64位)
    void FLASH_WriteDoubleWordData(uint32_t startAddress, uint64_t *writeData, uint16_t countToWrite)
    {
        uint32_t offsetAddress = startAddress - FLASH_BASE_ADDR; // 计算去掉0X08000000后的实际偏移地址
        uint32_t sectorPosition = offsetAddress / SECTOR_SIZE; // 计算扇区地址,对于STM32F103VET6为0~255
        uint32_t sectorStartAddress = sectorPosition * SECTOR_SIZE + FLASH_BASE_ADDR; // 对应扇区的首地址
        uint16_t dataIndex;
    
        if (startAddress < FLASH_BASE_ADDR || ((startAddress + countToWrite * 8) >= (FLASH_BASE_ADDR + SECTOR_SIZE * FLASH_SIZE)))
        {
            return; // 非法地址
        }
        FLASH_Unlock(); // 解锁写保护
    
        FLASH_ErasePage(sectorStartAddress); // 擦除这个扇区
    
        for (dataIndex = 0; dataIndex < countToWrite; dataIndex++)
        {
            HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, startAddress + dataIndex * 8, writeData[dataIndex]);
        }
    
        FLASH_Lock(); // 上锁写保护
    }
    
    // 读取指定地址的半字(16位数据)
    uint16_t FLASH_ReadHalfWord(uint32_t address)
    {
        return *(__IO uint16_t*)address;
    }
    
    // 读取指定地址的单字(32位数据)
    uint16_t FLASH_ReadWord(uint32_t address)
    {
        return *(__IO uint32_t*)address;
    }
    
    // 读取指定地址的双字(64位数据)
    uint16_t FLASH_ReadDoubleWord(uint32_t address)
    {
        return *(__IO uint64_t*)address;
    }
    
    // 从指定地址开始读取多个数据(16位数据)
    void FLASH_ReadHalfWordData(uint32_t startAddress, uint16_t *readData, uint16_t countToRead)
    {
        uint16_t dataIndex;
        for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
        {
            readData[dataIndex] = FLASH_ReadHalfWord(startAddress + dataIndex * 2);
        }
    }
    
    // 从指定地址开始读取多个数据(32位数据)
    void FLASH_ReadWordData(uint32_t startAddress, uint32_t *readData, uint16_t countToRead)
    {
        uint16_t dataIndex;
        for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
        {
            readData[dataIndex] = FLASH_ReadWord(startAddress + dataIndex * 4);
        }
    }
    
    // 从指定地址开始读取多个数据(64位数据)
    void FLASH_ReadDoubleWordData(uint32_t startAddress, uint64_t *readData, uint16_t countToRead)
    {
        uint16_t dataIndex;
        for (dataIndex = 0; dataIndex < countToRead; dataIndex++)
        {
            readData[dataIndex] = FLASH_ReadDoubleWord(startAddress + dataIndex * 8);
        }
    }
    

    test.c

    #include "main.h"
    #include "flash.h"
    
    void write_to_flash(void)
    {
    	uint16_t buff[64];
    	uint16_t count_len = sizeof(buff) / sizeof(buff[0]);
    	printf("
    WriteData successful!
    ");
    	for(uint16_t i = 0; i < count_len; ++i)
    	{
    		if(i != 0 && i % 16 == 0)
    			printf("
    ");
    		buff[i] = rand() % 64 + 1;
    		printf("0x%02x,", buff[i]);
    	}
        FLASH_WriteHalfWordData(UserFlashAddress,buff,count_len);
    	printf("
    ");
    }
    
    void read_from_flash(void)
    {
    	uint16_t buff[64];
    	uint16_t count_len = sizeof(buff) / sizeof(buff[0]);
    	FLASH_ReadHalfWordData(UserFlashAddress, buff,count_len);
    	printf("
    ReadData successful!
    ");
    	for(uint16_t i = 0; i < count_len; ++i)
    	{
    		if(i != 0 && i % 16 == 0)
    			printf("
    ");
    		printf("0x%02x,", buff[i]);
    	}
    	printf("
    ");
    
    }
    
    void main(void)
    {
        // 省略掉了部分初始化代码,只保留调用代码
        write_to_flash();
        read_from_flash();
        while(1);
    }
    

    测试结果截图:

    Posted By veis
  • 相关阅读:
    Android5.0全透明状态栏效果
    从关系库导入数据到hive-hbase表中
    Find Minimum in Rotated Sorted Array II 旋转数组中找最小值(有反复元素) @LeetCode
    深度解析国内首个云原生数据库POLARDB的“王者荣耀”
    深度解析国内首个云原生数据库POLARDB的“王者荣耀”
    nodejs中httpserver的安装和使用
    Java的循环语句
    Java的循环语句
    Java的循环语句
    Mybatis-generator生成Service和Controller
  • 原文地址:https://www.cnblogs.com/veis/p/15088305.html
Copyright © 2011-2022 走看看