STM32CubeMX配置SDRAM

 

目錄

一  STM32F7存儲器映射

二  使用STM32CubeMX配置SDRAM

三 SDRAM初始化時序

四 爲SDRAM空間配置MPU

五  通過自定義動態分配內存函數使用SDRAM中的空間


一  STM32F7存儲器映射

ITCM-RAM 0x0000 0000 ~ 0x4000 16 Kbytes 只能CPU訪問
Flash Memory On ITCM Interface 0x0020 0000 ~ 0x003F FFFF 2 Mbytes  
Flash Memory On AXIM Interface 0x0800 0000 ~ 0x081F FFFF 2 Mbytes  
Data TCM RAM 0x2000 0000 ~ (0x2002 0000 - 1 ) 128 Kbytes  
Main internal SRAM1 0x2002 0000 ~ (0x2007 C000 -1) 368 Kbytes  
Auxiliary internal SRAM2 0x2007 C000 16 Kbytes  
Peripheral

0x4000 0000 ~ 0x5FFF FFFF

512Mbytes Register Addr
Bank 1 0x6000 0000 ~ 0x6FFF FFFF 256 Mbytes NOR/PSRAM/SRAM
Bank 2 0x7000 0000 ~ 0x7FFF FFFF   Reserved
Bank 3 0x8000 0000 ~ 0x8FFF FFFF 256 Mbytes NAND
Bank 4 0x9000 0000 ~ 0x9FFF FFFF 256 Mbytes Reserved
SDRAM Bank 1 0xC000 0000 ~ 0xCFFF FFFF 256 Mbytes SDRAM
SDRAM Bank 2 0xD000 0000 ~ 0xDFFF FFFF 256 Mbytes SDRAM

二  使用STM32CubeMX配置SDRAM

  • SDRAM MODE配置如下所示:   

該配置參照原理圖就可以配置出來.
bank選擇線: BA0與BA1都有相連,2bit可以表示四種變化,所以選擇4 banks.
Byte enable:表示可以通過LDQM,UDQM線控制訪問8bit數據,還是16bit數據.

  • SDRAM Configuration配置如下

以上配置主要通過查閱從W9825G6KH數據手冊進行確定:

1.從W9825G6KH數據手冊中可得知Row Address:A0-A12,共13bits; Column Address:A0-A8,共9bits.

2.CAS latency從W9825G6KH數據手冊的目錄頁就可以看出

3.禁止寫保護 並 使能突發讀模式提高讀效率

4.SDRAM common clock:

   SDRAM有4個bank, 在切換bank時,SDRAM需要延遲一定時間, SDRAM common clock就是用於配置該時間.在SDRAM手冊中 
   對應= 2 tick , 所以該值配置爲2.

5.SDRAM common read pipe delay: CAS 延遲後延後多少個 HCLK 時鐘週期讀取數據

6.時序配置

在stm32f7的數據手冊有這麼一句話"SDRAM clock can be HCLK/2 or HCLK/3" ,所以SDRAM的時鐘最大爲216/2=108MHz,所以對於SDRAM而言, 1tick = 9.26ns

STM32CubeMX參數 說明 SDRAM手冊對應簡寫
Load mode register to active delay 2 加載模式寄存器到激活時間的延遲 2 tick
Exit self-refresh delay 8 退出自我刷新後需要延遲的時間 72ns
Self-refresh time 6 自我刷新週期 55ns
SDRAM common row cycle delay 6 行循環延遲 2 tick
Write recovery time 2 寫恢復延遲 2 tick
SDRAM common row precharge delay 2 行預充電延遲 15ns
Row to Column delay 6 行到列延遲 15ns

(tips: 按道理SDRAM common row cycle delay 與 Row to Column delay 可以最小設置爲2的, 但是在實際使用中發現配置爲2時SDRAM並不能正常工作, 經過試驗這兩個參數可設置爲6 )

  • GPIO配置

