最近做了几个项目,都需要做掉电检测,然后把重要数据写到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
|