Hallo !
Ich habe folgende Frage :
mit welchem Programmcode ( in C ) kann ich einen beschriebenen uint16_t
in 2 uint8_t umwandeln ?
außerdem besteht dann die Frage : wie kann ich dann die zwei einzelnen
bytes ansprechen ? ( ich bräuchte praktisch das high byte und das
low-byte vom integer)
Danke im Voraus,
Jakov
Andere Idee: (weiss aber nicht, wie man das beim AVR umsetzen kann)
Man definiert eine 16-Bit Variable und legt die fest auf bestimmte
RAM-Adressen. Danach definiert man 2 8-Bit Variablen und legt die auf
die selben (!) 8-Bit Adressen. Der Compiler spuckt zwar eine Warning
aus, aber die Aufteilung ist sehr elegant. Schreibe den 16-Bit Wert und
lese 2x die 8-Bit Werte. Beim 8051 mit RIDE sieht das dann so aus:
data at 0x10 unsigned int Ergebnis; // belegt Adressen 0x10 und 0x11
data at 0x10 unsigned char HighByte, LowByte; // Reihenfolge muss man
bei seinem Compiler testen...
// jetzt kann man zum Beispiel die Timer-Reloads so machen:
Ergebnis = (65536-Zeit_in_us); // nur die Subtraktion wird gerechnet
TL0 = LowByte; // dauert nur 2us
TH0 = HighByte; // und es wird nix gerechnet
tschuessle
Bernhard
Bernhard Spitzer schrieb:> (weiss aber nicht, wie man das beim AVR umsetzen kann)
Wie kann man das in irgendeinem Controller in C umsetzen? Eine
Möglichkeit wäre, dass man Zeiger erzeugt und denen die entsprechenden
Adresse zuweist. Da ist die Gefahr aber groß, dass der Compiler die
Adresse überschreibt.
>data at 0x10 unsigned int Ergebnis;
Ich würde zwar von mir sagen, dass ich C gut kann, aber 'data' und 'at'
ist mir da nie untergekommen. Was soll das machen?
oder so Ähnlich. Sowas habe ich gerade mit XCode ausprobiert und es
funktioniert. Ist ziemlich getrickst, aber wahrscheinlich schneller als
meine erste Variante.
Blub schrieb:> Naja, dann lieber gleich eine Union, die ist für sowas gedacht und der> Compiler meckert nicht.
Und wenn du dann beim Portieren des mal die Endianess gewechselt wird?
Das Einzige was gleichermaßen auf Little und Big Endian funktionieren
wird ist:
Dussel schrieb:> uint8_t var1=(uint8_t)(var16&0xFF);> uint8_t var2=(uint8_t)((var16>>8)&0xFF);
Gruß Oliver
Dussel schrieb:> uint16_t *zvar16=&var16;> uint8_t *zvar1=(uint8_t *)var16;> uint8_t *zvar2=(uint8_t *)(var16+1);> var1=*zvar1;> var2=*zvar2;
Das ist falsch. Du solltest schon richtigen Code posten. So ein Müll
nützt Niemandem.
So könnte man es über deine Idee auf Little Endian machen:
Oliver J. schrieb:> So könnte man es über deine Idee auf Little Endian machen:
Erstens ist es nicht meine Idee, sondern die von Bernhard Spitzer. Ich
habe sie nur Standard-C tauglich aufgeschrieben.
Oliver J. schrieb:> Das ist falsch. Du solltest schon richtigen Code posten. So ein Müll> nützt Niemandem.
Zweitens: Dann schreib mal, was daran falsch ist, wenn es so
funktioniert.
Drittens: Was ist in deinem Beispiel anders, als in meinem, außer dass
ich es ausführlicher mache?
Dussel schrieb:> Oliver J. schrieb:>> Das ist falsch. Du solltest schon richtigen Code posten. So ein Müll>> nützt Niemandem.> Zweitens: Dann schreib mal, was daran falsch ist, wenn es so> funktioniert.
Das funktioniert so nicht. zvar16 ist ein pointer vom typ uint16_t *,
dieser + 1 zeigt nicht auf das nächste Byte.
uint8_t*zvar1=(uint8_t*)zvar16;//Natürlich nicht var16
3
uint8_t*zvar2=(uint8_t*)(zvar16+1);
4
var1=*zvar1;
5
var2=*zvar2;
Mein Fehler. In der zweiten und dritten Zeile soll es natürlich zvar16
heißen. Wenn ich nicht noch einen Schreibfehler drinhabe, müsste es aber
so funktionieren.
Mit dem +1 funktioniert es bei mir. AVRStudio habe ich nicht hier, aber
mit XCode geht es, wenn ich statt uint16_t ein unsigned short int und
statt uint8_t ein unsigned char nehme.
Den funktionierenden Code habe ich mal angehängt. Vielleicht habe ich
mich beim Abschreiben ja nochmal vertan.
Oliver J. schrieb:> Mit zvar16+1 erwischst du niemals das High-Byte.
Das kann sein. C hat so viele 'Gemeinheiten'. Trotzdem wüsste ich noch
gerne, warum es funktioniert.
Dussel schrieb:> Trotzdem wüsste ich noch> gerne, warum es funktioniert.
Ich kann mir beim besten Willen nicht vorstellen, dass es mit deinem
Code wirklich funktioniert.
Gruß Oliver
Tut mir leid für das Durcheinander, aber jetzt habe ich es: Die Klammern
um das zvar16+1 sind zu viel. Die hatte ich in der ersten Version
fälschlicherweise drumgemacht und dann übernommen.
Danke.
Wenigstens war mein gedachter Ansatz richtig. Im Programm habe ich es ja
auch automatisch richtig gemacht, nur dann falsch übertragen.
Blub schrieb:> Naja, dann lieber gleich eine Union, die ist für sowas gedacht und der> Compiler meckert nicht.
Nein, es ist in Standard-C ausdrücklick verboten, einen anderen
Eintrag aus einer Union zu lesen, als derjenige, der zuletzt einen Wert
erhalten hat.
Die Zeigerrechnerei ist insofern fatal, als dass Zeiger keine Adressen
im Speicher sind. Das predige ich etliche Jahre und kriege dafür immer
nur müdes Lächeln. Und zwar meistens genau so lange, bis der Betroffene
auf die Fresse gefallen ist und stundenlang debuggt hat.
Es gibt außer Big- und Little-Endian dann auch noch Middle-Endian.
Könnte außerdem mit Alignment einen Unfall geben.
Der richtige Weg ist ja schon genannt worden: Simple Arithmetik mit
Schieben. Das optimiert jeder brauchbare Compiler ohnehin auf
irgendeinen Speicherversatz. Und das funktioniert sogar portabel.
Oliver J. schrieb:> Ich kann mir beim besten Willen nicht vorstellen, dass es mit deinem> Code wirklich funktioniert.
Probier es doch aus. Du hast doch sicher einen C(++)-Compiler rumliegen.
Für C musst du eben iostream.h einfügen und die Dateiendung in .C
ändern.
Dussel schrieb:> Oliver J. schrieb:>> Ich kann mir beim besten Willen nicht vorstellen, dass es mit deinem>> Code wirklich funktioniert.> Probier es doch aus. Du hast doch sicher einen C(++)-Compiler rumliegen.> Für C musst du eben iostream.h einfügen und die Dateiendung in .C> ändern.lol
Sven P. schrieb:> Blub schrieb:>> Naja, dann lieber gleich eine Union, die ist für sowas gedacht und der>> Compiler meckert nicht.> Nein, es ist in Standard-C ausdrücklick verboten, einen /anderen/> Eintrag aus einer Union zu lesen, als derjenige, der zuletzt einen Wert> erhalten hat.
Aber was ist denn dann die Anwendung für eine Union?
> Die Zeigerrechnerei ist insofern fatal, als dass Zeiger keine Adressen> im Speicher sind. Das predige ich etliche Jahre und kriege dafür immer> nur müdes Lächeln.
Das interessiert mich jetzt aber doch: Was meinst Du damit und worauf
willst Du damit hinaus?
Dosmo schrieb:> Das interessiert mich jetzt aber doch: Was meinst Du damit und worauf> willst Du damit hinaus?
Zeiger enthalten im Prinzip eine Adresse. Wenn man aber Integer-Werte zu
ihnen hinzu addiert oder von ihnen subtrahiert, dann ist der neue Wert
des Zeigers von der Breite des Datentyps abhängig, auf den der Zeiger
zeigt.
Bsp:
> Blub schrieb:>> Naja, dann lieber gleich eine Union, die ist für sowas gedacht und der>> Compiler meckert nicht.> Nein, es ist in Standard-C ausdrücklick verboten, einen /anderen/> Eintrag aus einer Union zu lesen, als derjenige, der zuletzt einen Wert> erhalten hat.
Na denn erzähl mir mal wie man sonst an die Bits eines floats rankommt
um sie zB über I2C zu senden.
Martin Wende schrieb:> Na denn erzähl mir mal wie man sonst an die Bits eines floats rankommt> um sie zB über I2C zu senden.
In Standard-C theoretisch garnicht.
Wenn du allerdings solcherlei Low-Level-Programmierung anstellen
möchtest, musst du ja ohnehin in das Handbuch deines Compilers gucken.
Insofern erübrigt sich die Portabilität dann. Das geht soweit, dass z.B.
im C-Standard von 1999 nichtmal spezifiziert ist, wie genau ein 'long
double' auszusehen hat.
Bei Unions kommt erschwerend dazu, dass es bei Bitfeldern keinerlei
Garantie hast, wie die Bits im Speicher angeordnet sind (Endianness,
Überlappung). Bei Strukturen kann am Ende aufgefüllt werden. Um das zu
umgehen, kann man z.B. ein Compiler-spezifisches 'packed'-Attribut
mitgeben. Da wären wir dann wieder beim Compiler-Handbuch...
Thomas:
Grammatikalisch in die Hose gegangen, da hast du Recht. Also nochmal:
>> Nein, es ist in Standard-C ausdrücklick verboten, einen /anderen/>> Eintrag aus einer Union zu lesen, als denjenigen, der zuletzt einen Wert>> erhalten hat.
Ansonsten habe ich natürlich alles falsch verstanden, meine Erfahrungen
sind Einbildung und du bist der einzig wahre Heilsbringer. In Ordnung?
Gut.
Dosmo:
Ein Beispiel hast du gesehen, nämlich dass Feldweise adressiert wird.
Wenn ein Feld vier Zeichen lang ist, rückt man im Speicher halt vier
Zeichen weiter, wenn man '1' zum Zeiger addiert.
Weiterhin ist die Zuordnung zwischen Zeiger und Ganzzahl nicht
spezifiziert. Es ist zwar erlaubt, einen Zeiger in eine Ganzzahl zu
wandeln und wieder zurück, es ist aber nicht festgelegt, dass diese
Ganzzahl in irgendeinen der Integer-Typen passen muss (ISO/IEC 9899:TC2
6.3.2.3 Abs. 6)!
Wenn du eine Ganzzahl in einen Zeiger umwandelst, muss dieser Zeiger
weder richtig ausgerichtet sein, noch auf ein gültiges Objekt zeigen.
Das insbesondere ist relevant, wenn man einen uint8_t* auf einen
uint16_t* umbiegt. Möglicherweise zeigt der dann nämlich garnicht mehr
auf den gewünschten uint16_t.
Auch da hilft das Compiler-Handbuch weiter.
Im übrigen, und das habe ich ja angedeutet, erschließt sich mir der Sinn
hinter der Pfriemelei nicht. Der arithmetische Weg mit dem
Schiebe-Operator ist gut lesbar, portabel und wird praktisch von jedem
Compiler optimiert.
Sven P. schrieb:> Im übrigen, und das habe ich ja angedeutet, erschließt sich mir der Sinn> hinter der Pfriemelei nicht. Der arithmetische Weg mit dem> Schiebe-Operator ist gut lesbar, portabel und wird praktisch von jedem> Compiler optimiert.
Den habe ich ja direkt geschrieben. Danach ging es nur noch um die
philosophische Überlegung, ob man es auch anders machen kann.
Sven P. schrieb:> Der arithmetische Weg mit dem Schiebe-Operator ist gut lesbar,> portabel und wird praktisch von jedem Compiler optimiert.
Und selbst wenn es nicht zu 100% optimal übersetzt wird und ne
überflüssige Instruktion bleibt, ist es allemal besser als die Adresse
einer lokalen Variablen zu nehemn!
Johann L. schrieb:> Und selbst wenn es nicht zu 100% optimal übersetzt wird und ne> überflüssige Instruktion bleibt, ist es allemal besser als die Adresse> einer lokalen Variablen zu nehemn!
Bei dir weiß ich ja, dass du ein Fachmann bist (sic) -- betonst du
absichtlich die lokale Variable?
Falls dem so ist, dann weil deren Adresse nicht konstant ist und es
dadurch zu Zeigerrechnerei kommt, wohingegen bei einer globalen Variable
meistens direkt lds/sts erzeugt werden kann?
Dussel schrieb:> Danach ging es nur noch um die> philosophische Überlegung, ob man es auch anders machen kann.
Wie kommt es, dass es Dir notwendig erscheint, die "Überlegung" als eine
"philosophische" zu bezeichnen, Dussel?
Sven P. schrieb:>> Dosmo:> Ein Beispiel hast du gesehen, nämlich dass Feldweise adressiert wird.> Wenn ein Feld vier Zeichen lang ist, rückt man im Speicher halt vier> Zeichen weiter, wenn man '1' zum Zeiger addiert.
Okay, das meinst Du.
Dazu am Rande:
Eine Sache, über die ich mal gestolpert bin, war auf dem MSP430 ein
UART-Empfangs-Ringpuffer, aus dem ich ein uint16_t auslesen wollte. Da
hab ich auch die Adresse des Datums feucht-fröhlich als uint16_t*
gecastet, ausgelesen und prompt folgt ein Address-Trap!
Was war passiert? Das Telegram lag an einer ungerade Adresse im Puffer,
daher war das auszulesende uint16 auch an einer ungeraden Adresse, und
auf diese kann man (beim MSP430 und vielen anderen) keinen 16Bit-Zugriff
machen.
Es half nur byteweise lesen und Bits schieben.
Dosmo schrieb:> [...]
Schau doch mal im Assembler-Listing, was dann tatsächlich draus wurde.
Sicherlich auch wieder nur Speicherversatz, also ein Byte-Zugriff oder
ein Byte-Swap im Arbeitsregister.
Aber du siehst ja auch, manche Leute wollen das einfach nicht verstehen.
Sven P. schrieb:> Johann L. schrieb:>> Und selbst wenn es nicht zu 100% optimal übersetzt wird und ne>> überflüssige Instruktion bleibt, ist es allemal besser als die Adresse>> einer lokalen Variablen zu nehemn!> Bei dir weiß ich ja, dass du ein Fachmann bist (sic) -- betonst du> absichtlich die lokale Variable?>> Falls dem so ist, dann weil deren Adresse nicht konstant ist und es> dadurch zu Zeigerrechnerei kommt, wohingegen bei einer globalen Variable> meistens direkt lds/sts erzeugt werden kann?
Vielleicht sollte man an der Stelle noch folgendes erwähnen:
Es gibt Architekturen, bei denen die CPU-Register nicht im
Speicheradressraum liegen, d.h. nicht per Adresse/Zeiger (bzw.
load/store) zugreifbar sind. Lokale Variablen liegen aber gerne in
Registern. Wenn man eine lokale Variablen dann mit
Adresse/Register-Zugriff benutzt, muß diese dazu dann auf den Stack
ausgelagert werden, was den Code auch nicht gerade effizienter macht.
Dosmo schrieb:> Vielleicht sollte man an der Stelle noch folgendes erwähnen:> Es gibt Architekturen, bei denen die CPU-Register nicht im> Speicheradressraum liegen, d.h. nicht per Adresse/Zeiger (bzw.> load/store) zugreifbar sind. Lokale Variablen liegen aber gerne in> Registern. Wenn man eine lokale Variablen dann mit> Adresse/Register-Zugriff benutzt, muß diese dazu dann auf den Stack> ausgelagert werden, was den Code auch nicht gerade effizienter macht.
Ja, das ist im C-Standard berücksichtigt, sodass man von einer
Variablen, die mit 'register' vereinbart wurde, keine Adresse erhalten
kann.
An Johann L. dachte ich dann, weil er knietief im AVR-GCC steckt.
Dussel schrieb:> uint16_t *zvar16=&var16;> uint8_t *zvar1=(uint8_t *)var16;> uint8_t *zvar2=(uint8_t *)(var16+1);> var1=*zvar1;> var2=*zvar2;
und wenn du dir deinen code 3 monate später anschaust, weisst du sofort
was du da geamcht hast? so einfach wie möglich, so kompliziert wie
nötig...
Dussel-Fänger schrieb:> Dussel schrieb:>> Danach ging es nur noch um die>> philosophische Überlegung, ob man es auch anders machen kann.>> Wie kommt es, dass es Dir notwendig erscheint, die "Überlegung" als eine> "philosophische" zu bezeichnen, Dussel?
Philosophisch als Ausdruck dafür, dass es nicht um die praktische
Anwendung geht, sondern nur um das 'wissen Wollen'. Was natürlich auch
nicht uninteressant ist.
funky schrieb:> Dussel schrieb:>> uint16_t *zvar16=&var16;>> uint8_t *zvar1=(uint8_t *)var16;>> uint8_t *zvar2=(uint8_t *)(var16+1);>> var1=*zvar1;>> var2=*zvar2;>> und wenn du dir deinen code 3 monate später anschaust, weisst du sofort> was du da geamcht hast? so einfach wie möglich, so kompliziert wie> nötig...
Der Code ist ja sowieso falsch, aber ich denke, dass ich mir die
Funktion des Codes rekonstruieren könnte. Davon abgesehen würde ich
ausnahmsweise mal einen Kommentar setzen, wenn ich sowas verwenden
würde…
Dussel schrieb:> Dussel-Fänger schrieb:>>> Dussel schrieb:>>> Danach ging es nur noch um die>>> philosophische Überlegung, ob man es auch anders machen kann.>>> Wie kommt es, dass es Dir notwendig erscheint, die "Überlegung" als eine>> "philosophische" zu bezeichnen, Dussel?> Philosophisch als Ausdruck dafür, dass es nicht um die praktische> Anwendung geht, sondern nur um das 'wissen Wollen'. Was natürlich auch> nicht uninteressant ist.
In diesem Zusammenhang ist die Verwendung des Attributs "philosophisch"
gänzlich unangebracht, ja sogar falsch.
Avanti Dilettanti, Dussel!
Tut mir leid, ich verstehe kein Italienisch.
Davon abgesehen gibt es die umgangssprachliche Bezeichnung philosophisch
für 'einfach nur des Wissens wegen' oder 'aus der Freude am Wissen'.
Jetzt übersetze die Wörter, aus denen Philosophie gebildet ist noch und
du wirst feststellen, dass man das hier durchaus gebrauchen kann.
Davon abgesehen: Wenn es dir solche (geistigen) Schmerzen bereitet,
kannst du es auf deine Verantwortung auch durch ein beliebiges anderes
Wort ersetzen.
Dosmo schrieb:> Sven P. schrieb:>> Johann L. schrieb:>>> Und selbst wenn es nicht zu 100% optimal übersetzt wird und ne>>> überflüssige Instruktion bleibt, ist es allemal besser als die Adresse>>> einer lokalen Variablen zu nehemn!>> Bei dir weiß ich ja, dass du ein Fachmann bist (sic) -- betonst du>> absichtlich die lokale Variable?Lokal ist eigentlich nicht zutreffend. Es geht um auto-Variablen bzw.
um solche, die nicht im Static Storage liegen.
In den meisten Anwendungen ist lokal Deckungsgleich mit auto, aber
dass ist natürlich nicht zwingend.
>> Falls dem so ist, dann weil deren Adresse nicht konstant ist und es>> dadurch zu Zeigerrechnerei kommt, wohingegen bei einer globalen Variable>> meistens direkt lds/sts erzeugt werden kann?
Über Adressen von Variablen im Static Storage weiß der Compiler, daß sie
unveränderlich sind. Zur Adressberechnung kann er also — bei AVR —
einfach LDI Instruktionen verwenden. Das geht, obwohl er selbst die
Adresse nicht kennt: Die Adresse wird erst vom Linker/Locator
festgelegt.
1
voidput(char*);
2
3
voidfoo_static(void)
4
{
5
staticcharx[]="AB";
6
put(x+1);
7
}
kann von avr-gcc also so übersetzt werden:
1
foo_static:
2
ldi r24,lo8(x.1327+1)
3
ldi r25,hi8(x.1327+1)
4
rjmp put
Ist x hingegen auto:
1
voidfoo_auto(void)
2
{
3
charx[]="AB";
4
put(x+1);
5
}
dann sieht der Code etwas anders aus:
1
foo_auto:
2
push r28
3
push r29
4
rcall .
5
push __zero_reg__
6
in r28,__SP_L__
7
in r29,__SP_H__
8
ldi r24,lo8(65)
9
ldi r25,lo8(66)
10
ldi r26,0
11
std Y+1,r24
12
std Y+2,r25
13
std Y+3,r26
14
movw r24,r28
15
adiw r24,2
16
rcall put
17
pop __tmp_reg__
18
pop __tmp_reg__
19
pop __tmp_reg__
20
pop r29
21
pop r28
22
ret
Beide Funktionen wurden mit
% avr-gcc -S -Os -mmcu=atmega8
übersetzt, und es ist nix mehr daran zu optimieren bis auf eine unnötige
Instruktion im zweiten Listing.
Natürlich sind die beiden Funktionen nicht gleichwertig; die erste ist
zum Beispiel nicht reentrant. Aber das Beispiel zeigt, daß man sich mit
der Adressbildung von auto-Variablen schnell den Code aufpusten kann —
insbesondere, wenn es ohne Not geschieht wie oben; und dann
möglicherweise noch verstreut über den ganzen Code und abgesehen von
bereits genannten Problemen wie Endianess, Alignment, Aliasing Rules,
Type Punning, Wart- und Lesbarkeit, Portierbarkeit, etc.
Um die Adresse zu nehmen, muss der Compiler das Auto auf dem Stack (im
Frame der Funktion) anlegen.
Um es im Frame anlegen zu können, muss ein Frame angelegt werden.
Um einen Frame anzulegen, wird ein Framepointer benötigt.
Um einen Framepointer zu haben, muss er einen initialisieren.
Um einen initialisieren zu können, muss er die betreffenden Register
(hier Y) sichern und wieder herstellen.
Y ist im weiteren Verlauf der Funktion an den Framepointer gebunden und
kann nicht mehr für andere Zwecke verwendet werden. Bei AVR mit seiner
üppigen Ausstattung an Zeigerregistern ist das besonders ungünstig.
> Vielleicht sollte man an der Stelle noch folgendes erwähnen:> Es gibt Architekturen, bei denen die CPU-Register nicht im> Speicheradressraum liegen, d.h. nicht per Adresse/Zeiger (bzw.> load/store) zugreifbar sind.
Ein Compiler wird niemals nicht die Adressberechnung einer Variablen auf
die Speicheradresse eines GPRs abbilden oder umgekehrt.
Das wäre absoluter Hack und ein schwerer technischer Fehler.
GCC wird niemals auf ein Register über dessen Speicheradresse zugreifen
oder Adressberechnung eines Auto entsprechend abbilden.
Greift man dennoch über (*((unsigned char volatile*) 10)) auf R10 zu, so
weiß der Compiler nicht, daß es um einen Zugriff aus R10 geht. Das
Verhalten ist komplett undefiniert.
Dussel schrieb:> gibt es die umgangssprachliche Bezeichnung philosophisch> für 'einfach nur des Wissens wegen' oder 'aus der Freude am Wissen'.
Dieser angeblich umgangssprachliche Gebrauch von 'philosphisch' ist frei
erfunden.
Nomen est omen, Dussel!
Dussel-Ex schrieb:> Dussel schrieb:>> gibt es die umgangssprachliche Bezeichnung philosophisch>> für 'einfach nur des Wissens wegen' oder 'aus der Freude am Wissen'.>> Dieser angeblich umgangssprachliche Gebrauch von 'philosphisch' ist frei> erfunden.
Ich habe eine Quelle gefunden (es gibt mindestens eine), wo das auch so
verstanden wird. Trotzdem habe ich es falsch verstanden (nicht
erfunden). Also muss ich zugeben, dass ich es falsch benutzt habe.
Johann L. schrieb:> Sag doch einfach "aus Wissbegier" oder "aus rein akademischem> Interesse".
Ja, danke. Sowas meinte ich und hatte halt dafür 'philosophisch' im
Kopf. 'Aus Freundschaft zum Wissen'. Es sollte aber längst jedem klar
sein, was gemeint war.