Forum: Mikrocontroller und Digitale Elektronik AVR: Ist PORTx |= atomar?


von Gerd (Gast)


Lesenswert?

Kann man bei den AVR-Mikrocontrollern PORTC |= xx, PORTC &= xx und PINC 
= xxx (Toggle) schreiben, wenn Interrupt-Code im Programm ist, der 
gleichzeitig andere Bits in PORTC ändert?

Es ginge nicht, wenn dieser C-Befehle zu zwei Befehlen führen (Port 
lesen und schreiben), aber das ist nicht der Fall, oder?

von Writer (Gast)


Lesenswert?

Gerd schrieb:
> PORTC |= xx, PORTC &= xx

Wie soll das bitte funktionieren ohne den Port vorher zu lesen? Atomar 
wäre nur PORTC = xx.

von Sebastian S. (amateur)


Lesenswert?

Falls der Zäh-Compiler diese Befehle "richtig" umsetzt, sollte es gehen.
Schau doch mal in die Befehlsreferenz zum AVR um zu sehen, was geht.

So weit mir bekannt, gehen ?= mit einem Register und einer Konstanten. 
Wie die Werte aber in das (Vergleichs-)Register kommen ist eine andere 
Sache.

von (prx) A. K. (prx)


Lesenswert?

Jein. Bei Ports 0..31 und festem Einzelbit ist &= und |= atomar, sonst 
nicht.

von Einer K. (Gast)


Lesenswert?

Gerd schrieb:
> Es ginge nicht, wenn dieser C-Befehle zu zwei Befehlen führen

Das kannst du im Assemblerlisting überprüfen und dir so selber 
beantworten.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Gerd schrieb:
> Es ginge nicht, wenn dieser C-Befehle zu zwei Befehlen führen
Er führt sogar zu noch viel mehr Befehlen, weil nach dem Lesen und vor 
dem Schreiben der Wert noch Verändert werden muss: Read-MODIFY-Write.

A. K. schrieb:
> Bei Ports 0..31 und festem Einzelbit ist &= und |= atomar, sonst nicht.
Das wird dann umgesetzt mit den Befehlen sbi und cbi

: Bearbeitet durch Moderator
von Bla bla (Gast)


Lesenswert?

Wie soll ein Compiler die die Grenzen eines AVR sprengen?

Wir wissen nichtmal, um welches AVR es geht. Jedoch werden die Ports 
über Adressregister auf dem Datenbus angesprochen. Es gibt zwar SBR und 
CBR Maschinenbefehle (einzelnes Bits setzen), die jedoch nur für 
Register in der CPU wirken.

Man muss die Ports immer erst auslesen, Bit ändern und das Ergebnis 
zurückschreiben.

Zumindest ist das bei einem Atmega8 etc. so.

von c-hater (Gast)


Lesenswert?

Gerd schrieb:

> Es ginge nicht, wenn dieser C-Befehle zu zwei Befehlen führen (Port
> lesen und schreiben), aber das ist nicht der Fall, oder?

Kommt (mindestens) auf den Port und den Controller an. Bei Hochsprachen 
auch noch auf die Intelligenz des Codegenerators.

Hast du uns alles nicht verraten, also musst du die Datenblätter selbst 
lesen. Also das tun, was du sowieso hättest tun sollen, bevor du 
wildfremde Leute mit solchen Trivialitäten behelligst...

von Gerd (Gast)


Lesenswert?

Ich verstehe keinen Assemböer. Hier der Code, wenn jemand helfen möchte. 
Es geht um PORTC vom ATMega1284: An dem läuft Software-SPI (s.u.) und 
andere Bits werden in Interrupts geändert. Im C-Code für SPI verändern 
die Makros SOFTSPI_SCK_TOGGLE(), SOFTSPI_MOSI_HIGH(), SOFTSPI_MOSI_LOW() 
den PORTC. CLI() notwendig?

Disassembly of section .text.softspi_write_byte:
1
00000000 <softspi_write_byte>:
2
   0:  98 2f         mov  r25, r24
3
   2:  2f b7         in  r18, 0x3f  ; 63
