cancel
Showing results for 
Search instead for 
Did you mean: 

How to configure the FMC peripheral to interface an STM32 MCU with an external NAND flash memory

Lionking
ST Employee

Introduction

NAND flash is a type of nonvolatile memory that retains data even when powered off, unlike RAM. This makes it ideal for storing information in portable devices like USB drives and smartphones. NAND flash offers several advantages: it is very affordable per gigabyte of storage, packs a lot of storage capacity into a small space, and boasts fast data transfer speeds for programming and erasing operations. These qualities have made NAND flash the usual memory solution in embedded systems like smartphones, wearables, and industrial controls.

1. Hardware and software prerequisites

Lionking_0-1722896772007.png

For this article, we interface the NUCLEO-H723ZG board with a NAND flash memory on a Waveshare breakout board. Here is the demonstration setup wired up using jumper cables:

Lionking_1-1722896772027.png

2. Development

We start this demonstration by creating a new project for the STM32H723ZG Nucleo board using STM32CubeIDE and configuring the clocks and the FMC peripheral. Here are the steps to perform the initial configurations.

  • Open the STM32CubeIDE application

  • Choose [File] -> [New] -> [STM32 Project]

  • Search for NUCLEO-H723ZG under board selector tab and click on the shown board selection and then click [Next].

Lionking_2-1722896834409.png

Lionking_3-1722896834411.png

  • Choose a name for the project and click on [Finish].
  • A popup appears, click on [No] to deny keeping all the peripherals present on the board in their default mode.

Lionking_4-1722896834412.png 

  • Once the .ioc file is open, click on [Clear Pinouts] under the [Pinout & Configuration] drop-down menu to start the peripheral initialization from scratch.

Lionking_5-1722896834420.png

  • Set PB14 as GPIO_OUTPUT to use the onboard LED, which is useful later to verify if the NAND flash memory is working as expected.
  • Right click on the pin and change the name to “TEST_LED”

Lionking_6-1722896834425.png

2.1. Configure the NAND flash timings

The NAND flash K9F1G08U0E has the following main features:

  • Asynchronous memory

  • x8 bus width, multiplexed data, and address bus

  • 1 Gbit memory with 4 MB of spare area

The STM32H723ZG FMC peripheral can support NAND flash memories only on one bank: Bank3. Bank3 is mapped at address 0x8000 0000 - 0x8FFF FFFF, which is 256 MB.

Under the connectivity tab, select FMC and enable the NAND flash 1. Make the configurations as shown below.

Lionking_7-1722897005004.png

Bank3 is used to support NAND flash memory through two memory spaces: Common and attribute memory space.

Each memory space is divided into three subsections:

  • Data section (64 Kbytes): The FW reads or writes the data from/to any memory location in the data section.

  • Command section (64 Kbytes): The FW can write a command value to any memory location in the command section.

  • Address section (128 Kbytes): The FW writes the address value into any memory location in this section. Since the address length varies depending on the actual memory size, several consecutive write operations to the address section are required to specify the full address.

Lionking_8-1722897005007.png

The timing configurations for the common memory space are done in the FMC_PMEM register and for the attribute, memory is done in the FMC_PATT register. The timing parameters can be calculated as follows:

This example is using an FMC clock frequency of 150 MHz, which is equal to a period of 6.6 ns.

  • HCLK = 150 MHz
  • tHCLK = 6.6 ns

Lionking_9-1722897005010.png

Lionking_10-1722897005012.png

Lionking_11-1722897005015.png

 

Setup Time (SET): The time required to set up the address before the command assertion. It contributes to the time from address valid to the start (falling edge) of the read or write operation.

The condition to satisfy the setup time for the FMC is:

  • (SET + 1) x tHCLK ≥ max (tCS, tCLS, tALS, tCLR, tAR) – tWP
  • Considering max (tCS, tCLS, tALS, tCLR, tAR) = 20 ns and tWP = 12 ns
  • SET + 1 = (20-12) ns/6.6 ns ≈ 2
  • SET = 2-1 =1

