Categories: Firmware Guide-PIC18

SPI in PIC Microcontrolletr

SPI which is the abbreviation for the serial peripheral interface is an interface bus that transmits data between microcontrollers and small peripherals including sensors, SD cards, and shift registers. It was Motorola which introduced SPI in the year 1979 This is also referred to as four wire serial bus. There are similar protocols available such as Microwire developed by National Semiconductor. An SPI can be regarded as a four-wire single-ended serial communications interface found in many microprocessors/microcontrollers, peripheral chips which enable the peripheral devices as well as controllers to communicate with each other. Even though SPI was developed for the purpose of communication between a host processor and it’s peripherals it is also possible with an SPI to enable communication between two processors. SPI can operate with a single master and multi master protocols even though multi-master mode is rarely used.

Synchronous Data Transmission

SPI is a synchronous data transmission protocol which means the sender and receiver share a single clock. There is asynchronous data transmission protocol as well. The classification is based on the nature of the signals used to synchronize the master and slave devices. In asynchronous mode, the sender provides a timing signal which prompts the receiver to read the next bit of the data. If there is no data available to transmit at a particular instant, a fill character will be sent to make sure that the data is always transmitted.

While in the case of asynchronous transmission, data should be transmitted without a clock signal being sent by the sender. In general case, the sender and receiver come in agreement with the speed of transmission. To make sure the data accessing follows that agreement, both the sender and the receiver set up their own internal circuits. In order to synchronize the sending and receiving units, special bits are added to each word.

SPI Connections and Control Lines

Control lines and data lines of a single master-single slave SPI is shown in the figure. The signal wires are MOSI, MISO, SCLK and SS. Master device generates the MOSI signal and is received by the slave whereas a MISO signal is generated by a slave and received by the master.

The serial clock signal (SCKL) is transmitted by the master to synchronize data transfer between the master and the slave. SS is the slave select signal, which is also an active low signal, is transmitted to the chip select pin of the slave. MOSI signal is also named as serial data in (SDI) and MISO signal is named as serial data out (SDO). MOSI and MISO are data lines while SCKL and SS are control lines.

Imagine a device which does not require an input, In that case, SDI pin may not be required and if a device does not require an output, SDO pin may not be needed. Similarly, if there is only one slave device is involved in the communication, then the CS pin on the particular slave device is grounded. In case of multiple slave operation, an SS signal is sent from the master to each slave device.

SPI communication is described in detail in Basics of SPI communication

MSSP Module in PIC18F4550

Master synchronous serial Port (MSSP) module is the serial interface for communicating with other devices like EEPROMs, display drivers, real-time clocks, A/D converters etc. It can operate in two modes, SPI or I2C. In this article, we are discussing about implementing SPI using this module.

SPI Modes

Based on the CKP and CKE values, SPI can be operated in 4 different modes. CKP indicates the clock polarity (state of the clock in idle phase) and CKE indicates the clock edge.

Control Registers

SSPSTAT

SMP :- Sample bit indicates the time at which data has to be sampled. In master mode, input data will be sampled at the end of the output time if the SMP is high. If it is low, data will be sampled at middle. In slave mode, SMP bit must be cleared.
CKE :- This is the clock edge select bit. If it is 1, transmits occur on the transition from active to idle clock state and transmit occur from Idle to active clock state on low CKE bit.
D/A :- Not used in SPI
P :- Not used in SPI
R/W :- Not used in SPI
UA :- Not used in SPI
BF :- This bit indicates the status of the buffer. In both the transmit and the receive mode, a logic one in this bit indicates the buffer register SSPBUF is full and a logic 0 indicates the buffer is empty.

SSPCON1

WCOL :- This bit is called write collision bit and is used only in transmit mode. A write to SSPBUF register when SPI module is not ready for transmission will set the bit.
SSPOV:- Receive overflow indicates the bit is used only in receive mode, the bit will be set when a new byte is received and SSPBUF is still holding the previous data.
SSPEN :- Master synchronous serial port enables bit setting. This bit will enable the serial port and be clearing the bit will disable the serial port which then configures the SPI pins as IO pins.
CKP :- Clock polarity select bit indicates what is the state of the cock in an idle state. If it is set, clock idle state is high and otherwise, it is low.
SSPM3:SSPM0 Master synchronous serial port mode select bit
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

Operation

For initializing the SPI, we need to configure some settings in the registers described earlier. Below are the options required to set for operating the MSSP module in SPI mode.

  • Master mode/slave mode of SPI
  • Clock polarity
  • Data input sample phase
  • Clock edge
  • Clock rate (in master mode only)
  • Slave select mode (in slave mode only)

The MSSP module contains two registers, transmit/receive shift register (SSPSR) and a buffer register (SSPBUF). SSPSR is not directly writable and it is accessed through SSPBUF register. SSPSTAT register indicates the various status conditions during SPI communication.

SSPSR shifts the data in and out of the module while the SSPBUF holds the data until the received data is ready. After receiving 8-bits of data, the byte gets moved to the SSPBUF register. It is indicated by the setting buffer full flag bit (BF) in SSPSTAT register and interrupt flag bit (SSPIF). The double buffering allows the module to start to receive next before reading the data. Writing to SSPBUF during the transmission or reception will result in the setting of write collision detect bit (WCOL) in SSPCON1.

During the reception, SSPBUF should be read before the next byte of data to be transferred is moved from SSPSR to SSPBUF. The Buffer Full bit (BF) indicates when the SSPBUF is loaded with new byte and, after the user reads the byte, the BF bit gets cleared.

Master mode

