Go Down

Topic: Using an ATtiny85 as SPI Slave (Read 10700 times) previous topic - next topic

fungus



Did I implement the provided SPI Code correctly ? I changed USIDR to USIBR everywhere. Was that correct?


Like I said, it was just something I typed in. I never tested it or even compiled it.

USIBR should be the one to use for receiving data.

Ideas:
a) Maybe you need to set the clock/serial in pins as inputs.

b) Try putting in some code to see if the interrupt is happening, see if the program is doing what you expect.  eg. You could set an output pin high in the interrupt and watch for it on your Quad. If it never goes high you're not getting the interrupt...etc.



And I was wondering how to implement the SS (Slave Select) PIN. I the Original Code I posted in my first Thread the Arduinos SPI library was used for that. I guess I can`nt use that on the ATtiny85.


You only need that if you have more than one slave and you need to stop them both putting data on MISO at the same time.
No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

jabami

Hi folks,

Im still stuck somehow. Could anyone of you help eventually?

Here is the code I was testing lately:

Code: [Select]

#include "pins_arduino.h"

// what to do with incoming data
byte command = 0;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

// Pin 3 has an LED connected on the ATtiny85.
// give it a name:
int led = 3;

void setup (void)
{
  // have to send on master in, *slave out*
   USICR = (1 << USIWM0) | (1 << USICS1) | (1 << USIOIE);
   // turn on SPI in slave mode
   // turn on interrupts
   // clock is external
   
   // initialize the digital pin as an output.
   pinMode(led, OUTPUT);   
   
}  // end of setup

// SPI interrupt routine
ISR(USI_OVF_vect)
{
  byte c = USIDR;

  hadSpiInterrupt = true; // We had an interrupt
 
  switch (command)
  {
  // no command? then this is the command
  case 0:
    command = c;
    USIDR = 0;
    break;
   
  // add to incoming byte, return result
  case 'a':
    USIDR = 10;  // spit out Frquency Reading
    break;

  } // end of switch
USISR = 1 << USIOIF;
}  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{
  if (hadSpiInterrupt == true) {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
  }
  else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    hadSpiInterrupt = false;
  // wait for a second
  }
}  // end of loop


I found another thread dealing with the same problem here:

http://arduino.cc/forum/index.php/topic,71975.0.html

What I'd like to know is how to set the clock/serial pins as output.

Any help would be greatly appreciated. Thanks in advance,

Jan

fungus


What I'd like to know is how to set the clock/serial pins as output.

Any help would be greatly appreciated. Thanks in advance,

Jan


pinMode(...)

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

jabami

Hi there,

I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny. I tried to mimic the code of this source:
http://avrhelp.mcselec.com/index.html?using_usi_universal_serial_int.htm (see the "Example (SPI Slave with USI):" section).
Furthermore I now use the  "spiSlaveTransfer()" function within the interrupt handler - I guess that should be correct. The "spiReceive()" function is unused an I guess I can delete this part of the code. However, I´m walking in the dark not completely knowing what I´m doing.
The SPI code for the master is still the same as in my first post in this topic thread. Here is my current, non-working SPI-Slave code:

Code: [Select]


#include "pins_arduino.h"

// Incoming data goes here
byte spiData;

//Initialize Output Pins
int MISO = 1;
int SCK = 2;


// Pin 3 has an LED connected.
// give it a name:
int led = 3;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

void setup()
{
  pinMode(MISO, OUTPUT);// initialize the MISO Port (PIN 1) as an output.
  pinMode(SCK, OUTPUT);// initialize the SCK Port (PIN 2) as an output.
  pinMode(led, OUTPUT); // initialize the digital pin as an output.
 
  USICR = (1<<USIWM0)  // SPI mode; Uses DO, DI, and USCK pins.
          |(1<<USIOIE)  // Enable interrupt
          |(1<<USICS1); // Clock is external
}

// The interrupt handler
ISR(USI_OVF_vect)
{
  spiData = spiSlaveTransfer();
  hadSpiInterrupt = true;
 
  if(spiData == 'a')
  {
    USIDR = 5; //Generic reply of 5
  }
  else
  { 
    USIDR = 7; //Generic reply of 7
  }
}

// Get incoming SPI data, return -1 if none was received
int spiReceive()
{
  int result = -1;
  if (hadSpiInterrupt) {
    result = spiData;
    hadSpiInterrupt = false;
  }
  return result;
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
  // Wait for complete byte to arrive
  while ((USISR & (1<<USIOIF))==0) {
  }
  // Clear counter overflow flag so next byte can begin arriving
  // While we're processing this one
  USISR = (1<<USIOIF);
  // Return received byte
  return USIDR;
}

void loop()
{
  if (hadSpiInterrupt == true){
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);
  } 
}



Best regards,
Jan

fungus


I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.


Shouldn't they be inputs...?

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

pico

#20
Oct 29, 2012, 11:10 am Last Edit: Oct 29, 2012, 11:23 am by pico Reason: 1


I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.


Shouldn't they be inputs...?




For a spi slave, SCK, MOSI and (N)SS are inputs, and only MISO (=Master Input/Slave Output) is an output.

If the size of the chip isn't an issue, I'd consider using an atmega8 instead of an attiny85. They are actually cheaper than the attiny85 in small qty (I'm buying them at about $1 ea at the moment), and are a more capable device. The atmega8 is pin compatible with the atmega168/328 chips. You can set the fuses to run off the 8MHz internal oscillator, same as the attiny85, simplifying the parts count on the circuit, if that matters.

The atmega8L variant can be powered at Vcc 3V3, which is also useful in some applications.

Just a thought. Pricing of chips isn't entirely rational -- at any given time, a more powerful part may actually cost than a less capable alternative. It's a good idea to keep an eye on the street prices.


WiFi shields/Yun too expensive? Embeddedcoolness.com is now selling the RFXduino nRF24L01+ <-> TCP/IP Linux gateway: Simpler, more affordable, and even more powerful wireless Internet connectivity for *all* your Arduino projects! (nRF24L01+ shield and dev board kits available too.)

fungus




I now tried with the PIN 1 (MISO) and PIN 2 (SCK) set as output Pins on the ATtiny.


Shouldn't they be inputs...?




For a spi slave, SCK, MOSI and (N)SS are inputs, and only MISO (=Master Input/Slave Output) is an output.


Yes, that's what I meant... I got MOSI and MISO mixed up. :)


If the size of the chip isn't an issue, I'd consider using an atmega8 instead of an attiny85.


28 pins is a big difference in size...

The Tiny85 will work perfectly, it's just a case of getting the details right. I never used one as a slave before.

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

jabami

#22
Oct 29, 2012, 10:48 pm Last Edit: Oct 30, 2012, 12:55 am by jabami Reason: 1
Hey guys,

thanks for the input. Actually, I'd thought of using a bigger microcontroller. But size is an issue.
However, looking at the SPI Slave code on this page:
http://avrhelp.mcselec.com/index.html?using_usi_universal_serial_int.htm
I thought setting D0 and USCK as outputs should be correct. At least they did it that way.
Did one of you take a look at the code I posted in my previous post? Is that correct or did I made some obvious mistakes?

Thanks,
Jan

jabami

#23
Oct 30, 2012, 03:12 am Last Edit: Oct 30, 2012, 03:14 am by jabami Reason: 1
Hi there,

a small success. I got some code working. But I obviously did a wiring mistake as well. I used different boards for flashing (an UNO) and for communication testing (a NANO). When I was switching between the boards i used to keep the SS wire. This is connected to PIN 10 on the UNO when flashing the ATtiny but has no use when testing communication. And as it turned out by chance, it is hindering communication. The code shown below seems to work one ways (from the slave to the Master) at least. But for some reason the other way around would not work.
I changed the inetrupt handler to use a case structure, furthermore you will notice that i changed the pins output/input settings to the post above. Actually that made a lot of sense since Im waiting for the Interupt on the SCK PIN 2. If that would be an output, I could never get a signal, right? I have no clou how the SPI Slave code of these BASCOM guys work - they seem to configure the Attinys85 PIN 2 as output. Anyways, here is the code so far. Any Input is highly appreciated.

Code: [Select]

#include "pins_arduino.h"

// Incoming data goes here
byte spiData = 0;

//Initialize Output Pins
int MOSI = PB0;
int MISO = PB1;
int SCK = PB2;

// Pin 3 has an LED connected.
// give it a name:
int led = PB3;

// Tells us if we had an SPI interrupt
bool hadSpiInterrupt;

void setup()
{
 pinMode(MOSI, INPUT);// initialize the MOSI Port (PIN 0) as an input. DI ---> MOSI of ATmega328 (Pin 11)
 pinMode(MISO, OUTPUT);// initialize the MISO Port (PIN 1) as an output. DO ---> MISO of ATmega328 (Pin 12)
 pinMode(SCK, INPUT);// initialize the SCK Port (PIN 2) as an input. USCK ---> SCK of ATmega328 (Pin 13)
 pinMode(led, OUTPUT); // initialize the digital pin as an output.
 
 USICR = (1<<USIWM0)  // SPI mode; Uses DO, DI, and USCK pins.
         |(1<<USIOIE)  // Enable interrupt
         |(1<<USICS1); // Clock is external
}

// The interrupt handler
ISR(USI_OVF_vect)
{
 spiData = spiSlaveTransfer();
 hadSpiInterrupt = true;
 byte c = USIDR;
 
 switch (spiData)
 {
 // no command? then this is the command
 case 0:
   spiData = c;
   USIDR = 42; //Generic reply of 42
   break;
   
 // incoming byte, return result
 case 'a':
   USIDR = 5; //Generic reply of 5
   break;
 } // end of switch
}

// Receive a byte from the master
byte spiSlaveTransfer()
{
 // Wait for complete byte to arrive
 while ((USISR & (1<<USIOIF))==0) {
 }
 // Clear counter overflow flag so next byte can begin arriving
 // While we're processing this one
 USISR = (1<<USIOIF);
 // Return received byte
 return USIDR;
}

void loop()
{
 if (hadSpiInterrupt == true){
   digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
   delay(1000);    // wait for a second
   hadSpiInterrupt = false;
 }
 else {
   digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
   spiData = 0;
   delay(1000);
 }  
}


Best regards,
Jan

fungus

#24
Oct 30, 2012, 01:10 pm Last Edit: Oct 30, 2012, 01:12 pm by fungus Reason: 1
Is your LED lighting up now?

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

jabami

Hi Fungus,

yes the LED blinks. thus Im having a connection with the master. Actually i get a answer which is 7. But as the master is sending an 'a' as command it should receive a 5. However I feel that Im somewhat close. Do you have suggestions for more tests?

Greetings,
Jan

fungus


Hi Fungus,

yes the LED blinks. thus Im having a connection with the master. Actually i get a answer which is 7. But as the master is sending an 'a' as command it should receive a 5. However I feel that Im somewhat close. Do you have suggestions for more tests?


Writing a value to USDR won't send it to the master, you have to do another exchange.

(All SPI 'sends' are really an exchange of bytes between master/slave.

So... get the master to send another byte call spiSlaveTransfer(); again at the end of the interrupt. That's where the '5' will be sent to the master.
No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Wagnospherus

#27
Oct 30, 2013, 04:09 am Last Edit: Oct 30, 2013, 04:12 am by Wagnospherus Reason: 1
Just for the record: I was able to use this code with the AtTiny44 8Mhz  (changing pin setup).
Had also to use SPI.setClockDivider(SPI_CLOCK_DIV8) as I was getting some garbage data.
Thanks guys!

Tzimitsce

#28
Aug 26, 2014, 07:55 pm Last Edit: Aug 26, 2014, 07:57 pm by Tzimitsce Reason: 1
Quite an old topic, but I think this can be a solution for those who seek it, so I'm writing it (I also needed it now  :) ):

I think your interrupt handler is broken. On SPI, the data is transferred half-duplex; which means "as you receive data, you send data". So in order to receive and send data back, you actually transfer 4 bytes, but two of them is garbage (or, simply not cared).

So, when the master sends you a command, it already receives whatever is in your USIDR register (which is most probably 0) - as in SPI, they "exchange" bytes (but master probably knows this data is garbage -- SPI library in Arduino knows it). Now, you must prepare your data at that instant and put it to USIDR, so that the master can receive it, when it sends garbage data  (or any data which you won't care) and clock pulses. Note that, this "data preparation" can take some time, so master might need a delay there (between sending the command and receiving the answer). Basically,

Code: [Select]
ISR (USI_OVF_vect)
{
 byte b = USIBR;
 hadSpiInterrupt = true;
 
if(command == 0)
   command = b;

 switch(command)
{  
 // add to incoming byte, return result
 case 'a':
   USIDR = frq;  // spit out Frquency Reading
   break;
 
  default: // this block will only work, when the master sent 'garbage' to retrieve answer
    USIDR = 0; // this is not important for one byte protocol - since this data is what is being sent at 'next clock pulse' -- which is the next command for one-byte-protocol
     command = 0; // we reset the command, so we can process the next one
    break;
 } // end of switch

} // end of interrupt service routine ISR (USI_OVF_vect)



Note that, whence the Master sent you command, you made some switch-case comparisons etc. before you put something back to USIDR (note that, not to the USIBR; because that register is just a 'buffer' (copy) register, you need to put data to USIDR, I think); so master must know 'how long to wait there'. Since clock is pulsed by the master; it's not a problem if it waits 'too long' as long as not too little. Say, it must wait 1 microseconds. After this, the master will start clock-pulsing again which will yield him whatever you put to the USIDR register - which is the response of the command. Note that we also reset command register to 0. This is true if all your commands return only one bytes. If they return more than one, you need a logic to determine this here (maybe check previous command and count the bytes sent to decide when it's over, etc. etc. etc.)

I'm sorry, I cannot test this right now but I think this is what was missing in your code. Can you try it? I'll also try it myself but I dunno when :)

fungus


master must know 'how long to wait there'. Since clock is pulsed by the master; it's not a problem if it waits 'too long' as long as not too little.


I2C is preferable for this, it automatically puts the master "on hold" until the slave responds.

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Go Up