细说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是缓冲区数据点个数,而不是字节数。

相关推荐
茯苓gao3 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
梁朝辉6 小时前
STM32H750VBT6中ADCINP与INN什么区别
stm32·单片机·嵌入式硬件
省四收割者7 小时前
一文详解信号完整性(1)
python·嵌入式硬件·数学建模·信息与通信·信号处理·智能硬件
崇山峻岭之间8 小时前
单片机无感FOC驱动之ADC
单片机·嵌入式硬件
Ligocious8 小时前
1.点亮一颗小小的LED
单片机·嵌入式硬件
KaifuZeng12 小时前
通信与接口协议面试八、CAN通信
单片机·嵌入式硬件·面试·通信与接口协议
智源单片机设计13 小时前
基于单片机的直流电机双闭环调速系统设计
单片机·嵌入式硬件
kebidaixu14 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片详解
stm32
kebidaixu14 小时前
两轮车换电 BMS 的模拟前端中颖 SH367306 AFE 芯片调试实录:四个关键问题与解决方案
stm32