【嵌入式 – GD32开发实战指南(ARM版)】第1部分 基础篇 – 第20章 GD32的存储结构

开发环境:
MDK:Keil 5.30
开发板:GD32F207I-EVAL
MCU:GD32F207IK

20.1 GD32存储结构的工作原理

20.1.1 Cortex-M内核的存储器映射

存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。

如下图,是Cortex-M3存储器映射结构图。

v7iqE9.png

Cortex-M3是32位的内核,因此其PC指针可以指向2^32=4G的地址空间,也就是0x0000_0000 – 0xFFFF_FFFF这一大块空间。根据图中描述,Cortex-M3内核将0x0000_0000 – 0xFFFF_FFFF这块4G大小的空间分成8大块:代码、SRAM、外设、外部RAM、外部设备、专用外设总线-内部、专用外设总线-外部、特定厂商等,因此使用该内核的设计者必须按照这个进行各自芯片的存储器结构设计。

20.1.2 GD32存储器结构

首先,我们对比一下Cortex-M3存储器结构和GD32存储器结构:

v7iTuF.jpg

图中可以很清晰的看到,GD32的存储器结构和Cortex-M3的很相似,不同的是,GD32加入了很多实际的东西,如:Flash、SRAM等。只有加入了这些东西,才能成为一个拥有实际意义的、可以工作的处理芯片-GD32。

GD32的存储器地址空间被划分为大小相等的8块区域,每块区域大小为512MB。

对GD32存储器知识的掌握,实际上就是对Flash和SRAM这两个区域知识的掌握。

20.2 FLASH读写数据

20.2.1 GD32的Flash

GD32的Flash,严格说,应该是Flash模块。该Flash模块包括:Flash主存储区(Main memory)、Flash信息区(Information block),以及Flash存储接口寄存器区(Flash memory interface)。三个组成部分分别在0x0000 0000 – 0xFFFF FFFF不同的区域,GD32F2的Flash结构如下表所示。

v7iHHJ.md.png

【注】信息块存储了boot loader,不能被用户编程或擦除。

GD32的闪存模块由:主存储闪存块、信息块和选项字节块3部分组成

主存储器,该部分用来存放代码和数据常数(如加const类型的数据)。对于主存储闪存容量不多于512KB的GD32F20x_CL,闪存页大小为2KB。对于主存储闪存容量不少于768KB的GD32F20x_CL,使用了两片闪存;前512KB容量在第一片闪存(bank0)中,后续的容量在第二片闪存(bank1)中。其中bank0的闪存页大小为2KB, bank1的闪存页大小为4KB。主存储闪存的每页都可以单独擦除。

信息块,是用来存储GD自带的启动程序,用于串口下载,当B0接3.3V,B1接GND时,运行的就这部分代码,用户选择字节,则一般用于配置保护等功能。

选项字节块,该部分用于控制闪存储器读取等,是整个闪存储器的控制机构。

对于主存储器和信息块的写入有内嵌的闪存编程管理;编程与擦除的高压由内部产生。

在执行闪存写操作时,任何对闪存的读操作都会锁定总线,在写完成后才能正确进行,在进行读取或擦除操作时,不能进行代码或者数据的读取操作。

下面对GD32的存储器进行总结。

v7iIjU.jpg

图中淡蓝色就是你需要知道的。

 Peripherals:外设的存储器映射,对该区域操作,就是对相应的外设进行操作;
 SRAM:运行时临时存放代码的地方;
 Flash:存放代码的地方;
 System Memory:GD32出厂时自带的你只能使用,不能写或擦除;
 Option Bytes:可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);

今后,你的编写代码、程序运行、寄存器设置、ICP、IAP都依靠这些东西。

20.2.2 FLASH读写实现

GD32F20x 系列产品的片上 Flash 起始地址时 0x0800 0000,最大容量可达 3072 KB。读操作为 0 等待,可支持字节、半字(16 bits)和字(32 bits)访问。 Flash 编程以半字(16 bits)或字(32bits)为单位。擦除可以以页(page)为单位,也可以进行全片擦除(information blocks 除外)。