Wait Time (WAIT): The time for read or write to enable the signal to undergo a low to high transition. Denoted as tRP or tWP in the memory datasheet. The condition to satisfy the wait time for the FMC is:

  • (WAIT + 1) x tHCLK ≥ max (tWP, tRP)
  • max (tWP, tRP) = 12 ns
  • (WAIT + 1) = max (tWP, tRP)  / tHCLK = (12/6.6) ns ≈ 2
  • WAIT = 2 – 1
  • WAIT=1   

Note: Choose a higher value as the one we found above and the tWP and tRP mentioned in the memory datasheet are the minimum values. Hence, considering WAIT = 3

Hold Time (HOLD): The time during which the address is held after a read or write signal is deasserted. In simple terms, this is the time from the rising edge of a read/write enable signal to the rising edge of chip select/address latch/command latch enable signals. The condition to satisfy the hold time for the FMC is:

  • (HOLD) x tHCLK ≥ max (tCH, tCLH, tALH)
  • max (tCH, tCLH, tALH) = 5 ns
  • (HOLD) = (5 / 6.6) ns ≈ 1

Data-bus HiZ time (HiZ): This parameter is only valid for write operations and corresponds to the time when the data bus is kept in the high impedance state after the start of write access, meaning time from the address valid to the data driven. This is measured between the falling edge of the Chip select signal and the data setup on the bus. The condition to satisfy the HiZ time for the FMC is:

  • HiZ x tHCLK >= max(tCS, tCLS, ALS) + (tWP - tDS)
  • max(tCS, tCLS, ALS) =12 ns
  • tWP – tDS = 12-12 =0
  • HiZ = 12/6.6ns ≈ 2

 

2.2. Memory Array organization:

  • Page size = (2 K + 64 spare) bytes
  • ECC page size = 2 K bytes
  • Spare area size in a single page = 64 bytes
  • Number of pages in 1 block = 64
  • Block size = (128 K + 4K spare) bytes
  • Number of blocks = 1024
  • Number of planes = 1, which consists of 1024 blocks

Lionking_12-1722897005016.png

From the memory datasheet,

  • CLE low to RE low delay (tCLR) = 10 ns
  • ALE low to RE low delay (tAR) = 10 ns

In HCLK the delays can be calculated as (tCLR,tAR) / tHCLK = 10 ns/6.6 ns ≈ 2

With the memory array and timing information as discussed above, input the required information in the NAND flash configuration tab as shown below.

Lionking_13-1722897005018.png

 

Lionking_14-1722897005023.png

 

Verify that the GPIOs for the FMC are in high-speed mode. However, by default they are in high-speed mode in this example

 
  Lionking_15-1722897005028.png

 

 


2.3. Set up the GPIO for the FMC


In this example, modifications have to be done for the FMC_NCE and FMC_NWAIT signals. By default when the NAND flash is enabled, PC6 and PC8 pins are used for FMC_NWAIT and FMC_NCE signals. In this example, we have remapped the FMC_NWAIT and FMC_NCE signals to PD6 and PG9 pins.
There is a shortcut to do this. Hover the mouse pointer on the pin signal you want to move, and hold CTRL + left mouse button, move it to the desired alternate function pin that blinks in black color.

Lionking_16-1722897005030.png

 

Let us configure the clock by enabling the HSE clock. Go to the RCC section under [Pinout & Configuration] and select [Crystal/Ceramic Resonator] as the HSE clock.

Lionking_17-1722897005033.png

Make sure that the FMC clock is running at a frequency of 150 MHz. This can be done by going into [Clock Configuration] tab and entering 150 in the FMC clock box and pressing enter. STM32CubeMX automatically selects the appropriate clock, calculates the values for the PLL multipliers and prescalars to generate this 150 MHz clock.

Lionking_18-1722897005040.png

Now that all the configurations are done and let’s generate code by clicking on this icon.

Lionking_19-1722897005041.png

 

2.4. Code to add in the main.c file 

The code to add in-between the respective user code comments for the main.c file is mentioned below:

/* USER CODE BEGIN Includes */ 

#define WRITE_READ_ADDR         ((uint32_t)0x800)  /* Base address at which data needs to be written  */ 

#define NAND_MAKERID         	((uint32_t)0xEC) 

#define NAND_DEVICEID        	((uint32_t)0xF1) 

 

#define NAND_PAGE_SIZE             ((uint16_t)0x0800) /* 2 * 1024 bytes per page w/o Spare Area */ 

#define NAND_BLOCK_SIZE            ((uint16_t)0x0040) /* 64 pages per block */ 

#define NAND_PLANE_SIZE            ((uint16_t)0x0400) /* 1024 Block per plane */ 

#define NAND_SPARE_AREA_SIZE       ((uint16_t)0x0040) /* last 64 bytes as spare area */ 

#define NAND_MAX_PLANE             ((uint16_t)0x0001) /* 1 plane of 1024 block */ 

 

#define BUFFER_SIZE             NAND_PAGE_SIZE 

/* USER CODE END Includes */ 

 

/* USER CODE BEGIN PTD */ 

static NAND_AddressTypeDef NAND_Address; 

static NAND_IDTypeDef NAND_Id; 

 

/* Read/Write Buffers */ 

uint8_t nand_aTxBuffer[BUFFER_SIZE]; 

uint8_t nand_aRxBuffer[BUFFER_SIZE]; 

 

 

/* Status variables */ 

__IO uint32_t uwWriteReadStatus = 0; 

 

/* Counter index */ 

uint32_t uwIndex = 0;	 

 

/* USER CODE END PTD */ 

 

 

/* USER CODE BEGIN PD */ 

typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus; 

/* USER CODE END PD */ 

 

 

/* USER CODE BEGIN PFP */ 

static  void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength); 

static  TestStatus Buffercmp(uint8_t* pBuffer, uint8_t* pBuffer1, uint32_t BufferLength); 

static  void NAND_GetAddress (uint32_t Address, NAND_AddressTypeDef *pNandAddress); 

/* USER CODE END PFP */ 

 

 

/* USER CODE BEGIN 2 */ 

 

  /* Read NAND memory ID */ 

  if(HAL_NAND_Read_ID(&hnand1, &NAND_Id) != HAL_OK) 

  { 

  /* NAND read ID Error */ 

  Error_Handler(); 

  } 

 

  /* Test the NAND ID correctness */ 

 if((NAND_Id.Maker_Id != NAND_MAKERID) || (NAND_Id.Device_Id != NAND_DEVICEID)) 

 { 

   /* NAND ID not correct */ 

   Error_Handler(); 

 } 

 

 NAND_GetAddress(WRITE_READ_ADDR, &NAND_Address); 

 

 if(HAL_NAND_Erase_Block(&hnand1, &NAND_Address)!= HAL_OK) 

 { 

  /* NAND erase Error */ 

  Error_Handler(); 

 } 

 

 /* Fill the buffer to write */ 

 Fill_Buffer(nand_aTxBuffer, BUFFER_SIZE); 

 

 /* Write data to the NAND memory */ 

 if((HAL_NAND_Write_Page_8b(&hnand1, &NAND_Address, nand_aTxBuffer, 1))!= HAL_OK) 

 { 

 /* NAND write Error */ 

 Error_Handler(); 

 } 

 

HAL_Delay(100); 

 

 if((HAL_NAND_Read_Page(&hnand1, &NAND_Address, nand_aRxBuffer, 1))!= HAL_OK) 

 { 

 /* NAND read Error */ 

 Error_Handler(); 

 } 

 

 /*##-3- Checking data integrity ############################################*/ 

 if(Buffercmp(nand_aTxBuffer, nand_aRxBuffer, BUFFER_SIZE) != PASSED) 

 { 

   Error_Handler(); 

 } 

 else 

 { 

   /* OK */ 

   /* Toggle Test LED */ 

while(1) 

{ 

HAL_GPIO_TogglePin(TEST_LED_GPIO_Port, TEST_LED_Pin); 

HAL_Delay(1000); 

} 

 } 

 

  /* USER CODE END 2 */ 

 

 

/* USER CODE BEGIN 4 */ 

/** 

 * @brief  Fills buffer with user predefined data. 

 *   pBuffer: pointer on the buffer to fill 

 *   uwBufferLenght: size of the buffer to fill 

 * @retval None 

 */ 

static void Fill_Buffer(uint8_t *pBuffer, uint32_t uwBufferLength) 

{ 

 uint16_t index = 0; 

 

 /* Put in global buffer same values */ 

 for (index = 0; index < uwBufferLength; index++ ) 

 { 

   pBuffer[index] = index; 

 } 

} 

 

/** 

 * @brief  Compares two buffers. 

 *   pBuffer, pBuffer1: buffers to be compared. 

 *   uwBufferLength: buffer's length 

 * @retval 1: pBuffer identical to pBuffer1 

 *         0: pBuffer differs from pBuffer1 

 */ 

static TestStatus Buffercmp(uint8_t* pBuffer, uint8_t* pBuffer1, uint32_t uwBufferLength) 

{ 

 uint32_t counter = 0; 

 while(uwBufferLength--) 

 { 

   if(*pBuffer != *pBuffer1) 

   { 

     return FAILED; 

   } 

 

   pBuffer++; 

   pBuffer1++; 

   counter++; 

 } 

 

 return PASSED; 

} 

 

 

static void NAND_GetAddress (uint32_t Address, NAND_AddressTypeDef *pNandAddress) 

{ 

  pNandAddress->Page = (Address % (NAND_BLOCK_SIZE * (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE))) / (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE); 

  pNandAddress->Block = (Address % (NAND_PLANE_SIZE * NAND_BLOCK_SIZE * (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE))) / (NAND_BLOCK_SIZE * (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE)); 

  pNandAddress->Plane = Address / (NAND_PLANE_SIZE * NAND_BLOCK_SIZE * (NAND_PAGE_SIZE + NAND_SPARE_AREA_SIZE)); 

} 

 

/* USER CODE END 4 */ 

Make the following changes in the error handler function: 

 

/* USER CODE BEGIN Error_Handler_Debug */ 

  /* User can add his own implementation to report the HAL error return state */ 

  __disable_irq(); 

  HAL_GPIO_WritePin(TEST_LED_GPIO_Port, TEST_LED_Pin, GPIO_PIN_SET); 

  while (1) 

  { 

  } 

  /* USER CODE END Error_Handler_Debug */ 

3. Results

Connect your board to the PC using a USB cable. Load and debug the binary file by clicking on the button shown below and the programming should be completed without any errors.

Lionking_20-1722897440899.png

When the debug configuration view opens, add the variables NAND_Id, nand_aRxBuffer and nand_aTxBuffer in the expressions window as shown below

Lionking_21-1722897440901.png

If the expressions window does not show up automatically, then click on [Window] -> [Show View] -> [Expressions].

Now, click on [Run] and you can see the red LED toggling every 1 second, which denotes that the program is working perfectly as expected. If the red test LED is not toggling, then there is an error or misconfiguration in the code or an issue with the hardware connections.

Lionking_22-1722897440903.png

Add a breakpoint at the final while(1) statement of the main function.

Lionking_23-1722897440905.png

Now, the code halts at the breakpoint, which we added. Then we can analyze the variables, which we added to the expressions window. The NAND_Id structure variable holds the electronic signature of the NAND flash device.

Lionking_24-1722897440907.png

The values of the nand_aTxBuffer and nand_aRxBuffer are the same when analyzed, which denotes that the writing and reading of data was successful on the NAND flash device.

Note: Remember if the device is going into a hard fault, then the MPU has to be configured to prevent the speculative nature of the cortex M7 processor. It causes the MCU to access the normal memory regions even when there is no actual external memory connected. The external memory peripheral control registers can be changed from normal to device memory type. The external memory peripheral data region can be changed to strongly ordered or device memory type.

Related links

 

Version history
Last update:
‎2024-08-27 02:46 AM
Updated by: