SPI EEPROM interfacing

I pulled a Pm25LV040 from an old PC (bios chip?) to experiment with. Search results indicate it’s a 512K x 8, 3.3V, SPI device and the pinout seems to conform to that of many similar chips.

Looking at pictures it appears that the difference between this and an SD card is the SD card is on a breakout board and has connection points and maybe some level translation built in. Have I got that right?

First I have to power it up. I assume the 3.3V pin on the UNO can power this one IC. Yes, no?

Concerning level translation: It’s my understanding that a voltage divider on the Arduino outputs will correctly drive the chip’s MOSI, SS, and SCK inputs and that a simple diode/resistor network will translate 3.3V back to 5V for the MISO pin. Am I good to go on this track?

I haven’t found a library that specifically names the Pm25LV040 as supported. The SD.h library in the IDE seems pretty generic. Is this a good place to start or should I look elsewhere?

Yes the UNO should be able to power this. The best thing is to purchase a level translator. Reason you cannot divide a voltage up. Also since this is a new endevor you want to mitigate as many potential problems as possible. The levels you are talking about are close and noise does funny things with the logic state read and the one send. I think you are on the correct track with a library.

gilshultz:
Yes the UNO should be able to power this.

Excellent! One box checked.

gilshultz:
The best thing is to purchase a level translator. Reason you cannot divide a voltage up. Also since this is a new endevor you want to mitigate as many potential problems as possible.

Since this is just for familiarization I don't relish the idea of working up an order and paying multi dollars for a single part. I was hoping to do this with parts on hand. I understand voltage division doesn't work for lower to higher, that's why I had my eye on the passive step up level shifter technique at the linked page.

gilshultz:
I think you are on the correct track with a library.

That's encouraging! Thanks for the reply!

After more reading I opted to go for the level shifter breakout board (pack of five off Ebay). It appears to work as advertised.

I first tried the IDE SD library and it reports “…initialization failed…” But, it looks like that won’t work anyway since the chip needs to be formatted.

Without touching the wiring I then made the following attempts:

Tried the SPI demo in the tutorial. This also did not recognize the EEPROM being present so I;

Worked up a small sketch to demo some basic commands, just reading/writing the config. and status registers.

/* This short sketch reads and prints the eeprom product info
    two times.  First time is via command 0xAB and requires addition
    of three dummy bytes in transmission.  Second way is via JEDEC
    command 0x9F, requiring no additional bytes transmitted.
    Next is read and printed the chip's status register
    Followed by the configuration register
*/

#include <SPI.h>
#include "PM25LV040_INSTRUCTIONS.h"

#define CS_PIN 10
#ifndef PIN_LED
#define PIN_LED 13
#endif

byte inBuffer[9]; // eeprom data in

void setup() {
  Serial.begin(115200);
  Serial.println(__FILE__);
  pinMode(PIN_LED, OUTPUT);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  SPI.begin();
}

void read_eeprom(unsigned int num_bytes) {
  unsigned int addr;
  byte resp;

  digitalWrite(CS_PIN, LOW);

  /* transmit read id command with required 3 dummy bytes */
  SPI.transfer(SPI_EEPROM_RDID);
  SPI.transfer(0x00);
  SPI.transfer(0x00);
  SPI.transfer(0x00);

  for (addr = 0; addr < num_bytes; addr++) {
    resp = SPI.transfer(0xff);
    Serial.println(resp, 16); // DISPLAY a column in hex notation
    /* success!  prints:
        0x9d, 1st mfr ID byte;
        0x7e, device ID (Pm25LV040);
        0x7f, 2nd mfr ID byte as predicted in datasheet.
    */
  }
  digitalWrite(CS_PIN, HIGH);
}

void read_JEDEC_ID(unsigned int num_bytes) {
  unsigned int addr;
  digitalWrite(CS_PIN, LOW);

  /* transmit read id command - jedec does not require 3 dummy bytes */
  SPI.transfer(SPI_EEPROM_JEDEC_ID);
  for (addr = 0; addr < num_bytes; addr++) {
    inBuffer[addr] = SPI.transfer(0xff);
  }
  Serial.print("mfr id1 = ");
  Serial.println(inBuffer[1], 16);
  Serial.print("mfr id2 = ");
  Serial.println(inBuffer[0], 16);
  Serial.print("device id = ");
  Serial.println(inBuffer[2], 16);
  digitalWrite(CS_PIN, HIGH);
}

