SPI interfacing experiments: EEPROMs, Bus Pirate, ADC/OPT101 with Raspberry Pi

R. X. Seger
15 min readSep 15, 2016

--

MCP3304 8–channel SPI ADC (bottom) connected to a OPT101 photodiode (top); and also a disconnected 25LC640 SPI EEPROM (middle)

Serial Peripheral Interface (SPI) is a popular four-wire protocol for high-speed short-distance communication within electronic circuits.

In this article, I’ll use SPI to read/write an EEPROM with a Bus Pirate, and then read an ADC analog-to-digital converter chip. Lastly I’ll connect the ADC to a Raspberry Pi, also using SPI, and use it to read the ambient light level through a Texas Instruments OPT101 photodiode, toggling an LED over GPIO ultimately to build a simple nightlight circuit.

SPI vs I²C

SPI isn’t the only inter-chip interfacing protocol out there, another popular protocol is I²C, which I covered in I²C interfacing on the Bus Pirate and Raspberry Pi to serial EEPROMs for a HAT. Some chips support only SPI, others only I²C, and a few even support both.

There are quite a few differences between SPI and I²C, but some of the most user-visible include: SPI is full-duplex, hence has wires for both master-input/slave-output (MISO) and master-output/slave-input (MOSI), whereas I²C only has one serial data (SDA) line. I²C uses 7-bit (or longer) device addresses you can scan for, in contrast SPI uses multiple chip select (CS) (aka chip enable, CE, or slave select, SS) lines, which you have to wire appropriately and activate (may be active-low or active-high) when you want to communicate with a particular device. SPI’s wiring from Wikipedia:

That’s only for one slave; for multiple slaves you would wire SCLK/MOSI/MISO together, and each /SS (called CE0 and CE1 on the Raspberry Pi, supporting two slaves) to each slave device.

SPI has more variations than I²C, but “mode 0” seems to be the most common. It can operate at various speeds, some very high (125+ MHz). To get started, I’m going to first wire up a SPI-based EEPROM.

SPI EEPROMs

SPI is a common interface for memories, especially newer fast Flash memory chips. The popular flashrom utility includes support for writing to hundreds of different types of flash chips over SPI (and a few other interfaces).

But in this article, I’m going to try using block-erasable EEPROMs first, using a trusty old Bus Pirate.

EEPROM: 25LC640, 64KBit

I²C is another protocol supported by many EEPROM chips, I used it in I²C interfacing on the Bus Pirate and Raspberry Pi to serial EEPROMs for a HAT, on the Microchip 24LC512 and 24LC515. Microchip has several other EEPROM product lines, including the 25LC640 supporting SPI.

See Hackaday: Bus Pirate v3: 25AA/25LC serial EEPROM (SPI) for the pinout. Connect the EEPROM 25LC640 pins to a Bus Pirate v3:

  • 1: /CS, chip select → CS (white)
  • 2: SO, serial out → MISO (black)
  • 3: /WP, write protect → AUX (blue)
  • 4: Vss, ground → GND (brown)
  • 5: SI, serial in → MOSI (gray)
  • 6: SCK, serial clock, → CLK (purple)
  • 7: /HOLD, hold → +3.3V (red)
  • 8: Vcc, power → +5.0V (orange)

to match the pinout from the datasheet:

In the 24LC640 datasheet: “Not recommended for new designs”, Microchip instead recommends 24LC640A, comparison: the A model supports a maximum clock frequency of 10 MHz (up from 3 MHz), and supports write-protecting quarter/half/whole array. Not much of a difference, the basic functionality should be the same between the 640 and 640A.

Configure the Bus Pirate as follows:

  • m5 to enter SPI mode
  • 3 to select speed 250 kHz
  • 1 to select clock polarity idle low (default)
  • 2 to select output clock edge active to idle (default)
  • 2 to select input sample phase end
  • 2 to select /CS (default)
  • 2 to select output type normal (H=3.3V, L=GND)

Instruction set from the datasheet:

In decimal: 1=WRSR, 2=WRITE, 3=READ, 4=WRDI, 5=RDSR, 6=WREN

To read 10 bytes at 0x0000:

  • W to turn power supply on
  • Read five bytes: [3 0 0 r:5] (out of the factory, reads 0xFF everywhere)

To write a few bytes:

  • A to set AUX to HIGH, therefore /WP to HIGH, disabling write-protect
  • Set the write enable latch (WREN): [6]
  • Write five bytes: [2 0 0 1 2 3 4 5]
  • Read back those five bytes you just wrote: [3 0 0 r:5]

should return: READ: 0x01 0x02 0x03 0x04 0x05

EEPROM: ST95010WP 128-byte SOIC-8 from a Tripp Lite BC350 UPS

Recall the surface-mount EEPROM chip on the UPS from Building an H-Bridge from a salvaged Uninterruptible Power Supply:

  • U6: 95010WP K938Q 1K serial SPI EEPROM, datasheet

I’m curious what is on this, and why an UPS needs an EEPROM. From the datasheet: 4K/2K/1K Serial SPI EEPROM with Positive Clock Strobe. This model is the lowest capacity, 1Kbit (128 x 8), and supports a wider range of voltage (the “W” series): 2.5–5.5V, tolerant to +3.3V and +5.0V.

Pinout from datasheet, SOIC-8 plastic small outline 150 mil width (JEDEC):

Connect this chip to the Bus Pirate:

  • 1: /S, chip select → CS (white)
  • 2: Q, serial data output → MISO (black)
  • 3: /W, write protect → AUX (blue)
  • 4: Vss, ground → GND (brown)
  • 5: D, serial data input → MOSI (gray)
  • 6: C, serial clock → CLK (purple)
  • 7: /HOLD, hold → +3.3V (red)
  • 8: Vcc, power → +5.0V (orange)

On the TrippLite printed circuit board, almost all of the other devices are through-hole instead of surface-mount, allowing easier soldering to access this chip. How to identify pin #1? There is no dot on the diagram, or notch as on DIP components. Solution: the beveled edge identifies the side with pin #1 in the upper corner, in this case facing the closer side of the PCB.

Pin #1 /S is wired through D35, R35/D34, active low to ground; #2 Q under the chip unknown, #3 /W R96 to the LEDs common, #4 Vss JP17 to a large ground plane, #5 D to R93 then the red LED, #6 C under the chip to JP11 by the “U6” silkscreen on the solder side, #7 /HOLD to R97 pull-up resistor to Vcc, and #8 Vcc to a large power trace. First attempted to solder into through-hole components and probed with Bus Pirate:

but it is not electrically isolated enough with rest of board. Desoldered and lifted up half of the chip to carefully solder each pin:

test with continuity tester to confirm contact. Power up Bus Pirate.

The instruction set is the same as the 25LC640, but on the 4K variant the an extra bit selects upper/lower page in the READ and WRITE instructions. For this 1K chip, will use 0 in this bit, so no difference from 24LC640.

What’s on this tiny 1024-bit (128-byte) chip? Not much:

SPI>[3 0 r:128]
/CS ENABLED
WRITE: 0x03
WRITE: 0x00
READ: 0xFC 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
/CS DISABLED

0xFC = bit pattern 0b1111_1100, then all zeros. But reading it again showed all zeroes, and sometimes 0xFC followed by zeros. Reading at the slowest speed, 30 kHz, is also all zeros.

Configure Bus Pirate: m5 for SPI, maximum speed on this chip is 2 MHz, use 3 for speed 30 kHz. “Data is clocked in during the low to high transition of clock C, data is clocked out during the high to low transition of clock C.” per the datasheet. This corresponds to CPOL=0, CPHA=0 or CPOL=1, CPHA=1 (clock polarity / clock phase).

This EEPROM appears to be some sort of log of power faults or overloads.

Can we write to it?

First read the status register:

SPI>[5 r]
/CS ENABLED
WRITE: 0x05
READ: 0xC1
/CS DISABLED

0xC1 = 0b1100_0001

but the status register is documented as having read-only bits:

Unable to write with A [6][2 0 0xfc]. No errors but [3 0 r:128] does not read back my changes. SPI settings? Check the Bus Pirate documentation on 3EEPROM explorer board. Maybe I destroyed the chip, or could interconnects on the PCB be interfering?

Unsoldered the other side of the chip, very carefully soldered on jumper wires and hooked up to the Bus Pirate again:

No success. Likely dead, killed it, or doing something else incorrectly.

Tested on another chip from a different unit, unsoldered from the board at 600ºF then soldered on wires and plugged into Bus Pirate again. [3 0 r:128] read all 0x00. [5 r] also 0x00. Perhaps this memory really is empty?

TODO: retest with an even better connection, e.g. a SMD to through-hole adapter PCB, or better yet a CPT-063 Test Clip SOIC8 Pomona 5250 Pomona test clip.

Oh well. Let’s move on to another, known-good chip.

EEPROM: 93AA86C Microwire-compatible, 16Kbit

Datasheet for the 93A86C. The 93xx86 comes in several variants, the “C” device has an ORG pin to select 8-bit (0) or 16-bit (1) word size organization, and PE to control program enable. The pinout is completely different than the two other SPI EEPROMs we saw before:

  • 1: CS (chip select) → CS (white)
  • 2: CLK (clock) → CLK (purple)
  • 3: DI (data in) → MOSI (gray)
  • 4: DO (data out) → MISO (black)
  • 5: Vss (ground) → GND (brown)
  • 6: ORG (organization) → AUX (blue)
  • 7: PE (program enable) → jumper to +5.0V
  • 8: Vcc (power) → +5.0V (orange)

Note the CS pin is active-high, not active-low (/CS). Configure as: m5 (SPI), 3 (250 kHz), 1 (clock polarity idle low), 2 (output clock edge active to idle), 2 (input sample phase end), 1 (CS = CS, that is, active high), 2 (output type normal).

Turn on the power supply with “W”.

Select 8-bit organization by driving AUX low with “a” (alternatively, 16-bit with “A”, but 8-bit will be simpler for now). The instruction set is familiar:

OK, but does Bus Pirate v3 support the Microwire protocol? There are no chip demonstrations of using Microwire in the official Bus Pirate documentation.

Found this: OLS Microwire eeprom, decoded via SPI protocol, reading a 93C86 chip using PonyProg, Open Bench Logic Sniffer. SPI is similar to Microwire, but:

For 93c86 organized as 2048x8 bits, select 11 bits of addressing instead of 8, as I did for the first time.

11-bits?! Not clear how to do this with the Bus Pirate. Trying with two 8-bit bytes = a 16-bit address:

SPI>[3 0 0 r:10]
CS ENABLED
WRITE: 0x03
WRITE: 0x00
WRITE: 0x00
READ: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CS DISABLED
SPI>

TODO: find out how to use Microwire

Another failure, these investigations into EEPROM are not proving fruitful, this could be a really disappointing blog post. Moving onto something else.

Analog-to-Digital Converter (ADC)

Allows reading analog signals in a digital system. There are a bunch of ADC and DAC chips to choose from, but I happen to have an MCP3304.

MCP3304 via Bus Pirate

13-Bit Differential Input, Low Power A/D Converter with SPI Serial Interface, datasheet and pinout, wired up to the Bus Pirate:

  • 9: DGND, digital ground → GND (brown)
  • 10: /CS/SHDN, chip select → CS (white)
  • 11: Din, data in → MOSI (gray)
  • 12: Dout, data out → MISO (black)
  • 13: CLK, clock → CLK (purple)
  • 14: AGND, analog ground → jumper to GND
  • 15: Vref, voltage reference → jumper to +5.0V
  • 15: Vdd, power → +5.0V (orange)

Configure the Bus Pirate with m5, 3, 1, 2, 2, 2, 2.

The channel(s) to read are selected by configuration bits:

[0b100 r:10], getting something:

SPI>[0b100 r:10]
/CS ENABLED
WRITE: 0x04
READ: 0x0F 0xFC 0x9F 0xF8 0x00 0x00 0x00 0x00 0x00 0x00
/CS DISABLED
SPI>

On page 27 there is a note about interfacing with a microcontroller, using groups of 8 bits, sending leading zeros before the start bit:

Therefore to read a single channel (SGL=1), CH0 (D2/D2/D0=000):

SPI>[0b1100 0 0 rr]

Test by jumping pin #1 (CH0) to +5.0V, it consistently reads about 0xFFE0, near the maximum supply voltage. Then plug in +3.0V (red) and read:

SPI>[0b1100 0 0 rr]
/CS ENABLED
WRITE: 0x0C
WRITE: 0x00
WRITE: 0x00
READ: 0x4A
READ: 0xA0
/CS DISABLED

looks about right:

>>> 0xa04a * 1.0 / 0xffff * 5.0
3.130693522545205

Wiring up the AUX wire (blue), driving low with “a”, reading shows 0x60 0x00, then driving high with “A” either 0x52 0xa0 or 0x32 0xa0. Consistent results, looks like it is working. Now for using this ADC in a system:

Using Raspberry Pi’s SPI

According to elinux/RPi_SPI, “The BCM2835 on the Raspberry Pi has 3 SPI Controllers. The main SPI (with two slave selects) is available on the header of all Pis.”

The BCM2835 ARM Peripherals datasheet documents the following peripherals:

  • Timers
  • Interrupt controller
  • GPIO
  • USB
  • PCM / I2S
  • DMA controller
  • I2C master
  • I2C / SPI slave
  • SPI0, SPI1, SPI2
  • PWM
  • UART0, UART1

SPI1 and SPI2 are “auxiliary peripherals”, sharing an interrupt with UART1. Could be interesting to use these other SPI buses however for simplicity I’ll be using SPI0 as exposed on the GPIO header.

MCP3304 via Raspberry Pi’s SPI

First the SPI interface has to be enabled:

sudo raspi-config
Advanced Options > SPI > Yes > Finish

Raspberry Pi A+/B+/B2 GPIO connector pinout, wire to the MCP3304:

  • 19: MOSI (master-out, slave-in) / GPIO 10 to #11 (Din)
  • 21: MISO (master-in, slave-out) / GPIO 9 to #12 (CLK)
  • 23: SCLK (serial clock) / GPIO 11 to #13 (Dout)
  • 22: CE0 (chip enable 0) / GPIO 8
  • 26: CE1 (chip enable 1) / GPIO 7

With the two chip-enable pins, two SPI slave devices can be accessed at /dev/spidev0.0 and /dev/spidev0.1 (TODO: GPIO chip-enables?)

But there’s a problem. The Raspberry Pi’s SPI bus is +3.3V, not +5.0V. The MCP3304 is specified as having a 4.5V to 5.5V input voltage. Ideally we could power with +5V and use level shifters to convert the SPI signals between V3.3 <-> 5V. But will the chip work with a 3.3V supply anyways?

Sort of. Testing with the Bus Pirate, it can read AUX high as 0xE0FF, and AUX low as 0x0000 (instead of 0x0060). May want to add a proper supply voltage and logic level shifters later, but for now:

  • GND to #14 (DGND), #9 (AGND)
  • +3.3V to #16 (Vdd), #15 (Vref)

What about chip select? Wired pin #10 (/CS/SHDN) to CE0, but note that the MCP3308 expects active-low (hence the / in front of CS). Raspberry PI SPI documentation makes no mention of whether CE0/Ce1 are active low or active high. But elinux/RPi_SPI documents the mode bits: SPI_CS_HIGH controls whether CS is active high or low. Or SPI_NO_CS can be used if there is only one device on the SPI (no chip select). This suggests the Raspberry Pi’s CE0 and CE1 signals are active-low, and this is in fact the case.

Found on Havnemark Blog INTERFACING RASPBERRY PI AND MICROCHIP MCP3304 SPI ADC a handy Python script, SPI_MCP3308.py. For some reason, their script reads from CH1 (pin #2) not CH0 (pin #1), and then CH2. Slightly modified to read from all eight channels:

With the pins floating they read 0.1–0.8V. Ground each channel and they will read zero volts, or near it (here rounded to two decimal points):

0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00

tie CH0 to +3.3V and it reads near this voltage, as expected:

3.30 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3.30 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3.28 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3.28 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3.29 0.00 0.00 0.00 0.00 0.00 0.00 0.00

with the 12-bit ADC, 2¹² possible values, 1/2¹² = ~0.000244140625, about four decimal points of resolution or less, but the accuracy isn’t perfect. Nothing is, but it’s quite close. Working quite well despite undervolting!

Application: Light sensor → Nightlight

What to use this 8-channel analogue interface for? An oscilloscope would be neat, but tests from @Jay9313 show he could achieve at most 50,000 samples per second, not very fast for a scope. Granted, better than the Bus Pirate: Python Oscilloscope at 5,720 samples per second, could still be useful, plus having eight channels (at reduced bandwidth). A multimeter could also be feasible, directly sensing up to 3.3 volts, or current through a shunt resistor, or voltage drop across a resistor for an ohmmeter.

Analog sensors are another possibility, of various types. Some ideas, browsing through Sparkfun’s catalog:

Note that some sensors are digital (or include integrated analog-to-digital converters), requiring a different interface. Vernier also has a large collection of calibrated sensors, both analog and digital.

But I didn’t have any of those nifty sensors, all I had on hand was an OPT101 — Monolithic Photodiode and Single-Supply Transimpedance Amplifier (datasheet) in a DIP8 package:

Wired up as follows:

  • 1: Vs, supports 2.7–36V → +3.3V (pin #1 of Raspberry Pi GPIO)
  • 2: -In, do not connect (optionally can supply more negative feedback)
  • 3: -V → jumper to ground
  • 4: 1MΩ feedback → jumper to output
  • 5: output → CH8 (pin #7) of MCP3304 ADC
  • 6: no connection
  • 7: no connection
  • 8: common → ground (pin #39 of Raspberry Pi GPIO)

Shining a flashlight directly into the OPT101, read about 2.5V (a high-powered laser should perhaps be able to get a higher reading, near 3.3V). A diffused ceiling light, about 0.78V, and darkness is 0.00V, occasionally 0.02V. The final circuit:

The exposed die and lead frame you can see in the OPT101 on top looks pretty cool, haven’t seen that in a dual-inline package since UV-erasable EPROMs. It also works quite well with the integrated on-chip amplifier.

For now the other 7 channels sit unused, haven’t decided what I’ll do with them yet but having analog input to the Raspberry Pi opens up a whole new world of possibility. Despite the huge 40-pin GPIO header, all of the built-in inputs on that header are digital; ADC converts analog to digital for the Pi.

To make use of this light sensor, a simple application is a nightlight: a light to turn on when it is dark, and off when there is ambient light. To do this I wired up a yellow LED to pin #32 (GPIO port 12) active-low through a 330 Ω resistor, and wrote this Python script:

To install, save to /home/pi/spi/nightlight.py and the startup script to /home/pi/spi/nightlight, then run:

chmod a+x ~/spi/nightlight.py
sudo ln -s ~/spi/nightlight /etc/init.d/nightlight
sudo ln -s /etc/init.d/nightlight /etc/rc5.d/S01nightlight
sudo /etc/init.d/nightlight start

You may need to adjust the voltage thresholds depending on your environment. Test by turning off all lights at night, the nightlight should light up, then turn a light on, the nightlight should extinguish.

Conclusions

SPI is another very useful interface (in addition to I²C), worth familiarizing yourself with if you are building systems from ICs or wish to extend the capabilities of your Raspberry Pi or other computer system, such as adding an 8-channel analog-to-digital converter for reading light levels using a photodiode, or if you are at all involved in electronics. SPI is ubiquitous in modern electronics, supported by countless chips, wiring up the 25LC640 and MCP3304 has only scratched the surface.

--

--