NAND flash support in 1.54

ecurtz

Well-known member
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:
@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/betaf...7e5daa0#diff-03151c2d562e0cfe77f4135c04afe3fd . 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 :)
 
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.
 
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":
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).
 
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.
 
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;
   }   
 }
 
@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 :)
 
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

[COLOR="#FF0000"]static const uint32_t flashBaseAddr = 0x01000000u;[/COLOR]
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.


[COLOR="#FF0000"]    // 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);[/COLOR]

[COLOR="#FF0000"]    // 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);[/COLOR]

    // 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  [COLOR="#FF0000"][COLOR="#FF0000"]//IMPLIES WRTIE LATCH ENABLED [/COLOR][/COLOR]
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
 
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.
 

Attachments

  • NAND_SPI_TEST.zip
    3.3 KB · Views: 72
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?
 
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.
 
Ok playing around some more but think I am stuck on LUTs again. So anyone that is a LUT expert wondering if you all could help with a few things.

In going through the existing code for PSRAM and FLASH I see construct:
Code:
	FLEXSPI2_LUT20 = LUT0(CMD_SDR, PINS4, 0xEB) |[COLOR="#FF0000"][B] LUT1(ADDR_SDR, PINS4, 24);[/B][/COLOR]
	FLEXSPI2_LUT21 = LUT0(DUMMY_SDR, PINS4, 6) | LUT1(READ_SDR, PINS4, 1);

I get CMD_SDR, DUMMY_SDR and READ_SDR but not sure what ADDR_SDR is doing. Yes i read the RM but still dont understand.

That's the first question. So here is the second. This chip is nothing like our existing flash, just a heads up :).

For most of the Op Codes you have to send the OP Code, then either a sub-address and then a value to be written or read (Think SPI) like when I want to read a status register the manual shows:
Code:
8.2.3 Read Status Register (0Fh / 05h)
Capture.PNG
and for this I have:
Code:

What I tried was this:
Code:
    FLEXSPI2_LUT32 = LUT0(CMD_SDR, PINS1, W25N01G_READ_STATUS_REG) | LUT1(CMD_SDR, PINS1, reg); 
    FLEXSPI2_LUT33 = LUT0(READ_SDR, PINS1 1);
    flexspi_ip_read(8, flashBaseAddr, &val, 1 );
For write it looks like:
Capture1.PNG Tried something similar but not sure if did it right"
Code:
    uint8_t buf[1];
    buf[0] = data;
    Serial.print("---> "); Serial.println(data, BIN);
    // cmd index 10 = write Status register #1 SPI
    //FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS1, W25N01G_WRITE_STATUS_REG) | LUT1(CMD_SDR, PINS1, reg); 
    //FLEXSPI2_LUT42 = LUT0(WRITE_SDR, PINS1, 1);
    
    FLEXSPI2_LUT40 = LUT0(CMD_SDR, PINS4, W25N01G_WRITE_STATUS_REG);
    FLEXSPI2_LUT41 = LUT0(WRITE_SDR, PINS4, 1);

    flexspi_ip_write(10, reg, buf, 1);
Pretty sure none of this is right.

But i did manage to get erase and readBytes working i think - but just a guess at this point.

PS: @PaulStoffregen - @Frank please don't laugh :) Really trying to understand and get this.
 
but not sure what ADDR_SDR is doing ... That's the first question.

ADDR_SDR causes FlexSPI to transmit the desired memory address you wish to access. It's used only for actually reading & writing the memory. You wouldn't use ADDR_SDR for LUTs which do things like reading the status address.

When you issue commands manually (the "IP" interface), the 32 bit address is whatever you wrote to the FLEXSPI2_IPCR0 register. When the processor accesses memory in the 70000000-7FFFFFFF range (the "AHB" interface) the ADDR_SDR command is whatever memory address the M7 process wanted to access.

There are actually 2 different address commands. So far we've only ever used row address and configured column width to zero. Looks like this chip is going to require using column addresses. Details on page 1634.


So here is the second. This chip is nothing like our existing flash, just a heads up :).

Yeah, it's quite a challenge...


when I want to read a status register .... What I tried was this:

That looks pretty good.


For write it looks like:

Use PINS1. If you use PIN4, it will transmit on all 4 data pins using only 2 clocks.


PS: @PaulStoffregen - @Frank please don't laugh :) Really trying to understand and get this.

Oh, I know how hard this is. I spent months (back in 2018) before I finally got it to work, for the much simpler W25Q16.
 
@PaulStoffregen
Thanks for getting back to me - know you are busier than a one-armed paper hanger right now. Gave me a lot to think about. Now to do some more reading and then back to playing - maybe I will have something today.

You know, even though this may be hard, its actually a lot of fun trying to get it to work.
 
I assume you've looked at the example LUTs in sections 27.6.5 and 27.6.3 in the reference manual? I'm trying to get the HW guy to build me up a 4.1 so I can help, since I think the WSON package is beyond my skill level / equipment.

Here's the W25N01GV code for a few more commands if it helps at all. _beginSPI does the initial chip select and also sends the argument.

Code:
bool SPIFlash::_loadPageData(uint16_t pageAddr) {
   if (pageAddr != _currentPage)
   {
      _beginSPI(CMD_PAGE_DATA);
      _nextByte(WRITE, DUMMYBYTE);
      _nextByte(WRITE, (pageAddr >> 8) & 0xFF);
      _nextByte(WRITE, (pageAddr >> 0) & 0xFF);

      _currentPage = pageAddr;
      CHIP_DESELECT
   }
   return true;
 }

bool SPIFlash::_readPageData(uint16_t columnAddr, uint8_t* buffer, uint32_t size) {
   if (_isBusy(PAGE_TIMEOUT)) {
      return false;
   }

   _beginSPI(CMD_READ);
   _nextByte(WRITE, (columnAddr >> 8) & 0xFF);
   _nextByte(WRITE, (columnAddr >> 0) & 0xFF);
   _nextByte(WRITE, DUMMYBYTE);

   for (uint32_t i = 0; i < size; i++)
   {
      buffer[i] = _nextByte(READ);
   }
   
   CHIP_DESELECT
   return true;
 }
 
bool SPIFlash::_writePageData(uint16_t columnAddr, uint8_t* buffer, uint32_t size)
{
   if (!_writeEnable()) return false;
   
   _beginSPI(CMD_DATA_PROG);
   _nextByte(WRITE, (columnAddr >> 8) & 0xFF);
   _nextByte(WRITE, (columnAddr >> 0) & 0xFF);

   for (uint32_t i = 0; i < size; i++)
   {
      _nextByte(WRITE, buffer[i]);
   }

   CHIP_DESELECT
   return true;
}

bool SPIFlash::_programPageData(uint16_t pageAddr)
{
   if (!_disableBlockProtect()) return false;
   
   _beginSPI(CMD_PROG_EX);
   _nextByte(WRITE, DUMMYBYTE);
   _nextByte(WRITE, (pageAddr >> 8) & 0xFF);
   _nextByte(WRITE, (pageAddr >> 0) & 0xFF);

   CHIP_DESELECT
   return true;
}
 
_beginSPI does the initial chip select and also sends the argument.

This is the way traditional SPI works. But FlexSPI is very different. The whole transfer, including chip select, is done completely by hardware. You don't write code to do the individual steps. You can't. The hardware doesn't even support that way, where it does each part of a transfer in response to software. Instead you program the LUTs to tell the FlexSPI hardware how to do everything.

Once the LUTs are set up, and some other registers are configured as needed, there are 2 ways you can trigger FlexSPI to actually do a transfer. The "IP" interface is driven by your code. You write to the FLEXSPI2_IPCR0 & FLEXSPI2_IPCR1 to give FlexSPI the address and which LUTs to use, and then you write to FLEXSPI2_IPCMD to tell FlexSPI to actually do the work. You don't ever write code to do things like change the chip select pin and move data. The FlexSPI hardware does everything according to how you set up the LUTs. After triggering it to start, you can poll the FLEXSPI2_INTR register to tell when it's done, or you can set it up to generate an interrupt. The data flows through 2 large FIFOs, which you can poll while the transfer is in progress, or you can set up DMA to move the data. But your code doesn't determine when the actual bytes transfer on the SPI pins. The FlexSPI runs the whole transfer all by itself and you just stuff data into the transmit FIFO and pull data out of the receive FIFO as needed, and keep doing that until it's done.

The reason FlexSPI runs so autonomously is it's mainly designed to be used by the other "AHB" interface, where it will do SPI transfers automatically as the Cortex-M7 cache wants to fill or flush rows. FlexSPI runs all by itself in response to memory access that generates cache misses. As it does the SPI transfers, those FIFOs flow automatically into buffers which the M7 process sees as memory. This is why the PSRAM chip acts like ordinary memory which you can simply use in your program without any SPI code. That's why FlexSPI is all built around configuring LUTs to program the hardware to do transfers without any software.

If you're used to ordinary SPI, this can be a difficult way to think about SPI. But hopefully understanding the design is all based on making the SPI chip appear as ordinary memory to the Cortex M7 processor can at least give you some context for why it's all based on hardware-only operation with configurable LUTs.
 
This is the way traditional SPI works. But FlexSPI is very different. The whole transfer, including chip select, is done completely by hardware. You don't write code to do the individual steps. You can't. The hardware doesn't even support that way, where it does each part of a transfer in response to software. Instead you program the LUTs to tell the FlexSPI hardware how to do everything.

I understand this, but I thought the basic commands encoded into the lookup table would still mirror the traditional method?
 
@PaulStoffregen - Great explanation !

You can see some of what Paul is talking about if you look at the flexspi_ip_read, flexsp_ip_write and flexspi_ip_command functions in the sketch. Right now at least not getting errors when I am writing the data buffer or trying to readBytes = just not working. Have to read some more - think I am missing something. Think I need a little break.

Anyway if you want to play I am attaching the latest version.

Here are a couple more references about NAND from NXP:
https://community.nxp.com/thread/418534 (not a good one but has and interesting pdf referenced);
https://community.nxp.com/thread/526984
https://community.nxp.com/message/1206885?commentID=1206885#comment-1206885 (of course I am not doing pages yet )
https://community.nxp.com/message/1116215?commentID=1116215#comment-1116215 (this one is interesting)
https://community.nxp.com/thread/534700
 
Looks like you forgot to include the new code? I should have a new Teensy with the chip on it by the end of the week.

Oops -- heres the attachment. Do you have an example sketch that I can use as reference for the calls to library? Just curious.

defragster said:
@mjs513 - in github section for extRAM - made a folder there for extNAND_t41 so code is available and can be source controlled? ... rather than forum hacking.
Ok sounds good - going forward i will push it up there.
 

Attachments

  • NAND_SPI_TEST.zip
    7.4 KB · Views: 77
@mjs513 - Can you give me the exact part number for the NAND chip you are using? When I looked it up I saw that there were at least three different package types available and I want to make sure that I order the proper one for the T4.1.

Thanks
 
Back
Top