这个玩意吧,说起来很简单,就是几行代码的事,但楞是折腾了我大半天时间才搞定。原因后面说,先看代码吧:
读操作
读操作很简单,以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次写入,极有可能写入失败。
所以必须重复写入几次,并且增加校验,即写入完成后,再读取数据,并进行比较,以确保正确。