Overview
In this tutorial, you will learn how to interface external SRAM with STM32F439 using the Flexible Memory Controller (FMC). This guide covers the exact steps required to integrate STM32F439 external SRAM into your system, with all the necessary code and hardware setup.
The goal of this project is not just to connect external memory, but to fully validate it through structured testing, integrate it into the system memory map, and demonstrate controlled data placement using a custom linker section.
What You Will Learn
- How STM32 FMC maps external SRAM into the address space
- How to configure FMC using STM32CubeMX
- How to validate external SRAM using pattern tests
- How to integrate external memory into a project using a linker section
- How to move initialized data into external SRAM at runtime
Prerequisites
This tutorial assumes familiarity with STM32CubeIDE, embedded C development, and basic digital hardware concepts. You should also be comfortable creating STM32 projects and modifying linker scripts.
Materials List
- STM32 Nucleo-F439ZI
- Custom FMC SRAM daughterboard (AS6C4008)
- USB cable for programming/debugging
- Optional: logic analyzer for signal validation
Project Structure
Core/
├── Inc/
│ ├── sram_validation.h
│ ├── sram_integration.h
│ ├── main.h
├── Src/
│ ├── main.c
│ ├── sram_validation.c
│ ├── sram_integration.c
│ ├── globalDataCopy.s
STM32F439ZITX_FLASH.ld
Hardware Configuration / Pinouts
PCB Overview

This PCB hosts the AS6C4008 SRAM and routes the FMC address, data, and control signals from the STM32F439. The wide parallel traces at the bottom of the board correspond to the address and data buses.
The PCB is designed as a daughterboard that plugs directly into the STM32 Nucleo-F439ZI through the two Morpho expansion connectors. Those connectors expose the STM32F439’s FMC-related GPIO signals, allowing the microcontroller to drive an external parallel SRAM device through the board-to-board connection rather than through flying wires or a separate cable harness.
On the SRAM board, two 70-pin female headers mate with the Nucleo-F439ZI Morpho headers. When the daughterboard is installed, the FMC address bus, data bus, and control lines are carried from the Nucleo into the SRAM PCB through those connectors. In other words, the Nucleo board is the active controller, and the daughterboard is the memory expansion hardware attached to it.
From the software point of view, the firmware does not communicate with the SRAM board using a serial protocol such as SPI or I2C. Instead, once the FMC peripheral is configured correctly, the external SRAM appears as a memory-mapped region in the STM32 address space starting at 0x60000000. Reads and writes are then performed using normal pointer-based memory access, while the FMC hardware automatically drives the necessary external bus timing over the Morpho connector pins.
This physical and logical arrangement is one of the main strengths of the design. The PCB provides the electrical routing and SRAM device, the Nucleo provides the MCU and FMC peripheral, and the Morpho connectors form the bridge between them. That makes it possible to validate the SRAM hardware, place selected data into external memory, and treat the daughterboard as a true memory expansion platform for the STM32F439.
Schematic
The schematic shows how the FMC peripheral connects to the external SRAM. Key signals include address lines, data lines, and control signals such as NOE, NWE, and chip enable.
Memory Mapping
The STM32F439 maps external FMC memory into the processor address space starting at 0x60000000. This project uses that base address for SRAM access, and all validation and integration code assumes this mapping.
CubeMX Configuration
Refer to the attached IOC report for full pin configuration and FMC timing setup.
Project Setup
Create a new STM32CubeIDE project targeting the STM32F439ZI. Enable FMC in CubeMX and configure it for asynchronous SRAM. Ensure timing parameters match your SRAM device.
Code Walkthrough
The project is divided into four main pieces: linker placement, startup relocation, validation, and integration. The application in main.c ties those pieces together and reports the test results over the console.
Linker Script (.sramdata section)
The linker script adds an external SRAM memory region and defines a .sramdata output section. Variables placed in that section live at 0x60000000 at runtime, while their initialized values are still stored in FLASH and copied over during startup.
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 2048K
/* External SRAM region mapped through FMC Bank1. */
SRAM (rw) : ORIGIN = 0x60000000, LENGTH = 512K
}
/* FLASH load address for initialized .sramdata contents.
* Used by globalDataCopy.s during startup.
*/
_sisram = LOADADDR(.sramdata);
/* Initialized data placed in external SRAM at runtime and stored in FLASH
* in the load image. Start/end are aligned to 4 bytes intentionally so
* globalDataCopy.s can copy the region as 32-bit words.
*/
.sramdata :
{
. = ALIGN(4);
_ssramdata = .; /* Start of .sramdata in external SRAM */
*(.sramdata) /* .sramdata sections */
*(.sramdata*) /* .sramdata* sections */
. = ALIGN(4);
_esramdata = .; /* End of .sramdata in external SRAM */
} > SRAM AT> FLASHStartup Copy Routine (globalDataCopy.s)
The startup copy routine uses the linker symbols from the previous section to move initialized data from FLASH into external SRAM before normal application code begins using that memory.
In this project, the startup copy is intentionally limited to variables that are explicitly placed into the custom .sramdata section. It is not a replacement for the normal STM32 startup copy of the standard .data section, and it does not relocate arbitrary memory ranges.
The source code marks these variables with the following macro:
#define SRAM __attribute__((section(".sramdata")))
Any initialized variable declared with that prefix is linked into the external SRAM runtime section. A representative example from this project is a declaration in the form of static SRAM FRAM_t testStruct = { ... };. At build time, the initial contents still live in the FLASH image. During startup, globalDataCopy.s copies only that .sramdata payload into the external SRAM region so the application sees the expected initialized values at runtime.
This approach is useful because it gives you explicit control over what lives in external memory. Large buffers, test structures, or other selected objects can be moved into SRAM without changing every global in the program. It also keeps the design easy to reason about: variables without the SRAM attribute continue to behave like normal internal-memory objects, while variables with the attribute are intentionally relocated into the FMC-mapped SRAM space.
/*
* @file globalDataCopy.s
* @brief Copies initialized .sramdata contents from FLASH into external SRAM.
*
* This routine uses linker-defined symbols:
* _sisram - FLASH load address of .sramdata
* _ssramdata - start address of .sramdata in external SRAM
* _esramdata - end address of .sramdata in external SRAM
*
* Design constraints:
* - Copy is performed in 32-bit words.
* - The linker script intentionally aligns the .sramdata start and end
* symbols to 4-byte boundaries.
* - This is a startup section copy helper for .sramdata, not a generic
* byte-oriented memcpy implementation.
*/
.syntax unified
.cpu cortex-m4
.text
.thumb
.global globalDataCopy
.p2align 2
.type globalDataCopy,%function
globalDataCopy:
.fnstart
/* Copy initialized .sramdata contents from FLASH into external SRAM. */
ldr r0, =_ssramdata // Start of .sramdata destination in external SRAM
ldr r1, =_esramdata // End of .sramdata destination in external SRAM
ldr r2, =_sisram // FLASH load address for initialized .sramdata contents
movs r3, #0 // Initialize r3 to zero, this is our byte counter
b LoopCopyDataInit // Jump to LooCopyDataInit, just in case there is nothing to do
CopyDataInit:
ldr r4, [r2, r3] // Copy 4 bytes from FLASH location into r4
// r2 is the location in FLASH to copy from and r3 is the offset
str r4, [r0, r3] // This stores the byte(s) from FLASH to SRAM. Store r4 into SRAM memory
// This also copies 4 bytes at a time because of alignment
adds r3, r3, #4 // Now we increment by 4 bytes.
LoopCopyDataInit:
adds r4, r0, r3 // Increment by 4 bytes. r4 is the location to copy to in the SRAM
cmp r4, r1 // Reached the end? r4 is the copy to location, and r1 is the end of SRAM
bcc CopyDataInit // If not continue to copy bytes
bx lr // Return by branching to the address in the link
// register.
.fnend
SRAM Validation Header (sram_validation.h)
This header defines the public validation interface, memory size constants, test pattern counts, and the report structure used to summarize the SRAM test results.
/**
* @file sram_validation.h
* @brief External SRAM daughterboard validation test interface.
*/
#ifndef SRAM_VALIDATION_H
#define SRAM_VALIDATION_H
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f4xx_hal.h"
#include <stdint.h>
/**
* @brief External SRAM base address on FMC Bank1 / NE1.
*/
#define SRAM_VALIDATION_BASE_ADDR ((uint32_t)0x60000000u)
/**
* @brief Total external SRAM size in bytes for the AS6C4008 device.
*/
#define SRAM_VALIDATION_TOTAL_SIZE_BYTES ((uint32_t)(512u * 1024u))
/**
* @brief Small-range test size in bytes.
*/
#define SRAM_VALIDATION_SMALL_RANGE_SIZE ((uint32_t)256u)
/**
* @brief Pattern-test size in bytes.
*/
#define SRAM_VALIDATION_PATTERN_TEST_SIZE ((uint32_t)(8u * 1024u))
/**
* @brief Number of alias probe offsets used by the alias test.
*/
#define SRAM_VALIDATION_ALIAS_COUNT ((uint32_t)19u)
/**
* @brief Validation test identifiers.
*/
typedef enum {
SRAM_VALIDATION_TEST_NONE = 0,
SRAM_VALIDATION_TEST_ALIAS_LINES,
SRAM_VALIDATION_TEST_SMALL_RANGE,
SRAM_VALIDATION_TEST_FULL_SWEEP,
SRAM_VALIDATION_TEST_PATTERN,
SRAM_VALIDATION_TEST_FULL_RANGE_PATTERN
} sramValidationTestId_t;
/**
* @brief Validation phase identifiers.
*/
typedef enum {
SRAM_VALIDATION_PHASE_IDLE = 0,
SRAM_VALIDATION_PHASE_PREPARE,
SRAM_VALIDATION_PHASE_FILL,
SRAM_VALIDATION_PHASE_VERIFY
} sramValidationPhase_t;
/**
* @brief Overall validation status.
*/
typedef enum {
SRAM_VALIDATION_STATUS_PASS = 0,
SRAM_VALIDATION_STATUS_FAIL
} sramValidationStatus_t;
/**
* @brief Detailed validation report kept in internal MCU RAM.
*/
typedef struct {
sramValidationStatus_t status;
sramValidationTestId_t failedTest;
sramValidationPhase_t failedPhase;
uint32_t failedOffset;
uint8_t expectedValue;
uint8_t actualValue;
uint32_t testsRun;
} sramValidationReport_t;
/**
* @brief Run the current SRAM validation tests and print results to stdout.
* @return Pointer to a persistent validation report structure.
*/
const sramValidationReport_t *SramValidation_RunAll(void);
/**
* @brief Get the most recent SRAM validation report.
* @return Pointer to a persistent validation report structure.
*/
const sramValidationReport_t *SramValidation_GetReport(void);
/**
* @brief Run the SRAM pattern test over a small region.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
HAL_StatusTypeDef SramValidation_RunPatternTest(void);
#ifdef __cplusplus
}
#endif
#endif /* SRAM_VALIDATION_H */
SRAM Validation Implementation (sram_validation.c)
This module performs the actual SRAM verification. It exercises address decoding, walking patterns, data bus behavior, and broader array tests so the daughterboard can be checked before trusting it for general storage.
/**
* @file sram_validation.c
* @brief External SRAM daughterboard validation tests using direct byte accesses.
*/
#include "sram_validation.h"
#include <stdio.h>
/**
* @brief Alias probe offsets used to catch missing or swapped address lines.
*/
static const uint32_t sramValidationAliasOffsets[SRAM_VALIDATION_ALIAS_COUNT] = {
0u,
1u,
2u,
4u,
8u,
16u,
32u,
64u,
128u,
256u,
512u,
1024u,
2048u,
4096u,
8192u,
16384u,
32768u,
65536u,
262144u
};
/**
* @brief Persistent validation report stored in internal MCU RAM.
*/
static sramValidationReport_t sramValidationReport;
/**
* @brief Return a byte pointer into the external SRAM window.
* @param offset Byte offset from SRAM_VALIDATION_BASE_ADDR.
* @return Volatile byte pointer for direct external SRAM access.
*/
static volatile uint8_t *SramValidation_GetBytePtr(uint32_t offset)
{
return (volatile uint8_t *)(SRAM_VALIDATION_BASE_ADDR + offset);
}
/**
* @brief Convert a test identifier to printable text.
* @param testId Test identifier.
* @return Pointer to static text.
*/
static const char *SramValidation_GetTestName(sramValidationTestId_t testId)
{
switch (testId) {
case SRAM_VALIDATION_TEST_ALIAS_LINES:
return "Address alias test";
case SRAM_VALIDATION_TEST_SMALL_RANGE:
return "Small range read/write test";
case SRAM_VALIDATION_TEST_FULL_SWEEP:
return "Full range sweep test";
case SRAM_VALIDATION_TEST_PATTERN:
return "Pattern data test";
case SRAM_VALIDATION_TEST_FULL_RANGE_PATTERN:
return "Full-range pattern data test";
case SRAM_VALIDATION_TEST_NONE:
default:
return "None";
}
}
/**
* @brief Convert a validation phase to printable text.
* @param phase Validation phase.
* @return Pointer to static text.
*/
static const char *SramValidation_GetPhaseName(sramValidationPhase_t phase)
{
switch (phase) {
case SRAM_VALIDATION_PHASE_PREPARE:
return "prepare";
case SRAM_VALIDATION_PHASE_FILL:
return "fill";
case SRAM_VALIDATION_PHASE_VERIFY:
return "verify";
case SRAM_VALIDATION_PHASE_IDLE:
default:
return "idle";
}
}
/**
* @brief Reset the persistent validation report to a passing default state.
*/
static void SramValidation_ResetReport(void)
{
sramValidationReport.status = SRAM_VALIDATION_STATUS_PASS;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_NONE;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_IDLE;
sramValidationReport.failedOffset = 0u;
sramValidationReport.expectedValue = 0u;
sramValidationReport.actualValue = 0u;
sramValidationReport.testsRun = 0u;
}
/**
* @brief Record a validation failure into the persistent report structure.
* @param testId Failing test identifier.
* @param phase Failing phase identifier.
* @param offset Byte offset associated with the failure.
* @param expectedValue Expected byte value.
* @param actualValue Actual byte value read back.
*/
static void SramValidation_RecordFailure(sramValidationTestId_t testId,
sramValidationPhase_t phase,
uint32_t offset,
uint8_t expectedValue,
uint8_t actualValue)
{
sramValidationReport.status = SRAM_VALIDATION_STATUS_FAIL;
sramValidationReport.failedTest = testId;
sramValidationReport.failedPhase = phase;
sramValidationReport.failedOffset = offset;
sramValidationReport.expectedValue = expectedValue;
sramValidationReport.actualValue = actualValue;
}
/**
* @brief Print the current persistent validation failure in a readable form.
*/
static void SramValidation_PrintFailure(void)
{
printf(" FAIL\r\n");
printf(" Test : %s\r\n", SramValidation_GetTestName(sramValidationReport.failedTest));
printf(" Phase : %s\r\n", SramValidation_GetPhaseName(sramValidationReport.failedPhase));
printf(" Offset : 0x%08lX\r\n", (unsigned long)sramValidationReport.failedOffset);
printf(" Expected: 0x%02X\r\n", sramValidationReport.expectedValue);
printf(" Actual : 0x%02X\r\n", sramValidationReport.actualValue);
}
/**
* @brief Run the SRAM address-alias validation test.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
static HAL_StatusTypeDef SramValidation_RunAliasTest(void)
{
uint32_t index;
uint32_t verifyIndex;
uint32_t offset;
uint8_t expectedValue;
uint8_t actualValue;
volatile uint8_t *bytePtr;
sramValidationReport.testsRun++;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_ALIAS_LINES;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_PREPARE;
printf("[TEST 1] %s\r\n", SramValidation_GetTestName(SRAM_VALIDATION_TEST_ALIAS_LINES));
for (index = 0u; index < SRAM_VALIDATION_ALIAS_COUNT; index++) {
offset = sramValidationAliasOffsets[index];
bytePtr = SramValidation_GetBytePtr(offset);
*bytePtr = (uint8_t)(0xA0u + index);
}
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_VERIFY;
for (index = 0u; index < SRAM_VALIDATION_ALIAS_COUNT; index++) {
offset = sramValidationAliasOffsets[index];
expectedValue = (uint8_t)(0xA0u + index);
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_ALIAS_LINES,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
return HAL_ERROR;
}
for (verifyIndex = 0u; verifyIndex < index; verifyIndex++) {
offset = sramValidationAliasOffsets[verifyIndex];
expectedValue = (uint8_t)(0xA0u + verifyIndex);
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_ALIAS_LINES,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
return HAL_ERROR;
}
}
}
printf(" PASS\r\n");
return HAL_OK;
}
/**
* @brief Run a small-range SRAM write/read validation test.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
static HAL_StatusTypeDef SramValidation_RunSmallRangeTest(void)
{
uint32_t offset;
uint8_t expectedValue;
uint8_t actualValue;
volatile uint8_t *bytePtr;
sramValidationReport.testsRun++;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_SMALL_RANGE;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_FILL;
printf("[TEST 2] %s\r\n", SramValidation_GetTestName(SRAM_VALIDATION_TEST_SMALL_RANGE));
for (offset = 0u; offset < SRAM_VALIDATION_SMALL_RANGE_SIZE; offset++) {
expectedValue = (uint8_t)(offset & 0xFFu);
bytePtr = SramValidation_GetBytePtr(offset);
*bytePtr = expectedValue;
}
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_VERIFY;
for (offset = 0u; offset < SRAM_VALIDATION_SMALL_RANGE_SIZE; offset++) {
expectedValue = (uint8_t)(offset & 0xFFu);
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_SMALL_RANGE,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
return HAL_ERROR;
}
}
printf(" PASS\r\n");
return HAL_OK;
}
/**
* @brief Run a full-range SRAM sweep validation test.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
static HAL_StatusTypeDef SramValidation_RunFullSweepTest(void)
{
uint32_t offset;
uint8_t expectedValue;
uint8_t actualValue;
volatile uint8_t *bytePtr;
sramValidationReport.testsRun++;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_FULL_SWEEP;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_FILL;
printf("[TEST 3] %s\r\n", SramValidation_GetTestName(SRAM_VALIDATION_TEST_FULL_SWEEP));
for (offset = 0u; offset < SRAM_VALIDATION_TOTAL_SIZE_BYTES; offset++) {
expectedValue = (uint8_t)(offset & 0xFFu);
bytePtr = SramValidation_GetBytePtr(offset);
*bytePtr = expectedValue;
}
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_VERIFY;
for (offset = 0u; offset < SRAM_VALIDATION_TOTAL_SIZE_BYTES; offset++) {
expectedValue = (uint8_t)(offset & 0xFFu);
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_FULL_SWEEP,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
return HAL_ERROR;
}
}
printf(" PASS\r\n");
return HAL_OK;
}
/**
* @brief Run the SRAM full-range pattern test using simple scrolling output.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
static HAL_StatusTypeDef SramValidation_RunFullRangePatternTest(void)
{
static const uint8_t patterns[] = {
0x00u,
0xFFu,
0x55u,
0xAAu,
0x01u,
0x02u,
0x04u,
0x08u,
0x10u,
0x20u,
0x40u,
0x80u,
0xFEu,
0xFDu,
0xFBu,
0xF7u,
0xEFu,
0xDFu,
0xBFu,
0x7Fu
};
uint32_t patternCount;
uint32_t patternIndex;
uint32_t offset;
uint32_t progressStep;
uint32_t nextProgressOffset;
volatile uint8_t *bytePtr;
uint8_t expectedValue;
uint8_t actualValue;
HAL_StatusTypeDef status;
status = HAL_OK;
patternCount = (uint32_t)(sizeof(patterns) / sizeof(patterns[0]));
progressStep = SRAM_VALIDATION_TOTAL_SIZE_BYTES / 4u;
sramValidationReport.testsRun++;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_FULL_RANGE_PATTERN;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_PREPARE;
printf("\r\n[TEST 5] %s\r\n", SramValidation_GetTestName(SRAM_VALIDATION_TEST_FULL_RANGE_PATTERN));
printf(" SRAM size : %lu KB\r\n", (unsigned long)(SRAM_VALIDATION_TOTAL_SIZE_BYTES / 1024u));
printf(" Pattern set : %lu patterns\r\n", (unsigned long)patternCount);
printf(" Progress : scrolling log output\r\n");
printf("\r\n");
for (patternIndex = 0u; patternIndex < patternCount; patternIndex++) {
expectedValue = patterns[patternIndex];
char buffer[80];
sprintf(buffer, " [%2lu/%2lu] Pattern 0x%02X\r\n",
(unsigned long)(patternIndex + 1u),
(unsigned long)patternCount,
expectedValue);
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_FILL;
nextProgressOffset = progressStep;
printf(" Fill 0%%\r\n");
for (offset = 0u; offset < SRAM_VALIDATION_TOTAL_SIZE_BYTES; offset++) {
bytePtr = SramValidation_GetBytePtr(offset);
*bytePtr = expectedValue;
if (offset + 1u == nextProgressOffset) {
if (nextProgressOffset < SRAM_VALIDATION_TOTAL_SIZE_BYTES) {
printf(" Fill %lu%%\r\n", (unsigned long)(((offset + 1u) * 100u) / SRAM_VALIDATION_TOTAL_SIZE_BYTES));
}
nextProgressOffset += progressStep;
}
}
printf(" Fill 100%%\r\n");
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_VERIFY;
nextProgressOffset = progressStep;
printf(" Verify 0%%\r\n");
for (offset = 0u; offset < SRAM_VALIDATION_TOTAL_SIZE_BYTES; offset++) {
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_FULL_RANGE_PATTERN,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
status = HAL_ERROR;
break;
}
if (offset + 1u == nextProgressOffset) {
if (nextProgressOffset < SRAM_VALIDATION_TOTAL_SIZE_BYTES) {
printf(" Verify %lu%%\r\n", (unsigned long)(((offset + 1u) * 100u) / SRAM_VALIDATION_TOTAL_SIZE_BYTES));
}
nextProgressOffset += progressStep;
}
}
if (status != HAL_OK) {
return status;
}
printf(" Verify 100%%\r\n");
printf(" Pattern 0x%02X PASS\r\n\r\n", expectedValue);
}
printf("\r\n[TEST 5] PASS\r\n");
return status;
}
/**
* @brief Run the SRAM pattern test over a small region.
* @return HAL_OK on success, HAL_ERROR on failure.
*/
HAL_StatusTypeDef SramValidation_RunPatternTest(void)
{
static const uint8_t patterns[] = {
0x00u,
0xFFu,
0x55u,
0xAAu,
0x01u,
0x02u,
0x04u,
0x08u,
0x10u,
0x20u,
0x40u,
0x80u,
0xFEu,
0xFDu,
0xFBu,
0xF7u,
0xEFu,
0xDFu,
0xBFu,
0x7Fu
};
uint32_t patternIndex;
uint32_t offset;
volatile uint8_t *bytePtr;
uint8_t expectedValue;
uint8_t actualValue;
HAL_StatusTypeDef status;
status = HAL_OK;
sramValidationReport.testsRun++;
sramValidationReport.failedTest = SRAM_VALIDATION_TEST_PATTERN;
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_PREPARE;
printf("[TEST 4] %s\r\n", SramValidation_GetTestName(SRAM_VALIDATION_TEST_PATTERN));
for (patternIndex = 0u; patternIndex < (sizeof(patterns) / sizeof(patterns[0])); patternIndex++) {
expectedValue = patterns[patternIndex];
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_FILL;
for (offset = 0u; offset < SRAM_VALIDATION_PATTERN_TEST_SIZE; offset++) {
bytePtr = SramValidation_GetBytePtr(offset);
*bytePtr = expectedValue;
}
sramValidationReport.failedPhase = SRAM_VALIDATION_PHASE_VERIFY;
for (offset = 0u; offset < SRAM_VALIDATION_PATTERN_TEST_SIZE; offset++) {
bytePtr = SramValidation_GetBytePtr(offset);
actualValue = *bytePtr;
if (actualValue != expectedValue) {
SramValidation_RecordFailure(SRAM_VALIDATION_TEST_PATTERN,
SRAM_VALIDATION_PHASE_VERIFY,
offset,
expectedValue,
actualValue);
SramValidation_PrintFailure();
status = HAL_ERROR;
break;
}
}
if (status != HAL_OK) {
break;
}
}
if (status == HAL_OK) {
printf(" PASS\r\n");
}
return status;
}
/**
* @brief Run the current SRAM validation tests and print results to stdout.
* @return Pointer to a persistent validation report structure.
*/
const sramValidationReport_t *SramValidation_RunAll(void)
{
HAL_StatusTypeDef status;
SramValidation_ResetReport();
printf("SRAM validation start\r\n\r\n");
status = SramValidation_RunAliasTest();
if (status != HAL_OK) {
printf("\r\nSRAM validation complete: FAIL\r\n");
return &sramValidationReport;
}
printf("\r\n");
status = SramValidation_RunSmallRangeTest();
if (status != HAL_OK) {
printf("\r\nSRAM validation complete: FAIL\r\n");
return &sramValidationReport;
}
printf("\r\n");
status = SramValidation_RunFullSweepTest();
if (status != HAL_OK) {
printf("\r\nSRAM validation complete: FAIL\r\n");
return &sramValidationReport;
}
printf("\r\n");
status = SramValidation_RunPatternTest();
if (status != HAL_OK) {
printf("\r\nSRAM validation complete: FAIL\r\n");
return &sramValidationReport;
}
printf("\r\n");
status = SramValidation_RunFullRangePatternTest();
if (status != HAL_OK) {
printf("\r\nSRAM validation complete: FAIL\r\n");
return &sramValidationReport;
}
printf("\r\nSRAM validation complete: PASS\r\n");
return &sramValidationReport;
}
/**
* @brief Get the most recent SRAM validation report.
* @return Pointer to the persistent validation report structure.
*/
const sramValidationReport_t *SramValidation_GetReport(void)
{
return &sramValidationReport;
}
SRAM Integration Header (sram_integration.h)
The integration header exposes the application-facing SRAM integration test entry point.
/**
* @file sram_integration.h
* @brief External SRAM integration test interface.
*/
#ifndef SRAM_INTEGRATION_H
#define SRAM_INTEGRATION_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Run the SRAM integration struct initialization and modification test.
*/
void SramIntegration_RunStructTest(void);
#ifdef __cplusplus
}
#endif
#endif /* SRAM_INTEGRATION_H */
SRAM Integration Implementation (sram_integration.c)
This module demonstrates placing structured initialized data into the external SRAM section and then modifying it at runtime. It proves that linker placement and startup relocation are both working as intended.
/**
* @file sram_integration.c
* @brief SRAM integration test implementation.
*/
#include "sram_integration.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define SRAM __attribute__((section(".sramdata")))
/**
* @brief Nested array test structure stored inside the SRAM integration object.
*/
typedef struct {
int table[5];
} arrayTest_t;
/**
* @brief Composite SRAM integration test structure.
*/
typedef struct {
int test1;
char buf[100];
float myFloat;
arrayTest_t myArray;
int test2;
} FRAM_t;
/**
* @brief Initialized SRAM-resident integration test object.
*/
static SRAM FRAM_t testStruct = {
.test1 = 100,
.buf = "This is a test",
.myFloat = 3.1415f,
.myArray = { .table = {200, 0, 0, 0, 0} },
.test2 = 10
};
/**
* @brief Prints the current SRAM integration test structure state.
* @param title Section title to print before the structure contents
*/
static void SramIntegration_PrintStructState(const char *title)
{
printf("\r\n[%s]\r\n", title);
printf(" test1 = %d\r\n", testStruct.test1);
printf(" buf = %s\r\n", testStruct.buf);
printf(" float = %.4f\r\n", testStruct.myFloat);
printf(" array0 = %d\r\n", testStruct.myArray.table[0]);
printf(" array1 = %d\r\n", testStruct.myArray.table[1]);
printf(" array2 = %d\r\n", testStruct.myArray.table[2]);
printf(" array3 = %d\r\n", testStruct.myArray.table[3]);
printf(" array4 = %d\r\n", testStruct.myArray.table[4]);
printf(" test2 = %d\r\n", testStruct.test2);
}
/**
* @brief Modifies selected SRAM integration test structure fields.
*/
static void SramIntegration_ModifyStruct(void)
{
const char *text;
text = "The fox became very ill and wasn't feeling well";
snprintf(testStruct.buf, sizeof(testStruct.buf), "%s", text);
testStruct.myFloat = testStruct.myFloat / 2.0f;
testStruct.myArray.table[1] = 111;
testStruct.myArray.table[2] = 222;
testStruct.myArray.table[3] = 333;
testStruct.myArray.table[4] = 444;
}
/**
* @brief Verifies the SRAM integration test structure contents.
* @return 1 if all checks pass, otherwise 0
*/
static uint8_t SramIntegration_VerifyStruct(void)
{
const char *text;
uint8_t testPass;
text = "The fox became very ill and wasn't feeling well";
testPass = 1u;
if (testStruct.test1 != 100) {
testPass = 0u;
}
if (strcmp(testStruct.buf, text) != 0) {
testPass = 0u;
}
if ((testStruct.myFloat < 1.5707f) || (testStruct.myFloat > 1.5708f)) {
testPass = 0u;
}
if (testStruct.myArray.table[0] != 200) {
testPass = 0u;
}
if (testStruct.myArray.table[1] != 111) {
testPass = 0u;
}
if (testStruct.myArray.table[2] != 222) {
testPass = 0u;
}
if (testStruct.myArray.table[3] != 333) {
testPass = 0u;
}
if (testStruct.myArray.table[4] != 444) {
testPass = 0u;
}
if (testStruct.test2 != 10) {
testPass = 0u;
}
return testPass;
}
/**
* @brief Runs the SRAM integration struct initialization and modification test.
*/
void SramIntegration_RunStructTest(void)
{
uint8_t testPass;
SramIntegration_PrintStructState("SRAM INIT TEST");
SramIntegration_ModifyStruct();
SramIntegration_PrintStructState("SRAM MODIFY TEST");
testPass = SramIntegration_VerifyStruct();
if (testPass == 1u) {
printf(" RESULT = PASS\r\n");
} else {
printf(" RESULT = FAIL\r\n");
}
}
main.h
The generated project header contains the board-level pin definitions and common declarations used by main.c.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.h
* @brief : Header for main.c file.
* This file contains the common defines of the application.
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */
/* USER CODE END ET */
/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */
/* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */
/* USER CODE END EM */
/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);
/* USER CODE BEGIN EFP */
/* USER CODE END EFP */
/* Private defines -----------------------------------------------------------*/
#define USB_PowerSwitchOn_Pin GPIO_PIN_6
#define USB_PowerSwitchOn_GPIO_Port GPIOG
#define USB_OverCurrent_Pin GPIO_PIN_7
#define USB_OverCurrent_GPIO_Port GPIOG
#define OE_Pin GPIO_PIN_4
#define OE_GPIO_Port GPIOD
#define WE_Pin GPIO_PIN_5
#define WE_GPIO_Port GPIOD
#define CS_Pin GPIO_PIN_7
#define CS_GPIO_Port GPIOD
/* USER CODE BEGIN Private defines */
#define REDIRECT_PRINTF
/* USER CODE END Private defines */
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */
main.c
The main.c file contains the top-level execution flow for the project. After HAL initialization, clock setup, GPIO configuration, FMC initialization, and USART bring-up, the application chooses between two different SRAM test paths using the SRAM_INTEGRATION_TEST_ENABLE build-time switch.
With the macro left disabled, the project runs the full destructive SRAM validation suite. This is the recommended first step because it verifies address integrity, basic read/write behavior, full-range coverage, and multi-pattern data correctness across the full 512 KB memory device. When the macro is enabled, main() instead runs the smaller integration test that proves initialized application data tagged with the SRAM attribute is copied into external SRAM correctly at startup.
Important note: an earlier revision of the project had a larger explanatory comment directly near the #ifdef SRAM_INTEGRATION_TEST_ENABLE path inside main(). That explanation should be restored in the source so readers can immediately understand why the application has both a raw validation mode and a linker/startup integration mode.
The main application initializes the MCU, GPIO, USART, and FMC, then runs the SRAM validation and integration tests. This is the top-level control flow for the complete project.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "sram_validation.h"
#include "sram_integration.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define SRAM __attribute__((section(".sramdata")))
#define SRAM_BANK_ADDR ((uint32_t)0x60000000)
#define SRAM_MEMORY_WIDTH FMC_NORSRAM_MEM_BUS_WIDTH_8
//#define SRAM_MEMORY_WIDTH FMC_NORSRAM_MEM_BUS_WIDTH_16
#define SRAM_TIMEOUT ((uint32_t)0xFFFF)
#define BUFFER_SIZE ((uint32_t)16)
#define WRITE_READ_ADDR ((uint32_t)0x0800)
/**
* @brief SRAM status definition
*/
#define SRAM_STATUS_OK 0x00
#define SRAM_STATUS_INIT_ERROR 0x01
#define SRAM_CS_ENABLE HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, 0);
#define SRAM_CS_DISABLE HAL_GPIO_WritePin(GPIOD, GPIO_PIN_7, 1);
/*
* Developer build-time switch for the SRAM integration path.
*
* Enable this macro when verifying linker/startup-driven placement of
* initialized application data in external SRAM. When enabled, main() runs
* SramIntegration_RunStructTest() instead of the destructive raw-memory
* validation suite. Leave it disabled for the default full validation flow.
*
* See the SRAM Integration Flow documentation for the complete explanation of
* why this switch exists and how it changes execution behavior.
*/
//#define SRAM_INTEGRATION_TEST_ENABLE
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
SRAM_HandleTypeDef hsram1;
/* USER CODE BEGIN PV */
#ifndef SRAM_INTEGRATION_TEST_ENABLE
static const sramValidationReport_t *gSramValidationReport = NULL;
#endif
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_FMC_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
extern void globalDataCopy(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef REDIRECT_PRINTF
/**
* @brief Retargets printf output to USART1 one character at a time.
* @param ch Character to transmit
*/
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
PUTCHAR_PROTOTYPE
{
HAL_StatusTypeDef status;
status = HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
if (status != HAL_OK) {
/* Debug output path: ignore transmit failure. */
}
return ch;
}
#endif
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("\x1b[2J\x1b[H"); // Clear the dumb terminal screen
globalDataCopy();
#ifdef SRAM_INTEGRATION_TEST_ENABLE
SramIntegration_RunStructTest();
#else
gSramValidationReport = SramValidation_RunAll();
#endif
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 180;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Activate the Over-Drive mode
*/
if (HAL_PWREx_EnableOverDrive() != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 19200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/* FMC initialization function */
static void MX_FMC_Init(void)
{
/* USER CODE BEGIN FMC_Init 0 */
/* USER CODE END FMC_Init 0 */
FMC_NORSRAM_TimingTypeDef Timing = {0};
/* USER CODE BEGIN FMC_Init 1 */
/* USER CODE END FMC_Init 1 */
/** Perform the SRAM1 memory initialization sequence
*/
hsram1.Instance = FMC_NORSRAM_DEVICE;
hsram1.Extended = FMC_NORSRAM_EXTENDED_DEVICE;
/* hsram1.Init */
hsram1.Init.NSBank = FMC_NORSRAM_BANK1;
hsram1.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
hsram1.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8;
hsram1.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
hsram1.Init.WrapMode = FMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
hsram1.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
hsram1.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY;
hsram1.Init.PageSize = FMC_PAGE_SIZE_NONE;
/* Timing */
Timing.AddressSetupTime = 1;
Timing.AddressHoldTime = 15;
Timing.DataSetupTime = 10;
Timing.BusTurnAroundDuration = 0;
Timing.CLKDivision = 16;
Timing.DataLatency = 17;
Timing.AccessMode = FMC_ACCESS_MODE_A;
/* ExtTiming */
if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FMC_Init 2 */
/* USER CODE END FMC_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(USB_PowerSwitchOn_GPIO_Port, USB_PowerSwitchOn_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : USB_PowerSwitchOn_Pin */
GPIO_InitStruct.Pin = USB_PowerSwitchOn_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(USB_PowerSwitchOn_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : USB_OverCurrent_Pin */
GPIO_InitStruct.Pin = USB_OverCurrent_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(USB_OverCurrent_GPIO_Port, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
Testing / Verification
After flashing the board, open the configured USART console and observe the validation output. The default firmware path runs the destructive validation suite, which is intended to prove that the FMC interface, PCB routing, external SRAM device, and software configuration are all working together correctly before moving on to the structured integration demonstration.
The five validation tests are designed to answer different questions about the hardware. The address alias test checks that unique addresses really map to unique SRAM locations. The small range read/write test confirms basic FMC read and write cycles. The full range sweep test verifies that the entire 512 KB device is accessible. The pattern tests then stress the data bus with alternating and walking-bit values to catch stuck bits, shorted lines, and data corruption that may not appear during a simple write/read test.
- Test 1: detects address aliasing and address-line wiring faults
- Test 2: confirms basic read/write operation over a smaller working region
- Test 3: verifies the full external SRAM address space can be swept successfully
- Test 4: checks data bus integrity using selected fixed patterns
- Test 5: performs the deepest validation by applying 20 patterns across the full 512 KB region with fill and verify progress reporting
The captured output below is a shortened excerpt from a real successful validation run using the default code path:
SRAM validation start
[TEST 1] Address alias test
PASS
[TEST 2] Small range read/write test
PASS
[TEST 3] Full range sweep test
PASS
[TEST 4] Pattern data test
PASS
[TEST 5] Full-range pattern data test
SRAM size : 512 KB
Pattern set : 20 patterns
Progress : scrolling log output
Fill 0%
Fill 25%
Fill 50%
Fill 75%
Fill 100%
Verify 0%
Verify 25%
Verify 50%
Verify 75%
Verify 100%
Pattern 0x00 PASS
...
[TEST 5] PASS
SRAM validation complete: PASS
A complete pass means the SRAM subsystem is operating reliably as a memory-mapped external device. In practical terms, it means the address bus is decoding correctly, the data bus is not showing stuck or shorted-bit behavior, the FMC timing is good enough for sustained accesses, and the full 512 KB memory range can be filled and verified repeatedly without corruption.
The validation output shown above is a shortened excerpt. The full SRAM validation log captures the complete sequence of tests, including all pattern sweeps and progress output.
You can download the full validation log below to review the complete test results or compare against your own system.
Once the destructive validation suite passes, you can enable the integration test path by turning on SRAM_INTEGRATION_TEST_ENABLE. This path is smaller and non-destructive. Its purpose is to prove that variables explicitly declared with the SRAM attribute are placed in the custom .sramdata section, loaded from FLASH at startup, and then accessed correctly from external SRAM during normal application execution.
The integration test output is short enough to show in full. It demonstrates that initialized data, strings, floating-point values, and array members all survive the startup relocation process and can then be modified successfully in place:
[SRAM INIT TEST]
test1 = 100
buf = This is a test
float = 3.1415
array0 = 200
array1 = 0
array2 = 0
array3 = 0
array4 = 0
test2 = 10
[SRAM MODIFY TEST]
test1 = 100
buf = The fox became very ill and wasn't feeling well
float = 1.5707
array0 = 200
array1 = 111
array2 = 222
array3 = 333
array4 = 444
test2 = 10
RESULT = PASS
This second output is especially useful because it proves the project is doing more than raw SRAM transactions. It shows that real application data structures can be initialized in the firmware image, copied into external SRAM at boot by globalDataCopy.s, and then read and modified successfully through ordinary C code.
If you have a logic analyzer, you can also probe the FMC address, data, and control lines during the test run to confirm that chip enable, read enable, and write enable activity matches the expected access patterns.
Troubleshooting
- If the MCU faults immediately when accessing SRAM, double-check the FMC timing configuration, FMC pin assignments, and verify that the SRAM device is really mapped at
0x60000000. - If Test 1 (address alias test) fails, suspect a wiring, routing, or soldering problem on one or more address lines. Aliasing often indicates that two logical addresses are unintentionally reaching the same physical memory cell.
- If Test 2 (small range read/write) fails early, review basic FMC configuration first. Incorrect data width, access timing, or chip enable behavior can prevent even simple transactions from working.
- If Test 3 (full range sweep) fails after some addresses pass, verify that the full memory range is decoded correctly and that the board really supports the configured 512 KB span without missing or misrouted address lines.
- If Test 4 or Test 5 fails on specific patterns such as
0x55,0xAA, or walking-bit tests, suspect data bus integrity issues such as stuck bits, shorted data lines, or marginal timing. - If initialized data in
.sramdatalooks wrong during the integration test, verify the linker section, theSRAMattribute macro, the startup copy routine, and that the symbols_sisram,_ssramdata, and_esramdataare all consistent. - If the integration test path works inconsistently, confirm which code path
main()is taking and whetherSRAM_INTEGRATION_TEST_ENABLEis enabled or commented out in the current build. - If console output is inconsistent, verify the USART retargeting path and make sure the validation status messages are actually reaching your terminal application.
Conclusion
This project shows the full path from hardware connection to software validation for an external SRAM daughterboard on the STM32F439. By combining FMC configuration, validation tests, linker placement, and a startup relocation routine, you can treat off-chip SRAM as a deliberate part of the system memory map rather than just a raw peripheral.
Related Tutorials
- STM32CubeMX FMC peripheral configuration walkthrough
- Custom linker sections on STM32
- Startup assembly customization on Cortex-M
- External memory validation strategies for hobbyist PCB projects
