Bitshift funktioniert auf AVR nicht - warum?

Gast #5172594
Lesenswert?

Hallo zusammen,

ich habe folgendes Szenario:

Auf einem Atmega8 wird via I2C ein ByteArray mit 4 Bytes empfangen und 
soll in ein Int32_t umgewandelt werden.

Anbei der relevante Codeausschnitt:
1
// Variablendeklaration:
2
volatile uint8_t i2cdata[4] // Das ist der I2C-Empfangspuffer
3
int32_t Variable = 0;       // Das ist die letztendliche Variable
4

5
// Umwandlung in Int32:
6
Variable = i2cdata[0] | ((uint32_t)i2cdata[1] << 8) | ((uint32_t)i2cdata[2] << 16) | ((uint32_t)i2cdata[3] << 24);

Wenn der I2C-Partner (RPi3, I2C-Master) nun die Zahl 291991 auf den Bus 
schreibt, lese ich - mithilfe des UART - die folgenden Variablenwerte 
auf dem µC:
1
i2cdata[0]: 10010111
2
i2cdata[1]: 01110100
3
i2cdata[2]: 00000100
4
i2cdata[3]: 00000000
5
Variable: 29847

Das Byte-Array wird korrekt empfangen, aber das shiften funktioniert 
nicht.
Die obersten 16 Bit fehlen.
Auf dem PC funktioniert der gleiche Code in C++ mit Visual Studio, auf 
dem AVR nicht.

Was mache ich falsch? Was habe ich übersehen? Ich komme nicht weiter.

LG Sebastian
Gast #5172634
Lesenswert?

Markus schrieb:
> Du musst rechts vom Gleichheitszeichen casten....
Was genau meinst Du damit?

Rufus Τ. F. schrieb:
> Der fehlende cast für i2cdata[0] dürfte das Problem sein.
Das hat leider nicht zum Erfolg geführt. Immer noch dieselbe Ausgabe.

holger schrieb:
> Zeig mal die Zeile wo du die Variable per UART ausgiebst.
1
char String[10];
2
itoa(Variable, String, 10);
3
uart_puts("Variable: ");
4
uart_puts(String);
Gast #5172644
Lesenswert?

Aaaaaahhhhh, danke für den Denkanstoß!

Es muss natürlich LTOA in der Ausgabe sein und nicht ITOA...

Vielen Dank für die schnelle Hilfe, und das Entfernen des Brettes vor 
meinem Kopf. :-)

LG
Sebastian
Gast #5172691
Lesenswert?

Hallo Sebastian,

bin bei embedded C nicht so ganz zu Hause, deswegen weiß ich nicht ob 
die Entwicklungsumgebung Unions kennt,aber wenn es geht, dann würde ich 
ein Union bilden mit i2cdata[4] und int32_t Variable. Setzt man erst den 
i2cdata[4] Array und liest man die int32_t Variable aus dann hat man 
gleich den Wert ohne Schiebereien. Natürlich muss man dazu 
berücksichtigen ob little Endian oder big Endian Darstellung.
Gast #5172705
Lesenswert?

Karoly Ozvald schrieb:
> bin bei embedded C nicht so ganz zu Hause, deswegen weiß ich nicht ob
> die Entwicklungsumgebung Unions kennt,aber wenn es geht, dann würde ich
> ein Union bilden mit i2cdata[4] und int32_t Variable. Setzt man erst den
> i2cdata[4] Array und liest man die int32_t Variable aus dann hat man
> gleich den Wert ohne Schiebereien.
der schieben wird eh wegoptimiert.

> Natürlich muss man dazu
> berücksichtigen ob little Endian oder big Endian Darstellung.
und das es nicht zulässig ist unions so zu missbrauchen.

So wie er es gemacht hat es richtig.
Gast #5172730
Lesenswert?

type punning ist in (explizit) C erlaubt. Wobei die Frage ist, ob das 
passiert, was man erwartet. Das ist meistens nur dann der Fall, den 
zu/von einem char-Array gewandelt wird.

In C++ ist das des des nicht-aktiven Members UB!
Gast #5172741
Lesenswert?