Master initiates the data transfer and reception by controlling SCK line. It generates the clock for slave devices also and controls when to transfer data by the slave devices. The data transfer can be initiated by writing into the SSPBUF register and the data will be shifted out by SSPSR register. During the reception, after each byte received is getting transferred to the SSPBUF, status bits are set (BF and SSPIF).
The SPI clock rate is selected by the user. There are four options

  • Fosc/4
  • Fosc/16
  • Fosc/64
  • Timer2 out/2

This is selected by setting SSPM3: SSPM0 in SSPCON1 register value. Please refer the SSPCON1 register description for this.

Slave mode

In the slave mode, data is transmitted or received when the clock pulses are received on the SCK pin from the master. The slave select pin (SS) can be used to use the module in synchronous slave mode. SSPM3: SSPM0 in SSPCON1 should be 0100 to use this mode. A low on SS pin will start the SPI module and high on the pin will reset the module.

Enabling pins

The SSPEN bit in SSPCON1 registers is used for enabling the SPI port pins for serial communication. The direction of the pin should be set in their TRIS registers. Since we are using the module as master, the SDO, SCK and SS pins will be in output mode, so clear the TRIS bits for them. SDI is controlled automatically by the module. In slave mode, SDO will be output pin and SCK and SS will be input pins controlled by the master device. So set the TRIS bit accordingly.

SPI Firmware Implementation

Below is a sample library implemented using the PIC18F4550 microcontroller. This code is adapted from the XC8 SPI library from Microchip.

/********************************************//*      SPI.h                               *//********************************************/
#define TRIS_SDI TRISCbits.TRISC4       // DIRECTION define SDI input
#define TRIS_SCK TRISCbits.TRISC3      // DIRECTION define clock pin as output
#define TRIS_CS TRISAbits.TRISB5       // DIRECTION CS - RA5
#define TRIS_SDO TRISCbits.TRISC5      //DIRECTION SD0 as output

void OpenSPI( unsigned char sync_mode, unsigned char bus_mode, unsigned char smp_phase);
signed char WriteSPI( unsigned char data_out );
unsigned char ReadSPI( void );
unsigned char DataRdySPI( void );

The function definitions are as below.

/********************************************//*      SPI.c                               *//********************************************/
void OpenSPI( unsigned char sync_mode, unsigned char bus_mode, unsigned char smp_phase)
{
    SSPSTAT &= 0x3F;                // power on state
    SSPCON1 = 0x00;                 // power on state
    SSPCON1 |= sync_mode;           // select serial mode       (0b00000010 - SPI Master mode, clock = Fosc/64)
    SSPSTAT |= smp_phase;           // select data input sample phase

    switch( bus_mode )
      {
        case 0:                       // SPI bus mode 0,0
          SSP1STATbits.CKE = 1;       // data transmitted on rising edge
          break;    
        case 2:                       // SPI bus mode 1,0
          SSP1STATbits.CKE = 1;       // data transmitted on falling edge
          SSP1CON1bits.CKP = 1;       // clock idle state high
          break;
        case 3:                       // SPI bus mode 1,1
          SSP1CON1bits.CKP = 1;       // clock idle state high
          break;
        default:                      // default SPI bus mode 0,1
          break;
      }
      
    switch( sync_mode )
      {
        case 4:                         // slave mode w /SS enable
              TRIS_SCK  = 1;            // define clock pin as input    
              TRIS_CS  = 1;        // define /SS1 pin as input
            break;
    
        case 5:                   // slave mode w/o /SS enable
            TRIS_SCK  = 1;        // define clock pin as input    
            break;
        
        default:                 // master mode, define clock pin as output
            TRIS_SCK  = 0;       // define clock pin as output    
             break;
      }
      
    TRIS_SDI = 1;               // define SDI input
    TRIS_SDO = 0;               //SD0 as output   
    
    SSPCON1 |= 0x20;          // enable synchronous serial port,  0b00100000  Enable serial port and configures SCK, SDO, SDI
}


signed char WriteSPI( unsigned char data_out )
{
    unsigned char TempVar;
    TempVar = SSPBUF;           // Clears BF
    PIR1bits.SSPIF = 0;         // Clear interrupt flag
    SSPCON1bits.WCOL = 0;            //Clear any previous write collision
    SSPBUF = data_out;           // write byte to SSPBUF register
    if ( SSPCON1 & 0x80 )        // test if write collision occurred
        return ( -1 );              // if WCOL bit is set return negative #
    else
        while( !PIR1bits.SSPIF );  // wait until bus cycle complete
    return ( 0 );                // if WCOL bit is not set return non-negative#
}


unsigned char ReadSPI( void )
{
  unsigned char TempVar;
  TempVar = SSPBUF;        // Clear BF
  PIR1bits.SSPIF = 0;      // Clear interrupt flag
  SSPBUF = 0x00;           // initiate bus cycle
  while(!PIR1bits.SSPIF);  // wait until cycle complete
  return ( SSPBUF );       // return with byte read
}

unsigned char DataRdySPI( void )
{
  if ( SSPSTATbits.BF )
    return ( +1 );                // data in SSPBUF register
  else
    return ( 0 );                 // no data in SSPBUF register
}

 

Using the above function, one can easily implement the SPI mode. Here is an example code for using it in master mode with Fosc/4 in mode 0;

#include <xc.h>  
   #include "spi.h"
   #include "config.h"      // configuration settings (not included)
   
   #define _XTAL_FREQ 20000000
   
   
   void main(){

        char temp;
        OpenSPI(0,0,1);         // SPI in mode 0 with Fosc/4 - master mode
        WriteSPI(0xFF);         // Send a byte
        while(!DataRdySPI());   // wait for a data to arrive
        temp = ReadSPI();       // Read a byte from the 
        /* Change according to the slave device */    
   }

The different devices have different types of operation and data format, so read the datasheet of the devices carefully before using the SPI module with any devices.

Share