Multiple slaves: Using multiple ATTiny85 as SPI slaves with Arduino Uno as SPI master

Hello,
I am using Arduino Uno (ATMega 328p) as SPI Master and multiple ATTiny85 as SPI slaves. Since ATTiny85 implements USI instead, there is no Slave Select pin. So I am wondering where to connect the Slave Select coming from Uno (acting as SPI Master) for ATTiny85 (acting as SPI Slave).

Since I am using multiple ATTiny85 as slaves, I am left wondering how a slave will know that master is communicating with it?

Thanks,
aquaman

As the ATtiny85 doesn't have hardware support for the SS signal you have to use one of the remaining pins and emulate the functionality in software. Fortunately, the other pins support a pin change interrupt, so you can react fast enough on an SS low signal.You have to ensure to tri-state the MISO pin if SS goes high.
Here you can find an example implementation.

Thank you @pylon
Will try the solution you provided in the link.

Regards,
aquaman

Hello,

I am wondering how to do that in ATTiny85.
The datasheet says this on page 55:

So accordingly, so as to tristate MISO pin on ATTiny85, I tried doing this in the pin-change ISR:

// Interrupt routine at the CS pin. Always executed when value on CS pin changes:
ISR (PCINT0_vect)
{
 if ((PINB & (1 << CS)) == 0) {

   // If edge is falling, the command and index variables shall be initialized
   // and the 4-bit overflow counter of the USI communication shall be activated:
   USICR |= (1 << USIOIE);
   USISR = 1 << USIOIF;    // Clear Overflow bit
   DDRB |= 1 << DO; // ensure that MISO is set to OUTPUT
 }
 else {
   // If edge is rising, turn the 4-bit overflow interrupt off:
   USICR &= ~(1 << USIOIE);
   // Tristate the MISO pin. Turn DO as Input to tristate.
   PORTB &= ~(1 << DO);
   DDRB &= ~(1 << DO);
 }
}

But by doing this (clearing PB1(MISO) via PORTB &= ~(1<<DO) and clearing corresponding DDB pin via DDRB &= ~(1 << DO) ), the slave is not able to send any values to master even when CS is pulled low and master just receives only 0x0 no matter what it sends to slave. (Some context here, in my toy example, when the SPI master sends 0x02 to slave, slave should send back 0x05 to master).

When I remove the two lines (PORTB &= ~(1<<DO); DDRB &= ~(1 << DO)), the slave is able to transmit and receive values with master fine. However, in a multiple slave scenario, it makes good sense to have MISO tristated to not have bus contention issues with un-selected slaves (whose CS is high). Again, this would be a non-issue if ATTiny85 had hardware SPI, but since it has USI, I am wondering how to get tristate working for MISO.

Any help is appreciated.

Best Regards,
aquaman

Finally got 2 slaves working.

Just in case someone else lands up here:

  1. make sure slave select is turned HIGH for all slaves in setup() of master. Even if you miss one, it will cause a mess as bus contention on MISO might drive slaves into unknown states. Point being, slave select LOW of one slave must never overlap with that slave select LOW of another.
  2. in the pin change ISR, its recommended to do this in this order to tristate MISO:
    // Tristate the MISO pin. Turn DO as Input to tristate. 
    DDRB &= ~(1 << DO);
    PORTB &= ~(1 << DO);

First make the the respective DDRB port bit as input AND THEN clear the corresponding PORTB port bit.

Then it should work fine.

Here is the slave code that worked for me for 2 slaves. Its a toy example. ISR can be made cleaner.
For slave 1, use SLAVE1_* inside pin change ISR. For slave 2, use SLAVE2_* inside pin change ISR.

#define CS PB3    // Chip Select or Slave Select
#define DO PB1    // MISO or Data Out
#define USCK PB2  // Clock
#define LED PB4   // Test LED
#define SLAVE2_DATA 0x08
#define SLAVE2_ACK 0x0A
#define SLAVE2_NACK 0x0B
#define SLAVE1_DATA 0x02
#define SLAVE1_ACK 0x05
#define SLAVE1_NACK 0x09

void setup() {

  cli(); // Deactivate interrupts
  SPI_USI_init();
  sei(); // Activate interrupts
}

void loop() {
  // put your main code here, to run repeatedly:

}


void SPI_USI_init() {
  DDRB |= 1 << LED;                   // Set LED pin as output
  DDRB |= 1 << DO;                    // MISO Pin has to be an output

  USICR = ((1 << USIWM0) | (1 << USICS1)); // Activate 3- Wire Mode and use of external clock but NOT the interrupt at the Counter overflow (USIOIE)

  PORTB &= ~(1 << LED);                  // Turn LED off
  PORTB |= 1 << CS;                   // Activate Pull-Up resistor on CS

  PCMSK |= 1 << CS;                   // Activate Interrupt on CS
  GIMSK |= 1 << PCIE;                  // General Interrupt Mask Register / PCIE bit activates external interrupts

  delay(500);                         // Let things settle
}

