SPI übertragungsablauf

Gast #4639211
Lesenswert?
• ▲
▼
Tag,
eine Frage zum ablauf einer Übertragung von Nachrichten über SPI.

Controller A sendet Daten nach Controller B.
Controller setzt einen Pin auf high und startet direkt danach die 
ÜBertragung. Nach der Übertragung wird der Pin wieder auf Low gesetzt.
Im Controller B wird durch dem von Controller A auf high gesetztem Pin 
ein Interrupt ausgelöst. In der ausgelösten ISR werden die Daten, 
herausgeholt.

Wie schnell sind die Daten vom Controller A da? Kann es vorkommen, dass 
Controller B die Daten abfragt obwohl die Übertragung noch gar nicht 
abgeschlossen ist? Also Controller A sendet noch und Controller B fragt 
schon ab.

Eigentlich werden die Daten doch alle zunächst automatisch in einem 
Register gespeichert, wo ich mir die nur rausziehe oder?

Aber wie kann ich sicher sein, dass die Übertragung zu ende ist?
Oder wie frage ich am besten nach den Daten ab?

Kimmy
#4639221
Lesenswert?
• ▲
▼
@ Kimmy (Gast)

>Controller A sendet Daten nach Controller B.
>Controller setzt einen Pin auf high und startet direkt danach die
>ÜBertragung.

Jain. Meistens sind die Chip Select Signale Low aktiv, d.h. für die 
Auswahl des SPI-Slave wuird das SIgnal auf LOW gesetzt und am Ende 
wieder auf High.

>Nach der Übertragung wird der Pin wieder auf Low gesetzt.
>Im Controller B wird durch dem von Controller A auf high gesetztem Pin
>ein Interrupt ausgelöst. In der ausgelösten ISR werden die Daten,
>herausgeholt.

Jain. Der Empfänger kann auch mehrere Bytes empfangen und dann muss er 
schon, sobal das CS-Signal aktive (=low) wird in die passende ISR 
springen.

>Wie schnell sind die Daten vom Controller A da?

So schnell, wie er sie sendet.

> Kann es vorkommen, dass
>Controller B die Daten abfragt obwohl die Übertragung noch gar nicht
>abgeschlossen ist?

Nein, denn Controller A gibt den Takt vor.

>Also Controller A sendet noch und Controller B fragt
>schon ab.

Geht nicht.

>Eigentlich werden die Daten doch alle zunächst automatisch in einem
>Register gespeichert, wo ich mir die nur rausziehe oder?

Ja. Und wenn die Übertragung von einem Byte abgeschlossen ist, wird ein 
passendes Flag in einem IO-Register gesetzt. Das muss der Empfänger 
abfragen und dann die Daten auslesen.
Gast #4639226
Lesenswert?
• ▲
▼
Ja, das mit dem Aktive Low für den SlaveSelect war mir bekannt, hatte 
ich jetzt aber vergessen. Werde ich aber so umsetzen.


Falk B. schrieb:
> Ja. Und wenn die Übertragung von einem Byte abgeschlossen ist, wird ein
> passendes Flag in einem IO-Register gesetzt. Das muss der Empfänger
> abfragen und dann die Daten auslesen.


Also Controller A setzt den Pin auf Low und fängt an zu übertragen.
Controller B springt in die ISR und wartet, bis die Übertragung beendet 
ist.

richtig?
Gast #4639247
Lesenswert?
• ▲
▼
Kimmy schrieb:
> Controller A sendet Daten nach Controller B.
...
> Wie schnell sind die Daten vom Controller A da? Kann es vorkommen, dass
> Controller B die Daten abfragt obwohl die Übertragung noch gar nicht
> abgeschlossen ist? Also Controller A sendet noch und Controller B fragt
> schon ab.

