项目背景
在开发项目的整个过程中,产品同学经常给开发团队传递一类信息:这个数据由MCU来进行掉电记忆。
甲方的开发团队觉得数据记忆就是要写在E方里面的,并且在以往的项目里面已经加了个E方的芯片,用IIC来通讯,E方多大就只能记忆多少数据,于是很多数据都不能由MCU记忆下来。
产品得到这个消息的时候总是很疑惑,为什么这么多数据都不能由MCU记忆下来,沟通几次之后大家都决定由SOC来记忆,导致有些只能由MCU记忆的功能只能被迫砍掉。
突然有天我在查BootLoader代码的时候,发现用到了Flash管理的代码里面有个模拟E方的东东,看了看,里面居然有4K的空间!还有个更大的64K的FlexNVM区域!人家都提醒你这么多次你能记忆得下来了,结果一直浪费这么好的资源不用,真的太可惜了。
PE配置
打开之后有个FlexRam的地方,设定个基地址,我这里使用默认的0x14000000。
原理
规格书里面有写,NXP的K1芯片都有下面这么几块内存区域。
我这个芯片是146的,所以有1M的PFlash/64K的FlexNVM和4K的FlexRAM。
内存映射图
最上面的1M空间PFlash是不会动的,把4K的FlexRAM变为EERAM。
能分区的只有4K的FlexRAM有三种配置方式,分一部分给Dflash,一部分给EFlash。
这个不是本文的重点,我们要用的是4K的FlexRAM,知道大概怎么分就行。
生成代码
生成的代码在Generated_Code文件夹下的Flash.c,里面规定了:
1、Pflash的起始地址为0、因为默认就那1M大小,所以范围就是0-0x0100 0000
2、Dflash的起始地址为0x10000000,然后就看配置多大了,64K里面没有配置到的都是EFlash。
3、EERAM也就是模拟E方的起始地址为0x14000000,大小为4K已经定好了。
/*! @brief Configuration structure flashCfg_0 */
const flash_user_config_t Flash_InitConfig0 = {
.PFlashBase = 0x00000000U, /* Base address of Program Flash block */
.PFlashSize = 0x00100000U, /* Size of Program Flash block */
.DFlashBase = 0x10000000U, /* Base address of Data Flash block */
.EERAMBase = 0x14000000U, /* Base address of FlexRAM block */
/* If using callback, any code reachable from this function must not be placed in a Flash block targeted for a program/erase operation.*/
.CallBack = NULL_CALLBACK
};
初始化代码
大致过程如下,基本都是标准的。
uint8_t FlashEmulateEE_Init(void)
{
uint8_t ret = STATUS_SUCCESS;
ret = POWER_SYS_SetMode(PWR_RUN, POWER_MANAGER_POLICY_AGREEMENT);//电源管理相关
ret = FLASH_DRV_Init(&Flash_InitConfig0, &flashSSDConfig);//初始化所有内存区域
if (flashSSDConfig.EEESize == 0u)//没有分出E方的区域
{
ret = FLASH_DRV_DEFlashPartition(&flashSSDConfig, 0x02u, 0x08u, 0x03u, false, true);//分出E方区域
DEV_ASSERT(STATUS_SUCCESS == ret);
}
ret = FLASH_DRV_SetFlexRamFunction(&flashSSDConfig, EEE_ENABLE, 0x00u, NULL);//允许使用模拟E方功能
DEV_ASSERT(STATUS_SUCCESS == ret);
ret = POWER_SYS_SetMode(PWR_HSRUN, POWER_MANAGER_POLICY_AGREEMENT);//电源管理相关
return ret;
}
下面我们详细分析每个接口
FLASH_DRV_Init
初始化所有Flash内存区域,FEATURE_FLS_HAS_FLEX_NVM的宏定义要开起来,除了完成初始化以外,把使用初始化结构体Flash_InitConfig0执行完的信息放到flashSSDConfig结构体里面。
status_t FLASH_DRV_Init(const flash_user_config_t * const pUserConf,
flash_ssd_config_t * const pSSDConfig)
{
DEV_ASSERT(pUserConf != NULL);
DEV_ASSERT(pSSDConfig != NULL);
status_t ret = STATUS_SUCCESS;
#if FEATURE_FLS_HAS_FLEX_NVM
uint8_t DEPartitionCode; /* store D/E-Flash Partition Code */
#endif
pSSDConfig->PFlashBase = pUserConf->PFlashBase;
pSSDConfig->PFlashSize = pUserConf->PFlashSize;
pSSDConfig->DFlashBase = pUserConf->DFlashBase;
pSSDConfig->EERAMBase = pUserConf->EERAMBase;
pSSDConfig->CallBack = pUserConf->CallBack;
#if FEATURE_FLS_HAS_FLEX_NVM
/* Temporary solution for FTFC and S32K144 CSEc part */
/* Get DEPART from Flash Configuration Register 1 */
DEPartitionCode = (uint8_t)((SIM->FCFG1 & SIM_FCFG1_DEPART_MASK) >> SIM_FCFG1_DEPART_SHIFT);
/* Get data flash size */
FLASH_DRV_GetDEPartitionCode(pSSDConfig, DEPartitionCode);
if (pSSDConfig->DFlashSize < FEATURE_FLS_DF_BLOCK_SIZE)
{
pSSDConfig->EEESize = FEATURE_FLS_FLEX_RAM_SIZE;
}
else
{
pSSDConfig->EEESize = 0U;
}
#else /* FEATURE_FLS_HAS_FLEX_NVM == 0 */
/* If size of D/E-Flash = 0 */
pSSDConfig->DFlashSize = 0U;
pSSDConfig->EEESize = 0U;
#endif /* End of FEATURE_FLS_HAS_FLEX_NVM */
return ret;
}
flashSSDConfig结构体不仅有初始化结构体Flash_InitConfig0原来的信息,还多了个EEESize表示划分出来的模拟E方范围和DFlashSize表示DFlash范围大小。
typedef struct
{
uint32_t PFlashBase; /*!< The base address of P-Flash memory */
uint32_t PFlashSize; /*!< The size in byte of P-Flash memory */
uint32_t DFlashBase; /*!< For FlexNVM device, this is the base address of D-Flash memory (FlexNVM memory);
* For non-FlexNVM device, this field is unused */
uint32_t DFlashSize; /*!< For FlexNVM device, this is the size in byte of area
* which is used as D-Flash from FlexNVM memory;
* For non-FlexNVM device, this field is unused */
uint32_t EERAMBase; /*!< The base address of FlexRAM (for FlexNVM device)
* or acceleration RAM memory (for non-FlexNVM device) */
uint32_t EEESize; /*!< For FlexNVM device, this is the size in byte of EEPROM area which was partitioned
* from FlexRAM; For non-FlexNVM device, this field is unused */
flash_callback_t CallBack; /*!< Call back function to service the time critical events. Any code reachable from this function
* must not be placed in a Flash block targeted for a program/erase operation */
} flash_ssd_config_t;
FLASH_DRV_DEFlashPartition
重新分区,分区完成之后也是把分区之后的结果给到flashSSDConfig结构体。
status_t FLASH_DRV_DEFlashPartition(const flash_ssd_config_t * pSSDConfig,
uint8_t uEEEDataSizeCode,
uint8_t uDEPartitionCode,
uint8_t uCSEcKeySize,
bool uSFE,
bool flexRamEnableLoadEEEData)
{
DEV_ASSERT(pSSDConfig != NULL);
DEV_ASSERT(uCSEcKeySize <= CSE_KEY_SIZE_CODE_MAX);
status_t ret; /* Return code variable */
/* Check CCIF to verify the previous command is completed */
if (0U == (FTFx_FSTAT & FTFx_FSTAT_CCIF_MASK))
{
ret = STATUS_BUSY;
}
else
{
/* Clear RDCOLERR & ACCERR & FPVIOL & MGSTAT0 flag in flash status register. Write 1 to clear */
CLEAR_FTFx_FSTAT_ERROR_BITS;
/* Passing parameter to the command */
FTFx_FCCOB0 = FTFx_PROGRAM_PARTITION;
FTFx_FCCOB1 = uCSEcKeySize;
FTFx_FCCOB2 = (uint8_t)(uSFE ? 1U : 0U);
FTFx_FCCOB3 = (uint8_t)(flexRamEnableLoadEEEData ? 0U : 1U);
FTFx_FCCOB4 = uEEEDataSizeCode;
FTFx_FCCOB5 = uDEPartitionCode;
/* Calling flash command sequence function to execute the command */
ret = FLASH_DRV_CommandSequence(pSSDConfig);
}
return ret;
}
第二个入参uEEEDataSizeCode是放到FCCOB4寄存器来划分模拟E方大小的,0x0F就是不用模拟E方,2就是4K全部用来做模拟E方。
第三个入参uDEPartitionCode放到FCCOB5里面,有下面几种分法,前提是你的芯片支持你这么分,我们这里用了8,也就是全部分成EFlash。
FLASH_DRV_SetFlexRamFunction
它主要就是看第二个入参flexRamFuncCode,只要写成EEE_ENABLE就能放入FCCOB1寄存器使能模拟E方,后面就都能够用了。
status_t FLASH_DRV_SetFlexRamFunction(const flash_ssd_config_t * pSSDConfig,
flash_flexRam_function_control_code_t flexRamFuncCode,
uint16_t byteOfQuickWrite,
flash_eeprom_status_t * const pEEPROMStatus)
{
DEV_ASSERT(pSSDConfig != NULL);
status_t ret; /* Return code variable */
/* Check CCIF to verify the previous command is completed */
if (0U == (FTFx_FSTAT & FTFx_FSTAT_CCIF_MASK))
{
ret = STATUS_BUSY;
}
else
{
/* Clear RDCOLERR & ACCERR & FPVIOL flag in flash status register. Write 1 to clear */
CLEAR_FTFx_FSTAT_ERROR_BITS;
/* Passing parameter to the command */
FTFx_FCCOB0 = FTFx_SET_EERAM;
FTFx_FCCOB1 = (uint8_t)flexRamFuncCode;
if (flexRamFuncCode == EEE_QUICK_WRITE)
{
FTFx_FCCOB4 = (uint8_t)(byteOfQuickWrite >> 0x8U);
FTFx_FCCOB5 = (uint8_t)(byteOfQuickWrite & 0xFFU);
}
/* Calling flash command sequence function to execute the command */
ret = FLASH_DRV_CommandSequence(pSSDConfig);
if ((flexRamFuncCode == EEE_STATUS_QUERY) && (ret == STATUS_SUCCESS))
{
if (pEEPROMStatus == NULL)
{
ret = STATUS_ERROR;
}
else
{
pEEPROMStatus->brownOutCode = FTFx_FCCOB5;
pEEPROMStatus->sectorEraseCount = (uint16_t)((uint16_t)FTFx_FCCOB8 << 8U);
pEEPROMStatus->sectorEraseCount |= (uint16_t)FTFx_FCCOB9;
pEEPROMStatus->numOfRecordReqMaintain = (uint16_t)((uint16_t)FTFx_FCCOB6 << 8U);
pEEPROMStatus->numOfRecordReqMaintain |= (uint16_t)FTFx_FCCOB7;
}
}
}
return ret;
}
写入E方
status_t FlexRAM_Emulated_EEPROM_Write(uint32_t WriteOffsetAddress,uint32_t WriteDataLength,uint8_t * WriteData){
status_t ret = STATUS_SUCCESS;
uint32_t WriteAddress = flashSSDConfig.EERAMBase + WriteOffsetAddress;
if (flashSSDConfig.EEESize != 0u)//E方有空间
{
ret = POWER_SYS_SetMode(PWR_RUN, POWER_MANAGER_POLICY_AGREEMENT);//通过电源管理将系统频率切换为正常频率,这样才能操作flash
DEV_ASSERT(STATUS_SUCCESS == ret);
ret = FLASH_DRV_EEEWrite(&flashSSDConfig,WriteAddress,WriteDataLength,WriteData);///写入E方
DEV_ASSERT(STATUS_SUCCESS == ret);
ret = POWER_SYS_SetMode(PWR_HSRUN, POWER_MANAGER_POLICY_AGREEMENT);//把频率切换回高速频率
DEV_ASSERT(STATUS_SUCCESS == ret);
for(uint8_t i = 0;i < WriteDataLength;i++){//看看写入是否成功
if ( (*WriteData) != (*(uint8_t *)WriteAddress) & 0xff)
{
ret = STATUS_ERROR;
}
WriteData++;
WriteAddress++;
}
}
else
{
ret = STATUS_ERROR;
}
return ret;
}
读出E方
读取的话就没有系统频率的问题
status_t FlexRAM_Emulated_EEPROM_Read(uint32_t ReadOffsetAddress,uint32_t ReadDataLength,uint8_t * ReadData){
status_t ret = STATUS_SUCCESS;
uint32_t ReadAddress = flashSSDConfig.EERAMBase + ReadOffsetAddress;
if (flashSSDConfig.EEESize != 0u)//E方有空间
{
memcpy(ReadData,ReadAddress,ReadDataLength);
}
else
{
ret = STATUS_ERROR;
}
return ret;
}
测试代码
uint32_t OffsetAddress = 0x0100;//在E方区域里面的地址偏移,注意不要写到E方外面
uint32_t DataLength = 0x8;//数据长度
uint8_t WriteData[8] = {0x00,0x55,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF};//要写入的数据
uint8_t ReadData[8] = {0x00};
FlexRAM_Emulated_EEPROM_Write(OffsetAddress,DataLength,WriteData);
CPU_DealyMs(5);
FlexRAM_Emulated_EEPROM_Read(OffsetAddress,DataLength,ReadData);
重启之后内存的数据正确