Software SPI (PIC18)

Thread Starter

blah2222

Joined May 3, 2010
582
Hi all,

In short, I need to have use of both UART Rx and SPI SDO functionality but unfortunately this device does has both of those located on the same pin with no re-mappable pins.

With one being an input (Rx) from a Bluetooth module and the other being an output (SDO) to a DAC and they would be strictly operated with only one on at a time, I feel as though it would be somewhat clunky to keep it this way.

Since I can't remap the pins, the only option I have, (besides getting a new IC, which isn't an option), is to implement software SPI.

Wondering if anyone has any experience with this and might be able to point me in the right direction to getting this set up.

Thanks,
JP

*EDIT*

Found this code snippet from here:

Rich (BB code):
//Define all SPI Pins
#define SPI_OUT     RC4_bit     /* Define SPI SDO signal to be PIC port RC4 */
#define SPI_IN      RC5_bit     /* Define SPI SDI signal to be PIC port RC5 */
#define SPI_CLK     RC3_bit     /* Define SPI CLK signal to be PIC port RC3 */
#define SPI_CS      RB1_bit     /* Define SPI CS signal to be PIC port RB1 */

/**
 * This function writes a byte out onto the SPI OUT port, and reads a byte from
 * the SPI IN port.
 *
 * @param c Gives the byte to write out onto the SPI port
 *
 * @return Returns the byte read from the SPI IN port
 */
char spiPutGetByte(char c) {
    char ret;
    unsigned char mask;
    
    //SPI Mode 0. CS active low. Clock idle 0. Clock rising edge.
    SPI_CLK = 0;
    
    //Enable SPI communication. The SPI Enable signal must be pulsed low for each byte sent!
    SPI_CS = 0;
    
    //Ensure a minimum delay of 500ns between falling edge of SPI Enable signal
    //and rising edge of SPI Clock!
    Nop();
    mask = 0x80;                //Initialize to write and read bit 7
    ret = 0;                    //Initialize read byte with 0
    
    do  {
        SPI_OUT = 0;                //Clock out current bit onto SPI Out line
        if (c & mask) SPI_OUT = 1;
        SPI_CLK = 1;                //Set SPI Clock line
        if (SPI_IN) ret |= mask;    //Read current bit fromSPI In line
        Nop();                      //Ensure minimum delay of 500nS between SPI Clock high and SPI Clock Low
        SPI_CLK = 0;                //Set SPI Clock line
        mask = mask >> 1;           //Shift mask so that next bit is written and read from SPI lines
        Nop();                      //Ensure minimum delay of 1000ns between bits
    } while (mask != 0);


    //Ensure a minimum delay of 750ns between falling edge of SPI Clock signal
    //and rising edge of SPI Enable!
    Nop();Nop();

    //Disable SPI communication. The SPI Enable signal must be pulsed low for each byte sent!
    SPI_CS = 1;
    
    return ret;
}
Thoughts?
 
Last edited:

Thread Starter

blah2222

Joined May 3, 2010
582
Can you change port configuration during program execution?
I can change the directionality (I/O) of the pin on the mcu but the BT module would be hardwired to the same pin as the DAC is hardwired to.

I'm confident that this wouldn't be an issue with the DAC as it will not do anything unless I specifically toggle the CS pin, but I feel as though this setup might be a tad clunky.
 

JohnInTX

Joined Jun 26, 2012
4,787
Wondering if anyone has any experience with this and might be able to point me in the right direction to getting this set up.
Sure. First, dedicate the USART to the async RX and bit-bang the SPI on other pins.

The code snippet should work but it can be improved:
The SPI DAC is one-way so you can omit reading / shifting / returning a value.
'mask' is used as a bit-selector AND counter. Clever but limits the DAC output to 8 bits unless its bumped to an unsigned int.
The first two lines of the 'do' loop put unnecessary transitions on SDO. A better approach would be:
Rich (BB code):
if (c & mask)
 SPI_OUT = 1;
else
 SPI_OUT = 0;
It doesn't make a difference to the DAC but its less cluttered to observe on a scope, etc.
Take advantage of housekeeping tasks to provide short delays, for example in:
Rich (BB code):
   //Ensure a minimum delay of 500ns between falling edge of SPI Enable signal
    //and rising edge of SPI Clock!
    Nop();
    mask = 0x80;
The Nop(); is likely unnecessary since loading mask takes more time than nop in the first place - it depends ultimately on the PIC speed but something to consider (and document!). EDIT: AND check the compiler output to see how many instructions are generated.

Hope that helps. Good luck!

EDIT: BTW, be sure to specify your 1 bit outputs as LATx not PORTx. Bit inputs are PORTx as always.
 
Last edited:

Thread Starter

blah2222

Joined May 3, 2010
582
Sure. First, dedicate the USART to the async RX and bit-bang the SPI on other pins.

The code snippet should work but it can be improved:
The SPI DAC is one-way so you can omit reading / shifting / returning a value.
'mask' is used as a bit-selector AND counter. Clever but limits the DAC output to 8 bits unless its bumped to an unsigned int.
The first two lines of the 'do' loop put unnecessary transitions on SDO. A better approach would be:
Rich (BB code):
if (c & mask)
 SPI_OUT = 1;
else
 SPI_OUT = 0;
It doesn't make a difference to the DAC but its less cluttered to observe on a scope, etc.
Take advantage of housekeeping tasks to provide short delays, for example in:
Rich (BB code):
   //Ensure a minimum delay of 500ns between falling edge of SPI Enable signal
    //and rising edge of SPI Clock!
    Nop();
    mask = 0x80;
The Nop(); is likely unnecessary since loading mask takes more time than nop in the first place - it depends ultimately on the PIC speed but something to consider (and document!). EDIT: AND check the compiler output to see how many instructions are generated.

Hope that helps. Good luck!

EDIT: BTW, be sure to specify your 1 bit outputs as LATx not PORTx. Bit inputs are PORTx as always.
Thank you!
 
Top