Am besten dürfte es sein, dass wir nicht von Controller A und B sondern 
von Master und Slave sprechen. Der Master bestimmt die Geschwindigkeit 
(er gibt die Clock vor) und dementsprechend auch wann was passiert. Nur 
der Master kann Übertragungen beginnen. Das muss man als Programmierer 
natürlich im Master richtig handeln, dass sich da nichts überschneidet. 
Der Slave muss natürlich entsprechend reagieren können, wenn der Master 
seine Anfragen sendet - das muss bei der Programmierung des Masters 
beachtet werden.

Kimmy schrieb:
> Controller B springt in die ISR und wartet, bis die Übertragung beendet
> ist.
>
> richtig?

Das kann man so pauschal nicht sagen. Meistens wird der Interrupt erst 
ausgelöst, wenn die Übertragung (für gewöhnlich 8 bit) beendet ist. Dann 
steht das Ergebnis in einem Register und kann verarbeitet werden. 
Größere Controller haben einen Puffer oder DMA, damit man nicht ständig 
mit Interrupt Requests zugeballert wird. Das wäre z.B. bei einem 30 MHz 
SPI ziemlich nervig, wenn mit ca. 3,75MHz Interrupt Requests (IRQs) 
kommen.
Gast #4639263
Lesenswert?
• ▲
▼
Sto schrieb:
> Meistens wird der Interrupt erst
> ausgelöst, wenn die Übertragung (für gewöhnlich 8 bit) beendet ist.

Dazu habe ich noch einmal eine Frage
Das Auslösen des Interrupts mache ich ja mit einem extra pin. Ich muss 
bei der Programmierung selber den Pin auf Low und dann wieder auf High 
setzen, wodurch beim Slave der Interrupt ausgelöst wird.
Soll ich diesesn wechsel dann erst nach dem senden machen?

Die Sende-Funktion sieht ja wie folgt aus
1
void spi_transmit (uint8_t data)
2
{
3
  SPDR = data;
4
  while((SPSR & (1<<SPIF))==0);
5
}

angenommen, der SlaveSelect Pin ist auf PB0

soll ich dann nach der while-schleife PB0 auf low setzen und sofort 
wieder auf high?

ich hätte jetzt vor der while schleife PB0 auf low gesetzt und danach 
wieder auf high.
Dann wäre es aber wie ich denke, dass der Slave empfängt, während der 
Master noch sendet

ISR vom Slave:
1
ISR(INT0_vect){
2
while(!(SPSR & (1<<SPIF)))
3
received_data = SPDR;
4
}
#4639299
Lesenswert?
• ▲
▼
@ Kimmy (Gast)

>> Meistens wird der Interrupt erst
>> ausgelöst, wenn die Übertragung (für gewöhnlich 8 bit) beendet ist.

Das kann man machen, das macht den Slave aber nochmal langsamer.

>Das Auslösen des Interrupts mache ich ja mit einem extra pin. Ich muss
>bei der Programmierung selber den Pin auf Low und dann wieder auf High
>setzen, wodurch beim Slave der Interrupt ausgelöst wird.

Ja.

>Soll ich diesesn wechsel dann erst nach dem senden machen?

Nein.

>void spi_transmit (uint8_t data)
>{
>  SPDR = data;
>  while((SPSR & (1<<SPIF))==0);
>}

Soweit OK.

>angenommen, der SlaveSelect Pin ist auf PB0

Also sind deine Controller AVRs.

>soll ich dann nach der while-schleife PB0 auf low setzen und sofort
>wieder auf high?

Nein! VOR der Übertragung setzt man CS auf LOW, DANACH wieder auf High!

>ich hätte jetzt vor der while schleife PB0 auf low gesetzt und danach
>wieder auf high.

Genau so!

>Dann wäre es aber wie ich denke, dass der Slave empfängt, während der
>Master noch sendet

Muss er doch!


>ISR vom Slave:

>ISR(INT0_vect){
>while(!(SPSR & (1<<SPIF)))
>received_data = SPDR;
>}

Genau so.
Gast #4639385
Lesenswert?
• ▲
▼
Ja, das habe ich auch, aber da ist nur die Initalisierung und das Senden

The following code demonstrates how to use the USI module as a SPI 
Slave:
1
init:
2
ldi r16,(1<<USIWM0)|(1<<USICS1)
3
out USICR,r16
4
...
5
SlaveSPITransfer:
6
out USIDR,r16
7
ldi r16,(1<<USIOIF)
8
out USISR,r16
9
SlaveSPITransfer_loop:
10
sbis USISR,USIOIF
11
rjmp SlaveSPITransfer_loop
12
in r16,USIDR
13
ret
Gast #4639393
Lesenswert?
• ▲
▼
>Ja, das habe ich auch, aber da ist nur die Initalisierung und das Senden

Nein, dort steht nicht senden sondern Transfer.
Und diese Zeile

in r16,USIDR

holt die empfangenen Daten ab.

Ansonsten ist dieses USI Geraffel eher sowas wie ein
ständiges jucken im Arsch.
Moderator (Firma: Titel) Persönliche Seite #4639406
Lesenswert?
• ▲
▼
Kimmy schrieb:
> Frage zum ablauf einer Übertragung von Nachrichten über SPI.
Da sollte man sich vielleicht vorher mal im Klaren sein, was "SPI" 
überhaupt ist. Das sind letztlich nur 2 gekoppelte Schieberegister mit 
nachgeschalteten Latches: 
http://www.lothar-miller.de/s9y/archives/15-SPI.html

Und wenn man sich diese Ablauf bewusst gemacht hat, kann man die 
Zeitpunkte, wo diese Register am besten beschrieben werden sollten, viel 
besser vorstellen. Dann ist auch klar, dass ein Slave eigentlich immer 
"alte" Daten sendet. Und dass ein Slave nichts senden kann, wenn der 
Master nich4s sendet...
Gast #4639516
Lesenswert?
• ▲
▼
>Das Auslösen des Interrupts mache ich ja mit einem extra pin.

Das ist falsch.
Der Slave Select löst i.A keinen Interrupt aus, sondern steuert
nur die SPI HW.

Interrupt hat ja nur einen Sinn, wenn ein Datum zum Slave übertragen 
wurde und von der SW abgeholt werden kann.

>Controller B springt in die ISR und wartet, bis die Übertragung beendet
>ist.
>
>richtig?

Nein. Da ist nix mit Warten. Slave Interrupt wenn Übertragung fertig.
#4639527
Lesenswert?
• ▲
▼
@ jkidghwd (Gast)

>>Das Auslösen des Interrupts mache ich ja mit einem extra pin.

>Das ist falsch.
>Der Slave Select löst i.A keinen Interrupt aus, sondern steuert
>nur die SPI HW.

Nicht zwangsläufig. Ein AVR als Slave ist eher langsam, der braucht ein 
paar Dutzend Takte, eher er im Interrupt ist (in C).

>>Controller B springt in die ISR und wartet, bis die Übertragung beendet
>>ist.
>
>>richtig?

>Nein. Da ist nix mit Warten. Slave Interrupt wenn Übertragung fertig.

Wenn er erst in die SPI-ISR springt, wenn die Übertragung beendet ist, 
dauert das relativ lange, das kann bei hohen SPI-Taktraten zu langsam 
sein.
Gast #4639534
Lesenswert?
• ▲
▼
Jetzt verstehe ich immer weniger.
Der Master Sendet wie folgt Daten zum Slave
1
void spi_transmit (uint8_t data)
2
{
3
  PORTB &= ~(1<<PB2);
4
  SPDR = data;
5
  while((SPSR & (1<<SPIF))==0);
6
  PORTB |= (1<<PB2);
7
}


PORTB &= ~(1<<PB2); SlaveSelect wird auf low geschaltet
  -> Slave sprint in ISR (Leitung ist mit INT0 vom Slave verbunden)

SPDR = data; Schreibe Daten ins Register und Sende diese Raus