4
   4:  f8 94         cli
5
   6:  87 ff         sbrs  r24, 7
6
   8:  00 c0         rjmp  .+0        ; 0xa <softspi_write_byte+0xa>
7
   a:  41 9a         sbi  0x08, 1  ; 8
8
   c:  00 c0         rjmp  .+0        ; 0xe <softspi_write_byte+0xe>
9
   e:  41 98         cbi  0x08, 1  ; 8
10
  10:  81 e0         ldi  r24, 0x01  ; 1
11
  12:  86 b9         out  0x06, r24  ; 6
12
  14:  88 b1         in  r24, 0x08  ; 8
13
  16:  8c 7f         andi  r24, 0xFC  ; 252
14
  18:  88 b9         out  0x08, r24  ; 8
15
  1a:  96 ff         sbrs  r25, 6
16
  1c:  00 c0         rjmp  .+0        ; 0x1e <softspi_write_byte+0x1e>
17
  1e:  82 e0         ldi  r24, 0x02  ; 2
18
  20:  86 b9         out  0x06, r24  ; 6
19
  22:  81 e0         ldi  r24, 0x01  ; 1
20
  24:  86 b9         out  0x06, r24  ; 6
21
  26:  88 b1         in  r24, 0x08  ; 8
22
  28:  8c 7f         andi  r24, 0xFC  ; 252
23
  2a:  88 b9         out  0x08, r24  ; 8
24
  2c:  95 ff         sbrs  r25, 5
25
  2e:  00 c0         rjmp  .+0        ; 0x30 <softspi_write_byte+0x30>
26
  30:  82 e0         ldi  r24, 0x02  ; 2
27
  32:  86 b9         out  0x06, r24  ; 6
28
  34:  81 e0         ldi  r24, 0x01  ; 1
29
  36:  86 b9         out  0x06, r24  ; 6
30
  38:  88 b1         in  r24, 0x08  ; 8
31
  3a:  8c 7f         andi  r24, 0xFC  ; 252
32
  3c:  88 b9         out  0x08, r24  ; 8
33
  3e:  94 ff         sbrs  r25, 4
34
  40:  00 c0         rjmp  .+0        ; 0x42 <softspi_write_byte+0x42>
35
  42:  82 e0         ldi  r24, 0x02  ; 2
36
  44:  86 b9         out  0x06, r24  ; 6
37
  46:  81 e0         ldi  r24, 0x01  ; 1
38
  48:  86 b9         out  0x06, r24  ; 6
39
  4a:  88 b1         in  r24, 0x08  ; 8
40
  4c:  8c 7f         andi  r24, 0xFC  ; 252
41
  4e:  88 b9         out  0x08, r24  ; 8
42
  50:  93 ff         sbrs  r25, 3
43
  52:  00 c0         rjmp  .+0        ; 0x54 <softspi_write_byte+0x54>
44
  54:  82 e0         ldi  r24, 0x02  ; 2
45
  56:  86 b9         out  0x06, r24  ; 6
46
  58:  81 e0         ldi  r24, 0x01  ; 1
47
  5a:  86 b9         out  0x06, r24  ; 6
48
  5c:  88 b1         in  r24, 0x08  ; 8
49
  5e:  8c 7f         andi  r24, 0xFC  ; 252
50
  60:  88 b9         out  0x08, r24  ; 8
51
  62:  92 ff         sbrs  r25, 2
52
  64:  00 c0         rjmp  .+0        ; 0x66 <softspi_write_byte+0x66>
53
  66:  82 e0         ldi  r24, 0x02  ; 2
54
  68:  86 b9         out  0x06, r24  ; 6
55
  6a:  81 e0         ldi  r24, 0x01  ; 1
56
  6c:  86 b9         out  0x06, r24  ; 6
57
  6e:  88 b1         in  r24, 0x08  ; 8
58
  70:  8c 7f         andi  r24, 0xFC  ; 252
59
  72:  88 b9         out  0x08, r24  ; 8
60
  74:  91 ff         sbrs  r25, 1
