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

2023-05-16

一、简介

在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("\r\n The Five Data Is : \r\n");
        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 NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。


• 由 Leung 写于 2023 年 2 月 7 日

• 参考:STM32CubeMX | STM32F1系列HAL库读写内部FLASH
    STM32CubeMX系列|STM32内部FLASH

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32CubeMX学习笔记(51)——读写内部Flash 的相关文章

随机推荐