while((SPSR & (1<<SPIF))==0); Warte bis fertig gesendet worden ist

Also bevor die Daten ins Register geschrieben worden sind und er Master 
noch nicht fertig gesendet hat, springt der Slave in die ISR und wartet, 
dass der Master fertig ist.
Gast #4639803
Lesenswert?
• ▲
▼
an Kimmy/Frank:

In der INT0-ISR wird USI zurückgesetzt, um eine definierte 
Anfangsbedingung zu schaffen. Gewartet wird anschließend nicht, der 
Slave kann irgendetwas anderes machen. Wenn der Master fertig gesendet 
hat, springt der Slave in die USI_OVF-ISR und holt das eben empfangene 
Byte ab.

(Ich bin kein Spezialist in Sachen USI)
Gast #4640457
Lesenswert?
• ▲
▼
Hallo, leider funktioniert das alles noch nicht so wie gewünscht. Daher 
bitte ich noch einmal um eure Hilfe

Atmega SPI:
1
#define PORT_SPI    PORTB
2
#define DDR_SPI     DDRB
3
#define DD_MISO     DDB4
4
#define DD_MOSI     DDB3
5
#define DD_SS       DDB2
6
#define DD_SCK      DDB5
7

8
volatile unsigned char spi_data;
9
volatile unsigned char new_spi_data;
10

11
void spi_init()
12
// Initialize pins for spi communication
13
{
14
  /* Set MOSI SCK and SS output, all others input */
15
  DDR_SPI = ((1 << DD_MOSI) | (1 << DD_SCK) | (1 << DD_SS));
16
  PORT_SPI |= (1 << DD_SS);
17
  DDR_SPI &= ~(1 << DD_MISO);
18

19
  /* Enable SP */
20
  SPCR = ((1<<SPE)|               // SPI Enable
21
      (1<<SPIE)|              // SPI Interupt Enable
22
      (0<<DORD)|              // Data Order (0:MSB first / 1:LSB first)
23
      (1<<MSTR)|              // Master/Slave select
24
      (0<<SPR1)|(1<<SPR0)|    // SPI Clock Rate
25
      (0<<CPOL)|              // Clock Polarity (0:SCK low / 1:SCK hi when idle)
26
      (0<<CPHA));             // Clock Phase (0:leading / 1:trailing edge sampling)  
27
}
28

29
uint8_t spi_fast_shift (uint8_t data)
30
// Clocks only one byte to target device and returns the received one
31
{
32
  usart_transmit_string("\r\nsend data: ");
33
  PORT_SPI &= ~(1 << DD_SS);
34
  SPDR = data;
35
  while((SPSR & (1<<SPIF))==0);
36
  return SPDR;
37
  PORT_SPI |= (1 << DD_SS);
38
}

main Atmega:
1
int main(void){
2
  spi_data = 0;
3
  usart_data = 0;
4
  new_usart_data = 0;
5
  usart_init(USART_MYUBRR);
6
  usart_transmit_string("RX devices ready"); 
7
  spi_init();
8
  usart_transmit_string("\r\nSPI device ready");
9

10
  sei(); // Global Interrupts activate
11
  while (1){
12
    if(new_usart_data == 1){
13
      usart_transmit_string("\r\nnew data: ");
14
      spi_data = spi_fast_shif(0x02);
15
      usart_transmit_char(spi_data);
16
      new_usart_data = 0;
17

18
    }
19

20
  }
21
}


Wie in der Funktion spi_fast_shift zu sehen ist, setze ich vor dem 
Senden den Pin PB2 auf low, wodurch der ATtiny in die ISR springt. 
Zunächst soll dieser nur eine LED Toggeln, damit ich sehe, der Attiny in 
die ISR gesprungen ist.



Attiny SPI
1
#define PORT_SPI    PORTB
2
#define DDR_SPI     DDRB
3
#define DD_MISO     DDB4
4
#define DD_MOSI     DDB3
5
#define DD_SS       DDB2
6
#define DD_SCK      DDB5
7

