• AVR Freaks

Hot!What are good practices when using SPI?

Page: 123 > Showing page 1 of 3
Author
weetabixharry
Starting Member
  • Total Posts : 28
  • Reward points : 0
  • Joined: 2018/02/12 12:52:29
  • Location: 0
  • Status: offline
2018/12/18 22:34:48 (permalink)
0

What are good practices when using SPI?

I am experimenting with SPI comms for the first time (between two PIC16LF88s). I'm a bit confused about how SPI is intended to be used (or, what good practices are to get it to work well). I've read the appropriate sections in the PIC16LF88 datasheet, but it's not very enlightening.
 
For my tests, I have the following setup. The master sends a repeating sequence of 15 bytes ("123456789ABCDEF"). The slave receives the bytes (by polling the SPI interface, transmitting dummy 0xFF) and displays a number 1-15 in binary on 4 status LEDs, depending on where in the sequence it is. If the slave receives anything except the next byte in the sequence, it turns off all the LEDs and pauses briefly to indicate error.
 
What I am finding is that - if I just let the master transmit freely (i.e. as fast as possible) - then the slave repeatedly misses bytes. So, I kludged in a delay of 500us (4000 clock cycles @8MHz) and then the slave never misses a byte. Obviously, this workaround is stupid, but I'm not sure what the correct method is to get the SPI interface working both quickly and reliably.
 
Is there some status register I should be using? Should I be using interrupts (I haven't looked at interrupts before)?
 
For reference, I have attached my test code. It is not intended to be highly performant. And I don't really lay my code out that way - the indentation has been trashed by the forum's interface. And I probably need to change some char*s to unsigned char*s. But hopefully it illustrates what I'm trying to say here.
 
(1 of 3: spi.h)
#ifndef SPI_H
#define SPI_H
 
#include <xc.h>
#include <stdint.h>
 
void set_sspm(uint8_t x)
{
SSPCONbits.SSPM3 = (x >> 3) & 0x1;
SSPCONbits.SSPM2 = (x >> 2) & 0x1;
SSPCONbits.SSPM1 = (x >> 1) & 0x1;
SSPCONbits.SSPM0 = (x >> 0) & 0x1;
}
 
void spi_init(uint8_t is_master, uint8_t use_slave_select)
{
// -------------------
// -- Configure I/O --
// -------------------
TRISB1 = 1; // SDI (pin 7) is input
TRISB2 = 0; // SDO (pin 8) is output
if (is_master)
{
TRISB4 = 0; // Master: SCK (pin 10) is output
if (use_slave_select)
TRISB5 = 0; // Master: Use RB5 (pin 11) as output for one !SS
}
else
{
TRISB4 = 1; // Slave: SCK (pin 10) is input
if (use_slave_select)
TRISB5 = 1; // Slave: !SS (pin 11) is input
}

// ----------------------------
// -- Configure SSPSTAT<7:6> --
// ----------------------------
if (is_master)
{
// SSPSTAT<7> = SMP. Use default in master mode?
}
else
{
SSPSTATbits.SMP = 0; // SSPSTAT<7> = SMP. Must be cleared in slave mode.
}
// SSPSTAT<6> = CKE. Use default?

// ---------------------------
// -- Configure SSPCON<5:0> --
// ---------------------------

// SSPCON<4> = CKP. Use default?

// SSPCON<3:0> = SSPM<3:0>
if (is_master)
{
//set_sspm(0b0000); // clock = OSC/4
//set_sspm(0b0001); // clock = OSC/16
set_sspm(0b0010); // clock = OSC/64
//set_sspm(0b0011); // clock = TMR2 output/2
}
else
{
if (use_slave_select)
set_sspm(0b0100);
else
set_sspm(0b0101);
}

SSPCONbits.SSPEN = 1; // SSPCON<5> = SSPEN. Enable Synchronous Serial Port
}
 
// This function was copied from MCC-generated code
uint8_t spi_transceive_byte(uint8_t data)
{
// Clear the Write Collision flag, to allow writing
SSPCONbits.WCOL = 0;

// Load TX data into shift register
SSPBUF = data;
 
while(SSPSTATbits.BF == 0); // Wait until RX data shifts in

// Always read the RX shift register to prevent overflow
return SSPBUF;
}
 
#endif /* SPI_H */

 
(2 of 3: master_main.c)
// CONFIG1
#pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = ON // Low-Voltage Programming Enable bit (RB3/PGM pin has PGM function, Low-Voltage Programming enabled)
#pragma config CPD = OFF // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB0 // CCP1 Pin Selection bit (CCP1 function on RB0)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
 
// CONFIG2
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor enabled)
#pragma config IESO = ON // Internal External Switchover bit (Internal External Switchover mode enabled)
 
