Electronic – STM32F4 NAND flash via FSMC, difference between bytes written and read back

flashmemorystm32stm32f4

I have a Waveshare Open407V-D development board, basically a "motherboard" in which an STM32F4DISCOVERY kit is fitted (this kit, for those not familiar with it, is made by ST itself and based on the STM32F407VG Cortex-M4 MCU). The Open407V-D board provides very little in terms of functionality, consisting mostly of a series of connectors in which modules (also sold by Waveshare) are plugged. One of these modules, called "NandFlash Board", contains a Samsung K9F1G08U0D 1 Gbit NAND Flash IC. The connector on the motherboard connects it to the relevant FSMC signals:

  • FSMC_D0 through FSMC_D7 on the MCU are connected to I/O0 through I/O7 on the Flash IC
  • FSMC_A17 is connected to CLE
  • FSMC_A16 is connected to ALE
  • FSMC_NWAIT is connected to R/B
  • FSMC_NWE is connected to WE
  • FSMC_NOE is connected to RE
  • FSMC_A18/PD13 is connected to CE

I'm trying to interface to this module. I started writing my own init code, and then incorporated ideas from this forum thread. For the read/write/erase code I combined some example code for the STM32F10E-EVAL board, bundled with the STM32F1xx standard peripheral library as shipped by ST (especially the file Utilities/STM32_EVAL/STM3210E_EVAL/stm3210e_eval_fsmc_nand.c), with the example code for the WaveShare NandFlash Board which can be downloaded from here.