61
  76:  00 c0         rjmp  .+0        ; 0x78 <softspi_write_byte+0x78>
62
  78:  82 e0         ldi  r24, 0x02  ; 2
63
  7a:  86 b9         out  0x06, r24  ; 6
64
  7c:  81 e0         ldi  r24, 0x01  ; 1
65
  7e:  86 b9         out  0x06, r24  ; 6
66
  80:  88 b1         in  r24, 0x08  ; 8
67
  82:  8c 7f         andi  r24, 0xFC  ; 252
68
  84:  88 b9         out  0x08, r24  ; 8
69
  86:  90 ff         sbrs  r25, 0
70
  88:  00 c0         rjmp  .+0        ; 0x8a <softspi_write_byte+0x8a>
71
  8a:  82 e0         ldi  r24, 0x02  ; 2
72
  8c:  86 b9         out  0x06, r24  ; 6
73
  8e:  81 e0         ldi  r24, 0x01  ; 1
74
  90:  86 b9         out  0x06, r24  ; 6
75
  92:  00 00         nop
76
  94:  88 b1         in  r24, 0x08  ; 8
77
  96:  8c 7f         andi  r24, 0xFC  ; 252
78
  98:  88 b9         out  0x08, r24  ; 8
79
  9a:  2f bf         out  0x3f, r18  ; 63
80
  9c:  08 95         ret

