单片机
返回首页

STM32CubeMX学习笔记(51)——读写内部Flash

2025-02-11 来源:jianshu

一、简介

在STM32芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中,由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。


STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小见下表


  • 主存储器

一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。

主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。

注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
主存储器是以页为单位划分的。stm32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。

  • 小容量产品:主存储块1-32KB,     每页1KB。系统存储器2KB

  • 中容量产品:主存储块64-128KB,   每页1KB。系统存储器2KB

  • 大容量产品:主存储块256KB以上,  每页2KB。系统存储器2KB

  • 互联型产品:主存储块256KB以上,  每页2KB。系统存储器18KB

  • 系统存储区

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

  • 选项字节

选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”


2. 选择 MCU 和封装


3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)


选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置


4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire


三、添加串口打印

串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用

四、生成代码

输入项目名和项目路径


选择应用的 IDE 开发环境 MDK-ARM V5


每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。


点击 GENERATE CODE 生成代码


五、查看工程的空间分布

由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生
的“*.map”后缀文件,可以了解程序存储到了哪些区域。


打开 map 文件后,查看文件最后部分的区域,可以看到一段以 “Memory Map of the image” 开头的记录(若找不到可用查找功能定位)


观察表中的最后一项,它的基地址是 0x0800175c,大小为 0x00000020,可知它占用的
最高的地址空间为 0x0800177c,跟执行区域的最高地址 0x0000177c 一样,但它们比加载
区域说明中的最高地址 0x80017a8 要小,所以我们以加载区域的大小为准。对比表 45-1 的
内部 FLASH 页地址分布表,可知仅使用页 0 至页 2 就可以完全存储本应用程序,所以从页
3(地址 0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程
序空间的数据。


六、官方HAL库Flash操作常见函数

//源文件: stm32f1xx_hal_flash.c和stm32f1xx_hal_flash_ex.c

HAL_FLASH_Unlock(void); //解锁函数

HAL_FLASH_Lock(void);   //锁定函数

HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data);   //写操作函数

HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError);   //擦除函数

HAL_FLASH_WaitForLastOperation(uint32_t Timeout);   //等待操作完成函数


HAL库中定义了一个Flash初始化结构体,如下:

/**

  * @brief  FLASH Erase structure definition

  */

typedef struct

{

  uint32_t TypeErase;   /*!< Mass erase or page erase.

                             This parameter can be a value of @ref FLASH_Type_Erase */

  uint32_t Banks;       /*!< Select bank to erase.

                             This parameter must be a value of @ref FLASH_Banks

                             (FLASH_BANK_BOTH should be used only for mass erase) */

  uint32_t Page;        /*!< Initial Flash page to erase when page erase is disabled

                             This parameter must be a value between 0 and (max number of pages in the bank - 1)

                             (eg : 255 for 1MB dual bank) */

  uint32_t NbPages;     /*!< Number of pages to be erased.

                             This parameter must be a value between 1 and (max number of pages in the bank - value of initial page)*/

} FLASH_EraseInitTypeDef;


七、读取Flash

7.1 读取函数

/* FLASH大小:STM32F103VET6:256K */

#define STM32FLASH_SIZE         0x00040000UL

/* FLASH起始地址 */

#define STM32FLASH_BASE         FLASH_BASE

/* FLASH结束地址 */

#define STM32FLASH_END          (STM32FLASH_BASE | STM32FLASH_SIZE)

/* FLASH页大小:1K */

#define STM32FLASH_PAGE_SIZE    FLASH_PAGE_SIZE

/* FLASH总页数 */

#define STM32FLASH_PAGE_NUM     (STM32FLASH_SIZE / STM32FLASH_PAGE_SIZE)


#define WRITE_START_ADDR        ((uint32_t)0x08008000)

#define WRITE_END_ADDR          ((uint32_t)0x0800C000)


/**

 @brief 内部Flash读取

 @param address -[in] 读取的地址

 @param pData -[out] 指向需要操作的数据

 @param dataLen -[in] 数据长度

 @return 读出成功的字节数

*/