void write_status_reg(unsigned int num_bytes) {

  digitalWrite(CS_PIN, LOW);

  /* transmit command */
  SPI.transfer(SPI_EEPROM_WREN);
  digitalWrite(CS_PIN, HIGH);
  delay(10);
  digitalWrite(CS_PIN, LOW);
  SPI.transfer(SPI_EEPROM_WRSR);
  SPI.transfer(0);
  digitalWrite(CS_PIN, HIGH);
}

void read_Status_Reg() {
  byte resp;
  digitalWrite(CS_PIN, LOW);
  SPI.transfer(SPI_EEPROM_RDSR); // returns one byte

  resp = SPI.transfer(0xff);
  Serial.println("\nStatus Register");
  Serial.print("bit 0 - wip\t");
  Serial.println(bitRead(resp, 0));
  Serial.print("bit 1 - wel\t");
  Serial.println(bitRead(resp, 1));
  Serial.print("bit 2 - BP0\t");
  Serial.println(bitRead(resp, 2));
  Serial.print("bit 3 - BP1 =\t");
  Serial.println(bitRead(resp, 3));
  Serial.print("bit 4 - BP2 =\t");
  Serial.println(bitRead(resp, 4));
  Serial.print("bit 5 -\t");
  Serial.println(bitRead(resp, 5));
  Serial.print("bit 6 -\t");
  Serial.println(bitRead(resp, 6));
  Serial.print("bit 7 - SWRD\t");
  Serial.println(bitRead(resp, 7));
  digitalWrite(CS_PIN, HIGH);
}

void loop() {

  digitalWrite(PIN_LED, LOW);

  if (Serial.read() == 'D') {
    digitalWrite(PIN_LED, HIGH);
    read_eeprom(3);
    read_JEDEC_ID(3);
    read_Status_Reg();
    write_status_reg(0); // clear write protect bits
    read_Status_Reg();
  }

}

and associated header file with defines for the EEPROM command set.

/* Pm25LV040 instruction set - for 512k SPI EEPROM

*/

#define SPI_EEPROM_WREN 0x06 // write enable command
#define SPI_EEPROM_WRDI 0x04 // WRITE DISABLE
#define SPI_EEPROM_RDSR 0x05 // read the status register, receive one byte
#define SPI_EEPROM_WRSR 0x01 // write the status register, append one byte
#define SPI_EEPROM_READ 0x03 // read data normal speed, append three byte address,
//                              receive one byte, repeatable
#define SPI_EEPROM_FAST_READ 0x0B // read data fast mode
#define SPI_EEPROM_RDID 0xab // read mfr and product ID 
#define SPI_EEPROM_JEDEC_ID 0x9F // read mfr and product by JEDEC ID command
#define SPI_EEPROM_PAGE_PROG 0x02 // page program data bytes into memory, append
//                                   three byte address, receive up to 256 bytes
#define SPI_EEPROM_RDCR 0xA1 // read the configuration register, receive one byte
#define SPI_EEPROM_WRCR 0xF1 // write the configuration register, append one byte
#define SPI_EEPROM_SECTOR_ERASE 0xD7 // sector erase command, append three byte address
#define SPI_EEPROM_BLOCK_ERASE 0xD8 // block erase, append three byte address
#define SPI_EEPROM_CHIP_ERASE 0xC7  // chip erase

Once that worked it showed that the three Block Protect bits were all set, effectively making the entire memory write protected. I added a function to clear these bits and a subsequent call to the status register function shows success.

9D
7E
7F
mfr id1 = 9D
mfr id2 = 7F
device id = 7E

Status Register
bit 0 - wip 0
bit 1 - wel 0
bit 2 - BP0 0
bit 3 - BP1 = 0
bit 4 - BP2 = 0
bit 5 - 0
bit 6 - 0
bit 7 - SWRD 0

Status Register
bit 0 - wip 1
bit 1 - wel 1
bit 2 - BP0 0
bit 3 - BP1 = 0
bit 4 - BP2 = 0
bit 5 - 0
bit 6 - 0
bit 7 - SWRD 0

Based on my limited knowledge this chip basically follows the ‘standard’ SPI EEPROM conventions as to command set and status register bit placement so I *believe *it’s getting the correct instructions and is returning expected data.

Still the SPIEEPROM tutorial code would not recognize the chip so I loaded this (GitHub - kbondarev/spieeprom: Arduino library which helps to read and write to an EEPROM over SPI) sketch which fills a 256 byte buffer with the for() loop iterator, sends buffer to the chip, then reads back and displays the just-written data. I expect to see the Value numbers matching the Address numbers. It sorta works ( the original data was all 255s ) in that the sketch will read but the data doesn’t match (sample).

Address:|241| - Value:|240|
Address:|242| - Value:|240|
Address:|243| - Value:|242|
Address:|244| - Value:|244|
Address:|245| - Value:|0|
Address:|246| - Value:|0|
Address:|247| - Value:|2|
Address:|248| - Value:|0|
Address:|249| - Value:|0|
Address:|250| - Value:|0|
Address:|251| - Value:|2|
Address:|252| - Value:|4|
Address:|253| - Value:|8|
Address:|254| - Value:|8|
Address:|255| - Value:|10|
Address:|0| - Value:|0|
Address:|1| - Value:|0|
Address:|2| - Value:|0|
Address:|3| - Value:|0|
Address:|4| - Value:|4|
Address:|5| - Value:|0|
Address:|6| - Value:|0|
Address:|7| - Value:|0|
Address:|8| - Value:|0|
Address:|9| - Value:|0|

May be relevant: It’s not random, this same sequence of written values repeats over and over, even after a reset.
My only other experience with SPI was the basic experiments with 74HC SIPO, PISO registers. Any ideas where I should be looking?

I must conclude the sketch above is defective. This one, with a few adjustments to the command codes in the .h file gives the stated output - so the chip survived transplantation.

// **********************************************************************************
// This sketch is an example of using the SPIFlash library with a Moteino
// that has an onboard SPI Flash chip. This sketch listens to a few serial commands
// Hence type the following commands to interact with the SPI flash memory array:
// - 'd' dumps the first 256bytes of the flash chip to screen
// - 'e' erases the entire memory chip
// - 'i' print manufacturer/device ID
// - [0-9] writes a random byte to addresses [0-9] (either 0xAA or 0xBB)
// Get the SPIFlash library from here: https://github.com/LowPowerLab/SPIFlash
// **********************************************************************************
// Copyright Felix Rusu, LowPowerLab.com
// Library and code by Felix Rusu - felix@lowpowerlab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************


#include <SPIFlash.h>    //get it here: https://github.com/LowPowerLab/SPIFlash
#include <SPI.h>

#define SERIAL_BAUD      115200
char input = 0;
long lastPeriod = -1;

//#ifdef __AVR_ATmega1284P__
//#define LED           15 // Moteino MEGAs have LEDs on D15
//#define FLASH_SS      23 // and FLASH SS on D23
//#else
#define LED           13 // Arduinos have LEDs on D13
#define FLASH_SS      10 // and FLASH SS on D10
//#endif

//////////////////////////////////////////
// flash(SPI_CS, MANUFACTURER_ID)
// SPI_CS          - CS pin attached to SPI flash chip (8 in case of Moteino)
// MANUFACTURER_ID - OPTIONAL, 0x1F44 for adesto(ex atmel) 4mbit flash
//                             0xEF30 for windbond 4mbit flash
//                             0x&F9d for PMC 4mbit Pm25LV040  
//////////////////////////////////////////

SPIFlash flash(FLASH_SS, 0x7f9d); // init class with SS pin no. & chip ID

void setup() {
  Serial.begin(SERIAL_BAUD);
  Serial.print("Start...");

  if (flash.initialize())
  {
    Serial.println("Init OK!");
    Blink(LED, 20, 10);
  }
  else
    Serial.println("Init FAIL!");

  delay(1000);
}