Flash的寄存器有很多,当时GD的工程师已经封装好了,直接用就可以,我这里就不在贴出来了。

Flash操作很简单,我们将数据写入到Flash中,再将其读出来。主要有以下步骤:

1.Flash解锁操作
2.清除Flash标志
3.页擦除
4.读写操作
5.锁定

核心代码如下:

#define FLASH_ADR 0x0807F800

/**
  * @brief  flash test
  * @param  WriteAddr, InData
  * @retval OutData
  */
uint32_t flash_test(uint32_t WriteAddr, uint32_t InData)
{
    uint32_t OutData = 0;

    //解锁
    fmc_unlock();
    //清除标志位
    fmc_flag_clear(FMC_FLAG_BANK0_PGERR | FMC_FLAG_BANK0_WPERR | FMC_FLAG_BANK0_END | FMC_FLAG_BANK1_PGERR | FMC_FLAG_BANK1_WPERR | 
FMC_FLAG_BANK1_END);
    //要擦出页的起始地址
    fmc_page_erase(WriteAddr);
    //写数据
    fmc_word_program(WriteAddr, InData);

    //锁定
    fmc_lock();

    OutData=(*(__IO uint32_t*)(WriteAddr));
    return OutData;
}

程序就不讲了,这里需要注意一个C语言的知识点。

OutData=(*(__IO uint32_t*)(WriteAddr));

这一句很多新手很懵逼,也就是从一个地址中读取数据,多看看指针相关的知识就好理解了。

主函数如下:

/*
    brief      main function
    param[in]  none
    param[out] none
    retval     none
*/
int main(void)
{
    uint32_t InData = 12345678;
    uint32_t OutData;

    // systick init
    sysTick_init();

    // UART1 init
    com_init(COM1);

    // led1 init
    led_init(LED1);

    printf("InData = %d\r\n",InData);

    // flash test
    OutData= flash_test(FLASH_ADR, InData);

    printf("OutData = %d\r\n",OutData);

    if(OutData == InData)
    {
        printf("Flash test success !\r\n");
    }
    else
    {
        printf("Flash test fail !\r\n");
    }
    while(1)
    {
        led_on(LED1); // 亮
        delay_ms(1000);
        led_off(LED1); // 灭 
        delay_ms(1000);
    }
}

20.2.3实验结果

将程序边看一完成后下载到板子中,通过串口助手,按下板子的复位按键可以看到如下现象。

v7i7B4.md.png

20.3 SRAM启动

20.3.1 GD32的启动模式

首先要回顾一下GD32的启动模式,因为启动模式决定了向量表的位置,GD32有三种启动模式:

1)主闪存存储器(Main Flash)启动:从GD32内置的Flash启动(0x0800 0000-0x0807 FFFF),一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以0x08000000 对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x08000000 操作,且都是操作的同一块内存。

2)系统存储器(System Memory)启动:从系统存储器启动(0x1FFFF000 – 0x1FFF F7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。以0x1FFFFFF0对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x1FFFFFF0操作,且都是操作的同一块内存。

3)片上SRAM启动:从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过0x20000000进行操作,与上述两者不同。从SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

用户可以通过设置BOOT0和BOOT1的引脚电平状态,来选择复位后的启动模式。如下图所示:

v7iLNR.md.png

启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。

值得注意的是GD32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。

20.3.2 GD32的SRAM

不同类型的Cortex-M单片机的SRAM大小是不一样的,但是他们的起始地址都是0x2000 0000,终止地址都是0x2000 0000+其固定的容量大小。

SRAM的理解比较简单,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。设备断电后,SRAM中存储的数据就会丢失。

GD32F20x 系列产品的片上 SRAM 起始地址是 0x2000 0000,最大容量可达 384KB,可支持字节、半字(16bits)和字(32bits)访问。 片上 SRAM 被分为 SRAM0、 SRAM1 和 SRAM2 等三个模块,且每个模块都有一个与 AHB 总线矩阵连接的专用接口,这意味着它们可以被同时访问。

