Forum: Mikrocontroller und Digitale Elektronik Threadsichere Bitmanipulation mit Bitbanding


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

bislang habe ich Bitbanding, obwohl es in vielen 
ARM-Cortex-M-Beschreibungen weit vorn steht, noch nie benötigt. Aber 
jetzt habe ich wohl einen Anwendungsfall: Ich will zu einer Variable 
einzelne Bits hinzufügen oder löschen.

Threadsicher oder besser: Interrupt-Sicher muss es sein. Performance ist 
nicht ganz so wichtig - ansonsten hätte ich statt eines Bit-Arrays ein 
Array aus 32-Bit-Werten genutzt.
1
/** Warnungen */
2
enum warning_e
3
{
4
    warning_lorem = 1<<0,
5
    warning_ipsum = 1<<1,
6
    warning_dolor = 1<<2,
7
    warning_sit   = 1<<3,
8
    warning_end = warning_sit,
9
};
10
11
typedef struct WarnState_s
12
{
13
    enum warning_e last;
14
    volatile enum warning_e this;
15
}
16
WarnState_t;
17
18
WarnState_t WarnState = {};
19
20
/* Bitbanding Makros https://www.mikrocontroller.net/articles/ARM_Bitbanding */
21
#define BBSram1Bit(addr, bit)   (*(__typeof__(addr)*) ((SRAM1_BB_BASE + (((unsigned)&(addr) - SRAM1_BASE) << 5) + ((bit) << 2))))
22
#define BBitOfMask(mask)        (31 - __builtin_clz(mask))
23
24
25
/** Eine oder mehrere neue Warnungen hinzufuegen. */
26
void warning_add(enum warning_e wrn)
27
{
28
    WarnState_t *const State = &WarnState;
29
    #if defined( STM32F4XX )
30
        /* Auf STM32 Bit threadsicher ueber Bitbanding-Region sichern
31
           Bitbanding: Abbildung des Speicherbereichs 0x20000000-0x200FFFFF auf den
32
           Bereich 0x22000000-0x23FFFFFF
33
           clz() ist auf den Cortex M3/M4 billig. */
34
        assert( (uint32_t) State>=0x20000000 && (uint32_t) State<= 0x200FFFFF);
35
36
        while(wrn)
37
        {
38
            int32_t bitNo = BBitOfMask(wrn);     /* Erstes Bit der Maske */
39
            BBSram1Bit(State->this, bitNo) = 1;  /* Bit ueber Bitbanding-Region setzen */
40
            wrn &= ~(1UL<<bitNo);                /* Behandeltes Bit loeschen */
41
        }
42
    #else
43
        /* Auf anderen Plattformen hoffen */
44
        State->this = wrn;
45
    #endif
46
}
47
48
49
/** Eine oder mehrere Warnungen entfernen
50
 *
51
 * Es duerfen auch Warnungen entfernt werden, die nicht gesetzt sind. */
52
void warning_remove(enum warning_e wrn)
53
{
54
    WarnState_t *const State = &WarnState;
55
    #if defined( STM32F4XX )
56
        while(wrn)
57
        {
58
            int32_t bitNo = BBitOfMask(wrn);     /* Erstes Bit der Maske */
59
            BBSram1Bit(State->this, bitNo) = 0;  /* Bit ueber Bitbanding-Region loeschen*/
60
            wrn &= ~(1UL<<bitNo);                /* Behandeltes Bit loeschen */
61
        }
62
    #else
63
        /* Auf anderen Plattformen hoffen */
64
        State->this &= ~wrn;
65
    #endif
66
}
Habe ich das so richtig umgesetzt, oder ist da noch ein Denkfehler?
(Es funktioniert zumindest schon einmal. Ein gutes Zeichen.)

: Bearbeitet durch User
von PittyJ (Gast)


Lesenswert?

Einfach mal so eine Anmerkung. Falls der Code jemals irgendwann auch auf 
C++ kompiliert wird, sollte man auf einen Bezeichner 'this' verzichten.

Auch wenn C++ Erfahrene sich den Source-Code anschauen, könnten sie 
dadurch verwirrt werden.


Ansonsten verstehe ich das eigentliche Problem gar nicht. Was ist das 
Problem mit der Bitmaske?

von Walter T. (nicolas)


Lesenswert?

Eine Nacht drüber geschlafen, und eine Sache ist mir aufgefallen:
1
/** Eine oder mehrere neue Warnungen hinzufuegen oder loeschen
2
 *
3
 * @param[in] wrn: Bitmaske Warnungen
4
 * @param[in] to: true/false (setzen/loeschen)
5
 *
6
 * Auf STM32 werden die Bits threadsicher ueber die Bitbanding-Region geschrieben.
7
 * Bitbanding: Abbildung des Speicherbereichs 0x20000000-0x200FFFFF auf den
8
 * Bereich 0x22000000-0x23FFFFFF.  clz() ist auf den Cortex M3/M4 billig. */
