一、简介
闪存控制器(FMC),提供了片上闪存需要的所有功能。在闪存的前256K字节空间内,CPU执行指令零等待。FMC也提供了页擦除,整片擦除,以及32位整字/16位半字/位编程等闪存操作
主要特征
◼ 高达3M字节的片上闪存可用于存储指令或数据;
◼ 在闪存的前256K字节空间内,CPU执行指令零等待,在此范围外,CPU读取指令存在较长延时;
◼ 对于GD32F30x_CL和GD32F30x_XD,使用了两片闪存,前512KB容量在第一片闪存(bank0)中,后续的容量在第二片闪存(bank1)中;
◼ 对于主存储闪存容量不多于512KB的GD32F30x_CL和GD32F30x_HD,只使用了bank0。
◼ 对于GD32F30x_CL和GD32F30x_HD,GD32F30x_XD,bank0的闪存页大小为2KB,
bank1的闪存页大小为4KB;
◼ 支持32位整字/16位半字/位编程,页擦除和整片擦除操作;
◼ 大小为16字节的可选字节块可根据用户需求配置;
◼ 每次系统复位后选项字节中的内容将重新加载到选项字节控制寄存器中;
◼ 具有安全保护状态,可阻止对代码或数据的非法读访问;
◼ 具有擦除和编程保护状态,可阻止意外写操作。
二、gd32f30x的闪存基地址和构成
三、读操作
闪存可以像普通存储空间一样直接寻址访问。对闪存取指令和取数据分别使用CPU的IBUS或
DBUS总线。
四、FMC_CTLx 寄存器解锁
复位后,FMC_CTL0寄存器进入锁定状态,LK位置为1。
通过先后向FMC_KEY0寄存器写入0x45670123和0xCDEF89AB,可以使得FMC_CTL0寄存器解锁。两次写操作后,FMC_CTL0寄存器的LK位被硬件清0。
可以通过软件设置FMC_CTL0寄存器的LK位为1再次锁定FMC_CTL0寄存器。任何对FMC_KEY0寄存器的错误操作都会将LK位置1,从而锁定FMC_CTL0寄存器,并引发一个总线错误。
FMC_CTL0寄存器的OBPG位和OBER位在FMC_CTL0寄存器第一层解锁后,仍然需要第二层解锁。第二层解锁过程也是两次写操作,向FMC_OBKEY寄存器先后写入0x45670123和
0xCDEF89AB,然后硬件将FMC_CTL0寄存器的OBWEN位置1。
软件可以将FMC_CTL0的OBWEN位清0来锁定FMC_CTL0的OBPG位和OBER位。
对于GD32F30x_CL和GD32F30x_XD,FMC_CTL0寄存器用来设置对bank0和选项字节块的
操作,FMC_CTL1寄存器用来设置对bank1的擦写操作。FMC_CTL1的解锁和锁定机制和
FMC_CTL0类似。对FMC_KEY1写解锁序列可解除FMC_CTL1的锁定。
五、页擦除
FMC的页擦除功能使得主存储闪存的页内容初始化为高电平。 每一页都可以被独立擦除,而
不影响其他页内容。
FMC擦除页步骤如下:
1. 确保FMC_CTLx寄存器不处于锁定状态;
2. 检查FMC_STATx寄存器的BUSY位来判定闪存是否正处于擦写访问状态,若BUSY位为1,
则需等待该操作结束,BUSY位变为0;
3. 置位FMC_CTLx寄存器的PER位;
4. 将待擦除页的绝对地址(0x08XX XXXX)写到FMC_ADDRx寄存器;
5. 通过将FMC_CTLx寄存器的START位置1来发送页擦除命令到FMC;
6. 等待擦除指令执行完毕,FMC_STATx寄存器的BUSY位清0;
7. 如果需要,使用DBUS读并验证该页是否擦除成功。
注意:当页擦除成功执行,FMC_STATx寄存器的ENDF位将置位。若FMC_CTLx寄存器的ENDIE位被置1,则FMC将触发一个中断。需要注意的是,用户需确保写入的是正确的擦除地址,否则当待擦除页的地址被用来取指令或访问数据时,软件将会跑飞。该情况下,FMC不会提供任何出错通知。另一方面,对擦写保护的页进行擦除操作将无效。如果FMC_CTLx寄存器的ERRIE
位被置位,该操作将触发操作出错中断。中断服务程序可通过检测FMC_STATx寄存器的WPERR位来判断该中断是否发生
六、整片擦除
FMC提供了整片擦除功能可以初始化主存储闪存块的内容。当设置FMC_CTL0寄存器中MER
为1时,擦除过程仅作用于Bank0,当设置FMC_CTL1寄存器中MER为1时,擦除过程仅作用于
Bank1,当设置FMC_CTL0和FMC_CTL1寄存器中MER为1时,擦除过程作用于整片闪存。整
片擦除操作,寄存器设置具体步骤如下:
1. 确保FMC_CTLx寄存器不处于锁定状态;
2. 等待FMC_STATx寄存器的BUSY位变为0;
3. 如果单独擦除Bank0,置位FMC_CTL0寄存器的MER位。如果单独擦除Bank1,置位
FMC_CTL1寄存器的MER位。如果整片擦除闪存,同时置位FMC_CTL0和FMC_CTL1寄
存器的MER位;
4. 通过将FMC_CTLx寄存器的START位置1来发送整片擦除命令到FMC;
5. 等待擦除指令执行完毕,FMC_STATx寄存器的BUSY位清0;
6. 如果需要,使用DBUS读并验证是否擦除成功。
七、主存储闪存块编程 主存储闪存块编程
FMC提供了一个32位整字/16位半字/位编程功能,用来修改主存储闪存块内容。编程操作使用
各寄存器流程如下:
1. 确保FMC_CTLx寄存器不处于锁定状态;
2. 等待FMC_STATx寄存器的BUSY位变为0;
3. 置位FMC_CTLx寄存器的PG位;
4. DBUS写一个32位整字/16位半字到目的绝对地址(0x08XX XXXX);
5. 等待编程指令执行完毕,FMC_STATx寄存器的BUSY位清0;
6. 如果需要,使用DBUS读并验证是否编程成功。
八、软件编程
#include "myflash.h"
uint32_t MyFLASH_Readword (uint32_t Address)
{
return *( ( uint32_t *)(Address) );
}
//注意:地址为小端存储
uint16_t MyFLASH_ReadHalfword (uint32_t Address)
{
return *(( uint16_t *)(Address) );
}
uint8_t MyFLASH_ReadByte (uint32_t Address)
{
return * ( ( uint8_t * ) (Address)) ;
}
void MyFLASH_EraseAllPages (void)
{
fmc_unlock () ;
fmc_mass_erase ( ) ;
fmc_lock () ;
}
void MyFLASH_ErasePage (uint32_t PageAddress)
{
fmc_unlock ( );
fmc_page_erase (PageAddress ) ;
fmc_lock ();
}
void MyFLASH_Programword (uint32_t Address, uint32_t Data)
{
fmc_unlock ( ) ;
fmc_word_program (Address,Data) ;
fmc_lock ( ) ;
}
void MyFIASH_ProgramHalfword (uint32_t Address,uint16_t Data)
{
fmc_unlock ( ) ;
fmc_halfword_program (Address,Data) ;
fmc_lock ();
}
#include "store.h"
#define LAST_PAGE_ADDR 0x0807F800
uint16_t store_Data [1024];
//上电调用初始化函数,然后就可以加载之前存储在flash中的数据
void store_Init (void)
{
uint16_t i;
//0x0800Fc00最后一页的首地址;OxA5A5为了判断之前是否存储数据
if (MyFLASH_ReadHalfword(LAST_PAGE_ADDR)!= 0xA5A5)
{
MyFLASH_ErasePage (LAST_PAGE_ADDR);
MyFIASH_ProgramHalfword(LAST_PAGE_ADDR,0xA5A5);
// 一页的大小是2K,存储数据数组大小是半字。第一位存储标志位,后面数据位清0
for (i = 1; i <1024; i ++)
{
MyFIASH_ProgramHalfword (LAST_PAGE_ADDR + i * 2,0x0000);
}
}
for (i = 0; i <1024; i ++)
{
store_Data [i] = MyFLASH_ReadHalfword (LAST_PAGE_ADDR + i * 2);
}
}
//调用保存函数就会将数据写入flash存储器中
void store_save (void)
{
uint16_t i;
MyFLASH_ErasePage (LAST_PAGE_ADDR) ;
for (i = 0; i < 1024; i ++)
{
MyFIASH_ProgramHalfword (LAST_PAGE_ADDR + i * 2,store_Data[i]);
}
}
void store_clear (void)
{
uint16_t i;
for (i = 1; i < 1024; i ++)
{
store_Data[i] = 0x0000;
}
store_save () ;
}
九、易错点
如果下载选择只擦除选中的部分
第一个坑就是,他并不是将你设置选中的所有部分都擦除之后在写入,而是你需要用多少页的内存它擦除多少页。
第二个坑就是,使用库函数编程一个字节数据时,不管编程地址是否在选中擦除部分的地址里面,都会编程成功