Forum: Mikrocontroller und Digitale Elektronik STM8 Halt Mode / Wake Up über externen Interrupt


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Markus (markuswoe)


Lesenswert?

Hallo Zusammen,

ich habe hier eine Hardware mit STM8AF und einem TJA1022 Dual LIN 
Transceiver.

Wenn einer der LIN Busse einschläft führt das System einen Reset durch, 
beim Neustart soll die Software die LIN Transceiver schlafen schicken 
und so lange in den Halt Mode gehen bis der LIN Transceiver einen 
externen Wakeup (sprich Busaktivität) erkennt.

Aussehen tut das so:
1
void main(void)
2
{
3
  delay_ms(50);
4
  // clock konfigurieren
5
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
6
  CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO, CLK_SOURCE_HSE, DISABLE, CLK_CURRENTCLOCKSTATE_DISABLE);
7
  // LIN Transceiver in den Sleep Mode versetzen - EXTI_PORT_GPIOD sollt dann so lange floaten bis Aktivität erkannt wird
8
  GPIO_Init(SLIN_SLP_OUT_GPIO, (GPIO_Pin_TypeDef)SLIN_SLP_OUT_PIN, GPIO_MODE_OUT_PP_LOW_SLOW);
9
  GPIO_Init(MLIN_SLP_OUT_GPIO, (GPIO_Pin_TypeDef)MLIN_SLP_OUT_PIN, GPIO_MODE_OUT_PP_LOW_SLOW);
10
  // GPIO D6 als interruptfähigen Input mit PullUp konfigurieren
11
  GPIO_Init(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6, GPIO_MODE_IN_PU_IT);
12
  // Externen interrupt konfigurieren 
13
  EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY);
14
  delay_ms(5);
15
  // prüfen, ob der Port schon LOW ist - falls Nein, Interrupts aktivieren und in den Halt Mode gehen
16
  if (GPIO_ReadInputPin(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6) == SET)
17
  {
18
    enableInterrupts();
19
    halt();
20
  }
21
  // ...
22
}
Das Problem ist nur es funktioniert nicht - halt() wird auch bei einem 
Reboot entweder nie aufgerufen oder das System kommt mehr oder weniger 
sofort zurück.

Hat irgendjemand eine Idee was ich hier falsch mache?

Danke und Gruß,
Markus

von Dieter S. (ds1)


Lesenswert?

Mit dem hier

https://community.st.com/t5/stm8-mcus/stm8-library-bug-gpio-readinputpin/td-p/490178

beschriebenen Bug ("STM8 library bug - GPIO_ReadInputPin()"), der wohl 
davon abhängt welchen Compiler man benutzt, hat es nichts zu tun?

von Markus (markuswoe)


Lesenswert?

OMG - ich hatte den Post sogar gesehen...
Habe mir den Quelltext von GPIO_ReadInputPin angesehen - das wird es 
gewesen sein.

Vielen Dank!

Habe es jetzt auf
1
if (GPIO_ReadInputPin(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6))
 geändert und werde es testen.

Mich stört aber noch, dass da eine Race Condition auftritt:

Was passiert wenn der Pegel GPIO D6 zwischen GPIO_ReadInputPin() und 
halt() auf Low geht? Dann kommt kein Interrupt (oder war schon da) und 
aus halt() wird nie zurückgekehrt - oder?

: Bearbeitet durch User
von Dieter S. (ds1)


Lesenswert?

Eventuell hilft gegen die Race Condition wenn man in der ISR für den Pin 
ein Flag setzt und halt() nicht aufruft wenn das Flag gesetzt ist? 
Vermutlich müsste man dann aber auch den Ablauf bis zu halt() umstellen 
damit es passt.

von Markus (markuswoe)


Lesenswert?

Das ändert aber nix - die Race condition geht nicht weg. Egal was man 
tut - wenn der Interrupt unmittelbar vor dem Aufruf von halt() kommt ist 
das Problem da. So kann man nur das Zeitfenster minimieren.

: Bearbeitet durch User
von Dieter S. (ds1)


Lesenswert?

Muss man denn enableInterrupts() explizit vor halt() aufrufen?

Laut PM0044 ("STM8 CPU programming manual") für HALT:

"The interrupt mask is reset, allowing interrupts to be fetched."

von Markus (markuswoe)


Lesenswert?

Danke - das probiere ich aus.
Da aber das prüfen des Input Pins und der Aufruf von halt() keine 
atomare Operation sind, kann da dennoch ein Interrupt 
dazwischengrätschen.

Anders wäre es, wenn Pegelwechsel nach Low nach Konfiguration des 
Interrupts "zwischengespeichert" würden und erst bei Reset der Interrupt 
Mask feuern würden.

Das weiß ich aber nicht.

von Peter D. (peda)


Lesenswert?

Dieter S. schrieb:
> "The interrupt mask is reset, allowing interrupts to be fetched."

So ist es. HALT gibt die Interrupts wieder frei (atomar).

von Markus (markuswoe)


Lesenswert?

Aber das hier ist keine atomare Operation:
1
if (GPIO_ReadInputPin(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6))
2
{
3
  // wenn der GPIO jetzt zwischen GPIO_ReadInputPin() und halt() auf LOW geht haben wir ein Problem.
4
  halt();
5
}

von Peter D. (peda)


Lesenswert?

Markus schrieb:
> Aber das hier ist keine atomare Operation:

Vor das if kommt natürlich noch das Interrupt disable.

von Markus (markuswoe)


Lesenswert?

