打印
[STM32F1]

PVD断电写入方式FLASH模拟EEPROM

[复制链接]
512|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
shipeng1989|  楼主 | 2023-7-18 15:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 shipeng1989 于 2023-7-19 11:31 编辑

我们在使用STM32时经常会遇到需要保存少量数据但又不想增加EEPROM芯片,这时一般都会使用芯片内部FLASH来充当这一功能。其实不管是芯片内部FLASH还是EEPROM芯片都存在擦写寿命的,只是FLASH的擦写寿命比专业的EEPROM更短。这对一个强迫癌晚期患者的打击是很大的,每每想起来都寝食难安。尤其是近期遇到一个应用场景不得不考虑一个新方法来治好我的焦虑。事情是这样婶儿滴:有一个累计计数32位变量,开机后实时记录用户的使用计数,数据变化最快17Hz。要求断电后数据不能丢失,直到用户操作清零后重新开始计数。

我的解决方案是这样筛儿滴:
  • 使用内部flash的最后一个页来保存这4个字节,并且写入地址每次增加4字节直到写满整页后擦除一次从页起始写入。读取数据寻址时从页起始开始读每次增加4字节,直到读到0xffffffff前面4个字节即为目标数据或者读到页末尾为目标数据;
  • 为了最大限度的减少写入次数,数据写入时机放到芯片掉电瞬间。需要打开PVD检测中断在PVD中断函数中完成数据写入。这里有一个问题:由于掉电时VCC电压已经几近枯竭,而擦除时间又比较长,此外擦除还会增加芯片的电流消耗,这让本就不多的VCC电压更加捉襟见肘,安全起见掉电时不能执行页擦除操作,页擦除应该放到上电瞬间。上电时先读取flash数据,如flash页数据已满则执行页擦除并将最后的数据写回页起始位置;
  • 由于flash擦除后为0xffffffff,我们的寻址标记也是0xffffffff,计数值从零往上计数到0xffffffff后与寻址标记混淆,所以要将数据取反后存储。
以下是源代码:
#define USER_DATA1_ADDRESS        0x0801FC00

/*******************************************************************************
* Function Name  : Read32DataFromIntFlash
* Description    : Read 32bit data from internal flash
* Input          : None
* Output         : None
* Return         : uint32_t
* Author         : shipeng
*******************************************************************************/
uint32_t Read32DataFromIntFlash(void)
{
        uint32_t i,buf32;
        const uint32_t* flash_ptr = (const uint32_t*)USER_DATA1_ADDRESS;
        for (i = 0;i < 1024/4; i++)
        {
                buf32 = *flash_ptr++;
                if (i!=1024/4-1 && 0xFFFFFFFF==*flash_ptr)break;
        }
        if (i==1024/4 && 0xFFFFFFFF!=buf32)
        {
                FLASH_Unlock();
                FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
                FLASH_ErasePage(USER_DATA1_ADDRESS);
                FLASH_ProgramHalfWord(USER_DATA1_ADDRESS,(uint16_t)(buf32&65535));
                FLASH_ProgramHalfWord(USER_DATA1_ADDRESS+2,(uint16_t)(buf32>>16));
                FLASH_Lock();
                SystemParams.flash_null_addr = USER_DATA1_ADDRESS+4;
        }
        else if (0xFFFFFFFF==buf32)SystemParams.flash_null_addr = (uint32_t)flash_ptr-4;
        else SystemParams.flash_null_addr = (uint32_t)flash_ptr;
        return (~buf32);
}

/*******************************************************************************
* Function Name  : Write32DataToIntFlash
* Description    : Write 32bit data to internal flash
* Input          : uint32_t
* Output         : None
* Return         : void
* Author         : shipeng
*******************************************************************************/
void Write32DataToIntFlash(uint32_t dat32)
{
        uint32_t buf32 = ~dat32,flash_addr = SystemParams.flash_null_addr;
        if (*((uint32_t*)flash_addr-1)==buf32)return;
        FLASH_Unlock();
        FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
        FLASH_ProgramHalfWord(flash_addr,(uint16_t)(buf32&65535));
        FLASH_ProgramHalfWord(flash_addr+2,(uint16_t)(buf32>>16));
        FLASH_Lock();
}
PVD检测初始化和PVD中断处理函数:

/*******************************************************************************
* Function Name  : PVD_EXTI_Init
* Description    : Configures PVD INTERRUPT.
* Input          : None
* Output         : None
* Return         : None
* Author         : Shipeng
*******************************************************************************/
void PVD_EXTI_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
        /*Power Voltage Down NVIC Configuration*/
        NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
   
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);

    EXTI_DeInit();
        EXTI_StructInit(&EXTI_InitStructure);
        EXTI_InitStructure.EXTI_Line = EXTI_Line16;
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);

        PWR_PVDLevelConfig(PWR_PVDLevel_2V9);
        PWR_PVDCmd(ENABLE);
}

void PVD_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line16) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line16);
        if (RESET!=PWR_GetFlagStatus(PWR_FLAG_PVDO))//if (PWR->CSR&PWR_CSR_PVDO)
        {
            PWR->CR &= 0x7F;
            RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,DISABLE);
            Write32DataToIntFlash(SystemParams.total_counter);
            __set_FAULTMASK(1);NVIC_SystemReset();
        }
        else PWR->CR |= 0x80;
    }
}






使用特权

评论回复
沙发
MessageRing| | 2023-7-18 23:27 | 只看该作者
那上电后怎么判断哪个是最后写入的数据啊?

使用特权

评论回复
板凳
Sunriver_Yao| | 2023-7-19 09:46 | 只看该作者
MessageRing 发表于 2023-7-18 23:27
那上电后怎么判断哪个是最后写入的数据啊?

非 0xFFFF_FFFF 即为计数值! 0xFFFF_FFFF视为空字。

使用特权

评论回复
地板
shipeng1989|  楼主 | 2023-7-19 11:26 | 只看该作者
MessageRing 发表于 2023-7-18 23:27
那上电后怎么判断哪个是最后写入的数据啊?

不是说了吗,上电从页起始开始读,当读到0XFFFFFFFF时停止,此时前4个字节为目标数据

使用特权

评论回复
5
shipeng1989|  楼主 | 2023-7-19 11:28 | 只看该作者
Sunriver_Yao 发表于 2023-7-19 09:46
非 0xFFFF_FFFF 即为计数值! 0xFFFF_FFFF视为空字。

0xFFFFFFFF作为寻址标记,前4个字节即为目标数据

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

134

帖子

1

粉丝