Forum: Mikrocontroller und Digitale Elektronik Bitshift funktioniert auf AVR nicht - warum?


von Sebastian (Gast)


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

von Markus (Gast)


Lesenswert?

Du musst rechts vom Gleichheitszeichen casten....

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Der fehlende cast für i2cdata[0] dürfte das Problem sein.

von holger (Gast)


Lesenswert?

>mithilfe des UART - die folgenden Variablenwerte
>auf dem µC:
>
>Variable: 29847

Zeig mal die Zeile wo du die Variable per UART ausgiebst.

von Peter II (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Der fehlende cast für i2cdata[0] dürfte das Problem sein.

sicher?

Bei Addition und Multiplikation wird doch auch immer mit dem größte 
Datentype der beiden werte gerechnet. Warum sollte da bei | anders sein?

Ich vermute, das die Variable falsch ausgeben wird.

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Die ausgegebene Zahl ist nur 16 bit breit.

MfG

von Sebastian (Gast)


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);

von holger (Gast)


Lesenswert?

>Ich vermute, das die Variable falsch ausgeben wird.

Ich auch. %ld statt %d;)

von Sebastian (Gast)


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

von Karoly Ozvald (Gast)


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.

von Peter II (Gast)


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.

von Karoly Ozvald (Gast)


Lesenswert?

Peter II schrieb:
> und das es nicht zulässig ist unions so zu missbrauchen.

Wieso ist das ein "Missbrauch"?

von Peter II (Gast)


Lesenswert?

Karoly Ozvald schrieb:
> Wieso ist das ein "Missbrauch"?

weil man bei einer Union nur den Datentype lesen darf, den man 
geschrieben hat. Das es meist auch so funktionierst ist nicht durch den 
Standard abgedeckt.

von Scott Meyers (Gast)


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!

von Scott Meyers (Gast)


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!

von Karoly Ozvald (Gast)


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.

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


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.

von mh (Gast)


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 ]

von Roland F. (rhf)


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?

von asdfasd (Gast)


Lesenswert?

> Offensichtlich verhält sich itoa() in der avr-lib anders als
> in der Libary für Windows.

Weil unter Windows ein int 32bit ist und unter AVR nur 16bit.

von Roland F. (rhf)


Lesenswert?

> Weil unter Windows ein int 32bit ist und unter AVR nur 16bit.

Stimmt, daran habe ich gar nicht gedacht.

rhf

von Timmy (Gast)


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.

von Wolfgang (Gast)


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.

von Carl D. (jcw2)


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?

von Scott Meyers (Gast)


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.

von Yalu X. (yalu) (Moderator)


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.

: Bearbeitet durch Moderator
von Timmy (Gast)


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.

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


Lesenswert?

Ah ja, und so ein bin16dec hat dein AVR gleich eingebaut? ;-)

Auch das würde einen natürlich nicht davor feien, die falsche Routine
aufzurufen, und nicht mehr und nicht weniger ist ja dem TE hier als
Fehler unterlaufen.

von Timmy (Gast)


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.

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


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 …

von Timmy (Gast)


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.

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


Lesenswert?

Einen ATtiny10 programmiere ich auch in Assembler. :) Aber in der
Regel werde ich dafür bezahlt, deutlich komplexere Gebilde zu schaffen
als das, und da möchte ein Kunde sicher nicht, dass ich ihm ein
Lebenswerk präsentiere.

Bei der von dir genannten Steuerung würde man dann wohl auch kein
bin16dec benötigen …

von Michael D. (nospam2000)


Lesenswert?

Timmy schrieb:
> Für Programme wie
> IF spicmd == "open"
> AND PinX == HIGH
> AND adcvalue > 500
> AND PinY == LOW
> THEN PinY = LOW
>
> braucht man kein C

Dafür braucht man auch kein Assembler, da der Code überhaupt keine 
Funktion ausführt und von einem guten Optimizer komplett entfernt 
wird...

Nur wenn PinY == LOW ist wird PinY = LOW gesetzt

  Michael

von Dr. Sommer (Gast)


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.

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.