SPI communication between PICs

From Mech
Revision as of 03:33, 11 February 2009 by Kwang Sim (talk | contribs)
Jump to navigationJump to search

Overview

SPI or Serial Peripheral Interface is a communication method that was once used to connect devices such as printers, cameras, scanners, etc. to a desktop computer. This function has largely been taken over by USB, but SPI can still be a useful communication tool for some applications. SPI runs using a master/slave set-up and can run in full duplex mode, meaning that signals can be transmitted between the master and the slave simultaneously. There is no standard communication protocol for SPI.

SPI is still used to control some peripheral devices and has some advantages over I2C (another type of serial data communication). SPI can communicate at much higher data rates than I2C. Furthermore, when multiple slaves are present, SPI requires no addressing to differentiate between these slaves. Compared to parallel buses, SPI has the additional benefit of requiring only simple wiring.


Peripheral devices that still use SPI:

• Converters (ADC and DAC)

• Memories (EEPROM and FLASH)

• Real Time Clocks (RTC)

• Sensors (temperature, pressure, etc.)

• Others (signal mixer, potentiometer, LCD controller, UART, CAN controller, USB controller, amplifier)


Basic Operation

SPI requires four lines, and is therefore often termed the “four wire” serial bus. These four lines are described in the table below.

Line Name Description
SCLK Serial Clock Output from master
MOSI/SIMO Master Output, Slave Input Output from master
MISO/SOMI Master Input, Slave Output Output from slave
SS Slave Select Output from master (active low)
Spi-diagram.png

The master, as its name suggests, controls all communication. By controlling the clock, the master decides when data is sent and received. Within each clock cycle a full duplex communication is carried out; each side sends and receives one bit of information. Because there is no standard communication protocol, the master can either send data or both send and receive data, depending on the needs of the application. Likewise, the slave can either receive data or both receive and send data back to the master.

Using the “Slave Select” line, the master chooses which slave with which to communicate. Note that more than one slave may be selected, simply by applying a logic low to the desired SS lines, as illustrated in the schematic diagram shown above. If a given slave is not selected (its SS is high) it disregards signals sent by the master.


References

SPI Background.www.totalphase.com

Serial Peripheral Interface. Www.wikipedia.org

mct.net

Circuit

see www.totalphase.com

Above shows the Master connected to three slaves. Each slave must be enabled through the slave select pin in order to communicate with the Master.

Spi.jpg

The two PICs can be wired directly by connecting these input and output pins of the diagram above.

Code

SPI communication on the PIC is accomplished via the functions spi_read() and spi_write(), which read and write one byte at a time over SPI. In the example below, the master reads an 8-bit value from the analog-to-digital converter and sends it to the slave via SPI. The slave reads the value and displays it on an LCD display.

Note: Our code did not work perfectly: the slave failed to received 10-20% of the messages sent by the master. In this instance that failure rate is not critical, as the slave just misses an update of the ADC reading when it misses a message. If you need accurate data transmission this is an unacceptable failure rate, however. We are not sure why this occurred.


SPI Modes

There are four SPI "modes" according to the relationship between the phase of the clock line and the phase of the MISO/MOSI lines. Wikipedia has a good explanation of what the modes are. The important point is that if you are communicating with a device that uses mode 3, for example, you need to use mode 3 as well to be compatible with it.


The Slave Select Line

The slave select (SS) line serves two purposes: not only is it used to select which slave is active during a communication, it is also used for synchronization. A slave's SS line should be high except when the master is talking to it. When the SS line is pulled low, the slave knows that a new communication is starting. If a slave somehow fails to receive a message properly, it will be reset when the SS line goes high at the end of the message, and be fresh and ready for a new message when the SS line goes low again. If you don't use an SS line (e.g. by disabling it or running it directly to ground) you run the risk of the slave getting out of sync with the master: if the slave misses a bit, it will always be one bit off in the future. Using the SS line allows the slave and master to realign themselves at the beginning of each communication, in case they get out of sync.


Master Code:

#include <18f4520.h>
#fuses EC,NOLVP,NOWDT,NOPROTECT
#device ADC=8
#use delay(clock=40000000)

