Wednesday, 9 July 2014

PIC to PIC Communications

Introduction

Microchip have released a new family of 18F microcontroller devices that support a new feature called PPS-Lite. Peripheral Pin Select (PPS) allows the various digital IO functions such as UARTs onto pins of your choice.

In this post I will show you how to communicate between two PICs using SPI and how to set up the  PPS registers.


Schematic


Both PICs are the same type 18F65J94, one is designated the Master the other the SLAVE. The master and slave SPI pins are connected as shown in the image below. Note that here we do not use the slave select line.



Source Code

I wanted to be able to use the same binary for both PICs to help make programming of both chips easier as there is only one choice of hex file. The PICs will check pin E1 to see if they are a master or slave and behave accordingly.

One difficulty I faced is that the Master SCLK is an output and the Slave SCLK is an input. So SCLK is actually two different hardware functions SCLKOUT and SCLKIN. PPS uses two types of registers, output and input, so I needed to set the master clock pin to output register RPOR23 and the slave to input register RPINR8. Luckily the CCS compiler makes setting up these registers easy and I was able to use the same code/binary for both pics:

#pin_select SDO1=PIN_D1
#pin_select SDI1=PIN_D4
#pin_select SCK1OUT=PIN_D3
#pin_select SCK1IN=PIN_D3