Scott Meyers schrieb:
> type punning ist in (explizit) C erlaubt. Wobei die Frage ist, ob
> das
> passiert, was man erwartet. Das ist meistens nur dann der Fall, den
> zu/von einem char-Array gewandelt wird.
>
> In C++ ist das des des nicht-aktiven Members UB!

Typo: In C++ ist das Lesen des nicht-aktiven Members UB!
Gast #5172770
Lesenswert?

Peter II schrieb:
> weil man bei einer Union nur den Datentype lesen darf, den man
> geschrieben hat

Das weicht jetzt vom ursprünglichen Thema ab, aber:
Wenn es so ist wie du geschrieben hast, dann wozu eine Union, wenn ich 
auf die andere Variable nicht einmal lesend zugreifen darf? Union 
verwendet man wenn man auf einen Speicherbereich auf unterschiedliche 
Weise zugreifen möchte.

Die vielleicht einzige sinnvolle Anwendung einer Union ist die implizite 
Typumwandlung, und ist vielleicht ungewöhnlich aber ich sehe das nicht 
außerhalb vom C Standard, schließlich ist die Union nicht meine 
Erfindung.
Moderator Persönliche Seite #5172783
Lesenswert?

Karoly Ozvald schrieb:
> wozu eine Union

Um Speicherplatz zu sparen.  Wenn du in einem Objekt, abhängig von
anderen Umständen, wahlweise eine Ganzzahl, eine Gleitkommazahl oder
einen Zeiger auf einen Text hinterlegen können möchtest, dann müsstest
du ansonsten eine struct benutzen, die für alle drei Platz reserviert.
Wenn du aber sicher bist, dass nur eine der drei Varianten zu einem
Zeitpunkt gebraucht werden, dann kann man stattdessen deren Speicher
überlagern.

Hilft natürlich vor allem dann, wenn man davon viele hat, also als
Array angelegt.

Implizite Typumwandlung damit ist eher ein Nebeneffekt, bei dem noch
dazu in C alles implementierungsabhängig ist (und in C++, wie oben
gesagt, sogar undefiniertes Verhalten).  Portable Typumwandlung macht
man daher besser mit Schieben und Zusammenfügen.
Gast #5172786
Lesenswert?

Scott Meyers schrieb:
> In C++ ist das des des nicht-aktiven Members UB!

Das stimmt nicht so wirklich ... zumindest nicht allgemein für alle 
unions bzw. alle Zugriffe.
Aus dem c++17 draft (N4687 §12.2.24)
1
In a standard-layout union with an active member (12.3) of struct type T1,
2
it is permitted to read a non-static data member m of another union member of
3
struct type T2 provided m is part of the common initial sequence of T1 and T2;
4
the behavior is as if the corresponding member of T1 were nominated.
5
[ Example:
6
struct T1 { int a, b; };
7
struct T2 { int c; double d; };
8
union U { T1 t1; T2 t2; };
9
int f() {
10
U u = { { 1, 2 } }; // active member is t1
11
return u.t2.c; // OK, as if u.t1.a were nominated
12
}
13
— end example ]
#5172790
Lesenswert?

Hallo,

> Es muss natürlich LTOA in der Ausgabe sein und nicht ITOA...

Ja, klar das ist das Problem, aber warum funktioniert dann folgendes 
Programm auf dem PC (mit CodeBlocks als Entwicklungsumgebung) und itoa() 
statt ltoa()?
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <inttypes.h>
4

5
int main()
6
{
7
    uint8_t i2cdata[4];
8
    int32_t Variable = 0;
9

10
    char String[10];
11

12
    i2cdata[0] = 151;
13
    i2cdata[1] = 116;
14
    i2cdata[2] = 4;
15
    i2cdata[3] = 0;
16

17
    Variable = i2cdata[0] 
18
             | ((uint32_t)i2cdata[1] << 8) 
19
             | ((uint32_t)i2cdata[2] << 16)
20
             | ((uint32_t)i2cdata[3] << 24);
21

22
    itoa(Variable, String, 10);
23

24
    puts("Variable: ");
25
    puts(String);
26

27
    return 0;
28
}
Offensichtlich verhält sich itoa() in der avr-lib anders als in der 
Libary für Windows. Warum?
Gast #5172810
Lesenswert?

