打印
[应用相关]

STM32/APM32掉电检测+写FLASH功能实现

[复制链接]
435|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-10-25 08:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
最近做了几个项目,都需要做掉电检测,然后把重要数据写到FLASH里,下次上电重新调用。尝试了很多方法都能实现,却都莫名其妙出现问题,写数据到FLASH里偶尔会失败!最后排查问题因为软复位自启。下面总结一下经验,和大家分享一下。

硬件支持:使用的是APM32F030RB,外部3.3V并联了蓄电电容。

软件实现几种方式:
1、使用ADC的模拟看门狗功能,设定内部电压为触发源,开启模拟看门狗中断,掉电后触发中断
2、使用ADC+DMA, ADC通道设定内部电压,实时扫描内部电压变化
3、使用PVD功能(最大阀值2.9V),PVD链接外部中断线16,开启PVD中断通道,当低于2.9V时触发中断。

第一种方法比较灵敏,因为我遇到了软复位自启的问题,所以直接使用了第二种方法,实时扫描。

功能实现过程中遇到的几个核心问题:
1、由于是掉电后写flash,所以时间有限,写flash函数尽量高效可靠。
2、避免掉电后单片机触发上电掉电的软复位自启!重点!重点!重点!
3、地址随机性问题,要么使用宏定义,要么使用volatile申明变量定义,确保地址唯一性
4、芯片内存大小问题,可以使用ST-LINK工具读出芯片最大内存地址,防止写flash时地址溢出
5、写flash次数是有限的,排查重复写入多次的问题
6、APM32与STM32是有微小差异的,写FLASH时尽量先关闭总中断

下面看看代码如何实现

用到了ADC+DMA,所以先初始化这两个功能:

/******************************************************************************
函数名称:void DMA1_Config(void)
函数功能:DMA初始化
输入参数:无
返回值:  无
说明:
******************************************************************************/
void DMA1_Config(void)
{
  DMA_InitTypeDef DMA_InitStructure;
       
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);

  DMA_DeInit(DMA1_Channel1);//设置DMA1通道1
  DMA_InitStructure.DMA_PeripheralBaseAddr=ADC1_DR_Address;//DMA外设基地址(源地址)
  DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)&ADC_ConvertedValue;//内存地址(目标地址)
  DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//DMA传输方向,数据来自外设ADC
  DMA_InitStructure.DMA_BufferSize=CHANNEL*CNT;//设置DMA在传输时缓冲区的长度(word)
  DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设寄存器地址不变
  DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//设置DMA内存递增模式       
  DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设数据字长
  DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//内存数据字长
  DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//设置传输模式连续不断的循环模式
  DMA_InitStructure.DMA_Priority=DMA_Priority_High;//设置DMA的优先级别
  DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//DMA通道没有设置内存到内存的传输

  DMA_Init(DMA1_Channel1,&DMA_InitStructure);

  DMA_Cmd(DMA1_Channel1,ENABLE);    //使能DMA通道1
}

/******************************************************************************
函数名称:void ADC_Config(void)
函数功能:ADC初始化
输入参数:无
返回值:  无
说明:
******************************************************************************/
void ADC_Config(void)
{
    ADC_InitTypeDef ADC_InitStructure;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
       
        /* ADCs DeInit */  
        ADC_DeInit(ADC1);

        /* Initialize ADC structure */
        ADC_StructInit(&ADC_InitStructure);

        /* Configure the ADC1 in continuous mode withe a resolution equal to 12 bits  */
        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
        ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
        ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
        ADC_Init(ADC1, &ADC_InitStructure);

        // ADC内置温度传感器禁止
        ADC_TempSensorCmd(DISABLE);

        /* Convert the ADC1 Channel 17 with 41.5 Cycles as sampling time */
                ADC_ChannelConfig(ADC1, ADC_Channel_17 , ADC_SampleTime_41_5Cycles);  //内部电压检测
        /* ADC Calibration */
        ADC_GetCalibrationFactor(ADC1);
        ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);//DMA

   /* Enable ADC_DMA */
    ADC_DMACmd(ADC1, ENABLE);//DMA
        /* Enable the ADC peripheral */
        ADC_Cmd(ADC1, ENABLE);  

        /* Wait the ADRDY flag */
        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY));

        /* ADC1 regular Software Start Conv */
        ADC_StartOfConversion(ADC1);
       
        // ADC内置参考电压
        ADC_VrefintCmd(ENABLE);
       
        DMA1_Config();       
       
}

完成初始化后,上电增加一个标志位判断,防止自启重复触发掉电检测写FLASH

uint8_t  STARTpower_flag = 0; // 首次上电
void ADCscan_3_3vpower_init(void)
{
        if( ADC_ConvertedValue[1] < 1650 )//内部电压1.2V采样值约1500
        {
                STARTpower_flag = 1;
        }
}
接着就是读写FLASH的实现函数了

//从指定地址开始读出指定长度的数据
void FLASH_ReadData(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)          
{
        u16 i;
        for(i=0;i<NumToRead;i++)
        {
                pBuffer=STMFLASH_ReadHalfWord(ReadAddr);//读取2个字节.
                ReadAddr+=2;//偏移2个字节.       
        }
}

//从指定地址开始写入指定长度的数据
void FLASH_WriteData(uint32_t Addr,  uint16_t *pBuffer, uint16_t NumToWrite)
{
                uint16_t i;
                __disable_irq();        // 写内部Flash前,关闭总中断
       
                FLASH_Unlock();//先解锁
                //清除相应的标志位
                FLASH_ClearFlag(FLASH_FLAG_BSY| FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPERR);

                /* 使用前先擦除*/
                FLASH_ErasePage(Addr);//擦除这个扇区       
               
                for(i=0; i<NumToWrite; i++)
                {
                        FLASH_ProgramHalfWord(Addr+i*2, pBuffer);//写入数据
                }

                /* 上锁 */
                FLASH_Lock();
                       
                __enable_irq();                //写内部Flash后,打开总中断
}

实现函数

void Rower_down(void)
{
    uint16_t flash_buff[8] = {0x111,0x222,0x333,0x444,0x555,0x666,0x777,0x888};
        static uint8_t flag = 1;
        if( flag == 1 && ADC_ConvertedValue[1] > 1650 && STARTpower_flag == 1) //1.2V 1507
        {       
                STARTpower_flag = 0;

                FLASH_WriteData(FLASH_SAVE_ADDR, flash_buff, 8);

                flag = 0;
        }
}
最后,功能也是实现了,反复测试还是比较稳定的检测和读写FLASH。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_32113815/article/details/143061563

使用特权

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

本版积分规则

2028

主题

15903

帖子

13

粉丝