Forum: Mikrocontroller und Digitale Elektronik Timer0 + UART = Interruptprobleme


von Bernd Gelsen (Gast)


Lesenswert?

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
unsigned char uart_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

von Ralf (Gast)


Lesenswert?

Also, an deinem geposteten Code kann's nicht liegen!

von Bernd Gelsen (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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!

von Bernd Gelsen (Gast)


Angehängte Dateien:

Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Karl H. (kbuchegg)


Lesenswert?

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 :-)

von Karl H. (kbuchegg)


Lesenswert?

> 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)

von Karl H. (kbuchegg)


Lesenswert?

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.

von Bernd Gelsen (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Bernd Gelsen (Gast)


Lesenswert?

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

von Bernd Gelsen (Gast)


Lesenswert?

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

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.