void loop() {
  // Handle serial input (to allow basic DEBUGGING of FLASH chip)
  // ie: display first 256 bytes in FLASH; erase chip; write bytes at first 10 positions, etc
  if (Serial.available() > 0) {
    input = Serial.read();
    if (input == 'd') //d=dump flash area
    {
      Serial.println("Flash content:");
      int counter = 0;
      Serial.print('.');

      while (counter <= 256) {
        Serial.print(flash.readByte(counter++), HEX);
        if (counter % 16 == 0) Serial.println();
        Serial.print('.');

      }

      Serial.println();
    }
    else if (input == 'e')
    {
      Serial.print("Erasing Flash chip ... ");
      flash.chipErase();
      while (flash.busy());
      Serial.println("DONE");
    }
    else if (input == 'i')
    {
      Serial.print("DeviceID: ");
      Serial.println(flash.readDeviceId(), HEX);
    }
    else if (input >= 48 && input <= 57) //0-9
    {
      Serial.print("\nWriteByte("); Serial.print(input); Serial.print(")");
      flash.writeByte(input - 48, millis() % 2 ? 0xaa : 0xbb);
    }
  }

  // Periodically blink the onboard LED while listening for serial commands
  if ((int)(millis() / 500) > lastPeriod)
  {
    lastPeriod++;
    pinMode(LED, OUTPUT);
    digitalWrite(LED, lastPeriod % 2);
  }
}

void Blink(byte PIN, int DELAY_MS, byte loops)
{
  pinMode(PIN, OUTPUT);
  while (loops--)
  {
    digitalWrite(PIN, HIGH);
    delay(DELAY_MS);
    digitalWrite(PIN, LOW);
    delay(DELAY_MS);
  }
}

I don't know anything about this chip, but the level shifter is limited as to speed. Basically the high state in either direction is just provided by a 10K pullup resistor to the relevant supply. So if things are off just a little bit, you may just need to slow things down. Each "gate" of the shifter is an N-channel mosfet with the gate tied to 3.3V. If the transmitting side is low, the mosfet turns on, thereby passing the low through to the other side. But if the transmitting side is high, the mosfet is off, and the pullup resistors take over.

But you do need level shifting of some kind on the three processor SPI outputs. Those outputs are at 5V, and you can't connect them directly to the memory chip if it's being powered at 3.3V.

ShermanP:
I don't know anything about this chip, but the level shifter is limited as to speed. Basically the high state in either direction is just provided by a 10K pullup resistor to the relevant supply. So if things are off just a little bit, you may just need to slow things down.

You may be on to something there. The library includes this line:

  SPI.setClockDivider(SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible

That may be why it works while the first library doesn't.

MicroSD modules use the 74LVC125A as a level shifter from 5V to 3.3V. It has a normal totem pole output, so it's pretty fast. It doesn't shift the 3.3V MISO line back to 5V, but apparently the 5V Arduinos read 3.3V as "high" anyway. But if you run MISO through the shifter, the /EN pin for that gate should be tied to CS, not ground.

ShermanP:
But if you run MISO through the shifter, the /EN pin for that gate should be tied to CS, not ground.

I don't know what this means.

The LVC125 has four gates, each with an input and an output, and an ENable pin that is active low. If the /EN pin is low, the output follows the input, but if /EN is high, the output is Off (high impedance, tristate).

If there are multiple devices using SPI, all of their MISO pins are connected together, but the only one that's supposed to be driving the line is the device with an active /CS pin. All the others are supposed to go tristate. However, most of the microSD modules available now just tie all four /EN pins to ground, which is wrong. The /EN pin for MISO should be tied to /CS so the gate will be active only when that device is selected.

So I'm just saying if you ever want to build your own (faster) level shifter, keep that MISO issue in mind so your memory device will play well with others.

ShermanP:
If there are multiple devices using SPI, all of their MISO pins are connected together, but the only one that's supposed to be driving the line is the device with an active /CS pin. All the others are supposed to go tristate. However, most of the microSD modules available now just tie all four /EN pins to ground, which is wrong. The /EN pin for MISO should be tied to /CS so the gate will be active only when that device is selected.

Oh, OK. That's good to know. Meanwhile, in the interest of science, I changed the divider ratio to SPI_CLOCK_DIV2 with no adverse effect.