I wrote code that fills a page (2048 bytes) of memory with the following array in little-endian form (notice it's an array of 32-bit variables):

uint32_t array[] = { 0, 1, 2, 3, 4, 5, ..., 509, 510, 511 };

Hence, when reading back out the page, one would expect the following:

00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 ...

What I actually get, however, is usually something close to this (an excerpt from the beginning of the page):

00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00
04 00 00 00 05 00 00 00 06 00 00 00 07 00 00 00
08 00 00 00 09 00 00 00 0A 00 00 00 0B 00 00 00
0C 00 00 00 0D 00 00 00 0E 00 00 00 0F 00 00 00
10 00 00 00 11 00 00 00 12 00 00 00 13 00 00 00
14 00 00 00 15 00 00 00 16 00 00 00 17 00 00 00
18 00 00 00 19 00 00 00 1A 00 00 00 1B 00 00 00
1C 00 00 00 1D 00 00 00 1E 00 00 00 1F 00 00 00
20 00 00 00 21 00 00 00 22 00 00 00 23 00 00 00
24 00 00 00 25 00 00 00 26 00 00 00 27 00 00 00
28 00 00 00 29 00 00 00 2A 00 00 00 2B 00 00 00
2C 00 00 00 2D 00 00 00 2E 00 00 00 2F 00 00 00
30 00 00 00 31 00 00 00 32 00 00 00 33 00 00 00
34 00 00 00 35 00 00 00 36 00 00 00 37 00 00 00
38 00 00 00 39 00 00 00 3A 00 00 00 3B 00 00 3C
00 00 00 3D 00 00 00 3E 00 00 00 3F 00 00 40 00
00 00 41 00 00 00 42 00 00 00 43 00 00 00 44 00
00 00 45 00 00 00 46 00 00 00 47 00 00 00 48 00
00 00 49 00 00 00 4A 00 00 00 4B 00 00 00 4C 00
00 00 4D 00 00 00 4E 00 00 00 4F 00 00 00 50 00
00 00 51 00 00 00 52 00 00 00 53 00 00 00 54 00
00 00 55 00 00 00 56 00 00 00 57 00 00 00 58 00

As can be seen, sometimes a zero byte is removed, and at other times, one is inserted into the stream (although the case of insertion didn't happen in this particular example). Look for instance to the 3C and 40 bytes in the example. I've also seen situations in which one of the bytes is repeated, for instance instead of 32 00 00 00 33 00 ... I get 32 00 00 33 33 00 ...

In fact, in order to get the results above, I can't use the calculated setup/hold/wait/Hi-Z/tAR/tCLR timings (seen in the code excerpts below); I didn't figure the exact threshold, but at least using a value of 100 clock cycles for setup/hold/wait/Hi-Z and 16 cycles for tAR/tCLR does work. The results appear to be very similar (and quite often exactly the same) over every run. I've tried changing around the address of the page but I keep getting the same results.

One thing I did try is to erase and write the pattern only once, and then read back data on a loop and inspect it with a debugger. Most of the time I always get the same result, though a couple of times I got a slightly different results (e.g. the case of the repeated 33 described above). So it appears there is a bug in the read function, but I can't rule out a bug in the write function as well.

For reference, I provide the bulk of my code in case it matters. First, a bunch of #defines that may be needed for reference in the rest of the code:

#define FSMC_Bank_NAND     FSMC_Bank2_NAND
#define Bank_NAND_ADDR     Bank2_NAND_ADDR
#define Bank2_NAND_ADDR    ((uint32_t)0x70000000)

#define ROW_ADDRESS (Address.Page + (Address.Block + (Address.Zone * NAND_ZONE_SIZE)) * NAND_BLOCK_SIZE)

#define CMD_AREA                   (uint32_t)(1<<17)  /* A17 = CLE  high */
#define ADDR_AREA                  (uint32_t)(1<<16)  /* A16 = ALE high */

#define DATA_AREA                  ((uint32_t)0x00000000)

#define NAND_CMD_AREA_A            ((uint8_t)0x00)
#define NAND_CMD_AREA_B            ((uint8_t)0x01)
#define NAND_CMD_AREA_C            ((uint8_t)0x50)

#define NAND_CMD_READ_1             ((uint8_t)0x00)
#define NAND_CMD_READ_TRUE          ((uint8_t)0x30)

#define NAND_CMD_WRITE0            ((uint8_t)0x80)
#define NAND_CMD_WRITE_TRUE1       ((uint8_t)0x10)

#define NAND_CMD_ERASE0            ((uint8_t)0x60)
#define NAND_CMD_ERASE1            ((uint8_t)0xD0)

#define NAND_CMD_READID            ((uint8_t)0x90)
#define NAND_CMD_STATUS            ((uint8_t)0x70)
#define NAND_CMD_LOCK_STATUS       ((uint8_t)0x7A)
#define NAND_CMD_RESET             ((uint8_t)0xFF)

#define NAND_VALID_ADDRESS         ((uint32_t)0x00000100)
#define NAND_INVALID_ADDRESS       ((uint32_t)0x00000200)
#define NAND_TIMEOUT_ERROR         ((uint32_t)0x00000400)
#define NAND_BUSY                  ((uint32_t)0x00000000)
#define NAND_ERROR                 ((uint32_t)0x00000001)
#define NAND_READY                 ((uint32_t)0x00000040)

#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_ZONE_SIZE             ((uint16_t)0x0400) /* 1024 Block per zone */
#define NAND_SPARE_AREA_SIZE       ((uint16_t)0x0040) /* last 64 bytes as spare area */
#define NAND_MAX_ZONE              ((uint16_t)0x0001) /* 1 zones of 1024 block */

#define ADDR_1st_CYCLE(ADDR)       (uint8_t)((ADDR)& 0xFF)               /* 1st addressing cycle */
#define ADDR_2nd_CYCLE(ADDR)       (uint8_t)(((ADDR)& 0xFF00) >> 8)      /* 2nd addressing cycle */
#define ADDR_3rd_CYCLE(ADDR)       (uint8_t)(((ADDR)& 0xFF0000) >> 16)   /* 3rd addressing cycle */
#define ADDR_4th_CYCLE(ADDR)       (uint8_t)(((ADDR)& 0xFF000000) >> 24) /* 4th addressing cycle */

This is the init code:

void NAND_Init()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    FSMC_NANDInitTypeDef FSMC_NANDInitStructure;
    FSMC_NAND_PCCARDTimingInitTypeDef p;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource4, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource6, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FSMC);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource7, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource8, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource9, GPIO_AF_FSMC);
    GPIO_PinAFConfig(GPIOE, GPIO_PinSource10, GPIO_AF_FSMC);

    RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE);

    FSMC_NANDInitStructure.FSMC_AttributeSpaceTimingStruct = &p;
    FSMC_NANDInitStructure.FSMC_CommonSpaceTimingStruct = &p;

    FSMC_NANDStructInit(&FSMC_NANDInitStructure);

    p.FSMC_SetupTime = 0;
    p.FSMC_WaitSetupTime = 2;
    p.FSMC_HoldSetupTime = 1;
    p.FSMC_HiZSetupTime = 4;

    FSMC_NANDInitStructure.FSMC_Bank = FSMC_Bank2_NAND;
    FSMC_NANDInitStructure.FSMC_Waitfeature = FSMC_Waitfeature_Enable;
    FSMC_NANDInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b;
    FSMC_NANDInitStructure.FSMC_ECC = FSMC_ECC_Disable;
    FSMC_NANDInitStructure.FSMC_TCLRSetupTime = 0;
    FSMC_NANDInitStructure.FSMC_TARSetupTime = 0;
    FSMC_NANDInit(&FSMC_NANDInitStructure);

    FSMC_NANDCmd(FSMC_Bank2_NAND, ENABLE);
}

This is the code for erasing a page:

uint32_t NAND_EraseBlock(NAND_ADDRESS Address)
{
    *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_ERASE0;

    *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS);
    *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS);

    *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_ERASE1;

    return (NAND_GetStatus());
}

The code for writing one or more pages (for now I'm only writing one page at a time):

uint32_t NAND_WriteSmallPage(uint8_t *pBuffer, NAND_ADDRESS Address, uint32_t NumPageToWrite)
{
    uint32_t index = 0x00, numpagewritten = 0x00, addressstatus = NAND_VALID_ADDRESS;
    uint32_t status = NAND_READY, size = 0x00;

    while((NumPageToWrite != 0x00) && (addressstatus == NAND_VALID_ADDRESS) && (status == NAND_READY))
    {
        /*!< Page write command and address */
        *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_WRITE0;

        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS);
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS);

        /*!< Calculate the size */
        size = NAND_PAGE_SIZE + (NAND_PAGE_SIZE * numpagewritten);

        /*!< Write data */
        for(; index < size; index++)
        {
            *(__IO uint8_t *)(Bank_NAND_ADDR | DATA_AREA) = pBuffer[index];
        }

        *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_WRITE_TRUE1;

        /*!< Check status for successful operation */
        status = NAND_GetStatus();

        if(status == NAND_READY)
        {
            numpagewritten++;

            NumPageToWrite--;

            /*!< Calculate Next small page Address */
            addressstatus = NAND_AddressIncrement(&Address);
        }
    }

    return (status | addressstatus);
}

The code for reading one or more pages (again I'm just reading one page at a time for now):

uint32_t NAND_ReadSmallPage(uint8_t *pBuffer, NAND_ADDRESS Address, uint32_t NumPageToRead)
{
    uint32_t index = 0x00, numpageread = 0x00, addressstatus = NAND_VALID_ADDRESS;
    uint32_t status = NAND_READY, size = 0x00;

    while((NumPageToRead != 0x0) && (addressstatus == NAND_VALID_ADDRESS))
    {
        /*!< Page Read command and page address */
        *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_READ_1;

        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = 0x00;
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_1st_CYCLE(ROW_ADDRESS);
        *(__IO uint8_t *)(Bank_NAND_ADDR | ADDR_AREA) = ADDR_2nd_CYCLE(ROW_ADDRESS);

        *(__IO uint8_t *)(Bank_NAND_ADDR | CMD_AREA) = NAND_CMD_READ_TRUE;

        /*!< Calculate the size */
        size = NAND_PAGE_SIZE + (NAND_PAGE_SIZE * numpageread);

        /*!< Get Data into Buffer */
        for(; index < size; index++)
        {
            pBuffer[index]= *(__IO uint8_t *)(Bank_NAND_ADDR | DATA_AREA);
        }

        numpageread++;

        NumPageToRead--;

        /*!< Calculate page address */
        addressstatus = NAND_AddressIncrement(&Address);
    }

    status = NAND_GetStatus();

    return (status | addressstatus);
}

Best Answer

Take a look at the table you want to write. You have defined as unsigned long

values uint32_t array[] = { 0, 1, 2, 3, 4, 5, ..., 509, 510, 511 };

So that - write operation is ok. unsigned long type is 4 bytes long.