//these define the different SPI modes in terms of constants the compiler knows about
//NOTE: our PICs only seemed to work in modes 1 and 3, though they are supposed to work with any modes
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1 (SPI_L_TO_H)
#define SPI_MODE_2 (SPI_H_TO_L)
#define SPI_MODE_3 (SPI_H_TO_L | SPI_XMIT_L_TO_H) 

#define SS_PIN PIN_D0 //this can be any output pin on the master

void main()
{
   int val;

   //set up the ADC to read from A0
   setup_adc_ports(AN0);
   setup_adc(ADC_CLOCK_INTERNAL);
   set_adc_channel(0);

   //the next statement sets up the SPI hardware, with this PIC as a master using mode 1
   //SPI_CLK_DIV_64 sets the speed of the SPI clock pulses--this is the slowest speed
   setup_spi(SPI_MASTER | SPI_MODE_1 | SPI_CLK_DIV_64); 
   
   while(true) {
       val = read_adc();      //read the value to be sent
       
       output_low(SS_PIN);    //pull the slave select line low to select the slave
       delay_us(10);          //give the slave time to notice this (may be unnecessary)
       
       spi_write(val);        //send the value
       
       delay_us(10);          //(may be unnecessary)
       output_high(SS_PIN);   //deselect the slave. 
       
       delay_ms(10);
   }
}


Slave Code:

#include <18f4520.h>
#fuses EC,NOLVP,NOWDT,NOPROTECT
#use delay(clock=40000000)

#include <LCD.C>

#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1 (SPI_L_TO_H)
#define SPI_MODE_2 (SPI_H_TO_L)
#define SPI_MODE_3 (SPI_H_TO_L | SPI_XMIT_L_TO_H)

void main()
{
   setup_spi(SPI_SLAVE | SPI_MODE_1); //set up SPI hardware as a slave in mode 1
   
   lcd_init();
   
   while(true) {
       val = spi_read(0); //spi_read must be passed an argument. The argument value is sent
                          //back to the master whenever the master sends us a message again. 
                          //This allows two-way communication, but here the master ignores
                          //whatever the slave sends back, so just send a 0.

       //display the value read:
       lcd_gotoxy(1, 1);
       printf(lcd_putc, "Pot at: %u   ", val);
   }
}


Alternative Slave Code, Using Interrupts:

#include <18f4520.h>
#fuses EC,NOLVP,NOWDT,NOPROTECT
#use delay(clock=40000000)

#include <LCD.C>

#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1 (SPI_L_TO_H)
#define SPI_MODE_2 (SPI_H_TO_L)
#define SPI_MODE_3 (SPI_H_TO_L | SPI_XMIT_L_TO_H)

#int_ssp //this interrupt will occur whenever we receive an SPI message (or an I2C message, actually)
void message_receieved() {
    unsigned int val;

    val = spi_read(); //When you don't pass an argument, spi_read just returns the most
                      //recently received SPI message. We can do this here because we know
                      //that we just received a new message.

    //display the value:
    lcd_gotoxy(1, 1);
    printf(lcd_putc, "Pot at: %u   ", val);
}

void main()
{
   setup_spi(SPI_SLAVE | SPI_MODE_1); //setup the PIC as a slave in mode 1
   enable_interrupts(INT_SSP); //enable interrupts on SPI messages
   enable_interrupts(GLOBAL);
   
   lcd_init();
   
   while(true); //sit and wait for a message
}


Two-way communication

Though not shown above, it is possible to use SPI for two-way communication between master and slave. If the slave calls

message_from_master = spi_read(123);

It will wait for the master to send some clock pulses; when the clock pulses come the slave will simultaneously read the message being sent by the master and send its own message (the number 123). The master, for its part, will call

message_from_slave = spi_read(22);

This will cause the master to send the number 22 to the slave and simultaneously read the message sent back by the slave (in this case the number 123). So the master and slave have exchanged one byte of information. If you are the master and you just want to get a byte from the slave without sending one, just send a junk value, which the slave will discard:

message_from_slave = spi_read(0);


Useful resources

The CCS user manual

CCS forum