Forum: Mikrocontroller und Digitale Elektronik Programm anhalten, Notaus


von Dres (Gast)


Lesenswert?

Hi,
Mit einem ATTiny85(GCC) steuere ich mittels Timer1 einen Triac an.
Ich habe eine Überlasterkennung. Falls eine Überlast erkennt wird, soll 
das
Programm sicher beendet werden und kann nur noch durch einen Reset 
wiedergestartet werden.

Hier mein Beispiel-Code:
1
C-Code

int main (void) {
  initPortB();
  initTimer1();
  sei();                        // Global Interrupts aktivieren

  while(1) {
    if(ueberlast()){            // Überlasterkennung
      cli();                    // disable all interrupts->Notaus
      PORTB &= ~(1 << PORTB3);  // Triac ausschalten
      PORTB |= (1 << PORTB1);   // ÜberlastLED einschalten
      while(1);                 // Programm in Endlos-Loop?
      //return 0;                oder lieber so aussteigen?
    }
  }
  return 0;
}

Nun meine Frage was ist schöner|besser?
Mit der while(1)-Schlaufe weiss ich, dass nichts mehr passiert, aber es 
scheint unsauber, da das Programm nie wircklich beendet ist.
Mit return ist das Programm beendet, aber ich weiss eigentlich nicht, 
wie der Kompiler das umsetzt. Ich nehme an, dass er ebenfalls in eine 
Endlosschleife geht?
Was bevorzugt ihr?

von Sina A. (sinapse)


Lesenswert?

ist alles prinzipiell gut so...

Dres schrieb:
> Ich nehme an, dass er ebenfalls in eine
> Endlosschleife geht?

ja... deshalb ist es egal ob du mit return rausgehst oder selber in 
endlosschleife gehst.


ich finds nur so viel schoener
1
int main (void) {
2
  //init
3
  initPortB();
4
  initTimer1();
5
  sei();                        // Global Interrupts aktivieren
6
7
  //body
8
  while(!ueberlast()){}; 
9
   
10
  //aufraeumen
11
  cli();                    // disable all interrupts->Notaus
12
  PORTB &= ~(1 << PORTB3);  // Triac ausschalten
13
  PORTB |= (1 << PORTB1);   // ÜberlastLED einschalten
14
15
  return 0;                //hier ein kommentar dass in endlosschleife
16
                           //geht... dann verstehts auch der 
17
                           //programmierer von nebenan, der das 
18
                           //maintainen soll ;) 
19
}

dann ist das programm eher so aufgebaut: init, body, aufräumen, ende

lg

: Bearbeitet durch User
von Dres (Gast)


Lesenswert?

Vielen Dank für die Antwort und den Vorschlag zur Strukturierung. Macht 
den Code übersichtlicher.

von Sina A. (sinapse)


Lesenswert?

noch eine sache:

wenn es wirklich ein notaus ist, ist es wichtig, dass du garantieren 
kannst, dass der notaus sofort triggert. aber zwischen überlasterkennung 
und cli koennen theoretisch ganz viele andere interrupts triggern und 
den notaus verzögern. deshalb besser die überlasterkennung an einen 
interrupt zu hängen mit höchster prio und dort die notaus 
konfigurationen vorzunehmen. dann kann niemand mehr dazwischenfunken.
1
int main (void) {
2
3
  initPortB();
4
  initTimer1();
5
  sei();                        // Global Interrupts aktivieren
6
7
  
8
  while(1){}; 
9
   
10
11
  return 0;                
12
}
13
14
void ueberlast_ISR (void) { //event handler von überlast interrupt mit höchster prio
15
 
16
  cli();                    // disable all interrupts->Notaus
17
  PORTB &= ~(1 << PORTB3);  // Triac ausschalten
18
  PORTB |= (1 << PORTB1);   // ÜberlastLED einschalten
19
20
}

von Peter D. (peda)


Lesenswert?

Sina A. schrieb:
> deshalb besser die überlasterkennung an einen
> interrupt zu hängen mit höchster prio

CLI ist schneller, als erstmal einen Interrupt zu triggern.
Prioritätslevel gibt es beim AVR nicht.

von T0m (werwolf92)


Lesenswert?

Peter D. schrieb:
> Prioritätslevel gibt es beim AVR nicht.

Da hab ich ne Frage zu.
Ich habe gehört, dass das Prio Level abhängig ist von der Vector No.

Stimmt das?
Beispiel Atmega8.
Vector No.1   RESET
Vector No.2   INT0
Vector No.3   INT1
Vector No.4   TIMER2 COMP
usw.

