Close

SPI-Flash Test

Frank BussFrank Buss wrote 04/05/2019 at 14:58 • 8 min read • Like

I bought the 8 Mbit flash P25Q80H-SSH-IT at lcsc.com for $0.10 if you buy 10. First I tried it with this circuit on a breadboard:

The numbers are the pin numbers on a Arduino Nano, which runs with 5 V, so I used the resistor dividers for scaling down the output voltage. The 3.3 V output from the flash was enough for the MISO line, but it didn't run stable, sometimes bits were missing when I tried to read the chip ID. And it got worse when I tried to measure the clock with the scope, probably because of the increased capacity and slower rising and falling times.

To fix this, I added two 74LVC1G14 schmitt trigger inverter ($0.04 at lcsc.com) :

The datasheet says it allows 5 V at the input, even with 3.3 V supply voltage, for using it as a voltage converter. This is the test setup, with an additional 100 nF capacitor for the supply voltages for the inverters and the flash:

The other pins are not critical, because they are only sampled at rising clock and changed at falling clock. This is the full script with a write test at the start, and then read tests in the loop:

#include <stdint.h>
#include <SPI.h>

#define PAGE_SIZE 256

uint8_t data[PAGE_SIZE];
const int csPin = 10;

unsigned long startTime;

void startMeasure() {
  startTime = millis();
}

void error(const char* text) {
  Serial.println(text);
  while (1) {}
}

void stopMeasure(const char* text) {
  unsigned long currentTime = millis();
  unsigned long elapsedTime = currentTime - startTime;  
  Serial.print(text);
  Serial.print(": ");
  Serial.print(elapsedTime);
  Serial.println(" ms");
}

void sendOneByteCommand(uint8_t command) {
  digitalWrite(csPin, LOW);
  SPI.transfer(command);
  digitalWrite(csPin, HIGH);
}

uint8_t readStatusRegister() {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x05);
  uint8_t result = SPI.transfer(0);
  digitalWrite(csPin, HIGH);
  return result;
}

void waitUntilWriteDone() {
  // wait until WIP bit is 0
  while (readStatusRegister() & 1) {}
}

void reset() {
  sendOneByteCommand(0x66);
  sendOneByteCommand(0x99);
  delay(20);
}

void readId() {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x9f);
  uint8_t manufacturerId = SPI.transfer(0);
  uint8_t memoryType = SPI.transfer(0);
  uint8_t memoryDensity = SPI.transfer(0);
  digitalWrite(csPin, HIGH);

  Serial.print("manufacturer ID: 0x");
  Serial.print(manufacturerId, HEX);
  Serial.print(", memory type: 0x");
  Serial.print(memoryType, HEX);
  Serial.print(", memory density: 0x");
  Serial.println(memoryDensity, HEX);
  if (manufacturerId == 0x85 && memoryType == 0x60 && memoryDensity == 0x14) {
    Serial.println("ID ok");
  } else {
    error("wrong ID");
  }
}

void writeEnable() {
  sendOneByteCommand(0x06);
}

void writeAddress(uint32_t address) {
  SPI.transfer((address >> 16) & 0xff);
  SPI.transfer((address >> 8) & 0xff);
  SPI.transfer(address & 0xff);
}

// erase the whole chip
void chipErase() {
  writeEnable();
  sendOneByteCommand(0x60);
}

// erase one page
void pageErase(uint32_t address) {
  writeEnable();
  digitalWrite(csPin, LOW);
  SPI.transfer(0x81);
  writeAddress(address);
  digitalWrite(csPin, HIGH);
}

// program one page
// note: changes the content of the data array
void pageProgram(uint32_t address, uint8_t* data) {
  writeEnable();
  digitalWrite(csPin, LOW);
  SPI.transfer(0x02);
  writeAddress(address);
  SPI.transfer(data, PAGE_SIZE);
  digitalWrite(csPin, HIGH);
}

// reads data from flash
unsigned long readData(uint32_t address, uint8_t* data, uint32_t size) {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x03);
  writeAddress(address);
  SPI.transfer(data, size);
  digitalWrite(csPin, HIGH);
}

void setup() {
  pinMode(csPin, OUTPUT);
  Serial.begin(115200);

  SPI.begin();
  SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));

  reset();
  readId();

  // erase whole chip
  chipErase();
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("chip erase time");

  // verify
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("chip erase verify error");
    }
  }

  // program first page
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0xff - i;
  }
  pageProgram(0, data);
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("page program time");

  // verify programmed data
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff - i) {
      error("page program verify error");
    }
  }

  // erase page
  pageErase(0);
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("page erase time");

  // verify
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("page erase verify error");
    }
  }

  // program test data in first 2 pages
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0xff - i;
  }
  pageProgram(0, data);
  waitUntilWriteDone();
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0;
  }
  pageProgram(PAGE_SIZE, data);
  waitUntilWriteDone();

}

uint64_t counter = 0;

void loop() {
  // read and verify first 3 pages
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff - i) {
      error("page 0 verify error");
    }
  }
  readData(PAGE_SIZE, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0) {
      error("page 1 verify error");
    }
  }
  readData(2 * PAGE_SIZE, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("page 2 verify error");
    }
  }

  // counter output
  counter++;
  if ((counter % 1000) == 0) {
    Serial.print(float(counter));
    Serial.println(" read tests");
  }
}

The output looked like this:

manufacturer ID: 0x85, memory type: 0x60, memory density: 0x14
ID ok
chip erase time: 8 ms
page program time: 1 ms
page erase time: 8 ms
1000.00 read tests
2000.00 read tests
3000.00 read tests