#include <xc.h>
#include <string.h>
#define _XTAL_FREQ 8000000
 
#include "pic16lf88/spi.h"
 
const char* SECRET_PASSWORD = "123456789ABCDEF";
 
void main(void)
{
// Configure internal RC oscillator 8 MHz.
OSCCON = 0b01110010;

// Set ANSEL for analogue(1) or digital(0) I/O
ANSEL = 0b0000000;

// Set RA2 as input for switch
TRISAbits.TRISA2 = 1;

// Set RA0 as output for LED
TRISAbits.TRISA0 = 0;

// Power LED
RA0 = 1;

// Configure SPI as master
spi_init(1, 0);

while (1)
{
// Send password to slave
for (uint8_t i=0; i<strlen(SECRET_PASSWORD); i++)
{
// Send next byte of password
spi_transceive_byte(SECRET_PASSWORD[i]);
__delay_us(500); // Removing this delay causes slave to miss bytes.

// Use switch to do something a bit different
if (RA2)
{
spi_transceive_byte(0x00);
__delay_ms(500);
}
}
}

}

 
(3 of 3: slave_main.c)
// CONFIG1
#pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = ON // Low-Voltage Programming Enable bit (RB3/PGM pin has PGM function, Low-Voltage Programming enabled)
#pragma config CPD = OFF // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB0 // CCP1 Pin Selection bit (CCP1 function on RB0)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
 
// CONFIG2
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor enabled)
#pragma config IESO = ON // Internal External Switchover bit (Internal External Switchover mode enabled)
 
#include <xc.h>
#include <string.h>
#define _XTAL_FREQ 8000000
 
#include "pic16lf88/spi.h"
 
const char* SECRET_PASSWORD = "123456789ABCDEF";
 
void display(uint8_t x)
{
// Display 4-bit number on LEDs
RA2 = (x >> 0) & 0x1;
RA3 = (x >> 1) & 0x1;
RA4 = (x >> 2) & 0x1;
RB0 = (x >> 3) & 0x1;
}
 
void main(void)
{
// Configure internal RC oscillator 8 MHz.
OSCCON = 0b01110010;

// Set ANSEL for analogue(1) or digital(0) I/O
ANSEL = 0b0000000;

// Set digital outputs for status LEDs
TRISAbits.TRISA0 = 0;
TRISAbits.TRISA2 = 0;
TRISAbits.TRISA3 = 0;
TRISAbits.TRISA4 = 0;
TRISBbits.TRISB0 = 0;

// Configure SPI as slave
spi_init(0, 0);

display(0); // Turn 4-bit number display off.
uint8_t init = 0;
uint8_t state = 0;
while (1)
{
// Turn on the "everything is okay" LED.
RA0 = 1;

// Receive a byte from master. We have to send something, so send 0xFF.
uint8_t rx = spi_transceive_byte(0xFF);
 
// Sync up with wherever the master is in the password
if (init == 0)
{
for (size_t k=0; k<8; k++)
if (rx == SECRET_PASSWORD[k])
{
state = k;
init = 1;
}
}
else
{
uint8_t next_state = (state+1) % strlen(SECRET_PASSWORD);

if (rx == SECRET_PASSWORD[next_state])
{
// Success! Received the expected character. Proceed to next.
state = next_state;
display(1+state); // +1 so at least 1 LED is lit.
}
else
{
// Error! Unexpected character received. Turn off all the lights
// to signal error and wait a while so we see it.
display(0);
RA0 = 0;
__delay_ms(100);
}
}
}
}

post edited by weetabixharry - 2018/12/18 22:39:31
#1

