Onboard flash can be faster...

Back to the topic at hand: making flash writes not block interrupts...
I've replaced the latter half of eeprom.c (everything after the eeprom_write_block function) like so:
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 MODE8_SDR       FLEXSPI_LUT_OPCODE_MODE8_SDR
#define DUMMY_SDR       FLEXSPI_LUT_OPCODE_DUMMY_SDR
#define JMP_ON_CS       FLEXSPI_LUT_OPCODE_JMP_ON_CS
#define PINS1           FLEXSPI_LUT_NUM_PADS_1
#define PINS4           FLEXSPI_LUT_NUM_PADS_4

void setup_flexspi() {
  __disable_irq();

  // unlock the LUT
  FLEXSPI_LUTKEY = FLEXSPI_LUTKEY_VALUE;
  FLEXSPI_LUTCR = FLEXSPI_LUTCR_UNLOCK;
  asm volatile("dmb");

  // LUT 0: ReadContinuous
  FLEXSPI_LUT0 = LUT0(CMD_SDR, PINS1, 0xEB) | LUT1(ADDR_SDR, PINS4, 24);
  FLEXSPI_LUT1 = LUT0(MODE8_SDR, PINS4, 0x20) | LUT1(DUMMY_SDR, PINS4, 4);
  FLEXSPI_LUT2 = LUT0(READ_SDR, PINS4, 4) | LUT1(JMP_ON_CS, 0, 1);
  FLEXSPI_LUT3 = 0;
  // LUT 1: Read Status Register 1
  FLEXSPI_LUT4 = LUT0(CMD_SDR, PINS1, 0x05) | LUT1(READ_SDR, PINS1, 1);
  FLEXSPI_LUT5 = \
  FLEXSPI_LUT6 = \
  FLEXSPI_LUT7 = 0;
  // LUT 2: Read Status Register 2
  FLEXSPI_LUT8 = LUT0(CMD_SDR, PINS1, 0x35) | LUT1(READ_SDR, PINS1, 1);
  FLEXSPI_LUT9 = \
  FLEXSPI_LUT10 = \
  FLEXSPI_LUT11 = 0;
  // LUT 3: Disable continuous read
  FLEXSPI_LUT12 = LUT0(CMD_SDR, PINS1, 0xFF);
  FLEXSPI_LUT13 = \
  FLEXSPI_LUT14 = \
  FLEXSPI_LUT15 = 0;
  // LUT 4: WriteEnable
  FLEXSPI_LUT16 = LUT0(CMD_SDR, PINS1, 0x06);
  FLEXSPI_LUT17 = \
  FLEXSPI_LUT18 = \
  FLEXSPI_LUT19 = 0;
  // LUT 5: EraseSector (4K)
  FLEXSPI_LUT20 = LUT0(CMD_SDR, PINS1, 0x20) | LUT1(ADDR_SDR, PINS1, 24);
  FLEXSPI_LUT21 = \
  FLEXSPI_LUT22 = \
  FLEXSPI_LUT23 = 0;
  // LUT 6: Erase 32K
  FLEXSPI_LUT24 = LUT0(CMD_SDR, PINS1, 0x52) | LUT1(ADDR_SDR, PINS1, 24);
  FLEXSPI_LUT25 = \
  FLEXSPI_LUT26 = \
  FLEXSPI_LUT27 = 0;
  // LUT 8: Erase 64K
  FLEXSPI_LUT32 = LUT0(CMD_SDR, PINS1, 0xD8) | LUT1(ADDR_SDR, PINS1, 24);
  FLEXSPI_LUT33 = \
  FLEXSPI_LUT34 = \
  FLEXSPI_LUT35 = 0;
  // LUT 9: PageProgram
  FLEXSPI_LUT36 = LUT0(CMD_SDR, PINS1, 0x32) | LUT1(ADDR_SDR, PINS1, 24);
  FLEXSPI_LUT37 = LUT0(WRITE_SDR, PINS4, 1);
  FLEXSPI_LUT38 = \
  FLEXSPI_LUT39 = 0;
  // LUT 12: Erase/Program Suspend
  FLEXSPI_LUT48 = LUT0(CMD_SDR, PINS1, 0x75);
  FLEXSPI_LUT49 = \
  FLEXSPI_LUT50 = \
  FLEXSPI_LUT51 = 0;
  // LUT 13: Read (non-continuous)
  FLEXSPI_LUT52 = LUT0(CMD_SDR, PINS1, 0xEB) | LUT1(ADDR_SDR, PINS4, 24);
  FLEXSPI_LUT53 = LUT0(MODE8_SDR, PINS4, 0xFF) | LUT1(DUMMY_SDR, PINS4, 4);
  FLEXSPI_LUT54 = LUT0(READ_SDR, PINS4, 4);
  FLEXSPI_LUT55 = 0;
  // LUT 14: Erase/Program Resume
  FLEXSPI_LUT56 = LUT0(CMD_SDR, PINS1, 0x7A);
  FLEXSPI_LUT57 = \
  FLEXSPI_LUT58 = \
  FLEXSPI_LUT59 = 0;

  // set AHB read command to ReadContinuous
  FLEXSPI_FLSHA1CR2 = FLEXSPI_FLSHCR2_ARDSEQID(0);

  // re-lock the LUT to prevent changes
  FLEXSPI_LUTKEY = FLEXSPI_LUTKEY_VALUE;
  FLEXSPI_LUTCR = FLEXSPI_LUTCR_LOCK;
  asm volatile("dmb");

  __enable_irq();
}