uint32_t Internal_ReadFlash(uint32_t addrStart, void *pData, uint32_t dataLen)

{

    uint32_t nread = dataLen;

    uint8_t *pBuffer = (uint8_t *)pData;

    const uint8_t *pAddr = (const uint8_t *)addrStart;


    if(!pData || addrStart < STM32FLASH_BASE || addrStart > STM32FLASH_END)

    {

        return 0;

    }


    while(nread >= sizeof(uint32_t) && (((uint32_t)pAddr) <= (STM32FLASH_END - 4)))

    {

        *(uint32_t *)pBuffer = *(uint32_t *)pAddr;

        pBuffer += sizeof(uint32_t);

        pAddr += sizeof(uint32_t);

        nread -= sizeof(uint32_t);

    }


    while(nread && (((uint32_t)pAddr) < STM32FLASH_END))

    {

        *pBuffer++ = *pAddr++;

        nread--;

    }


    return dataLen - nread;

}


8、写入Flash

8.1 写入过程

8.1.1 解锁

由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。

所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:

  1. 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123

  2. 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB

8.1.2 页擦除

在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程如下:

  1. 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;

  2. 在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;

  3. 用 FLASH_AR 寄存器选择要擦除的页;

  4. 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;

  5. 等待 BSY 位被清零时,表示擦除完成。

/**

 @brief 内部Flash页擦除

 @param pageAddress -[in] 擦除的起始地址

 @param nbPages -[in] 擦除页数

 @return 0 - 成功;-1 - 失败

*/

int Internal_ErasePage(uint32_t pageAddress, uint32_t nbPages)

{

    uint32_t pageError = 0;

    FLASH_EraseInitTypeDef eraseInit;

    eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;

    eraseInit.PageAddress = pageAddress;

    eraseInit.Banks = FLASH_BANK_1;

    eraseInit.NbPages = 1;

    if(HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK)

    {

        return -1;

    }

    return 0;

}


8.1.3 写入数据

擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还需要配置一系列的寄存器,步骤如下:

  1. 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;

  2. 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;

  3. 向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;

  4. 等待 BSY 位被清零时,表示写入完成。


8.2 写入函数

/**

 @brief 内部Flash写入

 @param address -[in] 写入的地址

 @param pData -[in] 指向需要操作的数据

 @param dataLen -[in] 数据长度

 @return 实际写入的数据量,单位:字节

*/

uint32_t Internal_WriteFlash(uint32_t addrStart, const uint16_t *pData, uint32_t dataLen)

