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:
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.
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
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?
Yes, of course you are correct. I fail.
Fixed.
Can u tell us something about the maximum speed the "SPI by hand" can reach?
Thank you so much! I bought a bunch of MCP42100's and then was like, "Uh, what now?" This is what now. What. Now.
glad you found it useful :)
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.
i had issues with getting the hardware SPI port to work with this chip for some reason.
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.
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
I was wondering if I could make an arduino library out of your code, It was extremely helpful for me. Waiting for an answer.
Go for it, GP :)
Post a Comment