The code below is written for the CCS Compiler v5.025, MPLAB X v2.10. The master will send 0,1,2 and 3 every second via SPI. Both pics will illuminate the red/green leds as they send/receive the data.


 #include "18F65J94.h"  
 #include "Device.h"  
 #inline  
 void slave(void);  
 #inline  
 void master(void);  
 #inline  
 void slave_spi_init(void);  
 #inline  
 void master_spi_init(void);  
 void main(void) {  
     set_tris_e(TRISTATE_E);  
     if (input(MCU_ID)==ID_MASTER)  
     {  
         output_bit(LED_RED,LED_OFF);  
         output_bit(LED_GREEN,LED_ON);  
         delay_ms(1000);  
         master();  
     }  
     else  
     {  
         output_bit(LED_RED,LED_ON);  
         output_bit(LED_GREEN,LED_OFF);  
         delay_ms(500);  
         slave();  
     }  
 }  
 #inline  
 void slave_spi_init(void)  
 {  
     //Set SDI and SCK as inputs, SDO as output  
     set_tris_d(TRISTATE_D_SLAVE);  
     //SPI Setup  
     //Slave, slave select disabled  
     //Clear errors  
     SSP1CON1 = 0b00000101;  
     //Slave must clear Sample bit  
     SSP1STAT_SMP=0;  
     //Mode 0 CKP = 0, CKE = 1  
     //Mode 1 CKP = 0, CKE = 0  
     //Mode 2 CKP = 1, CKE = 1  
     //Mode 3 CKP = 1, CKE = 0  
     SSP1CON1_CKP = 0;  
     SSP1STAT_CKE=0;  
     //Enable SPI  
     SSP1CON1_SSPEN=1;  
 }  
 void master_spi_init(void)  
 {  
     set_tris_d(TRISTATE_D_MASTER);  
     //SPI Setup  
     //Master, clock = FOSC/64  
     //Clear errors  
     SSP1CON1 = 0b00000010;  
     //Sample in middle  
     SSP1STAT_SMP=0;  
     //Mode 0 CKP = 0, CKE = 1  
     //Mode 1 CKP = 0, CKE = 0  
     //Mode 2 CKP = 1, CKE = 1  
     //Mode 3 CKP = 1, CKE = 0  
     SSP1CON1_CKP = 0;  
     SSP1STAT_CKE=0;  
     //Enable SPI  
     SSP1CON1_SSPEN=1;  
 }  
 void set_led(int value)  
 {  
     switch(value){  
         case 1:  
             output_bit(LED_RED,LED_ON);  
             output_bit(LED_GREEN,LED_OFF);  
             break;  
         case 2:  
             output_bit(LED_RED,LED_OFF);  
             output_bit(LED_GREEN,LED_ON);  
             break;  
         case 3:  
             output_bit(LED_RED,LED_ON);  
             output_bit(LED_GREEN,LED_ON);  
             break;  
         default:  
             output_bit(LED_RED,LED_OFF);  
             output_bit(LED_GREEN,LED_OFF);  
             break;  
     }  
 }  
 void slave(void)  
 {  
     slave_spi_init();  
     while(true)  
     {  
         SSP1BUF='X';  
         while(!SSP1STAT_BF){}  
         set_led(SSP1BUF);  
     }  
 }  
 void master(void)  
 {  
     master_spi_init();  
     delay_ms(500);  
     int buf,i;  
     while(true)  
     {  
         for (i=0;i<4;i++)  
         {  
             SSP1BUF=i;  
             set_led(i);  
             while(!SSP1STAT_BF){}  
             buf = SSP1BUF;  
             delay_ms(1000);  
         }  
     }  
 }  




 #ifndef DEVICE_H  
 #define    DEVICE_H  
 //HS External Osc, FRC internal (Fast RC Oscillator)  
 #fuses HS  
 //T1OSC/SOSC Secondary Oscillator, low power circuit selected  
 #fuses SOSC_LOW  
 //Extended Instruction Set  
 #fuses XINST  
 //Stack Overflow / Underflow Reset Enable  
 #fuses STVREN  
 //Brown out detect: Resets the device if the voltage chipped  
 // Useful for when the user switches on/off quickly and the PIC needs resetting  
 #fuses BORV20  
 //Internal External Switch over: IESO,NOIESO  
 // IESO: Two speed start up mode is enabled. Used for power saving  
 #fuses NOIESO  
 //Code Protection: PROTECT, NOPROTECT  
 //    Use NOPROTECT otherwise chipped will be permantly programmed  
 #fuses NOPROTECT  
 //CLK0 Output enabled on RA6  
 #fuses CLOCKOUT  
 //PLL Frequency Multiplier  
 #fuses NOPLL  
 //Primary Oscillator - none  
 #fuses PR  
 //No clock switching, no fail safe clock monitor  
 #fuses NOCKSNOFSM  
 //Write Protect Flash Page  
 #fuses WPFP  
 //Segment Write Diabled  
 #fuses WPDIS  
 //TMR5 Gate is driven by the T5G input  
 #fuses T5G_IS_T5G  
 //C1INA and C3INA are on their default pin locations  
 #fuses CINA_DEFAULT  
 //IOLOCK One-way set enable  
 #fuses IOL1WAY  
 //USB Low speed clock select  
 #fuses LS48MHZ  
 //MSSP2 7-bit address masking mode enable  
 #fuses MSSP2MSK7  
 //MSSP1 7-bit address masking mode enable  
 #fuses MSSPMSK7  
 //Watch dog timer window  
 #fuses WDTWIN_25%  
 //Watch dog timer clock source  
 #fuses WDTCLK_FRC  
 //Watch dog timer postscaler  
 #fuses WDT32768  
 //Watchdog Timer:  
 #fuses NOWDT  
 //Windowed watch dog timer disable  
 #fuses NOWINDIS  
 //Watch dog prescaler  
 #fuses WPRES128  
 //Voltage Regulator Control  
 #fuses NOVREGSLEEP  
 //VBAT BOR Enable  
 #fuses VBATBOR  
 //Deep sleep BOR Enable  
 #fuses NODSBOR  
 //Deep sleep  
 #fuses NODS  
 //In-Circuit Debugger Mode: DEBUG, NODEBUG  
 // DEBUG - In circuit debugging enabled, RB6 ICSPCLK & RB7 ICSPDAT are dedicated to the debugger  
 #fuses NODEBUG  
 #device PASS_STRINGS=IN_RAM  
 #use delay(clock=20MHz)  
 //Write/read to a port directly, the compiler will not set the tristate  
 #use FAST_IO(ALL)  
 #define TRISTATE_E                    0b00000010  
 #define TRISTATE_D_MASTER            0b00010000  
 #define TRISTATE_D_SLAVE            0b01011000  
 #define LED_RED                        PIN_E4  
 #define LED_GREEN                    PIN_E5  
 #define MCU_ID                        PIN_E1  
 #define SPI_SDO1                    PIN_D1  
 #define SPI_SDI1                    PIN_D4  
 #define SPI_SCK1                    PIN_D3  
 #define ID_MASTER                    1  
 #define ID_SLAVE                    0  
 #define LED_ON                        1  
 #define LED_OFF                        0  
 /*  
  *            SPI Configuration  
  * MASTER  
  *        SDO1    Output        PIN_D1        RP21        RPOR20_21=0x40  
  *        SCK1    Output        PIN_D3        RP23        RPOR22_23=0x30  
  *        SDI1    Input        PIN_D4        RP24        RPINR8_9=0x6F  
  *  
  * SLAVE  
  *        SDO1    Output        PIN_D1        RP21        RPOR20_21=0x40  
  *        SCK1    Input        PIN_D3        RP23        RPINR8_9=0x05 !  
  *        SDI1    Input        PIN_D4        RP24        RPINR8_9=0x60 !  
  *  ! write 0x65 to RPINR8_9  
  *  
  */  
 #pin_select SDO1=PIN_D1  
 #pin_select SDI1=PIN_D4  
 #pin_select SCK1OUT=PIN_D3  
 #pin_select SCK1IN=PIN_D3  
 #byte SSP1CON1 = getenv("sfr:SSP1CON1")  
 #byte SSP1STAT = getenv("sfr:SSP1STAT")  
 #byte SSP1BUF = getenv("sfr:SSP1BUF")  
 #bit SSP1STAT_SMP = SSP1STAT.7  
 #bit SSP1STAT_CKE = SSP1STAT.6  
 #bit SSP1STAT_BF = SSP1STAT.0  
 #bit SSP1CON1_WCOL = SSP1CON1.7  
 #bit SSP1CON1_SSPOV = SSP1CON1.6  
 #bit SSP1CON1_SSPEN = SSP1CON1.5  
 #bit SSP1CON1_CKP = SSP1CON1.4  
 #endif    /* DEVICE_H */