Forum: Mikrocontroller und Digitale Elektronik SPI mit Interrupt: was macht der Compiler?


von Marc H. (mheimann84)


Angehängte Dateien:

Lesenswert?

Hallo,

ich bin gerade dabei spi funktionen für einen ATxmega128 zu schreiben, 
um ein display anzusteuern. Dabei orientiere ich mich an dem Code von 
Atmel: http://www.atmel.com/dyn/resources/prod_documents/AVR1309.zip

Da ich nur Daten versenden will, habe ich entsprechend den Code 
verkürzt. Im Anhang sieht man eine Aufzeichnung vom Logic Analyzer, wie 
es aussehen sollte. Es sollten also die vier folgenden Schritte 
stattfinden:

1. SS auf low
2. versende erstes byte
3. versende zweites byte
4. SS auf high

Ich bin quasi durch Zufall auf den "richtigen" Code gekommen. Der 
folgende Code macht nur Schritt 1 und 2, ein Interrupt wird nicht 
ausgelöst (die Initialisierung habe ich weggelassen):
1
void transmit_data( volatile struct spi_handle *sh )
2
{
3
  uint8_t data;
4
  
5
  // If more data
6
  if (sh->spi_stat.data_index < sh->spi_stat.data_size) {
7
    data = sh->spi_stat.data_pointer[sh->spi_stat.data_index];
8
    sh->spi_stat.data_index++; // Next byte
9
    sh->spi_reg->DATA = data;
10
  }
11
  
12
  // Transmission complete
13
  else {
14
    sh->spi_port->OUT |= SPI_SS_bm;
15
    sh->spi_stat.complete = 1;
16
  }
17
  
18
}
19
20
ISR(SPID_INT_vect)
21
{
22
  transmit_data( spi_handle_portd );
23
}
24
25
void spi_send( volatile struct spi_handle *sh, uint8_t *data, uint8_t size )
26
{
27
  while (sh->spi_stat.complete == 0); // wait until previous transmission finishes
28
  
29
  sh->spi_stat.data_pointer = data;
30
  sh->spi_stat.data_index = 0;
31
  sh->spi_stat.data_size = size;
32
  
33
  sh->spi_port->OUT &= ~SPI_SS_bm;
34
  transmit_data( sh );
35
}
36
37
38
int main( void )
39
{
40
  uint8_t data[2] = {0xaa, 0xf1};
41
  spi_send( sh, data, 2 );
42
  
43
  return 0;
44
}

Die nächste Version von transmit_data() war eigentlich die 
ursprüngliche. Hier wird Schritt 1, 2 und 3 durchgeführt, allerdings 
stimmen die Daten bei 3 nicht. Es wird einmal der Interrupt ausgelöst.
1
void transmit_data( volatile struct spi_handle *sh )
2
{
3
  // If more data
4
  if (sh->spi_stat.data_index < sh->spi_stat.data_size) {
5
    sh->spi_reg->DATA = sh->spi_stat.data_pointer[sh->spi_stat.data_index];
6
    sh->spi_stat.data_index++; // Next byte
7
  }
8
  
9
  // Transmission complete
10
  else {
11
    sh->spi_port->OUT |= SPI_SS_bm;
12
    sh->spi_stat.complete = 1;
13
  }
14
  
15
}

Da ich bereits ein funktionierendes hd44780 display habe, wollte ich mal 
den index ausgeben. Ich habe dann erstmal einen sprintf() Aufruf 
gebraucht, um den entsprechenden String zu bekommen, doch da 
funktionierte es auch schon. Die folgende Version macht alle 4 Schritte 
korrekt und die ISR wird auch zweimal aufgerufen:
1
void transmit_data( volatile struct spi_handle *sh )
2
{
3
  char buffer[16];
4
  sprintf( buffer, "index: %d", sh->spi_stat.data_index );
5
  
6
  // If more data
7
  if (sh->spi_stat.data_index < sh->spi_stat.data_size) {
8
    sh->spi_reg->DATA = sh->spi_stat.data_pointer[sh->spi_stat.data_index];
9
    sh->spi_stat.data_index++; // Next byte
10
  }
11
  
12
  // Transmission complete
13
  else {
14
    sh->spi_port->OUT |= SPI_SS_bm;
15
    sh->spi_stat.complete = 1;
16
  }
17
  
18
}

Ich glaube, dass ich irgendetwas noch nicht beachte. So will ich die 
Daten nicht übertragen, wer weiß was sonst noch passiert. Ich verwende 
avr-gcc. Kann mir bitte jemand sagen, warum hier so ein Chaos 
stattfindet?

von Hans W. (hans_w30)


Lesenswert?

Hy,
mit welcher Frequenz sendest du denn das ganze?
Bei mir gabs bei verwendetem (32 MHz (16MHz extern) Systemtakt) 4MHz 
Clock des SPI schon manchmal probleme. Versuche es mal mit ner kleineren 
Frequenz.
Was machst du eigentlich da mit deinem Buffer? Der wird doch nie benutzt 
oder?
Sicher das du nicht noch irgendwo anders was verändert hast?

von Marc H. (mheimann84)


Lesenswert?

Ausschnitt:
1
  // SPI configuration, p. 232f
2
  sh->spi_reg->CTRL = (0 << SPI_CLK2X_bp) | (1 << SPI_ENABLE_bp) | (0 << SPI_DORD_bp) | (1 << SPI_MASTER_bp) | (SPI_MODE_t)SPI_MODE_3_gc | (SPI_PRESCALER_t)SPI_PRESCALER_DIV4_gc;

Das ist nach Datenblatt clk/4. Ich verwende den internen Clock mit 
standardmäßig 2Mhz. Das ergibt 500 kHz, was ich auch mit dem Logic 
Analyzer messe.

von Hans W. (hans_w30)


Lesenswert?

Probier einfach mal den Clk clk/16 aus.
Sollte aber eigentlich nicht das Problem sein.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dass du deine Zeiger volatile deklarierst, wird zwar helfen, den
Code zu pessimieren, aber ob es auch hilft, die tatsächlichen Daten
passend zu holen?

Anyway, SPI mit Interrupt hat bei hinreichend schnell getaktetem
SPI gar keinen Sinn: die Interrupt-Latenz (bis die ISR tatsächlich
was tut) ist größer als die Zeit, die du auf das Ende der SPI-
Übertragung warten würdest.

von Marc H. (mheimann84)


Lesenswert?

Der buffer wird von sprintf verwendet. Der Witz ist ja, dass ich ihn ja 
eigentlich nicht brauche, aber dann habe ich den Fall mit nur einem ISR 
Aufruf (Version 2). Irgendetwas löst sprintf aus und dann gehts auf 
einmal.

Habe mal noch eine anderen prescaler wert verwendet: 
SPI_PRESCALER_DIV128_gc. Damit komme ich auf 15625 Hz. Allerdings machen 
jetzt alle Versionen nur noch Schritt 1 und 2.

Mit SPI_PRESCALER_DIV16_gc kommt das selbe wie mit 
SPI_PRESCALER_DIV128_gc raus :( Habe mit SPI_PRESCALER_DIV4_gc wirklich 
einen Zufallstreffer gelandet.

von Marc H. (mheimann84)


Lesenswert?

Ok, wollte nur sagen, dass ich es jetzt mit polling probieren werde. 
Später soll für das 128x64 display sowieso extra ein uC für die Grafik 
spendiert werden. Melde mich dann nochmal kurz, wenn es geklappt hat, 
dann belassen wir es einfach dabei. Falls jemand eine Lösung kennt, darf 
er/sie es trotzdem gerne mitteilen.

von Marc H. (mheimann84)


Lesenswert?

Also, das Aufräumen hat jetzt länger gedauert, als den Code 
umzuschreiben. Folgendes funktioniert nun mit verschiedenen prescaler 
Einstellungen:
1
void spi_send( struct spi_handle *sh, uint8_t *data, uint8_t size )
2
{
3
  uint8_t data_index;
4
  
5
  sh->spi_port->OUT &= ~SPI_SS_bm;
6
  for (data_index = 0; data_index < size; data_index++)
7
  {
8
    sh->spi_reg->DATA = data[data_index];
9
    while(!(sh->spi_reg->STATUS & SPI_IF_bm));
10
  }
11
  sh->spi_port->OUT |= SPI_SS_bm;
12
}

Danke, dass ihr euch Zeit genommen habt.

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.