Bei Assembler kann man höchstens den Fehler machen, dass man nicht

lsl
rol

schreibt, sondern als Anfängerfehler

lsl
lsl

Ich glaube das wars mit Fehlerquellen.

Wenn man das mal im Hinterkopf hat und dann sich die Beiträge hier über 
mir durchliest, muss man sich ja schon an den Kopf fassen, was man alles 
wissen muss, nur um in C ein Shifting korrekt auszuführen.
Gast #5172818
Lesenswert?

Timmy schrieb:
> Bei Assembler kann man höchstens den Fehler machen, dass man nicht
>
> lsl
> rol
>
> schreibt, sondern als Anfängerfehler
>
> lsl
> lsl

Das passiert aber nur, wenn man sich nie mit den dahinter stehenden 
Abläufen in den Registern beschäftigt, sondern blind irgendwelche 
Befehle eintippt.
#5172824
Lesenswert?

Timmy schrieb:
> Bei Assembler kann man höchstens den Fehler machen, dass man nicht
>
> lsl
> rol
>
> schreibt, sondern als Anfängerfehler
>
> lsl
> lsl
>
> Ich glaube das wars mit Fehlerquellen.
>
> Wenn man das mal im Hinterkopf hat und dann sich die Beiträge hier über
> mir durchliest, muss man sich ja schon an den Kopf fassen, was man alles
> wissen muss, nur um in C ein Shifting korrekt auszuführen.

Echt?
Der Fehler lag aber in der Benutzung von itoa() statt ltoa().
Wie schreibt man die beiden nochmal in Assembler?
Gast #5172835
Lesenswert?

mh schrieb:
> Scott Meyers schrieb:
>> In C++ ist das des des nicht-aktiven Members UB!
>
> Das stimmt nicht so wirklich ... zumindest nicht allgemein für alle
> unions bzw. alle Zugriffe.
> Aus dem c++17 draft (N4687 §12.2.24)
...
> [ Example:
> struct T1 { int a, b; };
> struct T2 { int c; double d; };
> union U { T1 t1; T2 t2; };
> int f() {
> U u = { { 1, 2 } }; // active member is t1
> return u.t2.c; // OK, as if u.t1.a were nominated
> }
> — end example ]

Das stimmt zwar, aber dabei geht es um die "common inital sequence". In 
dem Beispiel "wandelt" man also das int member a in das int member c. 
Und das ist nicht das, was hier ursprünglich mal angedacht war und 
allgemein als "type punning" bezeichnet wird. Um das zu erreichen, müßte 
man ausserhalb der common initial sequence zugreifen, und das ist eben 
in C++ UB.
Moderator #5172878
Lesenswert?

Timmy schrieb:
> Bei Assembler kann man höchstens den Fehler machen, dass man nicht
>
> lsl
> rol
>
> schreibt, sondern als Anfängerfehler
>
> lsl
> lsl
>
> Ich glaube das wars mit Fehlerquellen.

Carl D. schrieb:
> Echt?
> Der Fehler lag aber in der Benutzung von itoa() statt ltoa().
> Wie schreibt man die beiden nochmal in Assembler?

Und noch eins: Wenn man einen Ausdruck wie diesen hier

1
((uint32_t)i2cdata[3] << 24)

auf dem AVR tatsächlich mittels LSL und ROL implementiert, sollte man
überlegen, ob nicht besser in einer Hochsprache programmiert.
Gast #5173154
Lesenswert?

Carl D. schrieb:
> Echt?
> Der Fehler lag aber in der Benutzung von itoa() statt ltoa().
> Wie schreibt man die beiden nochmal in Assembler?

Ganz einfach. Für 16 bit Werte schreibt man

rcall bin16dec

und für 32 Bit Werte schreibt man

rcall bin32dec

es gibt noch

rcall bin64dec