It worked 118 million times, then there was one read error. But after starting the script again, but commenting the initial write test to see if the content of the flash was still valid, it worked again. I guess it was just a communication glitch because of the breadboard setup.

Next I changed the script to test how often I could erase and write the first page. The datasheet guarantees 100,000 cycles. This is the full script:

#include <stdint.h>
#include <SPI.h>

#define PAGE_SIZE 256

uint8_t data[PAGE_SIZE];
const int csPin = 10;

unsigned long startTime;

void startMeasure() {
  startTime = millis();
}

void error(char* text) {
  Serial.println(text);
  while (1) {}
}

void stopMeasure(char* text) {
  unsigned long currentTime = millis();
  unsigned long elapsedTime = currentTime - startTime;  
  Serial.print(text);
  Serial.print(": ");
  Serial.print(elapsedTime);
  Serial.println(" ms");
}

void sendOneByteCommand(uint8_t command) {
  digitalWrite(csPin, LOW);
  SPI.transfer(command);
  digitalWrite(csPin, HIGH);
}

uint8_t readStatusRegister() {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x05);
  uint8_t result = SPI.transfer(0);
  digitalWrite(csPin, HIGH);
  return result;
}

void waitUntilWriteDone() {
  // wait until WIP bit is 0
  while (readStatusRegister() & 1) {}
}

void reset() {
  sendOneByteCommand(0x66);
  sendOneByteCommand(0x99);
  delay(20);
}

void readId() {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x9f);
  uint8_t manufacturerId = SPI.transfer(0);
  uint8_t memoryType = SPI.transfer(0);
  uint8_t memoryDensity = SPI.transfer(0);
  digitalWrite(csPin, HIGH);

  Serial.print("manufacturer ID: 0x");
  Serial.print(manufacturerId, HEX);
  Serial.print(", memory type: 0x");
  Serial.print(memoryType, HEX);
  Serial.print(", memory density: 0x");
  Serial.println(memoryDensity, HEX);
  if (manufacturerId == 0x85 && memoryType == 0x60 && memoryDensity == 0x14) {
    Serial.println("ID ok");
  } else {
    error("wrong ID");
  }
}

void writeEnable() {
  sendOneByteCommand(0x06);
}

void writeAddress(uint32_t address) {
  SPI.transfer((address >> 16) & 0xff);
  SPI.transfer((address >> 8) & 0xff);
  SPI.transfer(address & 0xff);
}

// erase the whole chip
void chipErase() {
  writeEnable();
  sendOneByteCommand(0x60);
}

// erase one page
void pageErase(uint32_t address) {
  writeEnable();
  digitalWrite(csPin, LOW);
  SPI.transfer(0x81);
  writeAddress(address);
  digitalWrite(csPin, HIGH);
}

// program one page
// note: changes the content of the data array
void pageProgram(uint32_t address, uint8_t* data) {
  writeEnable();
  digitalWrite(csPin, LOW);
  SPI.transfer(0x02);
  writeAddress(address);
  SPI.transfer(data, PAGE_SIZE);
  digitalWrite(csPin, HIGH);
}

// reads data from flash
unsigned long readData(uint32_t address, uint8_t* data, uint32_t size) {
  digitalWrite(csPin, LOW);
  SPI.transfer(0x03);
  writeAddress(address);
  SPI.transfer(data, size);
  digitalWrite(csPin, HIGH);
}

void setup() {
  Serial.begin(115200);

  SPI.begin();
  SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));

  reset();
  readId();

  // erase whole chip
  chipErase();
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("chip erase time");

  // verify
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("chip erase verify error");
    }
  }

  // program first page
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0xff - i;
  }
  pageProgram(0, data);
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("page program time");

  // verify programmed data
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff - i) {
      error("page program verify error");
    }
  }

  // erase page
  pageErase(0);
  startMeasure();
  waitUntilWriteDone();
  stopMeasure("page erase time");

  // verify
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("page erase verify error");
    }
  }

  // program test data in first 2 pages
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0xff - i;
  }
  pageProgram(0, data);
  waitUntilWriteDone();
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0;
  }
  pageProgram(PAGE_SIZE, data);
  waitUntilWriteDone();

}

uint64_t counter = 0;

void loop() {
  // erase page
  pageErase(0);
  waitUntilWriteDone();

  // verify
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("page erase verify error");
    }
  }

  // program test data in first page
  for (int i = 0; i < PAGE_SIZE; i++) {
    data[i] = 0xff - i;
  }
  pageProgram(0, data);
  waitUntilWriteDone();

  // read and verify first 3 pages
  readData(0, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff - i) {
      error("page 0 verify error");
    }
  }
  readData(PAGE_SIZE, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0) {
      error("page 1 verify error");
    }
  }
  readData(2 * PAGE_SIZE, data, PAGE_SIZE);
  for (int i = 0; i < PAGE_SIZE; i++) {
    if (data[i] != 0xff) {
      error("page 2 verify error");
    }
  }

  // counter output
  counter++;
  if ((counter % 1000) == 0) {
    Serial.print(float(counter));
    Serial.println(" write tests");
  }
}

It did 8,536,000 successful write tests until it stopped with a page erase verify error. I restarted it and it did again 5,288,000 write tests until it stopped with a page 0 verify error. I measured the current with my uCurrent and it needed less than 2 mA when erasing and programming a page. The datasheet specifies 3 mA typical.

Like

Discussions

Simon Merrett wrote 09/08/2020 at 13:14 point

Hi Frank, thanks for this code. More than a year after buying some samples I finally got round to testing them. I unabashedly and blindly copied your first set of code and took a short time spent between variant.cpp and my oscilloscope to find the breadcrumb:

  pinMode(csPin, OUTPUT);

Hope it helps someone.

  Are you sure? yes | no