STM32CubeMX + HAL 库:用FSMC接口与IS62WV51216芯片实现stm32外部SRAM扩展

发布于:2025-08-14 ⋅ 阅读:(19) ⋅ 点赞:(0)

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] 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 猜想

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


网站公告

今日签到

点亮在社区的每一天
去签到