stm32f103 RTC Interrupt öfter als erwartet

Gast #5757229
Lesenswert?

Es geht um folgendes Programm auf einem Nucleo-64 Board mit STM32F103RB 
(ohne HAL):
1
#include "stm32f1xx.h"
2

3
void initRtc()
4
{
5
    // Enable the backup domain
6
    SET_BIT(RCC->APB1ENR, RCC_APB1ENR_BKPEN + RCC_APB1ENR_PWREN);
7

8
    // Enable write access to the backup domain
9
    SET_BIT(PWR->CR, PWR_CR_DBP);
10

11
    // Enable LSE oscillator
12
    SET_BIT(RCC->BDCR, RCC_BDCR_LSEON);
13

14
    // Wait until LSE oscillator is ready
15
    while(!READ_BIT(RCC->BDCR, RCC_BDCR_LSERDY)) {}
16

17
    // Select LSE as clock source for the RTC
18
    MODIFY_REG(RCC->BDCR, RCC_BDCR_RTCSEL, RCC_BDCR_RTCSEL_LSE);
19

20
    // Enable the RTC
21
    SET_BIT(RCC->BDCR, RCC_BDCR_RTCEN);
22

23
    // Wait until RTC is synchronized
24
    while(!READ_BIT(RTC->CRL, RTC_CRL_RSF)) {}
25

26
    // Wait until last write operation is done
27
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
28

29
    // Enable second interrupt
30
    SET_BIT(RTC->CRH,RTC_CRH_SECIE);
31

32
    // Wait until last write operation is done
33
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
34

35
    // Enter configuration mode
36
    SET_BIT(RTC->CRL,RTC_CRL_CNF);
37

38
    // Divide oscillator frequency by 32767+1 to get seconds
39
    RTC->PRLL=32767;
40
    RTC->PRLH=0;
41

42
    // Leave configuration mode
43
    CLEAR_BIT(RTC->CRL,RTC_CRL_CNF);
44

45
    // Wait until last write operation is done
46
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
47

48
    // Enable interrupt in NVIC
49
    NVIC_EnableIRQ(3);
50
}
51

52
void init_io()
53
{
54
    // Enable Port A
55
    SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
56

57
    // PA5 = Output
58
    MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF5 + GPIO_CRL_MODE5, GPIO_CRL_MODE5_0);
59
}
60

61
void RTC_IRQHandler(void)
62
{
63
    // If a second has elapsed, then ...
64
    if (READ_BIT(RTC->CRL,RTC_CRL_SECF))
65
    {
66
        // Toggle LED
67
        GPIOA->ODR ^= GPIO_ODR_ODR5;
68

69
        // Clear interrupt flag
70
        CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
71
    }
72
}
73

74
int main(void)
75
{
76
    init_io();
77
    initRtc();
78
    while(1){};
79
}

Das Programm funktioniert, die LED Blinkt im Sekundentakt. Aber wenn ich 
die Zeile
1
if (READ_BIT(RTC->CRL,RTC_CRL_SECF))

weg lasse, dann blitzt die LED jede Sekunde nur ganz kurz auf. Man kann 
es so gerade eben mit blossem Auge erkennen. Daraus schliesse ich, dass 
nach dem Sekunden-Interrupt immer noch ein zweiter Interrupt auftritt. 
Aber warum? Ich bin mir nicht dessen bewusst, eine andere 
Interrupt-Quelle aktiviert zu haben.
Gast #5757271
Lesenswert?

Jörn schrieb:
> RTC_IRQHandler() scheint ein Sammelinterrupt zu sein. Mit deiner Zeile
> fragst du das Sekundenflag ab. Ist aber jetzt nur so geraten...

Ja schon, aber da ich nur den Sekunden-Interrupt aktiviert habe, sollte 
es keine Rolle spielen, ob ich das Flag teste, oder nicht.

Aus irgend einem Grund wird der IRH Handler scheinbar immer genau 
zweimal aufgerufen. Einmal wegen dem Sekunden-Flag und dann nochmal 
wegen <keine Ahnung>.
Gast #5757274
Lesenswert?

