文章背景:
擦写数据在很多产品上都有应用,比如简单的电子吹风筒上面,记录用户每次的风速档和温度档习惯,下次开机用户则不需要再次去选择自己习惯的档位,提升产品智能度,擦写数据不一定要在断电瞬间,比如在按键按下,档位发生了改变之后去进行,它的好处是此时系统供电电压稳定,则擦写成功率高。然而他的缺点也很明显,以STM32F103来说,由于其没有内部eeprom,所以对于它的擦写数据我们都是将其放在flash中执行的,其flash擦除方式仅有页擦除和全擦除,页擦除的时间大约需要40ms,而其擦除过程处于一个优先级较高的中断,会导致其他低优先级中断无法执行,40ms的时间对系统正常执行会带来很大影响,比如我制作的一款风筒由于把擦写数据放在按键按下切换档位后,导致每次按下按键风筒就会抖动,很明显就是控制电机转速的中断被擦写数据的中断给影响了;其次FLASH读取数据没有次数限制,但擦写数据有寿命限制,F103为1万次,所以如果过于频繁的切换档位的话,会导致FLASH寿命迅速减少。
STM32F1系列的擦写时间及允许擦写次数:
相比较来说掉电瞬间进行擦写数据就可以避免以上的两个问题了,但是它要求MCU的供电电压从触发掉电中断后至少要能维持MCU系统工作至少50ms左右才能保证高成功率的掉电擦写数据,这就要求MCU的电源需要一个较大容量的电容,我一般采用220uF或470uF的电容。
PVD配置:
STM32 PVD功能具体可以检测到上电、掉电瞬间,其处理方式有中断响应及事件响应。
其配置过程有三个步骤:1.开启PVD中断并设置其优先级; 2.配置响应中断或事件的阈值电压; 3.配置响应模式。
1.在STM32CUBEMX开启PVD中断:
:
生成工程之后在工程里面进行模式和阈值的配置:
void PVD_config()//掉电监测初始化
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_NVIC_SetPriority(PVD_IRQn,0,0);
HAL_NVIC_EnableIRQ(PVD_IRQn);
PWR_PVDTypeDef sConfigPVD;
sConfigPVD.PVDLevel=PWR_PVDLEVEL_7;//level7为2.9V阈值,对于掉电来说阈值越高越快响应
sConfigPVD.Mode=PWR_PVD_MODE_IT_RISING ;//模式为掉电时触发中断,这里也不懂为什么设置成RISING才正常
HAL_PWR_ConfigPVD(&sConfigPVD);
HAL_PWR_EnablePVD();
}
然后将上面的PVD初始化函数放在主函数初始化里面执行,但是要在执行该函数前先延时一段时间,因为上电瞬间MCU供电电压还未稳定,如果不延时一段时间会导致误触发PVD中断,因为我们想要的效果是在掉电的时候触发中断:
最后在stm32f1xx.it.c文件中找到其对应的中断服务函数,把擦写数据放在里面即可:
flash擦写与读取:
这部分我直接提供我做的一款风筒的对应程序供大家进行参考及使用,其中我需要记忆三个数据,一个是3档温度,一个是5档风速,一个是离子工作的三个模式,在掉电时候将这三个数据写入flash,在上电后先将这三个数据读出再对风筒进行各项工作控制。
/*步骤:
1.解锁FLASH;
2.擦除FLASH;
3.写入FLASH;
4.上锁FLASH;
5.读取FLASH;
*/
uint32_t addr = 0x08007700;
uint32_t data_save[3];//第1个为memory_temp_level,第2个为memory_speed_level,第3个为memory_plasma_level
extern uint32_t memory_speed_level;//风速
extern uint32_t memory_temp_level;//温度
extern uint32_t memory_plasma_level;//离子
void data_assignment()//为其赋值,要写flash时先执行此函数为写入内容赋值
{
data_save[0]=memory_temp_level;
data_save[1]=memory_speed_level;
data_save[2]=memory_plasma_level;
}
void WriteFlashTest(uint8_t L,uint32_t data_save[],uint32_t addr)
{
uint32_t i=0;
/* 1/4解锁FLASH*/
HAL_FLASH_Unlock();
/* 2/4擦除FLASH*/
/*初始化FLASH_EraseInitTypeDef*/
/*擦除方式页擦除FLASH_TYPEERASE_PAGES,块擦除FLASH_TYPEERASE_MASSERASE*/
/*擦除页数*/
/*擦除地址*/
FLASH_EraseInitTypeDef FlashSet;
FlashSet.TypeErase = FLASH_TYPEERASE_PAGES;
FlashSet.PageAddress = addr;
FlashSet.NbPages = 1;
/*设置PageError,调用擦除函数*/
uint32_t PageError = 0;
HAL_FLASHEx_Erase(&FlashSet, &PageError);
/* 3/4对FLASH烧写*/
for(i=0;i<L;i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr+4*i, data_save[i]);//由于数据为uint8_t, add+1*i为每次加1个字节
}
/* 4/4锁住FLASH*/
HAL_FLASH_Lock();
}
void ReadFlashTest(uint8_t L,uint32_t addr)//分别读取2个字节的值赋给memory_temp_level,memory_speed_level
{
uint8_t i=0;
for(i=0;i<L;i++)
{
if(i==0)
{
memory_temp_level = *(__IO uint32_t*)(addr+i*4);
if(memory_temp_level>3)//由于烧录程序后第一次上电时地址中还没有存入数据,其值将为0xffffffff,此时先给其赋值为3
{
memory_temp_level=3;
}
}
if(i==1)
{
memory_speed_level=*(__IO uint32_t*)(addr+i*4);
if(memory_speed_level>5)//由于烧录程序后第一次上电时地址中还没有存入数据,其值将为0xffffffff,此时先给其赋值为5
{
memory_speed_level=5;
}
}
if(i==2)
{
memory_plasma_level=*(__IO uint32_t*)(addr+i*4);
if(memory_plasma_level>3)//由于烧录程序后第一次上电时地址中还没有存入数据,其值将为0xffffffff,此时先给其赋值为3
{
memory_plasma_level=3;
}
}
}
}
void flash_write()//将速度档位、温度档位、离子模式写进flash
{
memory_speed_level=speed_level;
memory_temp_level=temp_level;
memory_plasma_level=plasma_level;
data_assignment();//为数组赋值,要写flash时先执行此函数为写入内容赋值
WriteFlashTest(3,data_save,addr);
}