Forum: Compiler & IDEs avr-gcc 4.8.1: Fehler durch Optimierung / inline function


von oliver (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich habe hier ein kleines Problem, das mich total verwirrt:

In einigen meiner Projekte verwende ich (in nur leicht angepasster Form) 
die USI-UART Routine aus AN AVR307.
Funktioniert normalerweise gut, nur hier nicht.

Ein Tiny 861A "empfängt" nach jedem korrekt empfangenem Byte ein 0xFF, 
das nie gesendet wurde,

- wenn man vorher irgendein Bit in DIDR0 oder DIDR1 gesetzt hat 
(ausdrücklich NICHT für die Pins der USI),
- UND mit Optimierung -Os kompiliert,
- ODER die Funktion USI_UART_start() als "static inline" deklariert 
(dann tritt der Fehler auch mit Optimierung -O0 auf).

Sobald man in DIDR0 oder DIDR1 wieder alle Bits zurück setzt, 
funktioniert die Kommunikation wieder.

Was optimiert sich denn der avr-gcc (Version 4.8.1) da zusammen?
Oder ist mein µC defekt?
DIDR0 hat doch gar nichts mit der USI zu tun. Ich schalte ja nicht den 
Input Buffer für die USI-Pins ab.

Ich füge den Quelltext bei, der den Fehler zeigt. Es stammt aus einem 
größeren Projekt, aber ich habe alles entfernt, was nicht zum Fehler 
beiträgt bis auf die Atmel USI-UART Routine und
das Hauptprogramm, dem man per serieller Verbindung einfache 'Befehle' 
senden kann:
- 'B' setzt DIDR0 auf 0xd0,
- 'C' setzt DIDR0 auf 0,
- 'D' wartet auf die nächsten 3 Zeichen, sendet diese zurück, einmal als 
ASCII und einmal nach atoi().

Sendet man 'D056', so sollte man erhalten: Input = 056  Data = 8.
Im Fehlerfall erhält man jedoch Input = ?0? (0xff 0x30 0xff).

Was läuft hier schief?

Danke für Eure Hilfe!

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wenn du schon avr-gcc 4.8 verwenden willst, dann nimm mindestens 4.8.4.

Durch den Code wühl ich mich jestzt mal nicht durch.  Würdest du konkret 
aufzeigen, wo der Compiler falschen Code erzeugt hat?

von oliver (Gast)


Angehängte Dateien:

Lesenswert?

Johann L. schrieb:
> Wenn du schon avr-gcc 4.8 verwenden willst, dann nimm mindestens
> 4.8.4.

Hm, ich probier' mal, ob ich auf 4.9.2 upgraden kann...


> Durch den Code wühl ich mich jestzt mal nicht durch.  Würdest du konkret
> aufzeigen, wo der Compiler falschen Code erzeugt hat?

Ja, das wenn i wüsst ;-)
Ich nehme an, dass es im Bereich der USI_UART_start hakt.
Aber wie die dann vom Setzen von DIDR0 beeinflusst werden kann?


Hier lege ich noch die disassemblierten *.lss Dateien bei, einmal mit 
Optimierung, einmal ohne.

Daraus die Abschnitte USI_UART_start:

1
 mit Optimierung:
2
000000d2 <USI_UART_start>:
3
  d2:  f8 df         rcall  .-16       ; 0xc4 <timer_init>
4
  d4:  80 91 74 00   lds  r24, 0x0074
5
  d8:  88 60         ori  r24, 0x08  ; 8
6
  da:  80 93 74 00   sts  0x0074, r24
7
  de:  89 b7         in  r24, 0x39  ; 57
8
  e0:  84 60         ori  r24, 0x04  ; 4
9
  e2:  89 bf         out  0x39, r24  ; 57
10
  e4:  81 e0         ldi  r24, 0x01  ; 1
11
  e6:  81 bb         out  0x11, r24  ; 17
12
  e8:  82 e5         ldi  r24, 0x52  ; 82
13
  ea:  8d b9         out  0x0d, r24  ; 13
14
  ec:  08 95         ret
1
 ohne Optimierung:
2
0000014a <USI_UART_start>:
3
 14a:  cf 93         push  r28
4
 14c:  df 93         push  r29
5
 14e:  cd b7         in  r28, 0x3d  ; 61
6
 150:  de b7         in  r29, 0x3e  ; 62
7
 152:  e4 df         rcall  .-56       ; 0x11c <timer_init>
8
 154:  80 91 74 00   lds  r24, 0x0074
9
 158:  88 60         ori  r24, 0x08  ; 8
10
 15a:  80 93 74 00   sts  0x0074, r24
11
 15e:  89 e5         ldi  r24, 0x59  ; 89
12
 160:  90 e0         ldi  r25, 0x00  ; 0
13
 162:  29 e5         ldi  r18, 0x59  ; 89
14
 164:  30 e0         ldi  r19, 0x00  ; 0
15
 166:  f9 01         movw  r30, r18
16
 168:  20 81         ld  r18, Z
17
 16a:  24 60         ori  r18, 0x04  ; 4
18
 16c:  fc 01         movw  r30, r24
19
 16e:  20 83         st  Z, r18
20
 170:  81 e3         ldi  r24, 0x31  ; 49
21
 172:  90 e0         ldi  r25, 0x00  ; 0
22
 174:  21 e0         ldi  r18, 0x01  ; 1
23
 176:  fc 01         movw  r30, r24
24
 178:  20 83         st  Z, r18
25
 17a:  8d e2         ldi  r24, 0x2D  ; 45
26
 17c:  90 e0         ldi  r25, 0x00  ; 0
27
 17e:  22 e5         ldi  r18, 0x52  ; 82
28
 180:  fc 01         movw  r30, r24
29
 182:  20 83         st  Z, r18
30
 184:  00 00         nop
31
 186:  df 91         pop  r29
32
 188:  cf 91         pop  r28
33
 18a:  08 95         ret

Leider bin ich etwas schwach in Assembler...
Wo DIDR0 gesetzt wird und wie das die USI_UART_start "abschießen" kann, 
habe ich noch nicht gefunden.

von oliver (Gast)


Lesenswert?

So, ich fange nun an, anhand des Befehlssatzes im Datenblatt etwas 
Assembler zu lernen. ;-)

Meine Ergebnisse so weit:

1. M.E. stimmt das obige Listing für USI_UART_start mit Optimierung 
genau mit dem C Quelltext überein, Zeile für Zeile.
Das ohne Optimierung dagegen... ist etwas verwirrend. Da müssen wohl 
noch ein paar andere Programmteile verbacken sein.

2. Im *.lss mit Optimierung finde ich die Stellen, an denen DIDR0 (0x01) 
gesetzt wird, nämlich in diesen Zeilen:
37e - 380
und
392 - 3aa
Das sieht eigentlich auch nicht verkehrt aus.

In der *.lss ohne Optimierung finde ich jedoch nicht einmal die 
Schreibzugriffe auf 0x01. Wie wird denn das hier gemacht?


Ich habe nun gcc-avr 4.9.2 installiert. Mit Optimierung bekomme ich 
dasselbe Fehlerbild.
Und ohne -Os ist es völlig hoffnungslos den beabsichtigten Code im Tiny 
861A unterzubringen...

von Peter D. (peda)


Lesenswert?

oliver schrieb:
> - ODER die Funktion USI_UART_start() als "static inline" deklariert
> (dann tritt der Fehler auch mit Optimierung -O0 auf).

Scheint ein Laufzeitproblem zu sein. Bei -Os werden einmalig aufgerufene 
Funktionen geinlined, daher gehts dann schneller. Kannst ja mal ein 
_dely_us() davor einfügen.

oliver schrieb:
> Sobald man in DIDR0 oder DIDR1 wieder alle Bits zurück setzt,
> funktioniert die Kommunikation wieder.

Klingt merkwürdig.
Es hat aber schon seltsame Bugs gegeben, wo sich eigentlich getrennte 
HW-Einheiten beeinflußt haben. Z.B. darf beim ATtiny1634 PB3 kein Input 
sein, wenn der Watchdog aus ist.

Vielleicht gibt es auch Seiteneffekte zwischen RX und TX, das USI ist ja 
nicht Full-Duplex fähig. Man könnte daher jeweils bei der Umschaltung 
von RX nach TX und umgekehrt Delays probieren (2 Bitzeiten).

Ich fand die UART mit USI viel zu kompliziert und habe sie daher mit 
Timern gemacht, ist dann auch Full-Duplex:
Beitrag "Software UART"

von oliver (Gast)


Lesenswert?

Hallo Peter,

Peter D. schrieb:
> Scheint ein Laufzeitproblem zu sein.

Ja, so sieht es aus. Ich habe mal großzügig _delay_us(100) jeweils am 
Anfang der USI_UART_Initialise_Transmitter() und der 
USI_UART_Initialise_Receiver() eingefügt.
Damit läuft es.

Für die Anwendung ist das natürlich fatal, denn Atmel verwendet beide 
Funktionen in ISRs. :-(

Wenn ich ja wüsste, was genau da zu schnell ist und wie die 0xff in den 
Ringpuffer kommen... dann könnte man vielleicht durch einfaches 
Umstellen der Reihenfolge das Problem beheben.

Vielleicht wird in der USI_UART_Initialise_Transmitter() USIDR zu früh 
geschrieben? (USIDR = 0xFF;)

Peter D. schrieb:
> Klingt merkwürdig.
> Es hat aber schon seltsame Bugs gegeben, wo sich eigentlich getrennte
> HW-Einheiten beeinflußt haben. Z.B. darf beim ATtiny1634 PB3 kein Input
> sein, wenn der Watchdog aus ist.

Interessant - noch ein Kapitel aus dem Buch "µC-Esoterik", das ich noch 
nicht gelesen habe. ;-)
Wie der Inhalt von DIDR0 oder DIDR1 Auswirkungen auf die Laufzeit haben 
kann, ist mir ein Rätsel. Und auf einem Tiny 85 habe ich das Problem 
nicht.

von oliver (Gast)


Lesenswert?

Nein, an der USI_UART_Initialise_Transmitter() lag's nicht.
Schade - die wird nämlich nicht aus einer ISR aufgerufen.

Ich brauche am Anfang der USI_UART_Initialise_Receiver() mindestens 6µs 
delay (F_CPU = 8MHz). Drunter geht es nicht.

Das war jetzt reine Empirie - ich versteh's immer noch nicht.
Wieso braucht er gerade hier eine "Denkpause"?
Oder wo genau?
Woher bekommt er die 0xff?
Und was hat DIDR0 damit zu tun?


Peter D. schrieb:
> Ich fand die UART mit USI viel zu kompliziert und habe sie daher mit
> Timern gemacht,

Dein Soft-UART hatte ich gesehen. Bisher habe ich allerdings gedacht: 
"Was ich in Hardware machen kann, mache ich nicht in Software."
Inzwischen zweifele ich aber an dem Mehrwert des USI...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ich würd jetzt auf ein fehlendes volatile oder ein Glitch durch 
nicht-atomaren Zugriff in main tippen.

von oliver (Gast)


Lesenswert?

Johann L. schrieb:
> Ich würd jetzt auf ein fehlendes volatile oder ein Glitch durch
> nicht-atomaren Zugriff in main tippen.

Das würde mich freuen, denn das wäre einfach zu beheben.

Allerdings wäre ein fehlendes volatile ein logischer Fehler und somit 
zeitunabhängig. Egal wie schnell oder langsam - es wäre immer ein 
Fehler. Auch egal ob DIDR0 beschrieben wird oder nicht.

Und atomarer Zugriff würde doch erfordern, dass es irgendwo eine 16bit 
Variable gäbe, auf die atomar zugegriffen werden müsste. Alles ist 8bit.
Auch der Timer läuft im 8bit Modus.


Inzwischen ist es mir gelungen weiter einzugrenzen, wo der Delay sein 
muss:
1
 ISR(PCINT_vect)
2
{
3
4
    if (!( PINA & ( 1<< DI_RX) ))                                 // If the USI DI pin is low, then it is likely that it
5
    {                                                      //  was this pin that generated the pin change interrupt.
6
       _delay_us(8);
7
      TCNT1 =  INITIAL_TIMER1_SEED + INTERRUPT_STARTUP_DELAY;   // Plant initial TIMER1 seed for first bit to match baudrate (incl interrupt start up time.).
8
9
       USI_UART_start();
10
        USISR  = 0xF0 |                                           // Clear all USI interrupt flags.
11
                 USI_COUNTER_SEED_RECEIVE;                        // Preload the USI counter to generate interrupt.
12
                                                                  
13
        GIMSK &=  ~(1<<PCIE1);                                    // Disable pin change interrupt for PA7:0.
14
        // *** Besser DI pin aus PCMSK entfernen?
15
        USI_UART_status.ongoing_Reception_Of_Package = TRUE;             
16
    }
17
      return;
18
}
Genau dort, vor dem seed des TCNT1 - und nicht danach.
Nun bin ich auf den Gedanken gekommen, dass, wenn es nur darum ginge das 
Setzen des Seed und damit den Timer Overflow zu verzögern, man denselben 
Effekt ja genauso gut durch Ändern des Seeds erreichen könnte.
Geht aber nicht.
Oder ich habe mich verrechnet. F_CPU 8MHz, Timer Prescaler 4 - da 
sollten 8µs doch Seed - 16 sein...
Ach, es ist schon spät und mir brummt der Kopf.

Ich verstehe noch immer nicht, was das mit DIDR0 zu tun hat.

von Peter D. (peda)


Lesenswert?

oliver schrieb:
> Genau dort, vor dem seed des TCNT1 - und nicht danach.

Welcher Wert wird denn da konkret geladen?
Ich würde danach noch das Overflow-Flag löschen (auf 1 setzen).

oliver schrieb:
> Ich verstehe noch immer nicht, was das mit DIDR0 zu tun hat.

Gibt es da ein bestimmtes Bit, was das Fehlerbild bewirkt?

Kannst Du zukünftig statt .tar.gz besser .zip verwenden, das läßt sich 
leichter öffnen.

Beim ADC ist mir auch was komisches aufgefallen. Wenn man den ADC mit 
Timer triggert, muß man trozdem den Timerinterrupt ausführen, sonst wird 
das Overflow-Bit nicht gelöscht. Man spart also nicht wirklich 
Interruptlast ein. Scheint wohl beim USI auch nötig zu sein.

: Bearbeitet durch User
von oliver (Gast)


Lesenswert?

Guten Morgen, Peter,

Peter D. schrieb:
> Welcher Wert wird denn da konkret geladen?

206, sagt der Präprozessor.

Peter D. schrieb:
> Ich würde danach noch das Overflow-Flag löschen (auf 1 setzen).
Das geschieht gleich am Anfang der USI_UART_start():
1
 
2
void USI_UART_start( void) {
3
      spiX_status.modeUART     = 1;
4
      TIFR   = (1<<TOV1);
5
      TIMSK |= (1<<TOIE1);                     // Enable Timer1 OVF interrupt.
6
          USIPP  = (1<<USIPOS);                // USI auf PORTA legen.
7
          USICR  = (0<<USISIE)|(1<<USIOIE)|                 // Enable USI Counter OVF interrupt.
8
                   (0<<USIWM1)|(1<<USIWM0)|                 // Select Three Wire mode.
9
10
           (0<<USICS1)|(0<<USICS0)|(1<<USICLK)|    // use software clock strobe
11
                   (0<<USITC);
12
                                                           // Note that enabling the USI will also disable the pin change interrupt.
13
          return;
14
}

Peter D. schrieb:
> Gibt es da ein bestimmtes Bit, was das Fehlerbild bewirkt?

Nein. Der Fehler tritt sogar auf, wenn ich in DIDR1 Bits setze, also für 
PortB, obwohl die USI-UART PortA nutzt und PortB in dieser abgespeckten 
Programmversion leer ist.

Peter D. schrieb:
> Scheint wohl beim USI auch nötig zu sein.
Ja, genau. Da ich in der kompletten Anwendung die USI auf PortB für SPI 
nutze, trigger ich die USI clock in der ISR:
1
 ISR(TIMER1_OVF_vect)
2
{
3
  if (spiX_status.modeUART == 1)
4
    USICR  |= (1<<USICLK);
5
  else
6
    USICR |= (1<<USITC);  // Toggle clock output pin.
7
  return;
8
}
  Das setzt natürlich auch den Timer OVF Interrupt zurück. In der 
abgespeckten Version wird SPI nicht genutzt, d.h. modeUART ist immer 1 
und PortB ist leer.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

oliver schrieb:
> Johann L. schrieb:
>> Ich würd jetzt auf ein fehlendes volatile oder ein Glitch durch
>> nicht-atomaren Zugriff in main tippen.
>
> Und atomarer Zugriff würde doch erfordern, dass es irgendwo eine 16bit
> Variable gäbe, auf die atomar zugegriffen werden müsste. Alles ist 8bit.
> Auch der Timer läuft im 8bit Modus.

Das stimmt in dieser Allgeminheit nicht.  Erstens hast du auch bitfelder 
im Code welche nicht atomar gesetzt werden können da immer mindestens 
ein 8-Bit Wert gelesen und wieder gespreichert werden muss.  Analoges 
gilt für 8-Bit Werte.  Zwar kann atomar gelesen und gespeichert werden, 
aber wenn eine Opertaion atomar sein muss dann hilft es auch nicht, 
wenn es ein 8-Bit wert ist.

Wenn es z.B. einen 8-Bit Wert gibt, der 8 Flags enthält und wo ein Flag 
gesetzt wird, kann folgendes passieren:
1
uint8_t volatile flags;
2
3
void set_flag0 (void)
4
{
5
    flags |= 1;
6
}
1
set_flag0:
2
    lds r24,flags
3
    ;; Falls hier oder...
4
    ori r24,lo8(1)
5
    ;; hier eine ISR ausgeführt wird, die abanfalls eines der flag-
6
    ;; Bits verändert, werden all diese Änderungen durch folgendes
7
    ;; STS überschrieben.
8
    sts flags,r24
9
    ret
Aber das ist ja alles bereits im ober verlinkten Wiki-Artikel dargelegt, 
und solche Situationen treten in deinem Code wohl niemals auf.

von oliver (Gast)


Lesenswert?

Hallo Johann,
Johann L. schrieb:
> Das stimmt in dieser Allgeminheit nicht.  Erstens hast du auch bitfelder
> im Code welche nicht atomar gesetzt werden können
Ja, da hast Du recht.
Bei read-modify-write Operationen könnte es ein Problem geben.
Die sehe ich hier aber nicht. Auch die Ringpuffer, auf die ja auch aus 
den ISR zugegriffen wird, werden ja an dem einen Ende immer nur 
geschrieben und an dem anderen immer nur gelesen, und auf den 
dazugehörigen Index wird entweder nur in der ISR oder nur außerhalb 
schreibend zugegriffen.

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.