{   

    uint32_t i = 0;

    uint32_t pagepos = 0;         // 页位置

    uint32_t pageoff = 0;         // 页内偏移地址

    uint32_t pagefre = 0;         // 页内空余空间

    uint32_t offset = 0;          // Address在FLASH中的偏移

    uint32_t nwrite = dataLen;    // 记录剩余要写入的数据量

    const uint16_t *pBuffer = (const uint16_t *)pData;

    

    /* 非法地址 */

    if(addrStart < STM32FLASH_BASE || addrStart > (STM32FLASH_END - 2) || dataLen == 0 || pData == NULL)

    {

        return 0;

    }

    

    /* 解锁FLASH */

    HAL_FLASH_Unlock();


    /* 计算偏移地址 */

    offset = addrStart - STM32FLASH_BASE;

    /* 计算当前页位置 */

    pagepos = offset / STM32FLASH_PAGE_SIZE;

    /* 计算要写数据的起始地址在当前页内的偏移地址 */

    pageoff = ((offset % STM32FLASH_PAGE_SIZE) >> 1);

    /* 计算当前页内空余空间 */

    pagefre = ((STM32FLASH_PAGE_SIZE >> 1) - pageoff);

    /* 要写入的数据量低于当前页空余量 */

    if(nwrite <= pagefre)

    {

        pagefre = nwrite;

    }

    

    while(nwrite != 0)

    {

        /* 检查是否超页 */

        if(pagepos >= STM32FLASH_PAGE_NUM)

        {

            break;

        }


        /* 读取一页 */

        Internal_ReadFlash(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE);


        /* 检查是否需要擦除 */

        for(i = 0; i < pagefre; i++)

        {

            if(*(FlashBuffer + pageoff + i) != 0xFFFF) /* FLASH擦出后默认内容全为0xFF */

            {

                break;

            }

        }


        if(i < pagefre)

        {

            uint32_t count = 0;

            uint32_t index = 0;

            uint32_t PageError = 0;

            FLASH_EraseInitTypeDef pEraseInit;


            /* 擦除一页 */

            pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;

            pEraseInit.PageAddress = STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE;

            pEraseInit.Banks = FLASH_BANK_1;

            pEraseInit.NbPages = 1;

            if(HAL_FLASHEx_Erase(&pEraseInit, &PageError) != HAL_OK)

            {

                break;

            }


            /* 复制到缓存 */

            for(index = 0; index < pagefre; index++)

            {

                *(FlashBuffer + pageoff + index) = *(pBuffer + index);

            }


            /* 写回FLASH */

            count = Internal_WriteFlashNoCheck(STM32FLASH_BASE + pagepos * STM32FLASH_PAGE_SIZE, FlashBuffer, STM32FLASH_PAGE_SIZE >> 1);

            if(count != (STM32FLASH_PAGE_SIZE >> 1))

            {

                nwrite -= count;

                break;

            }

        }

        else

        {

            /* 无需擦除,直接写 */

            uint32_t count = Internal_WriteFlashNoCheck(addrStart, pBuffer, pagefre);

            if(count != pagefre)

            {

                nwrite -= count;

                break;

            }

        }


        pBuffer += pagefre;         /* 读取地址递增         */

        addrStart += (pagefre << 1);  /* 写入地址递增         */

        nwrite -= pagefre;          /* 更新剩余未写入数据量 */


        pagepos++;     /* 下一页           */

        pageoff = 0;   /* 页内偏移地址置零  */


        /* 根据剩余量计算下次写入数据量 */

        pagefre = nwrite >= (STM32FLASH_PAGE_SIZE >> 1) ? (STM32FLASH_PAGE_SIZE >> 1) : nwrite;

    }


    /* 加锁FLASH */

    HAL_FLASH_Lock();


    return ((dataLen - nwrite) << 1);

}


九、举例


/**

  * @brief  The application entry point.

  * @retval int

  */

int main(void)

{

  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration--------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */

  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */

  MX_GPIO_Init();

  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */

    uint8_t in_data[5]={11,22,33,44,55};//要写入的数据

    uint8_t out_data[5];//读存放

    int i;

    uint32_t STATUS = 0;

    STATUS = Internal_WriteFlash(0x08001800, (uint16_t *)in_data, 5);

    HAL_Delay(1000);

    if(STATUS)

    {

        Internal_ReadFlash(0x08001800, (uint16_t *)out_data, 5);

        printf('rn The Five Data Is : rn');

        for(i = 0; i < 5; i++)

        {

            printf('r %d r', out_data[i]);

        }

    }

  /* USER CODE END 2 */


  /* Infinite loop */

  /* USER CODE BEGIN WHILE */

  while (1)

  {

    /* USER CODE END WHILE */


    /* USER CODE BEGIN 3 */

  }

  /* USER CODE END 3 */

}


查看打印:


十、工程代码

链接:https://pan.baidu.com/s/1zfp9AkJ5jiaugWfdKaxfQg?pwd=9kpr 提取码:9kpr


十一、注意事项

用户代码要加在 USER CODE BEGIN N 和 USER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。

进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦线性调频增强器

  • 家用电器遥控器

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • DS1669数字电位器

  • HA1377 桥式放大器 BCL 电容 17W(汽车音频)

    相关电子头条文章