ac. How to get PIC18 SPI interface working?

There are two sets of three functions that enable initialization of the microcontroller SPI interface, and single read and write access to HopeRF RFM69CW wireless communication module registers, where the address is 7-bit RFM69CW internal register address.

The initialization functions set HW SPI operation and the appropriate SPI operating mode, or or SW SPI operation. Get PIC18F26J50 - SPI example (x64) from Downloads section for more information.

There are nine functions: six functions for HW SPI1 implementation:

Function SPI1Init() as Integer

Function SPI1ReadReg(addr as Byte) as Integer

Function SPI1WriteReg(addr as Byte, dta as Byte) as Integer

Function SPI1Write(dta as UInt16) as Integer

Function SPI1BurstReadReg(addr as Byte, size as Byte) as Integer

Function SPI1BurstWriteReg(addr as Byte, size as Byte) as Integer

... and three functions preceeded by SW (software implementation of SPI1 for testing). Look at PC USB Projects PROGRAMMING GUIDE for detailed documentation.

Additional control pins

A device with SPI interface may also have additional control pins besides SPI interface pins. There are two such pins on HopeRF RFM69CW wireless communication module: Reset and NSS. Reset signal is used to restart RFM69CW without the need to power it off and on. NSS pin not used only as a chip select signal, but it also determines the data access mode. There are three access modes: Single, Burst and FIFO (first in first out buffer). Please see the module information sheet for details (look for RFM69CW-V1.1.pdf document with a web search engine, ex. www.Google.com) .

Make sure that all additional pins operate as normal port outputs, so they are not used by other PIC18 modules.

Data transfer

RFM69CW module uses SPI interface to enable serial access to its internal registers and FIFO. NSS pin is used as an additional signal to set data transfer mode for each transfer. NSS must be set to zero before each SPI access to enable SPI interface on RFM69CW. Next, the address byte is transferred, followed immediately by the data byte. RFM69CW does not support date exchange, so ti dummy read is performed during each write operation and a dummy write is required during read operation.

Software SPI interface

Software (SW) SPI interface is also supported by PIC18F26J50 firmware v2.6.4. It disables MSSP1 module and sets RB5 (SDI), PB4 (SCK) and RC7 (SDO) as normal port pins. All the SW SPI functions mimic the operation of HW SPI functions with “FOR” loops in the firmware subroutines.

SPI functions in PIC18F26J50 firmware v2.6.5 or later and LIB_PCUSBProjects v4.7.NET4x64.dll or later

Finally, don’t let the microcontroller’s A/D converter module to "spoil your day"! Though it might be logical to some, others may forget that PIC18F2xJ50's A/D converter may use all pins with ANx marks (see Microchip datasheet for PIC18F46J50 microcontroller family: DS39931D, pages: 5 and 349). ANCON0 and ANCON1 registers determine ANx pins operation. All the pins that are used by MSSPx should be programmed as digital input/outputs. This is achieved by setting the appropriate bits to 1.

PIC18F2xJ50 microcontrollers have two MSSP (Master Synchronous Serial Port) interfaces (also referred as MSSPx): MSSP1 and MSSP2. MSSP1 can operate in either SPI or I2C mode, but MSPP2 only supports SPI mode due to lack of PORTD. Only a PIC18F4xJ50 microcontroller supports SPI and I2C modes on both MSSPs.

SPI is a three wire interface: SDO, SDI and SCK. The data is simultaneously transmitted and received on a master and a slave device. A special SSPxBUF register is used as a gateway to read and write bytes to SSPxSR shift register. When a byte of data on the master device is written to SSPxBUF register, it is copied to SSPxSR shift register and automatically transmitted to the slave device. As each bit is shifted out to the slave device a bit from the slave device is shifted into the SSPxSR register. After 8 shift operations the contents of the master SSPxSR register and the slave SSPxSR are exchanged.

PIC18 supports double buffering for data reception. Reading the register during the shift operation will return an 8-bit content being transmitted, but upon completion of the transfer then received value would be returned. Note: Only a write operation to SSPxBUF register will trigger data exchange between the master and the slave device. Even if the master device only needs to read one byte of data from the slave device, a dummy write operation (write of 0 to SSPxBUF register) must precede a read from SSPxBUF.

Setting up MSSPs

Getting the hardware SPI interface working on a PIC18 microcontroller may be quite tricky. The interface will only work, if the configuration is right. PIC18F2xJ50 microcontrollers use bits SPI1OD (0) and SPI2OD (1) in ODCON3 (Open Drain Control 3) register to enable open drain operation in SDO and SCK lines. Pull-up resistors may be used to achieve a higher voltage in SPI bus that is used for PIC18F2xJ50 power supply. This feature is rarely used. Though it is important to set SPI1OD and SPI2OD bit to 0, or no signal will be produced in SCK and SDO lines. This is the most common mistake made by beginners.

The next task is to properly set the PPS (Peripheral Pin Select) module. Though MSSP1 module has fixed connections to the PIC18F2xJ50 pins when enabled, its operation may still be obstructed by other remappable modules, like ECCP (Enhanced Capture, Compare and Pulse width modulation) module. Setting the values of RPOR8 and RPOR7, RPOR18 to zero will assure that the PORTB and PORTC pins used as SCK1, SDO1 and SDI1 would be controlled by MSSP1 module.

MSSP2 module has remappable SCK2, SDO2 outputs and an SDI2 input. PPS must be set appropriately to map MSSP2 to PIC18F2xJ50 outputs.

The next consideration is appropriate setup of port B and port C. Each of them has three control registers: PORTx, LATx and TRISx. The first contains the current output and input values. Latency register (LATx) is used when a corresponding bit in TRISx register is set to 0. If this causes the bit to turn in an output, an appropriate bit value from LATx register is used as a default value in PORTx register. So, first set the appropriate default value in LATx and then set TRISx register. For MSSP1 to operate properly, RB5 must be setup as an input, and RB4 and RC7 must be set as outputs. A similar setup is required for MSSP2.

SPI examples with simple read and write commands:

HW or SW SPI interface can also be implemented without the special firmware functionality:

SW SPI Initialzation

PIC.ClearDataMemBit(SSP1CON1, 5) ' disable ssp1

PIC.SetDataMemBit(ODCON3, 0) ' SPI1 Open drain OFF

PIC.DataMemWrite(CCP1CON, 0) ' Disable ECCP1

PIC.DataMemWrite(RPOR13, 0)   ' PPS RPOR13 = 0 --- RP13 = normal port, not ECCP1 port

PIC.DataMemWrite(LATB, PIC.DataMemRead(LATB) And &HCF)  ' LATB = LATB AND 0xCF --> RB5 = 0, RB4 = 0

PIC.DataMemWrite(LATC, (PIC.DataMemRead(LATC) And &H7F) Or &H44) ' LATC = (LATC AND 0x7F) OR 0x44 --> RC7=0, RC6=1, RC2=1

PIC.DataMemWrite(TRISB, (PIC.DataMemRead(TRISB) And &HEF) Or &H20)  ' TRISB  --> RB4 = SCK = 0 , RB5 = SDI = 1

PIC.DataMemWrite(TRISC, PIC.DataMemRead(TRISC) And &H3B)  ' TRISC --> RC7 = SDO = 0, RC6 = RESET = 0, RC2 = NSS = 0

PIC.SetDataMemBit(PORTC, 2)   ' NSS = 1

PIC.SetDataMemBit(PORTC, 6)   ' Reset = 1

Delay(100) ' delay in ms

PIC.ClearDataMemBit(PORTB, 4) ' SCK = 0

PIC.ClearDataMemBit(PORTC, 7) ' SDO = 0

addr = CByte(TbAddress.Text)

PIC.ClearDataMemBit(PORTC, 6)   ' Reset = 0

Delay(100) ' delay in ms

HW SPI Initialzation

... Use the same code as for SW SPI and add the following:

PIC.DataMemWrite(SSP1STAT,&h40)

PIC.DataMemWrite(SPP1CON,&h22)

Read from a RFM69CW register

Function SWSPI1ReadReg(addr as Byte)

 Dim n as Byte

 Dim m as Byte

 Dim tr as Byte=addr

 PIC.ClearDataMemBit(PORTC,2)

 m=&h80

 for n=0 to 7   ' WRITE BYTE TO SPI

  if(tr&m)>0 then

     PIC.SetDataMemBit(PORTC,7)

  else 

     PIC.ClearDataMemBit(PORTC,7)

  endif

  PIC.SetDataMemBit(PORTB,4)

  PIC.ClearDataMemBit(PORTB,4)

  m>>=1

 }

 m=&h80

 tr=0

 for n=0 to 7  ' READ BYTE FROM SPI

  PIC.SetDataMemBit(PORTB,4)

  if (PORTB&0x20)>0 then

   tr=tr or m

  end if  

  PIC.ClearDataMemBit(PORTB,4)

  m>>=1;

 }

 PIC.SetDataMemBit(PORTC,2) ' NSS=1

 return tr

}

Write to a RFM69CW register

Function SWSPI1ReadReg(addr as Byte, dta as Byte)

 Dim n as Byte

 Dim m as Byte

 Dim tr as Byte=addr or &h80

 PIC.ClearDataMemBit(PORTC,2)

 m=&h80

 for n=0 to 7   ' WRITE BYTE TO SPI

  if(tr&m)>0 then

     PIC.SetDataMemBit(PORTC,7)

  else 

     PIC.ClearDataMemBit(PORTC,7)

  endif

  PIC.SetDataMemBit(PORTB,4)

  PIC.ClearDataMemBit(PORTB,4)

  m>>=1

 }

 tr=dta

 m=&h80

 for n=0 to 7   ' WRITE BYTE TO SPI

  if(tr&m)>0 then

     PIC.SetDataMemBit(PORTC,7)

  else 

     PIC.ClearDataMemBit(PORTC,7)

  endif

  PIC.SetDataMemBit(PORTB,4)

  PIC.ClearDataMemBit(PORTB,4)

  m>>=1

 }

}

CONSTANTS (for PIC18F2xJ50 microcontrollers):

' ECCPx

Const CCP1CON As UInt16 = &HFBA ' CCP1 PIC18FxxJxx

Const CCPR1L As UInt16 = &HFBB

Const CCPR1H As UInt16 = &HFBC

Const CCP2CON As UInt16 = &HFB4 ' CCP2 PIC18FxxJxx

Const CCPR2L As UInt16 = &HFB5

Const CCPR2H As UInt16 = &HFB6

' Ports

Const TRISA As UInt16 = &HF92

Const TRISB As UInt16 = &HF93

Const TRISC As UInt16 = &HF94

Const LATA As UInt16 = &HF89

Const LATB As UInt16 = &HF8A

Const LATC As UInt16 = &HF8B

Const PORTA As UInt16 = &HF80

Const PORTB As UInt16 = &HF81

Const PORTC As UInt16 = &HF82

' Open drain control

Const ODCON3 As UInt16 = &HF40

' PPS

Const RPOR13 As UInt16 = &HED3

' SSP

Const SSP1CON1 As UInt16 = &HFC6

Const SSP1CON2 As UInt16 = &HFC5   ' I2C only

Const SSP1STAT As UInt16 = &HFC7

Const SSP1BUF As UInt16 = &HFC9

' Interrupt

Const PIR1 As UInt16 = &HF9E