细说STM32F407单片机FSMC连接外部SRAM的方法及HAL驱动

目录

一、FSMC连接外部SRAM的原理

[1、 FSMC控制区域的划分](#1、 FSMC控制区域的划分)

2、SRAM芯片与MCU的连接

二、访问外部SRAM的HAL驱动程序

1、外部SRAM初始化与控制

2、外部SRAM读写函数

3、直接通过指针访问外部SRAM

4、DMA方式读写外部SRAM


本文介绍STM32F407单片机FSMC连接外部SRAM及以轮询方式读写外部SRAM的方法。本文将继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。

FSMC的Bank1除能连接TFT LC外,还可以用于连接外部的SRAM、NOR FLASH、PSRAM等存储器。STM32F407ZG有192KB的SRAM存储器,一般的应用程序足够用了,但是在使用GUI等需要大量内存的功能时,就需要扩展SRAM了。

一、FSMC连接外部SRAM的原理

1、 FSMC控制区域的划分

FSMC控制器的存储区分为4个区(Bank),每个区256MB。其中,Bank1可以用于连接SRAM、NOR FLASH、PSRAM,还可以连接TFT LCD。Bank1的地址范围是0x60000000~0x6FFFFFFF。Bank1又分为4个子区,每个子区寻址空间是64MB,占用26位地址线。4个子区的地址范围分别如下。

  • Bank 1子区1:0x60000000~0x63FFFFFF。
  • Bank 1子区2:0x64000000~0x67FFFFFF。
  • Bank 1子区3:0x68000000~0x6BFFFFFF(开发板上用于外扩SRAM)。
  • Bank 1子区4:0x6C000000~0x6FFFFFFF(开发板上用于连接TFTLCD)。

每个子区有一个专用的片选信号。本文将使用Bank 1子区3连接一个1MB的SRAM芯片,为系统扩展内存。

2、SRAM芯片与MCU的连接

在开发板上有一个SRAM芯片IS61LV25616AL,这是一个16位宽256K容量(256K×16位,即512KB)的静态内存芯片。在开发板上它与MCU的连接电路如图所示。芯片几个主要管脚的功能,以及与MCU的连接原理如下。

  • A0至A18是19根地址线,连接FSMC的19根地址线,即FSMC_A0至FSMC_A18。
  • I/O0至I/O15是16位数据线,连接FSMC的FSMC_D0至FSMC_D15数据线。
  • CE是芯片的片选信号,连接MCU的FSMC_NE3(PG10引脚),也就是Bank 1子区3的片选信号。
  • OE是输出使能信号,连接MCU的FSMC_NOE(PD4引脚),是读数据时的使能信号。
  • WE是写使能信号,连接MCU的FSMC_NWE(PD5引脚),是写数据使能信号。
  • UB是高字节使能信号,连接MCU的FSMC_NBL1(PE1引脚)。
  • LB是低字节使能信号,连接MCU的FSMC_NBL0(PE0引脚)。

通过UB和LB的控制可以只读取一个地址的高字节(I/O15~I/O8)或低字节(I/O7~I/O0)数据,或读取16位数据。IS61LV25616AL有19根地址线,能表示的地址范围是512K,偏移地址范围是0x00000~0x7FFFF。但是IS61LV25616AL的数据宽度是16位,实际存储容量是512KB,按字节寻址范围是512K,偏移地址范围是0x00000~0x3FFFF。因为Bank 1子区3的起始地址是0x68000000,所以IS61LV25616AL全部512KB的地址范围是0x68000000~0x6803FFFF。FSMC_NBL1和FSMC_NBL0控制高位字节和低位字节访问,实现全部512KB存储空间的访问。

二、访问外部SRAM的HAL驱动程序

1、外部SRAM初始化与控制

访问外部SRAM的HAL驱动程序头文件是stm32f4xx_hal_sram.h,包括SRAM初始化函数、控制函数、读写函数和DMA方式读写函数等。SRAM初始化和控制的函数如表所示。

|---------------------------------------|-----------------------------------------------|
| 函数名 | 功能描述 |
| HAL_SRAM_Init() | 外部SRAM初始化函数,主要是FSMC访问接口的定义 |
| HAL_SRAM_MspInit() | 外部SRAM初始化MSP函数,需重新实现,主要是GPIO配 置和中断设置 |
| HAL_SRAM_WriteOperation_Enable() | 使能SRAM存储器的写操作 |
| HAL SRAM_WriteOperation_Disable() | 禁止SRAM存储器的写操作 |
| HAL_SRAM_GetState() | 返回SRAM存储器的当前状态,返回值是枚举类型HAL_ SRAM_StateTypeDef |

函数HAL_SRAM_Init()用于外部SRAM的初始化,其原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,FMC_NORSRAM_TimingTypeDef*Timing,FMC_NORSRAM_TimingTypeDef *ExtTiming);

