Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 12 of 12

Thread: NAND flash support in 1.54

  1. #1

    NAND flash support in 1.54

    Paul mentioned adding some "official" NAND flash support in the 1.53 Beta #2 thread, initially targeting the Winbond W25N01GVZE1G. Since I'm using that in my current project and I haven't seen a planning thread I thought I'd start one.

    I've only done a proof of concept / sanity test at this point based on a seriously hacked up version of the SPIMemory library, which is GPL and therefore probably not a good general solution. I don't know if the plan would be to extend SPIFFS or use something more targeted to NAND? In my limited research the obvious choices for that would be Dhara or LittleFS.

    Based on my reading of the 1062 datasheet, it appears that we can only use the R/W "macros" on the QSPI port (and earlier Teensy chips lack this support as well), so the library will probably need variants for the chip access that support both standard SPI config and the specialized instructions?

    I'll be happy to contribute once things get started, but that would mostly just be known working SPI sequences for the W25N01GVZE1G.
    Last edited by ecurtz; 06-27-2020 at 04:04 PM. Reason: typo

  2. #2
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    @ecurtz

    Thanks for starting this thread, kept meaning too but kept getting side tracked with other things

    Anyway @defragster posted a NXP Community question on using NAND: FlexSpi NOR and NAND Flash Configuration which gives you the idea of how to set it up with the 1062 SDK.

    I also found another project that uses the NAND chip which they developed a driver for it on the STM32: https://github.com/betaflight/betafl...f4135c04afe3fd . if you expand the diff for src/main/drivers/flash_w25n01g.c you will see the approach they took.

    I have a few of those chips on older. Once i get them I have to get the courage to try and solder them to a T4.1

  3. #3
    Senior Member brtaylor's Avatar
    Join Date
    Mar 2016
    Location
    Portland, OR
    Posts
    550
    I'd be super keen if someone got NAND flash working with Teensy. Having storage size rivaling an SD card in a soldered on solution would be awesome.

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,764
    REF >> From thread :: Teensyduino-1-53-Beta-2/page3

    Paul posted this - link to chip/source and notes on potential timeline and where it goes on T_4.1 and ... "improved file & filesystem abstraction and other storage media":
    Quote Originally Posted by PaulStoffregen View Post
    Winbond W25N01GVZE1G

    winbond.com/hq/product/code-storage-flash-memory/qspinand-flash/index.html?__locale=en&partNo=W25N01GV

    digikey.com/product-detail/en/winbond-electronics/W25N01GVZEIG-TR/W25N01GVZEIGCT-ND/7393545

    I designed the wide SOIC8 footprint on Teensy 4.1 with alternatively mounting this WSON8 part onto those pads.
    ...

    Probably best to start a new thread if you want to chat about the details. Perfectly fine to discuss on this forum. But please know I will not be working on NAND flash support until we're shipping Teensy 4 bootloader chips, so don't expect me to be active on that thread. After 1.53, getting a bootloader chip released is my top priority. Realistically, I'll probably also spend some time on much-needed website updates and documentation before getting into improved file & filesystem abstraction and other storage media (the main focus planned for 1.54).

  5. #5
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    Just by way of update and warning.

    Got my NAND chips from Digikey and just soldered up one to a T4.1. Soldering that chip is not easy, at least for me, it was a real challenge to get everything line up right. Lots of flux and fine tip soldering iron. At least when i hooked up the T4.1 with the NAND chip on it, the T4.1 still would load sketches and run sketches.

    Now to see if I can get it to work which will take some doing I think.

  6. #6
    As I said I unfortunately began by hacking on a GPL codebase so I shouldn't just post code without carefully verifying that it's actually stuff that I wrote, but here are a few snippets to help get started verifying the chip. I assume that those bottom pads can be used as regular single channel SPI?
    Code:
    #define CMD_RESET       0xFF
    #define CMD_JEDEC_READ  0x9F
    #define CMD_STAT_READ   0x0F
    #define CMD_STAT_WRITE  0x1F
    #define CMD_WRITE_EN    0x06
    #define CMD_WRITE_DIS   0x04
    #define CMD_BAD_BLOCKS  0xA1
    #define CMD_BBM_READ    0xA5
    #define CMD_ECC_ADDR    0xA9
    #define CMD_ERASE       0xD8
    #define CMD_DATA_PROG   0x02
    #define CMD_DATA_PROG4  0x32
    #define CMD_PROG_EX     0x10
    #define CMD_PAGE_DATA   0x13
    #define CMD_READ        0x03
    #define CMD_FAST_READ   0x0B
    #define CMD_FAST_READ2  0x3B
    #define CMD_FAST_READ4  0x6B
    #define CMD_DUAL_READ2  0xBB
    #define CMD_DUAL_READ4  0xEB
    
    #define SR1_SRP0        (0x01 << 7)
    #define SR1_BP3         (0x01 << 6)
    #define SR1_BP2         (0x01 << 5)
    #define SR1_BP1         (0x01 << 4)
    #define SR1_BP0         (0x01 << 3)
    #define SR1_TB          (0x01 << 2)
    #define SR1_WPE         (0x01 << 1)
    #define SR1_SRP1        (0x01 << 0)
    #define SR1_BLOCKS      (SR1_BP0 | SR1_BP1 | SR1_BP2 | SR1_BP3)
    
    #define SR2_OTPL        (0x01 << 7)
    #define SR2_OTPE        (0x01 << 6)
    #define SR2_SR1L        (0x01 << 5)
    #define SR2_ECCE        (0x01 << 4)
    #define SR2_BUF         (0x01 << 3)
    
    #define SR3_LUTF        (0x01 << 6)
    #define SR3_ECC1        (0x01 << 5)
    #define SR3_ECC0        (0x01 << 4)
    #define SR3_PFAIL       (0x01 << 3)
    #define SR3_EFAIL       (0x01 << 2)
    #define SR3_WELL        (0x01 << 1)
    #define SR3_BUSY        (0x01 << 0)
    
    #define WINBOND_MANID    0xEF  
    #define W25N01GV              0x21
    
    // JEDEC ID is available even if chip is busy, so no need to stall
    void GetJedecID(void) {
     
       if (!spiTransactionOpen) {
          spiSettings = SPISettings(SPI_CLK, MSBFIRST, SPI_MODE0);
          SPI.beginTransaction(spiSettings);
          spiTransactionOpen = true;
       }
       
       digitalWrite(csPin, LOW); // Chip select
       
       SPI.transfer(CMD_JEDEC_READ);
       SPI.transfer(0x00);  // needs an extra dummy byte to complete command
    
       manufacturerID = SPI.transfer(0x00);	// manufacturer id
       memoryTypeID = SPI.transfer(0x00);	// memory type
       capacityID = SPI.transfer(0x00);		// capacity
     	
       digitalWrite(csPin, HIGH); // Chip deselect
    
       if (spiTransactionOpen) {
          SPI.endTransaction();
          spiTransactionOpen = false;
       }   
     }

  7. #7
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    @ecurtz

    Thanks for posting so I can at least do a verification that i did the soldering correct. Will also help when I try to use FLEXSPI

  8. #8
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    Quick status. Not sure I did this right, probably not or my chip is not on right Here is the start of using FLEXSPI with the NAND chip where the FLASH goes:
    Code:
    #define LUT0(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand)))
    #define LUT1(opcode, pads, operand) (FLEXSPI_LUT_INSTRUCTION((opcode), (pads), (operand)) << 16)
    #define CMD_SDR         FLEXSPI_LUT_OPCODE_CMD_SDR
    #define ADDR_SDR        FLEXSPI_LUT_OPCODE_RADDR_SDR
    #define READ_SDR        FLEXSPI_LUT_OPCODE_READ_SDR
    #define WRITE_SDR       FLEXSPI_LUT_OPCODE_WRITE_SDR
    #define DUMMY_SDR       FLEXSPI_LUT_OPCODE_DUMMY_SDR
    #define PINS1           FLEXSPI_LUT_NUM_PADS_1
    #define PINS4           FLEXSPI_LUT_NUM_PADS_4
    
    // Config/status register addresses
    #define W25N01G_PROT_REG 0xA0
    #define W25N01G_CONF_REG 0xB0
    #define W25N01G_STAT_REG 0xC0
    
    static const uint32_t flashBaseAddr = 0x01000000u;
    static const uint32_t eramBaseAddr = 0x07000000u;
    static char flashID[4];
    
    void setup(){
        Serial.begin(115200);
        delay(1000);
        Serial.println("Begin Init");
        
        memset(flashID, 0, sizeof(flashID));
        
        FLEXSPI2_FLSHA2CR0 = 128u * 1024u * 1024u;          //Flash Size in KByte, 1F400
        FLEXSPI2_FLSHA2CR1 = FLEXSPI_FLSHCR1_CSINTERVAL(2)  //minimum interval between flash device Chip selection deassertion and flash device Chip selection assertion.
        | FLEXSPI_FLSHCR1_TCSH(3)                           //Serial Flash CS Hold time.
        | FLEXSPI_FLSHCR1_TCSS(3);                          //Serial Flash CS setup time
        
        FLEXSPI2_FLSHA2CR2 = FLEXSPI_FLSHCR2_AWRSEQID(6)    //Sequence Index for AHB Write triggered Command
        | FLEXSPI_FLSHCR2_AWRSEQNUM(0)                      //Sequence Number for AHB Read triggered Command in LUT.
        | FLEXSPI_FLSHCR2_ARDSEQID(5)                       //Sequence Index for AHB Read triggered Command in LUT
        | FLEXSPI_FLSHCR2_ARDSEQNUM(0);                     //Sequence Number for AHB Read triggered Command in LUT.
    
    
        // cmd index 7 = read ID bytes
        //Serial.println("LUT28: JDEC");
        FLEXSPI2_LUT28 = LUT0(CMD_SDR, PINS1, 0x9F) ;     //9fh Read JDEC 
        FLEXSPI2_LUT29 = LUT0(DUMMY_SDR, PINS1, 8) | LUT0(READ_SDR, PINS1, 1);
    
        // cmd index 8 = read Status register #1 SPI
        FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, 0x05) 
          | LUT1(CMD_SDR, PINS1, W25N01G_STAT_REG);      //5h Status Register, sr addr 0xC0
        FLEXSPI2_LUT33 = LUT0(READ_SDR, PINS1, 1);
    
        // cmd index 9 = read Status register #2 SPI
        //FLEXSPI2_LUT36 = LUT0(CMD_SDR, PINS1, 0x35) 
        //  | LUT1(READ_SDR, PINS1, 1);           //There is no status reg #2
    
        //cmd index 10 = exit QPI mode
        //FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS4, 0xFF);
    
        //cmd index 11 = write enable QPI
        //FLEXSPI2_LUT44 = LUT0(CMD_SDR, PINS4, 0x06);
    
        //cmd index 12 = sector erase
        ///FLEXSPI2_LUT48 = LUT0(CMD_SDR, PINS4, 0x20) 
        //| LUT1(ADDR_SDR, PINS4, 24);
    
        //cmd index 13 = page program
        //FLEXSPI2_LUT52 = LUT0(CMD_SDR, PINS4, 0x02) 
        //| LUT1(ADDR_SDR, PINS4, 24);
        //FLEXSPI2_LUT53 = LUT0(WRITE_SDR, PINS4, 1);
    
        //cmd index 14 = set read parameters
        //FLEXSPI2_LUT56 = LUT0(CMD_SDR, PINS4, 0xc0) 
        //| LUT1(CMD_SDR, PINS4, 0x20);
    
        //cmd index 15 = enter QPI mode
        //FLEXSPI2_LUT60 = LUT0(CMD_SDR, PINS1, 0x38);
    
        // reset the chip
        flexspi_ip_command(2, flashBaseAddr); //reset
        delayMicroseconds(150);
    
        Serial.println(); Serial.println("Status after Reset (S/B 0):");
        printStatusRegs();
    
    
        Serial.print("FLASH ID:");
        flexspi_ip_read(7, flashBaseAddr, flashID, sizeof(flashID) ); // flash begins at offset 0x01000000
    
        for (unsigned i = 0; i < sizeof(flashID); i++) Serial.printf(" %02X", flashID[i]);
        Serial.printf("\n");
        Serial.printf("at 0x %x\n", flashBaseAddr);
    
        flexspi2_flash_id(flashBaseAddr);
    }
    
    void loop(){}
    
    void flexspi_ip_read(uint32_t index, uint32_t addr, void *data, uint32_t length)
    {
      uint32_t n;
      uint8_t *p = (uint8_t *)data;
      const uint8_t *src;
    
      FLEXSPI2_IPCR0 = addr;
      FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index) | FLEXSPI_IPCR1_IDATSZ(length);
      FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG;
      while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)) {
        if (n & FLEXSPI_INTR_IPRXWA) {
          //Serial.print("*");
          if (length >= 8) {
            length -= 8;
            *(uint32_t *)(p+0) = FLEXSPI2_RFDR0;
            *(uint32_t *)(p+4) = FLEXSPI2_RFDR1;
            p += 8;
          } else {
            src = (const uint8_t *)&FLEXSPI2_RFDR0;
            while (length > 0) {
              length--;
              *p++ = *src++;
            }
          }
          FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA;
        }
      }
      if (n & FLEXSPI_INTR_IPCMDERR) {
        Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\r\n", FLEXSPI2_IPRXFSTS);
      }
      FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE;
      Serial.printf(" FLEXSPI2_RFDR0=%08lX\r\n", FLEXSPI2_RFDR0);
      if (length > 4) Serial.printf(" FLEXSPI2_RFDR1=%08lX\n", FLEXSPI2_RFDR1);
      if (length > 8) Serial.printf(" FLEXSPI2_RFDR1=%08lX\n", FLEXSPI2_RFDR2);
      if (length > 16) Serial.printf(" FLEXSPI2_RFDR1=%08lX\n", FLEXSPI2_RFDR3);
      src = (const uint8_t *)&FLEXSPI2_RFDR0;
      while (length > 0) {
        *p++ = *src++;
        length--;
      }
      if (FLEXSPI2_INTR & FLEXSPI_INTR_IPRXWA) FLEXSPI2_INTR = FLEXSPI_INTR_IPRXWA;
    }
    
    void printStatusRegs() {
    #if 1
      uint8_t val;
    
      flexspi_ip_read(8, flashBaseAddr, &val, 1 );
      Serial.print("Status 1:");
      Serial.printf(" %02X", val);
      Serial.printf("\n");
      Serial.print("Binary: "); Serial.println(val, BIN);
      Serial.println();
    
      // cmd index 9 = read Status register #2 SPI
      //flexspi_ip_read(9, flashBaseAddr[_spiffs_region], &val, 1 );
      //Serial.print("Status 2:");
      //Serial.printf(" %02X", val);
      //Serial.printf("\n");
    #endif
    }
    
    
    void flexspi_ip_command(uint32_t index, uint32_t addr)
    {
      uint32_t n;
      FLEXSPI2_IPCR0 = addr;
      FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(index);
      FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG;
      while (!((n = FLEXSPI2_INTR) & FLEXSPI_INTR_IPCMDDONE)); // wait
      if (n & FLEXSPI_INTR_IPCMDERR) {
        Serial.printf("Error: FLEXSPI2_IPRXFSTS=%08lX\n", FLEXSPI2_IPRXFSTS);
      }
      FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE;
    }
    
    FLASHMEM static uint32_t flexspi2_flash_id(uint32_t addr)
    {
      FLEXSPI2_IPCR0 = addr;
      FLEXSPI2_IPCR1 = FLEXSPI_IPCR1_ISEQID(7) | FLEXSPI_IPCR1_IDATSZ(4);
      FLEXSPI2_IPCMD = FLEXSPI_IPCMD_TRG;
      while (!(FLEXSPI2_INTR & FLEXSPI_INTR_IPCMDDONE)); // wait
      uint32_t id = FLEXSPI2_RFDR0;
      FLEXSPI2_INTR = FLEXSPI_INTR_IPCMDDONE | FLEXSPI_INTR_IPRXWA;
      Serial.println();
      Serial.print("FLASH ID2: "); Serial.println(id , HEX);
      return 0;
    }
    For a start not sure if I understood the LUT setup correctly but at least with what i have i get something even though its not right
    Code:
    Begin Init
    
    
    
    Status after Reset (S/B 0):
    
     FLEXSPI2_RFDR0=00000002  //IMPLIES WRTIE LATCH ENABLED 
    Status 1: 02
    Binary: 10
    
    FLASH ID: FLEXSPI2_RFDR0=78F9E4FD
    
     FD E4 F9 78  //First byte should be a dummy read then EFh, 21h, AAh
    at 0x 1000000
    
    
    FLASH ID2: 78F9E4FD
    So if any one gets around to soldering one on or has any comments let me know

  9. #9
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    Ok it lives after a successful operation and some playing with LUTs, Raw Dump:
    Code:
    Begin Init
    
    Status before Reset:
    * FLEXSPI2_RFDR0=00000000
    Status 1: 00
    Binary: 0
    
    RESET ISSUED
    
    Status after Reset (S/B 0):
     FLEXSPI2_RFDR0=00000000
    Status 1: 00
    Binary: 0
    
    FLASH ID: FLEXSPI2_RFDR0=21AAEFFF
     FLEXSPI2_RFDR1=00000000
     FF EF AA 21 00
    at 0x 1000000
    Attaching zip file for your reference. So now the fun stuff starts.
    Attached Files Attached Files

  10. #10
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,764
    Quote Originally Posted by mjs513 View Post
    Ok it lives after a successful operation and some playing with LUTs, Raw Dump:
    ...

    Awesome

  11. #11
    Great job! Can anyone confirm that my reading of the datasheet that "regular" SPI can't use the lookup tables and so we'll need two versions of the low level calls correct?

  12. #12
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    5,321
    Quote Originally Posted by ecurtz View Post
    Great job! Can anyone confirm that my reading of the datasheet that "regular" SPI can't use the lookup tables and so we'll need two versions of the low level calls correct?
    Way from any expert here but from what I read and have reviewed the answer is no LUTs are used with regular Serial. That one lib I referenced lets you do either/or.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •