在日常开发中,经常遇到要保存一些配置数据到flash中,而普通的保存方法虽然粗暴简单,但是当数据类型多时,这种方法就不适用了,所以需要使用结构体的存储方法。
我使用的是EFM32单片机,不同的单片机flash读写驱动不相同,需要自行开发,部分代码如下所示, 其中主要使用 iap_write_appbin(),与efmflash_read()函数。
#include "main.h"
#include "iap.h"
#include "userpage.h"
#include <string.h>
#include "em_device.h"
#include "em_msc.h"
#include "em_usart.h"
#include "em_leuart.h"
iapfun jump2app;
#if STM32_FLASH_SIZE<256 //flash大小< 256k
#define STM_SECTOR_SIZE 512 //定义每个扇区512字节
#else
#define STM_SECTOR_SIZE 2048
#endif
uint32_t STMFLASH_BUF[STM_SECTOR_SIZE/4]={0};//efm32:最多是2K字节 (长度为扇区长度,单位为字节)
extern msc_Return_TypeDef currentError;
/***************************************************
* 函数名称: iap_write_appbin
* 功能描述: 固件写入
* 输入参数: appxaddr:应用程序的起始地址
appbuf:应用程序CODE.
appsize:应用程序大小(字节). (必须小于一个扇区512字节)
* 输出参数: 无
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
uint8_t iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t appsize)
{
uint32_t iapbuf[128];
uint16_t t;
uint16_t i=0;
uint32_t temp;
uint32_t fwaddr=appxaddr;//当前写入的地址
uint8_t *dfu=appbuf;
if(appsize>512)
{
return 1;
}
memset(iapbuf, '\0',sizeof(iapbuf));
memset(STMFLASH_BUF, '\0',sizeof(STMFLASH_BUF));
for(t=0;t<appsize;t+=4)
{
temp=(uint32_t)dfu[3]<<24;
temp+=(uint32_t)dfu[2]<<16;
temp+=(uint32_t)dfu[1]<<8;
temp+=(uint32_t)dfu[0];
dfu+=4;//偏移4个字节
iapbuf[i++]=temp;
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//写入扇区
return 0;
}
/***************************************************
* 函数名称: STMFLASH_ReadHalfWord
* 功能描述: 读取指定地址的半字(4位数据)
* 输入参数: faddr:读地址(此地址必须为4的倍数
* 输出参数: 返回值:对应数据.
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
uint32_t STMFLASH_ReadHalfWord(uint32_t faddr)
{
return *(vu32*)faddr;
}
/***************************************************
* 函数名称: STMFLASH_Write
* 功能描述: 数据写入
* 输入参数:
从指定地址开始写入指定长度的数据
WriteAddr:起始地址(此地址必须为2的倍数
pBuffer:数据指针
NumToWrite:半字(16位)数(就是要写入的16位数据的个数
* 输出参数: 无
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
void STMFLASH_Write(uint32_t WriteAddr,uint32_t *pBuffer,uint16_t NumToWrite)
{
uint32_t secpos; //扇区地址
uint16_t secoff; //扇区内偏移地址(16位字计算)
uint16_t secremain; //扇区内剩余地址(16位字计算)
uint16_t i;
uint32_t offaddr; //去掉0X00000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))
{
return ;//非法地址
}
else
{;}
offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址. //0x5000
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 // 0x5000 / 512字节 (定位在哪个扇区)
secoff=(offaddr%STM_SECTOR_SIZE)/4; //在扇区内的偏移(4个字节为基本单位.) (定位在扇区内的地址)
secremain=STM_SECTOR_SIZE/4-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite; //不大于该扇区范围
while(1)
{
//STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/4);//读出整个扇区的内容
efmflash_read(STMFLASH_BUF,secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE, STM_SECTOR_SIZE);
for(i=0;i<secremain;i++)//校验数据,检查整个扇区有没有不是0XFFFF的
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除
}
if(i<secremain)//需要擦除
{
efmflash_erase(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除当前扇区!!!!!!!!
for(i=0;i<secremain;i++)//复制
{
STMFLASH_BUF[i+secoff]=pBuffer[i]; //以全字为单位
}
efmflash_write(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE);//efm32:写入整个扇区 (地址,数据,字节数!)传入的数据长度单位是字节(函数内部会转换为全字)
}
else efmflash_write(WriteAddr,pBuffer,secremain*4);//efm32:写已经擦除了的,直接写入扇区剩余区间. 单位:字节
if(NumToWrite==secremain)break;//写入结束了
else//写入未结束
{
secpos++; //扇区地址增1
secoff=0; //偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumToWrite-=secremain; //字节(16位)数递减
if(NumToWrite>(STM_SECTOR_SIZE/4))secremain=STM_SECTOR_SIZE/4;//下一个扇区还是写不完
else secremain=NumToWrite;//下一个扇区可以写完了
}
}
}
#endif
/***************************************************
* 函数名称: STMFLASH_Read
* 功能描述: 从指定地址开始读出指定长度的数据
* 输入参数: ReadAddr:起始地址 pBuffer:数据指针 NumToWrite:全字(32位)数
* 输出参数: 返回值:对应数据.
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
void STMFLASH_Read(uint32_t ReadAddr,uint32_t *pBuffer,uint16_t NumToRead)
{
uint16_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);//读取4个字节.
ReadAddr+=4;//偏移4个字节.
}
}
/***************************************************
* 函数名称: Test_Write
* 功能描述: 写一个32位数据到flash
* 输入参数: WriteAddr:起始地址 WriteData:要写入的数据
* 输出参数: 无
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
void Test_Write(uint32_t WriteAddr,uint32_t WriteData)
{
STMFLASH_Write(WriteAddr,&WriteData,1);//写入一个字
}
/***************************************************
* 函数名称: efmflash_write
* 功能描述: 写数据到flash
* 输入参数: uint32_t offset 地址, void const *data 数据, int numBytes 字节数
* 输出参数: 无
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
void efmflash_write(uint32_t offset, void const *data, int numBytes)
{
msc_Return_TypeDef ret;
MSC_Init();
/* Write data to the userpage */
//ret = MSC_WriteWord((uint32_t *)(NETPAGE + offset), data,numBytes);
//ret = MSC_WriteWord((uint32_t *)(NETPAGE ), data,numBytes);
ret = MSC_WriteWord((uint32_t *)offset, data,numBytes);
/* Check for errors. If there are errors, set the global error variable and
* deinitialize the MSC */
if (ret != mscReturnOk) //写入后循环累加地址,直到flash地址错误后return
{
currentError = ret;
MSC_Deinit();
return;
}
/* Deinitialize the MSC. This disables writing and locks the MSC */
MSC_Deinit();
}
/*******************************************************************
* 函数名称: efmflash_read
* 功能描述: 读取flash
* 输入参数:
* 输出参数: 无
* 返 回 值 无
* 其它说明: 无
* 修改日期:
修改人
***********************************************************************/
void efmflash_read(void *data,uint32_t offset, int numBytes)
{
memcpy(data, (uint32_t * )offset, numBytes); //从地址offset复制numBytes个字符到data
}
/*******************************************************************
* 函数名称: sysdelay
* 功能描述: 初始化timer0的基本信息
* 输入参数:
* 输出参数: 无
* 返 回 值 无
* 其它说明: 无
* 修改日期:
修改人
***********************************************************************/
void efmflash_erase(uint32_t addr)
{
msc_Return_TypeDef ret;
MSC_Init();
//ret = MSC_ErasePage((uint32_t *)NETPAGE);
//ret = MSC_ErasePage((uint32_t *)(NETPAGE+addr));
ret = MSC_ErasePage((uint32_t *)addr);
if (ret != mscReturnOk)
{
currentError = ret;
MSC_Deinit();
return ;
}
}
编写一个存储到flash中的结构体,注意此结构体必须使用无符号数据类型。例如unsigned char ,因为在存储或者读取结构体过程中,是以指针为载体的,如果数据类型结构没对齐,则会出现数据乱码的结果。
如下代码片段为我构造的结构体:
typedef struct //结构体 存放串口指令修改的变量
{
uint8_t HEART;//是否启用自定义心跳包 1:on 0:off
uint16_t HEARTTIME;//时间间隔
uint8_t HEARTDATA[100];//自定义心跳内容
uint32_t rs485_Baud_Rate;//485波特率
uint32_t rs232_Baud_Rate;//rs232波特率
uint32_t uart0_Baud_Rate;//无线模组波特率
uint8_t Password_flag;//是否启用密码功能 1:on 0:off
uint8_t PassSet[20];//输入密码
uint8_t AD1_OPEN;//打开AD1采集模式 1:on 0:off
uint8_t AD2_OPEN;//打开AD2采集模式 1:on 0:off
uint8_t AD1SENSORTYPE[6];//配置ad1传感器类型
uint8_t AD2SENSORTYPE[6];//配置ad2传感器类型
uint8_t rs485_OPEN;//关闭485采集模式 1:on 0:off
uint8_t PICKORDER[100];//自定义485采集指令
uint32_t SENSORTIME;//采集指令时间间隔 单位s
network_TypeDef Module;//网络指令缓存
uint8_t flash_flag;//flash 标志位 如果是1 则表示有数据写入
}DTU_TypeDef;
DTU_TypeDef config_dtu={0};
写入结构体函数是把结构体转成指针方式写入flash中。
其实就是调用写flash函数,其中:FLASH_REPIY_ADDR为写入到flash 的地址,(uint8_t *)&config_dtu 为结构体强制转换成指针代码,sizeof(config_dtu)为结构体大小。
/***************************************************
* 函数名称: config_dtu_Preservation
* 功能描述: 写入配置数据到flash中
* 输入参数:
* 输出参数:
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
uint8_t config_dtu_Preservation(void)
{
taskENTER_CRITICAL(); //进入临界区
iap_write_appbin(FLASH_REPIY_ADDR,(uint8_t *)&config_dtu,sizeof(config_dtu));
taskEXIT_CRITICAL(); //退出临界区
return 0;
}
读取结构体函数也是差不多原理,有时候或许需要清除一下flash。
/***************************************************
* 函数名称: cpoy_flash_config_dtu
* 功能描述: 拷贝flash 到配置结构体中
* 输入参数:
* 输出参数:
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
uint8_t cpoy_flash_config_dtu(void)
{
taskENTER_CRITICAL(); //进入临界区
efmflash_read((uint8_t *)&config_dtu,FLASH_REPIY_ADDR,sizeof(config_dtu));
taskEXIT_CRITICAL(); //退出临界区
return 0;
}
/***************************************************
* 函数名称: reset_flash_config_dtu
* 功能描述: 擦除flash保存数据 初始化
* 输入参数:
* 输出参数:
* 返 回 值
* 其它说明: 无
* 修改日期:
修改人
****************************************************/
uint8_t reset_flash_config_dtu(void)
{
taskENTER_CRITICAL(); //进入临界区
efmflash_erase(FLASH_REPIY_ADDR);
taskEXIT_CRITICAL(); //退出临界区
return 0;
}
在使用一段时间结构体存储的方法后,我发现十分的方便,例如在开机时读取flash数据,然后在应用代码中直接调用结构体就行;也可以很容易的实现热修改(即不需要关机重启才生效指令)。而且在以后的扩展中也很方便,只要直接在结构体中添加自己需要的数据就可以了。