其中,参数hsram是SRAM_HandleTypeDef结构体类型指针,是FSMC子区对象的指针;Timing和ExtTiming是FSMC读写时序的对象指针。

函数HAL_SRAM_Init()由CubeMX生成的FSMC外设初始化函数调用。初始化程序文件里会定义一个FSMC子区外设对象变量,例如,开发板上使用FSMC Bank 1的子区3访问外部SRAM,定义的FSMC子区外设对象变量如下:

cpp 复制代码
SRAM_HandleTypeDef hsram3;	//访问外部SRAM的FSMC子区外设对象变量

结构体SRAM_HandleTypeDef和FMC_NORSRAM_TimingTypeDef各成员变量的意义在CubeMX图形化设置和示例代码里解释。

表格里的其他函数都是需要一个SRAM_HandleTypeDef类型指针作为函数参数,例如,使能SRAM写操作和禁止SRAM写操作的两个函数的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_WriteOperation_Enable(SRAM_HandleTypeDef *hsram);
HAL_StatusTypeDef HAL_SRAM_WriteOperation_Disable(SRAM_HandleTypeDef *hsram);

2、外部SRAM读写函数

文件stm32f4xx_hal_sram.h定义了几个读写外部SRAM数据的函数,如表所示。

|-------------------------------|----------------------------|
| 函数名 | 功能描述 |
| HAL_SRAM_Read_8b() | 从指定地址读取指定长度的8位数据,存储到一个缓冲区 |
| HAL_SRAM_Write_8b() | 向指定地址写入一定长度的8位数据 |
| HAL_SRAM_Read_16b() | 从指定地址读取指定长度的16位数据,存储到一个缓冲区 |
| HAL_SRAM_Write_16b( ) | 向指定地址写入一定长度的16位数据 |
| HAL_SRAM_Read_32b() | 从指定地址读取指定长度的32位数据,存储到一个缓冲区 |
| HAL_SRAM_Write_32b() | 向指定地址写入一定长度的32位数据 |

这些函数可用于读写8位、16位、32位的数据,这几个函数的输入参数形式是相似的。例如,向外部SRAM写入8位数据的函数HAL_SRAM_Write_8b()的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Write_8b(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,uint8_t *pSrcBuffer,uint32_t BufferSize)

其中,hsram是FSMC子区对象指针,pAddress是需要写入的SRAM目标地址指针,pSrcBuffer是源数据的缓冲区地址指针,BufferSize是源数据缓冲区长度(数据点个数)。开发板上使用FSMC的Bank 1子区3访问外部SRAM,Bank 1子区3的起始地址是0x68000000,那么向这个起始地址的外部SRAM写入一个字符串的示意代码如下:

cpp 复制代码
uint32_t *pAddr=(uint32_t*)(0×68000000);//给指针赋值
uint8_t strIn[]="Moment in UPC";        //准备写入的字符串
uint16_t dataLen=sizeof(strIn);         //数据长度,包括最后的结束符'\O'
HAL_SRAM_Write_8b(&hsram3,pAddr,strIn,dataLen);

其中,第一行语句定义一个指向uint32_t类型数据的指针pAddr,指针的地址就是0x68000000。函数HAL_SRAM_Write_8b()写入的数据是以字节为单位的,其内部会将指向uint32_t类型数据的指针转换为指向uint8_t类型数据的指针,函数HAL_SRAM_Write_8b()中的第一行代码如下:

cpp 复制代码
__IO uint8_t*pSramAddress =(uint8_t*)pAddress;

注意,因为STM32是32位处理器,所以指针总是32位的,因为指针保存的是地址数据。另两个写数据的函数的原型定义如下:

cpp 复制代码
HAL_statusTypeDef HAL_SRAM_Write_16b(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,
uint16_t*pSrcBuffer,uint32_t BufferSize)
HAL_statusTypeDef HAL_SRAM_Write_32b(SRAM HandleTypeDef *hsram,uint32_t *pAddress,
uint32_t *pSrcBuffer,uint32_t BufferSize)

读取8位数据的函数HAL_SRAM_Read_8b()的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Read_8b(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,uint8_t *pDstBuffer,uint32_t BufferSize);

其中,pAddress是需要读取的SRAM目标地址指针,pDstBuffer是读出数据的缓冲区,BufferSize是缓冲区长度,即数据点个数。例如,从SRAM起始地址偏移1024字节处读取一个uint8_t类型数组数据的示意代码如下:

cpp 复制代码
uint32_t *pAddr=(uint32_t*)(0x68000000+1024);uint8_t strOut[30]; //数据点个数
uint16_t dataLen=30;
HAL_SRAM_Read_8b(&hsram3,pAddr,strout,dataLen);                  //给指针赋值

另两个读数据的函数的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Read_16b(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,uint16_t *pDstBuffer,uint32_t BufferSize)
HAL_StatusTypeDef HAL_SRAM_Read_32b(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,uint32_t *pDstBuffer,uint32_t BufferSize)

