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 总线上,它的主要功能是将总线上的数据和指令,转换成目标存储器所需的读写信号和时序控制。具体来说:

  1. 总线协议转换:将 AHB 总线的数据传输协议,转换为外部存储器(如 NOR Flash、SRAM)的访问协议。

  2. 时序控制:根据不同存储器的特性,自动生成片选信号、读写使能信号以及地址、数据有效时间,确保可靠通信。

  3. 透明访问:在 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]3Block 号选择 8 个存储区域之一(Block0~Block7)
A[28]1Bank 号区分 Bank0 / Bank1
A[27:26]2NOR/PSRAM 设备编号选择 Bank 中的 4 个外设之一
A[25:0]26FSMC 地址总线在选定的外部设备内寻址,最大支持 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_NOE1 根读使能信号,低电平有效控制从外部设备读取数据
FSMC_NWE1 根写使能信号,低电平有效控制向外部设备写入数据
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–DQ1516 位数据总线FSMC_D0 ~ FSMC_D15
CE#芯片使能(片选,低有效)FSMC_NE1(或 NE2/NE3/NE4,取决于所用 Bank)
OE#读使能(低有效)FSMC_NOE
WE#写使能(低有效)FSMC_NWE
LB#低字节使能(DQ0–DQ7 有效)FSMC_NBL0
UB#高字节使能(DQ8–DQ15 有效)FSMC_NBL1
VCC电源,3.3V
VSS
NC空脚,无功能悬空即可

1.3.2 地址对齐机制

FSMC 16 位数据宽度访问原理及地址对齐机制

        由于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字节

        在 STM32 与 16 位外部存储器(如 SRAM、显存或 LCD 控制器)交互时,FSMC 的地址线与 AHB 总线地址之间存在一个关键的对齐机制,这是保证数据正确读写的核心原理。

1.3.2.1. 地址的差异

总线地址与 FSMC 地址的差异

  • STM32 的 AHB 总线是字节寻址的,也就是说每个地址单元对应 1 个字节(8 位)。

  • FSMC 外部总线是 16 位数据总线,一次传输 2 个字节。

  • 如果直接把 HADDR[0](最低位字节地址)映射到 FSMC 的地址线上,会导致访问外部存储器时出现偏移错误,因为 FSMC 每次操作的是 16 位对齐单元

1.3.2.2. 地址对齐与右移

为了解决这个问题,FSMC 内部会把 AHB 总线地址 HADDR 右移一位(即舍弃最低位 HADDR[0])后作为 FSMC 的地址线输出:

HADDR[25:1] = FSMC_A[24:0]

  • 这样做的目的:

    1. 保证 16 位数据总线每次访问 2 个字节时,外部存储器地址总是偶数对齐(0x0000、0x0002、0x0004 …)。

    2. 避免因为字节寻址而产生的读写数据错位问题。

  • 低位字节的选择由 FSMC 内部生成的 NBL0/NBL1(Byte Lane)信号 完成,而不是通过地址线直接选择。

1.3.2.3. 字节屏蔽线

字节屏蔽线(NBL0/NBL1)的作用

  • HADDR[0] 用于生成 字节使能信号

    • HADDR[0] = 0 → 访问低字节(NBL0 有效)

    • HADDR[0] = 1 → 访问高字节(NBL1 有效)

  • 这样,FSMC 可以在保持 16 位总线对齐访问 的同时,实现对单字节的精确操作。

1.3.2.4. 总结
  • 右移地址:解决了 AHB 字节寻址与 FSMC 16 位总线对齐的冲突。

  • 字节屏蔽线 NBL0/NBL1:保证单字节操作的可行性。

  • 通过这两者的配合,STM32 在访问 16 位外部存储器时既高效又灵活,同时避免数据错位。

1.3.3 工作模式

        FSMC(灵活静态存储控制器)支持 四种异步时序模型,分别为 模式1、模式A、模式B、模式C(部分资料表述为模式A-D,本质一致),主要区别在于控制信号(如读/写使能)的时序生成方式,以适配不同外部存储器的时序要求。

模式说明主要时序参数特点应用场景
模式1通用异步读写时序,读写操作独立,适合大多数异步存储器读写周期独立设置,支持读写使能信号(NOE/NWE)分别控制,读写无等待信号适用于SRAM、NOR Flash、LCD等常规异步存储器
模式A简化写周期,写操作时间较短,写时序简化写时序不支持等待信号,写周期相对较短,读操作时序类似模式1适合对写时序要求不高的异步存储器
模式B读写时序分离,支持写操作等待信号写操作支持等待信号(WAIT),可以延长写周期,读时序与模式1类似适合写操作需要等待信号的存储设备
模式C读写时序分离,支持读操作等待信号读操作支持等待信号(WAIT),可延长读周期,写时序类似模式1适合读操作对时序有严格要求的存储设备

        在STM32CubeMX中配置FSMC时,根据存储器类型自动选择基础时序模型:

  • SRAM/PSRAM/LCD:默认使用模式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 猜想

如果在不指定地址的情况下,片内地址空间使用完了,是否会自动把数据存放在片外空间中呢,读者可以自行设计程序进行验证,并把结论在评论区告诉我。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