Compatibility of USI Three-wire modes with SPI modes

1 post / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

[edit: I completely revised the original post and title to clarify, and only show subsequent test results and associated code]

 

I tested an AVR Universal Serial Interface (USI) Three-wire slave in each external clock mode with a real AVR SPI master in SPI modes 0, 1, 2, and 3 to discover which mode combinations work or not. My results with an ATtiny84A (@20MHz) USI slave and ATmega328P (@16MHz) SPI master indicate that the USI Three-wire slave modes, USICS1:USICS0=1:1 and 1:0, are compatible with only SPI master mode 1 (CPOL=0, CPHA=1) and mode 3 (CPOL=1, CPHA=1), respectively.

 

I hope this helps someone working with the USI Three-wire mode together with SPI.

 

Here is the code for the master and slave devices that I used for the test:

 

Master (ATmega328P) SPI/USI test code:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

// Test data transfer exerciser using SPI in Master mode
#define SPI_CPOL_CPHA   1       // clock polarity and phase, [CPOL:CPHA] = 0, 1, 2, or 3

// Hardware abstraction for bit access:
struct bits
{
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(portLetter, bitNumber, registerName) ((*(volatile struct bits*)&registerName##portLetter).b##bitNumber)
// portLetter = A-D, bitNumber = 0-7, registerName = "PORT", "PIN", or "DDR"
#define INPUT   0   // DDR bit = input
#define OUTPUT  1   // DDR bit = output

// ATmega328P pin assignments
#define SPI_SS_(r)      BIT(B, 2, r)    // SPI slave select -- output to slave
#define SPI_MOSI(r)     BIT(B, 3, r)    // SPI MOSI -- output to slave
#define SPI_MISO(r)     BIT(B, 4, r)    // SPI MISO -- input from slave
#define SPI_SCK(r)      BIT(B, 5, r)    // SPI SCK -- output to slave
#define ERROR_LED_(r)   BIT(D, 1, r)    // transfer integrity error indicator (note: active-low)

int main(void)
{
    const double WAIT_SLAVE_SELECT      = 1.5e-6;   // wait time (seconds) for slave to prepare for transfer after select
    const double WAIT_SLAVE_DESELECT    = 1.0e-6;   // wait time (seconds) for slave to process Tx data after deselect

    uint8_t txData;
    uint8_t rxData;

    cli();

    SPI_MISO(DDR)   = INPUT;
    SPI_SS_(DDR)    = OUTPUT;
    SPI_MOSI(DDR)   = OUTPUT;
    SPI_SCK(DDR)    = OUTPUT;
    ERROR_LED_(DDR) = OUTPUT;

    SPCR    = (0<<SPIE) | (1<<SPE) | (0<<DORD) | (1<<MSTR);     // master, MSB-first

#if SPI_CPOL_CPHA == 0
    SPCR    |= (0<<CPOL) | (0<<CPHA);   // mode 0:  SCK normally  low; read MISO on SCK rise (lead);  write MOSI on SCK fall (trail) or half SCK cycle before 1st rise (lead); MOSI normally high (regardless of last bit)
#elif SPI_CPOL_CPHA == 2
    SPCR    |= (1<<CPOL) | (0<<CPHA);   // mode 2:  SCK normally high; read MISO on SCK fall (lead);  write MOSI on SCK rise (trail) or half SCK cycle before 1st fall (lead); MOSI normally high (regardless of last bit)
#elif SPI_CPOL_CPHA == 1
    SPCR    |= (0<<CPOL) | (1<<CPHA);   // mode 1:  SCK normally  low; read MISO on SCK fall (trail); write MOSI on SCK rise (lead); MOSI normally same as last bit
#elif SPI_CPOL_CPHA == 3
    SPCR    |= (1<<CPOL) | (1<<CPHA);   // mode 3:  SCK normally high; read MISO on SCK rise (trail); write MOSI on SCK fall (lead); MOSI normally same as last bit
#endif

    //SPCR  |= 0<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/2
    SPCR    |= 0<<SPR0;                     // Fsck = Fclk/4
    //SPCR  |= 1<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/8
    //SPCR  |= 1<<SPR0;                     // Fsck = Fclk/16
    //SPCR  |= 2<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/32
    //SPCR  |= 2<<SPR0;                     // Fsck = Fclk/64
    //SPCR  |= 3<<SPR0; SPSR = 1<<SPI2X;    // Fsck = Fclk/64
    //SPCR  |= 3<<SPR0;                     // Fsck = Fclk/128

    ERROR_LED_(PORT) = 1;                   // (note: active-low error indicator)
    SPI_SS_(PORT) = 1;                      // deselect slave
    txData = 1;

    while ( 1 )
    {
        SPI_SS_(PORT) = 0;                          // select slave
        _delay_us( WAIT_SLAVE_SELECT * 1.0e6 );     // wait for slave to prepare for transfer
        SPDR = txData;                              // start transfer with Tx data
        while ( (SPSR & (1<<SPIF)) == 0 );          // wait for transfer to finish
        rxData = SPDR;                              // get Rx data
        SPI_SS_(PORT) = 1;                          // deselect slave
        _delay_us( WAIT_SLAVE_DESELECT * 1.0e6 );   // wait for slave to process Tx data

        if ( rxData != (uint8_t) ~(txData - 1) )    // check Rx data integrity
        {
            ERROR_LED_(PORT) = 0;
            _delay_ms( 100 );
            ERROR_LED_(PORT) = 1;
        }

        txData++;
    }
}

 

Slave (ATtiny84A) SPI/USI test code:

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>

// Echo received test data using slave USI in Three-wire mode, external clock
#define USICS0_VALUE    1       // clock source edge select (0 or 1): 0=="positive edge", 1=="negative edge"

// Test observations for USI slave (ATtiny84A @20MHz) in both external clock modes, USICS1=1 and USICS0=0/1
// with real SPI master (ATmega328P @16MHz) in modes 0/1/2/3:
//  USI slave USICS0=0 / SPI master mode 0 (CPOL=0, CPHA=0):    fail (wrong data received by master)
//  USI slave USICS0=0 / SPI master mode 1 (CPOL=0, CPHA=1):    fail (wrong data received by master)
//  USI slave USICS0=0 / SPI master mode 2 (CPOL=1, CPHA=0):    fail (wrong data received by master)
//  USI slave USICS0=0 / SPI master mode 3 (CPOL=1, CPHA=1):    succeed @125KHz<=Fusck<=4MHz
//  USI slave USICS0=1 / SPI master mode 0 (CPOL=0, CPHA=0):    fail (wrong data received by master)
//  USI slave USICS0=1 / SPI master mode 1 (CPOL=0, CPHA=1):    succeed @125KHz<=Fusck<=4MHz
//  USI slave USICS0=1 / SPI master mode 2 (CPOL=1, CPHA=0):    fail (wrong data received by master)
//  USI slave USICS0=1 / SPI master mode 3 (CPOL=1, CPHA=1):    fail (wrong data received by master)

// Hardware abstraction for bit access:
struct bits
{
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(portLetter, bitNumber, registerName) ((*(volatile struct bits*)&registerName##portLetter).b##bitNumber)
// portLetter = A-D, bitNumber = 0-7, registerName = "PORT", "PIN", or "DDR"
#define INPUT   0   // DDR bit = input
#define OUTPUT  1   // DDR bit = output

// ATtiny84A pin assignments
#define SPI_SS_(r)      BIT(A, 7, r)    // SPI slave select -- input from master
#define USI_DI(r)       BIT(A, 6, r)    // SPI MOSI -- input from master
#define USI_DO(r)       BIT(A, 5, r)    // SPI MISO -- output to master
#define USI_USCK(r)     BIT(A, 4, r)    // SPI SCK -- input from master 

int main(void)
{
    cli();

    SPI_SS_(DDR)    = INPUT;
    USI_DI(DDR)     = INPUT;
    USI_USCK(DDR)   = INPUT;
    USI_DO(DDR)     = OUTPUT;

    USICR = (1<<USIWM0) | (1<<USICS1) | (USICS0_VALUE<<USICS0); // USI mode = 3-wire; clock source = external

    while( 1 )
    {
        if ( SPI_SS_(PIN) == 1 )
        {
            USI_DO(DDR) = INPUT;                    // slave is deselected; float DO
            USISR = 1<<USIOIF;                      // synch/reset USCK edge counter, and clear overflow flag
            USIDR = ~USIBR;                         // pre-load Tx test data
        }
        else
        {
            USI_DO(DDR) = OUTPUT;                   // slave is deselected; drive DO (master waits enough time)
            if ( USISR & (1<<USIOIF) )              // transfer complete? (master supplies clock)
            {
                USISR = 1<<USIOIF;                  // reset USCK edge counter, and clear overflow flag
            }
        }
    }
}

 

Last Edited: Tue. Jun 23, 2020 - 10:51 AM