Hallo liebe Gemeinde,
normalerweise lese ich hier im tollen Forum nur mit, aber diesmal habe
ich ein Problem, zu dem ich keine Lösung gefunden habe. :(
Vorweg: Ich programmiere in C.
An meinen ATmega8 habe ich eine 4x4-Matrixtastatur angeschlossen.
Um zyklisch die Tastenerkennung zu starten, kommt alle 10ms ein
Interrupt vom Timer0 und ruft bei einem definierten Zählerstand die
Taster-Funktion auf.
Allerdings passiert es, wenn ich per UART Zeichen bekomme (lese per
Interrupt ein), dass ein oder mehrere Zeichen fehlen. Das passiert aber
nicht immer.
Ich glaube, dass der Timer0-Interrupt dann zeitgleich mit dem
RX-Interrupt kommt. Wenn nun der Timer0-Interrupt abgearbeitet wird,
wird wohl in dieser Zeit ein neues Zeichen "eingelesen", das aber nicht
verarbeitet wird?!
Hinzu kommt, dass in der Funktion, die die Zeichen bearbeitet, ein
Timeout von 1 Sekunde eingepflegt wurde.
Die Timeout-Zeit ist in einer globalen Variable namens timeout abgelegt.
Taktfrequenz: 7,3728MHz
UART: 38400bps
Timer0: Interrupt alle 10ms
Ich bin leider nicht an meinem eigenen Rechner, aber hier der
Gedächtniscode:
1
ISR(TIMER0_OVF_vect)
2
{
3
TCNT0=184;// Timer0 wieder vorladen
4
waitforkey++;// wenn Variable 4 erreicht hat, wird die
5
// Funktion zur Tasterauswertung gestartet...
6
// und zwar passiert das in der main
7
if(timeout)timeout--;
8
}
Mehr ist in der ISR nicht vorhanden.
Und hier die Funktion zum Zeichen prüfen und behandeln als
Gedächtniscode:
1
unsignedcharuart_zeichen(void)
2
{
3
timeout=100;
4
do
5
{
6
// Zeichen behandeln
7
}while(timeout);// mache solange wie timeout != 0
8
}
Wie bereits geschrieben, manchmal funktioniert das Zeichen einlesen per
UART, meistens aber nicht.
Wenn ich unmittelbar nach der do-Anweisung (vor der Zeichenbehandlung)
ein delay_ms(1) einbaue, so verringert sich die Fehlerquote, ist aber
noch immer vorhanden.
Die UART-Kommunikation wird wie folgt abgewickelt:
* AVR fragt an
* Teilnehmer antwortet
* AVR fragt an
* Teilnehmer antwortet
* usw.
Also alles immer nach der Reihe!
Kann es sein, dass sich die beiden Interrupts gegenseitig "versperren"
bzw. der Timer0-Interrupt bevorzugt behandelt wird (hat ja eine höhere
Priorität)?!
Muss ich dann den Timer0-Interrupt sperren, solange der AVR in der
RX-ISR ist?
Der Timeout-Wert ist großzügig gewählt, ich erwarte Zeichen nach ca.
150ms zurück.
Was gibt es da für Lösungsansätze? Auf ein Polling möchte ich gerne
verzichten.
Besten Dank,
Bernd
Danke, Ralf, für die Antwort!
Wenn ich an meinem Rechner bin, werde ich die relevanten Funktionen
posten.
Habe das Thema "vorschnell" und am fremden Rechner gepostet, da mir das
keine Ruhe lässt.
Vielleicht hat aber jemand schon mal so ein Problem gehabt und kennt
einen Lösungsansatz.
Bis später,
Bernd
Bernd Gelsen schrieb:>> Kann es sein, dass sich die beiden Interrupts gegenseitig> "versperren" bzw. der Timer0-Interrupt bevorzugt behandelt wird> (hat ja eine höhere Priorität)?!
Nein.
Der Timer Interrupt hat ist Vergleich zu der Zeit, die notwendig ist um
1 Zeichen zu übertragen, blitzschnell durch. An dem liegt es sicher
nicht, wenn deine UART Zeichen verliert.
Aber dein Timeout Schema schaut auf den ersten Blick ein wenig suspekt
aus.
Zeig doch mal deinen ganzen UART Code. Dein ständiges Erwähnen von
_delay_ms macht mich stutzig. Höchst wahrscheinlich ist dort irgendwo
das Problem zu suchen.
> Auf ein Polling möchte ich gerne verzichten.
Und genau da vermute ich dann auch ein Problem. AUch eine UART ISR soll
schnell abgearbeitet werden. Eventuell wärst du mit einer FIFO am UART
Interrupt und Polling in der Hauptschleife viel besser bedient.
Karl Heinz Buchegger schrieb:> Aber dein Timeout Schema schaut auf den ersten Blick ein wenig suspekt> aus.
Den Verdacht habe ich auch, wenn in einem Interrupt ein wie auch immer
gestaltetes wait auftaucht...
In einem Interrupt wird auf nichts und niemanden gewartet!
Karl Heinz Buchegger schrieb:> Aber dein Timeout Schema schaut auf den ersten Blick ein wenig suspekt> aus.> Zeig doch mal deinen ganzen UART Code. Dein ständiges Erwähnen von> _delay_ms macht mich stutzig. Höchst wahrscheinlich ist dort irgendwo> das Problem zu suchen.
Ein Auszug aus meinem Code ist angehängt!
Darin ist der UART-Code zu sehen. Ich habe nur ein einziges Mal
_delay_ms erwähnt, und zwar nur, weil damit eine Besserung zu sehen ist,
aber das Problem damit nicht behoben wird. Würde sagen: Mit einem Delay
verschlimmbessere ich das Problem! ;)
Wie programmiert man denn sonst ein Timeout?
>> Auf ein Polling möchte ich gerne verzichten.>> Und genau da vermute ich dann auch ein Problem. AUch eine UART ISR soll> schnell abgearbeitet werden. Eventuell wärst du mit einer FIFO am UART> Interrupt und Polling in der Hauptschleife viel besser bedient.
Empfangs-FIFO ist bereits implementiert. Oder habe ich dabei einen
Fehler gemacht?
Anregungen und Kritik nehme ich gerne von Profis entgegen!
Lothar Miller schrieb:> wenn in einem Interrupt ein wie auch immer> gestaltetes wait auftaucht...> In einem Interrupt wird auf nichts und niemanden gewartet!
Was hat eine globale Variable (timeout) dekrementieren mit einem wait in
einer ISR gemeinsam? Verstehe ich nicht.
Ich weiß, dass ich in einer ISR nicht warten darf und diese so kurz wie
möglich halten muss.
Darum ist dort auch kein wait vorhanden.
Nun zum angehängten Code:
Ich habe diesen Code aus meinem Quellcode rauskopiert.
Da das Projekt noch in Kinderschuhen steckt, bitte nicht all zu doll
meckern. Ausgliedern in h-Dateien muss ich noch machen, möchte aber
erstmal schauen, ob mein Vorhaben überhaupt funktioniert.
Die Tasterabfrage habe ich rausgelassen, allerdings besteht noch immer
das Problem, dass Zeichen ins Nirvana geschossen werden.
Was "uint8_t uart0_getanswer(unsigned char *s, unsigned short len)"
macht:
Solange Zeichen einlesen, bis bcc zu Null geworden ist (das ist eine
empfangene Zeichenkette mit nicht vorhersehbarer Zeichenlänge - das Ende
wird anhand bcc=0 erkannt) oder die Variable timeout zu Null geworden
ist oder wenn die übergebene Zeichenlänge mit der empfangenen
Zeichenanzahl übereinstimmt.
Vor dem "timeout" hatte ich das alles mit einem Delay gelöst, also bevor
ich die Zeichen verwertet habe erstmal 150ms gewartet.
Das wollte ich nicht mehr haben und möchte nun Zeichen nach Zeichen
einlesen, bis halt bestimmte Ereignisse eintreffen, die die
do-while-Schleife beenden.
Es funktioniert so, nur nicht richtig, da irgendwann (nach gefühlten 10
Sekunden) die Kommunikation fehlerhaft wird.
Wäre super, wenn mich jemand auf meine Fehler hinweisen könnte,
Bernd
> void main(void) // hier ohne "for"-ever-Schleife (ist gewollt)
keine gute Idee.
wenn dir main hinten rausfällt, werden von der C-Rutime die Interrupts
abgeschaltet. Aber ich verstehe natürlich auch, dass das hier ein
Textprogramm ist
Was mit auffällt: Du musst deine Zugriffe auf die Pointer atomar machen,
da sie mehr als 8 Bit lang sind
> unsigned char *lesezeiger;> unsigned char *schreibzeiger;
die müssen
unsigned char * volatile lesezeiger;
unsigned char * volatile schreibzeiger;
sein
> }while( (!ReadChars || bcc) && timeout && (ReadChars!=len) );
das kommt mir jetzt etwas seltsam vor. Warum 2 mal ReadChars abfragen?
Warum bcc abfragen? (wozu ist bcc eigentlich gut?) Das hätte ich jetzt
anders erwartet
} while( timeout && (ReadChars!=len) );
timout hätte ich nach jedem empfangenen Zeichen wieder erneut starten
lassen. So wie du das jetzt hast, gilt der timeout für die komplette zu
empfangende Nachricht, die innerhalb der Zeit komplett da sein muss.
(Gut: Im Moment wartest du nur auf 1 Zeichen Nachrichten)
Aber ansonsten sieht das jetzt mit Code nicht schlecht aus. Was ich tun
würde: In die UART-ISR mir eine direkte Ausgabe einbauen. Irgendwie, und
wenn es 8 LED an einem Port sind.
Bernd Gelsen schrieb:> Lothar Miller schrieb:>> wenn in einem Interrupt ein wie auch immer>> gestaltetes wait auftaucht...>> In einem Interrupt wird auf nichts und niemanden gewartet!> Was hat eine globale Variable (timeout) dekrementieren mit einem wait in> einer ISR gemeinsam? Verstehe ich nicht.
In deinem Urposting war nicht klar, was du alles innerhalb der ISR hast.
Im Zweifelsfall gehen wir hier immer vom schlimmsten aus und haben damit
in >90% aller Fälle recht :-)
> Solange Zeichen einlesen, bis bcc zu Null geworden ist
Ah.
Du hast da also sowas wie eine Checksumm, die den Empfangsstrom immer so
abschliesst, dass sich XOR 0 ergibt. Seltsames Schema.
(Dann vergiss das was ich weiter oben über die Abbruchbedingung gesagt
habe, obwohl mir die Abfrage trotzdem immer noch seltsam vorkommt)
Wie und wo stellst du fest, dass dir Zeichen fehlen.
Beim 5-maligen Durchlesen des COdes ist mir da jetzt (ausser den Punkten
von oben) nichts aufgefallen. Das sieht alles nicht so schlecht aus.
Karl Heinz Buchegger schrieb:> In deinem Urposting war nicht klar, was du alles innerhalb der ISR hast.> Im Zweifelsfall gehen wir hier immer vom schlimmsten aus und haben damit> in >90% aller Fälle recht :-)
Da bin ich ja beruhigt, dass ich zu den <10% gehöre. :)
Karl Heinz Buchegger schrieb:> Du hast da also sowas wie eine Checksumm, die den Empfangsstrom immer so> abschliesst, dass sich XOR 0 ergibt. Seltsames Schema.>> (Dann vergiss das was ich weiter oben über die Abbruchbedingung gesagt> habe, obwohl mir die Abfrage trotzdem immer noch seltsam vorkommt)
Ja, richtig erkannt, das bcc ist eine Checksumme! Eigentlich hätte ich
das bcc auch crc nennen können, aber ich habe mich an die Namensgebung
vom Kommunikationspartner gehalten.
Meine Abbruchbedingung hätte ich bestimmt besser schreiben können
(bringe mir C alleine bei), aber ich musste etwas entwickeln, damit
folgende Abbruchbedingungen funktionieren:
* bei unbekannter Zeichenlänge muss bcc zu Null werden und mindestens
ein Zeichen bearbeitet worden sein
* wenn Anzahl der bearbeiteten Zeichen gleich der erwarteten Zeichen
sind
* wenn beides nicht zutrifft, muss timeout greifen
> unsigned char * volatile lesezeiger;> unsigned char * volatile schreibzeiger;
Was meinst du denn mit "atomar machen"?
Ich dachte immer, dass man mit volatile Variablen global macht.
Das werde ich auf jeden Fall morgen mal ausprobieren!
Danke für den Tipp.
Ich rede mir bestimmt nicht nur ein, dass Zeichen fehlen, denn im
Kommunikationsablauf frage ich jede Antwort vom Teilnehmer ab und wenn
die Bytes (Zeichen) nicht stimmen (also: u.a. fehlen), wird mit einem
Return die jeweilige Funktion beendet.
Ich habe es bereits geprüft: so kann es vorkommen, dass bereits das
erste Zeichen nicht in den AVR wandert und somit auch teilweise bcc nie
zu Null werden kann!
Die Tage geht's weiter, bin aber froh, dass mein Code im Grunde richtig
ist!
Die Tipps werde ich auf jeden Fall umsetzen und wieder hier berichten!
Vielen Dank bis jetzt, ist ein tolles Forum,
Bernd
Bernd Gelsen schrieb:>> unsigned char * volatile lesezeiger;>> unsigned char * volatile schreibzeiger;> Was meinst du denn mit "atomar machen"?> Ich dachte immer, dass man mit volatile Variablen global macht.
Nein.
volatile sagt dem Compiler, dass er mit dieser Variablen keine
Optimierungen machen darf.
Das hat aber nichts mit atomar zu tun.
Atomar bedeutet: Während des Zugriffs auf die Variable, darf kein
Interrupt dazwischen kommen, der die Variable verändert. Bei 8 Bit
Werten spielt das keine Rolle. Bei 16 Bit Werten aber schon. Denn die
kann der AVR nicht in einem Rutsch aus dem Speicher holen. Und dann
könnte es dir passieren, das der AVR zb die unteren 8 BIit aus dem
Speicher holt und noch ehe er die oberen 8 Bit holoen kann, kommt ein
Interrupt dazwischen und verändert die Variable, von der du gerade mal
einen Teil geladen hast. Folge: Dein Programm rechnet mit inkonsistenten
Werten.
Ist ungefähr so, wie wenn du ein Buch liest und in der Mittagspause
tauscht dir jemand das Buch aus. Dann solltest du dich nicht wundern,
wenn die Geschichte vom Nachmittag nicht mehr zur Geschichte vom
Vormittag passt.
Deine Pointer müssen volatile sein, weil sie sowoshl innerhalb einer ISR
als auch ausserhalb benutzt werden.
Und die Zugriffe müssen atomar ausgeführt werden, weil Pointer Variablen
größer als 8 Bit sind.
> Ich rede mir bestimmt nicht nur ein, dass Zeichen fehlen,
Sag ich auch nicht.
Aber was mir in deinem Programm fehlt, dass sind Kontrollmöglichkeiten.
Du hast nirgend Ausgaben oder sonstige Dinge, mit denen du feststellen
kannst was wirklich los ist. Zb welche Zeichen von der UART reingekommen
sind. Und zwar am besten schon direkt an der QUelle. Wenn die ISR für
ein Zeichen aufgerufen wird, kann man sich das Zeichen gleich mal
irgendwo ausgeben lassen. Dann weiß man, was auf unterster Ebene
reinkommt. Noch ehe Ringbuffer etc. sich um dieses Zeichen annehmen.
> denn im> Kommunikationsablauf frage ich jede Antwort vom Teilnehmer ab und wenn> die Bytes (Zeichen) nicht stimmen (also: u.a. fehlen), wird mit einem> Return die jeweilige Funktion beendet.
Schön. Das sagt dir aber eigentlich nur, dass irgendetwas nicht stimmt.
Aber du kannst keine Rückschlüsse darauf ziehen, was denn nun nicht
stimmt und wie es dazu gekommen ist.
Du musst dir zu Debug-Zwecken Möglichkeiten einbauen, mit denen du das
Stochern im Nebel abstellst. Im Idealfall sagt dir der AVR (auf LCD oder
über UART auf einem Terminel) was er gerade warum und aufgrund welcher
Dtaen macht.
> Die Tage geht's weiter, bin aber froh, dass mein Code im Grunde richtig> ist!
Die Codeidee sieht soweit richtig aus und offensichtliches Problem hab
ich soweit auch keines gesehen.
Karl Heinz Buchegger schrieb:> Deine Pointer müssen volatile sein, weil sie sowoshl innerhalb einer ISR> als auch ausserhalb benutzt werden.> Und die Zugriffe müssen atomar ausgeführt werden, weil Pointer Variablen> größer als 8 Bit sind.
Okay, wenn ich das richtig verstanden habe, muss ich den Lese- und
Schreibzeiger als volatile deklarieren, da in der ISR und in der
nicht-ISR darauf zugegriffen wird.
Das heißt aber noch nicht, dass der Zugriff auf den Lese-/Schreibzeiger
atomar ist.
Habe mir folgenden Artikel angeguckt:
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff
Heißt das, dass, wenn der Lese-/Schreibzeiger behandelt wird, ich
Interrupts mit cli deaktivieren und nach der Behandlung mit sei wieder
aktivieren muss?
Ich lese mich in dieses Thema gerade ein, weil ich immer davon
ausgegangen bin, dass volatile Variablen immer global sind und nicht
mehr macht.
Karl Heinz Buchegger schrieb:> Du musst dir zu Debug-Zwecken Möglichkeiten einbauen, mit denen du das> Stochern im Nebel abstellst. Im Idealfall sagt dir der AVR (auf LCD oder> über UART auf einem Terminel) was er gerade warum und aufgrund welcher> Dtaen macht.
Danke für den Tipp. Ich werde meine Schaltung erweitern und alle Sende-
und Empfangsdaten mitsniffen.
Dann weiß ich, was der AVR sendet und was der Kommunikationspartner
zurück gibt. Dauert aber ein bisschen, vor allem, weil ich die Daten
auswerten muss. Das sind nicht wenige Daten, denn anfangs funktioniert
die Kommunikation, nach knapp 10 Sekunden zeigt der AVR falsche Werte
an.
Karl Heinz Buchegger schrieb:> Die Codeidee sieht soweit richtig aus und offensichtliches Problem hab> ich soweit auch keines gesehen.
Danke! Vorher, ohne Timeout und nur mit Delays, hat die Kommunikation
wunderbar funktioniert, allerdings relativ langsam, weil ich ja immer
nur gewartet und gewartet und gewartet habe... ;)
Besten Dank bis jetzt für die Antworten,
später mehr,
Bernd
Hallo,
ich habe nun die Rohdaten über RS232 mitgeschrieben.
Was mir aufgefallen ist:
Ich erwarte vom Kommunikationspartner eine Zeichenkette mit dem bcc am
Ende, sagen wir mal:
0x0D 0x20 0x13 0x23 0x1D
Das 0x1D ist die Checksumme.
Mein Kommunikationspartner sendet aber:
0x0D 0x13 0x23 0x1D
Die Checksumme ist korrekt, aber es fehlt bereits im Rohdatenstream das
0x20. Somit ist meine Zeichenauswertung auf das bcc fehlerhaft!
Ich weiß nicht, wie der Kommunikationspartner mit den Daten umgeht,
vielleicht kommt dieser irgendwie durcheinander?! Vielleicht pollt
dieser auch die eingehenden Daten und wird durch die Kommunikation vom
AVR gestört?! Aber wenn der AVR Daten gesendet hat, werden nur die Daten
vom Partner bearbeitet, ohne dass der AVR dazwischen erneut etwas
sendet. Das ist sehr seltsam.
Wie bereits geschrieben, mit Delays hatte es ganz zu Anfang ohne
Probleme funktioniert.
So war vorher das AVR-Programm aufgebaut:
* AVR sendet
* _delay_ms(100)
* (während Delay hat der Partner Daten an den AVR gesendet)
* AVR wertet Daten aus dem Ringpuffer aus
* wenn Daten gültig, wieder etwas senden
* _delay_ms(100)
* usw...
Das Delay habe ich nur durch eine do-while-Schleife ersetzt und einen
Timeout eingebaut, so dass durch einen Fehler immer die Funktion
verlassen wird.
Den Lese-/Schreibzeiger habe ich nun auch volatile gemacht, ohne
Besserung.
Nun weiß ich nicht mehr weiter, denn das Problem tritt sehr häufig auf,
allerdings meist nicht an der selben Stelle.
Gruß,
Bernd