Forum: Mikrocontroller und Digitale Elektronik Interrupt-Pointer verbiegen für Zustandsautomat


von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

Hallo zusammen,

ich möchte einen Zustandsautomaten für das 1Wire-Protokoll 
programmieren. Er soll über Timer-Interrupts "im Hintergrund" laufen.

Klassisch würde man in der Interrupt-Service-Routine (ISR) bei jedem 
Interrupt den Zustand des Zustandsautomaten in einer 
switch-case-Anweisung herausfinden.

Der eigentliche Code ist aber oft nur ganz kurz:
- Setzte den neuen Timer-Wert
- Setze ein Bit
- Setze den nächsten Zustand

Damit die ISR so schnell wie möglich abgearbeitet wird, würde ich gern 
auf die (verhältnismäßig) umfangreiche switch-case-Anweisung verzichten 
und bei "Setze den nächsten Zustand" einfach den Interrupt-Pointer - je 
nach Zustand - verbiegen.

Ist das eine Schnapsidee oder gar üblich? Ich habe mit Google nix dazu 
gefunden.

Mich interessiert allgemein, wie das geht. Speziell möchte ich es 
erstmal mit dem MSP430 versuchen. Hier liegen die Vektoren m.E. im Flash 
und können zur Laufzeit nicht geändert werden, oder?

Es gibt im Netz ein Beispiel, wie man Code ins RAM kopiert, dort könnte 
man dann die Adressen überschreiben.

http://e2e.ti.com/support/microcontrollers/msp430/f/166/p/199206/710443.aspx

Alternativ könnte man versuchen, mit Funktions-Pointern in C zu 
arbeiten, aber meine ersten Versuche hat der Compiler nicht akzeptiert 
<<a value of type "int (*)(void)" cannot be used to initialize an entity 
of type "volatile int">>.

Bevor ich nun alles ausprobieree, wollte ich mal wissen, was Ihr meint, 
wie man das am besten macht.

von c-hater (Gast)


Lesenswert?

Torsten C. schrieb:

> Klassisch würde man in der Interrupt-Service-Routine (ISR) bei jedem
> Interrupt den Zustand des Zustandsautomaten in einer
> switch-case-Anweisung herausfinden.
[...]
> Damit die ISR so schnell wie möglich abgearbeitet wird, würde ich gern
> auf die (verhältnismäßig) umfangreiche switch-case-Anweisung verzichten
> und bei "Setze den nächsten Zustand" einfach den Interrupt-Pointer - je
> nach Zustand - verbiegen.
>
> Ist das eine Schnapsidee oder gar üblich?

Ich würde mal sagen, das der Sinngehalt schlicht davon abhängt, wie 
umfangreich der Switch-Block ist. Sobald das "verbiegen" des 
Interruptvektors billiger wird, macht es Sinn, diesen Mechanismus auch 
zu benutzen. Ist doch logisch.

Allerdings: Nur in Assembler sieht man ohne Probleme, was billiger ist.

von Peter D. (peda)


Lesenswert?

Wie kommst Du darauf, daß ein Switch-Case teuer sein muß?
Hast Du Dir den erzeugten Assembler schonmal angesehen?
Überlaß derartige Optimierungen mal besser dem Compiler.

Was dagegen teuer ist, sind Funktionspointer in Interrupts.
Der Compiler muß dann sämtliche Scratchpadregister sichern, da er nicht 
weiß, welche durch den indirekten Aufruf zerstört werden. Vielleicht muß 
er auch erst nen großen Stackframe anlegen.

von amateur (Gast)


Lesenswert?

Es ist überhaupt kein Problem die Funktionszeiger in einem Array 
abzulegen und später über ihren Index aufzurufen.
Das geht mit allem Drum-Und-Dran wie Parameterübergabe und -rückgabe.

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

c-hater schrieb:
> Allerdings: Nur in Assembler sieht man ohne Probleme, was billiger ist.

Hmmm, OK, ich habe in Assembler nachgeschaut:
Switch Case: 12 Takte + 4 weitere für jedes weitere "case"
Call + Return: 7 Takte

Also eindeutig Funktionszeiger.

amateur schrieb:
> Es ist überhaupt kein Problem …

Danke für's Mut machen. :-) Ich hab's probiert, es geht! Die LED an 
PORT1.0 blinkt, die CPU ist aus (Low-Power-Mode) und die StateMachine 
toggelt im Hintergrund:

1
#include <msp430.h>
2
void SMState_1(void);
3
void SMState_2(void);
4
void(*StateFunc)(void) = &SMState_1;
5
int main(void) {
6
  // Initialisierung übersprungen
7
  _BIS_SR(GIE); // Low-Power-Mode mit Interrupts
8
  LPM0;
9
}
10
void SMState_1(void) {
11
  CCR0 = 0x4321;
12
  P1OUT |= BIT0;
13
  StateFunc = &SMState_2;
14
}
15
void SMState_2(void) {
16
  CCR0 = 0x1234;
17
  P1OUT &= ~BIT0;
18
  StateFunc = &SMState_1;
19
}
20
#pragma vector=TIMER0_A0_VECTOR
21
__interrupt void Port_1(void) {
22
  //StateFunc(); // das wären 30 Takte mehr
23
  asm("        CALL      &StateFunc+0          ;");
24
}

Mit dem asm(" CALL &StateFunc+0") verhindere ich, dass der Compiler alle 
Register auf dem Stack zwischenspeichert.

amateur schrieb:
> … die Funktionszeiger in einem Array
> abzulegen und später über ihren Index aufzurufen.

Darin sehe ich gegenüber dem o.g. Beispiel keinen Zusatz-Nutzen.

Jetzt werde ich mich mal an das 1Wire-Protokoll machen.

Das wird wahrscheinlich besser als bei I²C, wo ich in einer dämlichen 
while-Schleife auf das Löschen des Start-Bits warte, um das Stop-Bit zu 
setzen.

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.