9
static_inline void warning_setTo(enum warning_e wrn, bool to)
10
{
11
  #if defined( STM32F4XX )
12
    /* Bitbanding-Region SRAM1 sicherstellen */
13
    /* Die Assertion ist teuer und verhindert Optimierungen.
14
       Mit einem Schreiben ins struct anstelle der Adresse des structs kann auch ohne
15
       Assertion sichergestellt werden, dass keine Flash-Region versehentlich ueber
16
       Bitbandig anzusprechen versucht wird. */
17
    //assert( (uint32_t) &WarnState>=0x20000000 && (uint32_t) &WarnState<= 0x200FFFFF);
18
19
    while(wrn)
20
    {
21
      int32_t bitNo = BBitOfMask(wrn);        /* Erstes Bit der Maske */
22
      BBSram1Bit(WarnState.this, bitNo) = to; /* Bit ueber Bitbanding-Region setzen */
23
      wrn &= ~(1UL<<bitNo);                   /* Behandeltes Bit loeschen */
24
    }
25
26
  #else
27
    /* Auf anderen Plattformen hoffen */
28
    if( v )
29
      WarnState.this |= wrn;
30
    else
31
      WarnState.this &= ~wrn;
32
  #endif
33
}
Wenn ich statt in einen Zeiger auf die Variable auf die Variable 
schreibe, warnt mich der Compiler auch ohne Assertion davon, eine 
Variable außerhalb des Bitbanding-Bereichs zu beschreiben. Eigentlich 
sind ja nur Flash-Variablen nicht bitbandbar, aber sie sind sowieso 
konstant und das wird mit einem Compiler-Fehler quittiert.

PittyJ schrieb:
> Ansonsten verstehe ich das eigentliche Problem gar nicht. Was ist das
> Problem mit der Bitmaske?

Ich will threadsicher Flags setzen und löschen.

PittyJ schrieb:
> Einfach mal so eine Anmerkung. Falls der Code jemals irgendwann auch auf
> C++ kompiliert wird, sollte man auf einen Bezeichner 'this' verzichten.

Stimmt. Wenn ich auf C++ umstellen sollte, ist das noch eine der 
kleinsten Umstellungen. Was ist eigentlich in C++ das übliche Idiom für 
"delta = this - last" oder "delta = new - this" ?

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Du könntest auch atomic_fetch_or verwenden, das ist ebenfalls 
thread/interrupt-sicher aber dafür standardisiert und portabel:

https://en.cppreference.com/w/c/atomic/atomic_fetch_or

von Walter T. (nicolas)


Lesenswert?

Programmierer schrieb:
> Du könntest auch atomic_fetch_or verwenden

Danke, das passt gut in den #else-Zweig.

von Purzel H. (hacky)


Lesenswert?

Ein einzelnes Bit schreiben ist ja sowieso atomar...

von Programmierer (Gast)


Lesenswert?

Purzel H. schrieb:
> Ein einzelnes Bit schreiben ist ja sowieso atomar...

Nein, nicht im Adressraum beim ARM. Ein Bit zu setzen bedeutet nämlich:
- Einen 8/16/32-Bit-Wert aus dem Adressraum/RAM in ein 
32bit-Prozessorregister laden
- Das Bit im Prozessorregister setzen
- Die 8/16/32 Bit des Registers zurück in den Adressraum/RAM schreiben

Und genau das macht der Compiler auch daraus. Wenn bei Schritt 2 ein 
Interrupt oder Thread-Kontext-Wechsel kommt und dann ein anderes Bit im 
8/16/32-Bit Wert geändert wird, wird das im Schritt drei wieder 
zurückgesetzt.

Daher braucht es atomische Operationen.

von EAF (Gast)


Lesenswert?

Programmierer schrieb:
> Nein, nicht im Adressraum beim ARM.

Auch nicht beim AVR.
Zumindest nicht im gesamten Adressraum

Beispiel:
1
volatile unsigned z = 42;
2
z |= (1<<5);
3
cout << z << endl;

Das ist nicht atomar, darum wirft es auch wohl in moderneren C++:
1
warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]

In dem z Fall ist es klar, aber in anderen Fällen sieht man nicht auf 
den ersten Blick, ob der Zugriff jetzt atomar erfolgt, oder eben nicht.
Ein Quell für Fehler.
Der wird jetzt ausgemerzt.

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.