需求描述
使用FSMC扩展外部SRAM。然后把内存数据存储到外部SRAM中。
STM32F1 系列的芯片不支持扩展SDRAM(STM32F429 系列支持),它仅支持使用 FSMC 外设扩展 SRAM。由于引脚数量的限制,只有 STM32F103ZE 或以上型号的芯片才可以扩展外部 SRAM。
SRAM芯片IS62WV51216
SRAM介绍
我们使用的SRAM型号为IS62WV51216,我们就以这个为例来介绍SRAM

功能框图

信号线
SRAM的控制比较简单,只要控制信号线使能访问,从地址线输入要访问的地址,即可从I/O数据线写入或读出数据。
几个重要的时间参数
这几个时间参数比较重要,设置FSMC参数的要用。


硬件电路设计
原理图中的芯片连接

芯片的CS1使能引脚对应着PG10
芯片引脚连接情况

STM32F103ZET6引脚连接情况

数据手册中的服用引脚说明

地址映射范围
64MB:FSMC_Bank1_NORSRAM1:0X6000 0000 ~ 0X63FF FFFF
64MB:FSMC_Bank1_NORSRAM2:0X6400 0000 ~ 0X67FF FFFF
64MB:FSMC_Bank1_NORSRAM3:0X6800 0000 ~ 0X6BFF FFFF
64MB:FSMC_Bank1_NORSRAM4:0X6C00 0000 ~ 0X6FFF FFFF
NE3对应的地址范围就是0x6800 0000 ~ 0x6BFF FFFF
SRAM软件设计(寄存器)
存储块使能
代码
/* 4.1 存储器块使能 */
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
寄存器


存储器类型
代码
/* 4.2 设置存储器类型 00: SRAM ROM 01:PSRAM 10:NOR闪存*/
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
寄存器


内存访问使能
代码
/* 4.3 闪存访问使能: 禁止访问闪存 */
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
寄存器

地址数据总线复用
代码
/* 4.4 地址和数据总线复用: 不复用 */
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
寄存器


数据总线宽度
代码
/* 4.5 数据总线宽度 00:8位 01:16位*/
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
寄存器


写使能
代码
/* 4.6 写使能 */
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
寄存器


实现产生
代码
/* 5. 配置SRAM的时序参数 BTCR[5] 访问bank1 3区对应的BTR寄存器 */
/* 5.1 地址建立时间(时钟周期) 0:表示一个时钟周期。 对同步读写来说无效,永远是1个时钟周期*/
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
/* 5.2 地址保持时间(时钟周期) 对同步读写来说无效,永远是1个时钟周期*/
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDHLD;
/* 5.3 数据保持时间(时钟周期)数据在总线上的停留时间 SRAM要求不能低于50ns */
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST; /* 对应的位置位0 */
FSMC_Bank1->BTCR[5] |= (71 << 8); /* 设置为1个us. 72个时钟周期 */
寄存器




在访问FSMC的寄存器的时stm32f10x.h并没有给所有的寄存器起名字,而是用了一个数组存储了所有的寄存器。
每个数组长度为8,表示一共存储了8个寄存器。
typedef struct
{
__IO uint32_t BTCR[8];
} FSMC_Bank1_TypeDef;
这个8个寄存器是按照下面的顺序来存储的。

