Friday, July 20, 2007

SPI by hand

Introduction
Previously, i was not able to control more than one SPI (serial peripheral interface) integrated circuit from the Arduino successfully. I was using the in built SPI registers, functions and pins inherent to the Arduino. However, the SPI bus is actually very easy to control by directly manipulating any digital pins. My previous post regarding the control of a digital pot via a picaxe chip is an example of this.


What is SPI?

Well, I am not going to go into too much depth here. If you're into Arduino, chances are you already know (and can write the code more elegantly than me ;-). The wiki article has a bit of a info, i am sure there are heaps of websites out there. Basically, the protocol has at least three, if not four lines. These are:
• MISO (master in, slave out): data is sent from the controller to a slave IC
• MOSI (master out, slave in): data is sent from a slave IC to the controller
• SCK (spi clock): a clock signal is used to time bit-shifting, to ensure a smooth transfer of data
• CS (chip select): this line selects the slave IC, to which data can be written and read from

In theory, it should be possible for multiple slave IC's to share commone MISO, MOSI and SCK lines while each retains an individual CS line. Thus, individual slave IC's can be targeted for manipulation.


Bit Banging
By manipulating the digital outputs of the Arduino directly using code (as opposed to calling a slightly more macro function within the Arduino SPI library), it is possible to use any pins as MOSI, MISO, SCK lines, as well as multiple CS ones.

See the code below for more info. It is fully commented. Fun on a sleepless saturday morning.
/* SPI by Hand

by Sebastian Tomczak

20 July 2007

Adelaide, Australia

*/

/* INITIALISATION */

int SS1 = 2; // set slave select 1 pin

int SS2 = 5; // set slave select 2 pin

int CLK = 3; // set clock pin

int MOUT = 4; // set master out, slave in pin


byte cmd_byte0 = B00010001; // command byte to write to pot 0, from the MCP42XXX datasheet

byte cmd_byte1 = B00010010; // command byte to write to pot 1, from the MCP42XXX datasheet

byte cmd_byte2 = B00010011; // command byte to write to pots 0 and 1, from the MCP42XXX datasheet


byte work = B00000000; // setup a working byte, used to bit shift the data out


/* SETUP */

void setup() { // setup function begins here

pinMode(SS1, OUTPUT); // set CS pin to output

pinMode(SS2, OUTPUT); // set CS pin to output

pinMode(CLK, OUTPUT); // set SCK pin to output

pinMode(MOUT, OUTPUT); // set MOSI pin to output


digitalWrite(SS1, HIGH); // hold slave select 1 pin high, so that chip is not selected to begin with

digitalWrite(SS2, HIGH); // hold slave select 2 pin high, so that chip is not selected to begin with

}


void spi_transfer(byte working) {

; // function to actually bit shift the data byte out

for(int i = 1; i <= 8; i++) { // setup a loop of 8 iterations, one for each bit

if (working > 127) { // test the most significant bit

digitalWrite (MOUT,HIGH); // if it is a 1 (ie. B1XXXXXXX), set the master out pin high

}

else {

digitalWrite (MOUT, LOW); // if it is not 1 (ie. B0XXXXXXX), set the master out pin low

}

digitalWrite (CLK,HIGH); // set clock high, the pot IC will read the bit into its register

working = working << 1;

digitalWrite(CLK,LOW); // set clock low, the pot IC will stop reading and prepare for the next iteration (next significant bit

}

}


void spi_out(int SS, byte cmd_byte, byte data_byte) { // SPI tranfer out function begins here

digitalWrite (SS, LOW); // set slave select low for a certain chip, defined in the argument in the main loop. selects the chip

work = cmd_byte; // let the work byte equal the cmd_byte, defined in the argument in the main loop

spi_transfer(work); // transfer the work byte, which is equal to the cmd_byte, out using spi

work = data_byte; // let the work byte equal the data for the pot

spi_transfer(work); // transfer the work byte, which is equal to the data for the pot

digitalWrite(SS, HIGH); // set slave select high for a certain chip, defined in the argument in the main loop. deselcts the chip

}


void loop () {

for(int j = 0; j < 256; j++) {

spi_out(SS1, cmd_byte0, j); // send out data to chip 1, pot 0

spi_out(SS1, cmd_byte1, 255 - j); // send out data to chip 1, pot 1

spi_out(SS2, cmd_byte0, j / 2); // send out data to chip 2, pot 0

spi_out(SS2, cmd_byte1, 128 - (j/2)); // send out data to chip 2, pot 1

delay(10); // set a short delay

}

}

13 comments:

Anonymous said...

I think something got left off of two lines:
working = working <<>
should be
working = working << 1;

and
the first line of the loop() routine is incomplete.

Probably just a HTML thing.

Sebastian Tomczak said...

yeah, it is just a html thing. i think i have fixed it now. that is what one gets for pasting arduino code directly into blogger -- these days, i just pass the code through a wysiwyg html editor, then this fixes things quickly. thanks for the warning.

seb

Unknown said...

Wouldn't MISO be Master In Slave Out and MOSI be Master Out Slave In? Is that a simple typo, or do I just understand everything backwards?

Sebastian Tomczak said...

Yes, of course you are correct. I fail.


Fixed.

Alessandro Ricco said...

Can u tell us something about the maximum speed the "SPI by hand" can reach?

patrick boniface said...

Thank you so much! I bought a bunch of MCP42100's and then was like, "Uh, what now?" This is what now. What. Now.

Sebastian Tomczak said...

glad you found it useful :)

Anonymous said...

Maximum speed using this approach is very slow. Using optimized instructions you can get up to 2 MHz SCLK speed. Using the arduino digitalWrite wrappers it's more in the 100kHz range. Switching to the hardware SPI interface instead you can theoretically reach 8 MHz.

Sebastian Tomczak said...

i had issues with getting the hardware SPI port to work with this chip for some reason.

A.J. Whitney said...

Thank you so much for posting this. I couldn't get the Hardware SPI port to work either, and for my application speed isn't an issue. This worked perfectly for me using a MCP42050.

G.Popoter said...

I was wondering if I could make a library out of your code, this code has helped me a lot. Waiting for an answer from you.

GP

G.Popoter said...

I was wondering if I could make an arduino library out of your code, It was extremely helpful for me. Waiting for an answer.

Sebastian Tomczak said...

Go for it, GP :)