Hallo zusammen,
in meinem Programm sollen ein paar Temperatursensoren und weitere
Sensoren alle 10 Sekunden abgefragt werden. Manche Sensoren sollen aber
auch dauernd abgefragt werden. Auch die UART wird die ganze Zeit
abgefragt und bekommt jede Sekunde einen NMEA Datensatz übermittelt, der
verarbeitet wird.
Ausserdem will ich bestimmte while-Schleifen mit einem Timeout nach ein
paar Sekunden beenden.
Zusätzlich sollen in verschiedenen Zeiten verschiedene Strings
übermittelt werden.
Timer1 habe ich aktiviert und jede ms wird eine Interrupt-Routine
durchlaufen.
So zu meiner Frage: Wie realisiere ich die Zeitsteuerung am besten bzw.
einfachsten?
Mein erster Gedanke war in der Interrupt-Schleife mehrere Variablen hoch
zu zählen und beim Erreichen eines bestimmten Wertes wieder 0-Zu-Setzen.
In meinen einzelnen Programmabschnitten vergleiche ich dann die
einzelnen Variablen mit einem bestimmten Wert und führe die Funktionen
dann bei Übereinstimmung aus.
Den Timeout erzeugt ich dann so, dass ich beim Erreichen der Schleife
den Variablenwert speicher und wenn die Schleife wiederholt wird mit dem
aktuellen Wert vergleiche. Bei z.B. 3 Sekunden unterscheidet sich der
Wert um >= 3 und die Schleife wird beendet.
Ich habe aber auch den Scheduler von Peter Dannegger gefunden
Beitrag "Wartezeiten effektiv (Scheduler)"
Ist das die beste Lösung für meine Aufgabe? Oder schieße ich damit mit
Kanonen auf Spatzen?
Wisst ihr zu diesen Problemstellungen ein gutes Tutorial oder eine
hilfreiche Seite?
Vielen Dank für eure Hilfe.
@ Peter Dannegger:
Würde deinen Scheduler gerne nutzen und ausprobieren, aber es kommen
beim compilieren schon einige Fehler, die ich nicht lösen kann.
Was ich verändert habe ist:
TCCR0 = 1<<CS02; in TCCR0B = 1<<CS02;
TIMSK = 1<<TOIE0; in TIMSK0 = 1<<TOIE0;
XTAL 8e6
und
#include <avr/signal>
habe ich gelöscht!
Sonst habe ich nichts verändert. Mein µC ist ein ATMEGA1280
Die Ausgabe des Compilers:
Verstehe ich das eigentlich richtig, dass ich keine while-Schleife mehr
benutzen darf, weil sonst das Timing durcheinander kommt?
z.B. frage ich in meiner main loop einen NMEA-Datensatz ab und schreibe
alle benötigten Werte in verschiedene Arrays. Bei der Abfrage werden ein
paar while-Schleifen benutzt um auf verschiedene Zeichen zu "warten".
Dem Scheduler adde ich eine Funktion die nur eine Ausgabe auf einer
anderen uart-Schnittstelle macht und sich dann wieder selber "addet", im
sekundentakt.
Die Ausgabe ist dann aber nicht sekündlich, sondern teilweise um einiges
länger. Kann ich das dann auch umgehen oder muss ich das so hinnehmen.
Wäre nicht sonderlich schlimm, aber besser, wenn es genau wäre.
Die Funktionen dürfen nicht länger dauern, als ein Aufruftakt des
Schedulers, sonst verliert man Takte.
Bei der UART ist das aber kein Problem, die kann man ja mit Interrupt
machen. D.h. der Scheduler schreibt nur in den Sendepuffer und der
UART-Interrupt sendet dann Byte für Byte.
Peter
Also meine uarts sind interruptgesteuert. Das ist deine Lib die ich
verwende.
Wäre es dann sinnvoll den Aufruftakt zu verlängern?
Bei mir dauert der bisher 8,2ms, wenn ich mich nicht verrechnet habe.
Was wäre ansonsten denn dann eine Alternative zu z.B.
while ( zeichen != '\n'); // Zeilende v. Datensatz erkennen
?
Danke.
Bene Jan schrieb:> Was wäre ansonsten denn dann eine Alternative zu z.B.>> while ( zeichen != '\n'); // Zeilende v. Datensatz erkennen
Nicht wartend, sondern abbrechend programmieren.
Hat eine Funktion nichts zu tun, gibt sie die Rechenzeit der Mainloop
zurück.
Schlecht:
1
main()
2
{
3
while(1){
4
fkt1();
5
fkt2();
6
fkt3();
7
...
8
fktn();
9
]
10
11
fkt1()
12
{
13
while(bedingung==unwahr);
14
mache_was();
15
}
Gut:
1
main()
2
{
3
while(1){
4
fkt1();
5
fkt2();
6
fkt3();
7
...
8
fktn();
9
]
10
11
fkt1()
12
{
13
if(bedingung==unwahr)
14
return;
15
mache_was();
16
}
Hat eine Funktion an mehreren Stellen zu warten, setzt sie sich einen
Merker (Variable), wo sie weiter machen muß (switch/case).
Peter
Hallo Peter,
ich habe mal versucht eine Funktion von mir abbrechend zu programmieren.
Das ganze funktioniert soweit, ist aber höchst wahrscheinlich alles
andere als perfekt. In meiner main loop befindet sich nur der Aufruf zu
dieser Funktion und die if-Schleife (f_timer_tick) von dir.
Ausserhalb der mainloop wird eine Funktion geadded die das Ergebnis
meiner Funktion auf die uart 1x pro Sekunde ausgibt und sich dann wieder
selber added.
Ich erreiche aber leider keine genaue Sekunde (mehr oder weniger), eher
2 Sekundentakt.
Du hattest in einem anderen Thread geschrieben, dass man die Ablaufzeit
größer machen soll, damit die mainloop mindestens einmal pro Tick
durchlaufen wird, wenn ich das richtig verstanden habe.
Ich habe dann in der main.h
#define TIMERTICK (XTAL 256 256)
ersetzt mit
#define TIMERTICK (XTAL 512 512)
Dadurch wird die Uart-Ausgabe aber 3x so schnell?!
Muss ich noch etwas anderes verändern?
Damit beim nächsten Durchlauf meiner Funktion die Variablenwerte noch
vorhanden sind, kann ich doch jetzt nur noch globale Variablen
verwenden, oder sehe ich das falsch?
Hier mal meine alt Warte-Funktion:
1
chargsmsignal[3];
2
char*GsmSignalAbrufen(void)
3
{
4
if(gsmreg[0]=='1')
5
{uint8_ti=0;
6
chargsmzeichen;
7
chargsmsatz[20];
8
9
while(!(0==ukbhit2()))//Alle noch vorhandenen Zeilen im Speicher abrufen und verwerfen
10
while(ugetchar2()!=10);
11
12
uputs2("AT+CSQ\r\n");//Eingabe AT-Befehl
13
while(ugetchar2()!=10);//Erste Zeile bis Linefeed (\r\n) einlesen und verwerfen
14
do// Kompletten Datensatz empfangen
15
{
16
gsmzeichen=ugetchar2();// Zeichen von USART einlesen
17
gsmsatz[i]=gsmzeichen;// Zeichenkette erweitern
18
i++;
19
}while(gsmzeichen!=10);// Zeilenende v. Datensatz erkennen
20
21
strncpy(gsmsignal,gsmsatz+6,2);//Benötigte Zahl in gsmtmp speichern
22
gsmsignal[2]='\0';//Stringende manuell setzen
23
24
}
25
returngsmsignal;
26
}
Und hier meine neue "Abbruch"-Funktion:
1
uint8_tmerkergsm1=0;
2
uint8_tigsm=0;
3
chargsmzeichen;
4
chargsmsatz[20];
5
6
char*GsmSignalAbrufen(void)
7
{
8
gsmreg[0]='1';
9
switch(merkergsm1)
10
{
11
case0:if(!(gsmreg[0]=='1'))
12
{
13
merkergsm1=0;
14
break;
15
}
16
17
case1:if(!(0==ukbhit2()))
18
{
19
ugetchar2();
20
merkergsm1=1;
21
break;
22
}
23
uputs2("AT+CSQ\r\n");
24
25
case2:if(!(ugetchar2()==10))
26
{
27
merkergsm1=2;
28
break;
29
}
30
31
case3:gsmzeichen=ugetchar2();// Zeichen von USART einlesen
Deine SChreibweise der switch-case mit den generellen Durchfallern zum
nächsten case und deine verquerten Abfragen machen mich wahnsinnig!
Dein ugetchar2, ist das ein wartendes getchar?
Ausserdem, denke ich, ist die Logik nicht dieselbe.
Schreib doch die Statemaschine mal ordentlich, so dass man nicht ständig
um die Ecke denken muss. Du bist in einem Zustand, was muss passieren,
damit in den nächsten Zustand gewechselt wird und nicht anders rum:
Unter welchen Umständen wird der Zustand nicht gewechselt und man fällt
durch in den nächsten Zustand.
Und schreib deine Abfrage vernünftig.
if(!(ugetchar2() == 10))
ist doch ein Schuss ins eigene Knie
if( ugetchar2() != 10)
Mach dir einen Plan welche Zustände es gibt und unter welchen Umständen
und unter Abarbeitung welchen Codes von diesem zustand aus in einen
anderen Zustand gewechselt wird.
Dein Zustand 0 wird zb sofort verlassenm, wenn du am Anfang der Funktion
schon die Bedingung zum verlassen herstellst. Dieser Zustand ist von
vorne herein schon mal sinnlos. Gerade Zustandsmaschinen haben einen
enormen Vorteil: man kann sie gut und einfach planen. Einfaches
Drauflosprogrammieren hingegen wird mit Fehlern bestraft.
Ich habe mir mal einen Plan gemacht und die Statemachine danach
programmiert.
Wenn ich es jetzt richtig verstanden habe, solle es jetzt besser sein.
Mein ugetchar ist ein wartendes, deshalb frage ich es jetzt immer erst
mit ukbhit ab.
Das am Anfang der Funktion
gsmreg[0] = '1';
steht, hat nur die Bedeutung, dass ich die Funktion die gsmreg[0]
zurückgibt, zur Zeit noch nicht vorher aufrufe. Daher setze ich den Wert
zur Zeit manuell.
Aber nochmal zu meinen anderen Fragen:
Wie kann in etwa eine mehr oder weniger genaue Sekunde erreichen?
Muss ich
#define TIMERTICK (XTAL 256 256)
umändern?
Kann man bei statemachines nur noch globale Variablen benutzen? Muss ich
dann jede Laufvariable global anlegen?
Hier mein verbesserter Code:
1
uint8_tmerkergsm1=0;
2
uint8_tigsm=0;
3
chargsmzeichen;
4
chargsmsatz[20];
5
6
char*GsmSignalAbrufen(void)
7
{
8
gsmreg[0]='1';
9
switch(merkergsm1)
10
{
11
case0:if(gsmreg[0]=='1')//GSM-Modul eingebucht?
12
merkergsm1=1;
13
break;
14
15
case1:if(0!=ukbhit2())//Zeichen vorhanden?
16
{
17
ugetchar2();//Zeichen abrufen und verwerfen
18
break;
19
}
20
merkergsm1=2;
21
break;
22
23
case2:uputs2("AT+CSQ\r\n");//AT-Befehl senden!
24
merkergsm1=3;
25
break;
26
27
case3:if(0!=ukbhit2())//Zeichen vorhanden?
28
if(ugetchar2()==10)//Ersten Datensatz abrufen und verwerfen
29
merkergsm1=4;//Später noch Timeout, wenn keine Zeichen zurückkommen!
30
break;
31
32
case4:if(0!=ukbhit2())
33
{
34
gsmzeichen=ugetchar2();// Zeichen von USART einlesen
Ich seh immer noch nicht, wie diese Statemachine dem hier
1
chargsmsignal[3];
2
char*GsmSignalAbrufen(void)
3
{
4
if(gsmreg[0]=='1')
5
{uint8_ti=0;
6
chargsmzeichen;
7
chargsmsatz[20];
8
9
while(!(0==ukbhit2()))//Alle noch vorhandenen Zeilen im Speicher abrufen und verwerfen
10
while(ugetchar2()!=10);
11
12
uputs2("AT+CSQ\r\n");//Eingabe AT-Befehl
13
while(ugetchar2()!=10);//Erste Zeile bis Linefeed (\r\n) einlesen und verwerfen
14
do// Kompletten Datensatz empfangen
15
{
16
gsmzeichen=ugetchar2();// Zeichen von USART einlesen
17
gsmsatz[i]=gsmzeichen;// Zeichenkette erweitern
18
i++;
19
}while(gsmzeichen!=10);// Zeilenende v. Datensatz erkennen
20
21
strncpy(gsmsignal,gsmsatz+6,2);//Benötigte Zahl in gsmtmp speichern
22
gsmsignal[2]='\0';//Stringende manuell setzen
23
24
}
25
returngsmsignal;
26
}
entsprechen soll.
Auch wenn der Teil etwas seltsam ist (und ich denke das es nicht das ist
was du eigentlich wolltest), aber das hier
while (!(0 == ukbhit2())) //Alle noch vorhandenen Zeilen
im Speicher abrufen und verwerfen
while (ugetchar2() != 10);
finde ich zb in der Statemaschine nicht wieder. Der case 0 ist immer
noch ein sinnloser Zustand. Der untere Teil der Statemaschine sieht ok
aus.
Bis auf das
if(gsmsignal[2] != '\0')
gsmsignal[2] = '1';
darauf kann ich mir überhaupt keinen Reim machen, was du da vor hattest.
while (!(0 == ukbhit2())) //Alle noch vorhandenen Zeilen
im Speicher abrufen und verwerfen
while (ugetchar2() != 10);
Das habe ich so abgeändert, weil ich alle noch vorhandenen Zeichen
abrufen und verwerfen will (Wenn welche vorhanden sind)und nicht nur bis
zum Zeilenende.
Ich weiß nicht, warum du den case 0 sinnlos findest. Soll ich die
Abfrage vor dem Eintreten in die Funktion machen? Ich muss doch
überprüfen ob das GSM-Modul eingebucht ist.
Mit
if(gsmsignal[2] != '\0')
gsmsignal[2] = '1';
will ich der nachfolgenden Funktion, die gsmsignal[0] und gsmsignal[1]
ausgibt, eine Möglichkeit geben zu überprüfen, ob ein "vernünftiger"
Wert in der Variable steht. Aber das ist in der Tat ausbaufähig.
Das soll aber eh entfallen, das die nachfolgende Funktion gsmreg[0]
abfragt.
Und wenn der Wert nicht == 1 ist, wird sie gar nicht ausgeführt.
Bene Jan schrieb:>> Ich weiß nicht, warum du den case 0 sinnlos findest.
Weil du am Anfang der Funktion mittels
gsmreg[0] = '1';
dafür sorgst, dass hier
case 0 : if(!(gsmreg[0] == '1'))
die Bedingung niemals falsch sein kann! gsmreg[0] ist immer gleich '1'.
Jedes einzelne mal, wenn die Funktion aufgerufen wird.
> while (!(0 == ukbhit2())) //Alle noch vorhandenen Zeilen> im Speicher abrufen und verwerfen> while (ugetchar2() != 10);>> Das habe ich so abgeändert, weil ich alle noch vorhandenen Zeichen> abrufen und verwerfen will (Wenn welche vorhanden sind)und nicht> nur bis zum Zeilenende.
Und was, wenn noch gar nicht alle Zeichen einer Zeile über die UART
eingetroffen sind?
Dann kriegst du spätestens beim Abwarten der 2.ten Antwort nachdem du
deinen Text rausgeblasen hast, ein Problem weil dann plötzlich ein \n
zuviel eintrudelt.
Aber das musst du wissen, du hast die Hardware bei dir und du kannst das
testen.
Ja das steht ja nur im Moment so, zum testen der Statemachine:
Bene Jan schrieb:> Das am Anfang der Funktion> gsmreg[0] = '1';> steht, hat nur die Bedeutung, dass ich die Funktion die gsmreg[0]> zurückgibt, zur Zeit noch nicht vorher aufrufe. Daher setze ich den Wert> zur Zeit manuell.Karl heinz Buchegger schrieb:> Und was, wenn noch gar nicht alle Zeichen einer Zeile über die UART> eingetroffen sind?> Dann kriegst du spätestens beim Abwarten der 2.ten Antwort nachdem du> deinen Text rausgeblasen hast, ein Problem weil dann plötzlich ein \n> zuviel eintrudelt.
Stimmt! Das änder ich wieder um!
Es ist aber doch richtig, dass ich nur noch globale Variablen in einer
Statemachine verwenden kann oder?
Ich denke mal, UART-Empfang als Statemaschine ist keine gute Idee.
Üblicher Weise sammelt man die Datenbytes im Empfangsinterrupt in einen
Puffer. Der Interrupt prüft dann auch, ob ein Endezeichen (z.B. '\n')
empfangen wurde und setzt ein Flag.
Und erst dann schnappt sich das Main den Puffer und wertet ihn aus. Das
kann in einem Ruck geschehen, da man auf nichts mehr warten muß.
Peter
Ich habe doch in meiner Frage oben beschrieben, was ich machen will etc.
Daraufhin hast du (peda) mir deinen Scheduler empfohlen. Und dann hast
du mir auch eine statemachine empfohlen.
Jetzt auf einmal sagst du, dass eine statemachine nicht sinnvoll bei
Uart-Empfang ist?!? Jetzt weiß ich gar nicht mehr, was ich machen soll?!
Eine Statemaschine ist generell eine gute Sache, um komplexe Abläufe
übersichtlich zu halten.
Nur für die UART ist sie das falsche Werkzeug, weil man nicht weiß, wann
und ob überhaupt Zeichen reinkommen.
Wenn Du mit jemandem sprichst, wartest Du ja auch erst ab, bis er seinen
Satz beendet hat und ziehst nicht vorher falsche Schlüsse.
Es gibt nicht das eine Universalwerkzeug, was für alles paßt. Man muß
immer das am besten geignetste auswählen.
Der C-Compiler meckert Dich z.B. an, wenn die Leerzeile am Ende fehlt.
Das ist nämlich für ihn das Zeichen, mit der Compilierung beginnen zu
können.
Peter
Bene Jan schrieb:> Ich habe doch in meiner Frage oben beschrieben, was ich machen will etc.
Wir sind ja hier keine Supportmitarbeiter, die dafür bezahlt werden.
Du mußt also davon ausgehen, daß die Tips allgemein meistens richtig
sind, aber vielleicht Dein Problem nicht zu 100% treffen.
Oftmals ist es auch schwierig für einen Außenstehenden, Dein Problem
richtig zu erkennen. Man kann ja nicht in Deinen Kopf oder auf Deine
Festplatte sehen, sondern kennt nur den kleinen Teil, den Du geschrieben
hast.
Peter
Ok, habe es mit der Statemachine jetzt erstmal versucht, bzw. versuche
es gerade und bisher klappt alles super! Ich benutze ja einen Fifo mit
Interruptsteuerung zum Uart-Empfang, dann sollte es ja eigentlich mit
der statemachine gehen?!?
Danke für deine Hilfe.
>Eine Statemaschine ist generell eine gute Sache, um komplexe Abläufe>übersichtlich zu halten.>Nur für die UART ist sie das falsche Werkzeug, weil man nicht weiß, wann>und ob überhaupt Zeichen reinkommen.>Wenn Du mit jemandem sprichst, wartest Du ja auch erst ab, bis er seinen>Satz beendet hat und ziehst nicht vorher falsche Schlüsse.
Wobei das streng genommen ja auch nur eine Statemaschine ist ;-)