The Serial Peripheral Interface (SPI) is a high speed, synchronous, serial communication standard. This communication protocol is basically a Master Slave implementation where the master device controls the clock based on which the slave devices operates. The master can communicates with one or more slave in the system through SPI bus.
The SPI is special because it is simple and easy to implement in the hardware. The article explores the SPI hardware module of the PIC18F4550 microcontroller. This project will help in better understanding of the SPI protocol in detail.
Two PIC18F4550 are used in this project and one of them is programmed to act as a slave transmitter and the other is programmed to act as a master receiver, the method of data transfer between a master and slave is demonstrated with the help of an LCD.
In PIC18F4550 microcontroller the hardware implementation for the SPI interface can be viewed as a simple SISO (Serial-In-Serial-Out)
Fig. 2: Block Diagram of SPI hardware as Single Buffer in PIC Microcontroller
The above diagram is a single buffer implementation of the SPI hardware. The data is shifted into the buffer from the slave device through the SDI (MISO) and the data is shifted out of the buffer to the slave through the SDO (MOSI). The only difference in the hardware configuration of a master and slave device is the direction of the clock. For a master device the clock is always output and for a slave device the clock is always input. The Clock unit generates the clock required for the data transmission. In case of a master device, the clock is generated by the device and all other devices in the SPI bus send or receive data to the master based on this clock. When the microcontroller is configured as a slave it can only accept the clock from a master device.
SSPBUF
Fig. 3: Bit Configuration of SSPSTAT Register for SPI Communication in PIC18F4550
SMP:
This bit decides whether the master should sample the data receiving from the slave at the beginning or at the middle of the clock. The following figure shows a master which is sampling the data from the slave SDI at the end of the clock SCK.
Fig. 4: SMP Master Sampling Data From Slave SDI at End Of Clock SCK in SPI Communicarion
The following table shows the function of SMP bit when its value is 0 or 1.
1 |
Input data sampled at end of data output time |
0 |
Input data sampled at middle of data output time |
Fig. 5: Function of SMP bit when its value is 0 or 1 in SPI Communication
When the microcontroller is configuring as a SPI slave the SMP bit should be cleared.
CKE:
This bit is significant for both the master and slave device since it decide whether the data should be transmitted on the clock transition from active to idle state.
Fig. 6: Data Transmitted through SDO when clock transitions from active to idle state
The following table shows the function of CKE bit when its value is 0 or 1.
1 |
Transmit occurs on transition from active to Idle clock state |
0 |
Transmit occurs on transition from Idle to active clock state |
Fig. 7: Function of CKE bit when its value is 0 or 1 in SPI Communication using PIC
BF:
This bit will get automatically set once eight bits are received in the buffer which makes the buffer full. The bit can hence be used to decide when the data can be read out. Before reading or writing to the SSPBUF the status of this BF bit should be checked till it becomes 1.
Whenever the BF bit is found to be set it means the transmission is complete or reception is complete, since in the SISO buffer implementation of the SSPBUF transmission of a byte happen along with the reception of another byte.
The following table shows the function of CKE bit when its value is 0 or 1.
1 |
Receive complete, SSPBUF is full |
0 |
Receive not complete, SSPBUF is empty |
In this particular project both the master and the slave are configured with
SSPSTAT = 0x00;
SSPCON1
The Synchronous Serial Port Control 1(SSPCON1) register is the register which is dedicated register for the control of SPI only. All the bits of these register are significant and should be carefully set them.
Fig. 8: Bit Configuration of Synchronous Serial Port Control 1(SSPCON1) register In PIC
WCOL:
This bit can be used to detect whether write collision has occurred or not. The write collision occurs when the data to be transmitted is written into the SSPBUF while it still transmitting the previous data.
The following table shows the function of WCOL bit when its value is 0 or 1.
1 |
The SSPBUF register is written while it is still transmitting the previous word |
0 |
No collision occurred |
Fig. 9: Function of WCOL bit when its value is 0 or 1 in SPI Communication
SSPOV:
This bit is significant only in the slave mode. It is used to detect whether a data overflow has occurred or not. The data overflow is said to happen when already eight bits are shifted into the SSPBUF from the master and then a ninth bit shifts in. As the ninth bit shifts in the first ever bit which has been shifted into the SSPBUF will be lost. In such a situation a read operation on the SSPBUF will give a junk data.
To avoid this data error while receiving from the master the SSPOV bit will get set once an overflow occurs. The overflow can be prevented by simply by reading out the SSPBUF once it completes receiving eight bits.
The following table shows the function of SSPOV bit when its value is 0 or 1.
1 |
Overflow occurred |
0 |
No overflow occurred |
Fig. 10: Function of SSPOV bit when its value is 0 or 1
SSPEN:
This is the most important bit which enables the MSSP module as an SPI module. This bit is set for the first time when the SPI configuration is done. Resetting this bit during running of a program will reset the entire SPI module. When this bit is enabled the SCK, SDO, SDI and SS are configured as the SPI pins.
The following table shows the function of SSPEN bit when its value is 0 or 1.
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 |
Fig. 11: Function of SSPEN bit when its value is 0 or 1.
CKP:
This bit decides whether the idle state of the clock should be the low level or the high level of the clock. This bit decides the polarity of the idle state while the CKE pin decides whether to send a data when the clock makes a transition from active to idle state or idle to active state.
The following table shows the function of CKP bit when its value is 0 or 1.
1 |
Idle state for clock is a high level |
0 |
Idle state for clock is a low level |
Fig. 12: Function of CKP bit when its value is 0 or 1
SSPM3 SSPM2 SSPM1 SSPM0:
This group of four bits decides whether the microcontroller should act as a master or slave. According to the bit combination this bits can enable or disable the SS control on the slave’s SPI module and to select the master’s SPI clock frequency.
The following table shows the meaning of different bit combinations for these four bits;
0101 |
SPI Slave mode, clock = SCK pin, SS pin control disabled, SS can be used as I/O pin |
0100 |
SPI Slave mode, clock = SCK pin, SS pin control enabled |
0011 |
SPI Master mode, clock = TMR2 output/2 |
0010 |
SPI Master mode, clock = FOSC/64 |
0001 |
SPI Master mode, clock = FOSC/16 |
0000 |
SPI Master mode, clock = FOSC/4 |
In this project the master microcontroller is initialized with SSPEN = 1 (enable serial port), SSPM[3-0] = 0b0010 (master mode, clock =FOSC/64), all other bits are zero. Hence the value that should be written into the SSPCON1 register is;
SSPCON1 = 0x22
The slave microcontroller is initialized with SSPEN=1(Enable SPI), SSPM [3-0] =0100(SPI Slave mode, clock=SCKpin,SSpin control enabled). Hence the value that should be written into the SSPCON1 register is;
SSPCON1 = 0x24
READ AND WRITE OPERATIONS
The SSPBUF register can be read or write just like any other normal register, but care should be taken not to overwrite the data which is still in transmission and should read the data before the buffer overflows. Both the write overflow and the buffer overflow can be avoided by checking the BF bit in the SSPSTAT register before reading and writing into the SSPBUF. The program should wait till the BF flag becomes 1 before reading from the buffer or writing into the buffer.
There is a slight difference in the way in which the master and slave device should perform read and write. For a master it simply writes the data into the SSPBUF to initiate a transmission. Then it should wait for the BF flag to become high before it could read the data and the writing of the next byte follows the read operation. Slave on the other hand waits for the BF flag to become high and then it reads the data and writes the data.
The algorithm follows by the master and slave for reading (receiving data) and writing (transmitting data) through the SSPBUF is shown in the following figures;
MASTER:
Fig. 13: Algorithm followed by Master for data transmission through SSPBUF in SPI Communication
SLAVE:
Fig. 14: Algorithm followed by Slave for data transmission through SSPBUF in SPI Communication
TIPS FOR CODING:
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 1) The SPI is a high speed protocol which is normally used by a controller to communicate with high speed slave devices. In case where the microcontrollers are used in a system as both master and slave, the slave is expected to be running at a high speed than the master controller. Hence when writing the code make the slave device to run faster than the master by increasing its clock frequency. It can be also done by reducing the clock frequency of the master itself and hence slow down the master or in SPI register of the master settings select the lowest SPI clock frequency which is divided out from the microcontrollers CPU clock frequency.
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 2) The clock polarity settings done at both the master and slave should be the same
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 3) {C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}The master’s SCK should be made output and the slave’s SCK should be made input.
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 4) The SDO should be output and the SDI should be input for both the master and the slave.
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 5) In PIC18F4550 most of the pins are multiplexed and hence care should be taken to deactivate other modules which access the same pins which are used for SPI. For example;
The SCK pin is multiplexed with the analog channel 10, SDI with analog channel 12, and SS with the analog channel 4. Hence the ADC module should be deactivated before initializing the SPI module. Again the SDO is multiplexed with the RX pin of USART module and hence the data transmission is not possible unless the USART module is deactivated.
The slave select pin (SS) which should be made input in case of a slave device is also multiplexed with one of the analog channel AN4 and also with a comparator module’s output C2OUT. Hence it is better to deactivate both the ADC module and the comparator module in a slave device.
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 6) Any of the digital I/O of the master can be configured as an output pin to drive the SS pin of the slave device. The slave is enabled for transmission only when the SS pin is made low.
{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}· 7) Avoid using delay waiting in the slave device ad also write the code in such a way that there is minimum function calls for faster operation. Also there should not be too much delay or read/write of variables between the data read from SSPBUF and the data write back to the SSPBUF.
THE IMPLEMENTATION AND CODE DETAILS
The project is implemented using two PIC18F4550 microcontrollers and a 16*2 LCD screen. One of the microcontroller acts like the master while the other microcontroller act as the slave. The LCD screen is connected to the master microcontroller only. The master communicates with the slave and all the data that the master transmits to the slave as well as receives from the slave is displayed on the LCD screen.
Fig. 15: Block Diagram of PIC18F4550 and a 16*2 LCD
The code includes a few functions for initializing the SPI module, sending and receiving the SPI data etc. The details of the functions are given below;
voidspi_master_init ( void )
This function is used to initialize the SPI module as a master with the required clock frequency. It also set the clock polarity and when to sample input data and to transmit output data regarding the clock transition states. It also sets the SPI pins as input or output as required by the master. It can also disable all other modules multiplexed into the SPI pins.
*============================================
Function: initialize the SPI module as master
Input : no
Output : no
=============================================*/
voidspi_master_init ( void )
{
SSPSTAT = 0x00; // SMP=0(slave mode),CKE=0(transmission on Idle to active clock state),all other bits 0
SSPCON1 = 0x22; // SSPEN = 1 (enable serial port), SSPM[3-0] = 0b0010 (master mode, clcok=FOSC/64), all other bits are zero
TRISC &= 0x7F; // clear 7th bit keeping all other bits unchanged (SDO output)
TRISB &= 0xFD; // clear 1th bit keeping all other bits unchanged (SCK output)
TRISA |= 0x20; // clearing 5th bit, SS as output
ADCON0 = 0x3C; // Disabling the ADC module which is multiplexed with SPI pins
ADCON1 = 0x0F; // Disabling the ADC module which is multiplexed with SPI pins
CMCON = 0x00; // Disabling the COMPARATOR module which is multiplexed with SPI pins
SPPCON = 0x00; // Disabling the SERIAL PERIPHERAL CONTROL module which is multiplexed with SPI pins
TRISD &= 0xFE; // Clearing 0th pin of PORTD as output ( SS for the slave )
PORTD |= 0x01; // Setting 0th pin of PORTD ( Slave not selected )
}
voidspi_slave_init ( void )
This function is used to initialize the SPI module as a slave with SS pin enabled. It also set the clock polarity and when to sample input data and to transmit output data regarding the clock transition states. It also sets the SPI pins as input or output as required by the slave. It can also disable all other modules multiplexed into the SPI pins.
*============================================
Function: initialize the SPI module as slave
Input : no
Output : no
=============================================*/
voidspi_slave_init ( void )
{
SSPSTAT = 0x00; // SMP=0(slave mode),CKE=0(transmission on Idle to active clock state),all other bits 0
SSPCON1 = 0x24; // SSPEN=1(Enable SPI),SSPM[3-0]=0100(SPI Slave mode,clock=SCKpin,SSpin control enabled)
TRISC &= 0x7F; // clearing 7th bit of portC (SDO as output), keeping all other unchanged
TRISB |= 0x02; // setting 2nd bit of portB (SCK as input), keeping all other unchanged
TRISA |= 0x20; // setting 5th bit of portA (SS as input), keeping all other unchanged
TRISB |= 0x01; // setting 0th bit of portB (SDI as input), keeping all other unchanged
ADCON0 = 0x3C; // Disabling the ADC module which is multiplexed with SPI pins
ADCON1 = 0x0F; // Disabling the ADC module which is multiplexed with SPI pins
CMCON = 0x00; // Disabling the COMPARATOR module which is multiplexed with SPI pins
SPPCON = 0x00; // Disabling the SERIAL PERIPHERAL CONTROL module which is multiplexed with SPI pins
}
unsigned char spi_data ( unsigned char tx_data )
This function can send a data byte which it takes as the argument and return the received data byte. The function is implemented with a slight difference in both the master and slave; recall the previous flow chart for data read and write into the SSPBUF for master and slave.
Master:
The master first write the data and then wait for the data to complete transmission and then read the received data. It can also enable and disable the slave by controlling the SS pin of the slave. For enabling the slave before data transmission it makes the pin low which is connected to the SS pin of the slave and for disabling the slave once the transmission is complete it simply makes the same pin high.
*================================================
Function: to send and receive SPI data to and from the slave
Input : data byte to send
Output : data byte received
================================================*/
unsigned char spi_data ( unsigned char tx_data )
{
chardata_read;
PORTD &= 0xFE; // Enabling the slave
SSPBUF = tx_data; // put the data in the SSPBUF register which going to be send
while ( !SSPSTATbits.BF ); // wait until the all bits received
PORTD |= 0x01; // Disabling the salve
data_read = SSPBUF; // read the received data from the buffer
returndata_read;
}
Slave:
The slave first wait till all the data bits has been received, and then reads the data followed by a data write. There is no SS pin control inside the function.
*==================================================
Function: to send and receive SPI data to and from the master
Input : data byte to send
Output : data byte received
==================================================*/
unsigned char spi_data ( unsigned char tx_data )
{
chardata_read;
while ( !SSPSTATbits.BF ); // wait until the all bits received
data_read = SSPBUF; // read the received data from the buffer
SSPBUF = tx_data; // put the data in the SSPBUF register which will get transmitted on master’s clock
returndata_read;
}
SLAVE AS TRANSMITTER AND MASTER AS RECEIVER
In this arrangement the slave transmits the data which has been stored in it as a response to the ‘demo data’ send by the master. However the first data byte that is transmitted from the slave is also a demo data. The demo data could be any meaningless byte which the master has to transmit in order to generate a SPI clock and the slave will transmit in response to the first byte from the master. The slave put its data on the master’s SDI pin on this clock transitions. The master then reads the data arriving from the slave and displays it on the LCD.
The data flows through the SDO and SDI pins during the working of the slave as transmitter only is shown in the following figure;
Fig. 16: Data flow through SDO and SDI pins of PIC in SPI Communication
A 16×2 LCD screen is used to observe the data flowing through the SDO and SDI lines of the SPI bus in the system. The data which is transmitted from the master is displayed in the first line of the LCD and the data which is received from the slave is displayed in the second line of the LCD.
Fig. 17: Demo Data from Master and Slave displayed on LCD
Project Source Code
###
/#############Master Code#############//#include <p18f4550.h>//======================= chip config ===================//#pragma config PLLDIV = 1#pragma config CPUDIV = OSC4_PLL6#pragma config FOSC = INTOSC_HS#pragma config USBDIV = 1#pragma config IESO = OFF#pragma config PWRT = OFF#pragma config BOR = OFF#pragma config VREGEN = OFF#pragma config WDT = OFF#pragma config WDTPS = 32768#pragma config CCP2MX = ON#pragma config PBADEN = OFF#pragma config LPT1OSC = OFF#pragma config MCLRE = ON#pragma config STVREN = ON#pragma config LVP = OFF#pragma config ICPRT = OFF#pragma config XINST = OFF#pragma config DEBUG = OFF#pragma config WRTD = OFF//======================= chip config ===================////LCD Control pins#define rs PORTBbits.RB4#define rw PORTBbits.RB3#define en PORTBbits.RB2//LCD Data pins#define lcdport PORTD#define lcd_port_dir TRISDvoid lcd_clear ( void );void lcd_2nd_line ( void );void lcd_1st_line ( void );void lcd_ini ( void );void dis_cmd ( unsigned char cmd_value );void dis_data ( unsigned char data_value );void lcdcmd ( unsigned char cmdout );void lcddata ( unsigned char dataout );void delay_ms ( int delay );void spi_master_init ( void );unsigned char spi_data ( unsigned char tx_data );unsigned char spi_read_data;void main(){unsigned int i = 0, a = 3, b = 3;unsigned char data1 [] = "EngineersGarage";unsigned char data2 [] = " SPI demo ";OSCCON = 0x72; // set CPU Frequency as 8 MHzlcd_ini (); // LCD initialization//========================= start up display on LCD ================================//while ( data1 [i] != '