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
  • 相关阅读:
    python3 TypeError: a bytes-like object is required, not 'str'
    Centos 安装Python Scrapy PhantomJS
    Linux alias
    Vim vimrc配置
    Windows下 Python Selenium PhantomJS 抓取网页并截图
    Linux sort
    Linux RSync 搭建
    SSH隧道 访问内网机
    笔记《鸟哥的Linux私房菜》7 Linux档案与目录管理
    Tornado 错误 "Global name 'memoryview' is not defined"
  • 原文地址:https://www.cnblogs.com/veis/p/15088305.html
Copyright © 2011-2022 走看看