比如,你要找Bank1的3区的寄存器:FSMC_BCR3和FSMC_BTR3。它们分别对应BTCR[4]和BTCR[5]。
寄存器实现
fsmc.h
#ifndef __FSMC_H
#define __FSMC_H
#include "stm32f10x.h"
void FSMC_Init(void);
#endif
fsmc.c
看起来很多,其实大部分代码是用来设置引脚输入输出模式的。
#include "fsmc.h"
void FSMC_GPIO_Init(void);
void FSMC_Init(void)
{
// 1. 开启时钟
RCC->AHBENR |= RCC_AHBENR_FSMCEN;
RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN |
RCC_APB2ENR_IOPEEN |
RCC_APB2ENR_IOPFEN |
RCC_APB2ENR_IOPGEN);
// 2. 配置GPIO工作模式
FSMC_GPIO_Init();
// 3. FSMC 配置 BCR3 - BTCR[4]
// 3.1 存储块使能
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
// 3.2 设置存储器类型 00 - SRAM/ROM
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
// 3.3 禁止访问闪存
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
// 3.4 数据宽度:16位 - 01
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
// 3.5 地址数据线不复用
FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
// 3.6 开启写使能
FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
// 4. FSMC 配置 BTR3 - BTCR[5]
// 4.1 地址建立时间 ADDSET
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
// 4.2 数据保持时间 DATAST
FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST;
FSMC_Bank1->BTCR[5] |= (71 << 8);
}
void FSMC_GPIO_Init(void)
{
// 1. 地址线 A0~A18,复用推挽输出,CNF-10,MODE-11
// 1.1 MODE = 11
GPIOF->CRL |= (GPIO_CRL_MODE0 |
GPIO_CRL_MODE1 |
GPIO_CRL_MODE2 |
GPIO_CRL_MODE3 |
GPIO_CRL_MODE4 |
GPIO_CRL_MODE5);
GPIOF->CRH |= (GPIO_CRH_MODE12 |
GPIO_CRH_MODE13 |
GPIO_CRH_MODE14 |
GPIO_CRH_MODE15);
GPIOG->CRL |= (GPIO_CRL_MODE0 |
GPIO_CRL_MODE1 |
GPIO_CRL_MODE2 |
GPIO_CRL_MODE3 |
GPIO_CRL_MODE4 |
GPIO_CRL_MODE5);
GPIOD->CRH |= (GPIO_CRH_MODE11 |
GPIO_CRH_MODE12 |
GPIO_CRH_MODE13);
// 1.2 CNF = 10
GPIOF->CRL |= (GPIO_CRL_CNF0_1 |
GPIO_CRL_CNF1_1 |
GPIO_CRL_CNF2_1 |
GPIO_CRL_CNF3_1 |
GPIO_CRL_CNF4_1 |
GPIO_CRL_CNF5_1);
GPIOF->CRL &= ~(GPIO_CRL_CNF0_0 |
GPIO_CRL_CNF1_0 |
GPIO_CRL_CNF2_0 |
GPIO_CRL_CNF3_0 |
GPIO_CRL_CNF4_0 |
GPIO_CRL_CNF5_0);
GPIOF->CRH |= (GPIO_CRH_CNF12_1 |
GPIO_CRH_CNF13_1 |
GPIO_CRH_CNF14_1 |
GPIO_CRH_CNF15_1);
GPIOF->CRH &= ~(GPIO_CRH_CNF12_0 |
GPIO_CRH_CNF13_0 |
GPIO_CRH_CNF14_0 |
GPIO_CRH_CNF15_0);
GPIOG->CRL |= (GPIO_CRL_CNF0_1 |
GPIO_CRL_CNF1_1 |
GPIO_CRL_CNF2_1 |
GPIO_CRL_CNF3_1 |
GPIO_CRL_CNF4_1 |
GPIO_CRL_CNF5_1);
GPIOG->CRL &= ~(GPIO_CRL_CNF0_0 |
GPIO_CRL_CNF1_0 |
GPIO_CRL_CNF2_0 |
GPIO_CRL_CNF3_0 |
GPIO_CRL_CNF4_0 |
GPIO_CRL_CNF5_0);
GPIOD->CRH |= (GPIO_CRH_CNF11_1 |
GPIO_CRH_CNF12_1 |
GPIO_CRH_CNF13_1);
GPIOD->CRH &= ~(GPIO_CRH_CNF11_0 |
GPIO_CRH_CNF12_0 |
GPIO_CRH_CNF13_0);
// 2. 数据线,复用推挽输出,CNF-10, MODE-11
/* =============MODE=============== */
GPIOD->CRL |= (GPIO_CRL_MODE0 |
GPIO_CRL_MODE1);
GPIOD->CRH |= (GPIO_CRH_MODE8 |
GPIO_CRH_MODE9 |
GPIO_CRH_MODE10 |
GPIO_CRH_MODE14 |
GPIO_CRH_MODE15);
GPIOE->CRL |= (GPIO_CRL_MODE7);
GPIOE->CRH |= (GPIO_CRH_MODE8 |
GPIO_CRH_MODE9 |
GPIO_CRH_MODE10 |
GPIO_CRH_MODE11 |
GPIO_CRH_MODE12 |
GPIO_CRH_MODE13 |
GPIO_CRH_MODE14 |
GPIO_CRH_MODE15);
/* =============CNF=============== */
GPIOD->CRL |= (GPIO_CRL_CNF0_1 |
GPIO_CRL_CNF1_1);
GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 |
GPIO_CRL_CNF1_0);
GPIOD->CRH |= (GPIO_CRH_CNF8_1 |
GPIO_CRH_CNF9_1 |
GPIO_CRH_CNF10_1 |
GPIO_CRH_CNF14_1 |
GPIO_CRH_CNF15_1);
GPIOD->CRH &= ~(GPIO_CRH_CNF8_0 |
GPIO_CRH_CNF9_0 |
GPIO_CRH_CNF10_0 |
GPIO_CRH_CNF14_0 |
GPIO_CRH_CNF15_0);
GPIOE->CRL |= (GPIO_CRL_CNF7_1);
GPIOE->CRL &= ~(GPIO_CRL_CNF7_0);
GPIOE->CRH |= (GPIO_CRH_CNF8_1 |
GPIO_CRH_CNF9_1 |
GPIO_CRH_CNF10_1 |
GPIO_CRH_CNF11_1 |
GPIO_CRH_CNF12_1 |
GPIO_CRH_CNF13_1 |
GPIO_CRH_CNF14_1 |
GPIO_CRH_CNF15_1);
GPIOE->CRH &= ~(GPIO_CRH_CNF8_0 |
GPIO_CRH_CNF9_0 |
GPIO_CRH_CNF10_0 |
GPIO_CRH_CNF11_0 |
GPIO_CRH_CNF12_0 |
GPIO_CRH_CNF13_0 |
GPIO_CRH_CNF14_0 |
GPIO_CRH_CNF15_0);
// 3. 控制信号,复用推挽输出,CNF-10, MODE-11
GPIOD->CRL |= (GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
GPIOD->CRL |= (GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
GPIOD->CRL &= ~(GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
GPIOE->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
GPIOE->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
GPIOE->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
GPIOG->CRH |= GPIO_CRH_MODE10;
GPIOG->CRH |= GPIO_CRH_CNF10_1;
GPIOG->CRH &= ~GPIO_CRH_CNF10_0;
}
main.c
#include "usart.h"
#include "fsmc.h"
// 方式一:使用关键字 __attribute__ at 指定全局变量的地址
uint8_t v1 __attribute__((at(0x68000000)));
uint8_t v2 __attribute__((at(0x68000004)));
uint16_t v3 = 30;
int main(void)
{
// 1. 初始化
USART_Init();
FSMC_Init();
printf("FSMC实验开始...\n");
// 定义各种全局和局部变量,打印值和地址进行对比
v1 = 10;
v2 = 20;
uint8_t v4 __attribute__((at(0x68000008)));
v4 = 40;
uint8_t v5 = 50;
printf("v1 = %d, @%p\n", v1, &v1);
printf("v2 = %d, @%p\n", v2, &v2);
printf("v3 = %d, @%p\n", v3, &v3);
printf("v4 = %d, @%p\n", v4, &v4);
printf("v5 = %d, @%p\n", v5, &v5);
// 方法二:定义指针,用地址赋值
uint8_t * p = (uint8_t *)0x68000001;
*p = 100;
printf("*p = %d, @%p\n", *p, p);
while (1)
{
}
}
HAL库实现

main.c
/* USER CODE BEGIN PV */
// 方式一:使用关键字 __attribute__ at 指定全局变量的地址
uint8_t v1 __attribute__((at(0x68000000)));
uint8_t v2 __attribute__((at(0x68000004)));
uint16_t v3 = 30;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
printf("FSMC实验开始...\n");
// 定义各种全局和局部变量,打印值和地址进行对比
v1 = 10;
v2 = 20;
uint8_t v4 __attribute__((at(0x68000008)));
v4 = 40;
uint8_t v5 = 50;
printf("v1 = %d, @%p\n", v1, &v1);
printf("v2 = %d, @%p\n", v2, &v2);
printf("v3 = %d, @%p\n", v3, &v3);
printf("v4 = %d, @%p\n", v4, &v4);
printf("v5 = %d, @%p\n", v5, &v5);
// 方法二:定义指针,用地址赋值
uint8_t *p = (uint8_t *)0x68000001;
*p = 100;
printf("*p = %d, @%p\n", *p, p);
/* USER CODE END 2 */