嵌入式应用中经常会遇到需要保存一些数据,比如配置信息等等,为了在设备关机或掉电情况下数据不丢失,我们通常做法是会将数据保存在存储区,可以在FLASH主存储区、备份寄存器和选项字节等存储用户数据。
MM32F013X系列芯片的嵌入式闪存高达 64K 字节 ,整个片内 FLASH由两部分组成:一部分是主存储块,另一部分是信息存储块。主存储块除了被用于存储用户代码,也可被模拟成EEPROM来存储用户数据;在信息存储块中,包括了保护字节、保密空间、系统存储器 ISP 和选项字节四部分,其中除了系统存储器 ISP区域用户不可使用外,用户可以通过对应的操作流程对其它区域进行读写操作,于用户而言,选项字节用户数据区也可以被用来存储2个字节长度的有效数据。
本文将重点介绍如何在MM32F013X上实现用FLASH 选项字节存储用户数据以及使能硬件独立看门狗模式的功能。
1. 选项字节介绍
MM32F013X 系列MCU 的选项字节主要用于存储用户对芯片的配置信息及用户关键数据,主要有写保护使能、看门狗模式切换等等不同配置内容,FLASH 控制器可以通过对这些值的设置来选择不同的系统功能选项。选项字节区块的前16字节,每两个字节组成一个正反对,其中用户只需要设置低位的字节,高位由系统自动填充为其反码。
选项字节的组成如下表所示(位 15 ∼ 8 中的值为位 7 ∼ 0 中选项字节 0 的反码):
注意:在写保护值中,一个比特位对应四页,即 4096 字节,其它详情请参见UM手册。
- USER:字节2,用户字节,配置看门狗模式、停机复位模式、待机复位模式以及BOOT1。
- DATA0:字节4,数据字节0,由用户存储数据。
- DATA1:字节6,数据字节1,由用户存储数据。
- WRP0:字节8,写保护字节0,存储对主存储块的写保护设置。
- WRP1:字节10,写保护字节1,存储对主存储块的写保护设置。
1.1 相关寄存器
1.2 擦除流程
选项字节区块擦除操作流程的具体步骤如下:
1.2.1 检查FLASH\_CR寄存器的LOCK位,如未被解锁则执行解锁操作。
if((FLASH->CR & FLASH_CR_LOCK) == SET )
{
FLASH->KEYR = FLASH_KEY1 ;
FLASH->KEYR = FLASH_KEY2 ;
}
1.2.2 检查FLASH\_CR寄存器的OPTWRE位,如有置位则执行解锁OPTWRE操作。
if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
FLASH->OPTKEYR = FLASH_KEY1 ;
FLASH->OPTKEYR = FLASH_KEY2 ;
}
1.2.3 将闪存选项字节块基地址写入FLASH\_AR寄存器。
FLASH->AR = OB_BASE ;
1.2.4 设置FLASH\_CR寄存器的OPTER位为1,选择选项字节擦除操作。
FLASH->CR |= FLASH_CR_OPTER ;
1.2.5 设置FLASH\_CR寄存器的STRT位为1。
FLASH->CR |= FLASH_CR_STRT ;
1.2.6 等待FLASH\_SR寄存器的BSY位变为0,再读出选项字节寄存器中所有地址来验证擦除操作,直到流程结束。
do{
ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY: \
((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG: \
((FLASH->SR & FLASH_FLAG_WRPRTERR) ?
FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
EraseTimeout-- ;
for (i = 0xFF; i != 0; i--) ;
}while((ret == FLASH_BUSY) && (EraseTimeout!= 0x00)) ;
FLASH->CR = 0 ;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;
return (FLASH_Status)((EraseTimeout== 0x00) ? FLASH_TIMEOUT : ret) ;
由于选项字节只有16字节,因此,擦除时是整个选项字节都将被擦除,此时需要注意,将有效的用户数据及配置信息提前保存到内存中再进行擦除。
1.3 编程流程
选项字节区块编程操作流程的具体步骤如下:
1.3.1 检查FLASH\_CR寄存器的LOCK位,如未被解锁则执行解锁操作。
if((FLASH->CR & FLASH_CR_LOCK) == SET )
{
FLASH->KEYR = FLASH_KEY1 ;
FLASH->KEYR = FLASH_KEY2 ;
}
1.3.2 检查FLASH\_CR寄存器的OPTWRE位,如有置位则执行解锁OPTWRE操作。
if((FLASH->CR & FLASH_CR_OPTER) == RESET)
{
FLASH->OPTKEYR = FLASH_KEY1 ;
FLASH->OPTKEYR = FLASH_KEY2 ;
}
1.3.3 设置FLASH\_CR寄存器的OPTPG位为1,选择编程操作。
FLASH->CR |= FLASH_CR_OPTPG ;
1.3.4 写入要编程的半字到指定的地址,启动编程操作。
__IO u16 temp ;
temp = (u16)(~data) ;
temp = (temp << 8) & 0xFF00 ;
temp = temp | (u16)data ;
*(__IO u16*)address = temp ;
1.3.5 等待FLASH\_SR寄存器的BSY位变为0,可选读目标地址数据,以确保半字编程成功,直到所有编程流程结束。
do{
ret = (((FLASH->SR & FLASH_FLAG_BSY)) ? FLASH_BUSY: \
((FLASH->SR & FLASH_FLAG_PGERR) ? FLASH_ERROR_PG: \
((FLASH->SR & FLASH_FLAG_WRPRTERR) ?
FLASH_ERROR_WRP : FLASH_COMPLETE))) ;
ProgramTimeout -- ;
for (i = 0xFF; i != 0; i--) ;
}while((ret == FLASH_BUSY) && (ProgramTimeout != 0x00)) ;
FLASH->CR = 0 ;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR ;
return (FLASH_Status)((ProgramTimeout == 0x00) ? FLASH_TIMEOUT : ret) ;
2. 软件实现步骤
下面列出配置代码,实现开启硬件IWDG独立看门狗以及将用户数据存储在DATA0和DATA1。
2.1 主程序初始化
s32 main(void)
{ /* Systick delay init */
DELAY_Init();
/* Uart1 init */
CONSOLE_Init(115200) ;
printf("\r\nMM32F013x OptionByte Demo %s %s\r\n", __DATE__, __TIME__);
/* OptionByte test entry */
FLASH_OptionByte_Entry() ;
/* Set the HWIWDG parameters,no need to turn on LSI or IWDG_Enable */
Set_IWDG(IWDG_Prescaler_64, 999);
printf("\r\n IWDG reload value is set to 1.6s !\r\n ");
while (1)
{
/* Feed the HW_IWDG if needed*/
Feed_IWDG() ;
DELAY_Ms(1000) ;
printf("\r\n System is going on. Feed the IWDG...\r\n ");
}
}
以上为整个软件工程的入口主函数,平台相关的堆栈设置、时钟设置以及FLASH延迟设置等等都在STARTUP\_MM32F013X\_KEIL.S和SYSTEM\_MM32F013X.C文件中已经完成,默认主频为内部时钟HSI倍频到72M,上电后程序会跳转到主函数。初始化好延时和串口后,进入到测试选项字节的功能函数中,并且接下来的初始化中还配置了IWDG的重装载值以及分频系数,在while(1)循环里选择是否喂狗看不同现象。
2.2 OptionByte 实验函数
void FLASH_OptionByte_Entry(void)
{
volatile uint32_t TempOptionByteValue = 0;
uint8_t WriteUserData[2] ={0x01,0x02} ;
/* Unlock The FLASH Controller */
FLASH_Unlock();
if( FLASH_GetFlagStatus(FLASH_FLAG_OPTERR) == RESET )
{
TempOptionByteValue = FLASH_GetUserOptionByte();
printf("\r\nTempOptionByteValue=0x%2x\r\n ", TempOptionByteValue);
if(((uint8_t)(TempOptionByteValue >> 10) != WriteUserData[0]) && \
((uint8_t)(TempOptionByteValue >> 18) != WriteUserData[1]) )
{/* User Data0 != 0x01 , User Data1 != 0x02*/
/* Erase Option Bytes */
FLASH_Status status = FLASH_EraseOptionBytes();
/* Write User Data0 */
status = FLASH_ProgramOptionByteData(0x1FFFF804, WriteUserData[0]) ;
/* Write User Data1 */
status = FLASH_ProgramOptionByteData(0x1FFFF806, WriteUserData[1]) ;
/* Write User Byte to enable HW mode of IWDG */
status = FLASH_ProgramOptionByteData(0x1FFFF802,0xFE) ;
printf("\r\n UserDatas and UserByte are written!\r\n ");
printf("\r\n INPORTANT! Need to reset the system ……\r\n ");
/* Lock The Flash Program Erase Controller */
FLASH_Lock();
/* System Reset */
NVIC_SystemReset();
}
else
{
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
{
printf("\r\n RCC_FLAG_IWDGRST is set!\r\n ");
/* Clear reset flags */
RCC_ClearFlag();
printf("HW IWDG was set successfully,need to RL_IWDG!\r\n ") ;
}
else
{
printf("\r\n Reboot ok.\r\n ");
printf("\r\n UserDatas were updated successfully!\r\n ");
}
}
}
/* Lock The Flash Controller */
FLASH_Lock();
}
由于每次系统复位后,选项字节才会被重新加载,并保存在选项字节寄存器 (FLASH\_OBR)中,每个选择位都在信息块中有它的反码位,在加载选择位时,反码位用于验证选择位是否正确,如果有任何的差别,将产生一个选项字节错误标志 (OPTERR),所以建议操作选项字节空间时先判断FLASH标志位。
按照流程对FLASH的选项字节空间进行数据读取和写入操作,且设置独立看门狗为硬件模式,做完所有的操作后,一定要将系统进行软复位或者上电复位,这样才能使之前的写入操作正式生效。
最后,通过RCC的复位标志判断此次复位是否由看门狗引发,搭配串口信息输出来进行所有功能验证。
3. 测试验证
本次实验同样是基于eMiniboard MB-025硬件资源完成的,前述完整代码中,开启了在主循环的喂狗操作,每隔1s钟进行喂狗动作且通过串口打印运行状态,串口助手完整显示如下:
可以发现已经正常将用户数据0x01和0x02分别写入到了Data0和Data1中,且独立看门狗的硬件模式也有设置,为了验证看门狗已经生效,屏蔽喂狗语句后重新编译工程,擦除全片后重新烧录程序,此刻,串口助手完整显示如下:
通过以上打印信息,可以确认硬件看门狗的硬件模式已经生效,无需通过用户代码开启LSI时钟和开启独立看门狗,只要芯片正常上电即可完成对用户程序异常情况发生的防护,从而提高系统整体安全性能。