Auch wenn ich alle drei Flags lösche, wird der Interrupt doppelt 
aufgerufen:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flags
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF + RTC_CRL_ALRF + RTC_CRL_OWF);
8
}

Es scheint noch eine vierte Quelle/Ursache für die Unterbrechungen zu 
geben.
Gast #5757302
Lesenswert?

noreply@noreply.com schrieb:
> Ich würde die Anzahl Interrupts mal vor dem if in einer globalen
> Variable zählen und ausgeben.

Guter Tipp. Habe ich gemacht:
1
void RTC_IRQHandler(void)
2
{
3
    static volatile uint8_t cnt=0;
4
    cnt++;
5

6
    // If the Interrupt came from the RTC
7
    if (READ_BIT(RTC->CRL,RTC_CRL_SECF + RTC_CRL_ALRF + RTC_CRL_OWF))
8
    {
9
        // Toggle LED
10
        GPIOA->ODR ^= GPIO_ODR_ODR5;
11

12
        // Clear interrupt flags
13
        CLEAR_BIT(RTC->CRL,RTC_CRL_SECF + RTC_CRL_ALRF + RTC_CRL_OWF);
14
    }
15
}

Das habe ich im Debugger ausgeführt, mit einem Watch auf die cnt 
Variable und einem Breakpoint auf der CLEAR_BIT Zeile. Sie wird so 
hochgezählt:

1,3,5,7,9,11,13,15,17, usw.
Gast #5757336
Lesenswert?

Jetzt wird es interessant:

Das läuft gut:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flags
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Toggle something else
10
    GPIOA->ODR ^= GPIO_ODR_ODR4;
11
}

Das läuft gut:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle something else
4
    GPIOA->ODR ^= GPIO_ODR_ODR4;    
5

6
    // Clear interrupt flags
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Toggle LED
10
    GPIOA->ODR ^= GPIO_ODR_ODR5;
11
}

Das läuft gut:
1
void RTC_IRQHandler(void)
2
{
3
    // Clear interrupt flags
4
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
5

6
    // Toggle LED
7
    GPIOA->ODR ^= GPIO_ODR_ODR5;
8
}

Das funktioniert nicht (doppel-Interrupts):
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Toggle something else
7
    GPIOA->ODR ^= GPIO_ODR_ODR4;
8

9
    // Clear interrupt flags
10
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
11
}

Das funktioniert nicht (doppel-Interrupts):
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Toggle something else
7
    GPIOA->ODR ^= GPIO_ODR_ODR4;
8

9
    // Clear interrupt flags
10
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
11
}

Dann hat es in meinem Kopf "klick" gemacht. Die vermutlich saubere 
Lösung ist:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flags
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Wait until last write operation is done
10
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
11
}
Gast #5757343
Lesenswert?

Einfaches Softwaredesign, das ich bei Interruptbearbeitung beachten 
würden.

Interrupt-Grund Abfragen
Interrupt-Grund Zurücksetzen
restliche Arbeit erledigen

Es gibt vielleicht auch noch Hinweise in der Dokumentation, wieviele 
Takte vergangen sein müssen, bis der Interrupt zuverlässig gelöscht 
wird. Aber man kann nur mit der Hardware arbeiten.
Gast #5757347
Lesenswert?

noreply@noreply.com schrieb:
> Interrupt-Grund Abfragen
> Interrupt-Grund Zurücksetzen
> restliche Arbeit erledigen

Meinst du so?:
1
void RTC_IRQHandler(void)
2
{
3
    // if a second has elapsed, then ...
4
    if (READ_BIT(RTC->CRL,RTC_CRL_SECF))
5
    {
6
        // Clear interrupt flag
7
        CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
        // Toggle LED
10
        GPIOA->ODR ^= GPIO_ODR_ODR5;
11
    }
12

13
    // Wait until last write operation is done
14
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
15
}
Gast #5757362
Lesenswert?

Stefanus F. schrieb:
> noreply@noreply.com schrieb:
>
>
> Meinst du so?:
> void RTC_IRQHandler(void)
> {
 Interrupt-Grund Abfragen
>     // if a second has elapsed, then ...
>     if (READ_BIT(RTC->CRL,RTC_CRL_SECF))
>     {
 Interrupt-Grund Zurücksetzen
>         // Clear interrupt flag
>         CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
 restliche Arbeit erledigen
>         // Toggle LED
>         GPIOA->ODR ^= GPIO_ODR_ODR5;
>     }
nachfolgendes könnte nur zusätzliche Zyklen erzeugen,
die der Interrupt-Verwaltung Zeit geben, sich zurückzusetzen
siehe Beitrag von Martin. Der Einwand mit den Verarbeitungsstufen
der CPU hat was.
>     // Wait until last write operation is done
>     while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
> }

...
Gast #5757407
Lesenswert?

Meine Vorredner haben wohl recht, mit dem Zeitproblem.
Hier liegt das seltene Problem vor, dass im Interrupt keine Register 
gesichert und wieder restauriert werden müssen, weil die Hauptschleife 
leer ist, und der Compiler das wegoptimiert hat.
Lass mal irgendwas in der Hauptschleife machen, dann wird es sehr 
wahrscheinlich wie gewünscht laufen.
Gast #5757582
Lesenswert?

.                                                . schrieb im Beitrag 
#5757407:
> Hier liegt das seltene Problem vor, dass im Interrupt keine Register
> gesichert und wieder restauriert werden müssen, weil die Hauptschleife
> leer ist, und der Compiler das wegoptimiert hat.

Das klingt plausibel. Vor allem erklärt es, warum ich vorher noch nie 
auf den Fehler gestoßen bin. In den vorherigen Programmen hatten die ISR 
tatsächlich etwas mehr gemacht.

> Lass mal irgendwas in der Hauptschleife machen, dann wird es sehr
> wahrscheinlich wie gewünscht laufen.

Ich wüsste nicht, warum dadurch das Verhalten der ISR anders werden 
sollte.
Gast #5757665
Lesenswert?

Ok, das mit dem Zeitproblem habe ich schon mal gehört. Also bei 
Sammelinterrupts erst die Quelle abfragen, Flag löschen, dann was 
machen.
Aber warum klappt dann die Variante aus dem Eröffnungspost mit dem IF 
welches die Quelle ermittelt, aber ohne das IF geht es nicht? Obwohl 
danach jeweils die "ungünstige" Reihenfolge erst LED togglen dann Flag 
löschen kam...
Gast #5757669
Lesenswert?

Jörn schrieb:
> Aber warum klappt dann die Variante aus dem Eröffnungspost mit dem IF
> welches die Quelle ermittelt, aber ohne das IF geht es nicht?

Weil der if() Ausdruck sicher stellt, dass die LED nur getoggelt wird, 
wenn eine Sekunde verstrichen ist.

Der Fehler war zwar, dass die ISR doppelt aufgerufen wurde, bis dahin 
war das Sekunden-Flag aber asynchron zurück gesetzt.
Gast #5757670
Lesenswert?

Vielleicht 18.3.4@RM0008
1
In addition, writing to any RTC register is only enabled if the previous write operation is
2
finished. To enable the software to detect this situation, the RTOFF status bit is provided in
3
the RTC_CR register to indicate that an update of the registers is in progress. A new value
4
can be written to the RTC registers only when the RTOFF status bit value is ’1’.

Also mit
1
1. Poll RTOFF, wait until its value goes to ‘1’
2
...
verfahren?
(Firma: @home) #5758343
Lesenswert?

.. das thema ist interessant, da es auch im context mit interrupts/dma 
steht und wie hier schon erwähnt sind dsb/dmb die keywords

einer von vielen links dazu,
https://stackoverflow.com/questions/15491751/real-life-use-cases-of-barriers-dsb-dmb-isb-in-arm

ein while() contruct ist dann wohl eher die "ahnunglose" variante das 
problem zu beherrschen.


mt
(Firma: @home) #5758480
Lesenswert?

Stefanus F. schrieb:
> Hast du das obige Zitat aus dem Referenzhandbuch gesehen, wo genau dies
> empfohlen wird?

so, jetzt habe ich die details im reference hb nochmals gelesen und sehe 
keinen zusammenhang zu deinem mehrfach interrupt problem und

Beitrag "Re: stm32f103 RTC Interrupt öfter als erwartet"

da das interrupt flag reg nicht in der rtc domain liegt.

ich würde sogar eher ein isb() cmd setzen anstatt dsb/dsm?!

probier mal aus!

ich denke, dass while() funktioniert nur weil es als "delay" wirkt.


mt
Gast #5758554
Lesenswert?

Bei 8MHz reicht zo siemlich alles, was ein paar Takte verbraucht. Zum 
Beispiel zwei oder mehr NOP.
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flag
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Delay a little
10
    asm volatile ("NOP; NOP;");
11
}

Apollo M. schrieb:
> ich würde sogar eher ein isb() cmd setzen anstatt dsb/dsm?!
> probier mal aus!

isb(), dsb(), dsm() kennt der Compiler nicht, auch nicht in der 
Schreibweise __isb(). Ich habe es daher mit Assembler versucht:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flag
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Flushes the pipeline
10
    asm volatile ("ISB");
11
}

Aber das klappt nur mit 8MHz Systemtakt. Bei 72MHz reicht das wieder 
nicht, da muss ich doch dies machen:
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flag
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    // Wait until last write operation is done
10
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
11
}

Ich denke, der Hinweis im Referenzhandbuch passt schon.
Gast #5758580
Lesenswert?

Stefanus F. schrieb:
> noreply@noreply.com schrieb:
>> Das Register RTC_CRH wurde hier nicht erwähnt.
>
> Doch, im Eröffnungspost. In diesem Register schalte ich den
> Sekunden-Interrupt ein.

Sorry. Ich meinte RTC_CRL

Verkürzt. RTOFF Abfragen macht nur Sinn in Verbindung der Register 
RTC_PRL, RTC_CNT und RTC_ALR. RTC_CRL ist normales Register im 
Addressbereich der CPU. Somit ist
    // Wait until last write operation is done
    while(!READ_BIT(RTC->CRL, RTC_CRL_RTOFF)) {}
nur eine Verzögerung.

18.3.4 Configuring RTC registers
To write in the RTC_PRL, RTC_CNT, RTC_ALR registers, the peripheral must 
enter
Configuration Mode. This is done by setting the CNF bit in the RTC_CRL 
register.
In addition, writing to any RTC register is only enabled if the previous 
write operation is
finished. To enable the software to detect this situation, the RTOFF 
status bit is provided in
the RTC_CR register to indicate that an update of the registers is in 
progress. A new value
can be written to the RTC registers only when the RTOFF status bit value 
is ’1’.
Configuration procedure
1. Poll RTOFF, wait until its value goes to ‘1’
2. Set the CNF bit to enter configuration mode
3. Write to one or more RTC registers
4. Clear the CNF bit to exit configuration mode
5. Poll RTOFF, wait until its value goes to ‘1’ to check the end of the 
write operation.
The write operation only executes when the CNF bit is cleared; it takes 
at least three
RTCCLK cycles to complete.
Gast #5758583
Lesenswert?

Warum pollen dann alle Tutorials RTC_CRL_RTOFF nach jedem Schreibzugriff 
auf RTC>CRL?

Die Beschreibung von RTOFF sagt:
If its value is ‘0’ then it is not possible to write to any
of the RTC registers.

Also nach meinem Verständnis ist RTC->CRL ein RTC Register, sonst würde 
es wohl kaum so heissen. Es ist auch Bestandteil der RTC Register Map.

