目錄
一 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