Posted on Leave a comment

Microchip 25LC256 EEPROM on STM32 Blue Pill and Arduino

For the big, bad synth project, I needed an EEPROM.  I selected the Microchip 25LC256 from Digikey simply because it had excess capacity (256k) and was thru-hole.   (I always recommend this approach when jumping into a new arena of unpolished prototyping).  I had never worked with external EEPROM before.   It turns out that EEPROM is fairly straight forward.  You pick an address and you write to it.  It’s not all that different from how a file cabinet works.  When you run out of room in one drawer, you move on to the next drawer.  Sometimes you want to put stuff in separate drawers even if they aren’t full.  These”drawers” are known as pages.

The page size in the Microchip 25LC256 is 64 bytes.   At the moment, each preset for my synth requires about 40 bytes, so this means I’m gonna waste 24 bytes per page in the interest of staying organized.  There are many other chips in the 25LCxxx series of varying sizes.  The smaller capacity EEPROM chips use smaller page sizes, which means I’ll need to dedicate multiple pages to each preset.   You’ll see a function to write a 64 byte array to the EEPROM all at once below.  This is significantly faster than writing one byte at a time as there is a 5ms penalty after finishing up the write process no matter if you are writing 1 byte or 64 bytes.

Of course, it took longer than I hoped to effectively use the Microchip 25LC256 EEPROM.  What else is new!    You’ll certainly want to adapt these functions below to suit your needs, but this code does work on an STM32 Blue Pill in PlatformIO using the Arduino framework.  I suspect the critical piece I was missing was slowing down the SPI clock. You’ll see this code uses a clock divider of 16.  According to the datasheet, the larger the Vcc, the faster clock you can get away with.

I’m moving these functions into a dedicated Microchip 25LCxx library which I’ll give away when its ready.  For now, here’s the working code in a rough format.



#include "Arduino.h"

#include <SPI.h>

byte pin_SS2 = PB12;

HardwareSerial Serial3(USART3); // PB11 (RX3)  PB10   (TX3)

void EEPROMsetup()
{
  //SPI_2.begin();
  pinMode(pin_SS2, OUTPUT);
  digitalWrite(pin_SS2, HIGH);
}

byte EEPROMread(uint16_t address)
{
  byte read_buffer = 0;
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000011);         // Set EEPROM to read mode.
  SPI.transfer16(address);          // Send the 16-bit address
  read_buffer = SPI.transfer(0xFF); // A dummy byte is sent to read the buffer.
  digitalWrite(pin_SS2, HIGH);

  Serial3.print("Reading:  ");
  Serial3.println(read_buffer);
  Serial3.print("Address:  ");
  Serial3.println(address);
  Serial3.println("");

  return read_buffer;
}

void EEPROMwriteByte(uint16_t address, byte value)
{
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000110); // WREN write enable latch.
  digitalWrite(pin_SS2, HIGH);

  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000010); // WRITE INSTRUCTION
  SPI.transfer16(address);
  SPI.transfer(value);
  digitalWrite(pin_SS2, HIGH);
  delay(5); // Taken from datasheet.  This slows down EEPROMWriteByte a bit, but if a second write is started before the
  // first can be completed, it will ignore the second one.  That's bad.   While the write is in progress, the STATUS register
  // may be read to check the status of the Write-in-process (WIP) bit (Figure 2-6).  I haven't tried that one yet.

  Serial3.println("Write complete");
}

byte EEPROMreadStatus()
{
  byte read_buffer;
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000101); // Red STATUS register
  read_buffer = SPI.transfer(0xFF); // A dummy byte to read the buffer.
  digitalWrite(pin_SS2, HIGH);
  return read_buffer;
  // Serial3.print("Status:  ");
  // Serial3.println(read_buffer);
  // Serial3.println("");
}

void EEPROMWritePage(uint16_t page_num, byte *save_this_array)
{
  // Pages are 64 bytes.  I'll give each preset a page for simplicity.

  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000110); // WREN write enable latch.
  digitalWrite(pin_SS2, HIGH);
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000010); // WRITE INSTRUCTION

  SPI.transfer16(page_num * 64); // Send address of page number.  Must start at n * 64.

  for (int i = 0; i < 64; i++)
  {
    SPI.transfer(save_this_array[i]); // WRONG WRONG WRONG WRONG WRONG
  }

  digitalWrite(pin_SS2, HIGH); // close up the write process
  delay(5);                    // See TWC in datasheet https://ww1.microchip.com/downloads/en/DeviceDoc/25AA256-25LC256-256K-SPI-Bus-Serial-EEPROM-20001822H.pdf

  Serial3.println("Page Write complete");

}

void setup()
{
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16); // 72Mhz / 16 = 4.5Mhz
  SPI.setBitOrder(MSBFIRST);

  Serial3.begin(19200); // PB11 (RX)  PB10   (TX)
  Serial3.println("Serial3: 3");
  EEPROMsetup();

  byte preset_arr[64];

  for (int i = 0; i < 64; i++)
  {
    preset_arr[i] = i;
  }

  EEPROMWritePage(0, preset_arr);

  EEPROMWritePage(2, preset_arr);

  // Read Page Two   Page 2 starts at 64*2 and ends at (64*3 - 1)
  for (int i = 128; i < 192; i++)
  {
    EEPROMread(i);
  }

  EEPROMwriteByte(50, 21);
  EEPROMwriteByte(52, 22);
  EEPROMwriteByte(30, 23);

} // end of setup

void loop()
{
  EEPROMread(50);
  EEPROMread(52);
  EEPROMread(30);
  delay(500);
}