Forum: Mikrocontroller und Digitale Elektronik AVR-UART Interruptgesteuert oder nicht?


von Atmega Anfänger (Gast)


Lesenswert?

Hallo,

ich bin ein ziemlicher Anfänger in der Programmierung mit Atmegas und 
wollte mal eine grundsätzliche Frage stellen.

Ich verwende einen Atmega8 und will einen Steuerbefehl über den UART 
ausgeben. Danach soll mir das Gerät antworten und einige Messdaten 
zurücksenden.

So nun zu meiner Frage:

Soll ich das ganze Interruptgesteuert machen oder einfach den Befehl 
senden und danach warten bis etwas zurückkommt, also ohne Interrupt.

Wo liegt der Vor- bzw. Nachteil der beiden Methoden.
Kann es zu irgendwelchen Fehlern kommen, wenn man einfach darauf bis man 
die Daten empfängt?

Ich habe hier schon einmal etwas getestet, ist jetzt aber nur ein Auszug 
davon und nicht das ganze Programm.
Das Problem ist es funktioniert nicht immer. Einmal funktioniert es gut 
und dann bei dem nächsten Befehl nicht mehr, obwohl es genau der gleiche 
Code ist und nur der gesendete Befehl und die zu erwartende Antwort 
verändert wurde. Kann das ein Problem sein wenn ich einfach auf eine 
Antwort warte?
1
 do{    
2
    uart_puts("AT Z");  // OBD Reader initialisieren
3
    _delay_ms(20);
4
    
5
    uart_gets( input, sizeof( input ) / sizeof( input[0] ) );
6
    
7
    zwischen[0]=input[7];
8
    zwischen[1]=input[8];
9
    zwischen[2]=input[9];
10
    zwischen[3]=input[10];
11
    zwischen[4]=input[11];
12
    zwischen[5]=input[12];
13
    zwischen[6]='\0';
14
    
15
    vergleich=1;
16
    stop=1;
17
    vergleich= strcmp( zwischen, "ELM327");
18
    if (vergleich==0)
19
    {
20
      stop=0;  
21
      obd=1;    
22
    }
23
    else 
24
    {
25
      stop=1;  
26
            
27
    }
28
    
29
    }while(stop==1);

mfg

von Paule H. (stk500-besitzer)


Lesenswert?

Atmega Anfänger schrieb:
> Wo liegt der Vor- bzw. Nachteil der beiden Methoden.
Der Vorteil, es mit Interrupts zu realisieren liegt darin, dass dein 
Controller nebenbei noch andere Sachen erledigen kann.
Wenn er die ganze Zeit auf ein Zeichen vom USART wartet, dann ist das 
verschenkte Zeit.

Atmega Anfänger schrieb:
> Kann es zu irgendwelchen Fehlern kommen, wenn man einfach darauf bis man
> die Daten empfängt?

Ja.


Wartschleifen in Programmen sind immer irgendwie doof (weil sinnlos...).
Wieso hast du da ein Delay drin, wenn du danach eine Funktion aufrufst, 
die vermutlich auf Polling basiert (wie funktioniert "uart_gets" ?).

von EGS_TI (Gast)


Lesenswert?

Atmega Anfänger schrieb:
> Soll ich das ganze Interruptgesteuert machen oder einfach den Befehl
> senden und danach warten bis etwas zurückkommt, also ohne Interrupt.

Kommt drauf an, wie komplex deine gesamte Anwendung wird, bzw. werden 
könnte.

Wenn die einzige Aufgabe, die oben beschriebene ist, so ist es 
sicherlich egal.
In der Polling-Methode wäre dann aber noch ein Timeout angebracht.

von M. K. (sylaina)


Lesenswert?

Atmega Anfänger schrieb:
> Wo liegt der Vor- bzw. Nachteil der beiden Methoden.