gpio是根據原理圖進行配置的, 可參照下圖進行修改: 

  • 通過STM32CubeMX生成的相關SDRAM代碼如下
/* FMC initialization function */
static void MX_FMC_Init(void)
{

  /* USER CODE BEGIN FMC_Init 0 */

  /* USER CODE END FMC_Init 0 */

  FMC_SDRAM_TimingTypeDef SdramTiming = {0};

  /* USER CODE BEGIN FMC_Init 1 */

  /* USER CODE END FMC_Init 1 */

  /** Perform the SDRAM1 memory initialization sequence
  */
  hsdram1.Instance = FMC_SDRAM_DEVICE;
  /* hsdram1.Init */
  hsdram1.Init.SDBank = FMC_SDRAM_BANK1;
  hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
  hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
  hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;
  hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
  hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;
  hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
  hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
  hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
  hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
  /* SdramTiming */
  SdramTiming.LoadToActiveDelay = 2;
  SdramTiming.ExitSelfRefreshDelay = 8;
  SdramTiming.SelfRefreshTime = 6;
  SdramTiming.RowCycleDelay = 6;
  SdramTiming.WriteRecoveryTime = 2;
  SdramTiming.RPDelay = 2;
  SdramTiming.RCDDelay = 6;

  if (HAL_SDRAM_Init(&hsdram1, &SdramTiming) != HAL_OK)
  {
    Error_Handler( );
  }

  /* USER CODE BEGIN FMC_Init 2 */

  /* USER CODE END FMC_Init 2 */
}


static void HAL_FMC_MspInit(void){
  /* USER CODE BEGIN FMC_MspInit 0 */

  /* USER CODE END FMC_MspInit 0 */
  GPIO_InitTypeDef GPIO_InitStruct ={0};
  if (FMC_Initialized) {
    return;
  }
  FMC_Initialized = 1;
  /* Peripheral clock enable */
  __HAL_RCC_FMC_CLK_ENABLE();
  
  /** FMC GPIO Configuration  
  PF0   ------> FMC_A0
  PF1   ------> FMC_A1
  PF2   ------> FMC_A2
  PF3   ------> FMC_A3
  PF4   ------> FMC_A4
  PF5   ------> FMC_A5
  PC0   ------> FMC_SDNWE
  PC2   ------> FMC_SDNE0
  PC3   ------> FMC_SDCKE0
  PF11   ------> FMC_SDNRAS
  PF12   ------> FMC_A6
  PF13   ------> FMC_A7
  PF14   ------> FMC_A8
  PF15   ------> FMC_A9
  PG0   ------> FMC_A10
  PG1   ------> FMC_A11
  PE7   ------> FMC_D4
  PE8   ------> FMC_D5
  PE9   ------> FMC_D6
  PE10   ------> FMC_D7
  PE11   ------> FMC_D8
  PE12   ------> FMC_D9
  PE13   ------> FMC_D10
  PE14   ------> FMC_D11
  PE15   ------> FMC_D12
  PD8   ------> FMC_D13
  PD9   ------> FMC_D14
  PD10   ------> FMC_D15
  PD14   ------> FMC_D0
  PD15   ------> FMC_D1
  PG2   ------> FMC_A12
  PG4   ------> FMC_BA0
  PG5   ------> FMC_BA1
  PG8   ------> FMC_SDCLK
  PD0   ------> FMC_D2
  PD1   ------> FMC_D3
  PG15   ------> FMC_SDNCAS
  PE0   ------> FMC_NBL0
  PE1   ------> FMC_NBL1
  */
  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3 
                          |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_11|GPIO_PIN_12 
                          |GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_2|GPIO_PIN_3;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_4 
                          |GPIO_PIN_5|GPIO_PIN_8|GPIO_PIN_15;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
  HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10 
                          |GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14 
                          |GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14 
                          |GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  /* USER CODE BEGIN FMC_MspInit 1 */

  /* USER CODE END FMC_MspInit 1 */
}

三 SDRAM初始化時序

SDRAM數據手冊中有這麼一段話

從該段話中可以得出初始化時序如下:

STM32CubeMX自動生成的初始化函數如下:

(Tips: 如果只做以上配置是不會自動生成以下初始化代碼的,如果要自動生成該段代碼,需要在STM32CubeMX中使用SDRAM,之所以我這裏生成了這段代碼是因爲我配置了TouchGFX的Parameter Settings中的SDRAM Instances:

如果沒有自動生成,我們可以自己複製如下代碼到自己的工程。

/**
  * @brief  Programs the SDRAM device.
  * @retval None
  */
void MX_SDRAM_InitEx(void)
{
  __IO uint32_t tmpmrd = 0;
  
  /* Step 1: Configure a clock configuration enable command */
  Command.CommandMode            = FMC_SDRAM_CMD_CLK_ENABLE;
  Command.CommandTarget          =  FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = 0;

  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);

  /* Step 2: Insert 100 us minimum delay */ 
  /* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
  HAL_Delay(1);
    
  /* Step 3: Configure a PALL (precharge all) command */ 
  Command.CommandMode            = FMC_SDRAM_CMD_PALL;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = 0;

  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);  
  
  /* Step 4: Configure an Auto Refresh command */ 
  Command.CommandMode            = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 8;
  Command.ModeRegisterDefinition = 0;

  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
  
  /* Step 5: Program the external memory mode register */
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1          |\
                     SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |\
                     SDRAM_MODEREG_CAS_LATENCY_3           |\
                     SDRAM_MODEREG_OPERATING_MODE_STANDARD |\
                     SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;

  Command.CommandMode            = FMC_SDRAM_CMD_LOAD_MODE;
  Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  Command.AutoRefreshNumber      = 1;
  Command.ModeRegisterDefinition = tmpmrd;

  /* Send the command */
  HAL_SDRAM_SendCommand(&hsdram1, &Command, SDRAM_TIMEOUT);
  
  /* Step 6: Set the refresh rate counter */
  /* Set the device refresh rate */
  HAL_SDRAM_ProgramRefreshRate(&hsdram1, REFRESH_COUNT); 
}

到這裏,SDRAM基本配置完成, 可以定義一個數組到SDRAM空間,對該數組進行讀寫操作, 以此判斷SDRAM是否正常工作:

uint16_t test_buf[128] __attribute__((at(0xC0000000)));	//定義一個數組,該數組首地址爲SDRAM首地址0xC0000000

//寫該數組
for(uint8_t i = 0;i < 128 ; i++){
    test_buf[i] = i;
}

//將該數組的值通過串口打印出來
for(uint8_t i = 0;i < 128 ; i++){
    printf("%d ",test_buf[i]);
}

四 爲SDRAM空間配置MPU

爲了防止出現在訪問SDRAM時出現莫名其妙的數據錯誤,建議配置MPU對SDRAM進行保護

附上配置截圖:

五  通過自定義動態分配內存函數使用SDRAM中的空間

具體設計原理可以參考正點原子的例子,這裏我就直接貼代碼了

malloc.c代碼如下所示

#include "malloc.h"

uint8_t   mem_rdy; 				//內存管理是否就緒

//內存池(32字節對齊), 外部SDRAM內存池,前面2M給LTDC用了(1280*800*2)
__align(32) uint8_t mem_base[MEM_MAX_SIZE] __attribute__((at(0xC0600000)));	

uint32_t mem_map[MEM_MAP_SIZE] __attribute__((at(0xC0600000 + MEM_MAX_SIZE)));//內存管理表

/***************************************************************************************
  * @brief   內存管理初始化  
  * @input   
  * @return  
***************************************************************************************/
static void mem_init(void)
{
    memset(mem_map, 0, MEM_MAP_SIZE *4);	//內存狀態表數據清零  
 	mem_rdy = 1;						//內存管理初始化OK  
}


/***************************************************************************************
  * @brief   內存分配(內部調用)
  * @input   size:要分配的內存大小(字節)
  * @return  返回值:0XFFFFFFFF,代表錯誤;其他,內存偏移地址 
***************************************************************************************/
static uint32_t sdram_malloc(uint32_t size)  
{
    signed long offset=0;  
    uint32_t nmemb;	//需要的內存塊數  
	uint32_t cmemb = 0;//連續空內存塊數
    uint32_t i;
    if( !mem_rdy ){
        mem_init();//未初始化,先執行初始化 
    }
    if(size == 0){
        return 0xFFFFFFFF;//不需要分配
    }
    nmemb = size / MEM_BLOCK_SIZE;//獲取需要分配的連續內存塊數
    if(size % MEM_BLOCK_SIZE){
        nmemb++;  
    }
    for(offset = MEM_MAP_SIZE - 1; offset >= 0; offset--)//搜索整個內存控制區  
    {
		if( !mem_map[offset] )
            cmemb++;//連續空內存塊數增加
		else
            cmemb=0;								//連續內存塊清零
        
		if(cmemb == nmemb){							//找到了連續nmemb個空內存塊
            for(i=0; i < nmemb; i++) { 					//標註內存塊非空 
                mem_map[offset+i] = nmemb;  
            }
            return (offset * MEM_BLOCK_SIZE);//返回偏移地址  
		}
    }  
    return 0XFFFFFFFF;//未找到符合分配條件的內存塊  
}


/***************************************************************************************
  * @brief   釋放內存(內部調用) 
  * @input   memx:所屬內存塊;
             offset:內存地址偏移
  * @return  返回值:0,釋放成功;1,釋放失敗;  
***************************************************************************************/
static uint8_t sdram_free(uint32_t offset)  
{
    int i;
    if( !mem_rdy )//未初始化,先執行初始化
	{
		mem_init();    
        return 1;//未初始化  
    }
    if(offset < MEM_MAX_SIZE)                 //偏移在內存池內. 
    {  
        int index = offset / MEM_BLOCK_SIZE;  //偏移所在內存塊號碼  
        int nmemb = mem_map[index];	  //內存塊數量
        for(i=0; i < nmemb; i++)  			  //內存塊清零
        {
            mem_map[index+i] = 0;  
        }  
        return 0;  
    }else 
        return 2;//偏移超區了.  
}  


/***************************************************************************************
  * @brief   釋放內存(外部調用) 
  * @input   ptr:內存首地址 
  * @return  
***************************************************************************************/
void mem_free(void *ptr)  
{
	uint32_t offset;   
	if(ptr == NULL) 
        return;//地址爲0.  
 	offset = (uint32_t)ptr - (uint32_t)mem_base;     
    sdram_free(offset);	//釋放內存      
}


/***************************************************************************************
  * @brief   分配內存(外部調用)
  * @input   size:內存大小(字節)
  * @return  
***************************************************************************************/
void *mem_malloc(uint32_t size)  
{
    uint32_t offset;   
	offset = sdram_malloc(size);  	   	 	   
    if(offset == 0xFFFFFFFF)
        return NULL;  
    else 
        return (void*)((uint32_t)mem_base + offset);  
}

malloc.h文件內容如下所示

#ifndef __MALLOC_H
#define __MALLOC_H

#include "stdint.h"
#include "string.h"

#ifndef NULL
#define NULL 0
#endif

//SDRAM內存參數設定
#define MEM_MAX_SIZE			20*1024*1024  					//最大管理內存20M
#define MEM_BLOCK_SIZE			64  	  						//內存塊大小爲64字節
#define MEM_MAP_SIZE        	(MEM_MAX_SIZE / MEM_BLOCK_SIZE)   //內存表大小


//用戶調用函數
void mem_free(void *ptr)  ;  			//內存釋放(外部調用)
void *mem_malloc(uint32_t size);			//內存分配(外部調用)
#endif

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章