Hallo,
bei der Programmierung eines AT tiny 26 tritt eine unerwartete
Speicherknappheit auf. Zur Behebung sollte ich folgendes Konstrukt in
eine union ändern:
1
// VHIT, (= vom Hirn ins Terminal, also ohne Test)
2
3
unsignedcharErgebnisH,ErgebnisL;
4
uintresult;
5
6
// Ergenbis H + L sind bereits mit Werten gefüllt,
7
8
result=ErgebnisH<<8+ErgebnisL;
-----------------------------------------------------
Mit der union würde ich 2 bytes sparen. ich habe aber keine Ahnung, wie
man damit umgeht.
Kann mir jemand das zeigen?
Gruss + Dank
Robert
[Mod.: [c]-Tags eingefügt.]
R. F. schrieb:> Mit der union würde ich 2 bytes sparen.
Nur, wenn du vorher analysiert hast, dass der Compiler das auch
tatsächlich getrennt ablegt.
Je nachdem, wie du die Variablen deklariert/definiert hast, kann es auch
gut sein, dass du rein gar nichts sparst, weil der Compiler schon genau
das tut, was du mit der Union erreichen möchtest.
Als erstes solltest du also anfangen, die Speicherfresser zu ermitteln.
Ansonsten:
Damit wird der Inhalt von Low- und High-Byte besser sichtbar.
Vielleicht postest Du mal den gesamten Code. Da lässtg sich vielleicht
noch etwas an anderen Stellen optimieren.
Jörg W. schrieb:> unportable!
Dessen sollte man sich halt bewusst sein. Je nach Endianness kommt bei
printf("%2x", combiner.word16);
verschiedenes raus.
mfg mf
PS @mods. Gibt es da nicht einen Artikel, den man verlinken kann?
Achim M. schrieb:> Dessen sollte man sich halt bewusst sein. Je nach Endianness kommt bei> printf("%2x", combiner.word16);> verschiedenes raus.
Die Endianess hat der TO vorgegeben. Er verwendet einen AVR.
Achim M. schrieb:> Und morgen löst er dasselbe Problem auf einer anderen Maschine und> wundert sich.
Wobei man ehrlich sein muss: eine big-endian-Maschine muss man dieser
Tage schon mächtig suchen. Klar können die diversen RISCs das oft von
Haus aus als Option; betrieben werden sie letztlich alle als little
endian. Ich habe hier noch 'ne UltraSPARC rumstehen, um sowas mal real
testen zu können, aber die Staubschicht da drauf ist schon ziemlich dick
:), aktuell ist meiner Erinnerung nach nur noch PowerPC relevant. (Es
gibt auch noch neuere UltraSPARCs, aber mit der Übernahme von Sun durch
Oracle ist der Laden ja ziemlich weit weg von Otto Normalnutzer.)
Achim M. schrieb:> Frank M. schrieb:>> Endianess hat der TO vorgegeben.>> Und morgen löst er dasselbe Problem auf einer anderen Maschine und> wundert sich.
Du meinst, der TO sucht sich als nächstes den µC mit Big Endianess und
dem kleinstmöglichen Speicher, damit er dasselbe Problem wieder hat? Ich
werde nie verstehen, warum man sich immer mit Speicherplatz quälen muss.
Der nächstgrößere µC hat meist den doppelten Speicher und kostet
unwesentlich mehr.
Aber der Vollständigkeit halber hier die portable Version:
1
union
2
{
3
struct
4
{
5
#if __BYTE_ORDER == __LITTLE_ENDIAN
6
unsignedcharl;
7
unsignedcharh;
8
#else
9
unsignedcharh;
10
unsignedcharl;
11
#endif
12
}ergebnis;
13
unsignedintresult;
14
}Ergebnis;
15
16
intmain()
17
{
18
Ergebnis.ergebnis.h=42;
19
Ergebnis.ergebnis.l=22;
20
21
printf("%d\n",Ergebnis.result);
22
}
P.S.
Bevor der nächste Bedenkenträger kommt:
Die Version ist nur auf Maschinen, wo "int" = 16 Bit ist, portabel. Man
sollte daher besser "uint16_t" statt "unsigned int" verwenden. Statt der
"unsigned char" kann man dann aus Symmetriegründen "uint8_t" nutzen.
Jörg W. schrieb:> eine big-endian-Maschine muss man dieser Tage schon mächtig suchen.
Gefunden! Ich halte eine Maschine mit umschaltbarer Endianness gerade in
der Hand 😉
Jörg W. schrieb:> aktuell ist meiner Erinnerung nach nur noch PowerPC relevant
Hast du einen fahrbaren Untersatz? Da gibt es reihenweise
Big-Endian-Maschinchen.
Nicht relevant? Ja, die Automobilindustrie in DE bald nicht mehr...
Frank M. schrieb:> portable Version:
👍
mfg mf
Achim M. schrieb:>> eine big-endian-Maschine muss man dieser Tage schon mächtig suchen.>> Gefunden! Ich halte eine Maschine mit umschaltbarer Endianness gerade in> der Hand 😉
Und, wird sie auch big endian betrieben?
Nur das ist letztlich relevant. "könnte betreiben" zählt nicht viel, wie
ich schon schrob, das geht bei vielen.
> Jörg W. schrieb:>> aktuell ist meiner Erinnerung nach nur noch PowerPC relevant>> Hast du einen fahrbaren Untersatz?
Ein Fahrrad. :-)
> Da gibt es reihenweise> Big-Endian-Maschinchen.
Welche eigentlich?
foobar schrieb:> sondern lokal auf den Stack
Streich das "auf den Stack".
Der Compiler wird in aller Regel Register dafür benutzen, und davon hat
ein ATtiny26 immerhin schon beinahe so viel¹ wie RAM. :-)
¹ OK, „nur“ ein Viertel davon, aber viele Zwischenergebnisse passen da
rein
Jörg W. schrieb:> Ein Fahrrad. :-)>>> Da gibt es reihenweise Big-Endian-Maschinchen.>> Welche eigentlich?
Auf dem Fahrrad bist du die Maschine, oder? Du liest Zahlen in deiner
nativen Darreichungsform (Arabische Zahlen) auch so, dass die
höhersignifikante Stelle zuerst da steht. Ätsch!
ich sehe überhaupt kein sinnvolles Codefragment oder eine Operation, die
irgendwie mit Unions besser werden könnte. Ist das Information aus einem
anderen Thread?
Frank M. schrieb:> union> {> struct> {> unsigned char l;> unsigned char h;> } ergebnis;> unsigned int result;> } Ergebnis;> Ergebnis.ergebnis.h = 42;> Ergebnis.ergebnis.l = 22;
Das sehen zu müssen,tut wieder richtig weh!
Schon mal was von unnamed struct gehört?
Also "ergebnis" solltet weggelassen werden!
kannAllesBesser! schrieb:> Also "ergebnis" solltet weggelassen werden!
Es kann weggelassen werden. Gerade bei einem C-Anfänger ist es
durchaus sinnvoll, die volle „Tippeltappeltour“ durchzuziehen, sonst
wird die Verwirrung noch größer.
Davon abgesehen: unnamed members sind kein C99-Feature, sie sind erst
mit C11 hinzu gekommen. Es soll ja heute sogar noch Compiler geben, die
noch nicht einmal C99 unterstützen …
kannAllesBesser! schrieb:> Schon mal was von unnamed struct gehört?
Ja, habe ich.
Ich habe es bewusst defensiv hingeschrieben, damit der TO nicht über die
nächste Hürde stolpert. Er scheint Anfänger zu sein, da muss man nicht
direkt die vollen Geschütze auffahren.
Dein "Hinweis" hätte daher ruhig etwas dezenter ausfallen können. Dein
Ton sowieso.
Frank M. schrieb:> Bevor der nächste Bedenkenträger kommt:>> Die Version ist nur auf Maschinen, wo "int" = 16 Bit ist, portabel. Man> sollte daher besser "uint16_t" statt "unsigned int" verwenden. Statt der> "unsigned char" kann man dann aus Symmetriegründen "uint8_t" nutzen.
Sowas kann nicht zu 100% portabel sein (muss es auch nicht), denn die
(u)intXX_t Typen sind optional, weil nicht auf der hinterletzten
Plattform verfügbar. Aber: Ein solcher Zugriff auf eine union erzeugt
undefiniertes Verhalten. Bei den allermeisten Compilern funktioniert
dieses "Feature" einwandfrei, man sollte sich das jedoch im Hinterkopf
behalten.
Man kann in C legal mit einem unsigned char Zeiger in beliebigen
Variablen herumwursteln. Schön ist das auch nicht, erzeugt aber kein
undefiniertes Verhalten.
1
uint16_tWert;
2
unsignedchar*Byte=(unsignedchar*)&Wert;
3
*Byte=0xAB;
4
*(Byte+1)=0xCD;
Edit:
Für mich hört sich das nach verfrühter Optimierung durch den TO oder
nicht eingeschalteter Optimierung des Compilers an. Also:
1. Prüfen was der Compiler draus macht => erzeugter Assemblercode
2. Prüfen, ob eine Einsparung an anderer Stelle einfacher/sinnvoller
ist. Alle Konstanten im Flash?
3. Prüfen, welche Variablen wirklich gehalten werden müssen (Laufzeit
vs. Speicher).
B. S. schrieb:> Sowas kann nicht zu 100% portabel sein (muss es auch nicht), denn die> (u)intXX_t Typen sind optional, weil nicht auf der hinterletzten> Plattform verfügbar.
Dann informiert der Compiler, das etwas nicht stimmt. Das ist das
zweitbeste Verhalten.
B. S. schrieb:> Aber: Ein solcher Zugriff auf eine union erzeugt> undefiniertes Verhalten.
Nö, nicht mit C. Für C++ würde es stimmen.
mh schrieb:> B. S. schrieb:>> Aber: Ein solcher Zugriff auf eine union erzeugt>> undefiniertes Verhalten.> Nö, nicht mit C. Für C++ würde es stimmen.
Danke für die Korrektur! Ich hatte wohl zu viel C++ im Kopf. In C90 war
es noch implementation defined, aber der TO wird hoffentlich einen etwas
neueren Standard verwenden.
B. S. schrieb:> In C90 war es noch implementation defined, aber der TO wird hoffentlich> einen etwas neueren Standard verwenden.
Implementation defined ist es natürlich immer noch. Damit ist es aber
"defined"
(und nicht "undefined behaviour", was ja alles Mögliche sein kann). Im
Wesentlichen muss die Implementierung dabei nur die Endianess
definieren.
R. F. schrieb:> Mit der union würde ich 2 bytes sparen.
Ja, aber für wie lange?
Der von Dir gezeigte Code hat ja einen Scope, und wenn der verlassen
wird, wird der benötigte Speicher für die zuvor angelegten Variablen
ErgebnisH, ErgebnisL und result doch ohnehin wieder frei. Es sei denn
natürlich, das sind alles globale Variablen die dauerhaft Speicher
belegen. Dann darf man sich freilich nicht wundern, wenn einem der
Speicherplatz ausgeht ;-)
Also mir kommt es eher nicht so vor, als ob man gerade an der Stelle
wirklich großartig was an Speicher einsparen könnte. Aber um das
wirklich beurteilen zu können müsste man die ganze Funktion sehen.
Mark B. schrieb:> Der von Dir gezeigte Code hat ja einen Scope
Wo sehr ihr alle Code?
Im OP gibt es eine Zuweisung plus die Definition der 3 Variablen.
Das kann kein Konstrukt sinnvoll reduzieren.
Hätte er nur "b=a;" gepostet, käme doch auch niemand auf die Idee zu
sagen, b kann dann entfallen.
Hallo,
derzeit bin ich damit beschäftigt, das Programm zu planen. Ich habe
versucht, einige gescheite Datenstrukturen zu bestimmen, aber code
i.S.e. Programmes gibt es derzeit nicht.
Das Programm soll eine tastatur auslesen, die max. 8*8 Zeilen/Spalten
haben (eine Erweiterung später ist möglich).
Mit der Definition
volatile * const uchar8_t Zeile = PORTA;
volatile * const uchar8_t Spalte = PORTB;
sind die Zeilen/Spaltenanschlüsse (im ROM) gespeichert. So kann man
diese einfach auslesen.
Derzeit ist keine zusätliche Funktion angedacht, denkbar sind her
Fehlkontakte der Druck mehrerer Tasten gleichzeitig usw. Eine Codierung
ist nicht vorgesehen, es werden nur die Scancodes übertragen. Vorgesehen
ist aber eine Implementation von Shift oder CRTL.
Lass das mal mit den Unions. Das ist so ein typisches Stilmittel, mit
dem man sich eher ins eigene Knie schießt, als etwas gutes zu erreichen.
Man hätte sie nie einführen sollen (wie so viele andere Dinge in C/C++
auch).
R. F. schrieb:> Mit der Definition> volatile * const uchar8_t Zeile = PORTA;> volatile * const uchar8_t Spalte = PORTB;>> sind die Zeilen/Spaltenanschlüsse (im ROM) gespeichert.
Nö.
Erstens hast du da auf der linken Seite einen Zeigertypen, auf der
rechten Seite aber eine 8-Bit-Ganzzahl (uint8_t) als Datentyp. Diese
Initialisierung hat so keinen Sinn, und sie verursacht vermutlich auch
eine Warnung.
Zweitens benutzt du in beiden Fällen das Ausgangsregister des Ports.
Wenn du eine Tastatur einlesen willst, dann solltest du wohl aber auf
einer von beiden Seiten das Eingangregister der Pins (PINA oder PINB)
lesen um zu erfahren, welche Tasten da gedrückt sind.
Schließlich und endlich, vergiss nicht, dass mechanische Tasten
entprellt werden müssen.
Um mit einem ATtiny26 eine 8x8-Tastatur direkt mit den Pins abzufragen,
musst du alle 16 IO-Pins benutzen – einschließlich des Reset-Pins,
dessen Reset-Funktion du folglich per Fuse wegdefinieren musst. Danach
ist der Controller nicht mehr per ISP programmierbar. Zu guter Letzt:
wohin mit den ausgelesenen Daten? Sind ja schon alle Pins vergeben …
Bist du dir völlig sicher, dass der Controller die adäquate Wahl für
dein Vorhaben ist? Ein ATmega8 / ATmega<X>8 (X = 4, 8, 16, 32) wäre da
wohl deutlich praktikabler, und dank des größeren RAMs müsstest du dir
nicht mit deinen offensichtlich auch nur geringen C-Kenntnissen bereits
in so einer frühen Phase des Projekts den Kopf über einzelne
einzusparende Bytes zerbrechen, sondern könntest erst einmal loslegen
und überhaupt eine Funktion hinzubekommen.
OK, man könnte einen 74HC138 benutzen und die 8 Zeilenleitungen mit 3
Pins codieren, dann blieben außer Reset noch 4 weitere Pins frei. Aber
das nimmt sich weder von Platzbedarf noch finanziell dann nennenswert
was zu den genannten Controller-Alternativen.
R. F. schrieb:> Hallo,>> derzeit bin ich damit beschäftigt, das Programm zu planen. Ich habe> versucht, einige gescheite Datenstrukturen zu bestimmen, aber code> i.S.e. Programmes gibt es derzeit nicht.
Darf man dann fragen, wie Dein Background ist? Also hast Du schonmal C
programmiert oder irgendwas mit µC gemacht?
Wie Du hier siehst, sind abstrakte Fragen oft relativ schwierig, da man
alle möglichen Fälle berücksichtigen muss. In Deinem UP war noch zu
vermuten, dass wenigstens Du Dich schon konkret damit beschäftigt hast.
R. F. schrieb:> eine unerwartete Speicherknappheit
Jörg W. schrieb:> R. F. schrieb:>> Mit der Definition>> volatile * const uchar8_t Zeile = PORTA;>> volatile * const uchar8_t Spalte = PORTB;>>>> sind die Zeilen/Spaltenanschlüsse (im ROM) gespeichert.>> Nö.>> Erstens hast du da auf der linken Seite einen Zeigertypen, auf der> rechten Seite aber eine 8-Bit-Ganzzahl (uint8_t) als Datentyp. Diese> Initialisierung hat so keinen Sinn, und sie verursacht vermutlich auch> eine Warnung.
OK. Das wird überdacht.
>> Zweitens benutzt du in beiden Fällen das Ausgangsregister des Ports.> Wenn du eine Tastatur einlesen willst, dann solltest du wohl aber auf> einer von beiden Seiten das Eingangregister der Pins (PINA oder PINB)> lesen um zu erfahren, welche Tasten da gedrückt sind.
Gut, das werde ich ändern.
>> Schließlich und endlich, vergiss nicht, dass mechanische Tasten> entprellt werden müssen.>> Um mit einem ATtiny26 eine 8x8-Tastatur direkt mit den Pins abzufragen,> musst du alle 16 IO-Pins benutzen – einschließlich des Reset-Pins,> dessen Reset-Funktion du folglich per Fuse wegdefinieren musst. Danach> ist der Controller nicht mehr per ISP programmierbar. Zu guter Letzt:> wohin mit den ausgelesenen Daten? Sind ja schon alle Pins vergeben …
Das ist mir klar. Der tiny26 wird nur mit einer max. 5 * 5 Tastatur
betrieben. Allerdings gibt es auch AVRs mit mehr Ausgängen.
>> OK, man könnte einen 74HC138 benutzen und die 8 Zeilenleitungen mit 3> Pins codieren, dann blieben außer Reset noch 4 weitere Pins frei. Aber> das nimmt sich weder von Platzbedarf noch finanziell dann nennenswert> was zu den genannten Controller-Alternativen.
Die Tastenleitungen haben ein oder kein Bit gesetzt, das erleichtert die
Fehlersuche sehr.
R. F. schrieb:> Das ist mir klar. Der tiny26 wird nur mit einer max. 5 * 5 Tastatur> betrieben. Allerdings gibt es auch AVRs mit mehr Ausgängen.
Dann nimm bitte einen solchen AVR mit mehr Ausgängen, um das alles zu
testen und in Betrieb zu nehmen. Wenn du einen ATmega328 nimmst, kannst
du mit einem x-beliebigen Arduino-Clone anfangen und hast eine
ready-to-run Experimentierplattform mit ausreichend Ressourcen.
Wenn das dann läuft und beim Downsizing auf 5x5 nicht in den
ATtiny26 passt, dann kannst du dir Gedanken um Mikrooptimierungen
machen.
(Warum zum Geier™ man heutzutage noch so ein Museumsstück wie ATtiny26
freiwillig nehmen will, erschließt sich mir allerdings nicht.)
>> OK, man könnte einen 74HC138 benutzen …> Die Tastenleitungen haben ein oder kein Bit gesetzt, das erleichtert die> Fehlersuche sehr.
Das kann man aber auch problemlos haben, wenn man die Leitungen direkt
mit dem Controller steuert. Man schiebt da einfach in jedem Timerschritt
ein 0-Bit durch:
Jörg W. schrieb:> (Warum zum Geier™ man heutzutage noch so ein Museumsstück wie ATtiny26> freiwillig nehmen will, erschließt sich mir allerdings nicht.)
Ich habe vor einiger zeit 40 Stck davon geschenkt bekommen.
R. F. schrieb:> Ich habe vor einiger zeit 40 Stck davon geschenkt bekommen.
OK, aber entwickeln würde ich zumindest nicht da drauf. Vermutlich
würde ich sie aber auch ansonsten eher rumliegen lassen, die sind noch
ziemlich spartanisch ausgestattet. (Ich lasse ja auch stangenweise
andere Bauteile rumliegen, die ich mal geschenkt bekommen habe, aber für
die ich eigentlich auch nur keine Verwendung habe.)
Wofür die ATtiny26 (bzw. deren Nachfolger ATtiny261/461/861) wirklich
gut sind ist genau das, wofür sie mal designt worden sind:
PWM-Generierung mit der Highspeed-PLL als Takt.
R. F. schrieb:> Mit der union würde ich 2 bytes sparen.
Sowas nennt sich Mikrooptimierung, d.h. wenig Effekt im Vergleich zum
Aufwand. Die 2 Bytes werden Dich nicht rausreißen.
Nimm den pinkompatiblen ATtiny861, der hat der 4-fachen RAM (512 Byte).
Wenn Du den RAM optimieren willst, mußt Du erstmal feststellen, was der
Flaschenhals ist, also was wirklich viel RAM belegt.
R. F. schrieb:> Mit der Definition>> volatile * const uchar8_t Zeile = PORTA;> volatile * const uchar8_t Spalte = PORTB;>> sind die Zeilen/Spaltenanschlüsse (im ROM) gespeichert. So kann man> diese einfach auslesen.
Das genaue Gegenteil ist der Fall, es wird nicht einfacher, sondern
komplizierter. Wenn man mit Pointern gedankenlos um sich schmeißt,
erhöht das massiv den Codeverbrauch. Statt den Port oder Pins direkt
einzulesen, muß man erstmal 2 Pointerregister laden. Die mit Pointern
belegten Register erhöhen wiederum den Stackverbrauch, d.h. belegen auch
mehr RAM.
Und ein Pointerarray ist dann quasi von hinten durch die Brust ins Auge.
Erstmal muß man einen Pointer mit dem Arraystart laden, den Index
16bittig addieren und dann damit 2 Register indirekt laden. Diese 2
Register wieder in ein Pointerpaar moven und schon kann man indirekt
drauf zugreifen.
Grob geschätzt der 10-fache Code gegenüber einem IN-Befehl. Die
PUSH/POP-Arie der zusätzlichen Register nicht mit eingerechnet.
Der AVR-GCC ist manchmal auch erstaunlich schlau. Wenn er feststellt,
daß alle Variablen schon zur Compilezeit bekannt sind, kann er die ganze
Arie durch ein LDS ersetzen.