打印
[研电赛技术支持]

【GD32F303红枫派使用手册】第五讲 FMC-片内Flash擦写读实验

[复制链接]
1698|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 聚沃科技 于 2024-6-4 11:02 编辑


5.1 实验内容
通过本实验主要学习以下内容:
• FMC控制器原理;
• FMC擦写读操作;
5.2 实验原理
5.2.1 FMC控制器原理
FMCFlash控制器,其提供了片上Flash操作所需要的所有功能,在GD32F303系列MCU中,Flash256K字节空间内, CPU执行指令零等待,具有相同主频下最快的代码执行效率。FMC也提供了页擦除,整片擦除,以及32位整字/16位半字/位编程等闪存操作。GD32F303系列MCU支持最大3M Flash空间,可以提供业内最大Flash的相关产品。
GD32F303系列MCUFlash结构如下图所示。由该图可知,GD32F303系列MCU可以支持最大3MFlash空间,前256页为2KB每页,共512KB空间,后面的空间为4KB每页,信息块为存储内部出厂BOOTLOADER,中容量的GD32F303系列产品空间为2KB,大容量的GD32F303系列产品空间为6KB,互联型的GD32F305/307系列产品空间为18KB,主要是由于不同的产品所支持的ISP烧录接口不同,所需要的代码空间也会有差别。可选字节块存储的是选项字节,其空间大小为16个字节,地址范围为0x1FFFF800-0x1FFFF80F,本章主要讲解FMC的操作,有关选项字节操作可以参考选项字节操作实验。

