STM32F4 series microcontrollers integrate limited internal SRAM and Flash memory. Complex applications requiring large data buffers or graphic frames often exceed these internal capacities, necessitating the integration of external memory. While SDRAM offers high density, the STM32F407 lacks native SDRAM support, relying instead on the Flexible Static Memory Controller (FSMC) to interface with static memories like SRAM and NOR Flash. This implementation details the hardware connection, driver configuration, and linker script strategies to utilize an external IS62WV51216 SRAM chip.
SRAM Interface and Timing Characteristics
The IS62WV51216 is a 1M x 16-bit high-speed CMOS static RAM. It utilizes 19 address lines (A0-A18) to address 512K word locations and 16 data lines (I/O0-I/O15) for parallel data transfer. Control logic involves Chip Select (CS1#), Output Enable (OE#), Write Enable (WE#), and Upper/Lower Byte controls (UB#, LB#).
Successful communication requires strict adherence to timing parameters. During a read cycle, the Read Cycle Time (tRC) and Address Access Time (tAA) must be satisfied. For the IS62WV51216, tAA typically does not exceed 55ns. In a write cycle, the Write Cycle Time (tWC) and Write Pulse Width (tWP) are critical, with tWC usually requiring a minimum of 55ns. The FSMC peripheral must be configured to generate delays (ADDSET, DATAST) that meet or exceed these hardware constraints, calculated based on the AHB clock frequency (HCLK).
FSMC Peripheral Configuration
FSMC maps external memory to specific regions of the microcontroller's address space. Bank 1 is dedicated to NOR/PSRAM/SRAM, divided into four sub-banks. When connecting a chip to the NE4 (Chip Select 4) pin, the memory is mapped to the address range 0x6C000000 to 0x6FFFFFFF.
The configuration involves two main structures: one for timing parameters and one for the control register. Timing is defined by FSMC_NORSRAMTimingInitTypeDef, where FSMC_AddressSetupTime and FSMC_DataSetupTime are calculated to ensure signal stability. The control structure, FSMC_NORSRAMInitTypeDef, defines the memory type (SRAM), data bus width (16-bit), and muxing mode.
The following code demonstrates a refactored initialization routine for the GPIO and FSMC peripherals:
/* Memory Handle Definition */
#define SRAM_BANK_ADDR ((uint32_t)0x6C000000)
#define SRAM_SIZE_BYTES (0x100000)
/**
* @brief Configure GPIO and FSMC for External SRAM
*/
void MEM_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
FSMC_NORSRAMInitTypeDef FSMC_InitStruct;
FSMC_NORSRAMTimingInitTypeDef timingStruct;
/* Enable Clocks for FSMC and GPIO Ports (A, D, E, F, G) */
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOD |
RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOF |
RCC_AHB1Periph_GPIOG, ENABLE);
/* Common GPIO Configuration */
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
/* Configure Address Lines (Simplified representation) */
/* Example: A0-A10 on Port F */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 |
GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 |
GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 |
GPIO_Pin_15;
GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource0, GPIO_AF_FSMC);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource1, GPIO_AF_FSMC);
/* ... (Repeat AF config for other pins) ... */
/* Configure Data Lines (D0-D15) on Ports D and E */
GPIO_InitStruct.GPIO_Pin = 0xFFFF; /* Pins 0-15 */
GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_Init(GPIOE, &GPIO_InitStruct);
/* ... (AF Config for Data Lines) ... */
/* Configure Control Lines: NOE, NWE, NE4, NBL0, NBL1 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; /* NOE, NWE */
GPIO_Init(GPIOD, &GPIO_InitStruct);
/* FSMC Timing Configuration (Async Mode A) */
/* HCLK is 168MHz (~6ns per cycle). Target tAA > 55ns. */
timingStruct.FSMC_AddressSetupTime = 2; /* ~12ns */
timingStruct.FSMC_AddressHoldTime = 0;
timingStruct.FSMC_DataSetupTime = 8; /* ~48ns + overhead */
timingStruct.FSMC_BusTurnAroundDuration = 0;
timingStruct.FSMC_CLKDivision = 0;
timingStruct.FSMC_DataLatency = 0;
timingStruct.FSMC_AccessMode = FSMC_AccessMode_A;
/* FSMC Bank Configuration */
FSMC_InitStruct.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_InitStruct.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_InitStruct.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_InitStruct.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_InitStruct.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_InitStruct.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
FSMC_InitStruct.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_InitStruct.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_InitStruct.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_InitStruct.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_InitStruct.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_InitStruct.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_InitStruct.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_InitStruct.FSMC_ReadWriteTimingStruct = &timingStruct;
FSMC_InitStruct.FSMC_WriteTimingStruct = &timingStruct;
FSMC_NORSRAMInit(&FSMC_InitStruct);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
}
Direct Memory Access
Once initialized, the external SRAM is mapped directly to the system address space. Data can be read or written using standard pointer operations, abstracting the low-level bus transactions.
void MEM_Test(void) {
uint32_t *ptr = (uint32_t *)SRAM_BANK_ADDR;
/* Write test pattern */
*ptr = 0xA5A5A5A5;
/* Read back */
uint32_t val = *ptr;
}
Linker Script Integration
To utilize external memory for static variables or the heap, the linker scatter file (.sct) must be modified. This allows the C runtime environment to manage external memory automatically.
Initialization Constraint
Before the C library initializes variables (copying .data and zeroing .bss), the external memory controller must be active. Therefore, MEM_Init must be called in the startup assembly file (startup_stm32f4xx.s) before the branch to __main.
; startup_stm32f4xx.s excerpt
Reset_Handler
LDR R0, =MEM_Init
BLX R0
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
Scenario 1: Global Variables in External SRAM
To place all RW and ZI data in external memory, define a new execution region in the .sct file mapped to the FSMC Bank address.
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
; Internal SRAM (Stack only)
RW_IRAM1 0x20000000 0x00020000 {
*.o (STACK)
}
; External SRAM (Global Variables)
RW_ERAM1 0x6C000000 0x00100000 {
.ANY (+RW +ZI)
}
}
Scenario 2: Selective Placement and Heap Management
A more robust strategy keeps the stack and critical global variables in internal SRAM for speed, while moving the heap (for malloc) and specific large buffers to external SRAM.
In the C code, use the section attribute to place specific variables:
#define EXT_RAM_ATTR __attribute__((section("ext_ram_zone")))
/* Large buffer in external SRAM */
uint8_t frameBuffer[1024] EXT_RAM_ATTR;
/* Standard variable in internal SRAM */
uint32_t systemCounter;
Update the .sct file to define the ext_ram_zone section and map the Heap to external SRAM:
LR_IROM1 0x08000000 0x00100000 {
ER_IROM1 0x08000000 0x00100000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
; Internal SRAM: Stack, default RW/ZI, and RW-data used by Init
RW_IRAM1 0x20000000 0x00020000 {
*.o (STACK)
stm32f4xx_rcc.o (+RW)
.ANY (+RW +ZI)
}
; External SRAM: Custom section and Heap
RW_ERAM1 0x6C000000 0x00100000 {
.ANY (ext_ram_zone)
*(HEAP)
}
}
This configuration ensures that malloc allocates memory from the external 1MB SRAM pool, while the stack and frequently accessed global variables remain in the faster internal SRAM. This approach optimizes both performance and memory capacity.