Introduction to SPI Communication Protocol
Serial Peripheral Interface (SPI) is a synchronous serial data protocol generally used for quick communication over short distances.
SPI is a synchronous protocol that allows a master device (usually microcontroller) to initiate communication with a slave device. It is a full-duplex communication protocol.
SPI uses separate clock and data lines with a select line to select the device with which we want to communicate.
SPI generally used to communicate quickly with devices like SD card, EEPROM chips, some of the TI ADCs, etc.
It can communicate with multiple slaves.
There are four basic pins used in PIC18F4550 SPI communication which are:
1. SCK (Serial Clock)
2. SDI (Serial Data In)
3. SDO (Serial Data Out)
4. SS (Slave Select)
General connection diagram of PIC18F4550 master and slave devices:
1. SCK: (Serial Clock)
The clock signal (SCK) is provided by the master to provide synchronization.
Only the master device can control the clock line, SCK.
2. SDI: (Serial Data In)
SDI is the serial data input which carries data into the device.
3. SDO: (Serial Data Out)
This is the Serial data output signal. It carries data out of the device.
4. SS: (Slave Select)
It is a slave select signal. In master mode, we can use it to choose the device (slave) with which we wish to communicate.
This is an active low signal, so a low on this line will indicate the SPI is active.
When multiple slaves are used then GPIO pins of the microcontroller are used to select slave devices.
Working of PIC18F4550 SPI
SPI allows 8 bit of data to be synchronously transmitted and received simultaneously.
Data leaving the master is put on the SDO (serial data output) line and data entering the master enters via the SDI (serial data input) line.
A clock (SCK), is generated by the master device. It controls when and how quickly data is exchanged between the two devices.
Master select devices using the SS (slave select) line.
SSPSR: It is the shift register used for shifting data in or out. It is not user-accessible.
SSPBUF: It is the buffer register to which data bytes are written to or read from.
- When the PIC18F4550 microcontroller wants to transmit the data, it will copy the data in the SSPBUF register (SSP Buffer) using a microcontroller.
- Then that data is moved to the SSPSR i.e. SSP shift register which will shift that data out bit by bit on each clock event from master to the slave via SDO. It transmits or shifts MSB bit first.
- While at the slave side the data is shifted in via SDI pin bit by bit using the same SSPSR register and moved to the SSPBUF once a byte of data has been exchanged between the two devices.
- Then it should be necessary to read the data from SSPBUF at master before another write. Otherwise, the incoming data will be lost. This will indicate buffer overflow.
- The master device transmits a clock and slave select signal. The slave device waits for these signals and uses them when processing the SPI data.
Note: When the master transmits data to the slave device, the slave also transmits the requested byte or flush byte simultaneously. So after data transmission via SSPBUF from master to slave, data should be read immediately to avoid overflow. It is also applicable for transmission from the slave device to master.
SPI Registers for PIC18F4550
To configure PIC18F4550 for SPI communication protocol following two registers are used
SSPSTAT: SSP Status Register for SPI mode
Bit 0 - BF: Buffer Full Status bit
1 = Receive complete, SSPBUF is full.
0 = Receive not complete, SSPBUF is empty.
Bit 6 - CKE: SPI Clock Select bit
When CKP =0
0 = Data transmitted on the falling edge of SCK
1 = Data transmitted on the rising edge of SCK
When CKP =1
0 = Data transmitted on the rising edge of SCK
1 = Data transmitted on the falling edge of SCK
Bit 7 - SMP: Sample bit
SPI Master mode:
1 = Input data sampled at end of data output time
0 = Input data sampled at the middle of data output time
SPI Slave mode:
SMP must be cleared when SPI is used in Slave mode.
Note: Other bits in this register are used for I2C protocol.
SSPCON1: SSP Control Register for SPI mode
Bits 3:0 - SSPM3: SSPM0: Master Synchronous Serial Port Mode Select bits
These bits are used to select Master or Slave mode and also a select clock.
SSPM3: SSPM0 | Mode | Clock / SS pin state |
---|---|---|
0000 | SPI Master mode | Fosc/4 |
0001 | SPI Master mode | Fosc/16 |
0010 | SPI Master mode | Fosc/64 |
0011 | SPI Master mode | TMR2 output/2 |
0100 | SPI Slave mode | SS enabled |
0101 | SPI Slave mode | SS disabled, used as I/O pin |
Other combinations are used for I2C Protocol.
Bit 4 - CKP: Clock Polarity Select bit
1= Idle state for the clock is a high level
0 = Idle state for the clock is a low level
Bit 5 - SSPEN: Master Synchronous Serial Port Enable bit
1 = Enables serial port and configures SCK, SDO, SDI, and SS as serial port pins.
0 = Disables serial port and configures these pins as I/O port pins.
Bit 6 - SSPOV: Receive Overflow Indicator bit
SPI Slave mode:
1 = A new byte is received while the SSPBUF register is still holding the previous data. In case of overflow, the data in SSPSR is lost.
0 = No overflow
Bit 7 - WCOL: Write Collision Detect bit (Transmit mode only)
1 = The SSPBUF register is written while it is still transmitting the previous word
(must be cleared through software)
0 = No collision
Importance of CKP and CKE bits
CKP decides the polarity of a clock, which means, the idle state of the clock. SPI devices need to be programmed with the same polarity. Then both devices will send or receive data at the same time. But this data is meaningful or dummy depends on the application.
This leads to the following three chances,
- Master sends data, the slave sends dummy data
- Master sends dummy data, the slave sends data
- The Master sends data, the slave sends data.
CKE decides on which rising/falling edge data should be transmitted. But actual data is put or latch on the SDO line opposite to the edge of data transmission.
CKE and CKP together decide what mode of SPI is used for all SPI transfers.
SPI Mode | CKP | CKE |
---|---|---|
0,0 | 0 | 1 |
0,1 | 0 | 0 |
1,0 | 1 | 1 |
1,1 | 1 | 0 |
Following Timing diagram show actual SPI communication
Programming PIC18F4550 SPI
Initialization:
Configure PORT pins used for SPI communication.
Also, disable SS pin i.e. SS=1.
Clear SSPIF.
Configure SSPSTAT register by setting bit SMP, CKE for data change at rising/falling edge of the clock, and make Buffer full (BF) = 0.
Initialize SSPCON register by setting bit SPEN =1 for SSP Enable. CKP indicates clock polarity.
Select mode of SPI master/slave mode and decide speed as you require using SSPM3: SSPM0 in SSPCON1 register.
Also de-multiplex all four pins SS, SDO, SDI, and SCK by using different registers i.e. ADCON0, ADCON1.
SPI_Master
void SPI_Init_Master()
{
/* PORT definition for SPI pins*/
TRISBbits.TRISB0 =1; /* RB0 as input(SDI) */
TRISBbits.TRISB1=0; /* RB1 as output(SCK) */
TRISAbits.TRISA5=0; /* RA5 as a output(SS') */
TRISCbits.TRISC7=0; /* RC7 as output(SDO) */
/* To initialize SPI Communication configure following Register*/
CS = 1;
SSPSTAT=0x40; /* Data change on rising edge of clk , BF=0*/
SSPCON1=0x22; /* Master mode,Serial enable,
idle state low for clk, fosc/64 */
PIR1bits.SSPIF=0;
/* Disable the ADC channel which are on for multiplexed pin when
used as an input */
ADCON0=0; /* This is for de-multiplexed the
SCL and SDI from analog pins*/
ADCON1=0x0F; /* This makes all pins as digital I/O */
}
SPI_Slave
void SPI_Init_Slave()
{
/* PORT definition for SPI pins*/
TRISBbits.TRISB0 = 1; /* RB0 as input(SDI) */
TRISBbits.TRISB1 = 1; /* RB1 as output(SCK) */
TRISAbits.TRISA5 = 1; /* RA5 as a output(SS') */
TRISCbits.TRISC7 = 0; /* RC7 as output(SDO) */
/* To initialize SPI Communication configure following Register*/
CS = 1;
SSPSTAT=0x40; /* Data change on rising edge of clk , BF=0*/
SSPCON1=0x24; /* Slave mode,Serial enable, idle state
high for clk */
PIR1bits.SSPIF=0;
/* Disable the ADC channel which are on for multiplexed pin
when used as an input */
ADCON0=0; /* This is for de-multiplexed the SCL
and SDI from analog pins*/
ADCON1=0x0F; /* This makes all pins as digital I/O */
}
Transmit Mode
- Copy data to the SSPBUF register.
- Wait for the SSPIF interrupt flag to set which is set after complete 1-byte transmission.
- Clear SSPIF
- Read the SSPBUF register immediately to flush data.
- Before transmission/writing of no. of bytes make SS active i.e. SS=0 and after complete transmission/writing disable it, SS=1.
void SPI_Write(unsigned char x)
{
unsigned char data_flush;
SSPBUF=x; /* Copy data in SSBUF to transmit */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0; /* Clear SSPIF flag */
data_flush=SSPBUF; /* Flush the data */
}
Receive mode
- Transmit flush byte by copying data to the SSPBUF register (optional).
- Wait for the SSPIF interrupt flag to set which is set after complete 1-byte is received.
- Read data from SSPBUF register and return SSPBUF.
unsigned char SPI_Read()
{
SSPBUF=0xff; /* Copy flush byte in SSBUF */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0;
return(SSPBUF); /* Return received byte */
}
Example 1
Let’s establish SPI communication between the PIC18F4550 microcontroller. One PIC will act as a master device and the other as a slave device.
By using this SPI communication, one PIC microcontroller will start counting continuously from 0-15 and transmit these values to another PIC microcontroller. Another PIC microcontroller will glow LED connected on PORT shows counting.
PIC18F4550 SPI Interfacing diagram
Program
SPI MASTER
/*
* Interface Two PIC18F4550 microcontroller
* for communicating with each other using SPI Protocol
* http://www.electronicwings.com
*/
#include <pic18f4550.h>
#include "Configuration_Header_File.h"
#define CS LATA5
void SPI_Write(unsigned char);
void SPI_Init_Master();
unsigned char SPI_Read();
void MSdelay(unsigned int);
void main()
{
int i;
OSCCON = 0x72; /* Use internal frequency 8 MHz */
INTCON2bits.RBPU=0; /* Enable internal Pull-up of PORTB */
SPI_Init_Master(); /* Initialize SPI communication */
MSdelay(10);
while(1)
{
CS = 0;
for(i=0;i<=15;i++) /* Start counter */
{
SPI_Write(i); /* Send counter value to Slave */
MSdelay(1000);
}
CS = 1;
i=0;
}
}
void SPI_Init_Master()
{
/* PORT definition for SPI pins*/
TRISBbits.TRISB0 = 1; /* RB0 as input(SDI) */
TRISBbits.TRISB1 = 0; /* RB1 as output(SCK) */
TRISAbits.TRISA5 = 0; /* RA5 as a output(SS') */
TRISCbits.TRISC7 = 0; /* RC7 as output(SDO) */
/* To initialize SPI Communication configure following Register*/
CS = 1;
SSPSTAT=0x40; /* Data change on rising edge of clk,BF=0*/
SSPCON1=0x22; /* Master mode,Serial enable,
idle state low for clk, fosc/64 */
PIR1bits.SSPIF=0;
/* Disable the ADC channel which are on for multiplexed pin
when used as an input */
ADCON0=0; /* This is for de-multiplexed the SCL
and SDI from analog pins*/
ADCON1=0x0F; /* This makes all pins as digital I/O */
}
void SPI_Write(unsigned char x)
{
unsigned char data_flush;
SSPBUF=x; /* Copy data in SSBUF to transmit */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0; /* Clear SSPIF flag */
data_flush=SSPBUF; /* Flush the data */
}
unsigned char SPI_Read()
{
SSPBUF=0xff; /* Copy flush data in SSBUF */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0;
return(SSPBUF); /* Return received data.*/
}
/*************************Delay Function****************************/
void MSdelay(unsigned int val) /* Delay of 1 ms for 8MHz Freq. */
{
unsigned int i,j;
for(i=0;i<val;i++)
for(j=0;j<165;j++);
}
SPI SLAVE
/*
* Interface Two PIC18F4550 microcontroller
* for communicating with each other using SPI Protocol
* http://www.electronicwings.com
*/
#include <pic18f4550.h>
#include "Configuration_Header_File.h"
#define CS LATA5
#define LED LATD
void SPI_Write(unsigned char);
void SPI_Init_Slave();
unsigned char SPI_Read();
void MSdelay(unsigned int);
void main()
{
int i;
TRISD =0; /* PORT initialize as output */
OSCCON = 0x72; /* Use internal osc. frequency 8 MHz */
INTCON2bits.RBPU=0; /* Enable internal Pull-up of PORTB */
SPI_Init_Slave(); /* Initialize SPI communication as a slave */
while(1)
{
LED = SPI_Read();
}
}
void SPI_Init_Slave()
{
/* PORT definition for SPI pins*/
TRISBbits.TRISB0 = 1; /* RB0 as input(SDI) */
TRISBbits.TRISB1 = 1; /* RB1 as output(SCK) */
TRISAbits.TRISA5 = 1; /* RA5 as a output(SS') */
TRISCbits.TRISC7 = 0; /* RC7 as output(SDO) */
/* To initialize SPI Communication configure following Register*/
CS = 1;
SSPSTAT=0x40; /* Data change on rising edge of clk , BF=0*/
SSPCON1=0x24; /* Slave mode,Serial enable, idle state
high for clk */
PIR1bits.SSPIF=0;
/* Disable the ADC channel which are on for multiplexed pin
when used as an input */
ADCON0=0; /* This is for de-multiplexed the SCL
and SDI from analog pins*/
ADCON1=0x0F; /* This makes all pins as digital I/O */
}
void SPI_Write(unsigned char x)
{
unsigned char data_flush;
SSPBUF=x; /* Copy data in SSBUF to transmit */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0; /* Clear SSPIF flag */
data_flush=SSPBUF; /* Flush received data */
}
unsigned char SPI_Read()
{
SSPBUF=0xff; /* Copy flush data in SSBUF */
while(!PIR1bits.SSPIF); /* Wait for complete 1 byte transmission */
PIR1bits.SSPIF=0; /* Clear SSPIF flag */
return(SSPBUF); /* Return received data.*/
}
/***************************Delay Function************************/
void MSdelay(unsigned int val) /* Delay of 1 ms for 8MHz Frequency */
{
unsigned int i,j;
for(i=0;i<val;i++)
for(j=0;j<165;j++);
}
Components Used |
||
---|---|---|
PIC18f4550 PIC18f4550 |
X 1 | |
PICKit 4 MPLAB PICKit 4 MPLAB |
X 1 |