8
volatile unsigned char spi_data;
9
volatile unsigned char new_spi_data;
10

11
void spi_init()
12
// Initialize pins for spi communication
13
{
14
  /* --- SLAVE --- */
15
  /* Set MISO as output, all others input */ 
16
  DDR_SPI = (1 << DD_MISO);
17
  DDR_SPI &= ~((1 << DD_MOSI) | (1 << DD_SCK) | (1 << DD_SS));
18

19
  /* Enable SP */
20
  USICR = ((0<<USISIE)|            // Start Condition Interrupt Enable
21
       (0<<USIOIE)|            // Counter Overflow Interrupt Enable
22
       (0<<USIWM1)|(1<<USIWM0)|      // Three-wire mode
23
       (1<<USICS1)|(0<<USICS0)|(0<<USICLK)|
24
       (0<<USITC));            // Toggle Clock Port Pin
25

26
  extern_interrupt_init();
27

28
}
29

30
void extern_interrupt_init(void){
31
  DDRB &= ~(1 << DDD2); // External Interrupt on INT0
32

33
  MCUCR |= (1 << ISC01); // INT0 falling edge
34
  MCUCR &= ~(1 << ISC00); // INT0 falling edge
35

36
  GIMSK |= (1 << INT0);  // enable INT0 interrupt
37
}
38

39
ISR(INT0_vect){
40
  PORTB ^= (1<<PORTB5); // Toggle PD5
41
  while(!(USIDR& (1<<USIOIF)))
42
  spi_data = USIDR;
43
  new_spi_data = 1;
44
}

Attiny main
1
int main(void){
2
  _delay_ms(200);
3
  spi_data = 0;
4
  usart_data = 0;
5
  new_usart_data = 0;
6
  usart_init(USART_MYUBRR);
7
  usart_transmit_string("USART devices ready \r\n"); 
8
  spi_init();
9
  usart_transmit_string("SPI devices ready \r\n"); 
10
  sei(); // Global Interrupts activate
11
  DDRB |= (1<<PORTD5); // PD5 -> Output
12
  PORTD &= ~(1>>PORTD5); // PD5 -> Low
13
  while (1){
14
    if(new_spi_data==1){
15
      new_spi_data = 0;
16
    }
17

18
  }
19
}

Was passiert?
der Atmega resettet sich bei der funktion spi_fast_shift. Der Attiny 
toggelt auch nicht die LED. Verbunden habe ich es wie folgt:
Atmega -> ATtiny
MISO -> MISO
MOSI -> MOSI
SCK -> SCK
PB2 -> INT0
Gast #4640470
Lesenswert?
• ▲
▼
>der Atmega resettet sich bei der funktion spi_fast_shift.

Natürlich tut er das. Du gibst den SPI Interrupt frei, hast
aber keine Interruptroutine dafür. Bumm.

Ausserdem wird der Slave Select nie wieder high:

  return SPDR;
  PORT_SPI |= (1 << DD_SS);

Da sollte der Compiler doch wenigstens ein "unreachable code"
als Warnung ausspucken.
Gast #4640486
Lesenswert?
• ▲
▼
holger schrieb:
> return SPDR;
>   PORT_SPI |= (1 << DD_SS);

Ja, dass war ein Denkfehler. Ich wollte erst am Ende der Funktion die 
Leitung wieder auf High setzen, habe aber nicht berücksichtigt, dass die 
Funktion beim return beendet wird.

Das mit dem Interrupt hatte ich auch falsch gedacht. Habe beides 
geändert. Jetzt stürzt der Atmega wenigstens schon einmal nicht ab.
Der Attiny scheint aber nicht in die ISR zu springen, da die LED aus 
bleibt.
Gast #4640493
Lesenswert?
• ▲
▼
S. Landolt schrieb:
> Diese Definitionen unter 'Attiny SPI' - reden wir überhaupt noch vom
> ATtiny2313?

