单片机FLASH存取结构体的方法

单片机学习笔记(一)

  • 单片机FLASH存取结构体的方法
    • 编写底层flash读写驱动
    • 编写存储结构体
    • 读写结构体函数编写
    • 使用心得

单片机FLASH存取结构体的方法

在日常开发中,经常遇到要保存一些配置数据到flash中,而普通的保存方法虽然粗暴简单,但是当数据类型多时,这种方法就不适用了,所以需要使用结构体的存储方法。

编写底层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数据,然后在应用代码中直接调用结构体就行;也可以很容易的实现热修改(即不需要关机重启才生效指令)。而且在以后的扩展中也很方便,只要直接在结构体中添加自己需要的数据就可以了。

你可能感兴趣的:(单片机学习笔记,嵌入式,指针,c++)