Forum: Mikrocontroller und Digitale Elektronik SPI macht komische Faxen


von Eugen T. (der_eugen_thorben)


Lesenswert?

Hi,

ich hab eine Frage zu SPI. Das Verhalten find ich ehrlich gesagt etwas 
verwunderlich und wüsste nicht, wieso es auftritt.

Mein Slave sendet dem Master ein Array, nachdem der Master es 
angefordert hat. Danach sendet mein Master dem Slave, dass alles ok ist 
(1 Byte). Der Slave erhält es, jedoch bleibt er irgendwo in einer while 
schleife (schätze ich) hängen und es passieren komische Sachen auf dem 
Display, wenn der Master wieder ein Empfangen anfordert.
Wenn der Slave aber 2 mal etwas empfängt, läuft es, falls das 2 
empfangene Byte 0 ist.

Hat jemand da Erfahrung mit sowas schonmal gemacht?
Es ist so, dass mein Master dummie Bytes sendet und dann der Slave die 
Bytes sendet. Keine Ahnung ob es daran liegen könnte.

Hab hier auch den Code, falls es hilft.

Ich hab den CRC16 Korrektheitstest erstmal ausgelassen.

Wenn jedoch 2 mal CRC16_is_CORRECT gesendet und empfangen wird, klappts 
nicht.

Wenn aber CRC16_IS_CORRECT und danach eine 0 gesendet wird, 
funktionierts.

CRC16_IS_CORRECT ist 0xFF
und CRC16_IS_NOT_CORRECT ist 0x00

Master:

1
      
2
      SPI_M_send_byte_to_hardware_slave(SEND_ARRAY_WITH_DEFINED_LENGTH_AND_CRC16);
3
    
4
        SPI_M_send_byte_to_hardware_slave(SPI_CHECK_BYTE);
5
        SPI_M_send_byte_to_hardware_slave(SPI_DUMMY_BYTE_FOR_RECEIVING);
6
        
7
        array_size = SPI_M_receive_byte();
8
        
9
          for(i = 0; i < array_size; i++)
10
          {
11
          
12
            SPI_M_send_byte_to_hardware_slave(SPI_DUMMY_BYTE_FOR_RECEIVING); // for receiving
13
            *tmp = SPI_M_receive_byte();
14
            *tmp++;
15
          
16
          }
17
        
18
        *tmp = ARRAY_END;
19
        tmp = array;
20
        
21
        
22
        crc16_check = crcSlow(tmp, array_size);
23
        
24
        SPI_M_send_byte_to_hardware_slave(SPI_DUMMY_BYTE_FOR_RECEIVING); // for receiving
25
        crc16 = SPI_M_receive_byte();
26
        crc16 <<= 8;
27
        SPI_M_send_byte_to_hardware_slave(SPI_DUMMY_BYTE_FOR_RECEIVING);
28
        crc16 |= SPI_M_receive_byte();
29
            
30
        SPI_M_send_byte_to_hardware_slave(CRC16_IS_CORRECT);
31
32
        SPI_M_send_byte_to_hardware_slave(CRC16_IS_NOT_CORRECT);
33
34
      
35
      return array_size;
36
    
37
    }


Slave:
1
if (SPI_S_receive_byte() >= SPI_CHECK_BYTE_FOR_RECEIVING)
2
      {
3
        
4
        SPI_S_send_byte(array_size);
5
        
6
          for(i = 0; i < array_size; i++)
7
          {
8
9
            SPI_S_send_pointer(tmp);
10
            tmp++;
11
            
12
          }
13
        
14
        *tmp = ARRAY_END;
15
        tmp = array;
16
17
        
18
          
19
          crc16 = crcSlow(tmp, array_size);
20
          crc16_low_byte = crc16 & 0b11111111;
21
          crc16_high_byte = crc16 >> 8;
22
        
23
        SPI_S_send_byte(crc16_high_byte);
24
        
25
        SPI_S_send_byte(crc16_low_byte);
26
27
          
28
          crc16_ok = SPI_S_receive_byte();
29
          
30
          crc16_ok = SPI_S_receive_byte();


Vielen Dank

von Joe F. (easylife)


Lesenswert?

Also du kannst dir das Leben etwas leichter machen, wenn du direkt auf 
das Array zugreifst und nicht so viel mit Pointern rummachst.

statt
1
*tmp = SPI_M_receive_byte();
könntest du z.B. einfach
1
array[i] = SPI_M_receive_byte();
schreiben (Master).

Im Master ist dementsprechend auch ein Fehler:
1
*tmp++;
soll sicher
1
tmp++;
heissen.

Das begründet aber nicht das Problem.
Ich denke du solltest mal mit einem Logicanalyzer oder mit einem DSO den 
SPI Bus angucken.
Sieht mir so aus, als bei deiner Kommunikation IN und OUT nicht im sync 
sind.

von Karl H. (kbuchegg)


Lesenswert?

Eugen Thorben schrieb:

> Slave:
>
> [c]
> if (SPI_S_receive_byte() >= SPI_CHECK_BYTE_FOR_RECEIVING)
>       {
>
>         SPI_S_send_byte(array_size);

Das ist sehr missverständlich ausgedrückt und ich denke irgendwo in 
diesem Umfeld wird auch dein Problem sitzen.

Der Slave kann von sich aus nichts senden!

Die Initiative liegt immer beim Master. Überhaupt sind die Begriffe 
Senden und Empfangen bei SPI etwas deplaziert. Denn eigentlich ist SPI 
eher so etwas wie ein gleichzeitig Byteaustausch. Während vom Maser 1 
Byte zum Slave wandert, wandert gleichzeitig 1 Byte vom Slave zum 
Master.
D.h. Wenn der Master die Übertragung eines Bytes initiiert, dann muss im 
Slave das gleichzeitig rückzuübertragende Byte schon im SPDR Register 
sein.
D.h. aber auch: der Master muss dem Slave die Zeit einräumen, die der 
Slave braucht um zb angeforderte Daten bereitzustellen.

SPI funktioniert so, wie wenn du und dein Kumpel sich am Tisch gegenüber 
sitzen. Du bist der Master, dein Kumpel ist der Slave. Ihr habt beide 
einen Zettel vor euch liegen auf dem ihr was aufschreiben könnt.
Auf DEIN Zeichen (du bist der Master) hin, nimmt jeder seinen Zettel und 
reicht ihn mit der rechten Hand zum Gegenüber rüber, der ihn mit der 
linken Hand entgegen nimmt.

Wenn du daher etwas von deinem Kumpel willst, dann geht das so
* du schreibst auf deinen Zettel: Wie alt bist du?
* dann tauscht ihr beide die Zettel aus. Logischerweise kann auf dem 
Zettel, den du gerade bekommen hast noch nichts vernünftiges stehen. 
Denn dein Kumpel weiss ja erst was du von ihm willst, wenn er deinen 
Zettel bekommen hat.
* Jetzt musst du deinem Kumpel auch ein wenig Zeit einräumen, damit er 
sein Alter auf den Zettel schreiben kann. Denn das dauert ja auch ein 
wenig
* Dann tauscht ihr wieder die Zettel aus. Was auf deinem Zettel steht, 
spielt keine Rolle. Aber du musst den Zettel rausrücken, weil ihr ja 
immer die Zettel austauscht.
* Mit dem Zettel, den du jetzt kriegst hast du dann die gewünschte 
Information.
* Du radierst das aus und schreibst "OK" drauf.
* wieder werden die Zettel ausgetauscht
* auf dem Zettel den du kriegst steht nichts vernünftiges. Vielleicht 
steht da "keine weitere Information verfügbar", aber im Grunde brauchts 
das nicht, denn auf die Frage nach dem Alter genügt es, wenn du die Zahl 
zurück kriegst.
* dein Kumpel hingegen kriegt den Zettel, liest ihn, liest das OK und 
weiss damit, dass er die nächste Zeit von dir Ruhe haben wird.

So läuft SPI ab.
Der eigentliche Transfer ist immer ein Byte Austausch. Daher sind die 
Begriffe Senden und Empfangen hier etwas deplaziert. Denn bei jedem 
Transfer gibt es einen Sender und einen Empfänger. Und zwar in beiden 
Richtungen gleichzeitig!

Und ganz wichtig: Der Slave kann sich nicht wehren! Wenn der Master den 
Zettelaustausch anordnet, dann werden die Zettel getauscht. Egal ob dein 
Kumpel sein Alter schon auf den Zettel geschrieben hat oder nicht. D.h. 
nach der Anforderung einer Information, wirst du gut daran tun, deinem 
Kumpel auch etwas Zeit einzuräumen. Vielleicht muss er ja erst nach der 
Information Googeln - das kann schon dauern. Entscheidend ist, dass du 
keine Information darüber hast, ob er schon fertig ist oder nicht.

Daher implementiert man meistens den Slave auch nicht so, wie du das 
machst. Im Slave implementiert man einen Interrupt, der immer dann 
aufgerufen wird, wenn ein Byteaustausch komplett fertig ist. In dieser 
ISR musst du dir dann zb einen Zustand merken um zu wissen, was beim 
nächsten ISR Aufruf zu tun ist. Die ISR stellt zb fest, dass jetzt 
gerade das Kommando 'Array liefern' eingetrudelt ist. VOn diesem 
Kommando weiss es, dass der Master als nächstes die Übertragung der 
Array Größe erwartet. Also stellt sie die gleich mal ins SPDR Register, 
damit sie sich der Master abholen kann. Holt sich der Master die 
Arraygröße, dann weiss die ISR anhand der gemerkten Zustände, dass als 
nächstes 1 Byte vom Array zu übertragen ist. Also wird dieses ins SPDR 
geschrieben, damit der Master sich das abholen kann. Beim nächsten ISR 
AUfruf kommt dann das nächste Array Element drann. Und so geht das 
dahin, bis das Array komplett übertragen wurde, woraufhin die ISR die 
CRC bereitstellt.

Der Slave 'sendet' also in diesem Sinne nichts. Der Slave stellt die 
jeweils nächste Information im SPDR bereit, auf dass der Master sich die 
abholen kann. Das muss deine Denkweise sein und dann gibts auch keine 
Schleifen, in denen dein Programm hängen könnte.

von Eugen T. (der_eugen_thorben)


Lesenswert?

Joe F. schrieb:
> Im Master ist dementsprechend auch ein Fehler:*tmp++;soll
> sichertmp++;heissen.

Hab den Fehler nicht gesehen, danke habs geändert und CRC16 mit do while 
funktioniert.

Danke, das Forum ist top :D


Danke auch für den Text Karl Heinz.

von Joe F. (easylife)


Lesenswert?

Na denn.
Man kann es aus deinen Codeausschnitten nicht sehen, aber stelle auch 
sicher, dass du
1
tmp = array;
am Anfang der Funktionen stehen hast. Sonst schreibst du irgend wo 
hin...

von Eugen T. (der_eugen_thorben)


Lesenswert?

ja, hab ich, hab nicht alles gepostet.

von Karl H. (kbuchegg)


Lesenswert?

Joe F. schrieb:
> Na denn.
> Man kann es aus deinen Codeausschnitten nicht sehen, aber stelle auch
> sicher, dass du
>
1
tmp = array;
> am Anfang der Funktionen stehen hast. Sonst schreibst du irgend wo
> hin...

Wie schon gesagt:
schmeiss den Pointer komplett raus. Den braucht keiner. Damit schiesst 
du dir nur ins Knie.

von Karl H. (kbuchegg)


Lesenswert?

Und immer schön darauf achten, dass du dem Slave auch etwas Zeit 
einräumen musst.

Genau aus dem Grund :-) hast du wahrscheinlich hier im Master
1
      SPI_M_send_byte_to_hardware_slave(SEND_ARRAY_WITH_DEFINED_LENGTH_AND_CRC16);
2
    
3
        SPI_M_send_byte_to_hardware_slave(SPI_CHECK_BYTE);
4
        SPI_M_send_byte_to_hardware_slave(SPI_DUMMY_BYTE_FOR_RECEIVING);
5
        
6
        array_size = SPI_M_receive_byte();
ein zusärtliches SPI_CHECK_BYTE übertragen. EInfach nur damit Zeit 
vergeht.

Noch ein Tipp: Mach dir das Leben einfacher und nenn deine Funktionen 
nicht so lang. Man kann alles übertreiben und dann dreht sich die gute 
Absicht ins Gegenteil um.
Auch sollte aus den Ausführungen schon klar sein, dass im Master es 
keine getrennten Funktionen für Send und Receive braucht. Eine simple 
Funktion 'Transfer' reicht völlig aus. Sie kriegt das Byte, dass sie zum 
Slave übertragen soll und liefert das Byte, welches im Gegenzug vom 
Slave reingekommen ist.
1
uint8_t SPI_M_Transfer( uint8_t byte )
2
{
3
  SPDR = byte;
4
  while(!(SPSR & (1<<SPIF)))
5
    ;
6
  return SPDR;
7
}
1
     SPI_M_Transfer( SEND_ARRAY_WITH_DEFINED_LENGTH_AND_CRC16 );
2
     _delay_ms( 1 ); // gib dem Slave ein wenig Zeit, damit er auswerten kann
3
                     // was der Master eigentlich will und die Array Size bereit
4
                     // stellen kann
5
 
6
                     // Jetzt is aber gut. Schön langsam sollte der Slave fertig sein.
7
                     // Rück mal die Array Size raus!
8
     array_size = SPI_M_Transfer( SPI_DUMMY_BYTE_FOR_RECEIVING );
9
10
     _delay_ms( 1 ); // gib dem Slave ein wenig Zeit um das erste Array Element bereit zu stellen
11
     
12
     for( i = 0; i < array_size; ++i ) {
13
       array[i] = SPI_M_Transfer( SPI_DUMMY_BYTE_FOR_RECEIVING );
14
       _delay_ms( 1 ); // gib dem Slave ein wenig Zeit um das nächste ELement bereit zu stellen (falls noch was da ist)
15
     }
16
17
     ....
(die 1 ms werden jetzt etwas übertrieben lang sein!)


Wohingegen du den Slave am vernünftigesten mittels Interrupt und 
Statemachine implementierst
1
// Die Zustände, in denen die ISR sein kann
2
#define IDLE                 0
3
#define SEND_ARRAY_DATA      1
4
#define SEND_ARRAY_CRC_BYTE1 2
5
#define SEND_ARRAY_CRC_BYTE2 3
6
7
volatile uint8_t state = IDLE;
8
9
// Variablen, mit denen sich die Statemachine Informationen von
10
// einem ISR Aufruf zum nächsten hinterlassen kann
11
uint8_t nextElemToSend;
12
uint8_t crc16_high_byte;
13
uint8_t crc16_low_byte;
14
15
ISR( SPI_STC_vect )
16
{
17
  uint8_t byte = SPDR;
18
19
  if( state == IDLE ) {    // Idle Zustand. Das nächste byte vom Master ist ein Kommando
20
    if( byte == SEND_ARRAY_WITH_DEFINED_LENGTH_AND_CRC16 ) {
21
      SPDR = array_size;
22
      nextElemToSend = 0;
23
      state = SEND_ARRAY_DATA;
24
    }
25
  }
26
27
  else if( state == SEND_ARRAY_DATA ) {
28
    SPDR = array[nextElemToSend++];
29
    if( nextElemToSend == array_Size ) {
30
      state = SEND_ARRAY_CRC_BYTE1;
31
      ... CRC Berechnen    // gleich mal die CRC Berechnen, kann hier
32
                           // passieren, während wir darauf warten, dass
33
                           // der Master sich das letzte Array Element holt
34
  }
35
36
  else if( state == SEND_ARRAY_CRC_BYTE1 ) {
37
    SPDR = crc16_high_byte;
38
    state = SEND_ARRAY_CRC_BYTE2;
39
  }
40
41
  else if( state == SEND_ARRAY_CRC_BYTE2 ) {
42
    SPDR = crc16_low_byte;
43
    state = IDLE;
44
  }
45
}


In der Hauptschleife vom Slave überwachst du dann auch noch die Chip 
Select Leitung des Slave. Wenn der Master mitten in einer Übertragung 
den Chip Select wieder frei gibt (den Slave also inaktiv schaltet), dann 
setzt du den state wieder zurück auf IDLE. So bleibt dann der Slave 
nirgends hängen. Zieht der Master den Chip Select wieder auf aktiv, dann 
ist das erste Byte, das er überträgt das Kommando, mit dem er mitteilt 
was er will. Und genau so reagiert dann auch der Slave: Da er im IDLE 
ist, wird das erste Byte vom Master als Kommando aufgefasst und die 
ganze Statemachine kommt wieder ins laufen. Nirgends wird gewartet, es 
gibt keine Schleifen, in denen sich der Slave verhängen könnte und aus 
denen ihn der Master nicht mehr rauskriegt. Selbst dann, wenn der Master 
mitten in einer Übertragung abschmiert, läuft der Slave weiter. Mit dem 
nächsten Aktivieren des Slave (mittels Chip Select Leitung) kann der 
Master wieder ein Kommando einspeisen.

von Eugen T. (der_eugen_thorben)


Lesenswert?

Mensch, das ist ja nett. Ich denke, dass mit dem delay ist ganz gut und 
auch das mit dem CS, damit es nicht in einer while-schleife hängen 
bleibt

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.