Friday, June 5, 2009

SPI Client Tutorial for the GoodFET

by Travis Goodspeed <travis at radiantmachines.com>

GoodFET SPI

I've just confirmed that SPI support is functional on the GoodFET, but I've done so with my ugly-as-sin Python client. I'd much rather that neighborly souls write their own clients for this device. The following article describes the protocol by which this is done, as well as some snippets of the GoodFET firmware. The present code should be sufficient to write an AVR programmer, an interface for Chipcon radios, and all sorts of other neighborly things.

SPI is a synchronous, full-duplex serial protocol, which is an uptown way of saying that it has a clock and that bits are simultaneously transmitted and received. Whenever you transmit a byte, you are actually exchanging values of a register. Instead of start and stop bits, clocks are synchronized by the falling of the !SS (Slave Select) line, and then they remain synchronized by counting bits. Data is transmitted MSB-first, received LSB-first.

The GoodFET attaches by a USB serial port, one which is also used for programming. As the DTR, Data Terminal Ready, line is connected to the !RST pin of the GoodFET's microcontroller, you will find that the GoodFET turns off when you connect to it with Minicom or Hyperterminal, then turns back on when you disconnect! To remedy this, you must drop the DTR line low by using ioctl() in Posix C or your language's equivalent. This will boot the GoodFET, beginning a session.

The session itself will begin at 9600 115200 8/N/1, although faster speeds will be supported in the near future. When you drop the line low, you will receive the three bytes {0x00, 0x7F, 0x00}. This is a GoodFET packet, and like all packets it follows the same, simple form. The first byte indicates the application, 0x00 being the Monitor. The other application that we will use is 0x01, SPI. The second byte is a verb, with all bytes less than 0x80 being generic verbs and all bytes higher being application-specific verbs. 0x7F means "OK" in every application. Other verbs include READ (0x00), WRITE (0x01), SETUP (0x10), and NOK (0x7E). A complete list of generic verbs is available in the Manual. The third byte, 0x00, indicates the number of data bytes that follow.

The GoodFET's protocol is transaction based, with every transaction after the first being begun by the host. It is recommended that your transmit function also automatically receive this reply. Further, any verb may have data attached. While the OK verb of the Monitor might never include data, your client had better accept whatever data it chooses to send your way.

Inside of the GoodFET, an application is merely a handler. All application present in the firmware are active simultaneously, and where it doesn't cause problems you may mix and match them. For example, you might wish to use the Monitor to manage the Test pin manually in order to implement an AVR programmer.

In any case, you can implement an SPI library through the GoodFET with only a few verbs. A sample session with commentary follows.

Transaction


First, the host adjusts to 9600 8/N/1, then the device announces its presence.
D: {0x00 (Monitor), 0x7F (OK), 0x00 (0 bytes)}

The host calls the SETUP verb of the SPI application to configure the I/O pins.
H: {0x01 (SPI), 0x10 (SETUP), 0x00 (0 bytes)}
D: {0x01 (SPI), 0x10 (SETUP), 0x00 (0 bytes)}

At this point, the I/O pins are now properly configured. Pins 1, 2, 5, and 7 are now MISO, MOSI, !SS, and SCK. As the READ and WRITE verbs are interchangeable for SPI, the host may use either. If nothing is connected, the MISO pin's pullup resistor will ensure that the reply is always 0xFF.
H: {0x01 (SPI), 0x00 (READ), 0x01 (1 byte), 0xDE}
D: {0x01 (SPI), 0x00 (READ), 0x01 (1 byte), 0xFF}
H: {0x01 (SPI), 0x01 (WRITE), 0x01 (1 byte), 0xAD}
D: {0x01 (SPI), 0x01 (WRITE), 0x01 (1 byte), 0xFF}

If pins 1 (MISO) and 3 (MOSI) are bridged, the output (MOSI) will feed right back into the input (MISO), causing the output value to be returned.
H: {0x01 (SPI), 0x01 (WRITE), 0x01 (1 byte), 0x80}
D: {0x01 (SPI), 0x01 (WRITE), 0x01 (1 byte), 0x80}

If multiple SPI devices are chained together, such as in a gang programmer, data may be shifted through one into the other. To do this, make a ring with each MISO line connected to the next's MOSI, then perform a transaction of as many bytes as you have devices. This will cause !SS (Slave Select) to drop low, then all bytes will be shifted, then !SS will rise high to indicate the end of the transaction. The same is appropriate for registers in multiples of 8 bits, such as a 16 bit device. The first byte in the reply is the first byte to be received, so when ganging devices the bytes of the reply will need to have their order swapped.
H: {0x01 (SPI), 0x01 (WRITE), 0x02 (2 bytes), 0xDE, 0xAD}
D: {0x01 (SPI), 0x01 (WRITE), 0x02 (2 bytes), 0xBE, 0xEF}

Ideas


A programmer/dumper for SPI EEPROMs could be written in short order, as could a command console for various Zigbee radio modules. The present application only supports SPI's master mode, but slave and sniffing support will be added at a later date.

A separate tutorial on extending the firmware will be available soon. If you'd like to get a head start, fork spi.c to make an I2C adapter. Email or catch me at a conference for a free board.

2 comments:

Travis Goodspeed said...

SPI Flash support has been added. See the goodfet.spiflash client.

--Travis

Travis Goodspeed said...

Since writing this article, I extended the length field from a single byte to a little-endian 16-bit word. Other forms in this article are also subject to change, and it's best to peek around the GoodFET documentation and source code if you run into trouble.