[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*)®isterName##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*)®isterName##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 } } } }