Ja. Der Attiny hat ja kein richtiges SPI, sondern über USI.

Bastian W. schrieb:
> If(new_usart_data ==1)
>
> wird nicht ausgeführt da du die Variable mit 0 initialisierst.

Sollte aber in der ISR auf 1 gesetzt werden, wenn eine neue Nachricht 
eintrifft

S. Landolt schrieb:
>>   PORTD &= ~(1>>PORTD5); // PD5 -> Low
> Nanu?

Da ist die LED angeschlossen.
Gast #4640496
Lesenswert?
• ▲
▼
>   while(!(USIDR& (1<<USIOIF)))
USIOIF steht in USISR.

>Ja. Der Attiny hat ja kein richtiges SPI, sondern über USI.
Schon, aber ich lese in meinem Datenblatt eine andere Pin-Belegung.

> Da ist die LED angeschlossen.
Schon, aber Sie schieben nach rechts.


Am besten nochmal, in aller Ruhe, gründlich über die Bücher.
Gast #4640501
Lesenswert?
• ▲
▼
>> Das oben gezeigte Programm ist doch nie und nimmer durch den Compiler
>> und Linker gekommen.

>doch

>Build succeeded.

Merkwürdig.

      spi_data = spi_fast_shif(0x02);

Hinter dem _shif fehlt das 't'.
Gast #4640518
Lesenswert?
• ▲
▼
spi.c
1
#include "spi.h"
2

3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5

6
void extern_interrupt_init(void);
7

8
#define PORT_SPI    PORTB
9
#define DDR_SPI     DDRB
10
#define DD_MISO     DDB4
11
#define DD_MOSI     DDB3
12
#define DD_SS       DDB2
13
#define DD_SCK      DDB5
14

15
volatile unsigned char spi_data;
16
volatile unsigned char new_spi_data;
17

18
void spi_init()
19
// Initialize pins for spi communication
20
{
21
  /* --- SLAVE --- */
22
  /* Set MISO as output, all others input */ 
23
  DDR_SPI = (1 << DD_MISO);
24
  DDR_SPI &= ~((1 << DD_MOSI) | (1 << DD_SCK) | (1 << DD_SS));
25

26
  /* Enable SP */
27
  USICR = ((0<<USISIE)|            // Start Condition Interrupt Enable
28
       (0<<USIOIE)|            // Counter Overflow Interrupt Enable
29
       (0<<USIWM1)|(1<<USIWM0)|      // Three-wire mode
30
       (1<<USICS1)|(0<<USICS0)|(0<<USICLK)|
31
       (0<<USITC));            // Toggle Clock Port Pin
32

33
  extern_interrupt_init();
34

35
}
36

37
void extern_interrupt_init(void){
38
  DDRB &= ~(1 << DDD2); // External Interrupt on INT0
39

40
  MCUCR |= (1 << ISC01); // INT0 falling edge
41
  MCUCR &= ~(1 << ISC00); // INT0 falling edge
42

43
  GIMSK |= (1 << INT0);  // enable INT0 interrupt
44
}
45

46
ISR(INT0_vect){
47
  PORTB ^= (1<<PORTB5); // Toggle PD5
48
  while(!(USIDR& (1<<USIOIF)))
49
  spi_data = USIDR;
50
  new_spi_data = 1;
51
}

in spi.h
extern volatile unsigned char spi_data;
extern volatile unsigned char new_spi_data;
Gast #4640562
Lesenswert?
• ▲
▼
Ich habe noch einmal eine etwas andere Frage.
Und zwar flashe ich den ATtiny mit dem Diamex Prog S Programmer. Da 
dieser allerdings ebenfalls an der SPI schnittstelle verbunden ist, gibt 
es Probleme, wenn der ATmega gleichzeitig dran hängt. Bisher habe ich 
immer die Kabel zwischen ATtiny und Atmega entfernt, geflasht und neu 
verbunden. Gibt es da irgendwie eine Alternative?

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren