Go Down

Topic: Wie bidirektionaler Datenaustausch zw. 2 Arduinos via SPI ? (Read 2434 times) previous topic - next topic

Tuetenflieger

Hallo,
ich versuche gerade, zwei UNOs via SPI miteinander kommunizieren zu lassen.

Es soll darauf hinauslaufen, daß Arduino 1 (Master) Daten zu Arduino 2 (Slave) schickt, der Salve etwas damit berechnet (hier im 2. Beispiel die Quadratzahl), und das Ergebnis wieder zum Master  zurückschickt.
Als Daten sollen prinzipiell Typen wie byte, int, float, am liebsten auch structs genutzt werden können.

Von Nick Gammon gibt es ein sehr kompaktes und elegantes Beispiel, mit dem beliebige Datentypen, auch Structs, von einem Arduino auf einen zweiten übertragen werden können.(http://www.gammon.com.au/spi)
Nach meinem Verständnis ist damit aber nur ein Transfer in eine Richtung möglich, Ich fand keinen Weg, Daten auch wieder zurückzugeben.
Nick Gammons Code des Masters
Code: [Select]
#include <SPI.h>
#include "SPI_anything.h"

typedef struct myStruct // create a structure to store the different data values:
{
  byte a;
  int b;
  long c;
};

#define SS 10

myStruct foo; // Beispiel für den Datenexport einer Struktur

void setup ()
{
  SPI.begin ();
  SPI.setClockDivider(SPI_CLOCK_DIV8);  // Slow down the master a bit

  foo.a = 42;
  foo.b = 32000;
  foo.c = 10;
}  // end of setup

void loop ()
{
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI_writeAnything (foo);
  digitalWrite(SS, HIGH);
  delay (10);  // for testing

  foo.c++;

}  // end of loop
#include <Arduino.h>

template <typename T> unsigned int SPI_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          SPI.transfer(*p++);
    return i;
  }  // end of SPI_writeAnything

Nick Gammons Code für den Slave:
Code: [Select]
#include <SPI.h>
typedef struct myStruct // create a structure to store the different data values:
{
  byte a;
  int b;
  long c;
};

volatile myStruct foo;
volatile bool haveData = false;

void setup ()
{
  Serial.begin (115200);   // debugging
  pinMode(MISO, OUTPUT);  // have to send on master in, *slave out*
  SPCR |= _BV(SPE);  // turn on SPI in slave mode
  SPI.attachInterrupt();  // now turn on interrupts
}  // end of setup

void loop ()
  {
  if (haveData)
     {
     Serial.println ((int) foo.a);
     Serial.println (foo.b);
     Serial.println (foo.c);
     Serial.println ();
     haveData = false;
     }
  }  // end of loop

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  SPI_readAnything_ISR (foo);
  haveData = true;
}  // end of interrupt routine SPI_STC_vect

template <typename T> unsigned int SPI_readAnything_ISR(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    *p++ = SPDR;  // get first byte
    for (i = 1; i < sizeof value; i++)
          *p++ = SPI.transfer (0);
    return i;
  }  // end of SPI_readAnything_ISR 

Im Netz fand ich unter von davyzhu aus: http://forum.arduino.cc/index.php?topic=69734.0 eine Zweiwege-Kommunikation. Das ist aber nur Einzelbyte-transfer, ich habe das zumindest auf 2 Bytes (für Ints) erweitern können, schaffe das aber nicht für andere Datenstrukturen wie Structs oder floats zu erweitern. Den "...anything..." Code von Nick hier zu nutzen geht deutlich über meine Fähigkeiten hinaus.
davyzhu Code für den Master
Code: [Select]
#include <SPI.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include "pins_arduino.h"

#define SDRDY 9 // slave data ready

#define mackPin PINB3

byte t = 0;
byte r = 0;
int Ergebnis = 0;

void setup()  // *******************************************
{
  Serial.begin (115200);   // debugging
  Serial.println("Ich bin der Master, der die Zahl an den Slave gibt, und das dort berechnete Ergebnis anzeigt");
  pinMode(SDRDY, INPUT);
  SPI_MasterInit();
}

void loop()   // *******************************************
{
  digitalWrite(SS, LOW);
  t++;
  if (t >= 15) t = 0;
  r = SPI_MasterTransmit(t);

  //block when slave data is not ready (not write to SPDR yet)
  while (digitalRead(SDRDY) == LOW)
    ;

  Ergebnis = SPI_MasterReceive();
  digitalWrite(SS, HIGH);
  Serial.print (t);
  Serial.print("\t");
  Serial.print (r);
  Serial.print("\t");
  Serial.println (Ergebnis);
  // delay (1);
}

// copy from http://arduino.googlecode.com/svn
// SPI.cpp, SPI.h
void SPI_MasterInit(void)  // *******************************************
{
  pinMode(SCK, OUTPUT);
  pinMode(MOSI, OUTPUT);
  pinMode(SS, OUTPUT);

  digitalWrite(SCK, LOW);
  digitalWrite(MOSI, LOW);
  digitalWrite(SS, HIGH);

  // Warning: if the SS pin ever becomes a LOW INPUT then SPI
  // automatically switches to Slave, so the data direction of
  // the SS pin MUST be kept as OUTPUT.
  SPCR |= _BV(MSTR);
  SPCR |= _BV(SPE);

  //default SPI speed is osc/2 (in arduino uno, 4Mhz, too fast)
  //change it to osc/16
  setClockDivider(SPI_CLOCK_DIV32);
}

byte SPI_MasterTransmit(byte cData)  // *******************************************
{
  /* Start transmission */
  SPDR = cData;
  /* Wait for transmission complete */
  while (!(SPSR & (1 << SPIF)))
    ;
  return SPDR;
}

byte SPI_MasterReceive(void)  // *******************************************
{
  SPDR = 0x00;
  /* Wait for reception complete */
  while (!(SPSR & (1 << SPIF)))
    ;
  /* Return Data Register */
  return SPDR;
}

void setClockDivider(byte rate)  // *******************************************
{
  SPCR = (SPCR & ~SPI_CLOCK_MASK) | (rate & SPI_CLOCK_MASK);
  SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | (rate & SPI_2XCLOCK_MASK);
}

davyzhu Code für den Slave
Code: [Select]
// arduino spi library only use arduino as master
//#include <SPI.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include "pins_arduino.h"

#define SDRDY 9 // slave data ready

byte t = 0;
byte r = 0;

// create a structure to store the different data values:
typedef struct myStruct
{
  byte a;
  int b;
  long c;
};

void setup()   // *******************************************
{
  SPI_SlaveInit();
  pinMode(SDRDY, OUTPUT);
  digitalWrite(SDRDY, LOW);
  //Serial.begin(9600);
}

void loop()   // *******************************************
{
  r = SPI_SlaveReceive();
  t = SPI_SlaveTransmit(r * r);
}

void SPI_SlaveInit(void)  // *******************************************
{
  pinMode(MISO, OUTPUT);
  digitalWrite(MISO, LOW);
  SPCR |= _BV(SPE);
}

char SPI_SlaveReceive(void)  // *******************************************
{
  /* Wait for reception complete */
  while (!(SPSR & (1 << SPIF)))
    ;
  /* Return Data Register */
  return SPDR;
}

char SPI_SlaveTransmit(byte cData)  // *******************************************
{
  /* Start transmission */
  SPDR = cData;
  // assert drdy signal(to master)
  digitalWrite(SDRDY, HIGH);

  /* Wait for transmission complete */
  while (!(SPSR & (1 << SPIF)))
    ;

  // deassert drdy signal
  digitalWrite(SDRDY, LOW);

  return SPDR;
}

Interessanterweise wird neben den 4 Standard SPI-Leitungen incl. SS noch eine 5. (Slave Data ready) gefordert, damit der Slave anzeigen kann, daß der Master nicht senden darf.
Diese Zusatzleitung finde ich auch im Drucksensor-Beispiel der SPI-Bibliothek.
Und mehrfach hab ich im Netz die Aussage gelesen, daß die standard SPI-lib nicht als Slave fungieren kann. Nick Gammon nutzt sie trotzdem dafür.

Frage:
Wie kann entweder Nick Gammons elegante Lösung auf bidirektionalen Betrieb erweitert werden,
oder davyzhu's Lösung auf allgemeine Datentypen ?
Am Anfang war: der HP-65...

Serenifly

Nach meinem Verständnis ist damit aber nur ein Transfer in eine Richtung möglich, Ich fand keinen Weg, Daten auch wieder zurückzugeben.
Wird doch hier gemacht:
Code: [Select]
   
for (i = 1; i < sizeof value; i++)
   *p++ = SPI.transfer (0);


transfer() macht gleichzeitig Lesen und Schreiben. Beim Lesen übergibt man 0 als Dummy Argument und wertet den Rückgabewert aus