// disable/remove when not testing
#if 1
void nonblocking_flash_test(void);
#else
#define nonblocking_flash_test(...)
#endif

static void flash_wait()
{
  // tSUS = ~22.5us @ 133MHZ = 3000 cycles
  FLEXSPI_FLSHA1CR1 |= FLEXSPI_FLSHCR1_CSINTERVAL(3000);
  // change AHB read sequence to suspend->read->resume
  FLEXSPI_FLSHA1CR2 = FLEXSPI_FLSHCR2_ARDSEQNUM(2) | FLEXSPI_FLSHCR2_ARDSEQID(12);
  asm volatile("dmb");
  // changing CSINTERVAL won't work without a reset
  FLEXSPI_MCR0 |= FLEXSPI_MCR0_SWRESET;
  while (FLEXSPI_MCR0 & FLEXSPI_MCR0_SWRESET) ; // wait
  __enable_irq();

  nonblocking_flash_test();

  FLEXSPI_IPCR0 = 0;
  uint16_t status;
  while (1) {
    // for whatever reason, these commands cannot be sent as one sequence (second command doesn't finish)
    FLEXSPI_IPRXFCR = FLEXSPI_IPRXFCR_CLRIPRXF;
    FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(2);
    FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
    while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE));
    FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
    FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(1);
    FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
    while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE));
    FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
    status = FLEXSPI_RFDR0 << 8;
    status |= (uint8_t)FLEXSPI_RFDR2;
    // continue if BUSY is set
    if (status & 1) continue;
    // finish if there's no suspended operation
    if (!(status & 0x8000)) break;
    // otherwise manually trigger resume since apparently it doesn't always get triggered by AHB sequence...
    FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(14);
    FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
    while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE));
    FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  }

  __disable_irq();
  // restore old values
  FLEXSPI_FLSHA1CR1 &= ~FLEXSPI_FLSHCR1_CSINTERVAL_MASK;
  FLEXSPI_FLSHA1CR2 = FLEXSPI_FLSHCR2_ARDSEQID(0);

  // purge stale data from FlexSPI's AHB FIFO
  FLEXSPI_MCR0 |= FLEXSPI_MCR0_SWRESET;
  while (FLEXSPI_MCR0 & FLEXSPI_MCR0_SWRESET) ; // wait
  __enable_irq();
}

static void flash_begin()
{
  __disable_irq();
  // disable continuous mode, then enable write commands
  FLEXSPI_IPCR0 = 0;
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQNUM(1) | FLEXSPI_IPCR1_ISEQID(3);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
}

// write bytes into flash memory (which is already erased to 0xFF)
void eepromemu_flash_write(void *addr, const void *data, uint32_t len)
{
  flash_begin();
  arm_dcache_delete(addr, len); // purge old data from ARM's cache
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  FLEXSPI_IPTXFCR = FLEXSPI_IPTXFCR_CLRIPTXF; // clear tx fifo
  FLEXSPI_IPCR0 = (uint32_t)addr & 0x00FFFFFF;
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(9) | FLEXSPI_IPCR1_IDATSZ(len);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  const uint8_t *src = (const uint8_t *)data;
  uint32_t n;
  while (!((n = FLEXSPI_INTR) & FLEXSPI_INTR_IPCMDDONE)) {
    if (len && n & FLEXSPI_INTR_IPTXWE) {
      uint32_t wrlen = len;
      if (wrlen > 8) wrlen = 8;
      if (wrlen > 0) {
        memcpy((void *)&FLEXSPI_TFDR0, src, wrlen);
        src += wrlen;
        len -= wrlen;
      }
      FLEXSPI_INTR = FLEXSPI_INTR_IPTXWE;
    }
  }
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE | FLEXSPI_INTR_IPTXWE;
  flash_wait();
}

// erase a 4K sector
void eepromemu_flash_erase_sector(void *addr)
{
  // don't need flash_begin, all three commands are consecutive and can be issued as one sequence
  __disable_irq();
  FLEXSPI_IPCR0 = (uint32_t)addr & 0x00FFF000;
  // execute LUTs 3 + 4 + 5
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQNUM(2) | FLEXSPI_IPCR1_ISEQID(3);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  arm_dcache_delete((void*)((uint32_t)addr & 0xFFFFF000), 4096); // purge data from cache
  flash_wait();
}

void eepromemu_flash_erase_32K_block(void *addr)
{
  flash_begin();
  arm_dcache_delete((void *)((uint32_t)addr & 0xFFFF8000), 32768); // purge data from cache
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  FLEXSPI_IPCR0 = (uint32_t)addr & 0x00FF8000;
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(6);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  flash_wait();
}

void eepromemu_flash_erase_64K_block(void *addr)
{
  flash_begin();
  arm_dcache_delete((void *)((uint32_t)addr & 0xFFFF0000), 65536); // purge data from cache
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  FLEXSPI_IPCR0 = (uint32_t)addr & 0x00FF0000;
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(8);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE;
  flash_wait();
}

And then we can make a simple test program that will access flash during programming, to test the suspend/resume commands:
Code:
#include <Arduino.h>
#include <EEPROM.h>

extern "C" {
// ideally this would be called by startup, soon after configure_cache() is done
void setup_flexspi();

void startup_middle_hook() {
  setup_flexspi();
}

FLASHMEM void nonblocking_flash_test(void) {
  puts(PSTR("flash erase/program is active"));
}
} // extern "C"

FLASHMEM void setup() {
  Serial.begin(0);
  while (!Serial);
  puts("Flash tester begin");
}

void loop() {
  static uint8_t eep_byte;

  delay(4000);
  auto old = EEPROM.read(eep_byte);
  printf("Writing eeprom index %u\n", eep_byte);
  EEPROM.write(eep_byte, ++old);
  if (EEPROM.read(eep_byte) != old) {
    puts("eeprom verification failed!");
    while(1);
  }
  ++eep_byte;
}
 