// Interrupt routine at the CS pin. Always executed when value on CS pin changes:
ISR (PCINT0_vect)
{
  if ((PINB & (1 << CS)) == 0) {
    // If edge is falling, the command and index variables shall be initialized
    // and the 4-bit overflow counter of the USI communication shall be activated:
    USICR |= (1 << USIOIE);
    USISR = 1 << USIOIF;    // Clear Overflow bit
    DDRB |= 1 << DO; // ensure that MISO is set to OUTPUT
  }
  else {
    // If edge is rising, turn the 4-bit overflow interrupt off:
    USICR &= ~(1 << USIOIE);
    // Tristate the MISO pin. Turn DO as Input to tristate. 
    DDRB &= ~(1 << DO);
    PORTB &= ~(1 << DO);
  }
}


// USI interrupt routine. Always executed when 4-bit overflows (after 16 clock edges = 8 clock cycles):
// for test purposes: the master will send 0x01 as data and should receive from slave 0x05 as response
// for test purposes: if the master sends anything other than 0x01, it will receive from slave 0x09 as response
ISR (USI_OVF_vect) {
  byte received = USIDR;
  
  if (received == SLAVE2_DATA) {
    USIDR = SLAVE2_ACK;//ACK for successfully receiving 0x01
    //Testing: XOR operation to turn PB3 (LED) on. (Not needed for USI)
    PORTB |= 1 << LED;
  }
  else {
    USIDR = SLAVE2_NACK;//ACK indicating non 0x01 value received
    //Testing: XOR operation to turn PB3 (LED) off. (Not needed for USI)
    PORTB &= ~(1 << LED);
  }

  USISR = 1 << USIOIF;
}

corresponding master code is:


#include <SPI.h>
#include "pins_arduino.h"

int dummyData = 7;
int slaveSelect = SS; // SS is pin 10
int slaveSelect1 = 9;
int delayTime = 500;
byte masterReceive;
//bool toggle = LOW;

// the setup function runs once when you press reset or power the board
void setup() {
  pinMode(slaveSelect, OUTPUT);
  pinMode(slaveSelect1, OUTPUT);
  digitalWrite(slaveSelect, HIGH);
  digitalWrite(slaveSelect1, HIGH);
  Serial.begin(9600); // set default baud rate for uart
  SPI.begin();
  // SPI.setBitOrder(MSBFIRST); // deprecated. use SPI.beginTranscation() with SPISettings
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(slaveSelect, LOW);
  delay(500);
  {
    SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
    
    delayMicroseconds(10);
    // send test string
    int sentData = 2;
    SPI.transfer(sentData);
    delay(500);
    byte recvData = SPI.transfer(dummyData);
    //digitalWrite(slaveSelect, HIGH);
    SPI.endTransaction();
    Serial.print("Sent data:");
    Serial.print(sentData);
    Serial.print(":::Received data from slave:");
    Serial.println(recvData);
  }
  {
    SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
    //digitalWrite(slaveSelect, LOW);
    delayMicroseconds(10);
    // send test string
    int sentData = 3;
    SPI.transfer(sentData);
    delay(500);
    byte recvData = SPI.transfer(dummyData);
    digitalWrite(slaveSelect, HIGH);
    SPI.endTransaction();
    Serial.print("Sent data:");
    Serial.print(sentData);
    Serial.print(":::Received data from slave:");
    Serial.println(recvData);
  }
  delay(1000);
  digitalWrite(slaveSelect1, LOW);
  delay(500);
  {
    SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
    
    delayMicroseconds(10);
    // send test string
    int sentData = 8;
    SPI.transfer(sentData);
    delay(500);
    byte recvData = SPI.transfer(dummyData);
    //digitalWrite(slaveSelect1, HIGH);
    SPI.endTransaction();
    Serial.print("Sent data:");
    Serial.print(sentData);
    Serial.print(":::Received data from slave2:");
    Serial.println(recvData);
  }
  {
    SPI.beginTransaction(SPISettings(9600, MSBFIRST, SPI_MODE0));
    //digitalWrite(slaveSelect1, LOW);
    delayMicroseconds(10);
    // send test string
    int sentData = 2;
    SPI.transfer(sentData);
    delay(500);
    byte recvData = SPI.transfer(dummyData);
    digitalWrite(slaveSelect1, HIGH);
    SPI.endTransaction();
    Serial.print("Sent data:");
    Serial.print(sentData);
    Serial.print(":::Received data from slave2:");
    Serial.println(recvData);
  }
  delay(1000);
}

best regards,
aquaman

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.