嵌入式开发--STM32G4系列片上FLASH的读写

这个玩意吧,说起来很简单,就是几行代码的事,但楞是折腾了我大半天时间才搞定。原因后面说,先看代码吧:

读操作

读操作很简单,以32位方式读取的时候是这样的:

c 复制代码
data = *(__IO uint32_t *)(0x0800F000);

需要注意的是,当以32位方式读取时,地址需要是4的整数倍,即32位。

8位或16位方式类似操作即可

写操作

需要注意的是,写操作时,是以64位方式写入数据,即以双字的方式写入,以下代码是将一个u64的值0x12345678aabbccdd,写入0x0800F000这个地址

c 复制代码
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
HAL_FLASH_Program(0, 0x0800F000, 0x12345678aabbccdd);
HAL_FLASH_Lock();

扇区擦除

c 复制代码
  EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;    //删除方式
  EraseInitStruct.Page        = start;                    //超始页号
  EraseInitStruct.NbPages     = len;                      //页的数量
  EraseInitStruct.Banks       = bank;                     //bank号
  
  HAL_FLASH_Unlock();         //解锁,以准备进行FLASH操作
  HAL_FLASHEx_Erase(&EraseInitStruct, &err);      //擦除
  HAL_FLASH_Lock();           //上锁,以结束FLASH操作

调试

写完烧录开始调试,发现问题了,有时能写入,有时不能写入。

先找了正点原子的例程来做参考,他的可以写入,但似乎也是有问题,没有仔细研究。而且原子的例程是操作寄存器进行读写的,不直观,移植性也不好,个人还是喜欢用HAL库的方式来做东西。

然后又找了ST的例程来看,刚好手上有一块G4的开发板,于是编译,报错,可能是我的开发环境比较新,与ST官方的编译环境不同,又是一通折腾,编译通过,但一加载调试,就卡死不动。

于是新建工程,再把ST的例程移植到我的工程中,编译通过,可以调试,还是有时能写有时不能写。又回到了起点。

不过在前面的折腾中总结了一个规律,第1次写入几乎都会失败,第2次有一半的机率成功,但后续成功率很高,几乎不会写入失败。于是改进一下,增加对写入的判断,如果发生错误,会重复写入不超过10次,如果超过10次仍然错误,则写入失败。这样就可以保证写入成功了。

测试代码如下

未包含写入失败的相关代码,需要的自行添加。

c 复制代码
if(GET_KEY() == 0)
{
  data32[0] = *(__IO uint32_t *)(0x08010000+i*8);		//FLASH读数据,以字的方式读取
  data32[1] = *(__IO uint32_t *)(0x08010000+i*8+4);
  if((data32[0] == 0xffffffff) && (data32[1] == 0xffffffff))	//FLASH内容为空
  {
    LED1(1);
     HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
    
    flash_count=0;
    while(flash_count < 10)
    {
      if(HAL_FLASH_Program(0, 0x08010000+i*8, 0xaabbccdd12340000+i) == HAL_OK)
        break;
      else
        flash_count++;
    
    }
    HAL_FLASH_Lock();
  }
  data32[0] = *(__IO uint32_t *)(0x08010000+i*8);
  data32[1] = *(__IO uint32_t *)(0x08010000+i*8+4);
  while(GET_KEY() == 0)
    ;
  LED1(0);
  i++;
}

本段代码能正确运行的前提,是待写入区域是空的,即全都是0xFF才行。

封装为函数

将代码封装一下,以便后续调用,并增加了一些条件判断

头文件如下:

c 复制代码
//********************************************************************************************************
//*                                                                                   
//*  文件名:LL_flash.h                                                               
//*  文件说明:STM32G系列片上FLASH的相关操作                                                     
//*  作者:李佳                                                                       
//*  微信:LAOLIDESENLIN                                                              
//*  说明:本文档遵循GNU3.0开源许可证规范,即代码可开源并免费使用,引用、修改、衍生代码也需要开源、免费使用,
//*        但不允许修改后和衍生的代码做为闭源的商业软件发布和销售
//*
//********************************************************************************************************

