一.概述
一般单片机有片内的RAM,但都不多,比如:STM32F407ZGT6 自带了 192K 字节的 RAM,对一般应用来说,已经足够了,不过在一些对内存要求高的场合,比如做华丽效果的 GUI,处理大量数据的应用等,STM32 自带的这些内存就可能不太够用了。好在嵌入式方案提供了扩展芯片 RAM 的方法,使用 SRAM 芯片,并驱动这个外部 SRAM 提供程序需要的一部分RAM 空间即可。
1.存储器的分类
2.嵌入式程序主要对应ROM 和 RAM这两种存储器。对于 RAM,目前常见的是 SRAM 和 DRAM,它们因工作方式不同而得名,它们主要有以下的特性:
二.SRAM芯片与国产替代
1.IS62WV51216 是 ISSI(Integrated Silicon Solution, Inc)公司生产的一颗 16 位宽 512K(512*16,即 1M 字节)容量的 CMOS 静态内存芯片。该芯片具有如下几个特点:
高速。具有 45ns/55ns 访问速度。
低功耗。
TTL 电平兼容。
全静态操作。不需要刷新和时钟电路。
三态输出。
字节控制功能。支持高/低字节控制。
2.国产替代一直是国内嵌入式领域的一个话题,国产替代的优势一般是货源稳定,售价更低,也有专门研发对某款芯片作 Pin to Pin 兼容的厂家,使用时无需修改 PCB,直接更换元件即可,十分方便。
一款替代 IS62WV51216 的芯片是 XM8A5121,它与IS62WV51216 一样采用 TSOP44 封装,引脚顺序也与前者完全一致。
XM8A51216 是星忆存储生产的一颗 16 位宽 512K(512*16,即 1M 位)容量的 CMOS 静态内存芯片。采用异步 SRAM 接口并结合独有的 XRAM 免刷新专利技术,在大容量、高性能和高可靠及品质方面完全可以匹敌同类 SRAM,具有较低功耗和低成本优势,可以与市面上同类型 SRAM 产品硬件完全兼容,并且满足各种应用系统对高性能和低成本的要求,XM8A51216也可以当做异步 SRAM 使用,该芯片具有如下几个特点:
高速,具有最高访问速度 10/12ns。
低功耗。
TTL 电平兼容。
全静态操作。不需要刷新和时钟电路。
三态输出。
字节控制功能。支持高/低字节控制。
该芯片与 IS62WV51216 引脚和完全兼容,控制时序也类似,大家可以方便地直接替换。
三.FSMC介绍
FSMC 接口可以通过地址信号,快速地找到存储器对应存储块上的数据。STM32F407 的 FSMC接口支持包括 SRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。F4 系列的大容量型号,且引脚数目在 100 脚及以上的 STM32F407 芯片都带有 FSMC 接口, STM32F407ZGT6是带有 FSMC 接口的。
四.使用 SRAM 的配置步骤:
1)使能 FSMC 时钟,并配置 FSMC 相关的 IO 及其时钟使能。
要使用 FSMC,当然首先得开启其时钟。然后需要把 FSMC_D0~15,FSMCA0~18 等相关
IO 口,全部配置为复用输出,并使能各 IO 组的时钟。
使能 FSMC 时钟的方法前面 LCD 实验已经讲解过,方法为:
__HAL_RCC_FSMC_CLK_ENABLE();
配置 IO 口为复用输出的关键行代码为:
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
2)设置 FSMC BANK1 区域 3 的相关寄存器。
此部分包括设置区域 3 的存储器的工作模式、位宽和读写时序等。本章我们使用模式 A、
16 位宽,读写共用一个时序寄存器。
这个是通过调用函数 HAL_SRAM_Init 来实现的,函数原型为:
HAL_StatusTypeDef HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,
FSMC_NORSRAM_TimingTypeDef *Timing, FSMC_NORSRAM_TimingTypeDef *ExtTiming)
通过以上几个步骤,我们就完成了 FSMC 的配置,初始化 FSMC 后就可以访问 SRAM 芯
片时行读写操作了,这里还需要注意,因为我们使用的是 BANK1 的区域 3,所以
HADDR[27:26]=10,故外部内存的首地址为 0X68000000。
五.SRAM 驱动
核心代码:
#define SRAM_WR_GPIO_PORT GPIOD
#define SRAM_WR_GPIO_PIN GPIO_PIN_5
#define SRAM_WR_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE();}while(0)
#define SRAM_RD_GPIO_PORT GPIOD
#define SRAM_RD_GPIO_PIN GPIO_PIN_4
#define SRAM_RD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0)
/* SRAM_CS(需要根据 SRAM_FSMC_NEX 设置正确的 IO 口) 引脚 定义 */
#define SRAM_CS_GPIO_PORT GPIOG
#define SRAM_CS_GPIO_PIN GPIO_PIN_10
#define SRAM_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOG_CLK_ENABLE();}while(0)
根据 STM32F4 参考手册,SRAM 可以选择 FSMC 对应的存储块 1 上的 4 个区域之一作为
访问地址,它上面有四块相互独立的 64M 的连续寻址空间,为了能灵活根据不同的计算出使用
的地址空间,我们定义了以下的宏:
/* FSMC 相关参数 定义
* 注意: 我们默认是通过 FSMC 块 3 来连接 SRAM, 块 1 有 4 个片选: FSMC_NE1~4
*
* 修改 SRAM_FSMC_NEX, 对应的 SRAM_CS_GPIO 相关设置也得改
*/
#define SRAM_FSMC_NEX 3 /* 使用 FSMC_NE3 接 SRAM_CS,取值范围只能是: 1~4 */
/*****************************************************************/
/* SRAM 基地址, 根据 SRAM_FSMC_NEX 的设置来决定基址地址
* 我们一般使用 FSMC 的块 1(BANK1)来驱动 SRAM, 块 1 地址范围总大小为 256MB,均分成 4 块:
* 存储块 1(FSMC_NE1)地址范围: 0X6000 0000 ~ 0X63FF FFFF
* 存储块 2(FSMC_NE2)地址范围: 0X6400 0000 ~ 0X67FF FFFF
* 存储块 3(FSMC_NE3)地址范围: 0X6800 0000 ~ 0X6BFF FFFF
* 存储块 4(FSMC_NE4)地址范围: 0X6C00 0000 ~ 0X6FFF FFFF
*/
#define SRAM_BASE_ADDR (0X60000000 + (0X4000000 * (SRAM_FSMC_NEX - 1)))
上述定义 SRAM_FSMC_NEX 的值为 3,即使用 FSMC 存储块 1 的第 3 个地址范围,上面
的 SRAM_BASE_ADDR 则根据我们使用的存储块计算出 SRAM 空间的首地址,存储块 3 对应
的是 0X68000000 ~ 0X6BFFFFFF 的地址空间。
sram 的初始化函数我们编写如下:
/**
* @brief 初始化 外部 SRAM
* @param 无
* @retval 无
*/
void sram_init(void)
{
GPIO_InitTypeDef GPIO_Initure;
FSMC_NORSRAM_TimingTypeDef fsmc_readwritetim;
SRAM_CS_GPIO_CLK_ENABLE(); /* SRAM_CS 脚时钟使能 */
SRAM_WR_GPIO_CLK_ENABLE(); /* SRAM_WR 脚时钟使能 */
SRAM_RD_GPIO_CLK_ENABLE(); /* SRAM_RD 脚时钟使能 */
__HAL_RCC_FSMC_CLK_ENABLE(); /* 使能 FSMC 时钟 */
761
STM32F407 开发指南
正点原子探索者 STM32F407 开发板教程
__HAL_RCC_GPIOD_CLK_ENABLE(); /* 使能 GPIOD 时钟 */
__HAL_RCC_GPIOE_CLK_ENABLE(); /* 使能 GPIOE 时钟 */
__HAL_RCC_GPIOF_CLK_ENABLE(); /* 使能 GPIOF 时钟 */
__HAL_RCC_GPIOG_CLK_ENABLE(); /* 使能 GPIOG 时钟 */
GPIO_Initure.Pin = SRAM_CS_GPIO_PIN;
GPIO_Initure.Mode = GPIO_MODE_AF_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SRAM_CS_GPIO_PORT, &GPIO_Initure); /* SRAM_CS 引脚模式设置 */
GPIO_Initure.Pin = SRAM_WR_GPIO_PIN;
HAL_GPIO_Init(SRAM_WR_GPIO_PORT, &GPIO_Initure); /* SRAM_WR 引脚模式设置 */
GPIO_Initure.Pin = SRAM_RD_GPIO_PIN;
HAL_GPIO_Init(SRAM_RD_GPIO_PORT, &GPIO_Initure); /* SRAM_CS 引脚模式设置 */
/* PD0,1,4,5,8~15 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 |
GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13
GPIO_PIN_14 | GPIO_PIN_15;
GPIO_Initure.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
GPIO_Initure.Pull = GPIO_PULLUP; /* 上拉 */
GPIO_Initure.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOD, &GPIO_Initure);
/* PE0,1,7~15 */
GPIO_Initure.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, &GPIO_Initure);
/* PF0~5,12~15 */
GPIO_Initure.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, &GPIO_Initure);
/* PG0~5,10 */
GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 |
GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
HAL_GPIO_Init(GPIOG, &GPIO_Initure);
g_sram_handler.Instance = FSMC_NORSRAM_DEVICE;
g_sram_handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
g_sram_handler.Init.NSBank = (SRAM_FSMC_NEX == 1) ? FSMC_NORSRAM_BANK1 : \
(SRAM_FSMC_NEX == 2) ? FSMC_NORSRAM_BANK2:\
(SRAM_FSMC_NEX == 3) ? FSMC_NORSRAM_BANK3:\
FSMC_NORSRAM_BANK4; /* 根据配置选择 FSMC_NE1~4 */
/* 地址/数据线不复用 */
g_sram_handler.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
g_sram_handler.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM; /* SRAM */
/* 16 位数据宽度 */
g_sram_handler.Init.MemoryDataWidth = SMC_NORSRAM_MEM_BUS_WIDTH_16;
/* 是否使能突发访问,仅对同步突发存储器有效,此处未用到 */
g_sram_handler.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
/* 等待信号的极性,仅在突发模式访问下有用 */
g_sram_handler.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
/* 存储器是在等待周期之前的一个时钟周期还是等待周期期间使能 NWAIT */
g_sram_handler.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
/* 存储器写使能 */
g_sram_handler.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
/* 等待使能位,此处未用到 */
g_sram_handler.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
/* 读写使用相同的时序 */
g_sram_handler.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
/* 是否使能同步传输模式下的等待信号,此处未用到 */
g_sram_handler.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
g_sram_handler.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE; /* 禁止突发写 */
/* FSMC 读时序控制寄存器 */
/* 地址建立时间(ADDSET)为 2 个 HCLK 1/168M=6ns*2=12ns */
fsmc_readwritetim.AddressSetupTime = 0x02;
fsmc_readwritetim.AddressHoldTime = 0x00;/* 地址保持时间(ADDHLD)模式 A 未用到 */
fsmc_readwritetim.DataSetupTime = 0x08; /* 数据保存时间为 8 个 HCLK=6ns*8=48ns */
fsmc_readwritetim.BusTurnAroundDuration = 0X00;
fsmc_readwritetim.AccessMode = FSMC_ACCESS_MODE_A; /* 模式 A */
HAL_SRAM_Init(&g_sram_handler,&fsmc_readwritetim,&fsmc_readwritetim);
}
初始化成功后,FSMC 控制器就能根据扩展的地址线访问 SRAM 的数据,于是我们可以直
接根据地址指针来访问 SRAM,我们定义 SRAM 的写函数如下;
void sram_write(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
for (; datalen != 0; datalen--)
{
*(volatile uint8_t *)(SRAM_BASE_ADDR + addr) = *pbuf;
addr++;
pbuf++;
}
}
同样地,也是利用地址,可以构造出一个 SRAM 的连续读函数:
void sram_read(uint8_t *pbuf, uint32_t addr, uint32_t datalen)
{
for (; datalen != 0; datalen--)
{
*pbuf++ = *(volatile uint8_t *)(SRAM_BASE_ADDR + addr);
addr++;
}
}