Any chance you could make a test case that shows this LittleFS slowness?
I have written an example that demonstrates the slowness:
Code:
#include <LittleFS.h>
#include <Entropy.h>
// NOTE: Entropy class defines "randomByte" and "randomWord" methods that aren't implemented!
LittleFS_Program progfs;
static elapsedMillis write_timer;
void setup() {
  Serial.begin(0);
  while (!Serial);
  Entropy.Initialize();
  if (!progfs.begin(2 << 20)) {
    Serial.println("Failed to initialize LittleFS");
    while(1);
  }
  progfs.format();
  Serial.println("FS formatted.");
  File f = progfs.open("test", FILE_WRITE);
  if (!f) {
    Serial.println("Failed to init testfile");
  }
  f.truncate(2048 * 128);
  f.close();
  Serial.println("Testfile initialized.");

  write_timer = 0;
}
void loop() {
  File f;
  uint8_t sector_buffer[128];
  // fill buffer with random data
  for (size_t i=0; i < sizeof(sector_buffer); i++) {
    sector_buffer[i] = Entropy.random(256);
  }
  // pick a random 128 byte sector to replace
  uint16_t sector = Entropy.random(2048);
  // wait until 4 seconds since last write finished
  while (write_timer < 4000);
  f = progfs.open("test", FILE_WRITE);
  if (!f) {
    Serial.println("Failed to open testfile");
    while (1);
  }
  Serial.print("Replacing sector ");
  Serial.print(sector);
  Serial.print("...");
  elapsedMicros measure;
  // set the position
  f.seek(sector * 128);
  // write
  f.write(sector_buffer, sizeof(sector_buffer));
  f.close();
  uint32_t usecs = measure;
  Serial.print("Done, duration: ");
  Serial.print(usecs);
  Serial.print(" usecs (");
  Serial.print(128 * 1000000 / usecs);
  Serial.println(" bytes/sec)");
 
  // reset update timer
  write_timer = 0;
}

Example output:
FS formatted.
Testfile initialized.
Replacing sector 1175...Done, duration: 274025 usecs (467 bytes/sec)
Replacing sector 18...Done, duration: 556007 usecs (230 bytes/sec)
Replacing sector 1152...Done, duration: 274986 usecs (465 bytes/sec)
Replacing sector 271...Done, duration: 548801 usecs (233 bytes/sec)
Replacing sector 1404...Done, duration: 265783 usecs (481 bytes/sec)
Replacing sector 409...Done, duration: 542935 usecs (235 bytes/sec)
Replacing sector 946...Done, duration: 402915 usecs (317 bytes/sec)
Replacing sector 302...Done, duration: 545897 usecs (234 bytes/sec)
Replacing sector 770...Done, duration: 407879 usecs (313 bytes/sec)
Replacing sector 214...Done, duration: 549767 usecs (232 bytes/sec)
Replacing sector 1207...Done, duration: 272842 usecs (469 bytes/sec)
Replacing sector 898...Done, duration: 403825 usecs (316 bytes/sec)
Replacing sector 623...Done, duration: 413808 usecs (309 bytes/sec)
Replacing sector 1056...Done, duration: 277788 usecs (460 bytes/sec)
The duration is very obviously affected by the position of the write (beginning of file = slower, end of file = faster).
I'm not sure the timing measurements are completely accurate either, since micros still relies on the systick interrupt updating systick_millis_count/systick_cycle_count and the interrupts are blocked during flash programming - they may actually be longer.
 
Last edited:
micros() is based off the LAST systick seen and ARM_DWT_CYCCNT then - as long as it isn't a whole (MILLI)-second away it will properly count elapsed micros.

Quick rewrite just looks for 4 seconds of ARM_DWT_CYCCNT and gives similar results here:
edit: though just seeing the second elapseMillis was not altered
Code:
#include <LittleFS.h>
#include <Entropy.h>
// NOTE: Entropy class defines "randomByte" and "randomWord" methods that aren't implemented!
LittleFS_Program progfs;
#define FOUR_SECS 4 * F_CPU
//static elapsedMillis write_timer;
static uint32_t write_timer;
void setup() {
  Serial.begin(0);
  while (!Serial);
  Entropy.Initialize();
  if (!progfs.begin(2 << 20)) {
    Serial.println("Failed to initialize LittleFS");
    while(1);
  }
  progfs.format();
  Serial.println("FS formatted.");
  File f = progfs.open("test", FILE_WRITE);
  if (!f) {
    Serial.println("Failed to init testfile");
  }
  f.truncate(2048 * 128);
  f.close();
  Serial.println("Testfile initialized.");
}
void loop() {
  File f;
  uint8_t sector_buffer[128];
  write_timer = ARM_DWT_CYCCNT;  // set update timer
  // fill buffer with random data
  for (size_t i=0; i < sizeof(sector_buffer); i++) {
    sector_buffer[i] = Entropy.random(256);
  }
  // pick a random 128 byte sector to replace
  uint16_t sector = Entropy.random(2048);
  // wait until 4 seconds since last write finished
  while (ARM_DWT_CYCCNT - write_timer < FOUR_SECS);
  f = progfs.open("test", FILE_WRITE);
  if (!f) {
    Serial.println("Failed to open testfile");
    while (1);
  }
  Serial.print("Replacing sector ");
  Serial.print(sector);
  Serial.print("...");
  elapsedMicros measure;
  // set the position
  f.seek(sector * 128);
  // write
  f.write(sector_buffer, sizeof(sector_buffer));
  f.close();
  uint32_t usecs = measure;
  Serial.print("Done, duration: ");
  Serial.print(usecs);
  Serial.print(" usecs (");
  Serial.print(128 * 1000000 / usecs);
  Serial.println(" bytes/sec)");
 }
 
Last edited:
as long as it isn't a whole second away it will properly count elapsed micros.
I think you mean millisecond, which is the period of the systick interrupt - and the flash procedures can easily disable interrupts for longer than that.
 
Suppose so - opps - lost track of units. As first written it would count ahead for ms misses, but that went away with edit for issue that or something caused limiting to 1 ms.
The way it should be done, is that systick_isr (seen here) should be comparing ARM_DWT_CYCCNT with CYCLES_PER_MILLISEC*systick_millis_count to work out what amount to add to systick_millis_count instead of just adding 1. That way it wouldn't drop milliseconds if the systick interrupt was delayed for more than 1 millisec (as long as it wasn't delayed long enough for ARM_DWT_CYCCNT to wrap passed its old value).
 
I'm looking into the report of bootloader getting stuck, mentioned in msg #43 on the DMAChannels thread, linking here.

About that, when I was testing auto-suspending flash writing I quite often ended up getting the flash stuck in a state that the bootloader couldn't seem to handle - likely a suspended program operation was active when the bootloader took control, and more writing is not possible until the suspended op is cleared. It's definitely not a super-important case to handle but until a physical power cycle it does tend to make the Teensy act like it's bricked.

So far I'm running the code from msg #26 (both the stuff copied to eeprom.c in core library, and the sketch in an IDE window). So far I've not looked deep. I've just uploaded it dozens of times to a Teensy 4.0. So far no apparent lockup or other problems. Is there something special I'm supposed to do?

EDIT: at the risk of flash write endurance, I deleted the delay(4000) line, in hopes of uploading at the "wrong moment" more easily. But I clicked upload many times while it was rapidly scrolling stuff. Every time it goes into bootloader mode, does the upload, and reboots to rapid scrolling in the serial monitor. So far I can't reproduce any sort of lockup.
 
Last edited:
Maybe try replacing the body of nonblocking_flash_test() with while(1); or other construct that stops it from ever returning, so the suspended program operation never gets resumed?

It's possible I inadvertently ended up working around it by changing the order of commands in the LUTs, or zeroing all of the unused LUT registers instead of only the minimum required.
 
After some experimenting, I came up with this simple program which doesn't require editing any core library files.

I'm able to reproduce the problem with putting the flash chip into power down mode. But so far I can't get anything unusual with quad fast continuous mode. Have you see the problem with continuous mode?

For anyone who tries this, to recover you may need to power cycle your Teensy while holding the pushbutton, so it goes directly into bootloader mode at powerup.

C++:
#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 MODE8_SDR       FLEXSPI_LUT_OPCODE_MODE8_SDR
#define DUMMY_SDR       FLEXSPI_LUT_OPCODE_DUMMY_SDR
#define JMP_ON_CS       FLEXSPI_LUT_OPCODE_JMP_ON_CS
#define PINS1           FLEXSPI_LUT_NUM_PADS_1
#define PINS4           FLEXSPI_LUT_NUM_PADS_4


