细说STM32F407单片机以轮询方式读写外部SRAM的方法

目录

一、实例的功能

二、工程配置

1、KEYLED 

2、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator

3、FSMC

(1) 模式设置

(2) Bank 1子区3参数设置

1) NOR/PSRAM control组,子区控制参数

2) NOR/PSRAM timing组,读写操作时序参数

3) DMA 

三、软件设计 

1、KEYLED

2、fsmc.h、fsmc.c

3、main.h

4、main.c

四、下载运行 


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

        关于  IS61LV25616AL,可以详见参考文章1。

        参考文章1:细说STM32F407单片机FSMC连接外部SRAM的方法及HAL驱动-CSDN博客  https://wenchm.blog.csdn.net/article/details/144930868

        参考文章2:细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV_stm32f407 spiflash驱动程序-CSDN博客  https://wenchm.blog.csdn.net/article/details/144587209

一、实例的功能

        连接外部SRAM的FSMC接口设置以及轮询方式读写外部SRAM的方法,并用HAL函数读写数据和直接用指针读写数据。本示例需要引用参考文章2的KEYLED的4个按键和对应的LED。

        本示例还用到RNG,在组件面板Security分组里有RNG模块,启用RNG即可。RNG需要用到48MHz时钟。

[S2]KeyUp   = Write by HAL functions   LED1 ON
[S3]KeyDown = Read by HAL functions    LED2 ON
[S4]KeyLeft = Write by pointer         LED3 ON
[S5]KeyRight= Read by pointer          LED4 ON

二、工程配置

1、KEYLED 

        本例的项目中要使用KEYLED,其中的keyled.c和keyled.h的使用方法与参考文章1相同。

2、时钟、DEBUG、USART6、NVIC、GPIO、CodeGenerator

        外部时钟,25MHz,设置到HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz,其它,都设置成168MHz。

        DEBUG,选择serial wire,USART6、NVIC、CodeGenerator的设置同参考文章1。        

3、FSMC

        开发板上使用Bank 1子区3连接外部SRAM,所以对NOR Flash/PSRAM/SRAM/ROM/LCD 3进行配置。

