SPI EEPROM Write / Read

Hi concerns Arduino DUE and SPI EEPROM (25LC512)
I tried writing data to the EEPROM and reading it back.
I am still unable to read the data back.
See the code below, please advise how to solve this.

//  SPI Serial EEPROM 25LC512 I/P          Arduino DUE

//  1 CS  Low Actief                      Pin 4
//  2 MISO                                 ,, PA25 CIPO                                      
//  3 WP to Vcc / 3.3V
//  4 Vss / GND
//  5 MOSI                                 ,, PA26 COPI
//  6 SCK                                  ,, PA27 SCK
//  7 HOLD to Vcc / 3.3V
//  8 Vcc / 3.3V


  const byte  WREN = 0b110;               // ?
  const byte  WRITE_DATA = 0b10;					// write opcode
  const byte READ_DATA = 0b10;					  // read opcode
  uint16_t ADDR = 0x00;							      // address 0
  byte data_write = 0x04;				          // data for test = 4
  #include <SPI.h>


void setup() {

	Serial.begin(9600);

	SPI.begin(4);                           // Special chip select pin 4
	SPI.setClockDivider(4, 84);             // Clock 
	SPI.setDataMode(4,SPI_MODE0);           // Clock polarity
	SPI.setBitOrder(4,MSBFIRST);            // Most significant bit shifted in and out first
}

void loop() {

      SPI.transfer( 4, WREN, SPI_CONTINUE);
      SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE);				  // send command
      SPI.transfer( 4, ADDR, SPI_CONTINUE );				      // address High   
      SPI.transfer( 4, data_write, SPI_CONTINUE );        // data write on address low
      SPI.transfer( 4, READ_DATA, SPI_CONTINUE );				  // read command
	    SPI.transfer( 4, ADDR, SPI_CONTINUE );              // address High
	    byte data_read = SPI.transfer(4,ADDR);		          // data read on address low
	    SPI.endTransaction(); 
 
      delay (1000);
      Serial.println ( data_read );
}

Have you tried any of the libraries that support SPI EEPROMs - just to check that the Due can read & write to the EEPROM?

Hi Markd833
As far as I know, these are libraries for I2C, so far I have not been able to find something for the DUE spi extended library that works well.

According to the Microchip datasheet, the READ command is 0x03. Try changing the code to:

const byte READ_DATA = 0b11;					  // read opcode
1 Like

I've not played with the Due, but surely, after the write transfer you would nead a rising edge CS# to close the write before you start the read.

https://ww1.microchip.com/downloads/en/DeviceDoc/22065C.pdf section 2.2 states

"While the write is in progress, the STATUS register may be read to check the status of the WPEN, WIP, WEL, BP1 and BP0 bits (Figure 2-6). A read attempt of a memory array location will not be possible during a write cycle. When the write cycle is completed, the write enable latch is reset."

I am also thinking that the WREN is a single byte command, and it too needs a closing CS#.

      SPI.transfer( 4, WREN, SPI_LAST);
      SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE);				  // send command
      SPI.transfer( 4, ADDR, SPI_CONTINUE );				      // address High   
      SPI.transfer( 4, data_write, SPI_LAST );        // data write on address low
      SPI.transfer( 4, READ_DATA, SPI_CONTINUE );				  // read command
	    SPI.transfer( 4, ADDR, SPI_CONTINUE );              // address High
	    byte data_read = SPI.transfer(4,ADDR);		          // data read on address low
	    SPI.endTransaction(); 

Otherwise, yes, you have read and write defined as the same opcode, so that seems wrong, as mentioned in another reply.

I changed the code but I still don't get any data back.
I also changed the READ_DATA code to 0b11, sorry it was a stupid mistake.

A quick read of the DUE SPI documentation and a bit of guesswork and incorporating the thoughts of @glorifiedplumber (which I think are reasonable assumptions), this is my untested stab at a fix:

void loop() {

      SPI.transfer( 4, WREN );								// Enable write access

      SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE);	        // send write command
      SPI.transfer16( 4, ADDR, SPI_CONTINUE );		        // address - 2 bytes
      SPI.transfer( 4, data_write);						    // data write on address low

      SPI.transfer( 4, READ_DATA, SPI_CONTINUE );		    // read command
      SPI.transfer16( 4, ADDR, SPI_CONTINUE );              // address - 2 bytes
	  byte data_read = SPI.transfer(4, 0x00 );		        // data read on address low
      SPI.endTransaction(); 
 
      delay (1000);
      Serial.println ( data_read );
}

