on 2024-08-27 01:00 PM
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.
STM32CubeIDE v1.15.0 or later
NUCLEO-H723ZG Board
STM32Cube MCU Package for STM32H7 series v1.11.0 or later for generating the HAL/LL code using STM32CubeMX.
NAND flash breakout board by waveshare, which integrates Samsung K9F1G08U0E NAND memory flash.
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:
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].
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.
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.
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.
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:
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:
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:
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:
From the memory datasheet,
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.
Verify that the GPIOs for the FMC are in high-speed mode. However, by default they are in high-speed mode in this example
|
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.
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.
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.
Now that all the configurations are done and let’s generate code by clicking on this icon.
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 */
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.
When the debug configuration view opens, add the variables NAND_Id, nand_aRxBuffer and nand_aTxBuffer in the expressions window as shown below
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.
Add a breakpoint at the final while(1) statement of the main function.
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.
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.