2025-08-11 6:32 AM - last edited on 2025-08-11 6:50 AM by Andrew Neil
Hello,
I am currently testing the MT25QL512ABB flash memory on an STM32H747I-DISCO board.
The board has two of these flash chips, and I am attempting to operate them in dual mode.
I am using the driver from the official STMicroelectronics repository available at this URL:
https://github.com/STMicroelectronics/stm32-mt25ql512abb
My test code is designed to do the following:
On the first boot, it writes data to a specific region of the flash.
It then immediately reads back the data from that same region to verify that the write operation was successful.
After a reboot, the code reads the same flash region again to ensure the data is still correct.
The initial write and immediate read-back verification works perfectly. However, after I reboot the board, the subsequent read operation fails. The data read from the flash does not match the data that was originally written.
I have already disabled all cache functions, so I don't believe this is a caching issue.
I am looking for advice on what could be causing this problem.
Thanks.
void MX_QUADSPI_Init(void)
{
/* USER CODE BEGIN QUADSPI_Init 0 */
/* USER CODE END QUADSPI_Init 0 */
/* USER CODE BEGIN QUADSPI_Init 1 */
hqspi.Init.FlashID = QSPI_FLASH_ID_2;
/* USER CODE END QUADSPI_Init 1 */
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 1;
hqspi.Init.FifoThreshold = 1;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = 26;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.DualFlash = QSPI_DUALFLASH_ENABLE;
if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN QUADSPI_Init 2 */
/* USER CODE END QUADSPI_Init 2 */
}
#include "main.h"
#include "quadspi.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mt25ql512abb.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* DUAL_CORE_BOOT_SYNC_SEQUENCE: Define for dual core boot synchronization */
/* demonstration code based on hardware semaphore */
/* This define is present in both CM7/CM4 projects */
/* To comment when developping/debugging on a single core */
#define DUAL_CORE_BOOT_SYNC_SEQUENCE
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
#ifndef HSEM_ID_0
#define HSEM_ID_0 (0U) /* HW semaphore 0*/
#endif
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
int QSPI_Write_Full_Chip(void);
int QSPI_Verify_Full_Chip(void);
int Erase_Test_Flag(void);
int Write_Test_Flag(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* Test configuration defines */
#define QSPI_FLASH_SIZE_128MB (0x8000000) /* 128 MB */
#define QSPI_SECTOR_SIZE_64KB (0x10000) /* 64 KB */
#define QSPI_PAGE_SIZE (256) /* 256 Bytes */
#define TEST_BUFFER_SIZE (256) /* Test buffer size, same as page size */
#define NUM_SECTORS (QSPI_FLASH_SIZE_128MB / QSPI_SECTOR_SIZE_64KB)
// Use the LAST sector for the state flag to avoid collision with test data
#define TEST_STATE_FLAG_ADDRESS ((NUM_SECTORS - 1) * QSPI_SECTOR_SIZE_64KB)
#define WRITE_COMPLETE_FLAG 0x5A5A5A5A // Magic number to indicate write is complete
uint8_t aTxBuffer[TEST_BUFFER_SIZE];
uint8_t aRxBuffer[TEST_BUFFER_SIZE];
MT25QL512ABB_Info_t flashInfo;
uint8_t id[6];
/**
* @brief Writes a test pattern to 1/10th of the flash memory.
* @retval 1 on success, 0 on failure.
*/
int QSPI_Write_Full_Chip(void)
{
// MODIFIED: Test only 1/10th of the data sectors to save time.
// The last sector is reserved for the flag.
uint32_t num_data_sectors = (NUM_SECTORS - 1) / 10;
uint32_t num_pages_per_sector = QSPI_SECTOR_SIZE_64KB / TEST_BUFFER_SIZE;
// --- Write test pattern to all data sectors ---
for(uint32_t i = 0;i < num_data_sectors;i++)
{
uint32_t sector_base_addr = i * QSPI_SECTOR_SIZE_64KB;
uint32_t page_index_in_sector = i % num_pages_per_sector;
uint32_t current_addr = sector_base_addr + (page_index_in_sector * TEST_BUFFER_SIZE);
// 1. Prepare data with a pattern unique to the address
for(uint16_t j = 0;j < TEST_BUFFER_SIZE;j++)
{
aTxBuffer[j] = j;
}
// 2. Erase the entire 64KB sector
if(MT25QL512ABB_WriteEnable(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_BlockErase(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, sector_base_addr, MT25QL512ABB_ERASE_64K) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_AutoPollingMemReady(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
// 3. Write data to the calculated page address
if(MT25QL512ABB_WriteEnable(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_PageProgram(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, aTxBuffer, current_addr, TEST_BUFFER_SIZE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_AutoPollingMemReady(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_ReadSTR(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, aRxBuffer, current_addr, TEST_BUFFER_SIZE) != MT25QL512ABB_OK)
{
return 0;
}
// 3. Verify data
for(uint16_t j = 0;j < TEST_BUFFER_SIZE;j++)
{
if(aRxBuffer[j] != aTxBuffer[j])
{
return 0; // Failure
}
}
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin); // Toggle Blue LED during write
}
return 1; // Success
}
/**
* @brief Verifies the test pattern across 1/10th of the data sectors.
* @retval 1 on success, 0 on failure.
*/
int QSPI_Verify_Full_Chip(void)
{
// MODIFIED: Verify only 1/10th of the data sectors to save time.
// The last sector is reserved for the flag.
uint32_t num_data_sectors = (NUM_SECTORS - 1) / 10;
uint32_t num_pages_per_sector = QSPI_SECTOR_SIZE_64KB / TEST_BUFFER_SIZE;
for(uint32_t i = 0;i < num_data_sectors;i++)
{
uint32_t sector_base_addr = i * QSPI_SECTOR_SIZE_64KB;
uint32_t page_index_in_sector = i % num_pages_per_sector;
uint32_t current_addr = sector_base_addr + (page_index_in_sector * TEST_BUFFER_SIZE);
// 1. Generate the expected data pattern locally
for(uint16_t j = 0;j < TEST_BUFFER_SIZE;j++)
{
aTxBuffer[j] = j;
}
// 2. Read data back from flash
if(MT25QL512ABB_ReadSTR(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, aRxBuffer, current_addr, TEST_BUFFER_SIZE) != MT25QL512ABB_OK)
{
return 0;
}
// 3. Verify data
for(uint16_t j = 0;j < TEST_BUFFER_SIZE;j++)
{
if(aRxBuffer[j] != aTxBuffer[j])
{
return 0; // Failure
}
}
HAL_GPIO_TogglePin(LED4_GPIO_Port, LED4_Pin); // Toggle Blue LED during verify
}
return 1; // Success
}
/**
* @brief Writes the 'Write Complete' flag to its dedicated sector.
* @retval 1 on success, 0 on failure.
*/
int Write_Test_Flag(void)
{
uint32_t flag_data = WRITE_COMPLETE_FLAG;
// Erase the flag sector first
if(MT25QL512ABB_WriteEnable(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_BlockErase(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, TEST_STATE_FLAG_ADDRESS,
MT25QL512ABB_ERASE_64K) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_AutoPollingMemReady(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
// Write the flag
if(MT25QL512ABB_WriteEnable(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_PageProgram(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, (uint8_t*) &flag_data, TEST_STATE_FLAG_ADDRESS,
sizeof(flag_data)) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_AutoPollingMemReady(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
return 1;
}
/**
* @brief Erases the flash sector dedicated to the test state flag.
* @retval 1 on success, 0 on failure.
*/
int Erase_Test_Flag(void)
{
if(MT25QL512ABB_WriteEnable(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
// Erase the last 64KB sector, which is dedicated for our flag.
if(MT25QL512ABB_BlockErase(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, TEST_STATE_FLAG_ADDRESS,
MT25QL512ABB_ERASE_64K) != MT25QL512ABB_OK)
return 0;
if(MT25QL512ABB_AutoPollingMemReady(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_DUALFLASH_ENABLE) != MT25QL512ABB_OK)
return 0;
return 1;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* USER CODE BEGIN Boot_Mode_Sequence_0 */
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
int32_t timeout;
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
/* USER CODE END Boot_Mode_Sequence_0 */
/* USER CODE BEGIN Boot_Mode_Sequence_1 */
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
/* Wait until CPU2 boots and enters in stop mode or timeout*/
timeout = 0xFFFF;
while((__HAL_RCC_GET_FLAG(RCC_FLAG_D2CKRDY) != RESET) && (timeout-- > 0))
;
if(timeout < 0)
{
Error_Handler();
}
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
/* USER CODE END Boot_Mode_Sequence_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 Boot_Mode_Sequence_2 */
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
/* When system initialization is finished, Cortex-M7 will release Cortex-M4 by means of
HSEM notification */
/*HW semaphore Clock enable*/
__HAL_RCC_HSEM_CLK_ENABLE();
/*Take HSEM */
HAL_HSEM_FastTake(HSEM_ID_0);
/*Release HSEM in order to notify the CPU2(CM4)*/
HAL_HSEM_Release(HSEM_ID_0, 0);
/* wait until CPU2 wakes up from stop mode */
timeout = 0xFFFF;
while((__HAL_RCC_GET_FLAG(RCC_FLAG_D2CKRDY) == RESET) && (timeout-- > 0))
;
if(timeout < 0)
{
Error_Handler();
}
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
/* USER CODE END Boot_Mode_Sequence_2 */
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_QUADSPI_Init();
/* USER CODE BEGIN 2 */
int32_t flashStatus = MT25QL512ABB_OK;
uint32_t test_state_flag = 0;
/* 1. Enter QPI mode */
flashStatus = MT25QL512ABB_EnterQPIMode(&hqspi);
if(flashStatus != MT25QL512ABB_OK)
Error_Handler();
/* 2. Read ID and verify */
flashStatus = MT25QL512ABB_ReadID(&hqspi, MT25QL512ABB_QPI_MODE, id, MT25QL512ABB_DUALFLASH_ENABLE);
if((flashStatus != MT25QL512ABB_OK) || (id[0] != 0x20) || (id[1] != 0x20) || (id[2] != 0xBA) || (id[3] != 0xBA) || (id[4] != 0x20)
|| (id[5] != 0x20))
{
Error_Handler();
}
/* 3. Enter 4-byte address mode */
flashStatus = MT25QL512ABB_Enter4BytesAddressMode(&hqspi, MT25QL512ABB_QPI_MODE);
if(flashStatus != MT25QL512ABB_OK)
Error_Handler();
/* 4. Read the test state flag from its dedicated address */
if(MT25QL512ABB_ReadSTR(&hqspi, MT25QL512ABB_QPI_MODE, MT25QL512ABB_4BYTES_SIZE, (uint8_t*) &test_state_flag, TEST_STATE_FLAG_ADDRESS,
sizeof(test_state_flag)) != MT25QL512ABB_OK)
{
Error_Handler();
}
/* 5. Decide whether to WRITE or VERIFY based on the flag */
if(test_state_flag != WRITE_COMPLETE_FLAG)
{
/*****************************************************************/
/* PHASE 1: WRITE DATA & REBOOT */
/*****************************************************************/
// Turn on Yellow LED to indicate writing phase
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
if(QSPI_Write_Full_Chip() != 1)
Error_Handler(); // Write data failed
if(Write_Test_Flag() != 1)
Error_Handler(); // Write flag failed
// Write successful, now reboot
HAL_NVIC_SystemReset();
}
else
{
/*****************************************************************/
/* PHASE 2: VERIFY DATA */
/*****************************************************************/
if(QSPI_Verify_Full_Chip() == 1)
{
// Verification successful: Turn on Green LED
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
}
else
{
// Verification failed: Turn on Red LED
HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
}
// Erase the flag to allow the test to run again on the next power cycle,
// regardless of success or failure.
if(Erase_Test_Flag() != 1)
{
Error_Handler();
}
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}