Was mich ärgert: Es gibt für jeden Kack eine Application Note, nur dafür 
nicht. In den Quelltexten der HAL habe ich auch nichts passenden 
gefunden.
Beitrag #5758602 wurde von einem Moderator gelöscht.
Gast #5758666
Lesenswert?

Bei 72MHz brauche ich vier NOPs.
1
void RTC_IRQHandler(void)
2
{
3
    // Toggle LED
4
    GPIOA->ODR ^= GPIO_ODR_ODR5;
5

6
    // Clear interrupt flag
7
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
8

9
    asm volatile ("NOP; NOP; NOP; NOP;");
10
}

Oder zuerst das Flag löschen, geht auch.
1
void RTC_IRQHandler(void)
2
{
3
    // Clear interrupt flag
4
    CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
5

6
    // Toggle LED
7
    GPIOA->ODR ^= GPIO_ODR_ODR5;
8
}

Ich mache das jetzt so und schreibt noch einen kleinen Kommentar dazu, 
dass nach den Clear ein Paar Takte Verzögerung nötig sind.
Gast #5758682
Lesenswert?

Stefanus F. schrieb:
> Oder zuerst das Flag löschen, geht auch.void RTC_IRQHandler(void)
> {
>     // Clear interrupt flag
>     CLEAR_BIT(RTC->CRL,RTC_CRL_SECF);
>
>     // Toggle LED
>     GPIOA->ODR ^= GPIO_ODR_ODR5;
> }

Krass. Braucht das Togglen wirklich so lang, dass das schon reicht?
Togglen = 4 NOPS? Ich dachte der Registerzugriff fürs Togglen dauert 
einen Takt...
Gast #5758709
Lesenswert?

Jörn schrieb:
> Braucht das Togglen wirklich so lang, dass das schon reicht?
> Togglen = 4 NOPS? Ich dachte der Registerzugriff fürs Togglen dauert
> einen Takt...

Offensichtlich ja. Mit nur 3 NOPS funktioniert es (bei 72MHz) nicht.

Das ist der Assembler-Code von der Variante, wo ich erst das Flag lösche 
und dann die LED toggle:
1
 188                RTC_IRQHandler:
2
 189                  @ args = 0, pretend = 0, frame = 0
3
 190                  @ frame_needed = 0, uses_anonymous_args = 0
4
 191                  @ link register save eliminated.
5
 192 0000 0549         ldr  r1, .L34
6
 193 0002 064A         ldr  r2, .L34+4
7
 194 0004 4B68         ldr  r3, [r1, #4]
8
 195 0006 23F00103     bic  r3, r3, #1
9
 196 000a 4B60         str  r3, [r1, #4]
10
 197 000c D368         ldr  r3, [r2, #12]   <---- 2 Cycles
11
 198 000e 83F02003     eor  r3, r3, #32     <---- 1 Cyle
12
 199 0012 D360         str  r3, [r2, #12]   <---- 2 Cycles
13
 200 0014 7047         bx  lr

Ich hoffe ich habe die Anzahl der Zyklen richtig ermittelt.

> Ich dachte der Registerzugriff fürs Togglen dauert einen Takt...

Vielleicht verwechselst du das mit dem Setzen/Löschen von I/O Pins über 
das GPIOx->BSRR Register.
Gast #5759022
Lesenswert?

Apollo M. schrieb:
> ... das hat irgendwie was esoterisches.
> ich glaube das alles nicht wirklich, weil das ist mir zu
> undeterministisch und damit nicht logisch.
>
> ich werde mal irendwann meine hw in stellung bringen und rumspielen.

Ich sehe da nichts undeterministisches.

Nach dem Clearen des Interrupts Flags RTC_CRL_SECF muss die ISR noch 
mindestens 4 CPU Takte warten, bevor sie zurück kehrt. Andernfalls steht 
das Interrupt-Signal danach noch an, so dass die ISR erneut ausgeführt 
wird. Bei 8MHz genügen zwei CPU Takte.

Das Einzige, was mir dazu fehlt, ist der entsprechende Hinweis in der 
Doku des Herstellers.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren