Forum: Mikrocontroller und Digitale Elektronik [XMega] Wie Daten ohne Pausen über USART Maser SPI Mode lesen?


von Nicolas G. (Gast)


Angehängte Dateien:

Lesenswert?

Hi Leute,

ich versuche jetzt schon länger Daten von einem AD7606 über USART im 
MSPI Mode zu empfangen. Das funktioniert auch gut, allerdings habe ich 
immer diese hässlichen Pausen zwischen jedem gelesenen Byte (siehe 
Screenshot).

Aktuell sieht mein Code so aus:
1
volatile uint8_t blockTransferDone;
2
3
ISR(USARTE1_RXC_vect) {
4
  ((uint8_t*) &ad7606.values)[ad7606.transferIndex++] = ad7606.usart.usart->DATA;
5
  if (ad7606.transferIndex < 16) {
6
    USART_send(&ad7606.usart, 0);
7
  } else {
8
    blockTransferDone = 1;
9
  }
10
}
11
12
void AD7606_readBlock(void) {
13
  while (AD7606_isBusy());
14
  ad7606.CS_P->OUTCLR = _BV(CS);
15
  ad7606.transferIndex = 0;
16
  blockTransferDone = 0;
17
18
  USART_setReceiveInterrupt(&ad7606.usart, 1);
19
20
  USART_send(&ad7606.usart, 0);
21
22
  LED_set(&LED_debug, 0);
23
24
  while (blockTransferDone == 0);
25
26
  ad7606.CS_P->OUTSET = _BV(CS);
27
}
Ich denke die Funktionsnamen sind selbsterklärend.

Ich habe es auch schon ganz ohne Interrupt versucht und einfach nur auf 
das RECEIVE-Bit vom USART gewartet bis alle Bytes durch waren. 
Allerdings wurden dadurch die Pausen auch nicht kleiner.

Ich würde gerne die 16 Bytes ohne Pausen aus dem AD7606 heraus clocken 
und in den Puffer schreiben. Ich habe auch schon DMA ausprobiert, 
allerdings hat sich nach längerem Herumprobieren und Lesen heraus 
gestellt, dass das damit gar nicht geht, sondern nur wenn ich entweder 
den SPI Slave Mode nutze oder wenn ich mit USART Daten aus einem Puffer 
heraus schreiben will. Aber das Lesen geht so scheinbar nicht. Falls 
doch wäre ich natürlich sehr froh, wenn mir jemand einen passenden 
Beispielcode zeigen könnte.

Es muss doch möglich sein schneller Daten von einem Chip zu lesen, wenn 
man selbst die Clock in der Hand hat.

Einen hässlichen Umweg könnte ich mir noch vorstellen. Und zwar würde 
ich dafür SPI im Slave Mode nutzen und den Clock von einem internen 
Timer generieren lassen. Dann dürfte der Timer aber nur 16 * 8 mal an 
und aus schalten. Dieses Clock-Signal kommt dann aus dem XMega raus und 
geht wieder auf die SCLK-Line, die den XMega mit dem AD7606 verbinden. 
So würde der XMega denken er bekäme die Daten vom AD7606 rein geclockt, 
obwohl er selbst die Clock generiert. Aber eigentlich wäre diese Lösung 
ziemlich schrecklich. Da muss es doch was besseres geben...

Vielen Danke schon mal für alle Antworten.

von holger (Gast)


Lesenswert?

Wenn du etwas sehr schnell tun möchtest vermeidet man
Umwege. Funktionsaufrufe mit Übergabeparametern zum Beispiel.

>  USART_send(&ad7606.usart, 0);

Ein

SPI_DATA_REGISTER = 0;

tut es erheblich schneller.
Wie dein SPI_DATA_REGISTER für deinen Controller
heisst weiss ich nicht.

von Nicolas G. (Gast)


Lesenswert?

Im Grunde mache ich da keine Umwege, weil die Funktionen alle inline 
sind. Der Compiler eliminiert an der Stelle den Funktionsaufruf.
1
static inline void USART_send(HWUSART* const usart, uint8_t const value) ATTR_ALWAYS_INLINE ATTR_NON_NULL_PTR_ARG(1);
2
static inline void USART_send(HWUSART* const usart, uint8_t const value) {
3
  while ((usart->usart->STATUS & USART_DREIF_bm) == 0);
4
  usart->usart->DATA = value;
5
}
Aber ich könnte eventuell die unnütze while-Schleife an der Stelle 
heraus nehmen.

von holger (Gast)


Lesenswert?

>Im Grunde mache ich da keine Umwege, weil die Funktionen alle inline
>sind. Der Compiler eliminiert an der Stelle den Funktionsaufruf.

Ok, Step2. Bei SPI lohnt es sich nicht per Interrupt
zu arbeiten wenn das SPI sehr schnell ist. Das wird oft
teurer als einfach nur auf das Ende der SPI Übertragung zu pollen.

von Nicolas G. (Gast)


Angehängte Dateien:

Lesenswert?

Interessanterweise ist es jetzt etwas schneller, weil ich den Interrupt 
weg gelassen habe (siehe Screenshot). Aber die Pausen bleiben und die 
stören mich am meisten. Der AD7606 kann normalerweise 200000 Samples pro 
Sekunde samplen, aber wenn ich die nicht schnell genug ausgelesen 
bekomme, dann klappt das eben hinten und vorne nicht. Das ist schade.

Ich dachte bei so was einfachem wie Daten von einem Chip zu empfangen, 
geht das alles etwas fixer.

von der tscheche (Gast)


Lesenswert?

Schreibe es doch mal im assembler. Evtl. kannst du die while-schleife 
weglassen und stattdessen ein paar nop's da haben, so dass du den 
nächsten byte in den register genau dann schreibst, wenn der nix mehr zu 
tun hat...

von c-hater (Gast)


Lesenswert?

der tscheche schrieb:

> Schreibe es doch mal im assembler.

Das ist hier nicht das Problem!

Das Problem ist, daß der TO nicht verstanden hat, daß das USART double 
buffered ist, auch im SPI-Mode. Oder er hat nicht verstanden, was die 
Implikationen so eines Doppelpuffers sind. Die erfordern nämlich ein 
anderes Programmierschema.

Um ein lückenlosen Datentransfer hinzubekommen, muß man zu Anfang 
einfach zweimal in das Datenregister schreiben und erst in der Folge bei 
jedem Interrupt nur einmal. Am Ende muß man beachten, daß nach dem 
letzten Schreibvorgang noch zweimal der Interrupt erfolgt.

Eigentlich ganz simpel.

von Nicolas G. (Gast)


Lesenswert?

Achsooooo!

Das klingt doch mal einleuchtend. Das kann ich leider erst wieder 
nächste Woche testen, aber ich nehme das soll dann so ähnlich aussehen:
1
while ((usart->usart->STATUS & USART_DREIF_bm) == 0);
2
usart->usart->DATA = 0;
3
usart->usart->DATA = 0;
4
while (!(usart->usart->STATUS & USART_TXCIF_bm)); usart->usart->STATUS = USART_TXCIF_bm;
5
BYTE0 = usart->usart->DATA;
6
usart->usart->DATA = 0;
7
while (!(usart->usart->STATUS & USART_TXCIF_bm)); usart->usart->STATUS = USART_TXCIF_bm;
8
BYTE1 = usart->usart->DATA;
9
usart->usart->DATA = 0;
10
while (!(usart->usart->STATUS & USART_TXCIF_bm)); usart->usart->STATUS = USART_TXCIF_bm;
11
BYTE2 = usart->usart->DATA;
12
while (!(usart->usart->STATUS & USART_TXCIF_bm)); usart->usart->STATUS = USART_TXCIF_bm;
13
BYTE3 = usart->usart->DATA;

Beziehungsweise mit Interrupt:
1
volatile uint8_t blockTransferDone;
2
3
ISR(USARTE1_RXC_vect) {
4
  ((uint8_t*) &ad7606.values)[ad7606.transferIndex++] = ad7606.usart.usart->DATA;
5
  if (ad7606.transferIndex < 15) {  //Und hier einmal weniger
6
    USART_send(&ad7606.usart, 0);
7
  }
8
  if (ad7606.transferIndex == 16) {
9
    blockTransferDone = 1;
10
  }
11
}
12
13
void AD7606_readBlock(void) {
14
  while (AD7606_isBusy());
15
  ad7606.CS_P->OUTCLR = _BV(CS);
16
  ad7606.transferIndex = 0;
17
  blockTransferDone = 0;
18
19
  USART_setReceiveInterrupt(&ad7606.usart, 1);
20
21
  USART_send(&ad7606.usart, 0);
22
  USART_send(&ad7606.usart, 0);  //Einmal mehr wegen Doublebuffer
23
24
  LED_set(&LED_debug, 0);
25
26
  while (blockTransferDone == 0);
27
28
  ad7606.CS_P->OUTSET = _BV(CS);
29
}

Ich melde mich dann nochmal, sobald ich es ausprobiert habe. Ich muss eh 
vorher noch im Datenblatt lesen, ob man das Doublebuffering noch 
aktivieren muss oder nicht.

von c-hater (Gast)


Lesenswert?

Nicolas G. schrieb:

[Zwei Codevarianten]

Ich habe mir jetzt bloß mal die Pollingvariante genauer angeschaut. Die 
ist im Prinzip richtig, aber trotzdem leider vollkommen falsch.

Wenn du allerdings überall TXCIF durch DREIF ersetzt und umgekehrt, dann 
paßt es schon sehr gut.

TXC heißt "transmit completed". Das ist genau dann der Fall, wenn 
beide Buffer leer sind. DRE hingegen heißt "data register empty". Das 
ist dann der Fall, wenn mindestens einer der beiden Buffer Daten 
entgegennehmen kann.

Dann kannst du wohl auch noch das Löschen des Flags nach jedem Byte 
rauswerfen, das DREIF wird wahrscheinlich beim Schreiben in's 
Datenregister automatisch zurücksetzt. So ist es jedenfalls bei den 
"normalen" AVRs.
Dafür mußt du aber das TXCIF einmal löschen, dies am Besten als Zeile 2 
einfügen.

Wenn du das in der Pollingvariante durchdacht hast, wirst du darauf 
kommen, daß du in der Interruptvariante natürlich den DRE-Int für den 
Datentransfer benutzen mußt.

Der Anfang sieht dann ganz genauso aus wie beim Polling:

while ((usart->usart->STATUS & USART_TXCIF_bm) == 0);
usart->usart->STATUS = USART_TXCIF_bm;
usart->usart->DATA = 0;
usart->usart->DATA = 0;

Jetzt kommt der feine Unterschied, genau hier mußt du nämlich jetzt den 
DRE-Int enablen. Alles andere passiert in dessen ISR.

von Nicolas G. (Gast)


Lesenswert?

Wow, Danke!

Das macht alles wesentlich klarer jetzt für mich. Wieso erklärt Atmel 
das eigentlich nicht so schön einfach in ihren ganzen Dokumenten? 
Tststs...

Sehr cool, dann werde ich das beim nächsten mal ausprobieren, wenn ich 
wieder im Bastelkeller bin. Übrigens geht es dabei um folgendes Projekt: 
https://www.youtube.com/watch?v=K0-o1sqe4-0

von Nicolas G. (Gast)


Lesenswert?

Ich merke gerade, dass ich wohl nur zu blöd zum Lesen war die ganze 
Zeit. Da steht's doch:

"TXCIF and DREIF may seem similar, but there is a small difference that 
can be utilized to speed up data transfers. TXCIF is not set until the 
USART has completed transmitting all data in the transmit shift register 
and the transmit buffers are empty. DREIF on the other hand, is set 
already when the buffer has room for more data. This means new data can 
be filled into the buffer before all data is transmitted, and the small 
pauses between each transmitted characters can be avoided."

Da sprechen sie genau über die Pausen, die ich meine. Das schlimme ist, 
dass ich das selbe Problem schon bei avr-freaks geschildert habe, was 
bei Atmel als offizielles Supportforum angegeben wird, aber da kriegt 
man von keinem richtig geholfen...

von Nicolas G. (Gast)


Lesenswert?

So, Dreifachpost. Ich habe noch eine Frage.

Meiner Meinung nach müsste es bei reinem Polling für 4 Bytes so 
aussehen:
1
// Warte bis die vorherige Transaction komplett beendet ist.
2
while (!(usart->STATUS & USART_TXCIF_bm));
3
4
// TXCIF-Bit löschen, indem wir es setzen
5
usart->STATUS |= USART_TXCIF_bm;
6
7
// Schreibe beliebigen Wert in DATA
8
usart->DATA = 1:
9
10
// Nach einem CLK Cycle wird DATA in TX Buffer kopiert und wir
11
// können direkt einen neuen beliebigen Wert in DATA schreiben.
12
usart->DATA = 2;
13
14
// Nach einem USART CLK Cycle wird TX Buffer in TX Shift kopiert.
15
// Daraufhin wird nach einem CLK Cycle DATA in TX Buffer kopiert.
16
// Jetzt warten wir darauf, dass TX Buffer wieder frei wird.
17
while (!(usart->STATUS & USART_DREIF_bm));
18
19
// An dieser Stelle steht 2 bereits in TX Shift und wird übertragen.
20
21
// Wir schreiben einen weiteren beliebigen Wert in DATA
22
usart->DATA = 3;
23
24
// Wir warten, dass wir den eben empfangenen Wert lesen können.
25
// Dies ist aber vermutlich gar nicht nötig.
26
while (!(usart->STATUS & USART_RXCIF_bm));
27
28
// Dann lesen wir den Wert
29
buffer(0) = usart->DATA;
30
31
// Und jetzt das selbe Spielchen wie zuvor:
32
while (!(usart->STATUS & USART_DREIF_bm));  // TX Buffer frei?
33
usart->DATA = 4;
34
while (!(usart->STATUS & USART_RXCIF_bm));  // Wert verfügbar?
35
buffer(1) = usart->DATA;
36
37
// Jetzt müssen wir nur noch zwei mal warten um die letzten beiden
38
// Werte zu empfangen:
39
40
while (!(usart->STATUS & USART_RXCIF_bm));
41
buffer(2) = usart->DATA;
42
while (!(usart->STATUS & USART_RXCIF_bm));
43
buffer(3) = usart->DATA;
Und ich glaube, dass man es noch etwas einfacher machen kann. Dieser 
Teil
1
while (!(usart->STATUS & USART_DREIF_bm));  // TX Buffer frei?
2
usart->DATA = 4;
3
while (!(usart->STATUS & USART_RXCIF_bm));  // Wert eingelesen?
4
buffer(1) = usart->DATA;
müsste eigentlich so vereinfacht werden können, ohne dass es irgendwas 
an der Geschwindigkeit ändern würde:
1
// Und jetzt das selbe Spielchen wie zuvor:
2
while (!(usart->STATUS & (USART_DREIF_bm | USART_RXCIF_bm)));  // TX Buffer frei und Wert verfügbar?
3
usart->DATA = 4;
4
buffer(1) = usart->DATA;
Testen kann ich das ganze sowieso erst wieder nächste Woche, aber über 
eine vorläufige Bestätigung würde ich mich dennoch freuen.

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.