Demnach das INT0 höher priorisiert wird als INT1.
Ich meine das habe ich irgendwo gehört, sicher bin ich mir aber nicht 
mehr. (war vllt auch bei PIC(?!) so...)

von Rolf Magnus (Gast)


Lesenswert?

Dres schrieb:
> Nun meine Frage was ist schöner|besser?

Die eigene while-Schleife finde ich besser, da sie explizit ausdrückt, 
was du machen willst und nicht darauf vertraut, dass implizit die 
Laufzeitumgebung das selbe macht, wenn du aus main() "rausfällst".

> Mit der while(1)-Schlaufe weiss ich, dass nichts mehr passiert, aber es
> scheint unsauber, da das Programm nie wircklich beendet ist.

Das sehe ich eher anders rum, da bei einem µC das Programm sowieso nie 
beendet werden sollte. Sieh deine Schleife nicht als "Ende des 
Programms", sondern als "in einem sicheren Zustand halten".

Tim W. schrieb:
> Peter D. schrieb:
>> Prioritätslevel gibt es beim AVR nicht.
>
> Da hab ich ne Frage zu.
> Ich habe gehört, dass das Prio Level abhängig ist von der Vector No.

Kommt drauf an, was man unter Interrupt-Prio versteht. Manche verstehen 
darunter, dass Interrupts höherer Priorität die mit geringerer Priorität 
unterbrechen können, aber nicht umgekehrt. Sowas gibt's beim AVR nicht.
Was die "Prioritäten" beim AVR angeht, wirken sie sich nur darauf aus, 
welche Interrupt zuerst abgearbeitet wird, wenn zwei 
Interrupt-Anforderungen gleichzeitig anstehen.

von Michael (Gast)


Lesenswert?

Persönlich löse ich sowas gerne so:
1
  while(ueberlast())
2
  { 
3
    //aufraeumen
4
    cli();                    // disable all interrupts->Notaus
5
    PORTB &= ~(1 << PORTB3);  // Triac ausschalten
6
    PORTB |= (1 << PORTB1);   // ÜberlastLED einschalten
7
  }

Normalerweise reicht es, den Port nur einmal auszuschalten. Aber da es 
nichts kostet und man nie weiß, was EMV so für spielchen treibt, schadet 
es auch nicht, die Abschaltung ständig zu wiederholen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tim W. schrieb:
> Demnach das INT0 höher priorisiert wird als INT1.

Eine IRQ-Priorisierung im eigentlichen Sinne gibt's wie gesagt auf AVR 
nicht.  Falls mehrere IRQs anliegen, wird diejenige mit kleiner Nummer 
zuerst abgearbeitet.  Dieses Szenario ist nicht so selten, weil viele 
AVR-Programme ISRs blockierent implementieren, d.h. es gibt recht große 
IRQ-Latenzen, während denen sich mehrere IRQs "ansammeln" können.

Zu deiner Ursprünglichen Frage:  Ich würd hier das explizite "while(1);" 
vorziehen, da explizit.  Und wenn du willst kannst es auch in eine 
eigene Funktion wie "freeze" oder was auch immer stecken.

ZUdem ist je nach AVR-Modell ein "cli + while(1);" nicht genug, weil der 
Watchdog bedient werden will; ein freeze könnte also in etwa so 
aussehen:
1
static inline __attribute__((__always_inline__))
2
void freeze (void)
3
{
4
    cli();
5
    while (1)
6
    {
7
        wdr();
8
    }
9
}

von Dres (Gast)


Lesenswert?

Vielen Dank allen für die Antworten.

Michael schrieb:
> Normalerweise reicht es, den Port nur einmal auszuschalten. Aber da es
> nichts kostet und man nie weiß, was EMV so für spielchen treibt, schadet
> es auch nicht, die Abschaltung ständig zu wiederholen.
Ein guter Gedanke, werde das beherzigen.

Sina A. schrieb:
> wenn es wirklich ein notaus ist, ist es wichtig, dass du garantieren
> kannst, dass der notaus sofort triggert.
Die Überlastbedingung wird im Originalcode bereits in einer ISR 
berechnet, aber nicht ausgewertet. Werde dies ändern. Somit ist 
garantiert, dass der Notaus schnellstmöglich bei Überlast ausgelöst 
wird. Manchmal sehe ich das Naheliegende nicht.

Johann L. schrieb:
> ZUdem ist je nach AVR-Modell ein "cli + while(1);" nicht genug, weil der
> Watchdog bedient werden will; ein freeze könnte also in etwa so
> aussehen:
Muss mich mal in die ganze Watchdog-Thematik einlesen, habe ich bis 
jetzt ignoriert.

Zum Thema Interrupt: Mit euren Anregungen und Fragen ist mir in den Sinn 
gekommen, wie wir das während dem Studium (lang ist's her) auf dem 
Motorola 68000 gelernt haben:
Dort kann man den Interrupts Priority Levels zuordnen und in der ISR 
dann die Levels mit niedriger Priority disablen. Und vor dem Return 
natürlich wieder enablen. Hat zu interresanten Debugging Marathons 
geführt, wenn jemand vergessen hat die Interrupts wieder zu enablen. :-)

von Dampf T. (ouuneii)


Lesenswert?

Die Idee das Programm zu verlassen ist unguenstigt. Was soll der 
Programmcounter dann machen ?

Man kann auch in einen sicheren Zustand fahren und dort bleiben, ohne 
das Programm zu beenden. Das Programm muss so gebaut seein, dass man es 
nie verlaesst.

: Bearbeitet durch User
von Schreiber (Gast)


Lesenswert?

Ein richtigen Notaus muss man entweder in Hardware oder mit einer 
zertifizierten icherheitssteuerung bauen. Letzteres ist "nicht ganz 
billig"...

von Peter II (Gast)


Lesenswert?

Dampf T. schrieb:
> Die Idee das Programm zu verlassen ist unguenstigt. Was soll der
> Programmcounter dann machen ?

man verlässt ja nur die main, nicht das Programm. Hinter der Main gibt 
es auch eine endlosschleife. (zumindest bei der AVR-Toolchain)

von Peter D. (peda)


Lesenswert?

Dres schrieb:
> Somit ist
> garantiert, dass der Notaus schnellstmöglich bei Überlast ausgelöst
> wird.

Ich habe oft das Problem, daß die Überlasterkennung viel zu empfindlich 
reagiert. Ich muß dann einen Timerinterrupt aufsetzen, der zyklisch 
prüft, ob die Überlast eine minimale Zeit (z.B. 10ms) lang andauert.


Dres schrieb:
> Dort kann man den Interrupts Priority Levels zuordnen und in der ISR
> dann die Levels mit niedriger Priority disablen. Und vor dem Return
> natürlich wieder enablen.

Warum das denn?
Wenn man Interrupt-Level zuordnen kann, dann macht die Hardware doch 
alles von alleine.
Z.B. beim AT89C51 weise ich T1 Priorität 0 (höchste) zu und UART0 
Priorität 3. Dann kann der T1 den UART-Interrupt unterbrechen, das 
Enable-Bit braucht dazu nirgends angefaßt zu werden.

von Stefan F. (Gast)


Lesenswert?

> Hinter der Main gibt es auch eine endlosschleife.

Ja, aber nur, wenn die main() Funktion nicht selbst eine Endlosschleife 
ohne Abbruch-Bedingung oder break ist.

Der Compiler spart die 2 Bytes noch, wenn er kann.

Das fällt in die Kategorie "was man nicht wissen muss" :-)

von S. R. (svenska)


Lesenswert?

Dres schrieb:
> Mit der while(1)-Schlaufe weiss ich, dass nichts mehr passiert, aber es
> scheint unsauber, da das Programm nie wircklich beendet ist.

Du willst dein Programm auch nicht beenden, sondern das System in einem 
sicheren Zustand halten. Unter UNIX heißt die entsprechende Funktion 
"panic()". :-)

> Mit return ist das Programm beendet, aber ich weiss eigentlich nicht,
> wie der Kompiler das umsetzt. Ich nehme an, dass er ebenfalls in eine
> Endlosschleife geht?

Das hängt vom Startup-Code ab. Niemand garantiert dir, dass deine 
eingestellte Konfiguration so erhalten bleibt, der Controller darf auch 
anfangen, auf einem Pin einen Hilferuf zu morsen.

Meine eigenen Routinen reagieren unterschiedlich:
- die CPU springt undefiniert mit aktiven Interrupts irgendwo hin
- Endlosschleife
- Reset
- blinkende LED
- Fehlermeldung auf UART oder Display (inklusive Mini-Treiber).

Alle Varianten haben ihre Berechtigung (ersteres war zwar 
unbeabsichtigt, ist aber als Falle für Studenten trotzdem lehrreich und 
bleibt daher so).

Stefan U. schrieb:
>> Hinter der Main gibt es auch eine endlosschleife.
> Ja, aber nur, wenn die main() Funktion nicht selbst eine Endlosschleife
> ohne Abbruch-Bedingung oder break ist.

Du darfst (bei gcc/clang) beliebige Funktionen mit 
__attribute__((noreturn)) dekorieren, dann spart sich der Compiler das 
Aufräumen (und sichert die Register nicht).

: Bearbeitet durch User
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.