I2C interfacing on the Bus Pirate and Raspberry Pi to serial EEPROMs for a HAT

R. X. Seger
17 min readAug 27, 2016

--

I²C (aka I2C, IIC) is a bus commonly used for interfacing integrated circuits to microcontrollers. In this article I will connect several EEPROM chips using I2C, first to a Bus Pirate for prototyping and testing, then to a Raspberry Pi 3 single board computer, using python-smbus to read and write the EEPROMs over the i2c-1 bus. Then I’ll move one of the EEPROMs to Raspberry Pi’s other I2C bus i2c-0, using ID_SC and ID_SD pins, for possible usage in a HAT (Hardware on Top) add-on board.

Prototyping with Bus Pirate

Before bringing the Raspberry Pi into the equation, I first started experimenting with I2C using a Bus Pirate. Not strictly necessary, but the Bus Pirate is a very useful tool for prototyping low-level hardware communication interfacing.

You can use it on any system with USB — it connects as a USB-to-serial device to your computer (thanks to the onboard FTDI chip, TODO: possible to connect the Bus Pirate v3 to the host computer using serial instead of USB, like on the Bus Pirate v1 and earlier which were serial-only? Bus Pirate v2go had a serial terminal (ST), an “unpopulated header is a tap into the UART connection between the PIC microcontroller and the FTDI 232BL chip that provides the USB connection”, but what about v3 or later?), on a Mac connect using `screen /dev/tty.usbserial* 115200`, or on the Raspberry Pi: `screen /dev/ttyAMA0 115200`.

Seeed vs Sparkfun Bus Pirates

Dangerous Prototypes originally designed the Bus Pirate and released it as open source, it can be manufactured by various vendors, or built yourself. For purchasing a Bus Pirate v3.6, they link to Seeed Studio:

There is also a Bus Pirate v4 in development, started in ~2011, comparison wiki page last modified 2012, still marked as “experimental”, so I went with the the v3 model instead for now.

Seeed also sells several different probe cables for the Bus Pirate:

But there’s other options for purchasing Bus Pirates, Sparkfun sells:

Which is better? Here’s how Sparkfun vs Speeed differs visually:

Top: Sparkfun Bus Pirate 3.6a + Sparkfun Bus Pirate Cable, bottom: Seeed Bus Pirate 3.6 + Seeed probe cable

The PCBs are mostly the same, some silk screen textual differences but nothing functional. However their breakout cable is noticeably different. Sparkfun’s cable seems to have been assembled backwards: it folds over the board, instead of away from it (but you can only plug it into the connector one way), unlike Seeed’s. And the connectors are vastly different:

Left: Sparkfun Bus Pirate Cable, right: Seeed Bus Pirate v3 cable with labels

Sparkfun’s cables include thinner wire, feels cheap, with smaller fragile female connectors. Seeed’s female connectors are robust and — crucially — labeled! (if you get the “probe cable with labels” item) This is quite handy, I much prefer the textual labels over decoding the wire color codes (reminiscent of resistor color codes, before I learned of multimeters).

There’s another oddity with Sparkfun’s cables. Dangerous Prototypes wiki documents Common Bus Pirate cable pinouts, Seeed Studio as:

Seeed Studio probe cable pin reference, from Common Bus Pirate cable pinouts

and then there’s a comment “The SparkFun cable is usually backwards from the Seeed arrangement.”, with the reversed pinout:

NOT the Sparkfun cable pinout I had! from from Common Bus Pirate cable pinouts

except my cable from Sparkfun was not reversed (i.e., MISO was black not brown). Also confirmed by shorting orange (+5V) to yellow (ADC) and reading the voltage with ‘v’, ADC reported 4.98V, confirming the pinout above, not the “reversed” pinout. I initially wired up the cable to the chip incorrectly due to this misdirection, fortunately the electronics survived, as far as I can tell no damage was sustained.

For these reasons, long story short, I prefer the Seeed Bus Pirate over the Sparkfun, or at least the Seeed cable. Labeled leads, higher-quality wire and connectors, no reverse-facing cable direction, it’s easier to work with.

However, they are both functionally equivalent in a pinch.