这些读写函数中的参数BufferSize是缓冲区数据点个数,而不是字节数。传递给函数的SRAM目标地址指针都是uint32_t类型指针,函数内部会进行转换。SRAM目标地址指针应该注意数据对齐,不要使用奇数开始的地址。

3、直接通过指针访问外部SRAM

用户还可以直接使用指针访问外部SRAM的数据,实际上,HAL提供的前述几个数据读写函数就是用指针实现数据访问的。例如,向SRAM一个目标地址写入一批uint16_t类型数据的示意代码如下。

cpp 复制代码
uint16_t num=1000;
uint16_t *pAddr_16b=(uint16_t*)(0x68000000); //16位数据的指针

for(uint16_t i=0;i<10;i++)	//连续写入10个16位整数
{
    num += 5;			//直接向指针所指的地址写入数据
    *pAddr_16b = num;	//++一次,地址加2,因为是16位数据
    pAddr_16b++;
    printf("num= %X\r\n",num);
}

同样地,也可以通过指针读出数据,示意代码如下。

cpp 复制代码
uint16_t num=0,data[10];
uint16_t *pAddr_16b = (uint16_t*)(0x68000000);	//指针赋值
for(uint16_t i=0;i<10;i++)	
{
    num=*pAddr_16b;		//直接从指针所指的地址读数
    data[i]=num;
    pAddr_16b++;		//++一次,地址加2,因为是16位数据
}

直接使用指针访问外部SRAM的数据时,要注意指针指向数据的类型。比如,代码里是要访问16位数据,就要定义为uint16_t类型指针,如果要访问8位数据,就需要定义为uint8_t类型指针。

4、DMA方式读写外部SRAM

外部SRAM还可以通过DMA方式读写,DMA2控制器具有存储器到存储器的DMA流。文件stm32f4xx_hal_sram.h定义了两个DMA方式读写数据的函数和相关的回调函数,如表所示:

|-------------------------------------------|-------------------------|
| 函数名 | 功能 |
| HAL_SRAM_Read_DMA() | 以DMA方式从指定地址读取一定长度的32位数据 |
| HAL_SRAM_Write_DMA( ) | 以DMA方式向指定地址写入一定长度的32位数据 |
| HAL_SRAM_DMA_XferCpltCallback() | DMA流传输完成事件中断的回调函数 |
| HAL_SRAM_DMA_XferErrorCallback( ) | DMA流传输错误事件中断的回调函数 |

一个FSMC子区只能关联一个DMA流,而且不区分发送和接收,所以这两个DMA读写函数在传输完成时的回调函数都是HAL_SRAM_DMA_XferCpltCallback(),需要用户在程序里添加代码进行DMA传输方向识别和控制。

函数HAL SRAM_Write_DMA()的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Write_DMA(SRAM_HandleTypeDef *hsram,uint32_t*pAddress,uint32_t *pSrcBuffer,uint32_t BufferSize)

其中,hsram是FSMC子区对象指针,pAddress是SRAM目标地址指针,pSrcBuffer是源数据缓冲区指针,BufferSize是缓冲区长度,是数据点个数,而不是字节数。函数HAL_SRAM_Read_DMA()的原型定义如下:

cpp 复制代码
HAL_StatusTypeDef HAL_SRAM_Read_DMA(SRAM_HandleTypeDef *hsram,uint32_t *pAddress,uint32_t *pDstBuffer,uint32_t BufferSize)

这两个DMA方式读写函数只能读写uint32_t类型数据,参数BufferSize是缓冲区数据点个数,而不是字节数。

相关推荐
9稳2 小时前
基于单片机的小功率数控调频发射器设计
数据库·单片机·嵌入式硬件·51单片机
LaoZhangGong1232 小时前
解决“KEIL5软件模拟仿真无法打印浮点数”之问题
经验分享·单片机·嵌入式硬件·float·仿真
犹若故人归2 小时前
计算机网络、嵌入式等常见问题简答
java·网络·嵌入式硬件·计算机网络·intellij-idea
LS_learner3 小时前
Arduino IDE刷微控制器并下载对应固件的原由
嵌入式硬件
xiebingsuccess4 小时前
去耦电容理解:“耦”了什么?非要“去”了?
单片机·嵌入式硬件
就叫飞六吧4 小时前
Keil C51 与 Keil MDK(ARM-stm32?):嵌入式开发的利器
arm开发·stm32·嵌入式硬件
又熟了4 小时前
stm32新建工程
stm32·单片机·嵌入式硬件
OpenVINO生态社区5 小时前
【FPGA与单片机的区别】
单片机·嵌入式硬件·fpga开发
iFinder@6 小时前
【51单片机】01入门篇
单片机·嵌入式硬件·51单片机
通信小菜鸡@posedge clk6 小时前
FPGA实现UART对应的电路和单片机内部配合寄存器实现的电路到底有何区别?
单片机·嵌入式硬件·fpga开发