第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操作片上外设(内存映射)
cpp
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 头文件
cpp
/*************************************************************************
*
* 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 源文件
cpp
/*************************************************************************
*
* 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---扩展外部SRAM
https://doc.embedfire.com/mcu/stm32/f103badao/std/zh/latest/book/FSMC.html