24LC512 vs 24LC515 Microchip EEPROMs

There are plenty of interesting I2C devices on the market, just search for “I2C” on Adafruit or Sparkfun. For this article I will be using a couple serial EEPROMs from Microchip since I had them readily available.

Microchip’s serial EEPROM product line is divided by the interfacing protocol used:

  • Microchip 24xxyyy: I2C (I²C), very popular, originated in 1982 by Philips
  • Microchip 11xxyyy: UNI/O, new in 2008 by Microchip
  • Microchip 93xxyy: Microwire, “essentially a precursor to SPI”
  • Microchip 25xxyyy: SPI, also very popular, originated 1985 by Motorola

and furthermore by the capacity in bits (note not bytes, but bits). Both the 24LC512 and 24LC515 are 512Kbit I2C serial EEPROMs, with the same pinout:

24LC515 (and 24LC512) pinout from datasheet

To use with the Bus Pirate, connect Vcc to 5V or 3.3V (because the chips can handle 2.2–5.5V, either power supply is acceptable), SCL serial clock to CLK clock, SDA serial data to MOSI master output slave input, and Vss to GND. Also connect the Bus Pirate VPU (voltage pull-up) to Vcc.

Then connect from your host computer to the Bus Pirate, get to the HiZ> terminal prompt. Change to I2C mode by typing “m” followed by 4, select any speed, enable the power supply with “W”, enable pull-up resistors with “P”. Now you should be able to scan the bus using the “(1)” macro:

I2C>(1)
Searching I2C address space. Found devices at:
0xA0(0x50 W) 0xA1(0x50 R)

This is with the 24LC512 chip. To use the ‘515, we must learn about the purpose of the A0/A1/A2 pins.

On the ‘512, these pins set the lower bits of the I2C device address, fairly straightforward. The upper bits for these EEPROM chips are fixed at 1010, but you can select the lower three bits by tying A2, A1, and A0 to Vss (0) or Vcc (1). If left floating, they will default to 0, but grounding is best practice.

With the ‘515, the A1 and A0 lines set the lower two bits of the address, but A2 must always be set to high! Otherwise, the chip will not function. As the datasheet explains, A2 is “Non-Configurable Chip Select. This pin must be hard wired to logical 1 state (VCC). Device will not operate with this pin left floating or held to logical 0 (VSS).

If this is confusing, these pictures should help enlighten:

24LC512 device addressing (datasheet page 7)
24LC515 device addressing (datasheet page 9)

The ‘515 introduces another bit in the I2C address, “B0”, to select the “block”, which “acts as the A15 address bit for accessing the entire array”. What would be A15 is actually a don’t-care (X) bit in the ‘515.

Anyways, for I2C in general, another bit is appended to the address to specify read/write. Recall the I2C address space scan results:

I2C>(1)
Searching I2C address space. Found devices at:
0xA0(0x50 W) 0xA1(0x50 R)

The device is at 0x50 = 0b1010_000, which means the write address is 0x50<<1 = 0xa0 = 0b1010_0000, and read address (0x50<<1)|1 = 0xa1 = 0xb1010_0001. Assuming A2-A0 are 0, that is. If A0 was 1, then the device address would be 0b1010_001 = 0x51, with write at 0xa2, read 0xa3, or if A0 and A1 and A2 were all 1, then 0b1010_111 = 0x57, r 0xae, w 0xaf, and so on.

On the ‘515, there is another pair of addresses for the other bank. With A0=A1=0 (and remember A2 must be 1 for the chip to enable itself in the first place), I2C scanning should find:

0xA0(0x50 W) 0xA1(0x50 R) 0xA8(0x54 W) 0xA9(0x54 R)

If the ‘512 was wired up identically (A2=1), you’ll instead see only:

0xA8(0x54 W) 0xA9(0x54 R)

since the full 512Kbit memory address space is available at the same I2C device address.

Why Microchip introduced this complexity with the 24LC515, who knows, but the design of the 24LC512 is much more logical and straightforward. The 24LC515 is “no longer recommended for new designs”, so although I was able to use both chips successfully, I’ll use the 24LC512 in the examples below, at the default address of 0x50.