Die Templates sind simpel. Man nimmt einfach die Adresse des Übergebenen Wertes (hier value vom Typ T) und castet diesen auf byte*:
Code: [Select]

byte * p = (byte*) &value;

Und schon kann man jeden Datentyp wie ein Array aus Bytes ansprechen

*ptr++ dereferenziert dann den Zeiger (d.h. spricht den Inhalt an) und inkrementiert den Zeiger

combie

Quote
Und mehrfach hab ich im Netz die Aussage gelesen, daß die standard SPI-lib nicht als Slave fungieren kann. Nick Gammon nutzt sie trotzdem dafür.
Die Arduino SPI Lib kann keinen Slave Mode.

SPI Slave, ist keine dankbare Aufgabe für einen AVR.
Er muss unmittelbar reagieren.
Das mit der "SlaveBusy" Leitung ist ein dirty Hack.


Warum verwendest du nicht I2C?
Das macht, wenn, dann erst im Multimaster Betrieb Sorgen.

Wer seine Meinung nie zurückzieht, liebt sich selbst mehr als die Wahrheit.

Quelle: Joseph Joubert

Tuetenflieger

Hallo,
@Serenifly vielen Dank, aber sorry, ich durchblicke das leider noch nicht.
Ich hatte das mal so umgeändert, aber es funktioniert nicht, Nicks Routinen von  writeanything einfach so mit readanything zu kombinieren.
response ist der gleiche struct wie foo, der Rest der Programme wie vorher.
Master schickt Daten zum Slave, und soll das zurückkommende anzeigen:
Code: [Select]
void loop ()
{
  for (byte Zahl = 0; Zahl < 20; Zahl++)
  {
    foo.a = Zahl;
    digitalWrite(SS, LOW);    // SS is pin 10
    SPI_writeAnything (foo);
    digitalWrite(SS, HIGH);
    delay (1);  // for testing
    SPI_readAnything(response);
    foo.b = response.b;
    foo.c++;
    Serial.println ((int) response.a);
    Serial.println (response.b);
    Serial.println (response.c);
    Serial.println ();
    delay (10);  // for testing
  }
}  // end of loop

Slave nimmt die Daten (ein Struct) an, und gibt es leicht verändert wieder aus:
Code: [Select]
void loop ()
{
  if (haveData)
  {
    transmit.b = foo.a * foo.a;
    haveData = false;
  }
  digitalWrite(SS, LOW);    // SS is pin 10
  SPI_writeAnything (transmit);
  digitalWrite(SS, HIGH);
}  // end of loop


@combie: Warum ich kein I2C nehme ?
Ganz einfach, mein Ziel ist es einen Arduino via I2C mit diversen Sensoren mit Daten zu füttern, und dieser soll via SPI mit einem zweiten Arduino sprechen.
Ich will zwei (oder mit einer Abspaltung von der Anzeige- & Eingabeeinheit gar drei) AVR einsetzen, um Rechenarbeit und Speicherplatz (bin bei > 30k, und da soll noch was dazu) aufzuteilen.
Ist alles etwas zeitkritisches für eine RC-Flugmodellregelung. Alle Daten (Rohdaten eines 10DOF IMU, sowie die berechneten Daten) über den langsamen I2C zu jagen dauert zu lange, um in den max. 20ms großen Zeitslots noch anderes zu erledigen.
Die Alternative wäre ein Teensy 3.2 etc, aber ich fange mich ja gerade an, mich an die AVRs zu gewöhnen...

Lg,
Tütenflieger
Am Anfang war: der HP-65...

Serenifly

Du solltest die Daten glaube ich nur zurückschicken wenn du auch was empfangen hast. Nicht ständig! Die Antwort gehört in die if(haveData) Abfrage

Tuetenflieger

@Serenifly: Stimmt natürlich, ich habe das nun in die haveData Schleife reingebracht, aber es hilft nichts. Es kommt nur eine Null zurück.
Ich habe die ganzen Datenstruktur jetzt zu Testzwecken auf den Transport einzelner Bytes reduziert, die mit dem "SPI_writeAnything" verschickt werden, aber es gab damit auch keine Besserung.
Der Logikanalyzer zeigt gleichzeitigen Traffic auf MOSI und MISO, was m.E. doch eigentlich nicht sein sollte, oder ?
Mal schauen, ob ich hier weiterkomme.
Hat noch jemand eine Idee ?

Master:
Code: [Select]
#include <SPI.h>
#include "SPI_anything.h"