void setup() {
  while (!Serial) ;
 
  Serial.println("Flash unusual config test in 2 seconds");
  delay(2000);
  Serial.println("begin");
  FLEXSPI_IPRXFCR = FLEXSPI_IPRXFCR_CLRIPRXF | FLEXSPI_IPRXFCR_RXWMRK(1);
  FLEXSPI_LUTKEY = FLEXSPI_LUTKEY_VALUE;
  FLEXSPI_LUTCR = FLEXSPI_LUTCR_UNLOCK;
  // sequence 8: put flash into Quad I/O Fast Read Continuous mode
  FLEXSPI_LUT32 = LUT0(CMD_SDR, PINS1, 0xEB) | LUT1(ADDR_SDR, PINS4, 24);
  FLEXSPI_LUT33 = LUT0(MODE8_SDR, PINS4, 0x20) | LUT1(DUMMY_SDR, PINS4, 4);
  FLEXSPI_LUT34 = LUT0(READ_SDR, PINS4, 1); //| LUT1(JMP_ON_CS, 0, 1);
  FLEXSPI_LUT35 = 0;
  // sequence 9: put flash info power down mode
  FLEXSPI_LUT36 = LUT0(CMD_SDR, PINS1, 0xB9);
  FLEXSPI_LUT37 = 0;
  FLEXSPI_LUT38 = 0;
  FLEXSPI_LUT39 = 0;

  if (FLEXSPI_INTR & FLEXSPI_INTR_IPCMDERR) Serial.println("error");
#if 0
  // configure flash into Quad I/O Fast Read Continuous mode
  FLEXSPI_IPCR0 = 0;
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(8) | FLEXSPI_IPCR1_IDATSZ(4);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
  Serial.println(FLEXSPI_RFDR0, HEX);       // should print 42464346
#else
  // configure flash into deep power down mode
  FLEXSPI_IPCR1 = FLEXSPI_IPCR1_ISEQID(9);
  FLEXSPI_IPCMD = FLEXSPI_IPCMD_TRG;
  while (!(FLEXSPI_INTR & FLEXSPI_INTR_IPCMDDONE)) ; // wait
#endif
  FLEXSPI_INTR = FLEXSPI_INTR_IPCMDDONE | FLEXSPI_INTR_IPRXWA;
  if (FLEXSPI_INTR & FLEXSPI_INTR_IPCMDERR) {
    Serial.println("error, sequence not run");
  } else {
    Serial.println("flash chip is now in unusual config");
  }
}

void loop() {
}
 
I don't remember any issues with continuous mode, only when suspending erase/program operations and having interrupts enabled (which requires disabling continuous mode to issue commands other than read).
 
I'm working on the next bootloader update, so it will be able to handle the powered down flash.

Odds are good this will also handle the chip busy, as I'm the 0x99 reset command. If I have extra time I might try to build a test case...
 
Quick update on my efforts to make the new bootloader handle this and other possible unexpected flash configurations.

The big question is what actions can recover from all possible flash configurations. So far I have these 5 steps:
  1. Send quad mode 0xFF command (2 SPI clocks). If the flash is in QPI mode, this returns to SPI mode. If it was already SPI mode, CS asserted with only 2 clocks should be ignored.
  2. Send 0xFF command. If a prior 0xEB Fast Quad I/O Read has the chip in continuous mode, 0xFF is seen as address and mode byte to end continuous mode. If normal SPI mode, 0xFF should be ignored.
  3. Send 0x66 command to enable reset
  4. Send 0x99 command to perform reset. If powerdown mode or busy with erase or write, this should return to idle mode
  5. Wait 1 ms
I've tested this with the powerdown code (msg #36). Pressing the button does recover. We don't end up apparaently bricked.

Clicking Upload in Arduino IDE usually doesn't work, but on some rare occasions it does. I spent an embarrassingly long time to figure out why. Turns out I made the _reboot_Teensyduino_() function in usb.c with FLASHMEM, since it's not performance critical, so why waste precious RAM1 memory on it? But this has the effect of (usually) crashing rather than going into bootloader mode, because the flash memory is in an unusable state when this function gets called. My best guess is the rare times it does work, the compiler must have placed other FLASHMEM startup code nearby, so at least the first part of _reboot_Teensyduino_() got put into the ARM instruction cache at startup and was still cached wheil I clicked Upload in the IDE.

FLASHMEM on _reboot_Teensyduino_() saves 128 bytes of RAM1 usage. My gut feeling is that's a worthwhile savings for the cost of crashing for this very unusual case where the flash chip gets left in an unusable configuration. But while playing with this, having Upload from Arduino IDE work sure feels better.
 
Back
Top