模块 容量 地址范围
SRAM1 112KB 0x2000 0000 ~ 0x2001 BFFF
SRAM2 16KB 0x2001 C000 ~ 0x2001 FFFF
SRAM3 256KB 0x2002 0000 ~ 0x2005 FFFF

20.3.3片上SRAM启动实现

在使用片上SRAM调试之前,需要了解为何要使用片上SARM来启动程序,因此GD32的片上Flash的擦写次数有限,若超过最大擦除次数则会损坏内部Flash,因此在平时的程序调试阶段,最好使用SRAM启动。

总的来说,SRAM启动程序有如下用途:

1.调试阶段,需要频繁更新程序,可以SRAM启动,加快调试,减少flash擦写损耗
2.程序SWD/JTAG接口已经配置为普通端口,程序启动后无法程序更新,可在SRAM中启动后,再更新flash程序
3.程序已经开启了读保护,可在SRAM启动后,进行读保护关闭

片上SRAM启动实现的方法很简单,这里以GDF207+Keil5举例,实现方法如下:

1.修改内存分配

修改了内存分配,也就是修改分散加载文件xxx.sct文件。

v7iO41.png

修改后再次生成工程,分散文件也会修改。

v7ij9x.md.png

下面谈谈SRAM参数的分配,首先确定MCU的RAM大小,笔者使用的MCU是GD32F207,因此SRAM大小是256KB,然后还需要根据程序的编译大小来分配SRAM空间。

v7ixgK.md.png

如果想方便点可以直接看MAP文件。

v7iv36.md.png

FLASH和RAM的大小分别如下:
Flash = Code + RO Data + RW Data = 3.88KB(3968)
RAM = RW-data + ZI-data=8.07KB(8264)

因此笔者这里分配空间如下:

IROM1地址为0x2000 0000, 大小是0x10000=65536=64kB
IRAM1地址为0x2000 3000, 大小是0x20000=131072=128kB

当然啦,SRAM空间分配需要根据自己的 MCU来决定。

2.修改debug配置
新建RAM.ini文件,配置如下:

/*----------------------------------------------------------------------------
 * Name:    RAM.ini
 * Purpose: RAM Debug Initialization File
 * Note(s):
 *----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
  Setup()  configure PC & SP for RAM Debug
 *----------------------------------------------------------------------------*/
FUNC void Setup (void) {
  SP = _RDWORD(0x20000000);          // Setup Stack Pointer
  PC = _RDWORD(0x20000004);          // Setup Program Counter
  _WDWORD(0xE000ED08, 0x20000000);   // Setup Vector Table Offset Register
}

FUNC void OnResetExec (void)  {      // executes upon software RESET
  Setup();                           // Setup for Running
}

load %L incremental

Setup();                             // Setup for Running

g, main

然后加载进来。

v7izjO.md.png

RAM.ini文件主要是初始化SP和PC指针。Cortex-M3复位时会从0x00000000和0x00000004的相对位置取出MSP初值以及将复位向量地址赋给PC,在SRAM中的绝对位置就是0x20000000和0x20000004。GD32F207需要手动配置。

v7FpuD.md.png

3.修改下载配置

需要把程序下载到SRAM,修改相应的下载地址。

v7F9De.md.png

4.修改中断向量表指针

由启动文件可知,程序启动首先执行Reset_Handler函数,SRAM启动硬件强制将PC指针赋值为0x200001E0,PC指针加1开始执行启动文件,故Reset_Handler函数的运行地址需为0x200001E1才行,否则程序就会跑飞。

因此需要在中断向量表的末尾添加SPACE 0x96。

v7FCHH.md.png

同时在在main函数中添加设置中断向量表以及系统初化函数。

nvic_vector_table_set(NVIC_VECTTAB_RAM,0x00);
SystemInit();

5.修改启动模式

这里选择SRAM启动,因此BOOT0->1 BOOT1->1。

最后编译下载程序,然后就可以运行程序了。

v7FiEd.md.png


欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎


资源获取方式

1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[GD32开发实战指南]获取资料提取码

Related posts

Leave a Comment