(1) 模式设置

  • Chip Select设置为NE3,也就是使用FSMC_NE3作为SRAM芯片的片选信号。
  • Memory type设置为SRAM。
  • Address设置为19bits,因为用到了FSMC_A0至FSMC_A18共19根地址线。
  • Data设置为16bits,因为使用了16位数据线。
  • Wait设置为Disable。Wait是PSRAM芯片发给FSMC的等待输入信号,本示例电路中IS62WV51216芯片没有这个输出信号。
  • Byte enable需要勾选,表示允许字节访问。允许字节访问时,将通过芯片的UB和LB信号控制访问高位字节和低位字节。
  •         这样设置后,在引脚视图上将自动标出使用的各FSMC引脚。其中,FSMC_D0至FSMC_D15是LCD和SRAM共用的16位数据线,FSMC_NOE和FSMC_NWE是共用的控制信号线。

            FSMC_NE3用于SRAM片选。FSMC_NBL1和FSMC_NBL0是SRAM的高低字节选择信号。        

            FSMC_A0至FSMC_A18共19根地址线的GPIO引脚分配如下图,自动分配的GPIO引脚与实际电路的引脚是一致的,所以无须更改。

      /** FSMC GPIO Configuration
      PF0   ------> FSMC_A0
      PF1   ------> FSMC_A1
      PF2   ------> FSMC_A2
      PF3   ------> FSMC_A3
      PF4   ------> FSMC_A4
      PF5   ------> FSMC_A5
      PF12   ------> FSMC_A6
      PF13   ------> FSMC_A7
      PF14   ------> FSMC_A8
      PF15   ------> FSMC_A9
      PG0   ------> FSMC_A10
      PG1   ------> FSMC_A11
      PE7   ------> FSMC_D4
      PE8   ------> FSMC_D5
      PE9   ------> FSMC_D6
      PE10   ------> FSMC_D7
      PE11   ------> FSMC_D8
      PE12   ------> FSMC_D9
      PE13   ------> FSMC_D10
      PE14   ------> FSMC_D11
      PE15   ------> FSMC_D12
      PD8   ------> FSMC_D13
      PD9   ------> FSMC_D14
      PD10   ------> FSMC_D15
      PD11   ------> FSMC_A16
      PD12   ------> FSMC_A17
      PD13   ------> FSMC_A18
      PD14   ------> FSMC_D0
      PD15   ------> FSMC_D1
      PG2   ------> FSMC_A12
      PG3   ------> FSMC_A13
      PG4   ------> FSMC_A14
      PG5   ------> FSMC_A15
      PD0   ------> FSMC_D2
      PD1   ------> FSMC_D3
      PD4   ------> FSMC_NOE
      PD5   ------> FSMC_NWE
      PG10   ------> FSMC_NE3
      PE0   ------> FSMC_NBL0
      PE1   ------> FSMC_NBL1
      */
      /* GPIO_InitStruct */

    (2) Bank 1子区3参数设置

            在模式设置中启用Bank 1子区3之后,在参数设置部分会出现NOR/PSRAM3参数设置页面,在这个页面设置SRAM的控制和时序参数。

    1) NOR/PSRAM control组,子区控制参数
  • Memory type只能选择SRAM,因为在模式配置部分已设置为SRAM。
  • Bank只能选择为Bank 1 NOR/PSRAM3,是与模式设置部分对应的。
  • Write operation设置为Enabled,表示允许写操作。
  • Extended mode设置为Disabled。FSMC自动使用模式A对SRAM进行操作。SRAM的读操作和写操作的速度基本相同,所以读写操作可以使用相同的时序参数,无须使用扩展模式单独设置读时序和写时序。
  • 2) NOR/PSRAM timing组,读写操作时序参数
  • Address setup time in HCLK clock cycles,即地址建立时间参数ADDSET,设置范围为0~15,设置为0即可。
  • Data setup time in HCLK clock cycles,即数据建立时间参数DATAST,设置范围为1~255,设置为8。
  • Bus turn around time in HCLK clock cycles,总线翻转时间,设置范围为0~15,设置为0即可。
  • 3) DMA 

            FSMC参数设置部分没有DMA设置页面,但是SRAM的HAL驱动程序中有DMA方式进行数据读写的函数,其DMA方式的设置与其他外设稍有不同的。 

    三、软件设计 

    1、KEYLED

            本例的项目中要使用KEYLED,其中的keyled.c和keyled.h的使用方法与参考文章1相同。

    2、fsmc.h、fsmc.c

             IDE自动生成。

    3、main.h

    /* USER CODE BEGIN Private defines */
    void SRAM_WriteByFunc();
    void SRAM_ReadByFunc();
    void SRAM_WriteByPointer();
    void SRAM_ReadByPointer();
    /* USER CODE END Private defines */

    4、main.c

    /* USER CODE BEGIN Includes */
    #include "keyled.h"
    #include <stdio.h>
    /* USER CODE END Includes */
    /* USER CODE BEGIN PD */
    // SRAM的容量不同,该处的定义就不同,更改SRAM就得修改此处的定义
    #define SRAM_ADDR_BEGIN	 0x68000000UL //Bank1子区3的SRAM起始地址
    #define SRAM_ADDR_HALF	 0x6801FFFFUL //SRAM容量256K*16bit,中间地址128K字节
    #define SRAM_ADDR_END	 0x6803FFFFUL //SRAM容量256K*16bit,结束地址512K字节
    //#define SRAM_ADDR_HALF 0x68080000UL //SRAM容量512K*16bit,中间地址512K字节
    //#define SRAM_ADDR_END	 0x680FFFFFUL //SRAM容量512K*16bit,结束地址1024K字节
    /* USER CODE END PD */
    /* USER CODE BEGIN 2 */
      printf("Demo19_1_FSMC: External SRAM\r\n");
      printf("Read/Write SRAM by polling\r\n");
    
      //显示菜单
      printf("[S2]KeyUp   = Write by HAL functions.\r\n");
      printf("[S3]KeyDown = Read by HAL functions.\r\n");
      printf("[S4]KeyLeft = Write by pointer.\r\n");
      printf("[S5]KeyRight= Read by pointer.\r\n");
    
      // MCU output low level LED light is on
      LED1_OFF();
      LED2_OFF();
      LED3_OFF();
      LED4_OFF();
      /* USER CODE END 2 */
    /* USER CODE BEGIN 3 */
    	KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
    	switch(curKey)
    	{
    	  case KEY_UP:
    	  {
    	  	SRAM_WriteByFunc();		//Write by HAL functions
    	    LED1_ON();
    	    LED2_OFF();
    	    LED3_OFF();
    	    LED4_OFF();
    	  }
    	  	break;
    
    	  case KEY_DOWN:
    	  {
    	  	SRAM_ReadByFunc();		//Read by HAL functions
    	    LED1_OFF();
    	    LED2_ON();
    	    LED3_OFF();
    	    LED4_OFF();
    	  }
    	  	break;
    
    	  case KEY_LEFT:
    	  {
    		SRAM_WriteByPointer();	//Write by pointer
    	    LED1_OFF();
    	    LED2_OFF();
    	    LED3_ON();
    	    LED4_OFF();
    		break;
    	  }
    
    	  case KEY_RIGHT:
    	  {
    		SRAM_ReadByPointer();	//Read by pointer
    	    LED1_OFF();
    	    LED2_OFF();
    	    LED3_OFF();
    	    LED4_ON();
    	  }
    	  default:
    	  {
    		LED1_OFF();
    		LED2_OFF();
    		LED3_OFF();
    		LED4_OFF();
    	  }
       }
    
    	printf("** Reselect menu or reset **\r\n");
    	HAL_Delay(500);				//延时,消除按键抖动
      }
      /* USER CODE END 3 */
    /* USER CODE BEGIN 4 */
    /* 用HAL函数写入数据 */
    void SRAM_WriteByFunc()
    {
    //1. 写入字符串
    	uint32_t *pAddr = (uint32_t *)(SRAM_ADDR_BEGIN);	//给指针赋值
    	uint8_t strIn[] = "Test FSMC";
    	uint16_t dataLen = sizeof(strIn); //数据长度,字节数,包括最后的结束符'\0'
    	if (HAL_SRAM_Write_8b(&hsram3,pAddr,strIn,dataLen) == HAL_OK)
    	{
    		printf("Write string at %p = %s\r\n",pAddr,strIn);
    	}
    
    //2. 写入1个随机数
    	uint32_t num = 0;
    	pAddr=(uint32_t *)(SRAM_ADDR_BEGIN+256);	//指针重新赋值
    	HAL_RNG_GenerateRandomNumber(&hrng, &num);	//产生32位随机数
    	if (HAL_SRAM_Write_32b(&hsram3, pAddr, &num, 1) == HAL_OK)
    	{
    		printf("Write 32b number at %p = %lx\r\n",pAddr,num);//十六进制显示,显示前缀0x
    	}
    }
    
    /* 用HAL函数读取数据 */
    void SRAM_ReadByFunc()
    {
    //1. 读取字符串
    	uint32_t *pAddr = (uint32_t *)(SRAM_ADDR_BEGIN);	//给指针赋值
    	uint8_t	strOut[30];
    	uint16_t dataLen = 30;
    	if (HAL_SRAM_Read_8b(&hsram3,pAddr,strOut,dataLen) == HAL_OK)
    	{
    		printf("Read string at %p = %s\r\n",pAddr,strOut);//显示自动以'\0'结束
    	}
    
    	//2. 读取1个uint32_t类型的数
    	uint32_t num = 0;
    	pAddr=(uint32_t *)(SRAM_ADDR_BEGIN+256);	//指针重新赋值指向1个新的地址
    	if (HAL_SRAM_Read_32b(&hsram3, pAddr, &num, 1) == HAL_OK)
    	{
    		printf("Read 32b number at %p = %lx\r\n",pAddr,num);
    	}
    }
    
    /* 直接通过指针写数据 */
    void SRAM_WriteByPointer()
    {
    	printf("Write five uint16_t numbers.\r\n");
    	printf("start from 0x6801 FFFF.\r\n");
    
    	uint16_t num = 100;
    	uint16_t *pAddr_16b = (uint16_t *)(SRAM_ADDR_HALF);	//uint16_t 类型数据指针
    	for(uint8_t i=0;i<5;i++) //连续写入5个16位整数
    	{
    		num += 3;
    		*pAddr_16b = num;	//直接向指针所指的地址写入数据
    		pAddr_16b ++;		//++1次,地址+2, 因为是uint16_t类型
    		printf("The data of Add %p = %d\r\n",pAddr_16b,num);
    	}
    }
    
    /* 直接通过指针读取数据 */
    void SRAM_ReadByPointer()
    {
    	printf("Read five uint16_t numbers.\r\n");
    	printf("start from 0x6801 FFFF.\r\n");
    	uint16_t num = 0;
    	uint16_t *pAddr_16b = (uint16_t *)(SRAM_ADDR_HALF); //uint16_t 类型数据指针
    	for(uint8_t i=0; i<5; i++)
    	{
    		num = *pAddr_16b;	//直接从指针所指的地址读数
    		pAddr_16b ++;		//++1次,地址+2, 因为是uint16_t类型
    		printf("The data of Add %p = %d\r\n",pAddr_16b,num);
    	}
    }
    
    //串口打印
    int __io_putchar(int ch)
    {
    	HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
    	return ch;
    }
    /* USER CODE END 4 */

            使用HAL函数读写外部SRAM的数据,就是使用参考文章2中的函数读写SRAM的数据。给这些函数传递的SRAM目标地址必须是uint32_t类型指针,如下所示:

    uint32_t *pAddr = (uint32_t*)(SRAM_ADDR_BEGIN);	//给指针赋值
    pAddr = (uint32_t*)(SRAM_ADDR_BEGIN+256);		//指针重新赋值,指向新的地址

            而在使用指针直接访问SRAM时,指针类型需要与实际访问的数据类型一致,例如,访问的数据是uint16_t类型,就应该定义如下的指针:

    uint16_t *pAddr_16b = (uint16_t*)(SRAM_ADDR_HALE);	//uint16_t类型数据指针

    四、下载运行 

            测试过程中发现一个有趣的现象,当num的值增加到超过1024以后,num值会被置0,余数仍然有效,比如某一时刻,num=1023,++3,新的num=2。然后从2开始自增下去直到再次超过1024。这可能是一个BUG,也可能是哪一处设置的不对,请感兴趣的网友,把解决办法贴上来。

            还发现一个BUG,写进SRAM的数据再次读出来,不一定一样。

     

    作者:wenchm

    物联沃分享整理
    物联沃-IOTWORD物联网 » 细说STM32F407单片机以轮询方式读写外部SRAM的方法

    发表回复