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

最近做了几个项目,都需要做掉电检测,然后把重要数据写到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[i]=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[i]);//写入数据
		}

		/* 上锁 */
		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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值