#ifndef __LL_FLASH_H__
#define __LL_FLASH_H__

#include "LL_define.h"


/**************************************************************************************/
/* G431芯片的128KFLASH容量的页地址分布如下,共有1个BANK,64页,每一页2kb大小 */
#define STM32_FLASH_BASE        0x08000000      /* STM32 FLASH 起始地址 */
#define STM32_FLASH_SIZE        0x20000         /* STM32 FLASH 总大小*/
#define STM32_FLASH_PAGE_SIZE   0x800           /* STM32 FLASH 页大小*/


u8 LL_flash_erase_page(u16 start, u8 len, u8 bank);     //删除FLASH扇区
u8 LL_flash_read(u32 addr, u64* pdata64, u32 len_64);   //读片上FLASH
u8 LL_flash_write(u32 addr, u64* pdata64, u32 len_64);  //向片上FLASH写入数据

#endif

C文件如下

c 复制代码
//********************************************************************************************************
//*                                                                                   
//*  文件名:LL_flash.c                                                               
//*  文件说明:STM32G系列片上FLASH的相关操作                                                     
//*  作者:李佳                                                                       
//*  微信:LAOLIDESENLIN                                                              
//*  说明:本文档遵循GNU3.0开源许可证规范,即代码可开源并免费使用,引用、修改、衍生代码也需要开源、免费使用,
//*        但不允许修改后和衍生的代码做为闭源的商业软件发布和销售
//*
//*
//*
//********************************************************************************************************


#include "LL_flash.h"
#include "LL_IO.h"


//按页删除片上FLASH数据
//start: 起始页号
//len:  待删除的页的数量
//bank: bank编号
//返回值:错误类型,0表示无错误
u8 LL_flash_erase_page(u16 start, u8 len, u8 bank)
{
  u32 err;
  u8 ret;
  FLASH_EraseInitTypeDef EraseInitStruct;
  u8 flash_count=0;
  
  EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;    //删除方式
  EraseInitStruct.Page        = start;                    //超始页号
  EraseInitStruct.NbPages     = len;                      //页的数量
  EraseInitStruct.Banks       = bank;                     //bank号
  
  HAL_FLASH_Unlock();         //解锁,以准备进行FLASH操作
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
  
  for(u8 i=0; i<10; i++)      //最多重复10次,如果仍然失败,则返回
  {
    ret = HAL_FLASHEx_Erase(&EraseInitStruct, &err);      //擦除
    if (ret == HAL_OK)
      break;
  }
  
  if(ret != 0)
    led_err_flash(10, 100);   //擦除失败后闪灯提示, 供调试阶段使用, 正式使用时可删除
  
  HAL_FLASH_Lock();           //上锁,以结束FLASH操作
  return ret;
}


//读片上FLASH
//addr: 32位的地址值,该值应当是8的整数倍,因为是按照64位的方式读取数据的
//pdata: 返回的数据首地址
//len: 待读取数据的长度, 长度是按u64的数量
//返回值: 错误类型,0表示无错误
u8 LL_flash_read(u32 addr, u64* pdata64, u32 len_64)
{
  u64 data;
  
  for(u32 i=0; i<len_64; i++)
  {
    data = *(__IO uint64_t *)(addr+i*8);
    pdata64[i] = data;
  }
  return 0;
}


