STM32F4 内部flash存放配置(优化存储)
扫描二维码
随时随地手机看文章
由于STM32F4系列内部flash的块非常大,因此擦除时间比较长,并且很浪费,我一般使用中间的64KB的块做存储,前面4个16KB为启动程序,后面的几个128KB为应用程序。
存储方案:
使用64KB的扇区,每次配置占用1KB,每次初始化的时候从后向前判断配置是否有效,如果最后一个为有效,则下次写入会擦除扇区,并将配置写入到第0个1KB,下次写入到第1个1KB,依次类推,这样可以保证写入64次配置才擦除一次扇区1来保证稳定性,二来降低每次擦除写入的时间。
这样可以有效的降低擦除次数,提高写入速度,如果配置有1KB大小,则写64次才会擦除一次,效率会高很多。
//Device_Config.c
/************************************************************************************************************* * 文件名: Device_Config.c * 功能: 设备配置相关 * 作者: cp1300@139.com * 创建时间: 2018-01-13 * 最后修改时间: 2018-01-13 * 详细: 设备配置相关-由于使用了串口打印,一定要在串口初始化后再调用 基本思路: 使用64KB的扇区,每次配置占用1KB,每次初始化的时候从后向前判断配置是否有效,如果最后一个为有效,则下次写入 会擦除扇区,并将配置写入到第0个1KB,下次写入到第1个1KB,依次类推,这样可以保证写入64次配置才擦除一次扇区 1来保证稳定性,二来降低每次擦除写入的时间。 *************************************************************************************************************/ #include "system.h" #include "usart.h" #include "main.h" #include "board.h" #include "wdg.h" #include "Device_Config.h" #include "stm32f4_flash.h" #include//调试开关 #define CONFIG_DBUG 0 #if CONFIG_DBUG #include "system.h" #define config_debug(format,...) uart_printf(format,##__VA_ARGS__) #else #define config_debug(format,...) / / #endif //CONFIG_DBUG CONFIG_TYPE g_SYS_Config; //全局系统配置 static u16 gs_ConfigSaveIndex; //系统配置存储索引(用于记录当前存储在这64KB中的位置,每存储一次就会向后增长一次,碰到非FF的时候才进行擦除,循环存储,无需保存之前的数据) #define STM32_CONFIG_PAGE_INDEX 4 //配置扇区索引:扇区4,64KB #define STM32_CONFIG_PAGE_ADDR 0x08010000 //配置文件页地址(字节地址),0x0801 0000 - 0x0801 FFFF #define STM32_CONFIG_PAGE_SIZE (64*1024) //配置文件页大小 #define CONFIG_MAX_SIZE 256 //配置文件最大大小 static bool CONFIG_SaveConfig(CONFIG_TYPE *pConfig); //存储配置数据到flash /************************************************************************************************************************* * 函数 : void CONFIG_Init(void) * 功能 : 上电初始化配置 * 参数 : 无 * 返回 : 无 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2014-10-18 * 最后修改时间 : 2017-03-22 * 说明 : 从配置页面中加载配置文件所在偏移,从后向前查找,会加载配置到全局:g_SYS_Config中 *************************************************************************************************************************/ void CONFIG_Init(void) { int index; u32 addr; u32 ConfigId; u16 count; STM32FLASH_STATUS status; if(sizeof(CONFIG_TYPE)>CONFIG_MAX_SIZE) { DEBUG("错误,配置文件大小超出限制rn"); while(1); } count = STM32_CONFIG_PAGE_SIZE/CONFIG_MAX_SIZE; //计算配置扇区可以存放配置数据的条数 for(index = (count-1);index >= 0;index --) //索引从后向前寻找,索引最大的一次为最后的配置 { addr = STM32_CONFIG_PAGE_ADDR+index*CONFIG_MAX_SIZE; //计算需要读取的地址 STM32FLASH_Read(addr, &ConfigId, 4/sizeof(u32)); //从指定地址开始读出指定长度的数据-读取配置区域最前面的4B,也就是配置id if(ConfigId == DEVICE_BOARD_CONFIG_ID) //有效的ID,意味着找到了配置缓冲区索引位置 { gs_ConfigSaveIndex = index; uart_printf("找到了配置索引:%drn",index); break; } } if(index < 0) //没有找到,那么加载默认的配置,格式化并将配置数据写入到第一个扇区中 { //先擦除 status = STM32FLASH_EraseSector(STM32_CONFIG_PAGE_INDEX); //擦除扇区 if(status == STM32FLASH_OK) { uart_printf("擦除配置扇区%d成功rn",STM32_CONFIG_PAGE_INDEX); } else { uart_printf("擦除配置扇区%d失败rn",STM32_CONFIG_PAGE_INDEX); } gs_ConfigSaveIndex = 0; //默认从索引0开始存储 uart_printf("系统未配置过,加载默认配置!rn"); CONFIG_Default(&g_SYS_Config, TRUE); //所有的配置恢复出厂配置 addr = STM32_CONFIG_PAGE_ADDR+gs_ConfigSaveIndex*CONFIG_MAX_SIZE; //计算需要写入的地址 status = STM32FLASH_Write_NoCheck(addr,(u32 *)&g_SYS_Config,sizeof(CONFIG_TYPE)/sizeof(u32)) ; //不检查的写入-写入配置到flash if(status != STM32FLASH_OK) { DEBUG("写入配置扇区%d失败rn",STM32_CONFIG_PAGE_INDEX); } } else //找到配置了,加载配置 { addr = STM32_CONFIG_PAGE_ADDR+gs_ConfigSaveIndex*CONFIG_MAX_SIZE; //计算需要写入的地址 STM32FLASH_Read(addr, (u32 *)&g_SYS_Config,sizeof(CONFIG_TYPE)/sizeof(u32)); //加载配置 if(CONFIG_Check(&g_SYS_Config, TRUE) == FALSE) { uart_printf("加载配置文件失败,配置无效,重新加载默认配置rn"); CONFIG_Default(&g_SYS_Config, TRUE); //所有的配置恢复出厂配置 } else { uart_printf("加载配置文件成功,配置大小:%dBrn",sizeof(CONFIG_TYPE)); } } CONFIG_Check(&g_SYS_Config,FALSE); //检查配置 if(CONFIG_CheckSN(g_SYS_Config.SN) == FALSE) { g_SYS_Config.SN[0] = 0; DEBUG("警告,没有设置SNrn"); } } /************************************************************************************************************************* * 函数 : bool CONFIG_Check(CONFIG_TYPE *pConfig,bool isCheckID) * 功能 : 检查配置参数是否合法 * 参数 : isCheckID:是否检查配置ID,如果检查配置ID不正确直接返回错误,否则将会对错误配置进行限制,最终依旧会返回TRUE;pConfig:配置数据指针 * 返回 : 无 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2014-10-18 * 最后修改时间 : 2017-03-22 * 说明 : 用于检查配置 *************************************************************************************************************************/ bool CONFIG_Check(CONFIG_TYPE *pConfig,bool isCheckID) { if(isCheckID) //需要检查配置ID { if(pConfig->ID != DEVICE_BOARD_CONFIG_ID) return FALSE; //配置ID无效 } //为每一个配置进行检查 pConfig->SN[15] = 0; //唯一序列号-不能进行随意修改 /*********************************************************************************************************************************************************************/ if(pConfig->HorizAngle > (90*1000)) pConfig->HorizAngle = 90*1000; //设备安装的水平夹角,保留3位小数 if(pConfig->ThresholdEnergy > 100000) pConfig->ThresholdEnergy = 100000; //阈值能量,低于阈值的将会进行过滤 if(pConfig->WaterLevelHeight > 9999999) pConfig->WaterLevelHeight= 999999; //水位计安装高度 if(pConfig->WaterCorrParame > 999999) pConfig->WaterCorrParame = 999999; //水位计线性修正值,4位小数点 if(pConfig->WaterCorrectionLe > 9999999) pConfig->WaterCorrectionLe = 9999999; //水位修正值,有符号,单位mm if(pConfig->WaterCorrectionLe < -9999999) pConfig->WaterCorrectionLe = -9999999; //水位修正值,有符号,单位mm if(pConfig->WarmTime > 999) pConfig->WarmTime = 999; //预热时间,单位S,0-999 if(pConfig->FlowSpeedCorrParame < 1) pConfig->FlowSpeedCorrParame = 1; //流速线性修正值,4位小数点 if(pConfig->FlowSpeedCorrectionLe > 29999) pConfig->FlowSpeedCorrectionLe = 29999; //流速修正值,有符号,单位mm/s if(pConfig->FlowSpeedCorrectionLe < -29999) pConfig->FlowSpeedCorrectionLe = -29999;//流速修正值,有符号,单位mm/s //u16 AcqCycle; //采集周期,单位秒 //u16 Filter[16][2]; //滤波器,预留16个滤波器,可以进行干扰频率过滤 //u8 SlaveAddr; //设备通信地址 //u8 WaterLevelAddr; //水位计通信地址 //u8 WaterLevelSelectIndex; //水位计选择 if(pConfig->FreqFilter < 20) pConfig->FreqFilter = 20; //频率差值滤波,IQ通道的频率差值超出范围后进行滤波 if(pConfig->FlowSpeedFilterCnt > 100) pConfig->FlowSpeedFilterCnt = 100; //流速滤波次数 if(pConfig->FlowRateFilterCnt > 100) pConfig->FlowRateFilterCnt = 100; //流量滤波次数 if(pConfig->FlowDireFilterCnt > 100) pConfig->FlowDireFilterCnt = 100; //流量方向滤波次数 if(pConfig->ProtectTempL < -99) pConfig->ProtectTempL = 99; //设备保护温度低温度值 if(pConfig->ProtectTempL > 0) pConfig->ProtectTempL = 0; //设备保护温度低温度值 if(pConfig->ProtectTempH < 20) pConfig->ProtectTempH = 20; //设备保护温度高温度值 if(pConfig->ProtectTempH > 99) pConfig->ProtectTempH = 99; //设备保护温度高温度值 return TRUE; } /************************************************************************************************************************* * 函数 : void CONFIG_Default(CONFIG_TYPE *pConfig, bool isAdmin) * 功能 : 恢复配置为出厂模式 * 参数 : isAdmin:是否为管理员模式 * 返回 : 无 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2014-10-18 * 最后修改时间 : 2017-03-21 * 说明 : 不会进行存储 *************************************************************************************************************************/ void CONFIG_Default(CONFIG_TYPE *pConfig, bool isAdmin) { int i; pConfig->ID = DEVICE_BOARD_CONFIG_ID; //首先设置配置ID if(isAdmin == TRUE) { pConfig->SN[0] = 0; //清除SN } else { pConfig->SN[15] = 0; //唯一序列号-不能进行随意修改 } /*********************************************************************************************************************************************************************/ pConfig->HorizAngle = 0; //设备安装的水平夹角,保留3位小数-0为自动 pConfig->ThresholdEnergy = 12000; //阈值能量,低于阈值的将会进行过滤 pConfig->WaterLevelHeight = 0; //水位计安装高度 pConfig->WaterCorrParame = 1.0*10000; //水位计线性修正值,4位小数点 pConfig->WaterCorrectionLe = 0; //水位修正值,有符号,单位mm pConfig->WarmTime = 5; //预热时间,单位S,0-999 pConfig->FlowSpeedCorrParame = 1.0*10000; //流速线性修正值,4位小数点 pConfig->FlowSpeedCorrectionLe = 0; //流速修正值,有符号,单位mm/s pConfig->AcqCycle = 0; //采集周期,单位秒0:连续采集 //滤波器,预留16个滤波器,可以进行干扰频率过滤 for(i = 0;i < 16;i ++) { pConfig->Filter[i][0] = 0; pConfig->Filter[i][1] = 0; } pConfig->SlaveAddr = 1; //设备默认通信地址 pConfig->WaterLevelAddr = 1; //水位计通信地址 pConfig->WaterLevelSelectIndex = 0; //水位计选择 pConfig->FreqFilter = 60; //频率差值滤波,IQ通道的频率差值超出范围后进行滤波 pConfig->FlowSpeedFilterCnt = 20; //流速滤波次数 pConfig->FlowRateFilterCnt = 20; //流量滤波次数 pConfig->FlowDireFilterCnt = 10; //流量方向滤波次数 pConfig->ProtectTempH = 90; //设备保护温度高温度值 pConfig->ProtectTempL = -30; //设备保护温度低温度值 } /************************************************************************************************************************* * 函数 : bool CONFIG_SaveConfig(CONFIG_TYPE *pConfig) * 功能 : 存储配置数据到flash * 参数 : pConfig:配置存储缓冲区 * 返回 : FALSE:失败;TRUE:成功 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2013-10-22 * 最后修改时间 : 2018-01-17 * 说明 : 只能一次存储所有的配置数据 配置数据应该小于1个页大小 STM32F407配置写入基本原理:将一个64KB的扇区分为多个写入区域,比如64个,索引为0-63,如果当前存储的索引为63,则直接擦除扇区,索引变为0 下次写入时,索引+1,同时检查待写入区域是否全部为FF,否则执行擦除,并将索引变为0,重复此循环,理论上可以写64次配置才擦除一次,可以有效的提高扇区利用率 以及降低flash擦除次数,提高flash寿命。 *************************************************************************************************************************/ static bool CONFIG_SaveConfig(CONFIG_TYPE *pConfig) { u32 addr; u16 count; STM32FLASH_STATUS status; u32 temp; bool isFistIndex = FALSE; //是否需要从索引0开始,并擦除当前扇区 int i; count = STM32_CONFIG_PAGE_SIZE/CONFIG_MAX_SIZE; //计算配置扇区可以存放配置数据的条数 if(gs_ConfigSaveIndex == (count-1)) //当前存储为最后一个索引了,从0开始存储 { isFistIndex = TRUE; //需要写入到首个扇区 } else //需要判断是否为FF { gs_ConfigSaveIndex ++; //存储索引位置自增 for(i = 0;i < (sizeof(CONFIG_TYPE)/sizeof(u32));i ++) { addr = STM32_CONFIG_PAGE_ADDR+gs_ConfigSaveIndex*CONFIG_MAX_SIZE+i*4; //计算需要读取的地址 STM32FLASH_Read(addr, &temp, 4/sizeof(u32)); if(temp != 0xFFFFFFFF) { uart_printf("存储区域发现非0部分,需要擦除,当前索引:%drn",gs_ConfigSaveIndex); gs_ConfigSaveIndex = 0; //索引变为0 break; } } if(gs_ConfigSaveIndex == 0) //需要擦除 { isFistIndex = TRUE; //需要写入到首个扇区 } } if(isFistIndex== TRUE) //需要写入到首个扇区 { //先擦除 status = STM32FLASH_EraseSector(STM32_CONFIG_PAGE_INDEX); //擦除扇区 if(status == STM32FLASH_OK) { uart_printf("擦除配置扇区%d成功rn",STM32_CONFIG_PAGE_INDEX); } else { uart_printf("擦除配置扇区%d失败rn",STM32_CONFIG_PAGE_INDEX); } //写入数据 addr = STM32_CONFIG_PAGE_ADDR+gs_ConfigSaveIndex*CONFIG_MAX_SIZE; //计算需要写入的地址 status = STM32FLASH_Write_NoCheck(addr,(u32 *)&g_SYS_Config,sizeof(CONFIG_TYPE)/sizeof(u32)) ; //不检查的写入-写入配置到flash gs_ConfigSaveIndex = 0; //索引变为0 } else //非首个扇区写入-并且无需擦除,前面已经做好了检查,直接写入即可 { addr = STM32_CONFIG_PAGE_ADDR+gs_ConfigSaveIndex*CONFIG_MAX_SIZE; //计算需要写入的地址 status = STM32FLASH_Write_NoCheck(addr,(u32 *)&g_SYS_Config,sizeof(CONFIG_TYPE)/sizeof(u32)) ; //不检查的写入-写入配置到flash } //判断是否写入成功 if(status == STM32FLASH_OK) { uart_printf("存储配置成功,索引:%drn",gs_ConfigSaveIndex); return TRUE; } else { uart_printf("存储配置失败,索引:%d(错误:%d)rn",gs_ConfigSaveIndex, status); return FALSE; } } /************************************************************************************************************************* * 函数 : bool CONFIG_WriteConfig(CONFIG_TYPE *pConfig) * 功能 : 更新写入配置(不会修改SN) * 参数 : pConfig:配置存储缓冲区 * 返回 : FALSE:失败;TRUE:成功 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2017-04-05 * 最后修改时间 : 2017-04-05 * 说明 : 用于更新系统配置,并且写入配置到flash,不检查配置,需要提前进行验证配置 *************************************************************************************************************************/ bool CONFIG_WriteConfig(CONFIG_TYPE *pConfig) { int i; /****************************************头部的注册相关信息,不管什么设备的配置开头必须是以下格式************************************************************************************/ g_SYS_Config.ID = DEVICE_BOARD_CONFIG_ID; //ID,标记是否进行过配置 g_SYS_Config.SN[15] = 0; //唯一序列号-不能进行随意修改 /*********************************************************************************************************************************************************************/ g_SYS_Config.HorizAngle = pConfig->HorizAngle ; //设备安装的水平夹角,保留3位小数 g_SYS_Config.ThresholdEnergy = pConfig->ThresholdEnergy; //阈值能量,低于阈值的将会进行过滤 g_SYS_Config.WaterLevelHeight = pConfig->WaterLevelHeight; //水位计安装高度 g_SYS_Config.WaterCorrParame = pConfig->WaterCorrParame; //水位计线性修正值,4位小数点 g_SYS_Config.WaterCorrectionLe = pConfig->WaterCorrectionLe; //水位修正值,有符号,单位mm g_SYS_Config.WarmTime = pConfig->WarmTime; //预热时间,单位S,0-999 g_SYS_Config.FlowSpeedCorrParame = pConfig->FlowSpeedCorrParame; //流速线性修正值,4位小数点 g_SYS_Config.FlowSpeedCorrectionLe = pConfig->FlowSpeedCorrectionLe; //流速修正值,有符号,单位mm g_SYS_Config.AcqCycle = pConfig->AcqCycle; //采集周期,单位秒 for(i = 0;i < 16;i ++) { g_SYS_Config.Filter[i][0] = pConfig->Filter[i][0]; //滤波器,预留16个滤波器,可以进行干扰频率过滤 g_SYS_Config.Filter[i][1] = pConfig->Filter[i][1]; //滤波器,预留16个滤波器,可以进行干扰频率过滤 } g_SYS_Config.SlaveAddr = pConfig->SlaveAddr; //设备通信地址 g_SYS_Config.WaterLevelAddr = pConfig->WaterLevelAddr; //水位计通信地址 g_SYS_Config.WaterLevelSelectIndex = pConfig->WaterLevelSelectIndex; //水位计选择 g_SYS_Config.FreqFilter = pConfig->FreqFilter; //频率差值滤波,IQ通道的频率差值超出范围后进行滤波 g_SYS_Config.FlowSpeedFilterCnt = pConfig->FlowSpeedFilterCnt; //流速滤波次数 g_SYS_Config.FlowRateFilterCnt = pConfig->FlowRateFilterCnt; //流量滤波次数 g_SYS_Config.FlowDireFilterCnt = pConfig->FlowDireFilterCnt; //流量方向滤波次数 g_SYS_Config.ProtectTempH = pConfig->ProtectTempH; //设备保护温度高温度值 g_SYS_Config.ProtectTempL = pConfig->ProtectTempL; //设备保护温度低温度值 return CONFIG_SaveConfig(&g_SYS_Config); } /************************************************************************************************************************* * 函数 : bool CONFIG_WriteSN(char pSN[16]) * 功能 : 修改SN * 参数 : pSN:SN * 返回 : FALSE:失败;TRUE:成功 * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2017-04-05 * 最后修改时间 : 2018-01-31 * 说明 : 不会检查SN合法性 *************************************************************************************************************************/ bool CONFIG_WriteSN(char pSN[16]) { g_SYS_Config.ID = DEVICE_BOARD_CONFIG_ID; //ID,标记是否进行过配置 pSN[15] = 0; strcpy(g_SYS_Config.SN, pSN); //唯一序列号-不能进行随意修改 return CONFIG_SaveConfig(&g_SYS_Config); } /************************************************************************************************************************* * 函数 : bool CONFIG_CheckSN(char pSN[16]) * 功能 : 检查SN的有效性 * 参数 : pSN:序列号存储缓冲区 * 返回 : TRUE:有效;FALSE:无效,没有配置SN * 依赖 : flash操作函数 * 作者 : cp1300@139.com * 时间 : 2014-10-18 * 最后修改时间 : 2018-01-17 * 说明 : 检查SN的有效性 *************************************************************************************************************************/ bool CONFIG_CheckSN(char pSN[16]) { u8 i; pSN[15] = ' '; if(strlen(pSN) != 15) return FALSE; for(i = 5;i < 15;i ++) { if((pSN[i] < '0') || (pSN[i] > '9')) { return FALSE; } } return TRUE; }
//Device_Config.h
/************************************************************************************************************* * 文件名: Device_Config.h * 功能: 设备配置相关 * 作者: cp1300@139.com * 创建时间: 2018-01-13 * 最后修改时间: 2018-01-13 * 详细: 设备配置相关 *************************************************************************************************************/ #ifndef _DEVICE_CONFIG_H_ #define _DEVICE_CONFIG_H_ #include "system.h" #include "rtc.h" #include "board.h" //配置相关,用于内部flash //必须保证为4字节对其,并且大小必须是4的整数倍 #define DEVICE_BOARD_CONFIG_ID 0xA5A87A6E //标记ID typedef struct { /****************************************头部的注册相关信息,不管什么设备的配置开头必须是以下格式************************************************************************************/ u32 ID; //ID,标记是否进行过配置 char SN[16]; //唯一序列号-不能进行随意修改 /*********************************************************************************************************************************************************************/ u32 HorizAngle; //设备安装的水平夹角,保留3位小数 u32 ThresholdEnergy; //阈值能量,低于阈值的将会进行过滤 u32 WaterLevelHeight; //水位计安装高度 u32 WaterCorrParame; //水位计线性修正值,4位小数点 s32 WaterCorrectionLe; //水位修正值,有符号,单位mm u32 Reserved32[4]; //预留 u16 WarmTime; //预热时间,单位S,0-999 u16 FlowSpeedCorrParame; //流速线性修正值,4位小数点 s16 FlowSpeedCorrectionLe; //流速修正值,有符号,单位mm/s u16 AcqCycle; //采集周期,单位秒 u16 Filter[16][2]; //滤波器,预留16个滤波器,可以进行干扰频率过滤 u16 Reserved16[4]; //预留 u8 SlaveAddr; //设备通信地址 u8 WaterLevelAddr; //水位计通信地址 u8 WaterLevelSelectIndex; //水位计选择 u8 FreqFilter; //频率差值滤波,IQ通道的频率差值超出范围后进行滤波 u8 FlowSpeedFilterCnt; //流速滤波次数 u8 FlowRateFilterCnt; //流量滤波次数 u8 FlowDireFilterCnt; //流量方向滤波次数 s8 ProtectTempH; //设备保护温度高温度值 s8 ProtectTempL; //设备保护温度低温度值 u8 Reserve8[2]; //保留 ///////////////////////////////////////////////////////////////////////////////////// }CONFIG_TYPE; extern CONFIG_TYPE g_SYS_Config; //系统配置 bool CONFIG_WriteSN(char pSN[16]); //修改SN; void CONFIG_Default(CONFIG_TYPE *pConfig, bool isAdmin); //恢复配置为出厂模式 bool CONFIG_Check(CONFIG_TYPE *pConfig,bool isCheckID); //检查配置参数是否合法 void CONFIG_Init(void); //初始化配置 bool CONFIG_WriteConfig(CONFIG_TYPE *pConfig); //写入配置,用于远程或上位机更新配置 bool CONFIG_CheckSN(char pSN[16]); //检查SN的有效性 #endif /*_DEVICE_CONFIG_H_*/