前言
用单片机做开发时,为了实现产品的某些功能,我们通常都需要记录一些数据并放在单片机掉电不丢失的区域中。在现在的主流8位机中,一般为EEPROM和FLASH存储区。这些存储区一般与我们的代码存储区是分开来的且一般不大(多数在1KB以内)。EEPROM是可以按字节读、写的,而FLASH则是以块为单位擦除、写入的。而在32位单片机中,一般不会为单独的数据存储再开辟一个区域,即存代码的和存这些数据的是同一个区域。所以,如果你看过其他有关32位机内部FLASH读写操作的,它们基本都是从FLASH最后一个或几个区块操作的,本文给出的示例也是如此。
RAM与ROM
讲内部FLASH读、写之前,咱们先简单聊聊单片机的RAM与ROM。RAM就是我们所说的内存,具有读写快的特点,但是掉电数据丢失。ROM为只读存储器,在这里指的就是FLASH,具有掉电不丢失的特性。选择FLASH型MCU的好处在于便于前中期调试与后期维护,与之相对的还有OTP与MTP型MCU。它们之间最大的区别在于可重复烧录次数的限制,这也导致了在整个代码调试上有很多区别。
各模块程序编写
在配置前,请确保你已经有一个GD32F303包含其对应标准库的keil工程,工程可使用官方的例程或可按照GD32F303调试小记(零)之工程创建与编译创建。
一、片内FLASH映射关系
- 根据上面的描述,我们知道整个FLASH区的地址范围。整个FALSH分为两个BANK区,对于FLASH容量不大于512K的都在BANK0区,每页大小为2KB。对于FLASH容量大于512的除去前512KB在BANK0以外,后面的都在BANK1中,每页大小为4KB。
- 由于FLASH区更多是用来存储代码的,你编写烧录的代码会从FLASH的第0页,也就是从0x-0800-0000开始占用一定的FLASH页数。要是你操作到这些地方,我们可以想象会发生什么样的后果。所以,我们一般根据你手上的芯片大小,往往都从最后一个或最后几个FLASH页上操作:
#define FMC_PAGE_SIZE ((uint16_t)0x1000U)
#define FMC_WRITE_START_ADDR ((uint32_t)0x080F0000U)
#define FMC_WRITE_END_ADDR ((uint32_t)0x080FFFFFU)
二、FLASH擦除
- 直接套用官方例程中的擦除函数,这里我使用了BANK1中的FALSH页,对应FMC_FLAG_BANK1_END、FMC_FLAG_BANK1_WPERR、和FMC_FLAG_BANK1_PGERR这三个标志位。BANK0的话修改这三个即可。
- 这里是多页擦除,PageNum 这个变量算的就是页数,这里我是擦除一页。
uint32_t PageNum = (FMC_WRITE_END_ADDR - FMC_WRITE_START_ADDR + 1) / FMC_PAGE_SIZE;
void fmc_erase_pages(void)
{
uint32_t EraseCounter;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_BANK1_END);
fmc_flag_clear(FMC_FLAG_BANK1_WPERR);
fmc_flag_clear(FMC_FLAG_BANK1_PGERR);
for(EraseCounter = 0; EraseCounter < PageNum; EraseCounter++)
{
fmc_page_erase(FMC_WRITE_START_ADDR + (FMC_PAGE_SIZE * EraseCounter));
fmc_flag_clear(FMC_FLAG_BANK1_END | FMC_FLAG_BANK1_WPERR | FMC_FLAG_BANK1_PGERR);
}
fmc_lock();
}
二、FLASH写入
- 这个也是沿用官方例程的写入函数,底层我用的是按字写入的方式,本想封装成多字写入的,后面因为忙忘了改了。注意清所在页对应的BANK标志位。
void fmc_program(uint32_t * data,uint32_t addressx)
{
fmc_unlock();
uint32_t address = addressx;
fmc_word_program(address, *data);
fmc_flag_clear(FMC_FLAG_BANK1_END | FMC_FLAG_BANK1_WPERR | FMC_FLAG_BANK1_PGERR);
fmc_lock();
}
void fmc_program(uint32_t data,uint32_t addressx)
{
fmc_unlock();
fmc_word_program(addressx, data);
fmc_flag_clear(FMC_FLAG_BANK1_END | FMC_FLAG_BANK1_WPERR | FMC_FLAG_BANK1_PGERR);
fmc_lock();
}
三、FLASH读取
- 这个是最简单的,定义一个指针类型,指向之前记忆的地址,就能获取其中的内容,与官方封装寄存器地址一样,我们这么写:
#define FMC_READ(addrx) ( *(volatile uint32_t*)(uint32_t)(addrx) )
#define CLIP12V_AD_ADDR ((uint32_t)(FMC_WRITE_START_ADDR + 0x000U))
#define LANGUAGE_ADDR ((uint32_t)(FMC_WRITE_START_ADDR + 0x004U))
#define BRIGHT_ADDR ((uint32_t)(FMC_WRITE_START_ADDR + 0x008U))
四、主函数
void task_key_event(void)
{
if(key_flag == 3)
{
fmc_erase_pages();
fmc_program(&ADV_Clip12V_AD,CLIP12V_AD_ADDR);
fmc_program(&Language_current,LANGUAGE_ADDR);
}
}
- 读取则是更随意了,在任何你需要的地方添加你要的这个即可:
ADV_Clip12V_AD = FMC_READ(CLIP12V_AD_ADDR);
Language_current = FMC_READ(LANGUAGE_ADDR);
Bright_Level = FMC_READ(BRIGHT_ADDR);
五、总结
- 总的来所,32位机的操作更简单,尤其是在读取上。
- 另外插播一个消息,郭天祥老师和他的团队将会在全平台出一套关于GD32F303的教程(就是那个10天教会51单片机的那个),涵盖的知识面会更加全面,点击此处。
- 接下来我个人关于GD32F303的文章还剩下两个部分,窗口看门狗和定时器出PWM(部分功能),之后关于GD32的内容基本就要画上句号了,感谢各位的点击。
!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处:)!!!
|