-----------
1
// A6: chip select
2
// A7: miso
3
// C0: clock
4
// C1: mosi
5
6
#define SOFTSPI_SCK_HIGH()     PORTC |=  BITMASK0
7
#define SOFTSPI_SCK_LOW()      PORTC &= ~BITMASK0
8
#define SOFTSPI_SCK_TOGGLE()   PINC   =  BITMASK0    /* setting a bit in PINx (like this, not |=) toggles corresponding bit in PORTx */
9
#define SOFTSPI_CS_HIGH()      PORTA |=  BITMASK6
10
#define SOFTSPI_CS_LOW()       PORTA &= ~BITMASK6
11
#define SOFTSPI_MOSI_HIGH()    PORTC |=  BITMASK1
12
#define SOFTSPI_MOSI_LOW()     PORTC &= ~BITMASK1
13
#define SOFTSPI_MOSI_TOGGLE()  PINC   =  BITMASK1    /* setting a bit in PINx (like this, not |=) toggles corresponding bit in PORTx */
14
#define SOFTSPI_MISO_READ()    PINA   &  BITMASK7
15
#define SOFTSPI_SCK_MOSI_LOW() PORTC &= ~(BITMASK0 | BITMASK1)
16
17
// send one byte using software SPI, unrolled (clock polarity: low when idle; phase: sample on rising edge of clock)
18
19
void
20
softspi_write_byte(uint8_t out)
21
{
22
  // port c (CLK, MOSI) shared with interrupt code
23
24
  uint8_t sreg =  SREG;
25
26
  cli();
27
28
  // SCK and /CS must be low when arriving here (setup by caller)
29
30
  // bit 7
31
32
  if (out & 128)
33
    SOFTSPI_MOSI_HIGH();
34
  else
35
    SOFTSPI_MOSI_LOW();
36
37
  SOFTSPI_SCK_TOGGLE();                                // clock now high
38
39
  SOFTSPI_SCK_MOSI_LOW();
40
41
  // bit 6
42
43
  if (out & 64)
44
    SOFTSPI_MOSI_TOGGLE();
45
46
  SOFTSPI_SCK_TOGGLE();                                // clock now high
47
48
  SOFTSPI_SCK_MOSI_LOW();
49
50
  // bit 5
51
52
  if (out & 32)
53
    SOFTSPI_MOSI_TOGGLE();
54
55
  SOFTSPI_SCK_TOGGLE();                                // clock now high
56
57
  SOFTSPI_SCK_MOSI_LOW();
58
59
  // bit 4
60
61
  if (out & 16)
62
    SOFTSPI_MOSI_TOGGLE();
63
64
  SOFTSPI_SCK_TOGGLE();                                // clock now high
65
66
  SOFTSPI_SCK_MOSI_LOW();
67
68
  // bit 3
69
70
  if (out & 8)
71
    SOFTSPI_MOSI_TOGGLE();
72
73
  SOFTSPI_SCK_TOGGLE();                                // clock now high
74
75
  SOFTSPI_SCK_MOSI_LOW();
76
77
  // bit 2
78
79
  if (out & 4)
80
    SOFTSPI_MOSI_TOGGLE();
81
82
  SOFTSPI_SCK_TOGGLE();                                // clock now high
83
84
  SOFTSPI_SCK_MOSI_LOW();
85
86
  // bit 1
87
88
  if (out & 2)
89
    SOFTSPI_MOSI_TOGGLE();
90
91
  SOFTSPI_SCK_TOGGLE();                                // clock now high
92
93
  SOFTSPI_SCK_MOSI_LOW();
94
95
  // bit 0
96
97
  if (out & 1)
98
    SOFTSPI_MOSI_TOGGLE();
99
100
  SOFTSPI_SCK_TOGGLE();                                // clock now high
101
102
  DELAY_50NS();
103
104
  SOFTSPI_SCK_MOSI_LOW();                              // this wil puit the first bit on MISO line
105
106
  SREG = sreg;
107
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Gerd schrieb:
> Kann man bei den AVR-Mikrocontrollern PORTC |= xx, PORTC &= xx und PINC
> = xxx (Toggle) schreiben, wenn Interrupt-Code im Programm ist, der
> gleichzeitig andere Bits in PORTC ändert?

SFR = val

ist atomar falls SFR ein 8-Bit SFR ist, unabhängig von Optimierungsstufe 
und Adresse des SFR.

PORTC |= xx
PORTC &= xx

Notwendige Bedingungen, dass das atomar ist (avr-gcc):

* PORTC ist ein 8-Bit SFR mit einer Adresse von __AVR_SFR_OFFSET__ bis 
__AVR_SFR_OFFSET__ + 0x1f, und die Adresse muss zur Compilezeit bekannt 
sein. (__AVR_SFR_OFFSET__ ist ein Built-in Makro von avr-gcc.) 
Alternativ kann PORTC eine Deklaration mit Attribut io_low sein.

* xx ist zur Compilerzeit bekannt, und xx ist exakte 2-er Potenz von 
0x1..0x80 (Fall |=) oder das 1-er Komplement einer solchen 2-er Potenz 
(Fall &=).

* Optimierung ist nicht -O0 oder -Og.

Es gibt jedoch ältere Compilerversionen, wo dies nicht hinreichend 
ist, z.B. wird bei 2× hintereinander SFR &= 0x7f das 0x7f erst in ein 
Register geladen, was dann zu LDI + 2*(IN+AND+OUT) führt.

Bei aktuellen Versionen (ab ca. 4.7) hab ich das noch nicht beobachtet, 
aber generell ist das nicht definitiv auszuschließen.

Eigentlich müsste sich mal jemand die Mühe machen, entsprechende atomic 
Built-ins zu implementieren, die dann ggf. LAS, LAT verwenden oder 
Interrupts temporär deaktivieren falls eine der obigen Bedingungen nicht 
erfüllt ist, z.B. xx keine 2-er Potenz ist oder Werte nicht zur 
Compiletime bekannt sind.

von Bla bla (Gast)


Lesenswert?

Es ist unsauberer Stil, wenn du in einem Interrupt auf eine 
Hardwareressource schreibend zugreifst, welche auch im normalen 
Programmfluss verändert wird.

Wenn du sämtlichen hardwarezugriff im Interrupt verhinderst, bist du aus 
der Sache raus. oder aber der Hardwarezugriff auf diese Ressource 
passiert ausschlißelich im Interrupt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Gerd schrieb:
> ... Makros ...
> CLI() notwendig?

Ja, sieht so aus.  Denn xx ist nicht das 1-er Komplement einer 2-er 
Potenz, eine der o.g genannten notwendigen Voraussetzungen:

>   14:  88 b1         in  r24, 0x08  ; 8
>   16:  8c 7f         andi  r24, 0xFC  ; 252
>   18:  88 b9         out  0x08, r24  ; 8

0xFC = 0b11111100 ist nicht darstellbar als 0xff ^ (1 << n).

Die Makro-Definitionen wurden nicht genannt.

von (prx) A. K. (prx)


Lesenswert?

Bla bla schrieb:
> Es ist unsauberer Stil, wenn du in einem Interrupt auf eine
> Hardwareressource schreibend zugreifst, welche auch im normalen
> Programmfluss verändert wird.

Sind mehrere Portpins mit völlig verschiedener Funktion verschiedener 
Module eine gemeinsame Ressource? Alternative?

von LinTau (Gast)


Lesenswert?

>Das kannst du im Assemblerlisting überprüfen und dir so selber
>beantworten.
Das würde ich so nicht unterschreiben, Du hast damit nur einen 
speziellen Fall geprüft.
Vor 5-10 Jahren. Kundenprojekt Microchip PIC + HiTech C-Compiler. Ein
1
BitStructIrgendwas.Bit42 = 1;
Wurde vom Compiler IM GLEICHEN Programm auf 4 verschiedenen Arten 
umgesetzt.

Dabei war sowohl ein:

assembler_bit_set byte007.4

als auch ein:

assembler_byte007 = 0;
assembler_byte007 |= 0x10;

Beim Setzen wurde also das Bit kurzzeitig sogar rückgesetzt. Was dazu 
führte, dass auf dem LIN Bus (Treiber lief im Interrupt) hin und wieder 
eine 0 gesendet wurde.

von c-hater (Gast)


Lesenswert?

A. K. schrieb:

> Sind mehrere Portpins mit völlig verschiedener Funktion verschiedener
> Module eine gemeinsame Ressource?

Kommt 'drauf an. Nämlich im Kern exakt darauf, ob sie sich atomar 
ansteuern lassen. Der TO hat hier also durchaus eine wunden Punkt 
gefunden. Naja, nicht einen wirklich neuen...

Wer auf Nummer sicher gehen will/muss, nimmt halt einfach an, das es 
eine gemeinsame Resource ist und trifft die nötigen Vorkehrungen gegen 
unerwünschte Beeinflussungen.

Wer 100% sicher sein kann, dass er atomar zugreifen kann (Asm rules!), 
der kann darauf verzichten und den dadurch möglichen Speedup nutzen. And 
again: Asm rules...

C/C++ stinks...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Gerd schrieb:
> #define SOFTSPI_SCK_MOSI_LOW() PORTC &= ~(BITMASK0 | BITMASK1)

Besser so:
1
#define SOFTSPI_SCK_MOSI_LOW()  \
2
    do {                        \
3
        PORTC &= ~(BITMASK0);   \
4
        PORTC &= ~(BITMASK1);   \
5
    } while (0)

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

LinTau schrieb:
> BitStructIrgendwas.Bit42 = 1;
> Wurde vom Compiler IM GLEICHEN Programm auf 4 verschiedenen Arten
> umgesetzt.
>
> Dabei war sowohl ein:
>
> assembler_bit_set byte007.4
>
> als auch ein:
>
> assembler_byte007 = 0;
> assembler_byte007 |= 0x10;
Sehen allesamt korrekt aus.
Woher soll der Compiler wissen, dass du ihm per Interrupt in die Suppe 
spuckst und dort Daten aus dem Struct abholst?
Oder andersrum: Wenn Daten im Struct konsistnet sein sollen, dann muss 
diese Semaphore so gesteuert werden, dass während der Manipulation kein 
anderer Zugriff erlaubt wird.
Im einfachsten Fall geht das, indem man eine lokale Kopie anlegt, und 
dort drin herumrechnet. Wenn alles fertig berechnet ist, wird 
schlussendlich das lokale Abbild bei gesperrtem Interrupt auf den 
globalen Sendepuffer kopiert.

Oder kürzer: man sollte nicht dem Compiler die Schuld für ein falsches 
System-/Softwaredesign geben.

: Bearbeitet durch Moderator
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.