Hallo,
ich habe mal eine kleine Frage bezüglich globalen Variablen. Konkret
geht es hierbei um avr-gcc, aber womöglich gilt das auch für andere
Compiler.
Globale Variablen werden ja bei der Initialisierung aus der .data
Sektion in den RAM kopiert. Von da an verwendet man die mitunter zur
Laufzeit manipulierte Variable im RAM.
Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash
zuzugreifen?
Danke,
Robert
Robert M. schrieb:> Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash> zuzugreifen?
Läuft das Programm rückwärts wenn man den Takt invertiert?
volatile int var_from_flash = *(_etext + ((const char*)&global_var - __data_start));
15
16
while(1)
17
{}
18
}
Die volatiles sind nicht erforderlich, die habe ich nur zum Test drin,
damit der Compiler nicht alles wegoptimiert.
Oliver
P.S. Die Annahme, daß die Daten im Flash immer direkt hinter der
Program-Text-Section stehen (Symbol _etext), ist eine Vermutung. Wenn du
das ganz sicher wissen willst, musst du in die linker scripte schauen
Das (oder ein vergleichbares Konstruckt in C) liefert dem Compiler zwar
den Initialwert ohne zusätlichen Speicherverbrauch, greift aber
natürlich nicht direkt auf den Flashspeicher zu.
Oliver
Oliver S. schrieb:> as (oder ein vergleichbares Konstruckt in C) liefert dem Compiler zwar> den Initialwert ohne zusätlichen Speicherverbrauch, greift aber> natürlich nicht direkt auf den Flashspeicher zu.
Ah, jetzt verstehe ich das Anliegen vom TO. Dann ist das natürlich am
Ziel vorbei - hatte mich auch schon gewundert.
Robert M. schrieb:> Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash> zuzugreifen?
Was willst Du erreichen?
Wilhelm M. schrieb:> Ah, jetzt verstehe ich das Anliegen vom TO. Dann ist das natürlich am> Ziel vorbei - hatte mich auch schon gewundert.
Nee, vermutlich nicht. Die Frage des TO klingt eher nach völlig falschem
Lösungsansatz für ein simples Problem.
Warum es ein klassisches
1
#define INIT_VALUE 42
2
int global = INIT_VALUE;
(oder deine C++-Variante davon) nicht tut, weiß wohl nur der TO selber.
Oliver
Mir geht es darum eine Möglichkeit zu schaffen, meinen uC zur Laufzeit
zur kalibrieren (u. a. PID Regler) und per einfachen Befehl (UART)
wieder auf die Originaldaten umzuschalten.
Der Gedanke kommt freilich nicht von mir, schließlich machen das ja
Automotive- und Industriesteuergeräte ja auch irgendwie. Die haben
natürlich keine 8 Bit AVRs drin.
Ich kann das natürlich auch mit Konstante plus dazugehöriger Variable
machen, dann brauche ich aber den doppelten Flash Speicherbedarf (.text
plus .bss für jeden Wert).
Robert M. schrieb:> Aber wie kommt man an die Adresse?
Bevor main () aufgerufen wird, werden vom OS die globalen Variablen vom
Flash heraus initalisiert. Entweder im Quellcode nach dieser Stelle
suchen, oder einen Debugger benutzen.
Man könnte auch eine globale Variable vom Typ long long z.B. mit
0x1234567887654321 initalisieren und nach dieser im Flash suchen.
Andere Variablen könnte man dann gemeinsam in einer globalen Struktur
unterbringen und so leicht zur Laufzeit auffinden.
Es ist sehr unwahrscheinlich, das diese Zeichenfolge im Flash woanders
vorkommt.
Oliver S. schrieb:> Die Frage des TO klingt eher nach völlig falschem> Lösungsansatz für ein simples Problem.
Na, evtl. ist das 2. posting des threads doch nocht sooo dumm?
Robert M. schrieb:> Der Gedanke kommt freilich nicht von mir, schließlich machen das ja> Automotive- und Industriesteuergeräte ja auch irgendwie. Die haben> natürlich keine 8 Bit AVRs drin.
Warum nicht? Was die ausserdem haben, ist EEPROM. Das baut Atmel extra
für diesen Anwendungfsfall da mit ein.
Aber trotzdem bleibt die Frage: Wenn die Kalibrierdaten zur Compilezeit
bekannt sind, warum kein #define o.ä.?
EEProm hat den Vorteil, daß sich darin gerätespezifische Daten ablegen
lassen, die erst bei der Programmierung des einzelnen Gerätes oder nach
dessen Erstinbetriebnahme bekannt sind. Prinzipiell lässt sich zwar auch
das Flash nach der Programmierung mit gerätespezifischen Daten
beschreiben, das ist aber umständlicher.
Oliver
Robert M. schrieb:> per einfachen Befehl (UART)> wieder auf die Originaldaten umzuschalten.
Und warum erstellst du dir nicht eine eigene set_defaults() Funktion,
die dir alle nötigen Variablen auf "default" setzt?
Die kannst dann einmal beim StartUp aufrufen und dann per UARt Befehl
und schon ist es sauber gelöst.
Oliver S. schrieb:> EEProm hat den Vorteil,
[..]
Ich kann auch die per Kalibrierung ermittelten Werte per ISP auslesen
ohne dass das Gerät das in Software per Schnittstelle kann.
Beim Arm Cortex-M gehört das initialisieren zum startup im Projekt, da
sieht man was passiert: es wird einfach in einer (selten mehreren)
Schleifen der Flashstart an den Ramstart kopiert. Die Startadressen
kommen aus dem Linkerscript und sind zugänglich.
Man kann da also den Offset berechnen als &Var - RamStart und Initstart
für die Var ist dann FlashStart + Offset. Mit einem memcopy kann man das
dann auch zu Fuss machen. Beim AVR sind die Sachen etwas versteckter
weil man die nicht als Quelle im Projekt liegen hat, sollte aber ähnlich
gehen. Beim AVR ist es wegen PROGMEM und RAM vermutlich noch
komplizierter.
Nur halte ich sowas auch für unnötig kompliziert und nicht sparsamer.
Wenn das Ram oder der Code nicht mehr für eine Handvoll Variablen reicht
ist es schon lange Zeit gewesen einen größeren µC zu nehmen. Gerade für
einen PID Regler kann man mit einem kleinen CM auch zügig in FP rechnen.
PS:
sehe gerade das es hier ja schon gezeigt wurde:
Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"
Robert M. schrieb:> Ich kann das natürlich auch mit Konstante plus dazugehöriger Variable> machen, dann brauche ich aber den doppelten Flash Speicherbedarf (.text> plus .bss für jeden Wert).
Wird denn die Konstante überhaupt an zentraler Stelle geführt und nicht
vom Compiler an der jeweiligen Stelle eingefügt?
Für den AVR ist ein LDI, welches ein 8-bit Wert enthält, als
Maschinenbefehl genauso 16bit lang wie ein LDS oder LPM, welches einen
Wert vom RAM bzw. Flash holt. Ein direkter Zugriff auf einzelne Werte im
Flash wird daher eher nicht lohnen oder sogar mehr Speicher
beanspruchen. Wenn man einen Datenblock hat und damit eine eizige
Blockadresse, und außerdem die gleiche Funktion zum Laden der
darinbefindlichen Daten verwendet, dann kann man einen Vorteil im
Speicherbedarf erreichen.
Robert M. schrieb:> Mir geht es darum eine Möglichkeit zu schaffen, meinen uC zur Laufzeit> zur kalibrieren (u. a. PID Regler) und per einfachen Befehl (UART)> wieder auf die Originaldaten umzuschalten.
Ich halte diese ganze Sache für vollkommen falsch herum aufgezäumt.
Kalibrierungsdaten ins EEPROM - fertig. Kann dann ja wieder
zurückgesetzt werden.
> Ich kann das natürlich auch mit Konstante plus dazugehöriger Variable> machen, dann brauche ich aber den doppelten Flash Speicherbedarf (.text> plus .bss für jeden Wert).
Denkfehler! .bss belegt keinen Flash-Speicher.
Robert M. schrieb:> Mir geht es darum eine Möglichkeit zu schaffen, meinen uC zur Laufzeit> zur kalibrieren (u. a. PID Regler) und per einfachen Befehl (UART)> wieder auf die Originaldaten umzuschalten.
Typisch legt man dazu eine Kopie im EEPROM an.
Beim Reset werden die Daten im RAM aus dem Flash default belegt. Dann
wird geprüft, ob die CRC im EEPROM stimmt und dann vom EEPROM
überschrieben. Danach lassen sich die Daten im RAM ändern und per
Kommando in den EEPROM sichern. Sichert man sie nicht, dann hat man nach
dem Reset wieder die alten Daten.
Ich habe auch noch ein Kommando implementiert, welches die CRC im EEPROM
ungültig macht. Danach hat man nach einem Reset wieder die default-Daten
aus dem Flash.
Johann L. schrieb:> Vincent H. schrieb:>> Wilhelm M. schrieb:>>> Yalu X. schrieb:>> Das dürfte eigentlich nicht compilieren weil data nicht initialisiert>> ist.>> Und es ist auch eine recht großzügige Auslegung von C.
Das stimmt.
Allerdings bin ich ja der Meinung, dass fast aller hier zu Diskussion
gestellter C-Code durch einen C++-Compiler problemlos übersetzt werden
kann, oder zumindest für spezielle Anteile um C++-Code ergänzt werden
kann.
Maxe schrieb:> Für den AVR ist ein LDI, welches ein 8-bit Wert enthält, als> Maschinenbefehl genauso 16bit lang wie ein LDS oder LPM,
Wobei LDS ein 32-Bit Encoding hat auf den meisten AVRs. Auf Reduced
Tiny ist LDS zwar tatsächlich ein 2-Byte Befehl, kann aber mitunter
nicht auf das komplette RAM zugreifen, etwa bei ATtiny40.
Die Idee ist zwar idiotisch, da so eine Frickellösung nicht skaliert,
aber natürlich ist das möglich. Der Startup-Code macht ja nichts
anderes. Und wenn Du Dir den anschaust, siehst Du, won Wo Wieviele Bytes
er nach Wohin kopiert. Dann Kopiere halt nur den Ausschnitt, der Deinen
gewünschten Variablen entspricht.
Ausgabe:
######## Test ########
global_i = 3
global_i = 6
i_from_flash = -32
Edit: Von euren Antworten nehme ich mit, dass es nicht unbedingt ein
uebliche Technik ist, zur Laufzeit auf den Initialwert im Flash
zuzugreifen.
Man kann Startup Code oder Linker Script anpassen und Variablen von der
Initialisierung ausschließen. Dann kostet die Initialisierung zur
Laufzeit keinen zusätzlichen Platz im Flash.
MaWin schrieb:> Man kann Startup Code oder Linker Script anpassen und Variablen von der> Initialisierung ausschließen.
Das macht den Startup-Code größer, was der TO sich nicht leisten kann.
MaWin schrieb:> Dann kostet die Initialisierung zur> Laufzeit keinen zusätzlichen Platz im Flash.
Falsch. Es wird natürlich dafür Code generiert. Woher sollte die
Initialisierung sonst herkommen.
Robert M. schrieb:> Edit: Von euren Antworten nehme ich mit, dass es nicht unbedingt ein> uebliche Technik ist, zur Laufzeit auf den Initialwert im Flash> zuzugreifen.
Genau. Weil es einfach Blödsinn ist.
Robert M. schrieb:> int i_from_flash = *(_etext + ((const char*)&global_i -> __data_start));
Damit das funktioniert, müsstest Du den Zeigertyp richtig wählen:
1 schau im Startup Code, wie er es macht. Vielleicht gibt es
flash-read-funktionen, vielleicht brauchst Du memcpy wegen allignment.
2 beachte den resultierenden Pointer. So würde nur 1 Byte kopiert.
Memcpy ist sauberer.
3 wandle alle Label/adressen vorher zu int.
Nenn Mal die Zahlenwerte der Label, + den Endwert. Ob die plausibel in
ram und Rom zeigen.
Wilhelm M. schrieb:> Das macht den Startup-Code größer, was der TO sich nicht leisten kann.
Nö, der Startup-Code wird erstmal kleiner, da die Vorbelegung eingespart
wird.
Was dazu kommt, ist ein Aufruf von memcpy, das dann die Struct aus dem
Flash in den RAM kopiert. Die Struct im Flash hat genau die Größe, die
bei der Vorbelegung eingespart wurde.
Damit kann man diesen memcpy Aufruf zur Laufzeit jederzeit erneut
ausführen.
Robert M. schrieb:> Edit: Von euren Antworten nehme ich mit, dass es nicht unbedingt ein> uebliche Technik ist, zur Laufzeit auf den Initialwert im Flash> zuzugreifen.
So ist es.
Die Vorbelegung brauche ich im Prinzip nur beim allerersten Einschalten,
solange im EEPROM noch Mumpitz steht.
Robert M. schrieb:> int i_from_flash = *(_etext + ((const char*)&global_i -> __data_start));
Dieses ganze Pointergewusel mit compilerinternen Namen macht den Code
nicht gerade lesbarer.
Wann immer Variablen zusammen gehören, packe ich sie in ein struct. Das
hat den Vorteil, daß man zur Übergabe nur den Pointer auf die struct
übergeben muß. Eine Funktion kann dann wiederum mit LDD/STD auf die
ersten 64Byte ganz ohne Pointerarithmetik zugreifen.
Wilhelm M. schrieb:> Damit das funktioniert, müsstest Du den Zeigertyp richtig wählen:> volatile int i_from_flash = *((const int*)(_etext + ((const> char*)&global_i - __data_start)));
Stimmt. Hatte ich übersehen.
Oliver
Peter D. schrieb:> Dieses ganze Pointergewusel mit compilerinternen Namen macht den Code> nicht gerade lesbarer.
Ungefähr die Hälfte der Antworten stammt von fürchterlichen
Hobbyfricklern. Deren Ziel ist es nur, C in Verruf zu bringen.
Nur weil irgendeine verquerer Ansatz in C geht, bedeutet es lange nicht
das so zu programmieren.
Peter D. schrieb:> Nö, der Startup-Code wird erstmal kleiner, da die Vorbelegung eingespart> wird.
Nein. Er wird größer werden, wenn Du die Idee vewirklichst, wie oben
vorgeschlagen (was aber Quatsch ist wie das ganze hier) nur bestimmte
globale Variablen zu initialisieren. Da brauchst Du eine
Fallunterscheidung, das macht den Code größer. Bringt aber das Problem,
dass man vom C-Standard abweicht.
Oliver S. schrieb:> Stimmt. Hatte ich übersehen.
Das macht ja gar nichts. Dein Beispiel war ja nur ein proof-of-concept.
Daran, dass der TO den kleinen Fehler nicht erkannt hat, sieht man doch
auch, dass er überhaupt nicht weiß, was er da tut, und wie unnötig das
ganze ist.
Nick M. schrieb:> Nur weil irgendeine verquerer Ansatz in C geht, bedeutet es lange nicht> das so zu programmieren.
das ist simple Zeigerarithmetik, absoluter Standard in C. Ausser
vielleicht für Hobbyfrickler.
Es wird für den AVR nur wegen seiner Harvard Architektur verkompliziert.
Johannes S. schrieb:> das ist simple Zeigerarithmetik, absoluter Standard in C. Ausser> vielleicht für Hobbyfrickler.
Wie man oben sieht, verstehen die Hobbyfrickler das gar nicht. Und sie
verstehen nicht, dass sie so eine Blödsinn gar nicht brauchen.
Wilhelm M. schrieb:> dass er überhaupt nicht weiß, was er da tut, und wie unnötig das> ganze ist.
den Fehler, mal die falsche Speicheradresse zu erwischen macht jeder der
mit AVR anfängt.
Wilhelm M. schrieb:> Wie man oben sieht, verstehen die Hobbyfrickler das gar nicht. Und sie> verstehen nicht, dass sie so eine Blödsinn gar nicht brauchen.
Wenn die Menge der Initialisierung größer wird könnte man etwas sparen.
Ob man eine grosse oder kleine Struktur kopiert macht keinen Unterschied
im Code. Nur wenn man stückelt lohnt es sich nicht.
Aber ich habe ja schon geschrieben das ich soetwas auch nicht machen
würde. Ich nehme sogar seit Jahren nicht mal mehr AVR weil das im
Hobbybereich nur übertriebene Sparsamkeit ist. Das Verfahren mit Werten
im EEPROM wie von PeDa beschrieben ist gut, oder ein generischer
KV-Store. Das kann man sich leisten wenn man einen richtigen µC nimmt.
Johannes S. schrieb:> den Fehler, mal die falsche Speicheradresse zu erwischen macht jeder der> mit AVR anfängt.
Er hat den fehlenden cast-Operator nicht entdeckt - auch nach Tests
nicht. Das sagt mir, dass er das ganze Konstrukt konzeptionell nicht
verstanden hat.
Wilhelm M. schrieb:> Er hat den fehlenden cast-Operator nicht entdeckt
mit dem (const int*) addressiert er das Flash, vorher hat er ins RAM
gegriffen. Also falscher Speicherbereich wegen Harvard.
Standardfalle bei AVR.
Johannes S. schrieb:> Das Verfahren mit Werten> im EEPROM wie von PeDa beschrieben ist gut, oder ein generischer> KV-Store. Das kann man sich leisten wenn man einen richtigen µC nimmt.
Das kann man sich auch auf einem kleinen AVR leisten.
Man schreibt sich einmal ein template dafür und hat einen generischen
KV-Store, der so einfach wie ein Array zu benutzen ist. Das hatte ich
ganz oben schon vorgeschlagen. Aber offensichtlich weiß der TO gar
nicht, was der Unterschied zwischem dem text-Segment im Flash und EEProm
beim AVR ist. Sonst hätte er ja schon erkannt, dass er nach der
EEProm-Lösung sucht.
Johannes S. schrieb:> mit dem (const int*) addressiert er das Flash, vorher hat er ins RAM> gegriffen. Also falscher Speicherbereich wegen Harvard.> Standardfalle bei AVR.
Nein, aus dem Flash lädt der auch ohne den cast, _etext ist entsprechend
definiert. Es wird nur die falsche Anzahl Bytes gelesen (char* statt
int*).
Johannes S. schrieb:> das ist simple Zeigerarithmetik, absoluter Standard in C.
So isses.
Oliver
Johannes S. schrieb:> mit dem (const int*) addressiert er das Flash, vorher hat er ins RAM
Blödsinn.
Es war ein const char*, er las also nur ein Byte des int's.
Johannes S. schrieb:> das ist simple Zeigerarithmetik, absoluter Standard in C. Ausser> vielleicht für Hobbyfrickler.
Das ist das Ergebnis eines ehemaligen Hobbyfricklers. Er hat es
geschafft so einen unleserlichen nicht portablen Code mehr als 10 mal
fehlerfrei zusammenzubruzzeln. Jetzt nennt er sich Profi und bekommt
dafür sogar Geld.
Ich würde mich schämen für so einen Schrottcode.
Max G. schrieb:> Robert M. schrieb:>> Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash>> zuzugreifen?>> Was spricht gegen diese Version?> const uint16_t startwert = 0xDEAD;> uint16_t variable = startwert;
Das ersetzt auf einem AVR die doppelte Speicherung im Flash durch
doppelte Speicherung im RAM.
A. S. schrieb:> Die Idee ist zwar idiotisch, da so eine Frickellösung nicht skaliert,
Warum sollte das nicht skalieren? Ob du jetzt 3 Variablen oder 100000 so
behandelst, macht doch keinen wirklichen Unterschied.
Wilhelm M. schrieb:> Peter D. schrieb:>> Nö, der Startup-Code wird erstmal kleiner, da die Vorbelegung eingespart>> wird.>> Nein. Er wird größer werden, wenn Du die Idee vewirklichst, wie oben> vorgeschlagen (was aber Quatsch ist wie das ganze hier) nur bestimmte> globale Variablen zu initialisieren. Da brauchst Du eine> Fallunterscheidung, das macht den Code größer.
Die gibt es eh. Je nach Initialisierung werden globale Variablen in eine
von drei Sektionen gepackt:
.bss für alles, was mit 0 initialisiert werden muss
.data für alles, was mit anderen Werten initialisiert werden muss
.noinit für alles, was gar nicht initialisiert werden muss
Für die erste wird sowas wie ein memset() gemacht, für die zweite etwas
in der Art von memcpy aus dem Flash und für das dritte gar nichts.
Das sollte man auch berücksichtigen, wenn man so Frickellösungen wie
oben angegeben verwendet. Wenn da sowas steht:
1
inta=3;
2
intb=0;
3
intc=5;
Dann landen die Initialisierungswerte von a und c im Flash, von b aber
nicht, weil es 0-initialisiert ist. Da passieren bestimmt lustige Dinge,
wenn man b aus dem Flash wieder zurücksetzen will.
Ich würde mir diese ganze Bastelei auch sparen und einfach eine Funktion
schreiben, die die Variablen vorbelegt und die am Anfang von main()
aufrufen, wie schon in
Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden" beschrieben. Die
kann man dann später auch zum resetten benutzen.
Also ganz einfach was in dieser Art:
1
inta;
2
intb;
3
intc;
4
5
voidinit_values(void)
6
{
7
a=3;
8
b=0;
9
c=5;
10
}
11
12
intmain()
13
{
14
init_values();
15
16
printf("%d\n",a);
17
18
a=7;
19
20
printf("%d\n",a);
21
22
init_values();
23
24
printf("%d\n",a);
25
}
Das ist extrem leicht verständlich, braucht keinerlei Zeigerakrobatik,
ist portabel, und ich kann dann immer noch entscheiden, ob ich dort die
Werte direkt hinschreibe oder sie aus einem EEPROM lese.
Nick M. schrieb:> Ich würde mich schämen für so einen Schrottcode.
Ansichtssache.
Nur weil AVR Otto Normalprogrammierer nie was mit den Adressen aus dem
Linkerscript macht, heißt es nicht das es sie nicht gibt und man die
nicht nutzen darf. Wenn man an die C-Runtime Programmierung kommt und
eigene Speicherverwaltung macht und z.B. ein _sbrk() selber
implementiert braucht man diese Adressen auch.
Diese Symbole gehören zur C-Runtime und nicht zum C Sprachstandard, sind
deshalb nicht festgelegt und der Code ist nicht portabel. Damit muss man
bei so hochgradigen Optimierungen leben und Abwägen ob es einem das Wert
ist.
Beim AVR bleibt der Startupcode dem Anwender verborgen. Das ist ok und
einfacher. Es gehört aber auch zu den Schreckmomenten beim Umstieg von
AVR auf ARM das so ein Startupcode, oft noch in kryptischem Assembler,
im Projekt liegt. Wenn sich jemand nicht damit befassen möchte wird ihm
hier von den Profis übrigens ständig Dummheit vorgeworfen.
Der TO hat das verstanden und ich finde es erstaunlich wie man ihm da
vorwerfen kann es nicht verstanden zu haben wg. einer kleinen Unschärfe.
Ihr Profis schreibt Code runter wie ein Buch und alles funktioniert ad
hoc? Respekt. Und gleich in C++ mit Templates und Lambdas? Noch mehr
Respekt.
Rolf M. schrieb:> Also ganz einfach was in dieser Art:> int a;> int b;> int c;>> void init_values(void)> {> a = 3;> b = 0;> c = 5;> }
Wie immer dreht sich die Diskussion hier im Kreis: es ist zwar schon
alles gesagt, aber eben nicht von allen. Was Du hier vorschlägst, steht
schon gaaanz oben in diesem Thread.
Wilhelm M. schrieb:> Und wenn Du Dich an C++ störst:
Mir wäre das im Prinzip egal, aber der TE hat halt nach C gefragt. Und
bist du sicher, dass es für AVR eine Implementation von std::array
überhaupt gibt?
Wilhelm M. schrieb:> Wie immer dreht sich die Diskussion hier im Kreis es ist zwar schon> alles gesagt, aber eben nicht von allen.
Mir ist lediglich unklar, warum hier offenbar krampfhaft versucht wird,
die einfachste und offensichtlichste Lösung zu vermeiden.
Rolf M. schrieb:> Max G. schrieb:>> Was spricht gegen diese Version?>> const uint16_t startwert = 0xDEAD;>> uint16_t variable = startwert;>> Das ersetzt auf einem AVR die doppelte Speicherung im Flash durch> doppelte Speicherung im RAM.
Wieso das? Mit const sollte startwert nur im Flash landen. variable
ist natürlich im RAM, aber das soll ja so sein.
Allerdings muss der Linker intelligent genug sein, die Initialisierung
von variable dann aus startwert zu machen, nicht aus einem
gedoppelten Wert in .cinit (oder wie das Segment dort auch immer heißt).
Max G. schrieb:>> Das ersetzt auf einem AVR die doppelte Speicherung im Flash durch>> doppelte Speicherung im RAM.>> Wieso das? Mit const sollte startwert nur im Flash landen.
Dann müssest du es noch static machen, denn sonst muss der Compiler
davon ausgehen, dass ggf. auch von einer anderen Übersetzungseinheit aus
drauf zugegriffen werden kann. Aber ja, dann sollte es gehen.
> Allerdings muss der Linker intelligent genug sein, die Initialisierung> von variable dann aus startwert zu machen, nicht aus einem> gedoppelten Wert in .cinit (oder wie das Segment dort auch immer heißt).
Das sollte gcc hinbekommen.
Rolf M. schrieb:> Mir wäre das im Prinzip egal, aber der TE hat halt nach C gefragt. Und> bist du sicher, dass es für AVR eine Implementation von std::array> überhaupt gibt?
Ja. Sonst würde ich es nicht schreiben.
Und wer es nicht hat, schreibt es sich eben selbst, oder kopiert es sich
aus der stdlibc++, die ist OSS.
Rolf M. schrieb:> Mir ist lediglich unklar, warum hier offenbar krampfhaft versucht wird,> die einfachste und offensichtlichste Lösung zu vermeiden.
Mir auch. Deswegen sagte ich ja bereits, das ich es für Blödsinn halte,
was der TO versucht. Auch wenn es technisch möglich ist. Ich denke immer
noch, er such eigentlich nach einer EEProm-Lösung.
Oliver S. schrieb:> Es wird nur die falsche Anzahl Bytes gelesen (char* statt int*).
Das ist zwar ein Fehler, aber -32 kann da nicht rauskommen. Sonst hättte
ich das dabei geschrieben.
Wobei ich die Plattform nicht kenne, aber der TO hat ja nun alle Infos,
um es zu kontrollieren und dann zu korrigieren.
Wilhelm M. schrieb:> Peter D. schrieb:>> Nö, der Startup-Code wird erstmal kleiner, da die Vorbelegung eingespart>> wird.>> Nein. Er wird größer werden, wenn Du die Idee vewirklichst, wie oben> vorgeschlagen (was aber Quatsch ist wie das ganze hier) nur bestimmte> globale Variablen zu initialisieren. Da brauchst Du eine> Fallunterscheidung, das macht den Code größer. Bringt aber das Problem,> dass man vom C-Standard abweicht.
Dann muß ich das wohl für Dich nochmal ausführlicher erklären.
Ohne Initialisierung werden globale Daten nicht aus dem Flash belegt,
sondern genullt. Der Code wird daher nur um den memcpy_P Aufruf größer.
Anbei mal ein Beispiel und auch ganz ohne kryptisches Pointergewusel.
Die Daten werden in beiden Fällen nur einmal im Flash angelegt. Ich hab
die Struct sehr groß gemacht, damit man das auch schön sieht.
Daten im Startup initialisiert:
1
Program: 10200 bytes (7.8% Full)
2
(.text + .data + .bootloader)
3
4
Data: 10008 bytes (61.1% Full)
5
(.data + .bss + .noinit)
Daten im Main initialisiert:
1
Program: 10252 bytes (7.8% Full)
2
(.text + .data + .bootloader)
3
4
Data: 10007 bytes (61.1% Full)
5
(.data + .bss + .noinit)
Der Unterschied sind also nur die 52 Byte für das memcpy_P.
Wilhelm M. schrieb:> Da brauchst Du eine> Fallunterscheidung, das macht den Code größer. Bringt aber das Problem,> dass man vom C-Standard abweicht.
Das mit der Fallunterscheidung verstehe ich nicht.
Und wo wird denn der C-Standard verletzt?
P.S.:
Geil, der Forenbetrachter kennt sogar #if 0 Kommentare. Schade, daß
Notepad++ sowas nicht kann.
Peter D. schrieb:> Dann muß ich das wohl für Dich nochmal ausführlicher erklären.> Ohne Initialisierung werden globale Daten nicht aus dem Flash belegt,> sondern genullt.
Dann muss ich Dir das auch nochmal erklären. Global Daten ohne
Initializer werden mit 0 initialisiert, und eben solche mit
initializer werden eben durch den startup-code mit den Werte als dem
Flash initialisiert.
Insofern passiert genau das, was der TO will. Er hat es nur noch nicht
kapiert, weil vermutlich aus dem EEProm initialisieren will.
Peter D. schrieb:> Das mit der Fallunterscheidung verstehe ich nicht.> Und wo wird denn der C-Standard verletzt?
Ich habe den TO so verstanden, dass er für manche globale
Datenstrukturen von dieser o.g. Regel abweichen will, und diese,
irgendwie besonders markierten globalen Strukturen, irgendwie anders
initialisieren will.
Man kann das im Application-Code lösen wie Du oben, dann wird der Code
wie ich gesagt habe, größer. Oder man baut sich einen anderen
startup-code, der eine irgendwie geartete Fallunterscheidung zwischen
den "normalen" globalen Daten und diesen "besonderen" globalen Daten
macht. Dann wird der startup-Code wohl auch größer.
Also, der Code wird wie ich gesagt habe größer. Auch wenn es eben nur 52
Byte sind, wie Du auch selbst heraus gefunden hast: er wird größer. Nur
das habe ich gesagt.
Wilhelm M. schrieb:> Auch wenn es eben nur 52> Byte sind
52 Byte sehe ich aber nicht als merkbaren Codeverbrauch an.
Robert hatte befürchtet, daß sich der Anteil an Daten im Code verdoppelt
und das ist eben nicht der Fall. Nichts anderes habe ich schon zu Anfang
gesagt.
Robert M. schrieb:> Ich kann das natürlich auch mit Konstante plus dazugehöriger Variable> machen, dann brauche ich aber den doppelten Flash Speicherbedarf (.text> plus .bss für jeden Wert).
Robert M. schrieb:> [[Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit> verwenden"]] scheint mir am> ehesten die Antwort auf meine Frage zu sein. Leider funkioniert es nicht> ganz:
Ich habe mir das jetzt dann doch mal genauer angesehen. Funktioniert
tatsächlich nicht, da die vom linkerscript exportierten Symbole vom
Compiler nicht direkt als Adresse, sondern als Symbol an einer Adresse
interpretiert werden. Meine erste Version lieferte da per Zufall
trotzdem richtige Werte, warum auch immer.
Mit
Peter D. schrieb:> 52 Byte sehe ich aber nicht als merkbaren Codeverbrauch an.
Ist mir recht.
Ich hatte ja auch nur gesagt, dass der Code größer wird. Auch wenn Du es
zunächst bezweifelt hast, hast Du nun selbst gezeigt, dass es so ist.
Anyway: ich bin immer noch davon überzeugt, dass er es nicht ganz
verstanden hat, und eigentlich einen EEProm-basierten K/V-Store sucht.
Oliver S. schrieb:> int i_from_flash = 0;> if ((char*)&global_i < &__bss_start)> i_from_flash = *((const __flash int*)(&_etext + ((char*)&global_i -> &__data_start)));
Solange es saubere Lösungen auf C-Ebene gibt, sollte man nicht in den
Linker-Internas rumwühlen.
Es nur zu tun, weil man es kann, ist kein guter Programmierstil.
Für die Aufgabenstellung "Kalibrierwerte" gibt es überhaupt keine Lösung
"in C". Nur mit C.
Das erfordert, egal wie man es löst, Zugriff auf die Hardware, sei es
auf EEPROM, Flash, oder sonstwie. Die dazu benötigten Funktionen kommen
ohne nicht-portable Addressen und Symbole nicht aus. Ob die jetzt
versteckt in libs oder direkt im Code auftauchen, ist letztendlich auch
egal.
Ob man jetzt auf das Eeprom-Datenregister per in einem
prozessorspezifischem include-File definiertem Pointer oder auf eine
Speicheraddresse per vom Linkerscript exportiertem Label zugreift, ist
doch Jacke wie Hose.
Oliver
Wilhelm M. schrieb:> eigentlich einen EEProm-basierten K/V-Store sucht.
Dazu müßte man erstmal wissen, was ein K/V-Store ist und wie man sowas
in C implementiert.
Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"
Da verstehe ich nur Bahnhof.
Vermutlich ist das ein Abwandlung meiner memcpy_P Lösung. Nur kann ich
nirgends die Liste der Initialisierungswerte finden.
Wie heißt das Array und mit was werden dessen Elemente geladen?
#include <array> ist vermutlich auch wieder kein Standardheader von C++.
Oliver S. schrieb:> if ((char*)&global_i < &__bss_start)
Die Sections haben doch alle ein _start und ein _end, und die global_i
variable ist doch für __data_start gillt doch für Werte, die >=
__data_start und < __data_end sind, also warum auf < __bss_start prüfen?
Ungetestet:
Peter D. schrieb:> Da verstehe ich nur Bahnhof.> Vermutlich ist das ein Abwandlung meiner memcpy_P Lösung. Nur kann ich> nirgends die Liste der Initialisierungswerte finden.> Wie heißt das Array und mit was werden dessen Elemente geladen?
Das Array mit den Initialwerten heißt global_init und wird in einer
(Compilezeit-)Schleife mit den Werten von 1 bis 100 belegt. Ohne die
Schleife sähe die Deklaration mit Initialisierung so aus:
1
constexprstd::array<int,10>global_init{
2
1,2,3,4,5,6,7,8,9,10,
3
11,12,13,14,15,16,17,18,19,20,
4
21,22,23,24,25,26,27,28,29,30,
5
31,32,33,34,35,36,37,38,39,40,
6
41,42,43,44,45,46,47,48,49,50,
7
51,52,53,54,55,56,57,58,59,60,
8
61,62,63,64,65,66,67,68,69,70,
9
71,72,73,74,75,76,77,78,79,80,
10
81,82,83,84,85,86,87,88,89,90,
11
91,92,93,94,95,96,97,98,99,100
12
};
Dieses Array belegt zunächst keinen Speicher auf dem Zielsystem, es
existiert nur während des Kompilierens.
In der Zeile
1
autoglobal{global_init};
wird eine Variable namens global gleichen Typs definiert und mit dem
Inhalt von global_init initialisiert. Diese Variable liegt – genauso
wie bei jeder anderen initialisierten Variable – im RAM, die zugehörigen
Initialisierungswerte im Flash.
Bis hierher ist noch alles in Ordnung: Es werden 200 Byte RAM und 200
Byte Flash benötigt, wie es dem TE vorschwebt.
Was Wilhelm bei seinem Vorschlag aber wohl nicht bedacht hat:
Irgendwann möchte der TE das Array wieder mit den ursprünglichen Werten
laden. Dies geschieht am einfachsten mit der Zuweisung
1
global=global_init;
Je nach Größe von global_init erzeugt der Compiler daraus eine Folge
von Lade- und Speicherinstruktionen, oder er legt die Variable nun auch
auf dem Zielsystem an, um sie von dort in einer Schleife kopieren zu
können. Da global_init mit 200 Byte relativ groß ist, wird die zweite
Alternative gewählt.
Da nun auf dem AVR zwei solcher 200 Byte großer und mit vorgegebenen
Werten initialisierter Arrays existieren, verdoppelt sich der dafür
benötigte Speicherplatz im RAM wie auch im Flash auf jeweils 400 Byte.
Aber genau das wollte der TE ja vermeiden.
Dein Vorschlag mit dem memcpy_P hat dieses Problem natürlich nicht. Ich
würde aber __flash statt PROGMEM und eine einfache Zuweisung statt
memcpy_P verwenden. Das sieht schöner aus und erlaubt in load_struct
zudem eine Typprüfung:
1
#include<stdint.h>
2
3
typedefvoid(*func)(void);
4
5
typedefstruct{
6
floatfl;
7
uint8_tby;
8
charar[10000];
9
funcfp;
10
}my_struct_t;
11
12
voidblub(void){}
13
14
__flashconstmy_struct_tflash_data={
15
1.2,
16
123,
17
"Hallo",
18
blub,
19
};
20
21
my_struct_tram_data;
22
23
voidload_struct(void){
24
ram_data=flash_data;
25
}
26
27
intmain(void){
28
load_struct();
29
for(;;);
30
}
Hier liegt – wie auch in deinem Code – flash_data im Flash (und nur
dort) und ram_data im RAM (und nur dort).
@Yalu,
vielen Dank für die Erklärung. Eine fortlaufende Belegung dürfte in der
Regel sinnfrei sein. Nun ist mir auch klar, warum ich nirgends eine
Werteliste finden konnte.
Das mit der Zuweisung werde ich mal probieren. Intern wird es wohl auch
auf memcpy_P hinauslaufen.
DPA schrieb:> __data_end sind, also warum auf < __bss_start prüfen?
Die beiden haben immer die selbe Adresse, daher ist’s eigentlich egal,
aber ok. Ist letztendlich Geschmacksache.
Oliver
Hallo zusammen,
vielen Dank fuer die Antworten und die Diskussion, ich finde das Thema
sehr interessant. Manche sehen anscheinen auf den ersten Blick, dass die
Frage "Bloedsinn" ist. Ich bin aber nur ein interessierter Hobbyist mit
begrenzter Zeit fuer das Thema (und auch nur im trueben Winter) und kein
Profi-Programmierer, von daher sind mir manche Dinge nicht sofort klar.
Ich habe die Verbesserungen von Oliver ausprobiert und es funktioniert
tadellos. Nebenbei habe ich noch einiges zum Thema Flash und Speicher
gelernt.
Danke nochmal,
Robert
Die Initialisierung (aus dem Flash) wird durch die in-class-initialiser
durchgeführt (bzw. die Konstanten befinden sich direkt im Code).
Das template StringBuffer ist im wesentlichen ein std::array<Char, N>
mit Elementtyp Char und ein paar Einfüge- und Konkat-Operationen.
_pgm ist ein UDL-Op, der ein C-String-literal ins Flash packt. Also etwa
das Gleiche, wie die __flash-extension im gcc.
Wilhelm M. schrieb:> Die Initialisierung (aus dem Flash) wird durch die in-class-initialiser> durchgeführt (bzw. die Konstanten befinden sich direkt im Code).
Das ändert aber nichts an der Tatsache, dass die Struktur sowohl im
Flash als auch im RAM jeweils doppelt angelegt wird.
Nimm doch einfach zur Kenntnis, dass es für das Problem des TE eine
effiziente und elegante in der von ihm benutzten Programmiersprache,
nämlich C, gibt. Dafür muss er nicht einmal irgendwelche zusätzlichen
Header installieren oder gar selber schreiben.
Du versuchst jetzt verzweifelt zu zeigen, dass das auch in C++ geht.
Zum einen bleibst du damit aber bislang ziemlich erfolglos, zum anderen
interessiert das den TE vermutlich sowieso kaum.
Da es in C++ kein __flash gibt, muss man es eben so machen, wie man es
in C vor der Einführung von __flash gemacht hätte, nämlich mit PROGMEM
und memcpy_P, siehe oben:
Peter D. schrieb:> main.c
Peters Code sollte ohne Änderungen auch vom C++-Compiler akzeptiert
werden.
PS: Ich hätte ja gerne eine noch viel coolere Lösung in Haskell oder
Lisp vorgestellt, habe aber längst eingesehen, dass ich damit auf dem
AVR nicht zum Ziel komme. Also lasse ich es lieber bleiben ;-)
Yalu X. schrieb:> Das ändert aber nichts an der Tatsache, dass die Struktur sowohl im> Flash als auch im RAM jeweils doppelt angelegt wird.
Das stimmt eben nicht (mehr).
Was stimmt, ist, dass die Initialisierung und auch das neue
Initialisieren durch den cctor etwas aufwändiger ist, als das reine
memcpy. Der StringBuffer wird aber tatsächlich direkt aus dem PGM und
die fundamentals direkt im Code initialisiert.
Yalu X. schrieb:> Da es in C++ kein __flash gibt, muss man es eben so machen, wie man es> in C vor der Einführung von __flash gemacht hätte, nämlich mit PROGMEM> und memcpy_P,
Muss man nicht (s.o.).
Da mir die Header mcu/avr.h und etl/stringbuffer.h fehlen, habe ich den
StringBuffer durch ein uint8_t-Array gleicher Größe ersetzt und dieses
mit numerischen Werten initialisiert. Das Ganze soll ja nicht nur für
spezifische Datentypen und Stringliterale funktionieren.
avrtest-cpp.cpp:
Die 210 Byte im data-Segment entsprechen der doppelten Größe der
Struktur. Das data-Segment belegt die 210 Byte sowohl im Flash als auch
im RAM. Die 120 Byte text-Segment sind der Programmcode.
Nun dasselbe in C mit __flash:
avrtest-c.c:
Das data-Segment ist nun leer, dafür liegen nun 105 Byte (einfache Größe
der Struktur) im bss-Segment (RAM) und nochmals 105 Byte im text-Segment
(Flash). Die restlichen 115 Bytes im Flash sind der Programmcode.
Bei mir ergibt sich:
340 0 107 447 1bf bm01a.elf
312 0 105 417 1a1 bm01.elf
Die Variante bm01a ist der C-Code. bm01 ist der C++-Code.
Das liegt eben daran, dass Du das Array nicht aus dem flash
initialisierst, wie bei mit etwa durch den _pgm-String.
Die fehlenden zwei Byte kommen daher, dass ich den Funktionszeiger in
der Struktur vergessen habe.
Der µC ist hier im Beispiel ein mega4809, deswegen ist der Code in sich
schon mal größer als bei Dir.
Wilhelm M. schrieb:> Die Variante bm01a ist der C-Code. bm01 ist der C++-Code.
Welcher C-Code und welcher C++-Code?
> Das liegt eben daran, dass Du das Array nicht aus dem flash> initialisierst, wie bei mit etwa durch den _pgm-String.
Und was müsste ich tun, damit das uint8_t-Array in meinem obigen Code
Yalu X. schrieb:> avrtest-cpp.cpp:
aus dem Flash initialisiert wird? Dein _pgm habe ich nicht, und als
UDL-Op ist seine Verwendung ohnehin auf String-Literale beschränkt, also
in meinem Beispiel nicht anwendbar. Gibt es nicht ein vergleichbares
Konstrukt (ein GCC-Attribut oder was auch immer), das auf beliebige
Initialisierer anwendbar ist?
Ich bin zwar immer noch der Meinung, dass das, was wir hier gerade tun,
nicht ist das was TO will, denn m.E. braucht er ein
Attribute-Value-Store im EEProm.
Wie auch immer, habe ich jetzt die C- und die C++-"Lösung" nebeneinander
gestellt. Auf aller Header für die C++-Variante habe ich verzichtet: sie
bilden ja im Prinzip nur die named-address-space-extension im gcc nach.
Die C-Variante:
1
#include<stdint.h>
2
3
typedefstruct{
4
floatfl;
5
uint8_tby;
6
charar[100];
7
}my_struct_t;
8
9
__flashconstmy_struct_tflash_data={
10
1.23f,
11
123,
12
{
13
1,2,3,4,5,6,7,8,9,10,
14
11,12,13,14,15,16,17,18,19,20,
15
21,22,23,24,25,26,27,28,29,30,
16
31,32,33,34,35,36,37,38,39,40,
17
41,42,43,44,45,46,47,48,49,50,
18
51,52,53,54,55,56,57,58,59,60,
19
61,62,63,64,65,66,67,68,69,70,
20
71,72,73,74,75,76,77,78,79,80,
21
81,82,83,84,85,86,87,88,89,90,
22
91,92,93,94,95,96,97,98,99,100
23
}
24
};
25
26
staticmy_struct_tram_data=flash_data;
27
28
inlinestaticvoidload_struct(void){
29
ram_data=flash_data;
30
}
31
32
intmain(void){
33
load_struct();
34
35
returnram_data.ar[10];
36
}
Die folgende C++-Variante berechnet den Array-Inhalt zur Compilezeit
(vgl. LUT) als Sinus-Tabelle. Man natürlich auch zu Fuß wie in C
explizit initialisieren. Das ändert ja am Code soweit nichts.
Die Re-Initialisierung ist hier durch ein placement-new gemacht worden
in der Funktion reset(). Man hätte es auch durch std-ctor und op= lösen
können, doch wird dafür beim "resetten" via op= viel Stack verbraucht,
was bestimmt auf kleinen avr so nicht gewollt ist. Daher placement-new.
Wir bekommen den angehängten Assembler-Code mit folgenden Größen (m88):
226 106 0 332 14c bm01a.elf
290 0 105 395 18b bm01.elf
Der Unterschied im text-segment kommt m.E. hauptsächlich durch die
unterschiedlichen Initialisierungswege: bei C wird alles aus dem flash
kopiert, bei C++ wird das Array aus dem flash initialisiert, die anderen
member aber direkt im Code.
Wilhelm M. schrieb:> Ich bin zwar immer noch der Meinung, dass das, was wir hier gerade tun,> nicht ist das was TO will, denn m.E. braucht er ein> Attribute-Value-Store im EEProm.
Ja, das wird wohl so sein. Wobei ich mir durchaus auch Anwendungen
vorstellen kann, wo man die Parameter nur temporär ändern und sie
jederzeit – entweder durch ein explizites Kommando oder durch einen
Neustart – zurücksetzen können möchte.
In der C-Version (bm01a.c) hast du ram_data irrtümlicherweise mit einer
Initialisierung versehen:
1
staticmy_struct_tram_data=flash_data;
Dadurch landet ram_data im data-Segment, und die Initialisierungsdaten
werden unnötigerweise – zusätzlich zu flash_data – im Flash abgelegt,
was ja vermieden werden sollte.
Stattdessen sollte das Befüllen der Struktur am Anfang von main durch
eine Zuweisung bzw. einen Aufruf von load_struct() erfolgen, was – außer
ein paar Bytes zusätzlichen Programmcodes – kein Flash benötigt. Dadurch
ändert sich der Gesamtspeicherverbrauch von
1
text data bss dec hex filename
2
226 106 0 332 14c bm01a.elf
in
1
text data bss dec hex filename
2
232 0 105 337 151 bm01a
Man sieht hier, dass die Daten jetzt vom data-Segment ins bss-Segment
gewandert sind, wo sie nur RAM, aber kein Flash belegen.
Wilhelm M. schrieb:> Der Unterschied im text-segment kommt m.E. hauptsächlich durch die> unterschiedlichen Initialisierungswege: bei C wird alles aus dem flash> kopiert, bei C++ wird das Array aus dem flash initialisiert, die anderen> member aber direkt im Code.
Ein größerer Anteil von Nicht-Array-Daten innerhalb der Struktur würde
sich demnach bei deiner Variante noch negativer auf den Flash-Verbrauch
auswirken, was dann erst recht für die C-Lösung sprechen würde.
Was ich aber nicht verstehe: Warum wendest du deine PGM-Magie nur auf
das Array und nicht auf die gesamte Struktur an? Dann wären wären die C-
und die C++-Version bzgl. des Flash- und RAM-Verbrauchs vermutlich
gleichauf.
Yalu X. schrieb:> In der C-Version (bm01a.c) hast du ram_data irrtümlicherweise mit einer> Initialisierung versehen:
So ganz irrtümlich war das nicht, denn bei mir wird das globale Objekt
ram_data auch durch den std-ctor initialisiert. Hat also schon vor
main() seine Werte. Bei Dir erst durch load_struct().
Yalu X. schrieb:> Ein größerer Anteil von Nicht-Array-Daten innerhalb der Struktur würde> sich demnach bei deiner Variante noch negativer auf den Flash-Verbrauch> auswirken, was dann erst recht für die C-Lösung sprechen würde.
Ja, das ist so.
Yalu X. schrieb:> Was ich aber nicht verstehe: Warum wendest du deine PGM-Magie nur auf> das Array und nicht auf die gesamte Struktur an? Dann wären wären die C-> und die C++-Version bzgl. des Flash- und RAM-Verbrauchs vermutlich> gleichauf.
Man müsste
1
intmain(){
2
new(&ram_data)Data(pointer_to_pgm);// wie reset()
3
}
benutzen und in Data ein ctor haben wie
1
structData{
2
explicitData(AVR::Pgm::Ptrp){
3
memcpy_P((uint8_t*)this,p.value,sizeof(Data));
4
}
5
};
Ich nehme an, dass das wohl funktionieren wird, aber strenggenommen ist
das UB, denn eigentlich darf man dafür nur std::memcpy() oder besser
std::bit_cast() verwenden.
Getestet habe ich das jetzt nicht, weil ich das ganze Konstrukt für
falsch halte.