【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用
[复制链接]
本帖最后由 hehung 于 2022-2-19 16:24 编辑
之前的帖子可以参考:
【GD32L233C-START评测】1.开箱
【GD32L233C-START评测】2.手把手创建新工程
【GD32L233C-START评测】3.移植FreeRTOS到GD32L233
【GD32L233C-START评测】4. 移植RT-Thread到GD32L233
【GD32L233C-START评测】5. IIC驱动OLED
【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示
【GD32L233C-START评测】7. PWM驱动LED
【GD32L233C-START评测】8. TRNG真随机数生成
【GD32L233C-START评测】9. CRC检验
【GD32L233C-START评测】10. ADC读取芯片内部温度
【GD32L233C-START评测】11.DAC输出电压值_ADC读取外部电压值
【GD32L233C-START评测】12. 硬件IIC驱动OLED
【GD32L233C-START评测】13. CAU加密算法之DES/TDES
【GD32L233C-START评测】14. CAU加密算法之AES
一、前言
我们平时在开发单片机程序的时候经常会有这样的情况:需要将某些数据保存起来掉电不丢失,我们一般会选择使用EEPROM来保存数据,但是有些单片机没有提供内部EEPROM,则需要我们外接EEPROM,这样增大了开发成本,如果我们不接外设EEPROM,需要如何操作呢?
这就需要用到我们的FLASH,将FLASH当做一个EEPROM一样进行操作,但是FLASH操作的时候也有一个缺点,就是FLASH在擦除的时候只能一页一页的擦除,不能单独擦除几个字节,所以我们在使用FLASH保存数据的时候需要注意不要将我们的代码程序擦除了,或者我们不期望擦除的程序擦除了。
一般使用FLASH来模拟EEPROM的时候,我们在擦除FLASH页的时候都需要现将数据备份,然后将待写入的数据更新,然后将擦除的整个页写入到指定的地址中去。
FLASH也是我们保存代码的地方,代码一般存储在FLASH的前面,后面一部分用不到,我们就可以用来保存我们的数据,但是如何确保我们的数据不会在下载代码的时候被覆盖呢,我们需要修改一下地址范围,在下面说明。
二、FLASH操作原理
在前言中,我们了解了FLASH作为EEPROM使用的一些基本知识之后,我们还需要知道怎么在GD32L233C中实现这个操作。
GD32L233C的FMC的操作在《用户手册》第2章 2. 闪存控制器( 闪存控制器(FMC )
1. FLASH大小
我们需要了解GD32L233C的FLASH的大小,在用户手册中没有描述,参考《数据手册》第2章 2.1. Device information
如下所示,GD32L233C的FLASH大小位256KB
2. FLASH结构
了解FLASH结构,对代码编辑必不可少,如下图,256KB大小的FLASH被分为了64页,每页4KBytes。
FLASH在擦除的时候一次性只能擦除4KBytes。
如下截图有一处笔误? 第63页,地址范围应该是0x0803F000 - 0x0803FFFF?
3. FLASH解锁
在对FLASH进行操作之前需要对Flash进行解锁,操作完毕之后需要上锁。
参考《数据手册》2.3.3. FMC_CTL 寄存器解锁
本帖不在做详细描述,因为GD32L233x的库文件已经提供了解锁和上锁的函数,可以直接调用,不需要我们自己再去操作寄存器
4. FLASH页擦除
FLASH页擦除参考《用户手册》2.3.4. 页擦除
本帖不做详细描述,库文件中提供的相应的接口,直接调用即可。
注意:针对GD32L233C这款芯片,一页是4KByes,在擦除的时候谨慎操作,不要将代码区域的代码夜擦除了。
5. FLASH写入
这是本文的重点内容,可以参考《用户手册》2.3.6. 主存储闪存块编程 以及 2.3.7. 主存储闪存块 快速编程
GD32L233x提供了两种写FLASH的方式,需要注意这两种写入方式的操作逻辑以及一次写得字节大小。
对于寄存器相关的操作逻辑了解即可,GD32官方库文件已经提供了写FLASH接口,直接调用即可。
下面重要说一下这两种写入方式的区别:
(1)FLASH普通写入
使用这种方式,一次写入的数据是32位或者16位,也就是4字节或者2字节,对于C语言的unsigned int类型和unsigned short类型,在编程的时候一定要注意
本帖中提供的函数接口只适用了32位的形式,为了方便操作。
看《用户手册》中已经写得比较清楚了,如果用在写32位的时候你传入的数据不是32位的,flash不会写进去,到时候不要出现了问题不知道为什么。
模拟EEPROM使用一般就是用的这种方式,可以写入一个16位或者32位的数据
(2)FLASH快速写入
对于快速写入方式,一次性写一行,一行是32个双字,一定要注意是32个双字,不能是64个字。
应为GD32L233x是32位的单片机,所以一个字是4个字节,双字也就是8个字节,对应的数据类型是unsigned long int
所以在使用快速方式写入flash的时候,需要定义一个32个元素的数据,每个数据是8字节的,这种方式一般是用来刷新FLASH的,可以快速的写入,比如我们编写bootloader程序来更新我们的程序就可以使用这种方式来编写下载flash算法。
对于使用中的注意事项,《用户手册》中也有比较详细的描述,如下:
三、如何防止代码更新擦除了我们写在FLASH中的数据
看了《用户手册》以及《数据手册》之后,我们知道了FLASH的范围,也知道的FLASH擦写的操作逻辑以及一些注意事项,同时为了避免我们写在flash中的数据被下载代码的时候更新,我们需要对编译器进行设置一下。
前言中已经说了代码一般保存在FLASH的前面,我们要保存数据在FLASH中就写在FLASH的后边,再加上FLASH擦除是以页为单位的,所以至少也要用到一页的FLASH空间,本帖中的代码使用了2页,操作涉及到的地址范围是:0x0803E000~0x0803FFFF。
所以修改FLASH代码区域的长度修改为0x3E000,原来是0x40000,如下:
四、代码实现
1. 宏定义
宏定义中定义了FLASH的起始地址与结束地址,以及一页的数据大小,以及两种FLASH写入方式
E_OK以及E_NOT_OK是FLASH擦除校验是否成功的返回值
FMC_PROGRAM_TYPE_WORD表示FLASH的普通写入模式
FMC_PROGRAM_TYPE_FAST表示FLASH的快速写入模式
FMC_PAGE_SIZE表示FLASH一页的大小,GD32L233C是4KBytes,也就是4096,对应16进制的0x1000
FMC_START_ADDRESS是FLASH开始地址,为0x08000000
FMC_END_ADDRESS是FLASH结束地址,为0x0803FFFF
#define E_OK ((uint8_t)0U)
#define E_NOT_OK ((uint8_t)1U)
#define FMC_PROGRAM_TYPE_WORD ((uint8_t)0x00U)
#define FMC_PROGRAM_TYPE_FAST ((uint8_t)0x01U)
#define FMC_PAGE_SIZE ((uint16_t)0x1000U)
#define FMC_START_ADDRESS ((uint32_t)0x08000000U)
#define FMC_END_ADDRESS ((uint32_t)0x0803FFFFU)
2. 全局变量定义
全局变量定义了我们期望写到FLASH中的数据
data0和data1是使用普通模式写FLASH的32位数据
data_buffer是使用快速模式写入的32个双字的数组
uint32_t data0 = 0x01234567U;
uint32_t data1 = 0xd583179bU;
/* data buffer for fast programming */
static uint64_t data_buffer[DOUBLE_WORDS_CNT_IN_ROW] = {
0x0000000000000000U, 0x1111111111111111U, 0x2222222222222222U, 0x3333333333333333U,
0x4444444444444444U, 0x5555555555555555U, 0x6666666666666666U, 0x7777777777777777U,
0x8888888888888888U, 0x9999999999999999U, 0xAAAAAAAAAAAAAAAAU, 0xBBBBBBBBBBBBBBBBU,
0xCCCCCCCCCCCCCCCCU, 0xDDDDDDDDDDDDDDDDU, 0xEEEEEEEEEEEEEEEEU, 0xFFFFFFFFFFFFFFFFU,
0x0011001100110011U, 0x2233223322332233U, 0x4455445544554455U, 0x6677667766776677U,
0x8899889988998899U, 0xAABBAABBAABBAABBU, 0xCCDDCCDDCCDDCCDDU, 0xEEFFEEFFEEFFEEFFU,
0x2200220022002200U, 0x3311331133113311U, 0x6644664466446644U, 0x7755775577557755U,
0xAA88AA88AA88AA88U, 0xBB99BB99BB99BB99U, 0xEECCEECCEECCEECCU, 0xFFDDFFDDFFDDFFDDU
};
3. 擦FLASH
FLASH擦除是以页位单位的,所以传入的参数就是第几页,从数据手册中我们知道把FLASH区域分为了64页,所以可传入的参数就是0-63
该函数会擦除指定页,当传入参数是63的时候,debug会报错,见最后。
uint8_t fmc_erase_pages(uint8_t page_num)
{
uint32_t s_addr = 0;
if(page_num < 64)
{
s_addr = FMC_START_ADDRESS + (FMC_PAGE_SIZE * page_num);
/* unlock the flash program/erase controller */
fmc_unlock();
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* erase the flash pages */
fmc_page_erase(s_addr);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the erase operation */
fmc_lock();
/* check whether erased successful */
return fmc_erase_pages_check(s_addr);
}
return E_NOT_OK;
}
4. 擦除数据校验
该函数用于检查数据指定页擦除之后数据是否全部都是0xFF,如果不是,说明没有擦除完整。
static uint8_t fmc_erase_pages_check(uint32_t s_addr)
{
uint32_t i;
uint32_t *ptrd;
uint8_t ret = E_OK;
ptrd = (uint32_t *)s_addr;
/* check flash whether has been erased */
for(i = 0; i < (FMC_PAGE_SIZE >> 2); i++)
{
/* check 4Bytse every time */
if(0xFFFFFFFF != (*ptrd))
{
ret = E_NOT_OK;
break;
}
else
{
ptrd++;
}
}
return ret;
}
5. FLASH普通模式写入数据
普通模式写入上面已经说过了,一次性写入一个32位的数据,也就是UInt32_t类型的数据,参数data就是期望写入的32位数据。
s_addr是指定写入的地址,地址范围最好使用fmc_erase_pages() 擦除过的页地址范围里面,避免没有擦除就写数据上报总线错误。
static void fmc_program_word(uint32_t s_addr, uint32_t data)
{
/* unlock the flash program/erase controller */
fmc_unlock();
/* program flash */
fmc_word_program(s_addr, (uint32_t)data);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the program operation */
fmc_lock();
}
6. FLASH快速写入模式写入数据
快速模式上面已经说过了,一次性写入32个双字,参数data就是期望写入的32个双字数据
s_addr是指定写入的地址,地址范围最好使用fmc_erase_pages() 擦除过的页地址范围里面,避免没有擦除就写数据上报总线错误。
static void fmc_program_fast(uint32_t s_addr, uint64_t* data)
{
/* unlock the flash program/erase controller */
fmc_unlock();
/* program flash */
fmc_fast_program(s_addr, data_buffer);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the program operation */
fmc_lock();
}
7. main函数
如下:
先擦除63页,地址范围是0x0803E000 ~ 0x0803EFFF
然后将data0写入地址0x0803E000
将data1写入地址0x0803E010
将数组data_buffer写入地址0x0803E014
int main(void)
{
// fmc_erase_pages(63); /* Bubug will error */
fmc_erase_pages(62);
fmc_program_word(0x0803E000, data0);
fmc_program_word(0x0803E010, data1);
fmc_program_fast(0x0803E014, data_buffer);
while(1)
{
}
}
五、检验效果
使用在线Debug的方式来运行我们的代码,然后再memory窗口中查看flash地址的数据,如下,测试成功
0x0803E000地址被写入了0x01234567,数据是反着的是因为编码方式是小端模式
0x0803E010地址被写入了0xd583179b,数据是反着的是因为编码方式是小端模式
0x0803E014地址被写入了数组data_buffer的内容
六、代码bug
在擦除最后一页flash,也就是64页的时候debug程序会报错,不知道为什么?有知道的小伙伴可以解答一下,谢谢。
如下图,也不知道是我的代码bug还是官方库的bug,等我后边再细细研究下。
|