STM32掉电数据存储(PVD掉电检测)
本次分享一种实现掉电数据存储的方法:通过PVD掉电检测,然后进行单片机内部flash的读写没从而实现掉电数据存储。
优势:因为内部flash有写的寿命,大约1万次,如果我们频繁的读写,可能会损害flash导致设备运行异常,
文章目录
1 PVD掉电检测简介
在实际做产品/项目的过程中,可能大家对PVD的作用会忽视掉,下面列举PVD的作用:
1.1 记录设备掉电时间
这个很好理解,可能业务上就有这个需求,或者可以利用这一点来完成低功耗设备的待机时长测试。
1.2 通知其他处理离线
假如设备中有由干电池供电的MCU1和由锂电池供电的MCU2,MCU1的部分功能可能需要MCU2来完成,MCU1需要知道MCU2是否离线(因为锂电池可拔插,可能随时被拔)。这种情况就可以在MCU2上利用PVD来通知MCU1。通知的方式有很多,例如串口直接通知另一方自己将要断电了。
PS:当然也可通过MCU2监听MCU1的电源来实现。
1.3 防止掉电过程中程序跑飞
小编在之前设计的一款产品有一定的概率会出现程序丢失的情况,最初怀疑竞争对手恶意破坏或竞争对手尝试读取程序导致程序自动擦除。
后来经过实际测试发现:由于设备电压降落较慢,下电时间较长(约20ms)。在下电过程中电压较低/电压不稳定时PC指针乱跑(即所谓的程序跑飞),正好PC指向了代码中擦除Flash的位置。(设备进行ISP程序升级需要将自身的Flash擦除写入新数据)。
2 PVD的使用
用户可以利用PVD对VDD电压与电源控制寄存器(PWR_CR)中的PLS[2:0]位进行比较来监控电源,这几位选择监控电压的阀值。
通过设置PVDE位来使能PVD。电源控制/状态寄存器(PWR_CSR)中的PVDO标志用来表明VDD是高于还是低于PVD的电压阀值。
该事件在内部连接到外部中断的第16线,如果该中断在外部中断寄存器中是使能的,该事件就会产生中断。当VDD下降到PVD阀值以下和(或)当VDD上升到PVD阀值之上时,根据外部中断第16线的上升/下降边沿触发设置,就会产生PVD中断。例如,这一特性可用于用于执行紧急关闭任务。
由PLS[2:0]的介绍可以发现PVD的电压阈值共有8个等级,实际使用时可根据自己的实际情况进行选择。
2 PVD的使用的代码
pvd.c
#include "pvd.h"
#include "usart.h"
// PVD初始化函数,设置PVD阈值和中断模式
void PVD_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure; // 定义外部中断结构体
NVIC_InitTypeDef NVIC_InitStructure; // 定义中断控制器结构体
// 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 设置PVD阈值为2.9V,可以根据你的电源电压选择其他值
PWR_PVDLevelConfig(PWR_PVDLevel_2V9);
// 使能PVD
PWR_PVDCmd(ENABLE);
// 使能PVD外部中断线
EXTI_ClearITPendingBit(EXTI_Line16); // 清除中断标志位
// EXTI_InitTypeDef EXTI_InitStructure; // 定义外部中断结构体
EXTI_InitStructure.EXTI_Line = EXTI_Line16; // PVD连接到外部中断线16
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 设置为上升沿触发,即电压低于阈值时触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断线
EXTI_Init(&EXTI_InitStructure); // 初始化外部中断
// 设置PVD中断优先级和使能
// NVIC_InitTypeDef NVIC_InitStructure; // 定义中断控制器结构体
NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn; // PVD中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure); // 初始化中断控制器
}
// PVD中断服务函数,用于掉电前的处理
void PVD_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line16) != RESET) // 判断是否是PVD中断
{
// 在这里添加你的掉电处理代码,例如保存数据到Flash或EEPROM等
// 注意不要执行太多的操作,因为电压可能很快就会降到无法工作的程度
// flash_write();
FLASH_Unlock();
FLASH_ProgramWord(Address, data); //直接写
FLASH_Lock();
printf("flash_write ok");
EXTI_ClearITPendingBit(EXTI_Line16); // 清除中断标志位
}
}
3 具体实现流程
3.1 pvd掉电检测
代码如上所示
3.2 内部flash读写
具体应用参考:
https://mp.csdn.net/mp_blog/creation/editor/134711793
4 试验验证
–验证流程:
创建1个变量,两个按键改变变量的数值,并进行打印,断电观察变量是否改变。
创建变量:uint32_t data = 1000
按键1:变量增加10
按键2:变量减小10
断电观察,变量是否为最后改变的数值。`
main.c
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "key.h"
#include "pvd.h"
//extern uint32_t data; //定义一个变量,4个字节
uint32_t data = 1000;
//按下key1,data减小10
void key1_handler(void)
{
data += 10;
// flash_write();
}
//按下key2,data增加10
void key2_handler(void)
{
data -= 10;
// flash_write();
}
u16 count=0;
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
uart_init(115200); //串口初始化为115200
KEY_Init();
PVD_Init();
printf("\r\n STM32 flash 掉电存储测试 \r\n");
//读取flash的值
FLASH_Unlock();
data = (*(__IO uint32_t*) Address);
FLASH_ErasePage(Address);
FLASH_Lock();
while(1)
{
u8 t=0;
if(0==count%1)
{
t=KEY_Scan(0); //得到键值
switch(t)
{
case 1:
{
key1_handler();
}
break;
case 2:
{
key2_handler();
}
break;
default:
break;
}
}
if(0==count%1000)
{
printf("The duchu number is: %d\n", data);
}
if(count == 10000)
{
delay_ms(1);
count = 1;
}
delay_ms(1);
count +=1;
}
}
注意事项:
- pvd掉电时间很短,只能满足写flash的要求,无法满足擦除,所以擦除要放在其他位置;
- 为了保证每次都写成功,要注意匹配电容的大小
完整代码:
https://download.csdn.net/download/qq_39742246/88607234
已经过验证
5 致谢
参考:
https://blog.csdn.net/qq_27575841/article/details/107602983
https://blog.csdn.net/hezengfu/article/details/121777219