46 Replies Related Threads

    qhb
    Superb Member
    • Total Posts : 9998
    • Reward points : 0
    • Joined: 2016/06/05 14:55:32
    • Location: One step ahead...
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/18 22:57:04 (permalink)
    +1 (1)
    Just a little pointer. Code like this

    void set_sspm(uint8_t x)
    {
    SSPCONbits.SSPM3 = (x >> 3) & 0x1;
    SSPCONbits.SSPM2 = (x >> 2) & 0x1;
    SSPCONbits.SSPM1 = (x >> 1) & 0x1;
    SSPCONbits.SSPM0 = (x >> 0) & 0x1;
    }


    can simply be written as
    void set_sspm(uint8_t x)
    {
     SSPCONbits.SSPM = x;
    }





    Nearly there...
    #2
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/18 23:18:35 (permalink)
    0
    Thanks. It only ended up that way because I wasn't certain and was erring on the side of caution. Thanks for clearing that up.
    #3
    Mysil
    Super Member
    • Total Posts : 3324
    • Reward points : 0
    • Joined: 2012/07/01 04:19:50
    • Location: Norway
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/18 23:50:03 (permalink)
    +1 (1)
    Hi,
    SPI isn't a communications protocol, it is a hardware shift register concept.
     
    Master transmitting as fast as hardware allow, may work OK when slave is a hardware shift register,
    but in your case, slave is a microcontroller running a sequence of instructions.
    SPI itself do not have a feedback signal from slave to master,
    so if slave is not ready for the next byte when master start the transfer,
    there will be a corrupted transfer.
     
    There may be several different possibilities to handle this:
    A:
    Analyze and measure the slave code, such that you know the response time before slave is ready for the next character, and adjust timing in the Master code accordingly.
    If you have access to a oscilloscope or logic analyzer of some kind, toggling a port pin at some points in program loop, may be a useful diagnostic tool.
     
    B:
    Introduce a separate synchronization signal from slave to master on another wire: Ready / Busy
    and make master wait until ready signal is asserted before next transfer is started.
     
    C:
    Rearrange slave code such that slave spi hardware is always ready for the next transfer:
    Instead of spi_transceive_byte(...); sending one byte, and waiting for transfer to complete:
    When a byte have been received, Immediately set up the next transfer by writing a dummy value into SSPBUF, 
    before returning the data byte to be processed.
     
        Mysil
     
    #4
    crosland
    Super Member
    • Total Posts : 1580
    • Reward points : 0
    • Joined: 2005/05/10 10:55:05
    • Location: Bucks, UK
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 01:43:39 (permalink)
    0
    Mysil
    SPI isn't a communications protocol, it is a hardware shift register concept.
     
    Master transmitting as fast as hardware allow, may work OK when slave is a hardware shift register,
    but in your case, slave is a microcontroller running a sequence of instructions.

     
    Really?
     
    The code seems to be writing to a hardware SPI peripheral, a hardware shift register in other words. The OPs PIC certainly has SPI hardware according to the data sheet.
     
    I know what you mean, but it's not clear from your post :)
    #5
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 04:55:06 (permalink)
    0
    This was one of the things that was confusing me. My understanding was that the hardware shift register is as fast as it needs to be, and my code just needs to execute fast enough to keep up. As I understood it, I had configured the SPI clock to run 64x slower than the main system clock, so if the slave could get to the wait-for-rx loop at least every 64 cycles, then all would be okay. However, what I seem to be seeing is that the master must back off for some thousands of cycles. Either my slave code is much slower than I thought, or I am misunderstanding something here.
    #6
    qhb
    Superb Member
    • Total Posts : 9998
    • Reward points : 0
    • Joined: 2016/06/05 14:55:32
    • Location: One step ahead...
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 05:01:01 (permalink)
    +1 (1)
    Your slave code is spending 99% of its time in the loop waiting for the next character.
    Try writing the dummy 0xFF character straight after receiving a byte, rather than when you're ready for the next one.
     

    Nearly there...
    #7
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 05:04:15 (permalink)
    0
    MysilThere may be several different possibilities to handle this:

    I was trying to avoid a solution like "A" for now. I don't think my understanding is strong enough to get it right. Maybe after I have tried "B" and/or "C".

    I was considering "B" as a workaround until I could find the proper dedicated flag. If there isn't a dedicated flag, then this seems like a good thing to try first. However, it still seems slow to me. I thought there might be some non-blocking solution.

    I think "C" is what I really wanted to do, but didn't know how. I'll sit down and have another go this evening, given your suggestions.
    #8
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 05:24:44 (permalink)
    0
    qhb
    Your slave code is spending 99% of its time in the loop waiting for the next character.Try writing the dummy 0xFF character straight after receiving a byte, rather than when you're ready for the next one. 

    This sounds good. I'll look at this this evening.
    #9
    NKurzman
    A Guy on the Net
    • Total Posts : 17385
    • Reward points : 0
    • Joined: 2008/01/16 19:33:48
    • Location: 0
    • Status: online
    Re: What are good practices when using SPI? 2018/12/19 06:49:29 (permalink)
    +1 (1)
    The master sends and receives a byte at the same time. The byte the slave is sending is for the next transaction, not the current one.
    The slave is not sending a byte. It is putting it in the buffer for the master to read. That means you need a byte in the buffer for the masters first read.
    #10
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 11:37:43 (permalink)
    +1 (1)
    Given the suggestions above, it seems like I shouldn't use the exact same spi_transceive_byte() function for both master and slave. I'm not sure whether I should also have different functions for transmitting and receiving (i.e. 4 functions in total: master_tx(), master_rx(), slave_tx(), slave_rx()). I've tried a few permutations, but I'm not really sure where I'm going with it.
     
    Is it always the desired behaviour that the slave should always transmit (either dummy or useful data) immediately after receiving? I can't seem to find this written anywhere, so I'm not sure if it's a general rule.
     
    Unable to find any source code online, I thought I would see what MCC would generate when configured as a slave. It gives the same transceive function as for the master, except with this line added at the start:
        // Clear the buffer full flag
        uint8_t dummyRead = SSPBUF;

    This seems weird to me. The dummyRead value just gets discarded, but it's not clear to me how we know it doesn't contain useful data. Maybe I should or shouldn't pay much attention to what MCC generates, but either way I can't figure out what I should be writing and can't find any other source code anywhere online.
    #11
    mbrowning
    Just a Member
    • Total Posts : 1369
    • Reward points : 0
    • Joined: 2005/03/16 14:32:56
    • Location: Melbourne, FL
    • Status: online
    Re: What are good practices when using SPI? 2018/12/19 12:20:18 (permalink)
    +1 (1)
    SPI is very simple hardware designed originally for control of very simple peripherals. There are no real standards on how it is used. Each device defines the interface in it's datasheet and you design your software to work with that interface.
     
    If you are designing both sides of a SPI bus then you are free to design it however it best suits your needs.
     
    My company is in this situation where we use SPI for a central processor (Keystone or Zynq) to control FPGAs and PICs. Commonly, a SPI transaction consists of 6 bytes:
    - byte 1 is a command byte that contains a R/W bit and 7 register address bits. No data (0x00) is returned
    - byte 2 is a dummy to give the slave time to process byte 1. No data (0x00) is returned.
    - bytes 3-6 are 32 bit bidirectional data. For write transactions, the 32bit transmit data is put in the register at the command address, and the slave returns the command plus 24bit fixed code (to verify the interface). For reads, the transmit data is dummy 0's and the receive data is from the addressed register.
     
    The FPGAs run this interface at 25-30MHz on the SPI clock. The 18lf56k42 is limited to 7MHz because of the processing time during byte 2 plus interrupt latency.

    Oh well - there's always next year
    #12
    NKurzman
    A Guy on the Net
    • Total Posts : 17385
    • Reward points : 0
    • Joined: 2008/01/16 19:33:48
    • Location: 0
    • Status: online
    Re: What are good practices when using SPI? 2018/12/19 12:25:38 (permalink)
    +1 (1)
    "Is it always the desired behaviour that the slave should always transmit (either dummy or useful data) immediately after receiving? I"
    NO it does not work like that.  The Data is exchanged.  The slave does NOT Transmit the data.  The Master exchanges bytes with the slave.  It Transmits and Receives with the same clock.  one bit out and one bit in, until the byte is finished. 
    #13
    qhb
    Superb Member
    • Total Posts : 9998
    • Reward points : 0
    • Joined: 2016/06/05 14:55:32
    • Location: One step ahead...
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 12:28:10 (permalink)
    +1 (1)
    I think "slave always transmit" should be re-worded to "slave always load its data buffer".
    As has already been mentioned in this topic, the slave MUST have loaded data to SSPBUF before the Master initiates the transfer.

    Nearly there...
    #14
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 14:46:54 (permalink)
    0
    Okay, thanks for the comments. I'm not getting anywhere and it's late, so I'll have to try again tomorrow evening. Maybe something will make sense with a fresh head.
    #15
    crosland
    Super Member
    • Total Posts : 1580
    • Reward points : 0
    • Joined: 2005/05/10 10:55:05
    • Location: Bucks, UK
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/19 14:47:51 (permalink)
    +1 (1)
    weetabixharry
    Is it always the desired behaviour that the slave should always transmit (either dummy or useful data) immediately after receiving? I can't seem to find this written anywhere, so I'm not sure if it's a general rule.
     

     
    The slave data is transmitted (sent) simultaneously with reception. It's the same shift register. As data from the master is shifted in, data from the slave is shifted out. ALL data exchange is controlled by the master. The block diagram pf the SPI peripheral sopuld make this clear.
     
    Sometimes the data from the slave will be ignored. Sometimes data from the master will be ignored (master needs to send dummy bytes to get more data out of the slave. It's up to you to design the protocol, including any delays needed for the slave to react.
     
     
     
    #16
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/20 12:22:13 (permalink)
    0
    Maybe I should just back-track all the way to the first thing I tried yesterday, as I still don't understand why it doesn't work. As suggested above, I tried writing the slave data into the buffer immediately after reading the data received from the master. So I created a new spi_slave_transceive() function like this:
     
     
    uint8_t spi_slave_transceive(uint8_t data)
    {
        // Clear the Write Collision flag, to allow writing
        SSPCONbits.WCOL = 0;
        
        while(SSPSTATbits.BF == 0); // Wait until RX data shifts in
        
        // Read the received byte
        uint8_t rx = SSPBUF;
        
        // Immediately load TX data into shift register so the master can transmit
        // again immediately.
        SSPBUF = data;

        return rx;
    }

     
    And this was used only on the slave device. The master continued using the same spi_transceive() function as before.
     
    However, using this, I might at best see a very occasional flicker on the LEDs (meaning the slave spends most of its time seeing unexpected bytes and turning off the LEDs). I poked around with the code for some hours, but never really made any progress.
    #17
    JorgeF
    Super Member
    • Total Posts : 3340
    • Reward points : 0
    • Joined: 2011/07/09 11:56:58
    • Location: PT/EU @ Third rock from the Sun
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/20 15:49:39 (permalink)
    +1 (1)
    Hi
    weetabixharry
    However, using this, I might at best see a very occasional flicker on the LEDs (meaning the slave spends most of its time seeing unexpected bytes and turning off the LEDs). I poked around with the code for some hours, but never really made any progress.

    Beware that, if the master is pooling the slave at free run speed the "flicker" may be too fast to be seen with a naked eye.

    Best regards
    Jorge
     
    I'm here http://picforum.ric323.com too!
    And it works better....
    #18
    NKurzman
    A Guy on the Net
    • Total Posts : 17385
    • Reward points : 0
    • Joined: 2008/01/16 19:33:48
    • Location: 0
    • Status: online
    Re: What are good practices when using SPI? 2018/12/20 16:32:47 (permalink)
    +1 (1)
    “I tried writing the slave data into the buffer immediately after reading the data received from the master.”
    The data needs to be there before you RX data from the master. If you don’t you can get a buffer overrun error. The byte you are writing is for the next transaction.
    #19
    weetabixharry
    Starting Member
    • Total Posts : 28
    • Reward points : 0
    • Joined: 2018/02/12 12:52:29
    • Location: 0
    • Status: offline
    Re: What are good practices when using SPI? 2018/12/20 23:20:27 (permalink)
    0
    "Beware that, if the master is pooling the slave at free run speed the "flicker" may be too fast to be seen with a naked eye."
    When I say flicker, I mean off almost all the time, then flickers on and off again. The way the code is written, if it's all running quickly, the LEDs appear lit all the time (even though they're switching on and off).
    post edited by weetabixharry - 2018/12/20 23:21:39
    #20
    Page: 123 > Showing page 1 of 3
    Jump to:
    © 2019 APG vNext Commercial Version 4.5