Bus Pirate

The Bus Pirate is a small USB device that can speak several bus protocols, like I2C and SPI.

The specific version I'm using below is TOL-12942 (v3.6a), with the following info blurb:

1
2
3
4
Bus Pirate v3b
Firmware v5.10 (r559)  Bootloader v4.4
DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
http://dangerousprototypes.com

Intial setup

To have the device created with the appropriate permissions and also get an automatic /dev/buspirate symlink, create /etc/udev/rules.d/98-buspirate.rules with the contents

1
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="buspirate"

To connect to it once it's plugged in, just run

1
screen -c /dev/null /dev/buspirate 115200

Typing ? followed by the enter key will output the help message, followed by a prompt.

There's support for some readline-like features. The left and right arrow keys move within the currently entered command. The up and down arrow keys move through command history.

Surprisingly, backspace acts like the delete key instead of backspace. That is, it deletes the character you're currently on, not the one to the left. To really backspace, press the left arrow key and then backspace.

When the device is unplugged, the screen session will automatically close. You can also close it by killing the window in the usual way (^A k), but this will not reset the device.

Some useful reading:

Pull-up resistors

The way that the on-board pull-up resistors are handled on the Bus Pirate is a tad unintuitive. Some buses need pull-ups on their various lines, and it's very convenient for the Bus Pirate to be able to provide them, but it's not enough to hit P to turn them on. There's also the matter of the VPU pin, which doesn't provide the pull-up voltage! Because the Bus Pirate doesn't know what voltage your circuit is operating on (and maybe you need the lines to be pulled to something weird, like 1 V), you're expected to feed the pull-up voltage into the VPU pin, and that will then be distributed to the appropriate lines.

PWM

It's not possible to generate a PWM signal from the default HiZ mode, but it's easy to change to the 1-WIRE mode using m, since it has no options.

Connect brown (GND) and blue (AUX) to whatever needs the signal. Then hit g to turn PWM on:

1
2
3
4
5
6
7
1-WIRE>g
1KHz-4,000KHz PWM
Frequency in KHz 
(50)>
Duty cycle in % 
(50)>
PWM active

and again to turn it off:

1
2
1-WIRE>g
PWM disabled

It's only possible to enter integer values for both the frequency and the duty cycle. The default is 50 kHz at 50%.

I2C

I2C is a 2-wire protocol, with a clock line (SCL) and a data line (SDA).

The only option is the clock speed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
HiZ>m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>4
Set speed:
 1. ~5KHz
 2. ~50KHz
 3. ~100KHz
 4. ~400KHz

(1)>
Ready
I2C>v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     SCL     SDA     -       -
P       P       P       I       I       I       I       I       I       I       
GND     0.00V   0.00V   0.00V   0.00V   L       L       L       L       L       

To connect a device, hook up brown (GND) to GND, orange/red (5.0V/3.3V) to VCC, purple (SCL) to SCL, and grey (SDA) to SDA. Then power it up:

1
2
I2C>W
Power supplies ON

Unless your device has the required pull-up resistors on both lines, you'll need to connect green (VPU) to the power source and turn on the internal pull-ups:

1
2
I2C>P
Pull-up resistors ON

The first byte that the master sends is the 7-bit slave address, followed by a single bit indicating write (0) or read (1) mode. The former is the address multiplied by 2, and the latter is the same plus 1. Instead of doing the multiplication ourselves, we can use the Bus Pirate to figure out what the special byte should be for a given address and mode. Suppose we want to talk to the device with address 0x12:

1
2
I2C>=0x12
0x12 = 18 = 0b00010010

Since

1
2
3
4
I2C>=0b000100100
0x24 = 36 = 0b00100100
I2C>=0b000100101
0x25 = 37 = 0b00100101

we should send 0x24 to write and 0x25 to read. It's also possible to use the address search macro:

1
2
3
I2C>(1) 
Searching I2C address space. Found devices at:
0x24(0x12 W) 0x25(0x12 R) 

LCD character display

Here we play with a generic LCD character display module with an I2C backpack.

It's a 5V device and has its own pull-up resistors.

The documented address is 0x27, so to write to it we need to send 0x4e. We can check that this works:

1
2
3
4
I2C>[0x4e]     
I2C START BIT
WRITE: 0x4E ACK 
I2C STOP BIT

Note that the response is an ACK and not a NACK. We can also send 0x4f to read:

1
2
3
4
5
6
I2C>[0x4f r]
I2C START BIT
WRITE: 0x4F ACK 
READ: 0xF7 
NACK
I2C STOP BIT

In this case, we got back 0xf7.

Now the fun begins. Everybody seems to insist on using a library to talk to the display, but I want to speak to it in raw I2C. I'm pretty sure the display itself is HD44780-compatible (datasheet). As far as I can tell, the "I2C backpack" is little more than a PCF8574T 8-bit port expander that reads in 8 bits over I2C and outputs them on 8 pins. The question is which pins of the display those are connected to, and in which order.

According to a tutorial, the bits are mapped according to

1
2
3
4
5
6
7
8
7654
||||/---- backlight
|||||/--- E
||||||/-- RW
|||||||/- RS
||||||||
vvvvvvvv
76543210

Clearly, we'll be using the 4-bit mode. We want the backlight to always be on, and we're not interested in reading, so those bits can be fixed. To send 4 bits, we need to set the enable bit high, and then low. Thus, to send a nibble to the command register, we must send

1
2
0bXXXX1100 0xXc
0bXXXX1000 0xX8

and to send a nibble to the data register, we must send

1
2
0bXXXX1101 0xXd
0bXXXX1001 0xX9

The display initializes itself in 8-bit mode, and it looks like the 4 low bits are wired high in this backpack. Until we switch to 4-bit mode, this limits us to sending command and data bytes of the form 0xXf. For example, we can send 0x0f to turn on the display with a blinking cursor, and then 0x3f to write a question mark:

1
[0x4e 0x0c 0x08 0x3d 0x39]

To change to 4-bit mode, we need to send 0x2X, which in our case will have to be 0x2f:

1
[0x4e 0x2c 0x28]

This has the side effect of requesting the number of lines to be 2 and the symbol size to be 10 dots high, which reduces the duty cycle and makes the text look very dim. (I think this combination is technically invalid, so it doesn't actually make the symbols bigger, as if we had sent 0x2b instead of 0x2f.) The display gets so dim, in fact, that I wasted several hours trying to figure out why it refused to work after switching to 4-bit mode. The next step is then to send 0x20, which is what we really want:

1
[0x4e 0x2c 0x28 0x0c 0x08]

Now we can do as we wish.

For example, we can clear the display with

1
[0x4e 0x0c 0x08 0x1c 0x18]

and then write some digits with

1
[0x4e 0x3d 0x39 0x0d 0x09 0x3d 0x39 0x1d 0x19 0x3d 0x39 0x2d 0x29 0x3d 0x39 0x4d 0x49]

Thus, we have arrived at the initialization sequence

1
[0x4e 0x2c 0x28 0x2c 0x28 0x0c 0x08 0x0c 0x08 0xfc 0xf8]

This isn't very robust, probably because we're being sloppy with the timing.

Nevertheless, we can now do this:

1
[0x4e 0x5d 0x59 0x9d 0x99 0x6d 0x69 0x1d 0x19 0x7d 0x79 0x2d 0x29 0x7d 0x79 0x2d 0x29 0x2d 0x29 0x1d 0x19]

LCD character display showing "Yarr!"

OLED screen

Here we try to get a tiny Kuman OLED display to light up.

It claims to be OK with 5 V, but one of the Amazon comments says it needs 3.3 V. It appears to provide its own pull-ups.

On the back, it looks like the address 0x78 is selected. The address is actually 0x3C, and the first byte will be 0x78 when writing.

1
2
3
4
I2C>[0x78]
I2C START BIT
WRITE: 0x78 ACK 
I2C STOP BIT

I think this board has an SSD1306 (datasheet). It's really easy to communicate with it over I2C. We have 3 options for sending commands and data:

Before the display will show anything, we need to turn on the charge pump:

1
[0x78 0x80 0x8d 0x80 0x14]

Then we can turn on the display:

1
[0x78 0x80 0xaf]

The board appears to retain the displayed data for a while, so at this point you may see whatever was displayed on the screen last. However, it's more likely that you'll see random noise.

Some useful commands are:

Sequence Effect
0x8d 0x14 Enable charge pump
0x8d 0x10 Disable charge pump
0xaf Turn on display
0xae Turn off display
0xa7 Inverted display
0xa6 Regular (non-inverted) display
0xa5 Turn on all pixels
0xa4 Don't turn on all pixels
0x81 0xXX Set brightness

For example, you can flash the screen by turning on all the pixels:

1
[0x78 0x80 0xa5 %:200 0x80 0xa4]

or by inverting all the pixels

1
[0x78 0x80 0xa7 %:200 0x80 0xa6]

The display is 128 pixels wide and 64 pixels tall, for a total of 8192 pixels. In this model, the top 16 pixels are yellow and the bottom 48 are blue, with a 2-pixel gap between them. The pixels are split into 128 vertical columns and 8 horizontal pages. Each page is 8 pixels tall, and every byte that is sent is written to the current column in the current page. As far as I can tell, the display is upside-down, so data is written bottom-to-top, right-to-left. (Conversely, the display is right side up, but the yellow stripe is actually at the bottom.)

By default, page addressing is used, so when we reach the last column, we wrap back around to the first column, but stay in the same page. We can set the current page by using the commands 0xb0 to 0xb7. Changing the page doesn't reset the column. We can also set the current column by using the commands 0x00 to 0x0f for the lower nibble and 0x10 to 0x1f for the upper nibble. For example, to go to the middle of the second-last page, we can use

1
[0x78 0x80 0xb6 0x80 0x00 0x80 0x14]

Go back to the beginning with

1
[0x78 0x80 0xb0 0x80 0x00 0x80 0x10]

Writing data at the current position is straightforward:

1
[0x78 0x40 ...]

Just fill in the desired bit patterns. For example, to clear the current page, use

1
[0x78 0x40 0x00:128]

To clear the whole screen, use the unwieldy sequence

1
2
3
4
5
6
7
8
[0x78 0x80 0xb0] [0x78 0x40 0x00:128]
[0x78 0x80 0xb1] [0x78 0x40 0x00:128]
[0x78 0x80 0xb2] [0x78 0x40 0x00:128]
[0x78 0x80 0xb3] [0x78 0x40 0x00:128]
[0x78 0x80 0xb4] [0x78 0x40 0x00:128]
[0x78 0x80 0xb5] [0x78 0x40 0x00:128]
[0x78 0x80 0xb6] [0x78 0x40 0x00:128]
[0x78 0x80 0xb7] [0x78 0x40 0x00:128]

Starting from a stock image, we make a low-resolution version. Then (assuming Pillow and pyserial are installed), this script will use the binary mode of the Bus Pirate to write the picture straight to the display: OLED display showing a skull and crossbones

RTC module

I have a generic RTC module based on the DS1307 (datasheet). Not sure why the module has two sets of contacts, or what the purpose of the AT24C32 EEPROM chip is.

DS1307

The DS1307 wants 5 V, so we use orange (5.0V). It seems to have its own pull-up resistors. The data sheet says it's only rated for 100 kHz.

The slave address is 0x68, so we'll try reading using the magic number 0xd1. There are 64 bytes of RAM ("registers": 3 time, 4 date, 1 control, 56 general purpose), and the reads should wrap back to the beginning (0x00) after the last one (0x3f). We have no idea what the pointer is set to, but if we do 64 reads, we should get back everything:

1
[0xd1 r:64]

gets us

1
2
3
4
5
6
7
8
0x99 0x00 0x00 0x01 0x01 0x01 0x00 0xB3
0xFD 0xEB 0xFD 0x99 0xFD 0xA9 0xDC 0xE7
0xF2 0x86 0x30 0x86 0xEF 0x45 0xD8 0xD7
0xEF 0x60 0xFC 0xBA 0x3E 0x99 0xB8 0x95
0xF5 0xBE 0xFD 0xD8 0xE7 0xCC 0xD4 0xB5
0xD3 0x1F 0xE3 0xF3 0xEF 0x07 0x24 0xC1
0x2D 0xB9 0xF9 0xB4 0xF2 0x8E 0xEF 0x3A
0xBA 0xBC 0x70 0x8D 0xE3 0xF3 0x5B 0x45

(xx00-01-01 Mon 00:00:19). Confusingly, if we read 32 more bytes and then another 32, we get

1
2
3
4
5
6
7
8
9
     0x25 0x00 0x01 0x01 0x01 0x00 0xB3
0xFD 0xEB 0xFD 0x99 0xFD 0xA9 0xDC 0xE7
0xF2 0x86 0x30 0x86 0xEF 0x00 0xD8 0xD7
0xEF 0x60 0xFC 0xBA 0x3E 0x99 0xB8 0x95
0xF5      0xFD 0xD8 0xE7 0xCC 0xD4 0xB5
0xD3 0x1F 0xE3 0xF3 0xEF 0x07 0x24 0xC1
0x2D 0xB9 0xF9 0xB4 0xF2 0x8E 0xEF 0x3A
0xBA 0xBC 0x70 0x8D 0xE3 0xF3 0x5B 0x45
0x98 0x26

(xx00-01-01 Mon 00:25:??, then ??:26:18). The next 64 are then

1
2
3
4
5
6
7
8
9
               0x01 0x01 0x01 0x00 0xB3
0xFD 0xEB 0xFD 0x99 0xFD 0xA9 0xDC 0xE7
0xF2 0x86 0x30 0x86 0xEF 0x45 0xD8 0xD7
0xEF 0x60 0xFC 0xBA 0x3E 0x99 0xB8 0x95
0xF5 0xBE 0xFD 0xD8 0xE7 0xCC 0xD4 0xB5
0xD3 0x1F 0xE3 0xF3 0xEF 0x07 0x24 0xC1
0x2D 0xB9 0xF9 0xB4 0xF2 0x8E 0xEF 0x3A
0xBA 0xBC 0x70 0x8D 0xE3 0xF3 0x5B 0x45
0x87 0x21 0x01

(xx00-01-01 Mon 01:21:07). It looks like a single byte goes missing after each read sequence.

We can also rewind to the beginning by writing zero:

1
[0xd0 0x00]

Then the first 8 bytes are

1
0xC8 0x54 0x03 0x01 0x01 0x01 0x00 0xB3

(xx00-01-01 Mon 03:54:48). The battery was successfully keeping the time going while the device was unplugged. The fact that the time's been going up doesn't make sense at all, since the CH (clock halt) bit has been set all along!

When power is first applied, the timekeeping registers should be set to

1
0x80 0x00 0x00 0x01 0x01 0x01 0x00 0x03

This is almost consistent with what I had, except:

I found an older (2006) datasheet that says:

Note that the initial power-on state of all registers is not defined. Therefore, it is important to enable the oscillator (CH bit = 0) during initial configuration.

I suppose anything is possible in that case.

Let's start from a clean slate.

1
[0xd0 0x00 0x88 0x07 0x06 0x05 0x04 0x03 0x12 0x00]

(xx12-03-04 Fri 06:07:08). Then we read back

1
0xA9 0x07 0x06 0x05 0x04 0x03 0x12 0x20

(xx12-03-04 Fri 06:07:41). Looks like bit 5 of the control register is just stuck at 1, even though both datasheets (10 years apart) say "Always reads back as 0.". Except if I do

1
[0xd0 0x00 0x08 0x07 0x06 0x05 0x04 0x03 0x12 0x00]

to clear the CH bit, I get back

1
0x21 0x07 0x06 0x05 0x04 0x03 0x12 0x00

where the CH bit is indeed cleared (and the time is still ticking), but bit 5 of the control register is also cleared. This is repeatable: the CH bit always sets bit 5 of the control register, but doesn't stop the clock.

Let's time travel to the end of a century to check that everything rolls over as it should:

1
[0xd0 0x00 0x59 0x59 0x23 0x07 0x31 0x12 0x99 0x00]

(xx99-12-31 Sun 23:59:59). Moments later, I get back

1
0x08 0x59 0x23 0x07 0x31 0x12 0x99 0x00

(xx99-12-31 Sun 23:59:08). What? If I let it go for a bit, it does eventually get to

1
0x12 0x01 0x00 0x01 0x01 0x01 0x00 0x00

(xx00-01-01 Mon 00:01:12), as it should. Keeping a closer eye on it, I see that it counts from 59 seconds up to 63 seconds, after which it resets to 00 seconds, counts up to 59 seconds, and then finally does what I expect. Setting CH doesn't change this behaviour. However, all of

1
[0xd0 0x00 0x58 0x59 0x23 0x07 0x31 0x12 0x99 0x00]

(xx99-12-31 Sun 23:59:58),

1
[0xd0 0x00 0x59 0x58 0x23 0x07 0x31 0x12 0x99 0x00]

(xx99-12-31 Sun 23:58:59) and even

1
[0xd0 0x00 0x59 0x59 0x23 0x07 0x31 0x12 0x98 0x00]

(xx98-12-31 Sun 23:59:59) have the correct behaviour. As a final check, I'll try

1
[0xd0 0x00 0x00 0x59 0x23 0x07 0x31 0x12 0x99 0x00]

(xx99-12-31 Sun 23:59:00) followed by

1
[0xd0 0x00 0x59]

(:59). It still bugs out.

As a final test of the timekeeping, we'll set the date and time to right now:

1
[0xd0 0x00 0x40 0x49 0x22 0x07 0x14 0x01 0x18 0x00]

(xx18-01-14 Sun 22:49:40). Then we let it run with power for a couple of minutes, without power for a bit longer, and with power again for a couple of minutes. The result is

1
0x25 0x03 0x23 0x07 0x14 0x01 0x18 0x00

(xx18-01-14 Sun 23:03:25), which is correct.

What happens if I remove all power from the module, including the backup battery? I'll first set it to a known state with

1
[0xd0 0x00 0x08 0x07 0x06 0x05 0x04 0x03 0x12 0x00]

As expected, it reset back to the initial conditions:

1
0xB9 0x00 0x00 0x01 0x01 0x01 0x00 0xB3

The pins on the other side of the module seem to give me access to the same I2C bus. We also have the pins BAT, DS, and SQ. The first of these is at the same potential as the battery pin of the DS1307, so that an external circuit can keep track of its charge. The module I'm using is probably related to this RTC module, so DS is supposed to be the output of the temperature sensor, but it's just pulled high to VCC. Finally, SQ is supposed to be controlled by the SQW/OUT pin of the DS1307.

The simple mode of operation for the SQW/OUT pin is to set it to bit 7 of the control register when bit 4 is zero. Indeed, with the control register set to 0x00, SQ is nearly GND. If we set it to 0x80, it's pulled up to VCC. The pin on the chip is open drain, so the module must be pulling it up for us. Finally, 0x10 through 0x13 successfully set it to square waves of varying frequency. For example,

1
[0xd0 0x07 0x12]

24C32

This module also has a 24C32 EEPROM chip (datasheet), presumably for us to use as we desire. Thankfully, this also runs at 5 V, or else it would've been fried by now. This should also run at 400 kHz, but to be nice to the DS1307, I'll run it at 100 kHz. It's not clear from the module what the address of this chip is wired to, but the address search macro reveals that it's

1
0xA0(0x50 W) 0xA1(0x50 R)

That means that the addressing bits (A0, A1, and A2) are all set to zero.

Let's dive in and read some data!

1
[0xa1 r:32]

Not very exciting:

1
2
3
4
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

There are supposed to be 4 KB of addressable data. The first 512 bytes are high endurance, and the remaining 3584 bytes are not. Apparently the address won't roll over from the end (0x07ff; should be 0x0fff?) to the beginning (0x0000) automatically, so we have to watch out for that.

Writing a single byte is easy. Let's put something at the beginning, then rewind, and read it back:

1
[0xa0 0x00 0x00 0x12 [0xa0 0x00 0x00 [0xa1 r:8]

That didn't work. Maybe it doesn't like that repeated start. Let's keep the one for the read:

1
[0xa0 0x00 0x00 0x12] [0xa0 0x00 0x00 [0xa1 r:8]

That's better:

1
0x12 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

It seems that there's a 64-byte cache that is used as a ring buffer, so it's not possible to write more than 64 bytes in one go. Sounds easy:

1
[0xa0 0x00 0x01 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10]

Then

1
[0xa0 0x00 0x00 [0xa1 r:18]

gives us

1
0x12 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 0xFF 0xFF

Excellent!

I2C sniffing

Trying to sniff an I2C bus that just has [0xa0+0x00+0x25+] being sent (between an ATmega328P micro and a 24C32 EEPROM) once per second. At 100 kHz, I get

1
2
3
4
I2C>(2)
Sniffer
Any key to exit
[0xA0+0x00+][0xA0+0x00+][0xA0+0x00+][0xA0+0x00+]

which is missing the last byte. At 50 kHz, I get the full sequence

1
[0xA0+0x00+0x25+][0xA0+0x00+0x25+][0xA0+0x00+0x25+][0xA0+0x00+0x25+]

This is independent of the speed selected for the I2C mode. It seems to work fairly reliably (for this small test case) up to around 70 kHz, and starts to get garbled at around 80 kHz. At 400 kHz, it's hopeless:

1
2
3
4
I2C>(2)
Sniffer
Any key to exit
][][0x80-[][][][]][][]][][][0x80-[]][

Interestingly, the binary sniffer is robust up to about 125 kHz (but not beyond), which makes it suitable for sniffing 100 kHz I2C traffic.

SPI

SPI is a 4-wire protocol, with a clock line (SCLK), two data lines (MOSI, MISO), and a slave select line (SS).

There are many different ways to do SPI, so the Bus Pirate offers quite a few options:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
HiZ>m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>5
Set speed:
 1. 30KHz
 2. 125KHz
 3. 250KHz
 4. 1MHz

(1)>
Clock polarity:
 1. Idle low *default
 2. Idle high

(1)>
Output clock edge:
 1. Idle to active
 2. Active to idle *default

(2)>
Input sample phase:
 1. Middle *default
 2. End

(1)>
CS:
 1. CS
 2. /CS *default

(2)>
Select output type:
 1. Open drain (H=Hi-Z, L=GND)
 2. Normal (H=3.3V, L=GND)

(1)>
Ready
SPI>v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     CLK     MOSI    CS      MISO
P       P       P       I       I       I       O       O       O       I       
GND     0.00V   0.00V   0.00V   0.00V   L       L       L       L       L       

To connect a device, hook up brown (GND) to GND, orange/red (5.0V/3.3V) to VCC, purple (CLK) to SCLK, white (CS) to SS, grey (MOSI) to MOSI, and black (MISO) to MISO. Then power it up:

1
2
I2C>W
Power supplies ON

SPI can be run without pull-up or pull-down resistors (though it seems to be common to pull MISO a little so that it's not floating when no slaves are writing). I'm not sure why the default is open drain or why there's no 5 V option.

SN74HC595

The SN74HC595 (datasheet) is a shift register. It isn't really an SPI device, but we can still pretend it's one. If we treat SRCLK as SCLK and SER as MOSI, then we can write bits as usual. The only tricky part is RCLK, which actually propagates the written bits to the outputs. Ideally, I'd just send the opposite of SRCLK to it to update after each bit, but I'm too lazy to set that up, so I'll simply toggle SRCLK manually. I'll pull OE low and SRCLR high permanently.

On the Bus Pirate side, I've chosen all the defaults, except "Normal" instead of "Open drain" operation.

If I do a simple

1
2
SPI>0xff
WRITE: 0xFF 

and then toggle RCLK, all 8 outputs are high. Then

1
2
SPI>0x00
WRITE: 0x00 

and another toggle of RCLK sets them all low. I can get fancy with

1
2
SPI>0x55
WRITE: 0x55 

in which case the odd outputs (A, C, E, G) are on and the even ones (B, D, F, H) are off. Well that's pretty boring.

25X40AL

The 25X40AL (datasheet) is a 4 Mb flash memory chip. That's 512 kB. This is a proper SPI device, although it supports a non-standard mode where MOSI is used to send data to the master.

The one I'm using was taken from a hard drive controller board and presumably has data on it, so we'll see how that goes. This thing wants no more than 3.6 V, so we'll give it 3.3 V. The write protect and hold pins will be pulled high permanently.

For MISO (DO on the chip), we have:

Data is shifted out on the falling edge of the Serial Clock (CLK) input pin.

For MOSI (DIO on the chip), we have:

Data is latched on the rising edge of the Serial Clock (CLK) input pin.

It therefore makes sense that it doesn't care about clock polarity:

Both SPI bus operation Modes 0 (0,0) and 3 (1,1) are supported.

If we choose mode 0, the clock idles low. I think that means that we want the defaults "Active to idle" for "Output clock edge" and "Middle" for "Input sample phase". The default chip select option is fine (/CS), but we need to choose "Normal" for the output type instead of "Open drain".

It looks like we get something if we ask for the JEDEC ID:

1
2
SPI>[0x9f r:3]
READ: 0xEF 0x30 0x13 

According to the spec sheet, the first byte indicates "Winbond Serial Flash" and the last two indicate "W25X40AL", which is indeed the correct model. The last byte seems to encode the capacity, as well: 2^19 = 512 * 1024.

I can also get it to power down and then come back to life:

1
2
3
4
5
6
SPI>[0xb9]    
SPI>[0x9f r:3]
READ: 0x00 0x00 0x00 
SPI>[0xab]    
SPI>[0x9f r:3]
READ: 0xEF 0x30 0x13 

It readily tells us its manufacturer and device IDs:

1
2
3
4
SPI>[0x90 0x00:3 r:2]
READ: 0xEF 0x12 
SPI>[0xab 0x00:3 r:1]
READ: 0x12 

Here, 0xef means "Winbond Serial Flash" and 0x12 means "W25X40AL".

Now we can try reading some data, starting from the start:

1
[0x03 0x00 0x00 0x00 r:64]

gives us

1
2
3
4
5
6
7
8
0x5A 0x04 0x00 0x00 0x89 0x0F 0x00 0x00
0x88 0x0F 0x00 0x00 0x20 0x00 0x00 0x00
0x00 0x10 0x00 0x00 0x00 0x10 0x00 0x00
0x02 0x0A 0x00 0x00 0x00 0x00 0x00 0xD9
0x6D 0x00 0x00 0xFB 0x03 0x1C 0x10 0xB5
0x02 0xE0 0x10 0xC9 0x04 0x3A 0x10 0xC3
0x04 0x2A 0xFA 0xD2 0x10 0xBD 0xF8 0xB5
0x0E 0x1C 0x05 0x1C 0x17 0x1C 0x00 0x24

Looks like a bunch of 32-bit little-endian integers, which seems sensible if it's storing parameters. As expected,

1
[0x03 0x00 0x00 0x10 r:16]

gives us

1
2
0x00 0x10 0x00 0x00 0x00 0x10 0x00 0x00
0x02 0x0A 0x00 0x00 0x00 0x00 0x00 0xD9

In order to write, we need to set the WEL flag:

1
2
3
4
5
SPI>[0x05 r]
READ: 0x00 
SPI>[0x06]
SPI>[0x05 r]
READ: 0x02 

It looks like the 4 kB sector at 0x070000 is unprogrammed (all 0xff). In particular,

1
2
SPI>[0x03 0x07 0x01 0x00 r:8]
READ: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 

We can write into it:

1
SPI>[0x02 0x07 0x01 0x03 0x01 0x02 0x03 0x04]

Then

1
2
SPI>[0x03 0x07 0x01 0x00 r:8]
READ: 0xFF 0xFF 0xFF 0x01 0x02 0x03 0x04 0xFF 

We can undo our work by erasing the entire sector:

1
2
3
4
SPI>[0x06]
SPI>[0x20 0x07 0x00 0x00]    
SPI>[0x03 0x07 0x01 0x00 r:8]
READ: 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 

1-Wire

The 1-Wire bus is extremely simple. As its name suggests, it requires only a single data wire (in addition to a ground wire), but it allows for bidirectional communication. It can even have one device power another over the data wire by charging a small capacitor.

It's so simple that is has no settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
HiZ>m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>2
1WIRE routines (C) 2000 Michael Pearce GNU GPL
Ready
1-WIRE>v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     -       OWD     -       -
P       P       P       I       I       I       I       I       I       I       
GND     0.00V   0.00V   0.00V   0.00V   L       L       L       L       L       

Let's power it up:

1
2
3
4
1-WIRE>W
Power supplies ON
1-WIRE>P
Pull-up resistors ON

MagSafe 2 charger (85 W)

Thanks to some detective work by Ken Shirriff, I don't need to muck about trying to figure out how to talk to the charger. It supposedly has a DS2413 chip (datasheet) inside, which can be powered over the data line, so the charger doesn't need to be plugged into the wall for this. It does need a pull-up resistor, however. The chip is OK with 5 V, so I'll be using that.

The physical connection is not fun to make. The best solution I've found is to tape two jumper wires very near the charger pins to keep them from going all over the place, but without attempting to make contact. Then each time I've typed out a command, I can touch the pins using both hands, then hold that steady with one hand and use the other to send the command. Occasionally, it would get stuck in a working position on its own, but it refused to do so unless it was by its own initiative.

When there's no connection, we see

1
2
1-WIRE>[
BUS RESET Warning: *No device detected

When we accidentally short the pins, we see

1
2
1-WIRE>[
BUS RESET Warning: *Short or no pull-up 

When things are good, we can send a read command to extract the contents of the 64-bit ROM:

1
2
3
4
1-WIRE>[ 0x33 r:8
BUS RESET  OK
WRITE: 0x33 
READ: 0x85 0xF4 0xC7 0xA7 0xC0 0x13 0xAA 0x97 

Apparently the ID is interpreted backwards, so the bytes are

1
0x97 0xaa 0x13 0xc0 0xa7 0xc7 0xf4 0x85

In this case, the contents are as follows (verified against the System Report on a Mac):

Field Data Meaning System Report value
CRC checksum 0x97
Customer ID 0xaa1 85 W 0x0aa1
Customer data 0x3c0
Serial number 0xa7c7f4 0x00a7c7f4
Family code 0x85 0x0085

To access the PIO commands, we need to issue a skip ROM command (0xcc) first. We can check that both LEDs (green and orange) are off:

1
2
3
4
5
1-WIRE>[ 0xcc 0xf5 r
BUS RESET  OK
WRITE: 0xCC 
WRITE: 0xF5 
READ: 0x5A 

The first nibble is the complement of the second, so we can ignore it. The second nibble tells us the following:

Bit Value
PIOA Pin State 0
PIOA Output Latch State 1
PIOB Pin State 0
PIOB Output Latch State 1

That is, both LEDs should be off.

We can try to turn on the first LED. We don't expect the 1-Wire bus to power the LEDs, so we plug the charger in, now being very careful not to touch the power pins. Then we ask politely:

1
2
3
4
5
6
7
1-WIRE>[ 0xcc 0x5a 0xfe 0x01 r:2
BUS RESET  OK
WRITE: 0xCC 
WRITE: 0x5A 
WRITE: 0xFE 
WRITE: 0x01 
READ: 0xAA 0x78 

The reason this is so complicated is that we must send the desired state in normal form (all ones except the two least significant bits), and then also in inverted form. Then we read a confirmation byte (0xaa) and the new state:

Bit Value
PIOA Pin State 0
PIOA Output Latch State 0
PIOB Pin State 0
PIOB Output Latch State 1

Looks like we've successfully told the chip to turn on the LED, since the latch has changed state, but the pin is still low. Indeed, the LED is still off. The same thing happens if I try to turn on the other LED (or both of them), and another charger (also MagSafe 2, but 60 W) behaves the same way. I suspect that this is because the charger only outputs a small voltage with very limited current until it senses the correct load, but I haven't had the chance to test this.