mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Suche Design Pattern für Echtzeitsysteme


Autor: dreimalsoviel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
...
volatile uint8_t m_flag; //ist das Flag gesetzt, soll gemessen werden

volatile uint8_t centi_seconds;
volatile uint8_t seconds;

...

ISR(TIMER1_COMPA_vect) {

  if(centi_seconds < 100){
    centi_seconds++;
  }
  else{
    centi_seconds = 0;
    seconds++;  
  }
  
  if(centi_seconds % 4 == 0){    // generate the clock for 25Hz sampling
    m_flag = DO_MEASUREMENT;
  }
}
...
void main(void){

..
  // configure timer1 (16bit) for 10ms clock
  OCR1A = (72-1);  // by counting from 1 to 72 at a clock of 7372800/1024=7200 10ms pass
  
  TCCR1B = (1<<WGM12)|(1<<CS12)|(0<<CS11)|(1<<CS10);  // CTC mode and division by 1024
  TIMSK1 = 1<<OCIE1A;
  
  
  centi_seconds = 0;
  seconds = 0;


...

  while(1){
...
  
    if(m_flag == DO_MEASUREMENT){
...//Lesen des Sensor
}

//do other things

// every 2s activat the LED for 0.1s to indicate that the system is on
      if(seconds % 2 == 0 && centi_seconds < 10){
        PORTB = 0<<0;
      }
      else{
        PORTB = 1<<0;
      }
..
}

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ß!

Autor: Zwölf Mal Acht (hacky)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja. Man hat einen Timer, der den Takt vorgibt. zB 10ms. Auf den reagiert 
man im Main() mit einer oder mehreren Zustandsmaschinen.

Autor: www (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
es liegt am sensor

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dein grundsätzlicher Aufbau ist gut.
Genau so macht man das


Was ist mit dem Teil hier
   if(m_flag == DO_MEASUREMENT){
...//Lesen des Sensor
}

//do other things

wieviel Zeit wird da verbraucht? Irgendwelche Warteschleifen?

Autor: dreimalsoviel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Karl Heinz,

in der i2c-Kommunikation zum Sensor sind ein paar Warteschleifen drin, 
also so etwas wie:
// wait until transmission completed and ACK/NACK has been received
  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!

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
dreimalsoviel schrieb:
> Hallo Karl Heinz,
>
> in der i2c-Kommunikation zum Sensor sind ein paar Warteschleifen drin,
> also so etwas wie:
>
> // wait until transmission completed and ACK/NACK has been received
>   while(!(TWCR & (1<<TWINT)));
> 
> .

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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: dito (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

#include "Modul.h"

Flags_t Flag;

ISR(<TIMER>)
{
    Flag |= Tick100Hz;
    ...herunterteilen
    Flag |= Tick10Hz;
}

int main()
{
    ModulInit(...);

    while(1)
    {
        if (Flag100Hz)
        {
            ModulTick(); /* Modul läuft mit 100Hz */
            ....
        }
 
        if (Flag10Hz)
        {
            ModulTick(); /* Modul läuft mit 10Hz */
            ....
        }

        ModulProcess(); /* Modul läuft so schnell wie möglich */
    }

    return 0;
}

Autor: dreimalsoviel (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.