Umwandlung von zwei uint8_t in uint16_t erzeugt falschen Code?

Gast #3010114
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?
Moderator Persönliche Seite #3010148
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.
Gast #3010331
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.
Gast #3010412
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!
Persönliche Seite #3021869
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.
Gast #3063802
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;

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren