Forum: Mikrocontroller und Digitale Elektronik Zeitproblem UART Daten speichern und mit Timerinterrupt ausgeben.


von dieter (Gast)


Lesenswert?

Hi.

Folgende Konstellation. Ich sende dem Mikrocontroller über die serielle 
Schnittstelle 1 Byte große Werte mit 1 MBaud/s also 125000 kByte/s.
Die Daten sind Amplitudenwerte einer Audiodatei mit 44100 Hz 
Samplingfrequenz. Der Timer im µC soll also alle 1/44100 s einen 
Amplitudenwert ausgeben.

Der µC Takt ist 16 MHz.


Wenn ein Byte empfangen wird, wird der USART Receive Interrupt 
ausgelöst, indem folgendes steht:
1
ISR (USART_RXC_vect)
2
{
3
  //Ausgabe mit Buffer
4
  _inline_fifo_put (&infifo, UDR);
5
}

Der Interrupt schreibt mit der FIFO Funktion die Daten die ankommen in 
die Warteschlange. (Die FIFO Funktion hab ich hierher: 
http://www.rn-wissen.de/index.php/FIFO_mit_avr-gcc)

Der Timer zählt immer bis 362, was bei mir auf die Samplingfrequenz von 
44100 Hz abgestimmt ist.
Der Interrupt wird bei Output Compare Match ausgelöst und sieht so aus:
1
void init_timer(void)
2
{
3
  // Timer 1 konfigurieren
4
  TCCR1B = (1<<CS10) | (1<<WGM12); // Prescaler 1
5
 
6
  // Output Compare Match Interrupt aktivieren
7
  TIMSK |= (1<<OCIE1A);  
8
  
9
  // Output Compare Register auf 362 setzen
10
  // 362 Wartezyklen
11
  OCR1AH = 0b00000001;
12
  OCR1AL = 0b01101001;
13
}
14
15
ISR (TIMER1_COMPA_vect)
16
{
17
  /* Interrupt Aktion alle
18
  (16000000Hz)/ 362  = 44100 Hz
19
  */
20
  PORTC_temp = fifo_get_nowait(&infifo);
21
  if (PORTC_temp != -1)
22
  {
23
    PORTC = PORTC_temp;    
24
  }
25
}

Wenn ein Byte in der Warteschlange ist (also 
fifo_get_nowait(&infifo)!=-1) wird der Wert an PORTC ausgegeben, an dem 
ein DAC hängt.

Das Problem ist, dass am Ausgang des DAC nur etwa mit 300 Hz Daten 
herauskommen. Ich vermute mal, dass sich die Interrupts gegenseitig 
behindern...
Aber im Moment bin ich ratlos wie ich, das Problem lösen soll. Sind die 
FIFO Funktionen vielleicht zu zeitintensiv? Wie könnte ich das 
beschleunigen? Bzw wie gebe ich dem Timer Interrupt quasi eine 
Priorität, dass er auf jedenfall immer ausgelöst wird? Denn es sieht ja 
so aus als würde der UART Interrupt immer so schnell ausgelöst, dass der 
Timer Interrupt keine Zeit zum zünden hat? Kann man den Timer Interrupt 
irgendwie in eine Warteschlange stellen und direkt auslösen sobald das 
Programm aus dem UART Interrupt zurückkehrt?

Ich habe schon versucht in der UART Interrupt Routine am Anfang sei(); 
einzufügen. Aber dabei hat sich der µC die ganze Zeit resettet, also das 
kanns nicht sein...

Jemand ne Idee? Wäre echt super!

Grüße.

von Karl H. (kbuchegg)


Lesenswert?

Die FIFO Funktionen sind schon sehr heftig. Allgemein geschrieben, aber 
heftig. Besser du schreibst dir neue, die ein wenig 'leichter' sind und 
zumindest die Schreibtfunktion direkt in die ISR. Ein Funktionsaufruf in 
einer ISR ist teuer.
Statisches globales Array anlegen, 2 Zähler dazu und den Ringbuffer 
implementieren. Wenn du deinem µC helfen willst, dann machst du die 
Arraygröße als 2-er Potenz (also 128 Bytes, 256 Bytes, etc)

Wieviel Speicher hast du denn?
Bei 125000 kByte/s rein und 44kByte/s raus ist die FIFO doch ratzfatz 
voll.

von Karl H. (kbuchegg)


Lesenswert?

1
  // Output Compare Register auf 362 setzen
2
  // 362 Wartezyklen
3
  OCR1AH = 0b00000001;
4
  OCR1AL = 0b01101001;
5
}

Warum schreibst du nicht einfach
1
  OCR1A = 361;
zu einfach? zu lesbar?

von dieter (Gast)


Lesenswert?