//向片上FLASH写入数据,每次写入8字节,不够8字节的,以0xFF补足
//addr: 32位的地址值,该值应当是8的整数倍,因为是按照64位的方式读取数据的
//pdata: 待写入的数据首地址
//len: 待写入数据的长度,长度是按u64的数量
//返回值: 错误类型,0表示无错误
u8 LL_flash_write(u32 addr, u64* pdata64, u32 len_64)
{
  u8 ret;
  u64 read;
  
  HAL_FLASH_Unlock();           //上锁,以结束FLASH操作
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);

  for(u16 j=0; j<len_64; j++) //按u64计算的
  {
    for(u8 i=0; i<10; i++)    //不超过10次的重复操作,以保证写入成功
    {
      ret = HAL_FLASH_Program(0, addr+j*8, *(pdata64+j));
      if(ret == HAL_OK)
      {
        read = *(__IO uint64_t *)(addr+j*8);  //在MCU认为写入正确以后,再次读取一下数据,并进行比对,如果比对不成功,也说明写入出错
        if(read != *(pdata64+j))
          ret = 255;
        return ret;
      }
    }
  }
  
  if(ret != 0)
    led_err_flash(10, 100);   //擦除失败后闪灯提示, 供调试阶段使用, 正式使用时可删除
  
  HAL_FLASH_Lock();           //上锁,以结束FLASH操作

  return ret;
}

封装后的测试函数

c 复制代码
u64 data64[32]={0};
u8 g_text_buf[64] = {"LIJIA000LIJIA001LIJIA002LIJIA003LIJIA004LIJIA005LIJIA006LIJIA007"};

    if(GET_KEY() == 0)
    {
      HAL_Delay(200);
      LL_flash_read(0x08010000, data64, 4);
      if(data64[0] == 0xffffffffffffffff)       //如果是空的,则写入
      {
        LED1(1);
        LED2(1);
        if(LL_flash_write(0x08010000, (u64*)g_text_buf, 4) != 0)
          continue;
      }
      else                                      //如果为非空,则擦除
      {
        LL_flash_erase_page(32, 1, 0);    //第32页,长度1页,BANK 0
      }
      LL_flash_read(0x08010000, data64, 4);
      while(GET_KEY() == 0)
        ;
      LED1(0);
      LED2(0);
      i++;
    }

测试结果如下图,如果写入成功,LED灯会在操作之后灭掉,如果写入失败,则LED灯会保持点亮状态,

总结

绝对不能只写入一次,就认为写入正确,恰恰相反,最开始的2次写入,极有可能写入失败。

所以必须重复写入几次,并且增加校验,即写入完成后,再读取数据,并进行比较,以确保正确。

相关推荐
国产化嵌入式平台解决方案1 小时前
【服务器主板】定制化:基于Intel至强平台的全新解决方案
嵌入式硬件·intel·服务器主板·至强处理器·定制化
不能只会打代码2 小时前
32单片机从入门到精通之硬件架构——内核与外设(一)
单片机·嵌入式硬件·硬件架构
陌夏微秋4 小时前
STM32单片机芯片与内部47 STM32 CAN内部架构 介绍
数据库·stm32·单片机·嵌入式硬件·架构·信息与通信
7yewh16 小时前
Linux驱动开发 IIC I2C驱动 编写APP访问EEPROM AT24C02
linux·arm开发·驱动开发·嵌入式硬件·嵌入式
上海易硅智能科技局有限公司16 小时前
AG32 MCU 的电机控制方案
单片机·嵌入式硬件
程序员JerrySUN16 小时前
Yocto 项目 - 共享状态缓存 (Shared State Cache) 机制
linux·嵌入式硬件·物联网·缓存·系统架构
嵌入式小强工作室18 小时前
stm32能跑人工智能么
人工智能·stm32·嵌入式硬件
MikelSun18 小时前
电压控制环与电流控制环
单片机·嵌入式硬件·物联网
陌夏微秋19 小时前
STM32单片机芯片与内部45 UART 不定长度接收 标志位结束 定时器超时 串口空闲中断
stm32·单片机·嵌入式硬件·信息与通信·智能硬件
挥剑决浮云 -20 小时前
STM32学习之 按键/光敏电阻 控制 LED/蜂鸣器
c语言·经验分享·stm32·单片机·嵌入式硬件·学习