Der Code läuft unmittelbar nach dem Reboot - da sind die Interrupts 
sowieso noch disabled. Aber bedeutet das konkret, dass es ggf. so 
ablaufen würde:

1) GPIO Pin geht von High nach Low
2) Aufruf halt() -> implizites enablen der Interrupts
3) Interrupt feuert, halt() kehrt zurück.

Das bedeutet, obwohl der GPIO Pin bei Aufruf von halt() schon Low ist 
feuert der Interrupt?

: Bearbeitet durch User
von Dieter S. (ds1)


Lesenswert?

"EXTI_SENSITIVITY_FALL_LOW" ("Interrupt on Falling edge and Low level") 
anstelle "EXTI_SENSITIVITY_FALL_ONLY" ("Interrupt on Falling edge only") 
geht nicht?

von Markus (markuswoe)


Lesenswert?

Und ob das geht - danke!

Genau das hatte ich gesucht, damit ist es nur noch dieser Dreizeiler:
1
GPIO_Init(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6, GPIO_MODE_IN_PU_IT);
2
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY);
3
halt();

von Dieter S. (ds1)


Lesenswert?

Markus schrieb:
>
> Genau das hatte ich gesucht, damit ist es nur noch dieser Dreizeiler:
>
>
1
> GPIO_Init(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6, GPIO_MODE_IN_PU_IT);
2
> EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY);
3
> halt();
4
>

Das sollte wohl "EXTI_SENSITIVITY_FALL_LOW" sein.

von Markus (markuswoe)


Lesenswert?

> Das sollte wohl "EXTI_SENSITIVITY_FALL_LOW" sein.

Aber ja:
1
GPIO_Init(GPIOD, (GPIO_Pin_TypeDef)GPIO_PIN_6, GPIO_MODE_IN_PU_IT);
2
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_LOW);
3
halt();

von Peter D. (peda)


Lesenswert?

Markus schrieb:
> Mich stört aber noch, dass da eine Race Condition auftritt:

Die µC-Entwickler machen sich schon Gedanken, daß Race Conditions nicht 
auftreten, bzw. programmtechnisch ausgeschlossen werden können.
Der STM8 macht es dadurch einfach, daß HALT und Interrupt enable atomar 
erfolgen.

Bei den AVRs ist das deutlich umständlicher gelöst. Da kann man 
unendlich schlafen, wenn man nicht aufpaßt. Man muß direkt vor dem SLEEP 
die Interrupts enablen (SEI). Da der SEI-Befehl erst nach dem nächsten 
Befehl greift, ist sichergestellt, daß vor dem SLEEP kein Interrupt 
angesprungen werden kann:
1
      cli();
2
      if (some_condition)
3
      {
4
        sei();
5
        sleep_cpu();
6
      }
7
      sei();

von Markus (markuswoe)


Lesenswert?

Ich hatte auch das Gefühl, dass es nicht sein kann, dass die Race 
Condition nicht zu vermeiden sein soll.

Habe aber einfach zu wenig Ahnung vom STM8.

Vielen Dank für die tolle Unterstützung!

P.S: Wie kriege ich den STM auf den kleinsten Stromverbrauch?

Aktuell: Alle Peripheral Clocks abschalten, alle GPIO auf 
GPIO_MODE_OUT_PP_LOW_SLOW, Halt.

Vorher wird noch die Clock auf 16 MHz Extern gesetzt, aber das sollte ja 
nichts ausmachen, da die Clock im Halt Mode ja ohnehin angehalten ist.

Das Ding braucht dennoch 2-4 mA (der "Hardware Guy" misst noch).
Kann natürlich an externen Komponneten liegen, auf die Hardware habe ich 
keinen direkten Einfluss.

Kann man da seitens der MCU noch was machen?

: Bearbeitet durch User
von Dieter S. (ds1)


Lesenswert?

AN2857 ("STM8S and STM8A family power management") gibt einen guten 
Überlick mit Angaben zum jeweiligen Stomverbrauch.

von Markus (markuswoe)


Lesenswert?

Ich kenne das Dokument. Hab's gerade nochmals durchgelesen.

Wenn ich es richtig sehe, kann ich mir das Abschalten der Peripheral 
clocks sparen, im Halt Mode sind sie sowieso aus - oder?

Ansonsten gab es keine Erleuchtungen.

: Bearbeitet durch User
von Markus (markuswoe)


Lesenswert?

EXTI_SENSITIVITY_FALL_LOW hat leider nicht wie erwartet funktioniert - 
ich mutmaße weil ich den Interrupt in der ISR nicht richtig zurücksetze.

Was wäre da der richtige Weg?

von Markus (markuswoe)


Lesenswert?

OK - nach langem hin- und herprobieren habe ich jetzt die scheinbar 
einzig funktionierende Variante gefunden. Ich schreibe das vor allem 
weil vielleicht ja jemand irgendwann über den Thread stolpert.
1
  // wait for external interrupt
2
  GPIO_Init(GPIOD, GPIO_PIN_6, GPIO_MODE_IN_PU_IT);
3
  EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOD, EXTI_SENSITIVITY_FALL_ONLY);  // EXTI_SENSITIVITY_FALL_LOW seems not to work
4
  delay_ms(1);
5
  if (GPIO_ReadInputPin(GPIOD, GPIO_PIN_6)) // don't compare with SET as GPIO_ReadInputPin will never return SET
6
  {
7
    enableInterrupts(); // halt() seems not to enable interrupts
8
    halt();
9
  }

: 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.