Also Speicher hab ich gar keinen. Ich hab ein Matlab Script was mir eine 
wav-Datei zerpflückt...das ist auch nur zum rumspielen, muss nicht toll 
sein.
Aber in Matlab hab ich ne Funktion zum Daten senden. Die kann ich 
maximal alle 1ms aufrufen. D.h. ich muss mit der Funktion 44100/1000 = 
44,1 Byte pro aufruf verschicken, d.h. ich sollte mit 256Byte Speicher 
im µC auskommen.

Ist nur die Frage wie ich schnelle FIFO-Funktionen implementiere...Denn 
ich wüsste jetzt nicht was ich anders machen soll als bei dieser 
fertigen Version von Roboternetz.

von dieter (Gast)


Lesenswert?

Gibt es nicht sowas wie push() und pop() beim Stack nur als FIFO Version 
in nem definierten Speicherbereich?

von holger (Gast)


Lesenswert?

>D.h. ich muss mit der Funktion 44100/1000 =
>44,1 Byte pro aufruf verschicken, d.h. ich sollte mit 256Byte Speicher
>im µC auskommen.

>Ist nur die Frage wie ich schnelle FIFO-Funktionen implementiere...Denn
>ich wüsste jetzt nicht was ich anders machen soll als bei dieser
>fertigen Version von Roboternetz.

Nimm einen Double Buffer und keinen FIFO. Dann reduziert
sich das rechnen gewaltig. Und wenn du schneller sendest
als du abspielen kannst musst du noch ein Handshake machen
was dem Sender sagt das der Receiver keine Daten mehr aufnehmen
kann.

von Falk B. (falk)


Lesenswert?

@  dieter (Gast)

>Also Speicher hab ich gar keinen. Ich hab ein Matlab Script was mir eine
>wav-Datei zerpflückt...das ist auch nur zum rumspielen, muss nicht toll
>sein.

Was um alles in der Welt lässt dich glauben, so ein wäre einfach mal so 
locker mit Matlab zu machen?

>Ist nur die Frage wie ich schnelle FIFO-Funktionen implementiere...

Selber, mit Köpfchen.

>Denn
>ich wüsste jetzt nicht was ich anders machen soll als bei dieser
>fertigen Version von Roboternetz.

Dann ist das Vorhaben zwei Nummern zu groß für dich. Für den Anfang pack 
einfach mal ein paar wenige WAV-Daten in den Flash des AVR und gib sie 
aus. Dann vielleicht die Daten aus einem seriellen, externen Flash per 
SPI lesen, ala AT25xx. Und wenn das läuft, kannst du über UART und 1 
Mbit/s nachdenken.

MfG
Falk

von Peter D. (peda)


Lesenswert?

Die hier ist optimiert:

Beitrag "AVR-GCC: UART mit FIFO"


Peter

von dieter (Gast)


Lesenswert?

@Falk:

Deinen ersten Satz versteh ich nicht.

Die Sache ist die...es funktioniert ja schon wenn ich die Daten über 
USART sende und direkt ausgebe, höre ich die Audiodatei. Nur leider 
etwas zu schnell bzw zu langsam, weil 125000Byte/s nicht glatt durch 
44100 Hz teilbar ist. So weicht beim direkten streamen die 
Abspielsamplingfrequenz leicht von der Originalen ab.

Ich habe vorher immer alles mit Assembler gemacht, aber wollte mir jetzt 
mal C genauer ansehen, nur ist das Problem halt, dass ich schlecht 
einschätzen kann wie lange was dauert. Bei Assembler ist das ja kein 
Problem.




Wie wäre denn folgendes? Wenn ich die neue FIFO Funktion habe, könnte 
ich doch einfach manuell in der USART Receive Interrupt Routine am ende 
Prüfen, ob mein Timer schon das Output Compare überholt hat. Ich muss ja 
nur verhindern, dass der Timer nach Output Compare Match gecleart wird. 
Das Flag für das Match müsste ja erhalten bleiben. Also Flag prüfen und 
dann aus dem FIFO ausgeben und Timer zurücksetzen. Das müsste doch 
klappen oder?

von Peter D. (peda)


Lesenswert?

dieter schrieb:
> Ich hab ein Matlab Script was mir eine
> wav-Datei zerpflückt...

Sicher daß das auch schnell genug geht?

Schreib erstmal ein kleines Testprogramm auf dem AVR, welches für 1s 
einfach nur die ankommenden Bytes zählt und dann die Zahl anzeigt, z.B. 
auf einem LCD.


Peter

von dieter (Gast)


Lesenswert?

@Peter: Ja funktioniert tatsächlich ;)

Hab jetzt folgendes. Timer Interrupt weg, dafür Abfrage, ob Output 
Compare Match stattgefunden hat in UART Interrupt Service Routine:
1
ISR (USART_RXC_vect)
2
{  
3
  //Ausgabe mit Buffer
4
  _inline_fifo_put (&infifo, UDR);
5
  
6
  //NEU!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7
  if(TCNT1 & (1<<OCF1A))
8
  {
9
    TIFR &= (0<<OCF1A);
10
    TCNT1=0;
11
    PORTC=fifo_get_nowait(&infifo);
12
  }
13
}

Und es läuft tatsächlich, sogar mit den alten FIFO Routinen! Wer hätte 
das gedacht? :)

von holger (Gast)


Lesenswert?

>    TIFR &= (0<<OCF1A);

Diese Zeile dürfte so gut wie gar nichts bewirken;)

>OCF1A is automatically cleared when the Output Compare Match A Interrupt >Vector 
is executed.
>Alternatively, OCF1A can be cleared by writing a logic one to its bit >location.

von dieter (Gast)


Lesenswert?

Stimmt, so ist es richtig:
1
if(TIFR & (1<<OCF1A))
2
  {
3
    TIFR &= (1<<OCF1A);
4
    TCNT1=0;
5
    PORTC=fifo_get_nowait(&infifo);
6
  }

Aber ich muss trotzdem die FIFA Funktion erweitern, damit ich mehr Platz 
hab.

von holger (Gast)


Lesenswert?

>Stimmt, so ist es richtig:

>    TIFR &= (1<<OCF1A);

Nein, ist es nicht. Interrupt Flags werden gelöscht
wenn man eine 1 reinschreibt. Das machst du aber nicht.

So müsstest du das eigentlich machen

    TIFR |= (1<<OCF1A);

Ist aber auch falsch.

Der richtige Weg ist

TIFR = (1<<OCF1A);

Wenn du TIFR erst einliest, OCF1A reinoderst und dann wieder
zurückschreibst weisst du ja nicht ob nicht vieleicht auch
OCF1B schon gesetzt war in TIFR. Schreibst du das ganze dann zurück
löschst du evtuell noch andere Interrupt Flags.

von Falk B. (falk)


Lesenswert?

@  dieter (Gast)

>Die Sache ist die...es funktioniert ja schon wenn ich die Daten über
>USART sende und direkt ausgebe, höre ich die Audiodatei.

Ach so? Komisch, in deinem ersten Posting klang das GANZ anders!

"Das Problem ist, dass am Ausgang des DAC nur etwa mit 300 Hz Daten
herauskommen. Ich vermute mal, dass sich die Interrupts gegenseitig
behindern..."

> Nur leider
>etwas zu schnell bzw zu langsam, weil 125000Byte/s nicht glatt durch
>44100 Hz teilbar ist. So weicht beim direkten streamen die
>Abspielsamplingfrequenz leicht von der Originalen ab.

Schön, dass du das auch schon mitteilst. Lies mal was über 
Netiquette.

>Ich habe vorher immer alles mit Assembler gemacht, aber wollte mir jetzt
>mal C genauer ansehen, nur ist das Problem halt, dass ich schlecht
>einschätzen kann wie lange was dauert.

Ja.

> Bei Assembler ist das ja kein Problem.

Ja.

>Wie wäre denn folgendes? Wenn ich die neue FIFO Funktion habe, könnte
>ich doch einfach manuell in der USART Receive Interrupt Routine am ende
>Prüfen, ob mein Timer schon das Output Compare überholt hat.

Ist eine Möglichkeit, wenn gleich nicht die beste.

> Ich muss ja
>nur verhindern, dass der Timer nach Output Compare Match gecleart wird.

gecleart, soso.

>Das Flag für das Match müsste ja erhalten bleiben. Also Flag prüfen und
>dann aus dem FIFO ausgeben und Timer zurücksetzen.

Nö, der Timer läuft schön weiter, sonst wird es arg ungleichmässig. 
Ausserdem gibt es den CTC-Modus, das Mittel der Wahl.

> Das müsste doch klappen oder?

Mehr oder weniger. Wenn man es wirklich gut machen will, muss der Timer 
alleine laufen, damit der UART das Timing nicht zerstükeln kann 
(Jitter). Bei 44100 Hz sind das 22,67 us. Dein UART spuckt 125.000 
Byte/s aus, also 8us. Das ist fast dreimal so schnell. Wahrscheinlich 
sendest du 16 Bit WAVs, wenn gleich das hier unsinnig ist, die 16 Bit 
bekommst du nie und nimmer mit einem einfachen DAC wiedergegeben. 8 Bit 
reichen locker. Der einfachste Ansatz wäre, die Datenrate direkt aus dem 
UART abzuleiten, dann brauchst du auch keinen Timer-Interrupt. Da du ja 
Matlab hast, kannst du ja dort eine Sampling rate conversion machen, 
ohne Echtzeit.

Wenn man es richtig machen will, braucht man ausserdem noch eine 
Flußsteuerung, denn der UART MUSS die Daten schneller liefern als sie 
ausgegeben werden, er muss aber auch ab und an kurz anhalten, damit dein 
FIFO im AVR nicht überläuft. Und damit bist du mitten in den 
Problemen von digitaler Audiowiedergabe.

MFG
Falk

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.