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.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
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