aber so hohe Zahlen habe ich auf dem AVR noch nie benötigt. Steht alles 
in der Doku.
Gast #5173305
Lesenswert?

Genauso wenig wie ein itoa(). Aber hier ging es letztendlich auch um 
Diskussionen über Speicherverbrauch, wo dann die Raterei losging, wie es 
C intern wohl umsetzt und wenn ich dann mir sowieso das ASM Listing 
ausgeben lasse, also eh wieder letztendlich bei Assembler gelandet bin, 
warum dann nicht gleich da bleiben?

Aber es stimmt schon, dass es wirklich hilfreich gewesen wäre, wenn es 
sowas wie eine grosse Lib für den AVR gäbe, wo all die schönen Sachen, 
die C bietet, schon mit drin sind. Bei C ist es ja auch nur eine Lib. 
Wie gesagt bietet itoa() kein Prozessor direkt an.
Moderator Persönliche Seite #5173318
Lesenswert?

Timmy schrieb:
> Aber es stimmt schon, dass es wirklich hilfreich gewesen wäre, wenn es
> sowas wie eine grosse Lib für den AVR gäbe, wo all die schönen Sachen,
> die C bietet, schon mit drin sind.

Dann hätte Atmel ja einen ordentlichen Assembler mit Linker liefern
müssen … oder gleich die GNU-Tools benutzen. Da wäre dann ja auch
gleich noch ein C-Compiler als add-on mit dabei. >:-)

> wenn ich dann mir sowieso das ASM Listing ausgeben lasse, also eh wieder
> letztendlich bei Assembler gelandet bin, warum dann nicht gleich da
> bleiben?

Weil es eben ein himmelweiter Unterschied ist, ob ich mal punktuell
auf ein Stück (Dis-)Assemblercode draufschauen möchte oder ob ich mir
das alles von der Pike auf zu Fuß abstoppeln muss.

Wenn es so einfach und effizient wäre, in Assembler zu programmieren,
würde wohl die Masse an Controllern in der Industrie durchaus auch
heute noch damit programmiert. Wird sie aber in der Praxis dann doch
nicht. Entgegen der landläufigen Meinung einiger liegt das keineswegs
daran, dass die Ingenieure der Industrie einfach nur nicht genial
genug dafür wären …
Gast #5173326
Lesenswert?

Du verfällst in Verallgemeinerungen. Wenn der eigene Job ist, eher 
kleine Aufgaben zu bewältigen, wie Motorsteuerungen oder 
Sicherheitsschaltungen, wo Einfachheit geradezu vorgeschrieben ist, da 
ist Assembler einfach die bessere Wahl, eben weil es keine bösen 
Überraschungen gibt, weil man genau weiss, was passiert.

Für Programme wie
IF spicmd == "open"
AND PinX == HIGH
AND adcvalue > 500
AND PinY == LOW
THEN PinY = LOW

braucht man kein C. Ich würde aber auch kein neues Mobile OS in 
Assembler schreiben wollen und auch keine Assemblerprogramme für einen 
normalen PC, weil es einfach sinnfrei ist.
Gast #5173373
Lesenswert?

Timmy schrieb:
> Wenn der eigene Job ist, eher
> kleine Aufgaben zu bewältigen, wie Motorsteuerungen oder
> Sicherheitsschaltungen, wo Einfachheit geradezu vorgeschrieben ist, da
> ist Assembler einfach die bessere Wahl, eben weil es keine bösen
> Überraschungen gibt, weil man genau weiss, was passiert.

Es sei denn man hat irgendwo ein "pop" vergessen, dann viel Spaß beim 
Suchen. Das passiert in C eher nicht. Motorsteuerungen wie z.B. bei 
PKW-Steuergeräten oder große Synchronmaschinen werden hauptsächlich in 
Matlab und C geschrieben. Kein Hersteller würde sich bei so einem 
Produkt mithilfe von Assembler auf eine einzige Prozessor-Architektur 
festnageln.

Btw, wenn man mit -Wconversion kompilieren würde, hätte es bei "itoa" 
eine Warnung gegeben.

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