1. 概述
1.1 实验目的
本实验旨在通过 STM32CubeMX 配置 FSMC(Flexible Static Memory Controller)外设,并使用 HAL 库驱动 IS62WV51216(512K x 16 位并行 SRAM),实现 MCU 与外部 SRAM 之间的高速数据读写操作。实验演示了外部 SRAM 的引脚连接与地址映射、MCU 向外部 SRAM 写入数据、以及从外部 SRAM 读取数据的全过程,并提供完整的示例代码,帮助读者深入理解 STM32 FSMC 接口的工作机制,以及并行静态存储器的数据访问方法。
通过本实验,读者不仅可以掌握 FSMC 外设的基本配置流程,在stm32中的存储空间地址映射,还能够熟悉 IS62WV51216 的存储结构、访问时序及应用方法。这对于日后在嵌入式系统中扩展高速、大容量的外部存储(如数据缓存、图像缓存、离线数据存储等)提供了技术积累和实践基础,具有良好的工程指导意义。
1.2 FSMC
FSMC(Flexible Static Memory Controller,灵活的静态存储器控制器) 是 STM32 系列 MCU 内置的一种专用外设,用于连接和管理外部静态存储器。它可以与多种存储器类型直接接口,包括 SRAM、ROM、PSRAM、NOR Flash 和 NAND Flash,从而显著扩展 MCU 的存储能力。
在嵌入式系统中,MCU 片上的 FLASH 和 SRAM 资源相对有限,通常只能满足一般控制类应用。但在需要处理大量数据的场景(如图像处理、缓存管理、大型数据存储)中,内部存储往往不够用。这时,FSMC 就发挥了重要作用——它为 MCU 提供了一条高速通道,使外部静态存储器能够像内部存储器一样被直接访问。
1.2.1 工作原理
FSMC 位于 STM32 的 AHB 总线上,它的主要功能是将总线上的数据和指令,转换成目标存储器所需的读写信号和时序控制。具体来说:
总线协议转换:将 AHB 总线的数据传输协议,转换为外部存储器(如 NOR Flash、SRAM)的访问协议。
时序控制:根据不同存储器的特性,自动生成片选信号、读写使能信号以及地址、数据有效时间,确保可靠通信。
透明访问:在 MCU 程序中,外部存储器被映射到特定的地址空间,用户无需手动控制读写时序,只需通过指针访问即可像操作内部 SRAM 一样使用。
FSMC(Flexible Static Memory Controller)会将外部 SRAM 映射到 Cortex-M3 的片上存储器地址空间中。当 CPU 对该地址范围发出读/写指令时,FSMC 会自动产生所需的地址、数据和控制信号(如片选、读写信号等),并按照配置的时序与外部 SRAM 通信。对 CPU 来说,这个过程与访问内部 RAM 没有区别,因此开发者可以直接通过指针或变量访问外部存储器,无需额外的总线驱动代码。
因此,借助 FSMC,访问外部 SRAM 就像访问内部 RAM 一样简单,无需编写复杂的驱动程序来传送地址和数据。只需定义一个变量,并将其存储地址映射到 FSMC 管理的外部 SRAM 区域,即可像操作普通内存一样完成读写。
需要注意的是,FSMC 仅支持静态存储器(Static Memory),不支持动态存储器(如 SDRAM),因为动态存储器需要定时刷新等复杂控制,这超出了 FSMC 的设计范围。
1.2.2 寻址空间1
在 STM32 的 32 位地址总线中,最高位的部分用于标识存储器所属的区域及其子区块。具体来说:
前 3 位(A[31:29])用于表示 Block 号,总共有 8 个 Block(Block0~Block7)。
第 4 位(A[28])用于表示 Bank 号,区分同一 Block 内的 Bank0 和 Bank1。
第 5、6 位(A[27:26])用于表示 NOR/PSRAM 区编号,同一 Bank 下可以有 4 个 NOR/PSRAM 设备。
剩余的 26 位(A[25:0])为 FSMC 的地址总线,用于在选定的 NOR/PSRAM 设备中进行寻址。
这样分配的结果是,高 6 位地址线用于确定 Block、Bank、以及 NOR/PSRAM 设备,而低 26 位地址线才真正用于外部存储器的寻址。
地址线范围 | 位数 | 用途 | 说明 |
---|---|---|---|
A[31:29] | 3 | Block 号 | 选择 8 个存储区域之一(Block0~Block7) |
A[28] | 1 | Bank 号 | 区分 Bank0 / Bank1 |
A[27:26] | 2 | NOR/PSRAM 设备编号 | 选择 Bank 中的 4 个外设之一 |
A[25:0] | 26 | FSMC 地址总线 | 在选定的外部设备内寻址,最大支持 2^26 地址单元 |
1.2.3 寻址空间2
HSDDR[31:29] (Block) | HSDDR[28] (Bank 大区) | HSDDR[27:26] (Bank 子区) | 地址/寻址说明 | 区域大小(字节) | 地址范围 |
---|---|---|---|---|---|
011 (Block3) | 0 (Bank1) | 00 (NOR/PSRAM 子区1) | 常规 memory-mapped,A[25:0] 用于设备内寻址 | 64 MB | 0x6000 0000 0x63FF FFFF |
011 (Block3) | 0 (Bank1) | 01 (NOR/PSRAM 子区2) | 同上 | 64 MB | 0x6400 0000 0x67FF FFFF |
011 (Block3) | 0 (Bank1) | 10 (NOR/PSRAM 子区3) | 同上 | 64 MB | 0x6800 0000 0x6BFF FFFF |
011 (Block3) | 0 (Bank1) | 11 (NOR/PSRAM 子区4) | 常用于 TFT-LCD / 另一个 NOR/PSRAM 区 | 64 MB | 0x6C00 0000 0x6FFF FFFF |
011 (Block3) | 1 (Bank2) | — | NAND 特殊映射:命令/地址/数据三段,逻辑寻址常用 A[17:0] 分段寻址 | 256 MB | 0x7000 0000 0x7FFF FFFF |
100 (Block4) | 0 (Bank3) | — | 同上(NAND) | 256 MB | 0x8000 0000 0x8FFF FFFF |
100 (Block4) | 1 (Bank4) | — | PC-Card / CompactFlash 映射区 | 256 MB | 0x9000 0000 0x9FFF FFFF |
101 (Block5) | — | — | FSMC 控制/时序寄存器、ECCR 等寄存器映射区域(非存储区) | — | — |
这里有个疑问困惑作者很久,在 STM32 的 FSMC 控制器中,Bank2, Bank3 对应的 NAND 区地址空间虽然在理论上分配了高达 256MB 的地址范围,但实际上 FSMC 只有 26 位地址线用于地址映射,最大线性寻址能力为 64MB ,其他地址空间如何访问呢? FSMC的地址线是不是不够啊?
FSMC 通过 26 根地址线最多能直接映射 64MB 的线性地址空间,这适用于 NOR Flash 或 SRAM 这类线性寻址存储器。
但对于 NAND Flash,访问机制是分阶段的:
26 位地址并不是直接映射物理的“字节地址”,而是拆分成多次发送的行地址(Row Address)和列地址(Column Address)。
FSMC 控制器根据 NAND 协议,在不同阶段分别传输这两个地址部分,实现对 NAND 更大容量(如256MB甚至更大)的寻址。
也就是说,FSMC 的 26 位地址线更多是做为“逻辑地址窗口”,结合命令和地址传输流程,实现对 NAND 大容量存储的访问。
1.2.4 引脚说明
引脚名称 | 数量/范围 | 功能说明 | 备注 |
---|---|---|---|
FSMC_A[25:0] | 26 根 | 地址总线,用于选择外部存储器的存储单元地址 | 支持最大 64M×16 位寻址(1GB 字节寻址),实际受器件限制 |
FSMC_D[15:0] | 16 根 | 双向数据总线,用于数据的输入与输出 | 支持 8 位或 16 位模式 |
FSMC_NE[4:1] | 4 根 | 片选信号,低电平有效,选择不同外部存储器 Bank | 可同时挂接多个外部设备 |
FSMC_NOE | 1 根 | 读使能信号,低电平有效 | 控制从外部设备读取数据 |
FSMC_NWE | 1 根 | 写使能信号,低电平有效 | 控制向外部设备写入数据 |
FSMC_NBL[1:0] | 2 根 | 字节屏蔽信号,低电平有效 | NBL0 屏蔽低字节(D[7:0]),NBL1 屏蔽高字节(D[15:8]) |
1.3 IS62WV51216
IS62WV51216 是一款高速、低功耗的静态随机存取存储器(SRAM),容量为512K×16位,约等于1MB字节存储空间。该芯片采用16位宽并行数据总线,支持高速随机读写,无需刷新,访问稳定且响应迅速。工作电压为3.3V,专为低功耗设计,适合电池供电及多种嵌入式应用。凭借高效的数据传输能力和标准并行接口,IS62WV51216 兼容性强,易于与各种MCU和FPGA集成,广泛应用于嵌入式系统的高速缓存存储、通信设备的数据缓冲以及工业控制系统的临时数据存储,特别适合需要快速随机访问且容量适中的存储需求场景。
1.3.1 引脚连线
IS62WV51216 引脚 | 功能说明 | STM32 FSMC 对应引脚 |
---|---|---|
A0–A18 | 地址线,共19根 | FSMC_A0 ~ FSMC_A18 |
DQ0–DQ15 | 16位数据线 | FSMC_D0 ~ FSMC_D15 |
CE# | 芯片使能(片选) | FSMC_NE1(或NE2/NE3/NE4,具体视Bank) |
OE# | 读使能 | FSMC_NOE |
WE# | 写使能 | FSMC_NWE |
VCC | 电源,3.3V | — |
VSS | 地 | — |
1.3.2 寻址原理
由于IS62WV51216的存储单元为16位宽度,即每个地址对应两个字节,而STM32的寻址体系是以字节(8位)为单位,一个地址对应一个字节。为了兼容这种16位宽度的外部存储器,FSMC的AHB总线地址与外部存储器地址并未直接一一对应,而是通过地址位的错位映射来实现。在实际连接中,FSMC会使用AHB总线地址信号的高25位HADDR[25:1]来产生外部存储器的地址信号FSMC_A[24:0]。无论外部存储器是8位还是16位宽,FSMC的地址线FSMC_A始终与外部存储器的地址线A保持一一对应关系,从而保证数据访问的准确性和兼容性。
数据宽度 | AHB地址线连接 | FSMC地址线连接 | 最大访问存储空间(位) | 说明 |
---|---|---|---|---|
8位 | HADDR[25:0] | FSMC_A[25:0] | 64MB × 8位 = 512M 位 | 每个地址对应1字节,地址线全用 |
16位 | HADDR[25:1] | FSMC_A[24:0] | 64M /2 × 16位 = 512M 位 | 地址线错位,HADDR[0]未接FSMC_A,1地址对应2字节 |
在16位数据宽度的外部存储器访问中,FSMC的地址线最低位FSMC_A[0]会直接连接到外部存储器的最低地址线A0,实现地址对齐。但STM32 AHB总线的最低位地址HADDR[0]不会直接传递到FSMC地址线上。相反,HADDR[0]被用作字节使能控制信号的触发条件,用来控制FSMC的低位和高位字节屏蔽线(NBL0 和 NBL1),以实现对16位存储器中单字节的精确访问。这样,FSMC通过地址线和字节屏蔽线的配合,既支持16位数据访问,又兼顾了字节级别的读写需求。
1.3.3 工作模式
FSMC(灵活静态存储控制器)支持 四种异步时序模型,分别为 模式1、模式A、模式B、模式C(部分资料表述为模式A-D,本质一致),主要区别在于控制信号(如读/写使能)的时序生成方式,以适配不同外部存储器的时序要求。
模式 | 说明 | 主要时序参数特点 | 应用场景 |
---|---|---|---|
模式1 | 通用异步读写时序,读写操作独立,适合大多数异步存储器 | 读写周期独立设置,支持读写使能信号(NOE/NWE)分别控制,读写无等待信号 | 适用于SRAM、NOR Flash等常规异步存储器 |
模式A | 简化写周期,写操作时间较短,写时序简化 | 写时序不支持等待信号,写周期相对较短,读操作时序类似模式1 | 适合对写时序要求不高的异步存储器 |
模式B | 读写时序分离,支持写操作等待信号 | 写操作支持等待信号(WAIT),可以延长写周期,读时序与模式1类似 | 适合写操作需要等待信号的存储设备 |
模式C | 读写时序分离,支持读操作等待信号 | 读操作支持等待信号(WAIT),可延长读周期,写时序类似模式1 | 适合读操作对时序有严格要求的存储设备 |
在STM32CubeMX中配置FSMC时,根据存储器类型自动选择基础时序模型:
- SRAM/PSRAM:默认使用模式1(FSMC_BCRx寄存器中MTYP=0x0或0x01),适用于异步读写。
- NOR Flash:默认使用模式A(MTYP=0x10),支持同步突发访问(需配置CLKDIV/DATLAT参数)。
- NAND Flash:需使用独立的NAND控制器(Bank2/3),与NOR/SRAM模式无关。
1.3.4 参数配置
模式1需要配置的主要参数包括:
参数类别 | 参数名称 | 描述 | 示例值 | 建议值 | SRAM推荐值 |
---|---|---|---|---|---|
NOR/PSRAM控制 | Memory type | 存储器类型 | SRAM | 根据实际存储器类型选择(如SRAM、NOR Flash) | SRAM |
Bank | 存储体选择 | Bank1 NOR/PSRAM 3 | 根据硬件连接选择对应的Bank | 根据硬件设计确定 | |
Write operation | 写操作使能 | Enabled | 根据需求使能或禁用写操作 | Enabled(如需写操作) | |
Extended mode | 扩展模式 | Disabled | 通常禁用,除非需要特殊功能 | Disabled | |
NOR/PSRAM定时 | Address setup time (HCLK周期) | 地址建立时间,保证地址线在数据传输前稳定 | 15 | 参考存储器手册最小地址建立时间,按HCLK调整 | 视HCLK频率及芯片要求 |
Data setup time (HCLK周期) | 数据建立时间,保证数据有效持续足够时间 | 255 | 参考存储器手册最小数据保持时间,按HCLK调整 | 视HCLK频率及芯片要求 | |
Bus turn around time (HCLK周期) | 总线切换时间,连续读写时可设小,切换时需调整 | 15 | 根据存储器支持情况及手册建议设置 | SRAM支持快速切换时 |
2. STM32CubeMx配置
2.1 SYS配置
作者使用的JLink调试下载器,所以选择serial wire,如果用STlink也一样。
2.2 RCC配置
2.3 USART配置
2.4 FSMC配置
2.5 project配置
3. keil MDK设置
3.1 targe设置
3.2 debug设置
作者用的JLink故选这个选项,如果读者是STLink就选对应的选项即可
4. VSCode编码
4.1 usart重定向
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.c
* @brief This file provides code for the configuration
* of the USART instances.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
UART_HandleTypeDef huart1;
/* USART1 init function */
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 4, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspDeInit 0 */
/* USER CODE END USART1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_USART1_CLK_DISABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
/* USART1 interrupt Deinit */
HAL_NVIC_DisableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspDeInit 1 */
/* USER CODE END USART1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
int fputc(int ch, FILE * file){ //打印重定向
HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,1000);
return ch;
}
/* USER CODE END 1 */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file usart.h
* @brief This file contains all the function prototypes for
* the usart.c file
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_USART1_UART_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __USART_H__ */
4.2 main文件
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "fsmc.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t v1_1 __attribute__((at(0x68000000)));
uint8_t v1_2 __attribute__((at(0x68000004)));
uint16_t v2_1 = 20;
uint16_t v2_2 = 21;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_FSMC_Init();
/* USER CODE BEGIN 2 */
printf("hello is62wv51216!!!\n");
v1_1 = 10;
v1_2 = 11;
// uint8_t v4 __attribute__((at(0x6807ffff)));
uint8_t v3_1 __attribute__((at(0x68000008)));
v3_1 = 30;
uint8_t v3_2 __attribute__((at(0x6800000A)));
v3_2 = 31;
uint8_t v4_1 = 40;
uint8_t v4_2 = 41;
printf("v1_1 = %d, @%p\n",v1_1,&v1_1);
printf("v1_2 = %d, @%p\n",v1_2,&v1_2);
printf("v2_1 = %d, @%p\n",v2_1,&v2_1);
printf("v2_2 = %d, @%p\n",v2_2,&v2_2);
printf("v3_1 = %d, @%p\n",v3_1,&v3_1);
printf("v3_2 = %d, @%p\n",v3_2,&v3_2);
printf("v4_1 = %d, @%p\n",v4_1,&v4_1);
printf("v4_2 = %d, @%p\n",v4_2,&v4_2);
uint8_t *p = (uint8_t *)0x68000001;
*p = 50;
printf("*p = %d, @%p\n",*p,p);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
5. 实验验证
5.1 实现现象
运行上述代码,串口打印结构如下所示:
[18:13:34.226]收←◆hello is62wv51216!!!
v1_1 = 10, @68000000
v1_2 = 11, @68000004
v2_1 = 20, @20000000
v2_2 = 21, @20000002
v3_1 = 30, @20000498
v3_2 = 31, @2000049c
v4_1 = 40, @200004a0
v4_2 = 41, @200004a4
*p = 50, @68000001
5.2 实验结论与说明
5.2.1 全局变量指定地址空间
在使用外部扩展 SRAM 进行读写时,可以通过 __attribute__((at()))
等方式,将变量固定在指定的内存地址中。
地址必须位于 FSMC 映射的外部 SRAM 地址范围内。例如,本实验的外部 SRAM 芯片 CE 引脚连接到 STM32 的 NE3,因此被映射到 Block3 → Bank1-3(NOR/PSRAM 子区3),起始地址为 0x6800_0000
,结束地址为 0x6BFF_FFFF
,理论映射空间为 64MB(26条地址线)。
不过,本实验的 IS62WV51216 芯片与 FSMC 实际只连接了 19 条地址线(A0~A18),容量为 512K × 16bit = 1MB 字节,所以有效访问地址范围为 0x6800_0000
~ 0x6807_FFFF
(512Kbit × 2Byte = 1MB)。在此范围内定义的全局变量,数据将存储在外部 SRAM 中。
5.2.2 直接定义全局变量(未指定地址)
若不显式指定地址,编译器会将全局变量分配到片内 SRAM(Block1),其地址范围为 0x2000_0000
~ 0x2000_FFFF
,容量为 64KB,本实验结果是0x2000 0000 和0x 2000 0002 均符合这个结论。
5.2.3 局部变量指定外部地址空间
局部变量无法通过 __at
或类似方式固定到外部 SRAM,即使指定了地址,也会被分配到片内 SRAM 中其地址是0x2000 0498 和 0x2000 049c 在 0x2000_0000
~ 0x2000_FFFF范围中
。
5.2.4 直接定义局部变量
未指定地址的局部变量默认分配到片内 SRAM(Block1,64KB)其地址是0x2000 04a0和 0x2000 04a4 在 0x2000_0000
~ 0x2000_FFFF范围中
。
5.2.5 定义指针并访问片外地址
可以通过定义指针变量指向外部 SRAM 地址(例如 (uint16_t*)0x68000000
),然后通过指针读写的方式访问片外存储器。
5.3 猜想
如果在不指定地址的情况下,片内地址空间使用完了,是否会自动把数据存放在片外空间中呢,读者可以自行设计程序进行验证,并把结论在评论区告诉我。