Forum: Mikrocontroller und Digitale Elektronik globale Variable mit einem assembler befehl auslesen


von obamoa (Gast)


Lesenswert?

Hallo,

ich habe eine kurze Frage. Ich möchte gern eine globale Variabe in einer 
Funktion auslesen.
1
uint32_t getUsTime(void)
2
{   
3
    uint32_t tempOverflows;
4
    uint16_t tempCounter;
5
    
6
    tempCounter = TIM_GetCounter(TIM_US_TIMER);
7
    tempOverflows = publicUsTimerOverflows;
8
9
    return ( (tempOverflows * 0x0000FFFF) + (uint32_t)tempCounter);
10
}

Jetzt habe ich aber gesehen das das Auslesen der globalen Variable 
publicUsTimerOverflows nicht mit einem Assemberbefehl funktioniert. 
Damit würde ich wahrscheinlich Probleme mit der ISR bekommen die die 
globale Variable beschreibt. Die ISR deaktivieren möchte ich nicht.

Hier der vom Compiler erzeugte Code der Funktion:
1
            getUsTime:
2
0x0800bbe8:   push {r7, lr}
3
0x0800bbea:   sub sp, #8
4
0x0800bbec:   add r7, sp, #0
5
131             tempCounter = TIM_GetCounter(TIM_US_TIMER);
6
0x0800bbee:   ldr r0, [pc, #40]       ; (0x800bc18 <getUsTime+48>)
7
0x0800bbf0:   bl 0x8000bcc <TIM_GetCounter>
8
0x0800bbf4:   mov r3, r0
9
0x0800bbf6:   strh r3, [r7, #6]
10
132             tempOverflows = publicUsTimerOverflows;
11
0x0800bbf8:   ldr r3, [pc, #32]       ; (0x800bc1c <getUsTime+52>)
12
0x0800bbfa:   ldr r3, [r3, #0]
13
0x0800bbfc:   str r3, [r7, #0]
14
134             return ( (tempOverflows * 0x0000FFFF) + (uint32_t)tempCounter);
15
0x0800bbfe:   ldr r2, [r7, #0]
16
0x0800bc00:   mov r3, r2
17
0x0800bc02:   mov.w r3, r3, lsl #16
18
0x0800bc06:   subs r2, r3, r2
19
0x0800bc08:   ldrh r3, [r7, #6]
20
0x0800bc0a:   adds r3, r2, r3
21
135         }

Meine Frage ist nun ist es möglich den Wert der globalen Variable 
publicUsTimerOverflows in die lokale Variable tempOverflows mit einem 
Assablerbefehl zu kopieren und wie würde das aussehen.

PS: Mit Assemblerprogrammierung kenne ich mich leider nur sehr schlecht 
aus und hoffe daher auf eure Hilfe.

von Daniel V. (danvet)


Lesenswert?


von obamoa (Gast)


Lesenswert?

Ach ja das hab ich ganz vergessen, es handelt sich dabei um einen STM32 
also ARM-Cortex M3.

von Peter D. (peda)


Lesenswert?

obamoa schrieb:
> Die ISR deaktivieren möchte ich nicht.

Dann möchtest Du auch nicht wirklich programmieren lernen.
Gleich mit unsauberen Tricks anfangen zu wollen, zerstört Deinen 
Programmierstil nachhaltig.

Atomare Abschnitte gehören zum Programmieren einfach dazu.
Und je größer die Programme werden, umso mehr solcher Abschnitte sind 
notwendig.


Peter

von e in anderen S (Gast)


Lesenswert?

>möglich den Wert der globalen Variable
>publicUsTimerOverflows in die lokale Variable tempOverflows mit einem
>Assablerbefehl zu kopieren und wie würde das aussehen.

Das steht hier:


132             tempOverflows = publicUsTimerOverflows;
0x0800bbf8:   ldr r3, [pc, #32]       ; (0x800bc1c <getUsTime+52>)
0x0800bbfa:   ldr r3, [r3, #0]
0x0800bbfc:   str r3, [r7, #0]

von e in anderen S (Gast)


Lesenswert?

>Damit würde ich wahrscheinlich Probleme mit der ISR bekommen die die
>globale Variable beschreibt.

Aus welchem Grund meinst Du das?

von obamoa (Gast)


Lesenswert?

Nach jedem Assemblerbefehl wird überprüft ob ein Interrupt aufgetretten 
ist. Wird nun ein Interrupt zwischen den 3 Assemblerbefehlen der zum 
auslesen der globalen Variablen erkannt wird dieser ausgefüht bevor die 
Daten komplett ausgelesen konnten.

von Daniel V. (danvet)


Lesenswert?

obamoa schrieb:
> Ach ja das hab ich ganz vergessen, es handelt sich dabei um einen STM32
> also ARM-Cortex M3.

Macht nix, das Prinzip bleibt das Gleiche.

von obamoa (Gast)


Lesenswert?

Meine Frage ist ja ob es möglich ist aus den drei Befehlen einen zu 
machen und so die Probleme mit einer ISR zu vermeiden.

von (prx) A. K. (prx)


Lesenswert?

Das Prinzip schon, die Praxis nicht. Der STM32 läd 16- und 32-Bit 
Variablen in einem einzigen Befehl, der Zugriff ist hier also 
automatisch atomar. Anders beim AVR.

von Peter D. (peda)


Lesenswert?

Wenn Du einen Timer in SW erweiterst, hast Du ein ganz anderes Problem. 
Der HW-Timer und SW-Timer werden zu unterschiedlichen Zeiten gezählt, 
sie sind also nicht jederzeit gültig.

Dafür gibt es eine einfache Lösung:
Beitrag "AVR Timer mit 32 Bit"


Peter

von e in anderen S (Gast)


Lesenswert?

>Nach jedem Assemblerbefehl wird überprüft ob ein Interrupt aufgetretten
>ist. Wird nun ein Interrupt zwischen den 3 Assemblerbefehlen der zum
>auslesen der globalen Variablen erkannt wird dieser ausgefüht bevor die
>Daten komplett ausgelesen konnten.

...

>Meine Frage ist ja ob es möglich ist aus den drei Befehlen einen zu
>machen und so die Probleme mit einer ISR zu vermeiden.

Nicht notwendig, da ist gar kein Prob.

Wie von PeDa bemerkt,  steckt das Prob hier:


>    tempCounter = TIM_GetCounter(TIM_US_TIMER);

>    tempOverflows = publicUsTimerOverflows;

>    return ( (tempOverflows * 0x0000FFFF) + (uint32_t)tempCounter);
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

die temp* könnten inkonsistent sein, wenn ein Interrupt dazwischenfunkt.

von obamoa (Gast)


Lesenswert?

Ich habe die Funktion nach PeDa Vorbild angepasst:
1
uint32_t getUsTime(void)
2
{   
3
    uint32_t tempOverflows;
4
    uint16_t tempCounter;
5
    ITStatus waitingForIsr;
6
    
7
    tempCounter = TIM_GetCounter(TIM_US_TIMER);
8
    tempOverflows = publicUsTimerOverflows;
9
    waitingForIsr = TIM_GetITStatus(TIM_US_TIMER, TIM_IT_Update);
10
11
    if((waitingForIsr == SET) && !(0x8000 & tempCounter))
12
    {
13
        tempOverflows++;
14
    }
15
16
    return ( (tempOverflows * 0x0000FFFF) + (uint32_t)tempCounter);
17
}

Mir ist nur nicht ganz klar ob ich jetzt den Timeroverflow-Interrupt 
während der Auslesevorgänge deaktivieren muss.

von Karl H. (kbuchegg)


Lesenswert?

obamoa schrieb:

> Mir ist nur nicht ganz klar ob ich jetzt den Timeroverflow-Interrupt
> während der Auslesevorgänge deaktivieren muss.

Mir ist nicht klar, wavor du dich eigentlich fürchtest, wenn du die 
Interrupts mal ganz kurz deaktivierst. Die Interrupts gehen ja deswegen 
nicht verloren, sie werden einfach nur um ein bischen was später 
ausgeführt. Viel wichtiger ist aber, dass du beim Auslesen der Variablen 
einen konsistenten Zustand ausliest.

von Peter D. (peda)


Lesenswert?

obamoa schrieb:
> Mir ist nur nicht ganz klar ob ich jetzt den Timeroverflow-Interrupt
> während der Auslesevorgänge deaktivieren muss.

Ja klar!
Das Lesen von HW-Timer, SW-Timer, Interruptflag muß zusammen ein 
atomarer Block sein.


Peter

von obamoa (Gast)


Lesenswert?

Ich hab den Interrupt wärend des auslesens der Daten deaktivert.
1
uint32_t getUsTime(void)
2
{   
3
    uint32_t tempOverflows;
4
    uint16_t tempCounter;
5
    ITStatus waitingForIsr;
6
    
7
    // Disable the Selected IRQ Channels -------------------------------------
8
    NVIC->ICER[TIM_US_TIMER_IRQn >> 0x05] = (uint32_t)0x01 << (TIM_US_TIMER_IRQn & (uint8_t)0x1F);
9
10
    tempCounter = TIM_GetCounter(TIM_US_TIMER);
11
    tempOverflows = publicUsTimerOverflows;
12
    waitingForIsr = TIM_GetITStatus(TIM_US_TIMER, TIM_IT_Update);
13
14
    // Enable the Selected IRQ Channels --------------------------------------
15
    NVIC->ISER[TIM_US_TIMER_IRQn >> 0x05] = (uint32_t)0x01 << (TIM_US_TIMER_IRQn & (uint8_t)0x1F);
16
17
    //Überprüfen der Daten auf konsistenz
18
    // Wenn Overflow dedektiert wurde aber ISR noch nicht ausgeführt wurde (Interrupt Status auf SET) und der
19
    // Überlauf vor dem Auslesen des Timerwerts war (höchstwertiges Bit des Timers ist 0),
20
    // muss dieser Überlauf auch noch mit berücksichtigt werden!
21
    if((waitingForIsr == SET) && !(0x8000 & tempCounter))
22
    {
23
        tempOverflows++;
24
    }
25
26
    return ( (tempOverflows * 0x0000FFFF) + (uint32_t)tempCounter);
27
}

Ich hab die Funtktion auch gerade getestet. Funktioniert alles so weit 
fehlerfrei.

Das mit den konsistenten Daten war mir vorher nicht bewusst.

@kbuchegg
Du hast natürlich recht. Ich hab mich da in was verrant.

Danke für eure Hilfe!!

von Peter D. (peda)


Lesenswert?

obamoa schrieb:
> return ( (tempOverflows * 0x0000FFFF) +
>
> Ich hab die Funtktion auch gerade getestet. Funktioniert alles so weit
> fehlerfrei.

Nö.
Entweder << 16 oder * 0x10000.


Peter

von Peter D. (peda)


Lesenswert?

Es ist keine gute Idee, nur den einen Interrupt zu sperren.
Es kann dadurch zu einer Prioritätsumkehr kommen, da ja ein niederer, 
lange dauernder Interrupt nun unterbrechen darf.
D.h. die Sperre kann erheblich länger dauern, als erwartet.

Eine globale Interruptsperre sperrt dagegen definiert und nur die 
kürzest mögliche Zeit.


Peter

von obamoa (Gast)


Lesenswert?

@Peter:
Du hast recht! Danke für den Tipp und die Erklärung!

Hier meine neu Funktion:
1
uint32_t getUsTime(void)
2
{   
3
    uint32_t tempOverflows;
4
    uint16_t tempCounter;
5
    ITStatus waitingForIsr;
6
    
7
    //Interrupts global sperren
8
    __disable_irq();
9
10
    //Atomic Code section START
11
    tempCounter = TIM_GetCounter(TIM_US_TIMER);
12
    tempOverflows = publicUsTimerOverflows;
13
    waitingForIsr = TIM_GetITStatus(TIM_US_TIMER, TIM_IT_Update);
14
    //Atomic Code section STOP
15
16
    //Interrupts global freigeben
17
    __enable_irq();
18
19
    //Überprüfen der Daten auf konsistenz
20
    // Wenn Overflow dedektiert wurde aber ISR noch nicht ausgeführt wurde (Interrupt Status auf SET) und der
21
    // Überlauf vor dem Auslesen des Timerwerts war (höchstwertiges Bit des Timers ist 0),
22
    // muss dieser Überlauf auch noch mit berücksichtigt werden!
23
    if((waitingForIsr == SET) && !(0x8000 & tempCounter))
24
    {
25
        tempOverflows++;
26
    }
27
28
    return ( (tempOverflows * 0x00010000) + (uint32_t)tempCounter);
29
}

von Klaus (Gast)


Lesenswert?

Als ich noch Assembler gemacht habe, war üblich, sich den 
Interruptstatus zu merken, dann zu disablen und am Ende den alten Status 
wiederherzustellen,

Ich hab noch sowas im Kopf:

PUSH PSW
DI
.
.
POP PSW

MfG Klaus

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Eine globale Interruptsperre sperrt dagegen definiert und nur die
> kürzest mögliche Zeit.

Bei den Cortex-M kann man in solchen Fällen an Stelle einer totalen 
Interrupt-Sperre die Interrupt-Priorität so setzen, dass nur höher 
priorisierte Interrupts durchkommen. Sinngemäss:

saved_prio = BASEPRI;
BASEPRI = Prio_vom_Timer_Interrupt;
...
BASEPRI = saved_prio;

Perfekt wird es, wenn man sicherstellt, dass BASEPRI nicht versehentlich 
reduziert wird, wenn verschachtelt aufgerufen.

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> Bei den Cortex-M sollte man in solchen Fällen an Stelle einer totalen
> Interrupt-Sperre die Interrupt-Priorität so setzen, dass nur höher
> priorisierte Interrupts durchkommen (=> BASEPRI).

Warum?

Popelige 3 Variablen atomar zu lesen geht bestimmt schneller, als das 
ganze Interrupt-Prolog-Epilog-Brimborium. D.h. niemand merkt diese 
kleine Verzögerung von ein paar Zyklen.
Es gibt also keinen Grund für eine unnötig komlizierte andere Lösung.

Vermutlich wird das Umschalten der Priorität sogar selber unter 
Interruptsperre erfolgen müssen. Man spart also garnichts aber hat 
zusätzlichen Code.


Peter

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Popelige 3 Variablen atomar zu lesen geht bestimmt schneller,

Wenns nur das ist - in seinem letzten Code sind aber 2 Aufrufe aus der 
STM32 Lib drin. Die sind mitunter deutlich aufwendiger implementiert.

Ausserdem ging es mir eher darum, das generelle Prinzip zu zeigen. Die 
Sache mit BASEPRI scheint nicht allgemein bekannt zu sein.

> Vermutlich wird das Umschalten der Priorität sogar selber unter
> Interruptsperre erfolgen müssen.

Ich wüsste nicht weshalb.

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
> Perfekt wird es, wenn man sicherstellt, dass BASEPRI nicht versehentlich
> reduziert wird, wenn verschachtelt aufgerufen.

=> BASEPRI_MAX. Wurde im CMSIS aber wohl vergessen.

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.