Forum: Compiler & IDEs Umwandlung von zwei uint8_t in uint16_t erzeugt falschen Code?


von Christoph U. (Gast)


Lesenswert?

Guten Morgen allerseits,

das Problem des "Verheiratens" von zwei uint8_t zu einem uint16_t taucht 
hier ja regelmäßig auf, aber ich habe bei der Suche keinen Beitrag 
gefunden, der dieses Verhalten beschreibt...

Es geht darum, zwei via UART empfangene Bytes (High und Low-Byte) zu 
einem uint16_t zu konvertieren, allerdings funktioniert es nicht ganz 
wie erwartet. Folgender (abgespeckter aber lauffähiger) Code produziert 
das Verhalten:

*main.c*
1
#include <avr/io.h>
2
3
extern char Uart_RxChar(void);
4
static void foo(uint16_t value);
5
6
7
int main(void)
8
{
9
    while(1)
10
    {
11
        uint8_t lowByte;
12
        uint8_t highByte;
13
        uint16_t value;
14
    
15
        highByte = (uint8_t)Uart_RxChar();
16
        lowByte = (uint8_t)Uart_RxChar();
17
        value = lowByte | (uint16_t)highByte<<8;
18
19
        foo(value);
20
    }
21
}
22
23
24
static void foo(uint16_t value)
25
{
26
  if (value > 1000)
27
  {
28
    PORTA |= (1<<PA0);
29
  }
30
  else
31
  {
32
    PORTA &= ~(1<<PA0);
33
  }
34
}

*uart.c*
1
#include <avr/io.h>
2
3
char Uart_RxChar(void)
4
{
5
  return UDR0;
6
}

Der AVR-GCC 3.3.1.27 mit Optimierung O1 erzeugt bei mir folgendes 
Assembler Listing (Ausschnitt):
1
int main(void)
2
{
3
  94:  ef 92         push  r14
4
  96:  ff 92         push  r15
5
  98:  1f 93         push  r17
6
  9a:  cf 93         push  r28
7
  9c:  df 93         push  r29
8
    {
9
        uint8_t lowByte;
10
    uint8_t highByte;
11
        uint16_t value;
12
    
13
        highByte = (uint8_t)Uart_RxChar();
14
  9e:  0e 94 60 00   call  0xc0  ; 0xc0 <Uart_RxChar>
15
        lowByte = (uint8_t)Uart_RxChar();
16
  a2:  0e 94 60 00   call  0xc0  ; 0xc0 <Uart_RxChar>
17
        value = lowByte | (uint16_t)highByte<<8;
18
  a6:  20 e0         ldi  r18, 0x00  ; 0
19
  a8:  e9 01         movw  r28, r18
20
  aa:  90 e0         ldi  r25, 0x00  ; 0
21
  ac:  8c 2b         or  r24, r28
22
  ae:  9d 2b         or  r25, r29
23
}
24
25
26
static void foo(uint16_t value)
27
{
28
  if (value > 1000)
29
  b0:  33 e0         ldi  r19, 0x03  ; 3
30
  b2:  89 3e         cpi  r24, 0xE9  ; 233
31
  b4:  93 07         cpc  r25, r19
32
  b6:  10 f0         brcs  .+4        ; 0xbc <main+0x28>
33
  {
34
    PORTA |= (1<<PA0);
35
  b8:  10 9a         sbi  0x02, 0  ; 2
36
  ba:  f1 cf         rjmp  .-30       ; 0x9e <main+0xa>
37
  }
38
  else
39
  {
40
    PORTA &= ~(1<<PA0);
41
  bc:  10 98         cbi  0x02, 0  ; 2
42
  be:  ef cf         rjmp  .-34       ; 0x9e <main+0xa>

Hier ergeben sich mir mehrere Fragen:

1. Warum wird der Rückgabewert des ersten Aufrufs von Uart_RxChar() 
überhaupt nicht beachtet?

2. Warum verwendet der Compiler bei movw r28, r18 implizit das 
undefinierte Register r19?

Die einfachste und wohl unwahrscheinlichste Antwort ist, der Compiler 
hat ein "mov r19, r24" nach dem ersten Aufruf von Uart_RxChar() 
vergessen, aber es ist wohl alles ganz anders und der Fehler sitzt 
vielmehr vor dem Rechner! Ich bin eigentlich der Meinung, dass die 
relevante Codezeile
1
value = lowByte | (uint16_t)highByte<<8;
 korrekt ist (siehe z.B. auch 
Beitrag "Re: 4 uint8_t in uint32_t konvertieren").

Könnt ihr mir bitte auf die Sprünge helfen?

von Peter D. (peda)


Lesenswert?

Christoph U. schrieb:
> Der AVR-GCC 3.3.1.27 mit Optimierung O1

Warum -O1 ?
Tritt der Fehler bei -Os auch auf?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph U. schrieb:
> Der AVR-GCC 3.3.1.27 mit Optimierung O1 erzeugt bei mir folgendes

3.3.1.27 klingt nicht nach einer GCC-Version.

Bei mir (GCC 4.7.2) wird folgendes erzeugt, wenn ich das für den
ATmega1281 compilieren lasse:
1
.global main
2
        .type   main, @function
3
main:
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 0 */
7
.L__stack_usage = 0
8
.L5:
9
        call Uart_RxChar
10
        mov r17,r24
11
        call Uart_RxChar
12
        mov r29,r17
13
        ldi r18,0
14
        mov r28,r18
15
        movw r18,r28
16
        or r18,r24
17
        movw r24,r18
18
        cpi r24,-23
19
        sbci r25,3
20
        brlo .L3
21
        sbi 0x2,0
22
        rjmp .L5
23
.L3:
24
        cbi 0x2,0
25
        rjmp .L5

Immer noch ein wenig umständlich mit dem Hin- und Hergeschiebe zwischen
r18 und r28, aber sollte korrekt sein.

Dein Problem sieht mir wie eine Inkarnation von GCC PR46779 aus:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46779

Vermutlich benutzt du eine buggige Version von Atmels AVR-Toolchain.

von Christoph U. (Gast)


Lesenswert?

Vielen Dank für die schnellen Antworten.

@Peter:

-O1 war die Standard-Einstellung. Bei -Os ist es aber genauso.


@Jörg:

Ich habe die im AVR Studio 5.1 angegebene Nummer fälschlicherweise als 
Version interpretiert. Die wirkliche Version ist 4.5.1 und ist wohl 
demnach schon ein wenig betagter. Ich wollte sowieso schon länger mal 
das AVR Studio 6 installieren. In jedem Fall ist es gut zu wissen, dass 
in diesem Fall wohl doch mal der Compiler Schuld hatte und nicht ich.

Also vielen Dank für den Hinweis.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christoph U. schrieb:
> Die wirkliche Version ist 4.5.1 und ist wohl
> demnach schon ein wenig betagter.

Ja, und vor allem leidet diese Version an besagtem Bug.  Prinzipiell
war der Bug wohl schon früher da, aber in noch älteren Versionen
trat er praktisch nicht in Erscheinung.

von Εrnst B. (ernst)


Lesenswert?

Christoph U. schrieb:
> #include <avr/io.h>
>
> char Uart_RxChar(void)
> {
>   return UDR0;
> }

Fehlt da nicht noch ein Warten auf den UART-Receive-Complete?

 while (!(UCSRA & (1<<RXC)));

oder so?

von Christoph U. (Gast)


Lesenswert?

Korrekt, das angegebene Programm war bloß als abgespeckte Version zum 
Nachvollziehen des Verhaltens gedacht. Im richtigen Programm gibt's auch 
die Abfrage und noch viel mehr. :)

Trotzdem Danke!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:

> Immer noch ein wenig umständlich mit dem Hin- und Hergeschiebe zwischen
> r18 und r28, aber sollte korrekt sein.

Der widerporstige PR41076:

http://gcc.gnu.org/PR41076

von Olek (Gast)


Lesenswert?

ist es falsch in so einem Fall die union zu verwenden?

von Andreas B. (andreasb)


Lesenswert?

Olek schrieb:
> ist es falsch in so einem Fall die union zu verwenden?

Nein, aber mühsamer;-)

Auf einem 8bitter sollte das sogar effizienter ablaufen.
Er sollte einfach die Adresse +1 Rechnen.

Habs jetzt aber nicht genauer angeschaut...



mfg Andreas

von Olek (Gast)


Lesenswert?

....sorry für doppelPost, aber die Frage geht mir grade nicht aus dem 
Kopf.
macht der Compiler, wenn du es so schreiben würdest, den selben Fehler?

value = (lowByte | (((uint16_t)highByte)<<8));

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Olek schrieb:
> macht der Compiler, wenn du es so schreiben würdest, den selben Fehler?
>
> value = (lowByte | (((uint16_t)highByte)<<8));

Der Compiler ersetzt nicht starr C-Text durch Assemblercode, sondern 
walkt ihn ca 200 mal durch.  Wenn dabei ein 16-Bit Register aus mehreren 
8-Bit Registern aufgebaut wird, kann PR46779 auftreten.

Das ist abhängig von den Optimierungseinstellungen, vom Kontext, in dem 
der Code steht, etc.  Mit einer Union kann der Fehler auch auftreten.

von Hans-jürgen H. (hjherbert) Benutzerseite


Lesenswert?

Versuch mal


 volatile uint8_t lowByte;
 volatile uint8_t highByte;
        uint16_t value;

So ähnlich konnte ich mal avr-gcc 4.5.3 überlisten, doch richtig zu 
kompilieren.

(Oder nimm besser 4.7.2 )

von Oliver (Gast)


Lesenswert?

Johann L. schrieb:
> Olek schrieb:
>> macht der Compiler, wenn du es so schreiben würdest, den selben Fehler?>>
>
>> value = (lowByte | (((uint16_t)highByte)<<8));
>
> Der Compiler ersetzt nicht starr C-Text durch Assemblercode, sondern
> walkt ihn ca 200 mal durch.  Wenn dabei ein 16-Bit Register aus mehreren
> 8-Bit Registern aufgebaut wird, kann PR46779 auftreten.

Das hier sollte dann aber immer funktionieren:

 value = lowByte  + highByte  * 256;

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.