Der Vorteil von Interupts ist, wie schon gesagt wurde, dass man nicht 
warten muss im Programmcode bis was passiert sondern so lange andere 
Dinge machen kann.
Der Nachteil ist, dass mehrere Interrupts gleichzeitig auftreten können 
und dann kann das Interrupthandling knifflig werden.
Ich finde, einen "normalen" Programmaufbau (also nicht 
Interrupt-basiert) ist für jemand Fremdes besser lesbar. Ich persönlich 
baue meine AVR-Codes (benutze den Atmega 88PA) aber auch immer 
Interruptgesteuert da man hier, finde ich zumindest, eine bessere 
Kontrolle über den Atmega hat wenn auch der Code dabei 
"komplexer/undurchsichtiger" wird.
Ich denke, welche Form mal wählt, hängt zum einem von der 
Aufgabenstellung ab und zum anderen aber auch von der Vorliebe des 
Programmierers.

von Atmega Anfänger (Gast)


Lesenswert?

Ok danke schonmal für die schnellen Antworten.

Und was sagt ihr zu meinem Code? Wieso funktioniert dieser nicht immer?

von Karl H. (kbuchegg)


Lesenswert?

Weil höchst wahrscheinlich in 'input' nicht das steht, was du denkst, 
dass da drinn stehen sollte.

Solche Dinge:
    zwischen[0]=input[7];
    zwischen[1]=input[8];
    zwischen[2]=input[9];
    zwischen[3]=input[10];
    zwischen[4]=input[11];
    zwischen[5]=input[12];
    zwischen[6]='\0';
sind immer strategisch gesehen gefährlich. Ein einziges zusätzliches 
Leerezeichen oder ein nicht abgeholter Carriage Return oder Line Feed, 
der noch im Input Buffer rumlungert, und schon ist das ein Griff ins 
Klo.

Lass dir halt input bzw. zwischen mal irgendwo ausgeben. Dann siehst du 
genau, aus welchem Grund der
    strcmp( zwischen, "ELM327");
keine Übereinstimmung findet.

Wenn du Ratespielchen nicht magst, dann gibt es eine einfache aber 
wirkungsvolle Abhilfe dagegen: Lass die Variablen irgendwo ausgeben! 
Genau aus diesem Grund finde ich es leichtsinnig, wenn man sich gerade 
als Anfänger nicht als allererstes eine Möglichkeit dazu schafft. Sei es 
ein LCD oder eine zusätzliche UART, an der ein Terminal-PC hängt und auf 
dem man nach Herzenslust Zwischenergebnisse rausgeben kann. Wenn du 
deinem Programm mangels geeignetem Debugger nicht während der Laufzeit 
zusehen kannst, dann musst du eben das nächst Beste tun: Dein Programm 
muss dir helfen festzustellen, was es warum tut. Den Code, den es 
abarbeitet, kennst du. Aber die aktuell gerade gültigen Variablenwerte 
kennst du nicht. Daher ....

von Oliver (Gast)


Lesenswert?

Wobei eine Debuggerausgabe über UART bei der Entwicklung eines 
UART-Treibers schon ein gewisses Henne-Ei-Problem darstellt ;)

Oliver

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Wenn du dein grundsätzliches Problem gelöst hast, würde ich dir 
empfehlen, zumindest den Empfängercode interruptbasiert zu machen. Dann 
entgeht dir kein Zeichen, das der ELM senden könnte. Dafür ist es 
allerdings nötig, ein paar Flags ins Programm zu nehmen, um dem 
Hauptprogramm den Status der Interruptroutine mitzuteilen, und evtl. 
Buffer zu behandeln.

Viel Spass mit OBD-II

von Atmega Anfänger (Gast)


Lesenswert?

Aber wie soll ich es denn sonst machen wenn ich eine Antwort von dem OBD 
Auslesegerät auswerten will?

Da bleibt mir doch nichts anderes übrig, außer die einzelnen Teile so 
rauszusuchen, oder?

Jetzt beim Testen sende ich die Befehle auch mit einem Terminalprogramm. 
Also sollten doch die Zeichen auch so ankommen. Aber ich werde es jetzt 
nochmal vorher mir zum Terminalprog zurückschicken lassen.

Wie kann ich das ganze dann aber verbessern, damit das im Normalbetrieb 
dann später nicht passiert?

mfg

von Karl H. (kbuchegg)


Lesenswert?

Oliver schrieb:
> Wobei eine Debuggerausgabe über UART bei der Entwicklung eines
> UART-Treibers schon ein gewisses Henne-Ei-Problem darstellt ;)

Zugegeben. Aber es gibt ja auch noch LCD bzw. andere Möglichkeiten.

von Karl H. (kbuchegg)


Lesenswert?

Atmega Anfänger schrieb:

> Da bleibt mir doch nichts anderes übrig, außer die einzelnen Teile so
> rauszusuchen, oder?

Du suchst aber nichts raus.
Du nimmst an, dass das gesuchte genau an diesen Positionen im String 
steht!

Das ist ein Unterschied!

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ich habe jetzt das ELM327 Protokoll nicht im Kopf, aber mit Sicherheit 
sendet der Chip am Ende einer Zeichenkette so etwas wie in CR oder ein 
LF, zumindest ein Marker, das er fertig ist mit der Übertragung.
Während des normalen Zeichenempfangs schreibt die ISR einfach alle 
Zeichen in einen Pufferspeicher. Sobald die ISR den 'Ende' Marker 
findet, setzt sie ein Flag a la 'Kompletter String empfangen' und das 
Hauptprogramm kann den Puffer dann anzeigen, bearbeiten etc.

von Walter S. (avatar)


Lesenswert?

Paule H. schrieb:
> Wenn er die ganze Zeit auf ein Zeichen vom USART wartet, dann ist das
> verschenkte Zeit.

für die man aber leider kein Geld kriegt,
wenn man nichts besseres vor hat ist Polling auch ok

Atmega Anfänger schrieb:
> Wie kann ich das ganze dann aber verbessern

indem du du die Antworten liest (Tipp: delay)

von Michael M. (technikus)


Lesenswert?

Atmega Anfänger schrieb:
> Kann es zu irgendwelchen Fehlern kommen, wenn man einfach darauf bis man
> die Daten empfängt?

Da kannst Du Gift drauf nehmen! Vor allem die Zuweisung
>     zwischen[0]=input[7];
>     zwischen[1]=input[8];
>     zwischen[2]=input[9];
>     zwischen[3]=input[10];
>     zwischen[4]=input[11];
>     zwischen[5]=input[12];
>     zwischen[6]='\0';
ist sehr optimistisch. Wenn Dein Input nur um ein Zeichen verrutscht, 
geht es schon schief! Während Deines _delay_ms(20) gehen alle Zeichen 
bis auf das letzte verloren! Keine Ahnung was uart_gets genau macht, 
wahrscheinlich wartet es dann bis in alle Ewigkeit auf diese verlorenen 
Zeichen.

Ich würde folgendes empfehlen:
Mach den Empfang mit IRQ-Handler. Der IRQ-Handler schreibt die 
empfangenen Bytes in ein char Array (=String). Der String wird jeweils 
vom Handler mit ASCII 0 C-konform abgeschlossen.
In dem String sucht die Hauptschleife mittels strstr dann ELM327. Wenn 
ELM327 gefunden wurde, wird der String wieder gelöscht.

Servus
Michael

von Atmega Anfänger (Gast)


Lesenswert?

An dem Delay kann es nicht liegen. Wenn ich es händisch eingebe dann bin 
ich sicher langsamer als 20 ms.

@michael

Danke für deinen Tipp das werd ich mal versuchen.

von Viktor N. (Gast)


Lesenswert?

>Der Nachteil ist, dass mehrere Interrupts gleichzeitig auftreten können
und dann kann das Interrupthandling knifflig werden.

Nein. Eigentlich nicht. Man darf enfach nicht auf dieselben Variablen 
zugreifen.

von Thomas E. (thomase)


Lesenswert?

Atmega Anfänger schrieb:
> An dem Delay kann es nicht liegen. Wenn ich es händisch eingebe dann bin
> ich sicher langsamer als 20 ms.
Oh, oh.
Eine grosse Fehleinschätzung.

Handeingabe entspricht diesem hier:
1
uart_puts("A");  // OBD Reader initialisieren
2
_delay_ms(xxx);
3
uart_puts("T");  // OBD Reader initialisieren
4
_delay_ms(xxx);
5
uart_puts(" ");  // OBD Reader initialisieren
6
_delay_ms(xxx);
7
uart_puts("Z");  // OBD Reader initialisieren
8
//Siehst du den Unterschied?
Nach dem "Z" rauscht dein ODB ohne sonderliche Verzögerung los und der 
Empfänger MUSS bereit sein. Und das ist er auch.

Aber nicht hier:
1
uart_puts("AT Z");  // OBD Reader initialisieren
2
_delay_ms(20);
3
//Siehst du den Unterschied?
Da wartet er noch 20ms.

mfg.

von Karl H. (kbuchegg)


Lesenswert?

Michael M. schrieb:

> In dem String sucht die Hauptschleife mittels strstr dann ELM327. Wenn
> ELM327 gefunden wurde, wird der String wieder gelöscht.

Trotzdem würde ich mir zuallerst mal ansehen, wie und warum der String 
offenbar manchmal verrutscht ist. Aus diesen Erkentnissen lernt man und 
hat dann bei anderen Dingen weniger Probleme, wenn man erst mal diese 
Basisproblemchen ausgeräumt hat bzw. davon weiß.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Eckmann schrieb:

> Handeingabe entspricht diesem hier:

Ich hab das anders rum interpretiert. Ich denke er meint: Er simuliert 
momentan händisch das OBD Gerät.

von Walter S. (avatar)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Ich hab das anders rum interpretiert. Ich denke er meint: Er simuliert
> momentan händisch das OBD Gerät.

und deswegen stört in dem Fall das delay nicht,
wenn die Antwort aber vom OBD kommt gehen evtl. Zeichen verloren.
Das delay ist sowohl sinnlos als auch falsch

von Karl H. (kbuchegg)


Lesenswert?

Walter S. schrieb:

> Das delay ist sowohl sinnlos als auch falsch

Das zweifellos.
Ich denke da hat er etwas falsch interpretiert. Bei "AT Z" steht ja oft 
dabei, dass man den Geräten dann erst mal eine kurze Pause gönnen muss 
(weil sie ja neu initialisieren). Gemeint ist damit aber, dass man keine 
weiteren Kommandos in einem Zeitraum schicken darf. Sich erst mal eine 
zeitlang 'tot' stellen, ehe man auf Antwort wartet, ist allerdings 
sinnlos. Denn das Gerät antwortet sowieso nicht früher, als wenn es 
bereit ist. Nur sollte der Empfänger (also der Mega) tunlichst dann auch 
schon bereit sein.


Gut, in seinem Fall mit Handsimulation des Geräts spielt es noch keine 
Rolle, drum hab ich auch nichts gesagt.

von Michael M. (technikus)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Michael M. schrieb:
>
>> In dem String sucht die Hauptschleife mittels strstr dann ELM327. Wenn
>> ELM327 gefunden wurde, wird der String wieder gelöscht.
>
> Trotzdem würde ich mir zuallerst mal ansehen, wie und warum der String
> offenbar manchmal verrutscht ist. Aus diesen Erkentnissen lernt man und
> hat dann bei anderen Dingen weniger Probleme, wenn man erst mal diese
> Basisproblemchen ausgeräumt hat bzw. davon weiß.

Das ist schon richtig. Man sollte sich nicht auf die Fehlertoleranz des 
Protokolls verlassen und seine Protokollfehler ignorieren. Aber im 
Hinblick auf die (leider nicht genannte) Applikation ist der Ansatz, 
erst mal alle Zeichen zuverlässig zu empfangen sicher auch nicht 
verkehrt. Nur genau das zu empfangen, was man erwartet und das noch mit 
_delay_ms() hingetrimmt wird relativ bald schief gehen.

Zumal der korrekte Rückgabewert wohl eher so aussieht
1
ELM327 v1.4b
2
>
So sieht es zumindest im Datenblatt 
http://elmelectronics.com/DSheets/ELM327DS.pdf aus. Wenn da dann die 
letzten Zeichen des Versionsstrings oder das neue Prompt > nicht 
mitgelesen werden, ist der Fehler schon geklärt. Wie uart_gets() 
aussieht, wissen wir ja nicht.

Servus
Michael

von Atmega Anfänger (Gast)


Lesenswert?

Ok das delay ist falsch das hab ich jetzt schon verstanden.

Ja vom OBd Lesegerät kommt nicht nur ELM327 zurück sondern
AT Z ELM327 v1.4 >

Wenn ich aber nur das ELM327 abfrage muss es doch auch reichen oder 
nicht?

Also das mit der Strstr Funktion hört sich doch recht gut an oder liege 
ich da auch schon wieder falsch?

Michael M. schrieb:
> Wenn da dann die
> letzten Zeichen des Versionsstrings oder das neue Prompt > nicht
> mitgelesen werden, ist der Fehler schon geklärt.

wenn das Prompt > nicht mitgelesen wird, dann passiert gar nichts weil 
ich das als Endzeichen genommen habe.

Michael M. schrieb:
> Wie uart_gets()
> aussieht, wissen wir ja nicht.
1
uint8_t uart_getc(void)
2
{
3
    while (!(UCSRA & (1<<RXC)))   // warten bis Zeichen verfuegbar
4
        ;
5
    return UDR;                   // Zeichen aus UDR an Aufrufer zurueckgeben
6
}
7
 
8
void uart_gets( char* Buffer, uint8_t MaxLen )
9
{
10
  uint8_t NextChar;
11
  uint8_t StringLen = 0;
12
 
13
  NextChar = uart_getc();         // Warte auf und empfange das nächste Zeichen
14
 
15
                                  // Sammle solange Zeichen, bis:
16
                                  // * entweder das String Ende Zeichen kam
17
                                  // * oder das aufnehmende Array voll ist
18
  while( NextChar != '>' && StringLen < MaxLen - 1 ) {
19
    *Buffer++ = NextChar;
20
    StringLen++;
21
    NextChar = uart_getc();
22
  }
23
 
24
                                  // Noch ein '\0' anhängen um einen Standard
25
                                  // C-String daraus zu machen
26
  *Buffer = '\0';
27
}

von c-hater (Gast)


Lesenswert?

Oliver schrieb:

> Wobei eine Debuggerausgabe über UART bei der Entwicklung eines
> UART-Treibers schon ein gewisses Henne-Ei-Problem darstellt ;)

Nicht mit einer vernünftigen Entwicklungsumgebung. Da gibt's nämlich so 
nette Sachen wie Simulatoren und Stimuli-Dateien...

von Michael M. (technikus)


Lesenswert?

Atmega Anfänger schrieb:
> Ja vom OBd Lesegerät kommt nicht nur ELM327 zurück sondern
> AT Z ELM327 v1.4 >
>
> Wenn ich aber nur das ELM327 abfrage muss es doch auch reichen oder
> nicht?

Ja klar, näheres unten. Aber eben im Hinterkopf behalten: Es gibt ein 
Echo (der ELM327 gibt Eingabezeichen 1:1 zurück) und er schreibt weitere 
Daten incl. neuem Prompt.

> Also das mit der Strstr Funktion hört sich doch recht gut an oder liege
> ich da auch schon wieder falsch?

Ich finde strstr zum Parsen von Strings recht praktisch. Vor allem 
bekommt man einen Zeiger auf die Fundstelle. Weil man ja die Länge des 
Suchstrings kennt, kann man schauen, welche Argumente danach kommen:
1
char *p; // Zeiger für die Fundposition
2
float version;
3
4
p = strstr(input, "ELM327 v");
5
if(p != NULL) // ansonsten war kein "ELM327 v" drin
6
{
7
   version = (float) strtod(p+8, &p); // Zahl beginnt 8 Zeichen danach
8
   // jetzt könnte man mit p weiterparsen, wenn es mehrere Argumente gäbe
9
}


> wenn das Prompt > nicht mitgelesen wird, dann passiert gar nichts weil
> ich das als Endzeichen genommen habe.

Ok, das ist eine wichtige Info. Wobei ich sagen würde, > wird in Deiner 
uart_getc mitgelesen, aber als Stoppzeichen verworfen. Das macht sehr 
wohl einen Unterschied: RXC in UCSRA wird nämlich durch das Lesen von 
UDR gelöscht.

>
1
> void uart_gets( char* Buffer, uint8_t MaxLen )
2
> {...}

Ok, das schaut schonmal gar nicht so verkehrt aus. Trotzdem solltest Du 
folgende Fälle bedenken:
Wenn der ELM327 aus irgendwelchen Gründen keine Daten zurückschickt, 
bleibt uart_gets() bis in alle Ewigkeit hängen. Denn das erste 
uart_getc() wird nie verlassen.

Wenn uart_gets() wegen StringLen < MaxLen - 1 abbricht, bleiben 
eventuell Zeichen im UDR stehen. Die werden dann beim nächsten Aufruf 
von uart_gets() ausgelesen und stehen vor dem erwarteten Rückgabestring. 
Das ist genau der Punkt, wo dann eine Zuordnung wie 
zwischen[0]=input[7]; schief geht.

Es bleibt jedem selber überlassen, ob er pollt oder einen IRQ-Handler 
benutzt. Ich finde es halt praktisch, wenn der Controller was sinnvolles 
wie LCD-Ausgaben erledigen kann, während er auf die Daten wartet. Vor 
allem muß man beim Pollen immer aufpassen, daß man nicht in einer 
Endlosschleife stecken bleibt, weil der erwartete Input irgendwie fehlt.

Servus
Michael

von Carsten R. (kaffeetante)


Lesenswert?

Ob Code mit Interrupts schlechter lesbar sind, ist eine Frage des 
(schlechten) Programmierstils. Dies ist nur ein subjektiver Trugschluß 
aus der Situation heraus.

Das ist genau so sinnvoll, als ob an Sagen würde:

"Es regnet nur wenn ich den Fuß vor die Wohnungstür setze."

Wenn man mal aus dem Fenster gucken würde, wäre es offensichtlich, daß 
es auch mal zu anderen Zeiten regnet.

Umgekehrt kann man aber sagen:

Wer sich die Mühe macht um über Interrupts und den Code nachzudenken, 
entwickelt manchmal die Fähigkeit strukturierten und wirklich lesbaren 
Code zu schreiben. Bei Interrupts werden oder sollten Programmteile 
ausgegliedert werden. Exakt das ist eines der Kernelemente die 
Spaghetti-Code von lesbarem Code unterscheiden.

Bei Kleinstprogrammen fällt das nur nicht so schnell auf, da man nicht 
ausgliedert um des Ausgliederns Willen, sondern zum Strukturieren. Hat 
man nur ein, zwei Programmfunktionen, so ist nichts zum strukturieren 
da. Folglich ist dort kaum ein Unterschied zu erkennen. Mit wachsender 
Komplexität wird der Unterschied zwischen "quick&dirty" und 
"programmieren mit System" größer.

Ohne System scheitert man an Interrupts. Die Strukturierug zu 
unterlassen und Interrupts in Spaghettimanier zu nutzen ist ganz großer 
Mist! Kurzfristig mag es gut gehen, aber man stößt schnell an die 
Grenzen. Und dann sind natürlich die bösen Interrupts schuld.

Aber nicht die Interrupts sind das Problem, sondern der Progammierstil!

Gruß Carsten

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.