有关Flash擦写操作均需要先解锁Flash,然后进行擦写操作,擦写完成后再进行锁Flash,注意Flash特性只能由10,也就是Flash需要先擦除才能写入新的数据,如果确保写入地址的数据为全0xFF,也可以直接写入。读取Flash数据可以采取直接寻址的方式进行读取。
下面为各位读者介绍Flash擦写读的相关操作。
5.2.2 Flash擦除操作原理
Flash擦除可分为页擦除以及整片擦除,如下图所示,页擦除时间典型值为48ms256KB Flash的块擦除时间典型值为2S
有关Flash的相关操作均在gd32f30x_fmc.c中实现,下面介绍下擦除实现的函数,如下表所示。
5.2.3 Flash写入编程操作原理
GD32F303系列MCU可支持32位整字编程/16位板字以及位编程,如下图所示,Flash 32位整字编程时间典型值为47.5us
有关Flash编程实现函数如下表所示。
注意:fmc_word_reprogram可以在不用擦除的情况下直接进行位编程,但仅可实现将1编程为0,比如若调用以上fmc_word_reprogram(0x08001000,0xFE)语句,即实现将最低位编程为0,若0x08001000原始数据为0x81,则执行完后改地址数据为0x80
5.2.4 Flash读取操作原理
Flash读取可以采用直接寻址的方式进行操作,具体可参考以下示例代码。
C
uint32_t read_data;
read_data = *(uint32_t *)0x08001000;
注意:有关Flash有以下参数读者需要了解,GD32F303系列MCU的内部Flash具有至少10万次的擦写次数以及20年的数据保持能力,但需注意,随着擦写次数的增加数据保持时间会下降。
5.3 硬件设计
本例程不涉及硬件电路。
5.4 代码解析
5.4.1 Flash写入16bit双字节函数
Flash写入双字节操作函数如下所示,写入的过程主要分为擦写两个操作,由于Flash特有特性,需要先擦除才可以写入,因而需要确保写入地址的初识数据为0xFF。另外GD32F303具有双bank,且不同bank的页大小具有差异,本函数可以实现根据地址识别对应页并进行擦除的功能,使用上非常方便,使用者只需要关心擦写的起始地址以及数据和长度即可,擦写的位置函数中会进行实现。
C
void fmc_write_data_16b(uint32_t write_start_addr, uint16_t *data_buf, uint16_t data_lengh)
{
        uint32_t write_addr,erase_addr;
        uint16_t data_write_num=0;
        int16_t data_earse_num;
        /* 解锁FMC */
        fmc_unlock();
        /* 清除BANK0和BANK1的错误标志 */
        fmc_flag_clear(FMC_FLAG_BANK0_PGERR|FMC_FLAG_BANK0_WPERR|FMC_FLAG_BANK0_END);
        fmc_flag_clear(FMC_FLAG_BANK1_PGERR|FMC_FLAG_BANK1_WPERR|FMC_FLAG_BANK1_END);
        
        
        erase_addr = write_start_addr;
        data_earse_num = data_lengh;
        /* 若写入起始地址加上总长*2小于0x08080000,说明需要擦写的数据均在BANK0,页大小为2K/页 */
        if((write_start_addr+data_lengh*2)<0x08080000)
        {
                /* 若写入地址为页起始地址 */
                if(write_start_addr%2048 == 0)
                {
                  for(;data_earse_num>0;)
                  {
                          fmc_page_erase(erase_addr);
                          erase_addr+=2048;
                          data_earse_num-=1024;
                  }
                        /*若写入地址不是页起始地址*/
          }else{
        for(;(data_earse_num>0||erase_addr>=write_start_addr+data_lengh*2);)
                    {
                            fmc_page_erase(erase_addr);
                            erase_addr+=2048;
                            data_earse_num-=1024;
                    }
            }
                /* 若写入地址加上写入长度*2大于0x08080000,说明擦写的数据可能跨BANK或者均在BANK1,页大小有差别 */
  }else{
                /* 如果起始地址小于0x08080000,说明跨BANK */
                if(write_start_addr<0x08080000)
                {
                        /* 首先擦除BANK0部分所需页 */
                        for(;erase_addr<0x08080000;)
                        {
                                        fmc_page_erase(erase_addr);
                            erase_addr+=2048;
                        }
                        /* 然后擦除BANK1部分所需页 */
                        erase_addr = 0x08080000;
                        for(;erase_addr<=write_start_addr+data_lengh*2;)
                        {
                                        fmc_page_erase(erase_addr);
                            erase_addr+=4096;
                        }
                }else{
                        /*若写入地址大于等于0x08080000,说明均在BANK1,页大小为4K/页*/
                if(write_start_addr%4096 == 0)  /* 若写入地址为页起始地址 */
                {
                  for(;data_earse_num>0;)
                  {
                          fmc_page_erase(erase_addr);
                          erase_addr+=4096;
                          data_earse_num-=2048;
                  }
                }else{
                          /*若写入地址不是页起始地址*/
        for(;(data_earse_num>0||erase_addr>=write_start_addr+data_lengh*2);)
                    {
                            fmc_page_erase(erase_addr);
                            erase_addr+=4096;
                            data_earse_num-=2048;
                    }
                        }
                }
        }
        
        /* 写入数据 */
        write_addr = write_start_addr;
        for(data_write_num = 0; data_write_num<data_lengh;data_write_num++)
        {
                fmc_halfword_program(write_addr, data_buf[data_write_num]);
                write_addr += 2;
        }
        fmc_lock();
}
5.4.2 Flash读取数据函数
Flash读取数据函数如下所示,采用直接寻址的方式,读取双字节数据。
C
uint16_t fmc_read_data_16b(uint32_t write_read_addr)
{
        return *(uint16_t *)write_read_addr;
}
5.4.3 主函数
主函数如下所示,通过该函数实现对flash起始地址为0x08001000的前20个字节擦写以及读取的验证。
C
int main(void)
{
        uint16_t read_num =0;
        uint8_t i_num;
        bsp_led_group_init();   
        fmc_write_data_16b(WRITE_START_ADDR,write_data,10);
        for(read_num=0;read_num<10;read_num++)
        {
                read_data[read_num] = fmc_read_data_16b(WRITE_START_ADDR+read_num*2);
        }
        for(i_num=0;i_num<10;i_num++)
        {
                if(read_data[i_num]!=write_data[i_num])
                {
                        bsp_led_on(&LED0);
                }else{
                        bsp_led_on(&LED1);
                }
        }
        
        while (1)
        {
        }
}
5.5 实验结果
将本实验烧录到红枫派实验板中,运行后可以观察到LED1常亮,表明擦写以及读取实验正常。
本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网,GD32MCU技术交流群:859440462



使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

170

主题

190

帖子

10

粉丝