I'm not sure that the SPI transfer function will send out 2 bytes if you give it a 16-bit datatype. Apparently there is the transfer16 function which I think you need here - or split your 16-bit address into 2 bytes and do 2 calls to transfer.

If you don't check the status register, then you may need to insert a delay between enabling write access and writing data. Similarly you may need to insert a delay between writing the data and reading it back.

1 Like

Do I feel silly for not checking the address size. Yes, you need to send that as a 16 bit transfer. Thanks @markd833 for calling me out on that one.

	// Transfer functions
	byte transfer(byte _pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);
	uint16_t transfer16(byte _pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);
	void transfer(byte _pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);
	// Transfer functions on default pin BOARD_SPI_DEFAULT_SS
	byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) { return transfer(BOARD_SPI_DEFAULT_SS, _data, _mode); }
	uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST) { return transfer16(BOARD_SPI_DEFAULT_SS, _data, _mode); }
	void transfer(void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST) { transfer(BOARD_SPI_DEFAULT_SS, _buf, _count, _mode); }

If there is a transfer16 ,it should clock out 16 bits. This might break down interally to two byte pushes to the SPI controller.

There is a the block transfer mode, where you could pass a buffer and the size of buffer. I'd use the transfer16 for the address, save you managing which byte to send first.
If I had more than one byte to write to that address I'd use the bulk transfer.

I agree that you might need to look at status for a RDY bit. I see there is a WIP bit. Remember, even if you have finished clocking data across the SPI bus, the internal EEPROM controller needs to update the non-volatile memory from its internal buffers, so the read access won't be available. This will be more of a problem if you update data on different pages.

Good luck wiht the EEPROM, they are very useful devices.


I'm a little frustrated how the Arduino SPI commands vary based on HW. I had been assuming that Arduino brought a common abstraction layer. This is not the SPI interface I'm using on the ESP32.

Hello,

I added the Transfer functions to the current program, and tested both programs.
But I'm not getting good results yet.
Furthermore, what you are doing now is beyond my knowledge, so I may be doing it wrong.


//  SPI Serial EEPROM 25LC512 I/P          Arduino DUE

//  1 CS  Low Actief                      Pin 4
//  2 MISO                                 ,, PA25 CIPO                                      
//  3 WP to Vcc / 3.3V
//  4 Vss / GND
//  5 MOSI                                 ,, PA26 COPI
//  6 SCK                                  ,, PA27 SCK
//  7 HOLD to Vcc / 3.3V
//  8 Vcc / 3.3V


  const byte  WREN = 0b110;               // ?
  const byte  WRITE_DATA = 0b10;					// write opcode
  const byte READ_DATA = 0b11;					  // read opcode
  uint16_t ADDR = 0x00;							      // address 0
  byte data_write = 0x04;				          // data for test = 4
  #include <SPI.h>


void setup() {

	Serial.begin(9600);

	SPI.begin(4);                           // Special chip select pin 4
	SPI.setClockDivider(4, 84);             // Clock 
	SPI.setDataMode(4,SPI_MODE0);           // Clock polarity
	SPI.setBitOrder(4,MSBFIRST);            // Most significant bit shifted in and out first
}

void loop() {
/* 
      SPI.transfer( 4, WREN, SPI_LAST);
      SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE);          // send command
      SPI.transfer( 4, ADDR, SPI_CONTINUE );              // address High   
      SPI.transfer( 4, data_write, SPI_LAST );        // data write on address low
      SPI.transfer( 4, READ_DATA, SPI_CONTINUE );         // read command
      SPI.transfer( 4, ADDR, SPI_CONTINUE );              // address High
      byte data_read = SPI.transfer(4,ADDR);              // data read on address low
      SPI.endTransaction(); 
      delay (1000);
      Serial.println ( data_read );
*/
      SPI.transfer( 4, WREN );                // Enable write access

      SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE);         // send write command
      SPI.transfer16( 4, ADDR, SPI_CONTINUE );            // address - 2 bytes
      SPI.transfer( 4, data_write);               // data write on address low
      SPI.transfer( 4, READ_DATA, SPI_CONTINUE );       // read command
      SPI.transfer16( 4, ADDR, SPI_CONTINUE );              // address - 2 bytes
      byte data_read = SPI.transfer(4, 0x00 );            // data read on address low
      SPI.endTransaction(); 
      delay (1000);
      Serial.println ( data_read );

}

 // Transfer functions
    byte transfer(byte _pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);
    uint16_t transfer16(byte _pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);
    void transfer(byte _pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);
    // Transfer functions on default pin BOARD_SPI_DEFAULT_SS
    byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) { return transfer(BOARD_SPI_DEFAULT_SS, _data, _mode); }
    uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST) { return transfer16(BOARD_SPI_DEFAULT_SS, _data, _mode); }
    void transfer(void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST) { transfer(BOARD_SPI_DEFAULT_SS, _buf, _count, _mode); }

Hello,

The Forum site is not very clear to me yet, I don't know how to add a photo yet.

So for the sake of clarity, I will send the photo directly via email of the serial data.

Yellow = serial data out

Blue = serial data in

Maybe it will provide some clarity.

Thanks

Jan

I see you have a 4 channel scope. I would suggest adding CS# and SCLK to your capture.

Without a clean transition to low on CS#, then the EEPROM will not drive SDO (POCI/MISO).
Seeing where the SCLK is active would help us identify individual bits in the protocol, but for now CS# is giong to help a lot.

If you look where channel 1 starts floating above ground, that may well be aligned with the CS# low period, but that is a guess at this time. It might also occur when the Arduino's SPI port is enabled, as at that time the POCI/MISO line should become an input to the controller, and any influence it had on the external signal should be removed. The EEPROM should be driving the lines at this time. Either there is a conflict, or neither end is driving, it looks more like the latter.

The schematic will also be key to look at right now, to see if anything else is contributing to the signal.


Are you allowed to use a thumb drive with that scope? If so, you can take screen captures and share them, rather than a photo with glare on it. I don't think I ever saw a Tek scope made after 2000 that didn't have a USB port up front. Maybe the newer ones rely on the network.

Hi glorifiedplumber,

To avoid mistakes, I first send the information by email.

I hope this is information, otherwise let me know.

Regards,

Jan

Test setup

Here is a measurement with the program below.

// SPI Serial EEPROM 25LC512 I/P Arduino DUE

// 1 CS Low Actief Pin 4

// 2 MISO ,, PA25 CIPO

// 3 WP to Vcc / 3.3V

// 4 Vss / GND

// 5 MOSI ,, PA26 COPI

// 6 SCK ,, PA27 SCK

// 7 HOLD to Vcc / 3.3V

// 8 Vcc / 3.3V

const byte WREN = 0b110; // ?

const byte WRITE_DATA = 0b10; // write opcode

const byte READ_DATA = 0b11; // read opcode

uint16_t ADDR = 0x00; // address 0

byte data_write = 0x04; // data for test = 4

#include <SPI.h>

void setup() {

Serial.begin(9600);

SPI.begin(4); // Special chip select pin 4

SPI.setClockDivider(4, 84); // Clock

SPI.setDataMode(4,SPI_MODE0); // Clock polarity

SPI.setBitOrder(4,MSBFIRST); // Most significant bit shifted in and out first

}

void loop() {

SPI.transfer( 4, WREN, SPI_LAST);

SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE); // send command

SPI.transfer( 4, ADDR, SPI_CONTINUE ); // address High

SPI.transfer( 4, data_write, SPI_LAST ); // data write on address low

SPI.transfer( 4, READ_DATA, SPI_CONTINUE ); // read command

SPI.transfer( 4, ADDR, SPI_CONTINUE ); // address High

byte data_read = SPI.transfer(4,ADDR); // data read on address low

SPI.endTransaction();

delay (1000);

Serial.println ( data_read );

/*

SPI.transfer( 4, WREN ); // Enable write access

SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE); // send write command

SPI.transfer16( 4, ADDR, SPI_CONTINUE ); // address - 2 bytes

SPI.transfer( 4, data_write); // data write on address low

SPI.transfer( 4, READ_DATA, SPI_CONTINUE ); // read command

SPI.transfer16( 4, ADDR, SPI_CONTINUE ); // address - 2 bytes

byte data_read = SPI.transfer(4, 0x00 ); // data read on address low

SPI.endTransaction();

delay (1000);

Serial.println ( data_read );

*/

}

// Transfer functions

byte transfer(byte _pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);

uint16_t transfer16(byte _pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);

void transfer(byte _pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);

// Transfer functions on default pin BOARD_SPI_DEFAULT_SS

byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) { return transfer(BOARD_SPI_DEFAULT_SS, _data, _mode); }

uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST) { return transfer16(BOARD_SPI_DEFAULT_SS, _data, _mode); }

void transfer(void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST) { transfer(BOARD_SPI_DEFAULT_SS, _buf, _count, _mode); }

Signal colors

Yellow CS

Blue CLK

Red SI

Green SO

Here is a measurement with the program below.

// SPI Serial EEPROM 25LC512 I/P Arduino DUE

// 1 CS Low Actief Pin 4

// 2 MISO ,, PA25 CIPO

// 3 WP to Vcc / 3.3V

// 4 Vss / GND

// 5 MOSI ,, PA26 COPI

// 6 SCK ,, PA27 SCK

// 7 HOLD to Vcc / 3.3V

// 8 Vcc / 3.3V

const byte WREN = 0b110; // ?

const byte WRITE_DATA = 0b10; // write opcode

const byte READ_DATA = 0b11; // read opcode

uint16_t ADDR = 0x00; // address 0

byte data_write = 0x04; // data for test = 4

#include <SPI.h>

void setup() {

Serial.begin(9600);

SPI.begin(4); // Special chip select pin 4

SPI.setClockDivider(4, 84); // Clock

SPI.setDataMode(4,SPI_MODE0); // Clock polarity

SPI.setBitOrder(4,MSBFIRST); // Most significant bit shifted in and out first

}

void loop() {

/*

SPI.transfer( 4, WREN, SPI_LAST);

SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE); // send command

SPI.transfer( 4, ADDR, SPI_CONTINUE ); // address High

SPI.transfer( 4, data_write, SPI_LAST ); // data write on address low

SPI.transfer( 4, READ_DATA, SPI_CONTINUE ); // read command

SPI.transfer( 4, ADDR, SPI_CONTINUE ); // address High

byte data_read = SPI.transfer(4,ADDR); // data read on address low

SPI.endTransaction();

delay (1000);

Serial.println ( data_read );

*/

SPI.transfer( 4, WREN ); // Enable write access

SPI.transfer( 4, WRITE_DATA, SPI_CONTINUE); // send write command

SPI.transfer16( 4, ADDR, SPI_CONTINUE ); // address - 2 bytes

SPI.transfer( 4, data_write); // data write on address low

SPI.transfer( 4, READ_DATA, SPI_CONTINUE ); // read command

SPI.transfer16( 4, ADDR, SPI_CONTINUE ); // address - 2 bytes

byte data_read = SPI.transfer(4, 0x00 ); // data read on address low

SPI.endTransaction();

delay (1000);

Serial.println ( data_read );

}

// Transfer functions

byte transfer(byte _pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);

uint16_t transfer16(byte _pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);

void transfer(byte _pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);

// Transfer functions on default pin BOARD_SPI_DEFAULT_SS

byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) { return transfer(BOARD_SPI_DEFAULT_SS, _data, _mode); }

uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST) { return transfer16(BOARD_SPI_DEFAULT_SS, _data, _mode); }

void transfer(void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST) { transfer(BOARD_SPI_DEFAULT_SS, _buf, _count, _mode); }

So, I was right, you have an undriven POCI line.

SPI name used to be tricky, then with the short lived introductions on CIPO/COPI and the final switch to PICO/POCI (which Arduino still hasn't caught up to yet), it got a whole lot more complicated.

I hope no one is offended by a quick summary of names, as all conventions have been included in the disussion, code, and data sheet.

MOSI aka COPI aka PICO have directionallity in the name, and you wire like to like. These are all Controller(master) Output, Peripheral (slave) Input.

MISO aka CIPO aka POCI have directionality in the name, and you wire like to like. These are all Controller Input, Periphera Output.

The other naming you will see are from the point of view of the device the are degfining.

SO (serial out), SDO (serial data output), Q (flop output gate) are all output from the device they are specified for.

SI, SDI and D (flop data input) are all input to the device they are specified for.

The output of the controller needs to do to the input of the peripheral.

In you case you took the EEPROM SO to be PICO and SI to be POCI, but you have that backwards.

Looking at the scope trace, the SDO/POCI line was being held low probably by the Arduino (internal pull down, but that is a guess) until just before the CS falling edge, at which time the SPI port would have been configured for transfer, and the PICO line turned into an un-driven input. The EEPROM should be driving its SDO out at that time.

Since both ends are trying to drive the PICO line, I am surprised at how readable that signal is. I'll be some of the noise on hte magenta trace will clean up once you swap the too leads.

This has been fun. Thanks for sharing.

EDIT:

If you want to see the differnece with and without the "SPI_LAST" (implicit of explicit) you can try this experiment. It will show if my inital comment about CS# edges was correct, and how things go wrong. But this was only extra fun after fixing the wiring.


Oh, for fun, restore the original code, with all the SPI_CONTINUEs, and you will see the three CS lows merge into one. The EEPROM won't respond to your commands correctly at that point, but you will see another example of how to spot SPI issues.

I didn't notice the 2 parameter call with the implicit SPI_LAST when I read spi.h, but that was a good find. Less typing, and will autmatically close on a buffer write.

Final statement. EEPROM writes finish up fast, but typically the write is to internal RAM, and on the rising edge of CS# an internal update of the non-volatile PROM portion takes place. You may need some turn around time between the write and the read commands. Looks like 5ms delay is all you need. See Twc ( Time internal Write Cycle) in the data sheet.


Hello,

I'm lost for a moment now.
I have tested all combinations but still no good results.
To avoid mistakes, please provide a code example that you will test and then review the result.

Sorry, ignore post #14, that was "more interesting stuff" to experiment with if you want.

Post #13 is the solution to your problem. It's not a code change. You must update your wiring.

  • MCU (Arduino) MOSI connects to EEPROM SI.
  • EEPROM SO connects to MCU MISO.

Okay, I understand that I didn't explicitly report that, but I have also tested that.

I have changed the SI and the SO data input and output, which I can easily change.

I also replaced the IC to check whether the IC was possibly broken.

Review HW

Okay. Let's look at that bread board again.

RED - power rails.
BLUE - GND
BROWN - CS#
ORANGE - SCK
GREEN (teal?) - ???
PURPLE - ???

EEPROM pin one at K22

Pins seem to be connected to

pin name wire Arduino pin Comment/question
1 CS# BROWN 4 GPIO Matches SW, GPIO 4 is used as CS#
2 SO PURPLE SPI 4 COPI BAD SO = PO, should be conneted to CIPO
3 WP# RED 3.3v See data sheet table 2-4 for details
4 VSS (gnd) BLUE GND
5 SI GREEN SPI 1 CIPO BAD SI = PI, should be connected to COPI
6 SCLK ORANGE SPI 3 SCK
7 HOLD# RED 3.3v
8 VCC (supply) RED 3.3v What is the blue component between PWR & GND? filtering cap?

Did re-wiring change anything?

So we had a mis-wiring. You say you changed that, and your code still didn't work.

What di the scope show? Last traces we saw here showed that when CS# was low (active) the SO was floating. After the reversal did SO hold a clean low, a clean high or clean square wave? It should be one of these three, otherwise we still have a wiring issue.

If this was a virgin part, then it should be in an erased state. Any value read out should be 0xff, which would be all high on the SO line. If you write had worked, then we would read back your value. If this was a re-used part, we have no idea what the read would fetch.

SW Controlled write protection

edit: I originally missed the 3.3v to WP#, as configured WREN should suffice.

Look at table 2-4 to work out what you have and what you want.

There are commands to access the status register. I would suggest that you add a read of the status register in your start up to make sure WPEN is disabled (0) so when you set WEL with the WREN command it has control over write protect.

Summary

  1. check scope for cleaner/different signal. The fix should change something.
  2. check Status Reg in software.
  3. reach out again if you continue to have problems, but include updated board photo and scope screen captures please.

What is the blue component between PWR and GND? Yes it is a buffer capacitor 100nF.
WP# = 3.3V (power supply voltage)
One more question should I use the SPI_LAST codes or not.
Hence the question regarding an example code.

I am a fan of explicit, but seeing the waveform, the implicit last in the two parameter call is happening. The two calls do the same, but one is easier to understand what is going on.

I would say that the best practice would be to use the explicit. Say you were to later have a write where send the WREN, WRITE ADDR and you send several buffer writes, using the three parameter you can easily see when you are closing the write with the SPI LAST.

With the implicit last, you may later add in another transfer after the final one, and wonder why it was never received.

Explicit code may be more verbose, but the intent is clearer, making debug easier.

And yes, looking closer, I missed that short red jumper for WP#. If you are not writing still, then the WPEN flag is probably set.