目录
[1、 FSMC控制区域的划分](#1、 FSMC控制区域的划分)
本文介绍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是缓冲区数据点个数,而不是字节数。