Try to write a few bytes (1, 2, 3), at memory address 0x0000:

[0xa0 0 0 1 2 3]

then reset the memory address pointer to 0x0000 and read back:

[0xa0 0 0]
[0xa1 r:3]

Notice how two bytes (0 0) are specified for the 16-bit address, as these are 16-bit serial memory devices. Smaller memories like the 8Kbit 24LC08B may instead use 8-bit addressing, requiring only one byte for the address above. Note that only 16-bit serial EEPROMs are supported by the Raspberry Pi HAT specification and they cost about the same, go with them if you can.

I2C on the Raspberry Pi

Finally, let’s wire an I2C device to the Pi. I used an EEPROM I wrote in the previous section with the Bus Pirate, but any I2C-compatible chip should do.

I2C is not enabled by default, configure as follows:

sudo raspi-config
Advanced Options > I2C > Select
Would you like the ARM I2C interface to be enabled? Yes

This creates the device node — even without rebooting, some tutorials recommend but I did not need to. Verify it is created with:

ls -l /dev/i2c-1

Instructions from Adafruit: Configuring I2C (skipped most steps there):

sudo apt-get install i2c-tools

This installs a detection tool, run on port 1 (no sudo needed here):

i2cdetect -y 1

Similar to Bus Pirate’s “(1)” macro (I2C 7-bit address search).

Wire up the EEPROM, or other I2C device. Connect Vss to GND, SCL to SCL (serial clock), SDA to SDA (serial data), and Vcc to 3V3 (could you use 5V instead? possibly, but out of spec) of the Raspberry Pi. Use the Pi Wedge or Pi Cobbler breakout for ease of wiring. Finds the same 0x50 address:

Although i2cdetect only shows the 7-bit address, unlike Bus Pirate’s scan which shows both read/write addresses, they can be derived from one another. 0x50 of course corresponds to 0xa0 write, 0xa1 read.

The Case of the Missing Ground

Running i2cdetect in a loop repeatedly showed it sometimes fails to detect the 0x50 address. It comes and goes. Testing with command-line tools:

i2cget -y 1 0x50

Sporadically returned “Error: Read failed”, but sometimes 0xff (correct).

Another user reported a very similar problem on StackExchange: I2C problem reading data — only sometimes, granted with a different device. No solution there. What could be wrong? I checked lots of things:

python
from RPi import GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup(3, GPIO.OUT)

This causes i2cdetect -y 1 to show all addresses as present, since it changes physical pin #3 (SDA.1) from ALT0 mode to OUT (as shown with gpio readall), not what we want. And you can’t set pull-up/down for outputs:

>>> GPIO.setup(3, GPIO.OUT, GPIO.PUD_DOWN)
ValueError: pull_up_down parameter is not valid for outputs

how about inputs?

>>> GPIO.setup(3, GPIO.IN, GPIO.PUD_DOWN)
__main__:1: RuntimeWarning: A physical pull up resistor is fitted on this channel!

OK, how to change it back? RPi.GPIO can only set modes to IN or OUT, not any ALT. Alt functions are documented on elinux RPi Low-level peripherals. Physical pin 3 (BCM pin 2) alt 0 function is I2C1_SDA, physical pin 5 (BCM pin 3) alt 0 function is I2C_SCL, to restore:

gpio -g mode 2ALT0
gpio -g mode 3ALT0

although rebooting will restore these pin’s functions as well.

  • What about adding physical pull-up resistors on the I2C data and clock? In i2c issue — attiny85, they used 4.7k pull-ups on SDA and SCL pins to 5v, but @dgordon42 says the Pi already comes with 1.8k pull-up resistors on the I2C bus 1 pins to the 3.3V power line, and to try without pullup. I’m not using any of my own pull-up resistors, and I didn’t try with any.
  • /usr/bin/gpio vs /usr/sbin/i2cdetect? `gpio i2cdetect` and `i2cdetect -y 1` behave identically, but I made a habit of using gpio i2cd for brevity.

To further analyze the intermittent connectivity problem, ran in a loop:

pi@raspberrypi:~ $ while true; do echo -n `date`’ ‘; gpio i2cd|grep 50; sleep 1; done

The results aren’t pretty:

First it detects the device at 6:50:00, then loses it for 1 second, then detects for only 4 more seconds. 62 seconds later detects again for 3 seconds, 97 seconds later for 1 second, 83 seconds later for 1 second, another 83 seconds later for also 1 second, 98 seconds then 4 seconds, a longer 122 seconds later and then detects it for a whopping 9 seconds, but then cuts out again for 45 seconds and only returns for 1 second (note that 1/s is the rate as which I scanned using gpio i2cd). Not nearly long enough to be useful.

What is going on with this connection? To find out, I was about to setup the Bus Pirate’s I2C sniffer (the “(2)” macro in I2C mode) but I got as far as wiring up the ground connection from the Pirate to isolate the error.

Solved: the chip wasn’t grounded! Missing jumper on breadboard rail:

The breadboard has four separate pairs of power rails, two on each side. I used the left for GND/+3.3V and the right for +5.5V/GND, but the upper and lower sections of the grounds were not connected. Once this was fixed, the EEPROM’s ground pin was no longer floating, and the chip could be detected consistently using gpio i2cd.

Sometimes it’s the simplest things that get you.

Sending I2C commands to the EEPROM

SMBus (system management bus) is a stricter subset of I2C, so we can use the Python smbus module to interface to I2C:

sudo apt-get install python-smbus

python
import smbus
bus = smbus.SMBus(1)
bus.write_byte(0x50,0)
bus.write_byte(0x50,0)
bus.read_byte(0x50)
bus.read_byte(0x50)
bus.read_byte(0x50)

This writes 0x0000 for the memory address, then reads back three bytes starting at that address. It should match the values written and read with the Bus Pirate (1, 2, 3).

Note that the python-smbus module accepts the same 7-bit address for reading and writing, whereas the Bus Pirate accepts 8-bit bytes encoding both the 7-bit address and the 1-bit read/write flag. The above Python code is analogous to these Bus Pirate commands:

[0xa0 0 0]
[0xa1 r:3]

Multiple EEPROMs on I2C-1

A useful characteristic of I2C is support for multiple devices on the same bus, distinguished by unique 7-bit addresses. The 24xx EEPROMs I2C device addresses always begin with 0b1010, but the lower bits can be chosen by wiring the A2/A1/A0 lines to Vcc or Vss for 1 or 0, respectively.

Wired up four devices, three 24FC512’s and one 24LC515:

As shown above (left pins), the device addresses are configured as follows:

  • 24FC512 #1, A2/A1/A0=000, I2C address 0x50
  • 24FC512 #2, A2/A1/A0=001, I2C address 0x51
  • 24FC512 #3, A2/A1/A0=010, I2C address 0x52
  • 24LC515, A2/A1/A0=111, I2C addresses 0x53 and 0x57

The three ‘512 devices are straightforward, but the ‘515 as you may recall uses A2 as the “non-configurable chip select” pin requiring it to be 1, and in its place is the B0 “block select bit” essentially splitting the 512Kbit memory into two 256Kbits, here at addresses 0x53 and 0x57.

Wired up correctly, gpio i2cdetect detects all of these devices:

Dumping the full EEPROM

First tried i2ctools/eepromer, “Use eepromer for large eeproms with two-byte addresses: 24C32, 24C64, 24C128, 24C256, and 24C512.”:

make
sudo ./eepromer -r -f /dev/i2c-1 0x50

but it failed:

Block read failed

Uses block read commands, not supported? Fortunately there is another way, we can use python-smbus and continue reading bytes in a loop until the end of the EEPROM. read-eeprom16.py:

Example usage:

python read-eeprom16.py 0x50 65536 | hexdump -C

It’s not fast (~24 seconds to read 64KBytes byte by byte), but it works.

Writing to EEPROM

Recall how we wrote to the EEPROM using the Bus Pirate:

[0xa0 0 0 1 2 3]

Writing the 16-bit memory address, followed by the data. The “[“ and “]” syntax starts and stops the I2C transaction. How to convert this operation to python-smbus?

Consult the i2c python documentation. There’s some unusual commands available in python-smbus, such as SMBus quick commands which may not be compatible with all I2C devices. From the TI article SMBus vs I2C:

“SMBus is built on I2C and is therefore generally compatible with I2C devices, though not in all respects. […] I2C allows several modes, Standard, Fast and High-Speed. […] SMbus allows clock frequencies only as high as 100kHz”

Check /var/log/kern.log, the Pi is using a 100 kHz clock rate:

Aug 22 04:13:53 raspberrypi kernel: [3623020.138960] bcm2708_i2c 3f804000.i2c: BSC1 Controller at 0x3f804000 (irq 83) (baudrate 100000)

From that same TI article, The SMBus protocols “Send Byte” and “Receive Byte” are directly compatible with the I2C data transfer commands “Single-Byte Write” and “Single-Byte Read”.:

To test using the command-line tool,writing to device 0x57 (the second 256Kbit half of the ‘515 in my wiring), memory address 0x0000, byte values 5, 6, 7, 8, I2C block data write (i) mode:

i2cset -y 1 0x57 0 0 5 6 7 8 i

Read back with Python script (i2cset has an -r option to read back after writing, but it fails). To write with python-smbus, write_block_data() or write_i2c_block_data()? The former is for smbus, so let’s use the I2C variant.

Takes three arguments: int addr, char cmd, long[] vals. addr is the device address, 0x57. cmd is… what? Turns out to be the memory address, per the 24xx EEPROM datasheets. However, cmd being an 8-bit quantity is a problem: this is a 16-bit EEPROM, with 16-bit memory addresses. The second half of the address is prepended to vals, as follows:

import smbus
smbus = smbus.SMBus(1)
bus.write_i2c_block_data(0x57, 0, [0, 5, 6, 7, 8])

To write an entire file, you might try to insert all of the bytes in the vals array, but this is rightfully rejected by python-smbus:

Traceback (most recent call last):
File “write-eeprom.py”, line 16, in <module>
print bus.write_i2c_block_data(device_address, 0, bytes)
OverflowError: Third argument must be a list of at least one, but not more than 32 integers

The write size is actually limited by the chip, 24LC515 documents “64-byte Page Write mode available” and 24FC512 “128-Byte Page Write Buffer”, that’s larger than python-smbus is letting us use, but oh well. Write in chunks.

Writing each 16-byte chunk immediately succeeds on the first chunk, but fails directly after on the second chunk:

writing to 57 at 0000 bytes 16
None
writing to 57 at 0010 bytes 16
Traceback (most recent call last):
File “write-eeprom.py”, line 24, in <module>
print write(device_address, i * 16, chunk)
File “write-eeprom.py”, line 17, in write
return bus.write_i2c_block_data(device_address, cmd, bytes)
IOError: [Errno 5] Input/output error

The EEPROM needs some time to finish writing before accepting the next write. Added a delay with time.sleep(). write-eeprom16.py:

Note that writing beyond the end of the EEPROM will wrap around and overwrite the beginning; this script does not take into account the size of the memory and will allow this wraparound to occur.

First test, writing 256 bytes to the beginning of the 0x51 EEPROM:

perl -e’print chr $_ for (0..255)’ | python write-eeprom16.py 0x51

and reading it back:

python read-eeprom16.py 0x51 256 | hexdump -C

Writing a full 32KBytes of zeros to 0x57 EEPROM, then reading to confirm all zeros were written:

dd if=/dev/zero of=zero32 bs=1024 count=32
python write-eeprom16.py 0x57 < zero32
python read-eeprom16.py 0x57 32768 | xxd

Looking good. We now have working scripts to read/write these I2C-based EEPROM devices connected to the Raspberry Pi, to recap:

I2C-0 and the Raspberry Pi HAT

Extra Storage, or Not

Alright we can write to EEPROMs, so what? How is this useful? Certainly not for expanding the storage of the Raspberry Pi. microSD flash is plenty adequate (at least the 16 GB class 10 card I am using), and adding (512Kbit*4)/8 = 256 KBytes of extra storage hardly makes a dent in the gigabytes of flash. Not to mention EEPROM read/write is significantly slower than flash memory (class 10 = minimum 10 MB/s). Why use EEPROM ever?

One answer: small embedded systems, where only a small amount of (perhaps configuration) data storage is required, readily available at various parts of the circuit, not necessarily from the primary data storage mechanism. Raspberry Pi HATs are one such example.

What is a HAT?

HAT = hardware on top, an expansion specification for the Pi. Plugs into the 40-pin header, and can make use of GPIO, I2C, SPI, and so on. But for plug-and-play interoperability, configuration data is needed: this is where the EEPROM comes in. HATs are required to have EEPROMs for this purpose.

Sparkfun Raspberry Pi SPI and I2C Tutorial, i2c-0 for HAT is a good introduction. Additionally there are plenty of details in the official raspberrypi/hats repository, and a tool to flash. Interestingly, there is an eepromutils/eepflash.sh script, recently changed to bit-bang I2C instead of using the /dev/i2c-0 kernel driver. But I’ll use my own scripts developed above, connected to /dev/i2c-1, instead for now.

From the HAT requirements document:

  1. The ID_SC and ID_SD pins must only be used for attaching a compatible ID EEPROM. Do not use ID_SC and ID_SD pins for anything except connecting an ID EEPROM, if unused these pins must be left unconnected

Note: the pinout I showed in Raspberry Pi 3 GPIO: pushbuttons, LEDs for RC and BARR, from ms-iot.github.io (archive) (originally linked from developer.microsoft.com, but that link is now broken) shows pins #27 and #28 as “Reserved”. What for? ID_SD and ID_SC! A better updated pinout from Element 14:

Programming the EEPROM

Enough talk, let’s get started writing one of these EEPROMs:

git clone https://github.com/raspberrypi/hats
cd hats/eepromutils
make
./eepmake eeprom_settings.txt settings.bin
python ~/i2c/write-eeprom16.py 0x50 < settings.bin

Wiring up the HAT EEPROM to I2C bus #0

Now wire it up to the ID_SC and ID_SD pins, instead of SCL and SDA. Reboot and see if it is detected. According to HAT identification:

To avoid the need for modules and applications to access the EEPROM, the VC bootloader reads the vendor, product and custom data and populates the /hat node of the device tree. This can be read by kernel modules using the standard DT API (of_*), or via /proc/device-tree/hat from user-space.

But I’m not seeing the HAT (nothing about hat in dmesg either):

pi@raspberrypi:~ $ ls /proc/device-tree/hat
ls: cannot access /proc/device-tree/hat: No such file or directory

Kits such as Adafruit Perma-Proto HAT for Pi Mini Kit — With EEPROM use an EEPROM. Why is it not showing up for me?

Hint from Uso de EEPROM hats: read from /sys/class/i2c-adapter/i2c-0. No such file or directory, I only see i2c-1.

Documented in /boot/overlays/README:

i2c_arm Set to “on” to enable the ARM’s i2c interface
(default “off”)
i2c_vc Set to “on” to enable the i2c interface
usually reserved for the VideoCore processor
(default “off”)

what does it mean, “usually reserved”? There is a note further in the README, “Leaving all interfaces enabled can lead to unwanted behaviour (i2c_vc interfering with Pi Camera…”, I’ll try it anyways, /boot/config.txt:

dtparam=i2c_arm=on
dtparam=i2c_vc=on

then reboot. Success! i2c-0 appears and so does /proc/device-tree/hat:

pi@raspberrypi:~$ grep -a ‘’ /proc/device-tree/hat/*
/proc/device-tree/hat/name:hat
/proc/device-tree/hat/product:Special Sensor Board
/proc/device-tree/hat/product_id:0x0000
/proc/device-tree/hat/product_ver:0x0000
/proc/device-tree/hat/uuid:<<randomly generated>>
/proc/device-tree/hat/vendor:ACME Technology Company

it does seem odd to have to edit the boot configuration to enable a HAT, but it works. The HAT is successfully detected as configured by the EEPROM.

The next step could now be to customize the HAT EEPROM configuration data further, enabling or disabling GPIO pins with various settings, adding device tree overlays, and so on, but not for this blog post.

--

--