今回はSTM32マイコンのFMCを用いたSDRAM設定を紹介します。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了しています。一方で実際にSDRAMを使いこなすためには追加設定が必要です。
追加設定の中でもメモリマップモードで使用する方法について紹介します。メモリマップモードの場合、変数宣言時に領域を指定することで変数をSDRAM上に配置することが可能です。SDRAMと内蔵RAMの差を意識せずに取り扱うことができます。
今回はSDRAMがボード上に既に実装されているSTM32F7 Discoveryを使用しました。
@リンカスクリプト追加
SDRAMの領域をリンカに追加します。プロジェクトファイル直下の***_FLASH.ldファイルをテキストエディタで編集します。Eclipseでそのまま開くと文字コードが異なるため、テキストエディタで編集します。Eclipseで編集する場合はEclipseのファイルプロパティからUTF-8を選択してから編集してください。
61行目付近に赤字の文字列を追加します。STM32F7 Discoveryは128Mbitのメモリを搭載していますが、データ線が16bit分しか配線されていないため、半分の64Mbit、つまり8MByteが使用できます。
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
SDRAM(rw) : ORIGIN = 0xC0000000, LENGTH = 8M
}
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
SDRAM(rw) : ORIGIN = 0xC0000000, LENGTH = 8M
}
160行目付近に赤字の文字列を追加します。
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
.sdram (NOLOAD) :
{
. = ALIGN(4);
*(.sdram)
*(.sdram.*)
. = ALIGN(4);
} >SDRAM
{
. = ALIGN(4);
*(.sdram)
*(.sdram.*)
. = ALIGN(4);
} >SDRAM
Aメモリ初期化コード追加
メモリ初期化コードを追加します。今回はmain.cのPrivate user code部分に下記のdefineと初期化関数を追加しました。下記のコードはSTM32CubeMXのファーム、STM32Cube\Repository\STM32Cube_FW_F7_V***\Drivers\BSP\STM32746G-Discovery内、stm32746g_discovery_sdram.c、stm32746g_discovery_sdram.hを参考にしています。
/* Private user code ---------------------------------------------------------*/
#define SDRAM_DEVICE_ADDR ((uint32_t)0xC0000000)
#define SDRAM_DEVICE_SIZE ((uint32_t)0x800000) /* SDRAM device size in MBytes */
#define SDRAM_DEVICE_SIZE ((uint32_t)0x800000) /* SDRAM device size in MBytes */
#define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
#define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
#define REFRESH_COUNT ((uint32_t)0x0603) /* SDRAM refresh counter (100Mhz SD clock) */
#define SDRAM_TIMEOUT ((uint32_t)0xFFFF)
#define SDRAM_TIMEOUT ((uint32_t)0xFFFF)
void SDRAM_Initialization_sequence(SDRAM_HandleTypeDef *sdramHandle, uint32_t RefreshCount)
{
{
__IO uint32_t tmpmrd = 0;
FMC_SDRAM_CommandTypeDef Command;
FMC_SDRAM_CommandTypeDef Command;
/* 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(sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);
/* Insert 100 us minimum delay */
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* Inserted delay is equal to 1 ms due to systick time base unit (ms) */
HAL_Delay(1);
/* 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;
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(sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);
/* 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;
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(sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);
/* Program the external memory mode register */
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
SDRAM_MODEREG_CAS_LATENCY_2 |\
SDRAM_MODEREG_OPERATING_MODE_STANDARD |\
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |\
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |\
SDRAM_MODEREG_CAS_LATENCY_2 |\
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;
Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
Command.AutoRefreshNumber = 1;
Command.ModeRegisterDefinition = tmpmrd;
/* Send the command */
HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand(sdramHandle, &Command, SDRAM_TIMEOUT);
/* Set the refresh rate counter */
/* Set the device refresh rate */
HAL_SDRAM_ProgramRefreshRate(sdramHandle, RefreshCount);
/* Set the device refresh rate */
HAL_SDRAM_ProgramRefreshRate(sdramHandle, RefreshCount);
}
/* USER CODE END 0 */
Bメモリ初期化関数追加
先ほど、追加したメモリ初期化関数をメモリ関連のペリフェラル初期化後に実行します。STM32CubeMXではFMCを有効化するとペリフェラル初期化コードが自動で生成されます。main.c内のmain関数内、MX_FMC_Init()がメモリ関連のペリフェラル初期化関数に該当します。MX_FMC_Init()内ではメモリタイミング等のFMC関連の設定がHAL_SDRAM_Init()で行われます。FMC関連の設定後に先ほどのメモリ初期化関数を追加します。ユーザーコードを記入する箇所、「USER CODE BEGIN FMC_Init 2」の中に下記のように関数を追加します。
/* USER CODE BEGIN FMC_Init 2 */
SDRAM_Initialization_sequence(&hsdram1, REFRESH_COUNT);
/* USER CODE END FMC_Init 2 */
Cグローバル変数追加
attributeを追加することでSDRAMに変数を配置します。コンパイラがarm-gccの場合は下記のようにattributeを指定します。
uint32_t sdram_data[85400]__attribute__((section(".sdram")));
arm-gccに限定されない場合はコンパイラに応じて対応できるようにifdefを使用して下記のように変数を定義します。
#if defined ( __ICCARM__ ) // !< IAR Compiler
#pragma location=0xC0000000
static __no_init uint32_t sdram_data[85400];
#pragma location=0xC0000000
static __no_init uint32_t sdram_data[85400];
#elif defined ( __CC_ARM ) //!< Keil Compiler
uint32_t sdram_data[85400] __attribute__((at(0xC0000000)));
uint32_t sdram_data[85400] __attribute__((at(0xC0000000)));
#elif defined ( __GNUC__ ) // !< GNU Compiler
uint32_t sdram_data[85400]__attribute__((section(".sdram")));
#endif
uint32_t sdram_data[85400]__attribute__((section(".sdram")));
#endif
以上の設定で通常の内蔵RAM上の変数と同じように使用することができます。基本的な設定はCubeMXで自動で生成されるため、最低限設定は完了していますが、メモリマップモードといったシームレスにSDRAMを使うためには追加の設定が必要です。なお、SDRAMの初期化タイミング等の設定についてはこちらのサイトで丁寧に説明がされています。また、前回のメモリ領域空き確認方法を設定することでSDRAMの使用率も可視化することができます。合わせて確認してみてください。