Datum:
Angehängte Dateien:Anbei mal ne UART-Routine für AVRs mit FIFO fürs Senden und Empfangen. Funktion: Die FIFO arbeitet mit Ein- und Ausgabeindex. Gegenüber Pointern ergibt sich dadurch kürzerer und schnellerer Code. In der Regel reichen max 258 Byte Empfangspuffer (HW-Puffer + SW-Puffer) aus. Sind beide Indexe gleich, dann ist nichts im Puffer. Die Eingabefunktion (Receiveinterrupt) schreibt was in den Puffer und erhöht ihren Index. Die Ausgaberoutine holt das Byte ab und erhöht ihren Index gleichfalls. Sind beide Indexe gleich, ist der FIFO wieder leer. Da die Indexe nur bis zum FIFO-Limit wachsen dürfen, macht das Erhöhen das Macro ROLLOVER. Es erhöht den Wert und setzt ihn wieder auf Null, wenn er das Maximum erreicht. Die Funktion ugetchar0() liefert ein Byte aus dem FIFO zurück. Aber ist nichts im FIFO, dann wartet sie, bis was empfangen wurde. Wenn in dieser Zeit aber andere Tasks auszuführen sind, ist es daher sinnvoll, erstmal ukbhit0() aufzurufen, ob überhaupt was im FIFO ist. Analog gibt es beim Senden die Funktion utx0_ready() zum Prüfen, ob der FIFO noch Platz hat. Damit kann man statt warten, bis ein Byte gesendet wurde, was anderes machen. Ein Empfangspufferüberlauf wird nicht abgetestet. Wer will, kann ihn hinzufügen. In der Regel sichert man sich mit einem Protokoll gegen falsche Daten bei Überlauf, z.B. CRC-Test. Fallgrube 1: Ich war es leid, ständig mit Fehlermeldungen bombardiert zu werden, wenn ich mal nen anderen AVR verwende. Der Hauptteil der Arbeit bestand daher darin, das Namens-Chaos bei den AVRs in geordnete Bahnen zu lenken. In der uart0.h sind daher ne ganze Menge Macros drinne, die die verschiedenen Namen umdefinieren, damit ein Code alle AVRs erschlägt. Die Funktionsnamen enden alle auf die Ziffer 0. Wenn man einen AVR mit 2..4 UARTs benutzt, kann man den Code einfach für die anderen UARTs kopieren und die 0 durch 1..3 ersetzen. Fallgrube 2: Da hier Interrupts mit main-Funktionen kommunizieren, müßten die Indexe als volatile definiert werden, damit sie der AVR-GCC nicht wegoptimiert. Dies würde aber in den Interrupts besonders weh tun, da es mehr Code bedeutet. Daher werden nur die Zugriffe in den main-Funktionen als volatile gecastet (per Macro in meiner mydefs.h). Fallgrube 3: Die Kommunikation Interrupts mit Funktionen birgt die Gefahr, daß ein Interrupt genau innerhalb eine Funktion zuschlägt und damit falsche Werte liest oder setzt. Die in meinem Code gewählte Reihenfolge der Instruktionen ist interruptfest, d.h. nirgends müssen atomare Zugriffe unter Interruptsperre gemacht werden. Will jemand was an dem Code ändern, muß er das berücksichtigen. Außerdem muß man mit Interruptsperre arbeiten, wenn man die Indexe auf 16Bit erweitern will oder Pointer statt Indexe verwenden will. 16Bit Zugriffe sind ja auf nem 8Bitter nicht interruptfest. Peter
Datum:
Peter Dannegger wrote: > Anbei mal ne UART-Routine für AVRs mit FIFO fürs Senden und Empfangen. > > > Funktion: > > Die FIFO arbeitet mit Ein- und Ausgabeindex. Gegenüber Pointern ergibt > sich dadurch kürzerer und schnellerer Code. Wieso ergibt sich denn kürzerer und schnellerer Code mit Index statt Pointern? Bei Pointern muss nur dereferenziert werden (ld mit der Adresse im X,Y,Z register). Bei Indizes muss noch gerechnet werden.. Oder liege ich da falsch?
Datum:
Angehängte Dateien:Simon K. wrote: > Wieso ergibt sich denn kürzerer und schnellerer Code mit Index statt > Pointern? Bei Pointern muss nur dereferenziert werden (ld mit der > Adresse im X,Y,Z register). Bei Indizes muss noch gerechnet werden.. Aber bei Pointern muß jede Operation 16-bittig gemacht werden (Laden, Rückschreiben, Test auf Gleichheit, Test auf Endwert, Setzen auf Startwert). Dagegen sind die beiden zusätzlichen 8-Bit Additionen beim Indexzugriff nur pillepalle. Ich hab mal nur die Empfangsroutine auf Pointer umgestellt und schon stieg der UART Code von 322 auf 378 Byte. Anbei der Code. Peter
Datum:
Wäre es nicht besser, UART und FIFO zu entflechten? Damit wäre FIFO unabhängig vom UART verwendbar. In meinen Anwendungen benutze ich auch FIFOs, teilweise mehrere in einer Anwendung. Wenn der Code dann immer wiederholt werden muss, spart man ja nix, und Klarheit gewinnt man auch nicht...
Datum:
Georg johann Lay wrote: > Wäre es nicht besser, UART und FIFO zu entflechten? Damit wäre FIFO > unabhängig vom UART verwendbar. Der FIFO ist speziell für die UART ausgelegt, z.B. die Enable/Disable Interupt Befehle, um den HW-Puffer mit zu benutzen. Auch würde ein Warten in den Funktionen einen Deadlock bedeuten, wenn der FIFO nicht mit Interrupts verwendet wird. > In meinen Anwendungen benutze ich auch > FIFOs, teilweise mehrere in einer Anwendung. Kannst Du dafür mal Beispiele nennen? Ich brauche nen FIFO ausschließlich in der UART, da nur dort die Bytes einzeln reinkommen bzw. Zeit zum Senden brauchen. > Wenn der Code dann immer > wiederholt werden muss, spart man ja nix, und Klarheit gewinnt man auch > nicht... Man könnte dafür Macros schreiben, das spart aber nix, da der Code für jeden FIFO separat existieren muß. Die FIFOs sollen sich ja nicht gegenseitig stören. Man könnte ein Array aus n FIFOs definieren und den FIFO per Argument auswählen. Damit wird der Code aber nur größer und langsamer, da dann nie direkt auf die FIFO-Indexe zugefriffen werden kann sondern die erst umständlich indirekt ausgelesen werden müssen. Peter
Datum:
Peter Dannegger wrote: > Georg johann Lay wrote: >> In meinen Anwendungen benutze ich auch >> FIFOs, teilweise mehrere in einer Anwendung. > > Kannst Du dafür mal Beispiele nennen? > Ich brauche nen FIFO ausschließlich in der UART, da nur dort die Bytes > einzeln reinkommen bzw. Zeit zum Senden brauchen. Zwei FIFOs brauchts in der genannten Anwendung für den UART, eine weitere brauch ich für Timer1. Timer1 muss einerseite per Auftrag ne HW-PWM erzeugen können, aber auch eine Zeitmessung (Kapazitätsmessung an einer LED zur Helligkeitsmessung) via InCapture. (Die beiden Verwendungen schliessen sich aus, erlauben aber kleiner Verzögerungen in der Ausführung. Das InCapt muss mit einer Soft-PWM synchronisiert werden, da die LED auch während der Helligkeitsmessung leuchten soll). Insgesamt verwende ich 4 FIFOs (2*ISR-UART (IN+OUT), 1*Timer1-Service, 1*HW-PWM-Muster). Da ich meine Anwendungen sämtlich nicht-blockierend schreibe, gibt es in der main-Schleife keine Warte- oder Poll-Schleifen. Es wäre also nicht möglich, auf die OFF-Phase der Soft-PWM zu warten, um eine Messung einzufügen. Die Anwendung ist zugegeben recht speziell, ich will aber net jedesmal FIFO neu coden und verwende auch anderswo, wo es nur eine FIFO braucht, den allgemeinen Code. Ich nehem dort ein paar Ticks mehr an Ausführungszeit in kauf. Peter Dannegger wrote: > Fallgrube 2: > > Da hier Interrupts mit main-Funktionen kommunizieren, müßten die Indexe > als volatile definiert werden, damit sie der AVR-GCC nicht wegoptimiert. > Dies würde aber in den Interrupts besonders weh tun, da es mehr Code > bedeutet. > Daher werden nur die Zugriffe in den main-Funktionen als volatile > gecastet (per Macro in meiner mydefs.h). Ja, volatile in einer ISR ist nicht toll. Eine Alternative zum volatile-Cast ist eine Barrier... nicht unbedingt hübscher, aber eben eine weitere Möglichkeit.
__asm volatile ("":::"memory"); |
Das sagt gcc, dass sich der Inhalt des Speichers möglicherweise geändert hat, und entsprechende REG-Inhalte nicht mehr verwendbar sind. Eine Barrier am Anfang der main-Loop erspart mir volatile und Casterei. Da ich wie gasagt keine Poll-Schleifen verwende, reicht diese eine Barrier, egal wieviele Werte in einer ISR manipuliert werden.
Datum:
Georg johann Lay wrote: > Zwei FIFOs brauchts in der genannten Anwendung für den UART, eine > weitere brauch ich für Timer1. Timer1 muss einerseite per Auftrag ne > HW-PWM erzeugen können, aber auch eine Zeitmessung (Kapazitätsmessung an > einer LED zur Helligkeitsmessung) via InCapture. Ich habe eine ähnliche Anwendung, Temperaturregelung mittels STM160 am ICP, und Heiztransistor am OC1B (OC1A geht ja nicht zusammen mit ICP). Allerdings verwende ich da keinerlei FIFO. Der ICP-Interrupt macht die Puls/Periodenmessung und wenn der Regler den Wert nicht ausliest, wird er einfach mit der neuen Messung überschrieben. Ein FIFO wäre da nur hinderlich, ich will ja den möglichst aktuellsten Meßwert und nicht irgendwelche uralten, sonst spinnt die Regelung. Ich verwende sehr gerne diesen Overwrite Modus zur Parameterübergabe, dann brauche ich mich nicht um nen Pufferüberlauf zu kümmern. Man könnte natürlich universelle Macros zum Erzeugen einer FIFO schreiben aber ich wollte das Beispiel nicht zu kompliziert machen. Man hätte dann ganz schön rumbasteln müssen, um die Unterschiede zwischen dem Empfangs- und dem Sende-FIFO irgendwie zu lösen. Das wäre dann erheblich auf Kosten der Lesbarkeit gegangen. Insbesondere in Zusammenhang mit Interrupts versuche ich den Code einfach zu halten, da dabei schnell Fehler passieren können. Wenn man also den Interruptcode kurz hält, kann man gut sehen, was er bewirken könnte, wenn er an jeder Stelle des Main zuschlägt. Mit irgendwelchen großen Macros oder Unterfunktionen darin, geht diese Übersicht verloren. Da hilft dann nur noch rohe Gewalt (cli/sei an jeder möglichen Stelle). > Ja, volatile in einer ISR ist nicht toll. Eine Alternative zum > volatile-Cast ist eine Barrier... nicht unbedingt hübscher, aber eben > eine weitere Möglichkeit. >
> __asm volatile ("":::"memory"); > |
Kannte ich bisher noch nicht. Dürfte aber das Kind mit dem Bade ausschütten, d.h. sämtliche Variablen betreffen, nicht nur die von Interrupts geänderten. Da ist das Casten also zielgenauer. Peter
Datum:
Hallo Peter, Vorerst einmal Danke für das geniale Prog. Benutze WinAVR-20081205 mit AVR-Studio. Folgendes Problem. Das Program läuft einwandfrei wenn ich die Optimierung abschalte, sobald ich aber mit -Os optimiere werden mir die volatile gecasteten Variablen wegoptimiert... Vielleich wer eine Ahnung warum das so ist? Ich hab jetzt die "Interrupt" Variablen fix als volatile deklariert. Dann funktioniert es auch mit der Optimierung. Was ich nicht verstehe, warum soll der Code bei fixer volatile Deklaration größer werden als bei gecastetem volatile? Dank lg Rudi
Datum:
Frage, muss man hier nicht die Interrupts sperren?
void uputchar0( u8 c ) { u8 i = tx_in; ROLLOVER( i, TX0_SIZE ); ==> UTX0_IEN = 0; // disable TX interrupt ??? // wieso braucht man das hier nicht? tx_buff[tx_in] = c; while( i == vu8(tx_out)); // until at least one byte free // tx_out modified by interrupt ! tx_in = i; UTX0_IEN = 1; // enable TX interrupt } |
Datum:
gast wrote:
> Frage, muss man hier nicht die Interrupts sperren?
Nein, weil erst hier der Interrupt den neuen Index bekommt und damit das
neue Zeichen senden kann:tx_in = i; |
Und das ist eine atomare Instruktion, da 8-bittig. Außerdem würde die Sperre an der von Dir vorgeschlagenen Stelle einen Deadlock bedeuten, da dann der Interrupt nie den Puffer freigeben kann. Peter P.S.: Da der FIFO prinzipiell immer ein Byte freilassen muß, darf man das neue Byte schon vorher reinschreiben.
Datum:
Wenn nicht 8-bittig müsste man das aber machen oder? Wieso Deadlock verstehe ich nicht sorry.
Datum:
gast wrote:
> Wieso Deadlock verstehe ich nicht sorry.
Weil bei vollem Puffer in der while-Schleife gewartet wird, bis wieder
Platz ist. Aber wie soll Platz frei werden, wenn zu diesem Zeitpunkt der
TX-Interrupt ausgeschaltet ist?
Datum:
Angehängte Dateien:Hallo, vielen Dank für den Code, damit hatte ich ratzfatz die serielle Schnittstelle angebunden. Ich hab etwas in dieser Art versucht (naja, in Realität nicht ganz so schlimm):
...
uputs0("Ich schreibe einen Roman und der ist laaaaaaaang...");
...
|
Der Compiler hat dann beim Optimieren das uputchar0 genommen und in die Schleife vom uputs0 mit reingepackt (hab ich im Assembler gesehen) - soweit Ok. Jetzt hat er aber in dieser Schleife das tx_in in einem Register gespeichert. Da das nicht volatile deklariert ist, war das vollkommen legal. Nachteil ist allerdings, daß dann dieser Code im uputchar0 nur noch das Register verändert:
tx_in = i; |
Dann kommt der Interrupt:
ISR( USART0_UDRE_vect )
{
if( tx_in == tx_out ){ // nothing to sent
UTX0_IEN = 0; // disable TX interrupt
return;
}
...
|
und schaltet sich wieder ab - er kann von den neuen Daten nix mitbekommen, denn er weiß ja nix von dem Register. Irgendwann ist dann der Puffer voll und das while im uputchar0 wird zur Endlosschleife. Der Fix sieht so aus, daß man den Schreibzugriff auf das tx_in im uputchar0 volatile machen muss:
... vu8(tx_in) = i ... |
Das gilt genauso für den Schreibzugriff (ROLLOVER) auf rx_out im ugetchar0. Außerdem verbraucht mein "Roman" aus dem Beispiel oben wertvolles SRAM. Ich hab daher noch eine kleine Funktion gebaut, die den Text direkt aus dem Flash liest:
uputs0_pgm_P("Ich schreibe einen Roman und der ist laaaaaaaang...");
|
Durch ein Makro wird der Text automatisch in den Flash gelegt. Wenn man die Daten schon im Flash hat gehts so:
unsigned char longtext[] PROGMEM = "dies ist ein langer Text"; void printtext(void) { uputs0_pgm((PGM_P)longtext); } |
Beide Änderungen hab ich in einen Patch gepackt und diesem Beitrag angehängt.
Datum:
Vielen vielen dank für den Code! Das erleichtert einem das Programmieren einer UART ja wesentlich! Was ich bisher nicht ganz verstehe is das Stückchen hier im uart Header: usable: RX0_SIZE + 2 (4 .. 258) Die Begrenzung auf 256 is klar, aber wieso dann das "+2"?
Datum:
@peter
ich habe mal generell eine frage zum thema FIFO und atomare operationen.
ich habe meine fifos bisher immer so gestaltet das ich neben dem
schreib/lese-index noch einen differenzindex benutze. dieser dient als
generelle abfrage dafür ob ich zeichen in meinem puffer habe oder nicht
(um mir die abfrage schreib-leseindex ist gleich zu sparen). wenn dieser
als byte ausgeführt wird müsste es ja dennoch möglich sein im
hauptprogramm atomare zugriffe zu gewährleisten, oder ?
beispiel :
volatile char rptr = 0; // read pointer
volatile char wptr = 0; // write pointer
volatile char diff = 0; // diff pointer
volatile char data [100];
isr (irgendwas)
{
if (diff < 100)
{
diff++;
wptr++;
if (wptr > 100) { wptr = 0; } // rollover
data [wptr] = int_data_register;
}
}
char fifo_read (void)
{
if (diff > 0)
{
diff --;
rptr ++;
if (rptr > 100) { rptr = 0; } // rollover
return data [rptr];
}
return 0;
}
meine frage ist nun : kann es zu kollisionen kommen dadurch das ich 2
indexe (schreib/differenzindex bzw lese/differenzindex) verwende, oder
ist lediglich die gewährleistung des atomaren zugriffs auf den
differenzindex (dadurch das ich eben die ausführung der schreib/lese
funktionen des FIFOs an die diff-variable koppele) entscheidend ?
Datum:
Rene Böllhoff schrieb: > wenn dieser > als byte ausgeführt wird müsste es ja dennoch möglich sein im > hauptprogramm atomare zugriffe zu gewährleisten, oder ? Wieso "dennoch"? Eher "deswegen". Aber nur weil es nur eine 8-Bit-Variable ist, ist nicht jeder Zugriff automatisch atomar. > if (diff > 0) Das ist ein atomarer Zugriff auf diff (weil nur gelesen wird). > diff --; Dieser ist nicht atomar, und daher problematisch. Das ist ein Read-Modify-Write-Zugriff. Was wenn zwischen Read und Write dein "irgendwas"-Interrupt auftritt?
Datum:
Stephan M. schrieb: > Was ich bisher nicht ganz verstehe is das Stückchen hier im uart Header: > usable: RX0_SIZE + 2 (4 .. 258) Der FIFO darf sich nicht einholen, daher Size - 1. Die UART hat noch einen Empfangspuffer für 3 Byte, also: Size - 1 + 3 Der Sendepuffer ist aber nur 2 Byte, also: Size - 1 + 2 Peter
Datum:
>> diff --; >Dieser ist nicht atomar, und daher problematisch. >Das ist ein Read-Modify-Write-Zugriff. Was wenn zwischen Read und Write >dein "irgendwas"-Interrupt auftritt? ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in der "ugetchar0" ja ebenfalls problematisch sein, da das "++x" im ROLLOVER-Makro ja ebenfalls kein atomarer zugriff ist oder vertue ich mich da ?! ich habe mir mit meiner 3-index-lösung eine interrupt-sperre eingebaut, aber es ist natürlich unschön jedesmal den interrupt zu sperren. da mal eine frage (auch auf die gefahr hin das ich nun ge/erschlagen werde) : wenn ich den interrupt global sperre (mit cli) in der funktion dann ein interrupt auftritt, und ich den interrupt wieder freigebe, geht mir dieser dann verloren oder wird unmittelbar nach freigabe des interrupts dieser ausgeführt ?
Datum:
Rene Böllhoff schrieb: > ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in > der "ugetchar0" ja ebenfalls problematisch sein, da das "++x" im > ROLLOVER-Makro ja ebenfalls kein atomarer zugriff ist oder vertue ich > mich da ?! Welche negativen Folgen soll der nicht-atomare Zugriff denn dort haben? rx_out wird im Interrupt ja nicht verändert (nur gelesen).
Datum:
Rene Böllhoff schrieb: > ok. soweit klar. aber dann müsste bei peters code das makro ROLLOVER in > der "ugetchar0" ja ebenfalls problematisch sein Nö, der Interrupt liest zwar den Ausgabeindex, aber er schreibt ihn nicht. Daher kann die Mainfunktion diesen gefahrlos ändern. Dein "diff" wird jedoch in Main und Interrupt geschrieben. Deine zusätzliche Variable ist nur dann einen Tick schneller, wenn der FIFO leer ist. Aber sie benötigt deutlich mehr Code. Peter
Datum:
> Die UART hat noch einen Empfangspuffer für 3 Byte, also: Size - 1 + 3 > Der Sendepuffer ist aber nur 2 Byte, also: Size - 1 + 2 Woher hast Du das mit der Größe des Empfangs-/Sendepuffers im UART? Ich hab wie blöd im Datenblatt vom meinem atmega168 genau nach dieser Größe gesucht und nix gefunden.
Datum:
Rene Böllhoff schrieb: > wenn ich den interrupt global sperre (mit cli) in der funktion dann ein > interrupt auftritt, und ich den interrupt wieder freigebe, geht mir > dieser dann verloren oder wird unmittelbar nach freigabe des interrupts > dieser ausgeführt ? Letzteres.
Datum:
Gerd E. schrieb: > Woher hast Du das mit der Größe des Empfangs-/Sendepuffers im UART? Ich > hab wie blöd im Datenblatt vom meinem atmega168 genau nach dieser Größe > gesucht und nix gefunden. Steht normalerweise bei der UDR-Beschreibung.
The receive buffer consists of a two level FIFO. |
Dazu kommt dann noch das Input-Shift-Register, also 3 Bytes effektiv.
Datum:
>Nö, der Interrupt liest zwar den Ausgabeindex, aber er schreibt ihn >nicht. >Dein "diff" wird jedoch in Main und Interrupt geschrieben. ah ok. da lag der knoten in meinen hirnwindungen. verstanden. aber mit einer interruptsperre sollte das problem ja dann umschifft sein. ich fand die diff-geschichte recht angenehm da man am diff direkt sehen kann wieviel noch im puffer steht, ohne den pufferstand aus dem schreib und lese index berechnen zu müssen.