Hi,
ich habe folgendes konkrete Problem: ein Microcontroller soll einen
Sensor auslesen und beim Eintreten bestimmter Messwerte diverse Aktionen
für eine vorgegebene Dauer ausführen. Gleichzeitig soll der
Mikrocontroller eine LED regelmäßig blinken lassen.
Ich benutze die Programmiersprache C und einen ATmega48.
Der Sensor soll mit 25 Hz gesamplet werden, die LED soll alle zwei
Sekunden für 0,1 Sekunden blinken.
Meine Lösung basiert darauf, dass ein counter regelmäßig eine Interrupt
Service Routine aufruft welche zwei Zeitvariablen (Sekunden und
Hundertstel Sekunden) und ein Flag zum Messen setzt. In der
Hauptschleife werden dann je nach Inhalt der Zeitvariablen die LED ein
oder ausgeschaltet und wenn das Messen-Flat gesetzt ist, wird der Sensor
ausgelesen.
Die Lösung funktioniert zwar, aber mit folgenden Problemen:
a) Die LED blinkt wenn man genau hinschaut manchmal etwas unregelmässig
b) Da ich mich Mikrocontroller Programmierung nicht gut auskenne, gehe
ich davon aus, dass das gesamte Konstrukt verbesserungsfähig ist. Konnte
dazu aber nichts finden.
Hier noch die relevanten Code Auszüge:
1
...
2
volatileuint8_tm_flag;//ist das Flag gesetzt, soll gemessen werden
3
4
volatileuint8_tcenti_seconds;
5
volatileuint8_tseconds;
6
7
...
8
9
ISR(TIMER1_COMPA_vect){
10
11
if(centi_seconds<100){
12
centi_seconds++;
13
}
14
else{
15
centi_seconds=0;
16
seconds++;
17
}
18
19
if(centi_seconds%4==0){// generate the clock for 25Hz sampling
20
m_flag=DO_MEASUREMENT;
21
}
22
}
23
...
24
voidmain(void){
25
26
..
27
// configure timer1 (16bit) for 10ms clock
28
OCR1A=(72-1);// by counting from 1 to 72 at a clock of 7372800/1024=7200 10ms pass
29
30
TCCR1B=(1<<WGM12)|(1<<CS12)|(0<<CS11)|(1<<CS10);// CTC mode and division by 1024
31
TIMSK1=1<<OCIE1A;
32
33
34
centi_seconds=0;
35
seconds=0;
36
37
38
...
39
40
while(1){
41
...
42
43
if(m_flag==DO_MEASUREMENT){
44
...//Lesen des Sensor
45
}
46
47
//do other things
48
49
// every 2s activat the LED for 0.1s to indicate that the system is on
50
if(seconds%2==0&¢i_seconds<10){
51
PORTB=0<<0;
52
}
53
else{
54
PORTB=1<<0;
55
}
56
..
57
}
Als Antwort würde mir schon ein Hinweis reichen, wie derartige Programme
im allgemeinen aufgebaut sind. Z.B. ein Tutorial für "Design Pattern"
für Echtzeitsysteme wäre vermutlich ideal.
Danke und Gruß!
Hallo Karl Heinz,
in der i2c-Kommunikation zum Sensor sind ein paar Warteschleifen drin,
also so etwas wie:
1
// wait until transmission completed and ACK/NACK has been received
2
while(!(TWCR&(1<<TWINT)));
.
Ansonsten gibt es da aber keine Warteschleifen wie z.B _delay_ms(x).
Vermutlich hast Du Recht, und das unregelmässige Blinken kommt
tatsächlich von der Dauer des zitierten Codes.
Ich habe das Ein- und Ausschalten der LED jetzt in den ISR verlagert. So
blinkt die LED jetzt regelmäßig. Allerdings würde ich den ISR lieber
etwas kürzer halten.
Danke für Deine Antwort!
dreimalsoviel schrieb:> Hallo Karl Heinz,>> in der i2c-Kommunikation zum Sensor sind ein paar Warteschleifen drin,> also so etwas wie:>
1
>// wait until transmission completed and ACK/NACK has been received
2
>while(!(TWCR&(1<<TWINT)));
3
>
> .
Und hier hast du den Übeltäter.
> Ich habe das Ein- und Ausschalten der LED jetzt in den ISR verlagert. So> blinkt die LED jetzt regelmäßig. Allerdings würde ich den ISR lieber> etwas kürzer halten.
Na ja.
Man muss nicht päpstlicher als der Papst sein.
Ob du jetzt ein Flag auf 1 setzt oder einen Portpin umschaltest, das ist
Jacke wie Hose. Ein bischen was darf man auch in einer ISR machen.
Die Alternative wäre, die Sensor Kommunikation mit einer
Zustandsmaschine abzubilden und in Einzelteile aufzulösen, in denen
nicht gewartet wird.
Hm, das sind jetzt zwei grundverschiedene Dinge.
Erstens:
dreimalsoviel schrieb:> Die Lösung funktioniert zwar, aber mit folgenden Problemen:> a) Die LED blinkt wenn man genau hinschaut manchmal etwas unregelmässig
Zweitens:
dreimalsoviel schrieb:> Z.B. ein Tutorial für "Design Pattern" für Echtzeitsysteme> wäre vermutlich ideal.
Zu Erstens:
Vom Quelltext sind nur Fragmente vorhanden.
(Das dient der Übersicht, ist also erstmal positiv.
Lässt aber natürlich Raum für Spekulation...)
Ich vermute, das Problem liegt daran:
Du willst zwei Dinge quasi gleichzeitig machen
1. Messen
2. LED blinken lassen
Zu 1. setzt du in der ISR ein Flag, das ist ok.
In main() fragst du das Flag (ich sehe nicht, daß das Flag
danach gelöscht wird, aber vielleicht ist das nur hier nicht
zu sehen - ich gehe mal davon aus, daß es gemacht wird).
Vom vielleicht fehlenden Zurücksetzen des Flags abgesehen ist
das auch ok.
Zu 2. setzt du kein Flag, sondern zählst nur die Zeit
weiter.
Wenn in main() die Messung solange dauert, daß Zeit zum Blinken
nicht nur erreicht, sondern überschritten wird, wird nicht
geblinkt.
Schlauer wäre es vielleicht auch dafür in der ISR beim Erreichen
der Zeit ein Flag zu setzen, in der main() abzufragen und nach
dem Erledigen wieder zu löschen.
Also schlage ich für 2. das gleiche "design pattern" vor wie
für 1.:
In der ISR die Bedingung prüfen ("Zeit erreicht"), ein Flag
setzen, und in main() abfragen, erledigen, und zurücksetzen.
Zu Zweitens:
Design Patterns sind ein Weg, öfter vorkommende Fälle über
einen Kamm zu scheren.
Das hat eine gewisse Berechtigung, ist aber nicht immer
sinnvoll.
Diese Verallgemeinerung kostet Resourcen, und ist auf
Controllern deshalb nicht unbedingt empfehlenswert.
Design Patterns gehen leicht in Richtung political correctness:
Im Prinzip richtig, aber wenn man sie ohne nachzudenken
als Totschlagargument mißbraucht, werden sie kontraproduktiv.
Hat man genug Resourcen, können sie oft vorkommende Probleme
erleichtern.
Bei MC kommt es aber wesentlich mehr auf den Einzelfall an,
und pauschale Lösungen muß man mit Verstand etwas flexibler
handhaben.
Deshalb erscheint mir etwas praxisfremd, nach design patterns
zu suchen.
Natürlich gibt es bewährte Vorgehensweisen, aber die sind sicher
nicht so konkret auszuformulieren wie in der OO-Welt auf
fetten Systemen.
Dort kann man ein design pattern relativ konkret ausformulieren
bis hin zu Quelltext in der gewünschten Sprache und der
Programmierer als Anwender des Patterns setzt nur noch ein paar
anwendungsspezifische Eigenheiten irgendwo ein, fertig ist eine
Lösung, die funktionieren sollte.
In der Controllerwelt gibt es zwar wie gesagt ebenso die
allgemein bewährte Vorgehensweise, aber die muß man etwas
individueller umsetzen.
Also nicht für eine bestimmte Problemstellung aus dem Regal
einen Quelltext nehmen, sondern mehr die Regel "in Timer-ISR
Flag setzen, in main() darauf reagieren" und das dann je nach
den Umständen individuell programmieren.
Ich will also jetzt beileibe nicht gegen design patterns
wettern.
Man darf sie nur nicht falsch verstehen oder zuviel konkrete
Lebenshilfe erwarten.
In 1. hast du dich an das genannte Muster gehalten, in 2. nicht,
und schon geht es schief.
Karl heinz Buchegger schrieb:>> Ich habe das Ein- und Ausschalten der LED jetzt in den ISR verlagert. So>> blinkt die LED jetzt regelmäßig. Allerdings würde ich den ISR lieber>> etwas kürzer halten.>> Na ja.> Man muss nicht päpstlicher als der Papst sein.> Ob du jetzt ein Flag auf 1 setzt oder einen Portpin umschaltest, das ist> Jacke wie Hose. Ein bischen was darf man auch in einer ISR machen.
Das stimmt einerseits.
Andererseits finde ich es schon erstrebenswert, ein und dieselbe
Vorgehensweise für ähnliche Fälle einzuhalten, gerade innerhalb
eines Programms.
Das macht ein Programm verständlicher und besser
änderbar/erweiterbar.
Also auch wenn es durchaus funktioniert, die LED direkt in
der ISR zu setzen, finde ich es symmetrischer, beides über ein
Flag zu steuern.
Kommt aber natürlich auf Geschmack und Windrichtung an, das
will ich jetzt nicht hochstilisieren.
Es war nur auch die Frage nach "design patterns", dann kann man
natürlich über den Stil philosophieren...
Das unregelmäßige Blinken wird vermutlich darauf zurückzuführen sein,
dass während des Auslesens des Sensors in der main-Methode die ISR
mehrfach aufgerufen wird und dadurch centi_seconds >= 10 wird.
Wenn das der Fall ist, könntest du eine Verbesserung erreichen, indem du
vor Auslesens des Sensors die "Blinkbedingung" (seconds % 2 == 0 &&
centi_seconds < 10) in einer booleschen Variablen speicherst und diese
dann später abfragst.
So baue ich das auf. "Modul" ist hierbei eine Variable. Kann z.B. "IO"
für Tastenentprellung sein (benutzt dann den 100Hz Takt) oder "LCD" für
eine LCD Implementierung (nutzt u.U. gar keinen Takt). Oder "StatusLED"
(benutzt den 10Hz zum Blinken).
Die Modulfunktionen müssen dabei nicht-blockierend geschrieben sein.
z.B. per State-Machine.
1
#include"Modul.h"
2
3
Flags_tFlag;
4
5
ISR(<TIMER>)
6
{
7
Flag|=Tick100Hz;
8
...herunterteilen
9
Flag|=Tick10Hz;
10
}
11
12
intmain()
13
{
14
ModulInit(...);
15
16
while(1)
17
{
18
if(Flag100Hz)
19
{
20
ModulTick();/* Modul läuft mit 100Hz */
21
....
22
}
23
24
if(Flag10Hz)
25
{
26
ModulTick();/* Modul läuft mit 10Hz */
27
....
28
}
29
30
ModulProcess();/* Modul läuft so schnell wie möglich */
Vielen Dank Euch für die hilfreichen Antworten! Ich bin begeistert von
diesem Forum!
Ich lasse jetzt das LED-Blinken im ISR. Das ist die einfachste Lösung --
zwar nicht die schönste, aber vermutlich die, welche am wenigsten
Taktzyklen verbraucht.
Ich gehe einfach davon aus, dass das LED-Blinken weniger als zehn
Takt-Zyklen verbraucht. Der ISR wird 100 mal pro Sekunde aufgerufen.
Selbst wenn ich den ATmega 168 bei der minimalsten Taktrate von 128 kHz
betreibe, würden so nur 100 Zyklen, also 0,0008 Sekunden verbraucht. Das
wiederum, sollte nur für eine vernachlässigbar kleine Verzerrung der
Abtastrate von 25 Hz sorgen.
@Klaus: sorry habe den Titel falsch gewählt, hatte keine Namen für mein
Problem.
@Simon: danke für dein Beispiel. Habe es leider nicht verstanden.
Eine gute Woche!