#define SS 10

byte Daten;
bool haveData = false;

void setup ()
{
  Serial.begin (115200);   // debugging
  SPI.begin ();
  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);
}  // end of setup

void loop ()
{
  for (byte Zahl = 0; Zahl < 20; Zahl++)
  {
    digitalWrite(SS, LOW);    // SS is pin 10
    SPI_writeAnything (Zahl);
    digitalWrite(SS, HIGH);
    SPI_readAnything(Daten);
    Serial.print (Zahl);
    Serial.print("\t");
    Serial.println (Daten);
  }  // end of loop
}

Slave:
Code: [Select]
#include <SPI.h>
#include "SPI_anything.h"

volatile byte Daten;
volatile bool haveData = false;
byte transmit;

void setup ()
{
  Serial.begin (115200);   // debugging
  pinMode(MISO, OUTPUT);  // have to send on master in, *slave out*
  SPCR |= _BV(SPE);// turn on SPI in slave mode
  // now turn on interrupts
  SPI.attachInterrupt();
}  // end of setup

void loop ()
{
  if (haveData)
  {
    transmit = Daten * Daten;
    haveData = false;
    digitalWrite(SS, LOW);    // SS is pin 10
    SPI_writeAnything (transmit);
    digitalWrite(SS, HIGH);
  }
}  // end of loop

// SPI interrupt routine
ISR (SPI_STC_vect)
{
  SPI_readAnything_ISR (Daten);
  haveData = true;
}  // end of interrupt routine SPI_STC_vect

template <typename T> unsigned int SPI_writeAnything (const T& value)
  {
    const byte * p = (const byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          SPI.transfer(*p++);
    return i;
  }  // end of SPI_writeAnything

template <typename T> unsigned int SPI_readAnything(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    for (i = 0; i < sizeof value; i++)
          *p++ = SPI.transfer (0);
    return i;
  }  // end of SPI_readAnything
 
 
template <typename T> unsigned int SPI_readAnything_ISR(T& value)
  {
    byte * p = (byte*) &value;
    unsigned int i;
    *p++ = SPDR;  // get first byte
    for (i = 1; i < sizeof value; i++)
          *p++ = SPI.transfer (0);
    return i;
  }  // end of SPI_readAnything_ISR 
Am Anfang war: der HP-65...

combie

Quote
Der Logikanalyzer zeigt gleichzeitigen Traffic auf MOSI und MISO, was m.E. doch eigentlich nicht sein sollte, oder ?
Immer wenn der Master Daten über MOSI zum Slave sended, sendet der Slave über MISO seine Daten.
Bei jedem Byte, eine Antwort. Gleichzeitig. Immer. Ohne Ausnahme.
Wer seine Meinung nie zurückzieht, liebt sich selbst mehr als die Wahrheit.

Quelle: Joseph Joubert

Tuetenflieger

OK, wieder was gelernt,
ich habe nun im Slave zwar das Zurückschreiben eines Datums komplett auskommentiert, aber sehe im Analyzer (siehe Anhang), daß der Slave den Input vom Master wiederholt.
Ist das normal ?



Etwas OT:
Kann man in dieser Forumssoftware auch Bilder direkt eingebunden anzeigen, oder geht das immer nur als Anhang ?

Gruß,
Tütenflieger
Am Anfang war: der HP-65...

combie

Quote
ich habe nun im Slave zwar das Zurückschreiben eines Datums komplett auskommentiert, aber sehe im Analyzer (siehe Anhang), daß der Slave den Input vom Master wiederholt.
Ist das normal ?
Wenn du nicht bestimmst, was der Slave senden soll, dann wird es wohl der Zufall, oder irgendein Implementationsdetail bestimmen.
Was stört dich daran?
Wer seine Meinung nie zurückzieht, liebt sich selbst mehr als die Wahrheit.

Quelle: Joseph Joubert

agmue

Etwas OT:
Kann man in dieser Forumssoftware auch Bilder direkt eingebunden anzeigen, oder geht das immer nur als Anhang ?
Bei dieser Frage kann ich helfen: Ja, das geht: Bild anhängen, Beitrag veröffentlichen (Post), Linkadresse der Grafik in die Zwischenablage, Beitrag editieren, Botton Insert an image klicken, Link einfügen usw.

Bis Du es machst, zeige ich Dein Bild:
Die Vorstellungskraft ist wichtiger als Wissen, denn Wissen ist begrenzt. (Albert Einstein)

Go Up