第1章 基础知识
Cortex-M4 处理器使用 32 位地址总线,这意味着它能够直接访问4GB(0x00000000 到 0xFFFFFFFF)的地址空间。ARM 公司为了方便芯片厂商设计和提高软件的可移植性,为这 4GB 空间制定了一个标准的“模板”划分规则,而具体的芯片厂商(如 ST 或 GD32)会在这个模板上填充自己的硬件。总之,单片机是通过地址来访问寄存器、FLASH,ARM寻址范围4GB。
1.1 Cortex-M4 内核的 4GB 寻址空间划分
1.1.1 CODE 区(代码区)
- 地址范围:
0x00000000-0x1FFFFFFF(512MB) - 作用:主要用于存放程序代码和常量数据。
- 实际情况:在 GD32 芯片中,这里通常映射的是片内的 Flash(闪存)。比如您的程序下载进去后,就放在这里运行。
- 特点:支持代码直接运行,且通常有预读缓存机制,以加快 CPU 取指令的速度。
1.1.2 SRAM 区(片内数据区)
- 地址范围:
0x20000000-0x3FFFFFFF(512MB) - 作用:用于存放程序运行时产生的数据,比如全局变量、局部变量、堆栈(Stack)等。
- 实际情况:对应 GD32 芯片内部的物理 SRAM。例如,如果一颗 GD32 芯片有 96KB 的 SRAM,这 96KB 就会映射在
0x20000000开始的区域。 - 特点:读写速度极快,掉电后数据丢失。
1.1.3 片上外设区(Peripheral)
- 地址范围:
0x40000000-0x5FFFFFFF(512MB) - 作用:所有的片上外设寄存器都映射在这个区域。
- 实际情况:当您写代码去控制 GPIO、USART、定时器等时,实际上是在访问这个地址段内的特定地址。
- 特点:每个外设占据一段地址,通过读写这些地址来控制硬件的动作。
1.1.4 EXMC 区(外部存储器控制器)
- 地址范围:
0x60000000-0x9FFFFFFF(1GB) - 作用:这是 GD32 独有的外部存储器扩展接口。
- 实际情况:
- EXMC是 GD32 对 ARM FSMC 的增强版。
- 它允许您通过引脚在芯片外部扩展 SRAM、NAND Flash 或 NOR Flash。
- 图中绿色的大区域就是专门留给这些外部存储设备的。访问这里时,MCU 会自动将其转换为外部设备的读写时序。
- 注意:虽然这里标了 EXMC,但部分高性能 GD32 芯片的 SDRAM 实际上映射在更靠上的位置(如 0xC0000000)。
1.1.5 未使用区(Reserved)
- 地址范围:
0xA0000000-0xDFFFFFFF(896MB) - 作用:保留地址空间,目前没有分配具体用途。
- 注意:不要尝试访问这部分地址,否则可能会触发“HardFault”硬件错误。部分特定型号的 SDRAM 可能会占用 0xC0000000 开始的地址。
1.1.6 Cortex-M4 内核寄存器区
- 地址范围:
0xE0000000-0xFFFFFFFF(512MB) - 作用:包含 CPU 核心自身的控制、调试和中断系统。
- 实际情况:
- NVIC(嵌套向量中断控制器):控制所有中断的优先级和使能。
- SysTick:系统滴答定时器,常用于操作系统的任务调度。
- MPU / FPU:内存保护单元和浮点运算单元的配置寄存器。
1.2 STM32的片上外设FSMC
STM32 中用于扩展外部存储器的片上外设是 FSMC,而 GD32 中对应的外设称为 EXMC。两者功能类似,都可以作为 MCU 访问外部存储器的总线控制器。
EXMC 主要用于驱动外部扩展的静态存储器,例如 SRAM、NOR Flash 和 NAND Flash;部分 GD32 型号的 EXMC 还支持 SDRAM。但是FSMC 本身只支持静态存储器,而 GD32 的 EXMC 在此基础上扩展了对 SDRAM 的支持。
硬件连接方面,外部 SRAM 只需将其地址线、数据线、片选、读/写使能等信号对应连接到 MCU 的 EXMC 引脚即可完成基本连接。配置好 EXMC 后,外部 SRAM 会被映射到 MCU 的地址空间中,程序可以像访问内部 RAM 一样直接读写外部 SRAM。
1.3 FSMC的地址映射
单片机的外扩 RAM 区域(即 FSMC 地址空间)共划分为四个 Bank:Bank1、Bank2、Bank3 和 Bank4。其中,Bank1专门用于访问静态存储器(如 SRAM、NOR Flash 等),它又被细分为四个子区域(Region),每个子区域拥有独立的片选信号(NE1~NE4),可以分别映射一片独立的 SRAM 芯片。
例如,工程中需要扩展 4 片 SRAM 时,可以将它们的地址线、数据线以及读写控制线并联到 FSMC 的对应引脚上,然后分别使用 Bank1 的四个片选信号 NE1~NE4 来选中不同的 SRAM 芯片。这样,四片 SRAM 就会被映射到四个不同的地址区间,程序通过访问不同的地址段即可读写对应的芯片
1.4 STM32的FSMC外设应用
一个Bank分为4个区域(Region),每个区域表示着不同的外设。每个外设通过不同的GPIO来选中。
每一个Bank的4个区域
| STM32端口 | 片选哪个Region区 | 基地址 |
|---|---|---|
| PD7 | NE1 | 0x60000000 |
| PG9 | NE2 | 0x64000000 |
| PG10 | NE3 | 0x68000000 |
| PG12 | NE4 | 0x6C000000 |
1.5 STM32操作片上外设(内存映射)
static BYTE g_buffer[1 * 1024] __attribute__((at(0x6C000000), zero_init));at() : 指定变量存放在绝对地址(外部SRAM起始处)
zero_init: 禁止启动代码在main()之前清零该区域(此时FSMC尚未初始化,
若启动时去写0x6C000000会导致总线错误/硬件错误)
在此,我们只将存储区域开辟了1K的大小。如果你们SRAM芯片的容量大小是1M的,那么这儿就可以写为:1*1024*1024的数组容量。
在程序内部对这块存储区域进行读写操作,就相当于操作外扩的SRAM。或者其他片上外设。
从上图再次验证,我们开辟的存储区域,确实在片上SRAM中。
第2章 工程实践
2.1 头文件
/************************************************************************* * * Copyright (C), 2017-2037, BPG. Co., Ltd. * * 文件名称: EX_SRAM.h * 软件模块: 驱动 * 版 本 号: 1.0 * 生成日期: 2026-10-13 * 作 者: lium * 功 能: 驱动模块头文件 * *************************************************************************/ #ifndef __EX_SRAM__ #define __EX_SRAM__ #ifdef __cplusplus extern "C"{ #endif #include "Drivers.h" void EXSRAMDrvInit(void); void EXSRAMDrvTest(void); #ifdef __cplusplus } #endif #endif /* __EX_SRAM__ */ /************************************************************************* * 改动历史纪录: Revision 1.0, 2026-10-13, lium describe: 初始创建. *************************************************************************/2.2 源文件
/************************************************************************* * * Copyright (C), 1999-2004, . Co., Ltd. * * 文件名: EX_SRAM.c * 所属模块: 驱动 * 版 本: 1.0 * 创建日期: 2026-10-13 * 作 者: lium * 备 注: 片外SRAM驱动(STM32F103 FSMC) * *************************************************************************/ #include "EX_SRAM.h" /*--------------------------------------------------------------------------- * 注意: GD32 与 STM32 的存储区编号不同 * GD32 EXMC Bank0 Region0~3 (NE0~NE3), 0x6C000000 = GD32 的 Region3/NE3 * STM32 FSMC Bank1 Region1~4 (NE1~NE4), 0x6C000000 = STM32 的 Region4/NE4 * 两者片选引脚同为 PG12, 物理连线一致, 只是编号偏移了 1。 *-------------------------------------------------------------------------*/ #define BANK1_REGION4_ADDR 0x6C000000 /* Bank1 Region4(NE4)起始地址 */ #define EX_SRAM_BUFFER_SIZE (1 * 1024 * 1024) /* 外部SRAM容量, 1M字节 */ #define EX_SRAM_BEGIN_ADDR (BANK1_REGION4_ADDR) #define EX_SRAM_END_ADDR (BANK1_REGION4_ADDR + EX_SRAM_BUFFER_SIZE - 1) /* 0x6C0FFFFF */ /* FSMC SRAM句柄 */ static SRAM_HandleTypeDef s_hsram; /*--------------------------------------------------------------------------- * 函数名: EXSRAM_ConfigFSMCGPIO * 功 能: 配置FSMC相关GPIO引脚(地址线/数据线/控制线) * 参 数: 无 * 返 回: 无 *-------------------------------------------------------------------------*/ static void EXSRAM_ConfigFSMCGPIO(void) { GPIO_InitTypeDef gpioInitStruct; /* 使能相关GPIO时钟 */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); /* FSMC引脚统一配置为复用推挽输出, 最高速度 */ gpioInitStruct.Mode = GPIO_MODE_AF_PP; gpioInitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* GPIOF: 地址线 A0~A9 (PF0~PF5, PF12~PF15) */ gpioInitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; HAL_GPIO_Init(GPIOF, &gpioInitStruct); /* GPIOG: 地址线 A10~A15 (PG0~PG5) + 片选 FSMC_NE4 (PG12) */ gpioInitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_12; HAL_GPIO_Init(GPIOG, &gpioInitStruct); /* GPIOD: 地址线 A16~A18 (PD11~PD13) + 数据线 D0~D3/D13~D15 * + 读/写信号 FSMC_NOE(PD4) / FSMC_NWE(PD5) */ gpioInitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; HAL_GPIO_Init(GPIOD, &gpioInitStruct); /* GPIOE: 数据线 D4~D12 (PE7~PE15) + 字节使能 FSMC_NBL0(PE0)/FSMC_NBL1(PE1) */ gpioInitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; HAL_GPIO_Init(GPIOE, &gpioInitStruct); } /*--------------------------------------------------------------------------- * 函数名: EXSRAM_ConfigBank1Region4 * 功 能: 配置并启用FSMC Bank1 Region4(NE4, 0x6C000000) * 参 数: 无 * 返 回: 无 *-------------------------------------------------------------------------*/ static void EXSRAM_ConfigBank1Region4(void) { FSMC_NORSRAM_TimingTypeDef timing; FSMC_NORSRAM_InitTypeDef init; /* 使能FSMC时钟 */ __HAL_RCC_FSMC_CLK_ENABLE(); /* 异步SRAM读写时序(模式A) * 注意: 数值n对应(n+1)个HCLK周期, 需按外部SRAM数据手册与HCLK频率调整 */ timing.AddressSetupTime = 2; /* 地址建立时间 */ timing.AddressHoldTime = 2; /* 地址保持时间 */ timing.DataSetupTime = 4; /* 数据建立时间 */ timing.BusTurnAroundDuration = 0; /* 总线周转时间(仅复用模式使用) */ timing.CLKDivision = 0; /* 同步时钟分频(异步模式无效) */ timing.DataLatency = 0; /* 同步数据延迟(异步模式无效) */ timing.AccessMode = FSMC_ACCESS_MODE_A; /* Bank1 Region4(NE4)配置 */ init.NSBank = FSMC_NORSRAM_BANK4; /* 使用Region4 */ init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE; /* 地址/数据不复用 */ init.MemoryType = FSMC_MEMORY_TYPE_SRAM; /* 存储器类型: SRAM */ init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; /* 数据宽度: 16位 */ init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; /* 禁用突发访问 */ init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; /* 等待信号低电平有效 */ init.WrapMode = FSMC_WRAP_MODE_DISABLE; /* 禁用回绕突发 */ init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS; /* 等待信号时序 */ init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE; /* 使能写操作 */ init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE; /* 禁用等待信号 */ init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE; /* 禁用扩展模式 */ init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE; /* 禁用异步等待 */ init.WriteBurst = FSMC_WRITE_BURST_DISABLE; /* 禁用写突发 */ init.PageSize = FSMC_PAGE_SIZE_NONE; /* 页大小: 无 */ /* FSMC控制寄存器基地址 */ s_hsram.Instance = FSMC_NORSRAM_DEVICE; s_hsram.Extended = FSMC_NORSRAM_EXTENDED_DEVICE; /* 初始化并启用Bank1 Region4 */ s_hsram.Init = init; HAL_SRAM_Init(&s_hsram, &timing, &timing); } /*--------------------------------------------------------------------------- * 函数名: EXSRAMDrvInit * 功 能: 外部SRAM初始化 * 参 数: 无 * 返 回: 无 *-------------------------------------------------------------------------*/ void EXSRAMDrvInit(void) { EXSRAM_ConfigFSMCGPIO(); EXSRAM_ConfigBank1Region4(); } /****************************************************/ /* 测试代码 */ /****************************************************/ #define __DEBUG__ #ifdef __DEBUG__ /* 测试缓冲区大小(字节) */ #define BUFFER_SIZE 1024 /* * 定义一个变量g_buffer;将其放置在外扩的SRAM区域上 * * * 外部SRAM测试缓冲区 * at() : 指定变量存放在绝对地址(外部SRAM起始处) * zero_init: 禁止启动代码在main()之前清零该区域(此时FSMC尚未初始化, * 若启动时去写0x6C000000会导致总线错误/硬件错误) */ static BYTE g_buffer[BUFFER_SIZE] __attribute__((at(EX_SRAM_BEGIN_ADDR), zero_init)); /*--------------------------------------------------------------------------- * 函数名: EXSRAMDrvTest * 功 能: 外部SRAM读写自检, 写入/读回数据均通过串口打印 * 参 数: 无 * 返 回: 无 *-------------------------------------------------------------------------*/ void EXSRAMDrvTest(void) { WORD i; printf("********************************\r\n"); printf("********外部SRAM测试开始********\r\n"); printf("********************************\r\n"); /* 写入测试数据并打印 */ printf("写入数据:\r\n"); for (i = 0; i < BUFFER_SIZE; i++) { g_buffer[i] = (BYTE)i; printf("%02X ", g_buffer[i]); if ((i % 16) == 15) { printf("\r\n"); } } if ((BUFFER_SIZE % 16) != 0) { printf("\r\n"); } /* 读回并打印 */ printf("读回数据:\r\n"); for (i = 0; i < BUFFER_SIZE; i++) { printf("%02X ", g_buffer[i]); if ((i % 16) == 15) { printf("\r\n"); } } if ((BUFFER_SIZE % 16) != 0) { printf("\r\n"); } /* 读回校验 */ for (i = 0; i < BUFFER_SIZE; i++) { if (g_buffer[i] != (BYTE)i) { printf("外部SRAM测试失败: 地址0x%X 写入0x%02X 读回0x%02X\r\n", (unsigned int)(EX_SRAM_BEGIN_ADDR + i), (unsigned int)(i & 0xFF), (unsigned int)g_buffer[i]); return; } } printf("**************************************************\r\n"); printf("********外部SRAM测试通过, 外部SRAM测试结束********\r\n"); printf("**************************************************\r\n"); } #endif /* __DEBUG__ */ /************************************************************************* * 修改历史记录: Revision 1.0, 2026-10-13, lium describe: 初始版本. *************************************************************************/参考文献
FSMC—扩展外部SRAMhttps://doc.embedfire.com/mcu/stm32/f103badao/std/zh/latest/book/FSMC.html