Forum: Compiler & IDEs Initialwert von globalen Variablen zur Laufzeit verwenden


von Robert M. (molle_ghc)


Lesenswert?

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

von Nick M. (Gast)


Lesenswert?

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?

von GEKU (Gast)


Lesenswert?

> Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash
> zuzugreifen?
>
Wenn die Adresse im Flash bekannt ist, warum nicht?

von Robert M. (molle_ghc)


Lesenswert?

Hallo GEKU,

danke für die Antwort.

Auslesen kann man vom Flash via pgm_read aus <avr/pgmspace.h>.

Aber wie kommt man an die Adresse?

von Oliver S. (oliverso)


Lesenswert?

z.B. so:
1
#include <avr/io.h>
2
3
volatile int  global_i   = 3;
4
volatile char global_c   = 'a';
5
volatile int  global_var = 42;
6
7
extern const char* __data_start;
8
extern const char* const __flash _etext;
9
10
int main(void)
11
{
12
  volatile int i_from_flash   = *(_etext + ((const char*)&global_i   - __data_start));
13
  volatile char c_from_flash  = *(_etext + ((const char*)&global_c   - __data_start));
14
  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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Vielleicht so:
1
namespace {
2
    constexpr int global_init{42};
3
}
4
5
auto global{global_init};

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> namespace {
>     constexpr int global_init{42};
> }
>
> auto global{global_init};

Wie geht das, wenn global ein Array ist?

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Wie geht das, wenn global ein Array ist?
1
#include <mcu/avr.h>
2
#include <array>
3
namespace {
4
    constexpr auto global_init = []{
5
        std::array<int, 100> data;
6
        for(uint8_t i{0}; auto& e : data) {
7
            e = ++i;
8
        }
9
        return data;
10
    }();
11
}
12
13
auto global{global_init};
14
15
int main() {
16
    return global_init[10];
17
}

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

von Vincent H. (vinci)


Lesenswert?

Wilhelm M. schrieb:
> Yalu X. schrieb:
>> Wie geht das, wenn global ein Array ist?
>
>
1
> #include <mcu/avr.h>
2
> #include <array>
3
> namespace {
4
>     constexpr auto global_init = []{
5
>         std::array<int, 100> data;
6
>         for(uint8_t i{0}; auto& e : data) {
7
>             e = ++i;
8
>         }
9
>         return data;
10
>     }();
11
> }
12
>


Das dürfte eigentlich nicht compilieren weil data nicht initialisiert 
ist.

von Oliver S. (oliverso)


Lesenswert?

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

Beitrag #6061688 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

Vincent H. schrieb:
> Das dürfte eigentlich nicht compilieren weil data nicht initialisiert
> ist.

Stimmt.

Nun aber:
1
      std::array<int, 100> data{};

von Robert M. (molle_ghc)


Lesenswert?

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

von GEKU (Gast)


Lesenswert?

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.

von Nick M. (Gast)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Adam P. (adamap)


Lesenswert?

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.

von Dunno.. (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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"

von Maxe (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

> 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.

von Peter D. (peda)


Lesenswert?

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.

von Max G. (l0wside) Benutzerseite


Lesenswert?

Robert M. schrieb:
> Gibt es eine Möglichkeit in C zur Laufzeit auf den Initialwet im Flash
> zuzugreifen?

Was spricht gegen diese Version?
1
const uint16_t startwert = 0xDEAD;
2
uint16_t variable = startwert;
3
4
void restore_defaults() {
5
  ...
6
  variable = startwert;
7
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Robert M. (molle_ghc)


Angehängte Dateien:

Lesenswert?

Vielen Dank fuer die vielen Antworten!

[[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:
1
#define set_bit(var, bit) (var) |= (1 << (bit))
2
#define clear_bit(var, bit) (var) &= ~(1 << (bit))
3
#define toggle_bit(var,bit) ((var) ^= (1 << (bit)))
4
5
#include <avr/io.h>
6
#include <avr/interrupt.h>
7
#include <stdio.h>
8
#include <util/delay.h>
9
#include <avr/pgmspace.h>
10
#include "libs/uart.h"
11
12
#define UART_BAUD_RATE      9600   
13
14
15
volatile int  global_i   = 3;
16
17
extern const char* __data_start;
18
extern const char* const __flash _etext;
19
20
void sendUART(const char*, int);
21
22
int main(void) {
23
24
  uart_init(UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU));
25
  sei(); 
26
  
27
  DDRB = (1<<PB1); // Pin PB1 ist Ausgang
28
  
29
  
30
  uart_puts("\n######## Test ######## \n");
31
  _delay_ms(20);
32
  
33
  sendUART("global_i", global_i);
34
35
  
36
  global_i = 6;
37
  
38
  sendUART("global_i", global_i);
39
  
40
  int i_from_flash   = *(_etext + ((const char*)&global_i   - __data_start));
41
42
  
43
  sendUART("i_from_flash", i_from_flash);
44
45
}
46
47
void sendUART(const char *varName, int val){
48
  uart_puts(varName);
49
  char buffer [30];
50
  _delay_ms(20);
51
  sprintf(buffer," = %d\n",val);
52
  uart_puts(buffer);
53
  _delay_ms(20);
54
}

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.

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
  volatile int i_from_flash   = *((const int*)(_etext + ((const char*)&global_i   - __data_start)));

Aber warum schreibst Du nicht einfach:
1
#define I_INIT 3
2
3
volatile int  global_i   = I_INIT;
4
5
...
6
7
global_i = 6;
8
9
...
10
11
global_i = I_INIT;

Hast Du auch die Beiträge über die Verwendung des EEProm gelesen? Was 
ist Dir daran nicht klar?

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Nick M. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Nick M. (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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
int a = 3;
2
int b = 0;
3
int c = 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
int a;
2
int b;
3
int c;
4
5
void init_values(void)
6
{
7
   a = 3;
8
   b = 0;
9
   c = 5;
10
}
11
12
int main()
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.

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> Der TO hat das verstanden

Das hat er bisher gut verborgen.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Was Du hier vorschlägst, steht schon gaaanz oben in diesem Thread.

… was ich deshalb auch explizit nochmal dazugeschrieben habe!

Rolf M. schrieb:
> wie schon in Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"
> beschrieben.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Was Du hier vorschlägst, steht schon gaaanz oben in diesem Thread.
>
> … was ich deshalb auch explizit nochmal dazugeschrieben habe!

Es steht sogar hier noch weiter oben:

Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"

oder hier:

Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"

Und wenn Du Dich an C++ störst:

Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"

von Rolf M. (rmagnus)


Lesenswert?

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.

von Max G. (l0wside) Benutzerseite


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

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.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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
1
extern const char __data_start;
2
extern const char __flash _etext;
3
...
4
volatile int i_from_flash = *((const __flash int*)(&_etext + ((char*)&global_i - &__data_start)));

funktioniert es jetzt, wie es soll.

Alle Anmerkungen zu Sinn oder Unsinn dieser Fingerübung behalten 
natürlich ihre Gültigkeit ;)

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

Oliver S. schrieb:
> funktioniert es jetzt, wie es soll.

Hast du mal probiert, was passiert, wenn du
1
volatile int  global_i   = 3;
durch
1
volatile int  global_i   = 0;
ersetzt? Wie schon gesagt: Auch explizit mit 0 initialisierte Variablen 
werden nicht aus dem Flash vorgeladen.

von Oliver S. (oliverso)


Lesenswert?

Auf besonderen Wunsch eines einzelnen Herren:
1
  int i_from_flash = 0;
2
  if ((char*)&global_i < &__bss_start)
3
    i_from_flash = *((const __flash int*)(&_etext + ((char*)&global_i - &__data_start)));

Oliver

von Peter D. (peda)


Lesenswert?

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.

von Oliver S. (oliverso)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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++.

von DPA (Gast)


Lesenswert?

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:
1
int reset_var(size_t size, void* var){
2
  extern const char __bss_start[];
3
  extern const char __bss_end[];
4
  extern const char __data_start[];
5
  extern const char __data_end[];
6
  extern const __flash char _etext[];
7
  char* buf = var;
8
  if(buf < __bss_end && buf >= __bss_start){
9
    memset(buf, 0, size);
10
    return 0;
11
  }
12
  if(buf < __data_end && buf >= __data_start){
13
    memcpy(buf, _etext, size);
14
    return 0;
15
  }
16
  return -1;
17
}

von Kaj (Gast)


Lesenswert?

Peter D. schrieb:
> #include <array> ist vermutlich auch wieder kein Standardheader von C++.
https://en.cppreference.com/w/cpp/container/array

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
  constexpr std::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
auto global{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
typedef void (*func)(void);
4
5
typedef struct {
6
  float fl;
7
  uint8_t by;
8
  char ar[10000];
9
  func fp;
10
} my_struct_t;
11
12
void blub(void) {}
13
14
__flash const my_struct_t flash_data = {
15
  1.2,
16
  123,
17
  "Hallo",
18
  blub,
19
};
20
21
my_struct_t ram_data;
22
23
void load_struct(void) {
24
  ram_data = flash_data;
25
}
26
27
int main( 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).

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

@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.

von Oliver S. (oliverso)


Lesenswert?

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

von Robert M. (molle_ghc)


Angehängte Dateien:

Lesenswert?

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
1
#define set_bit(var, bit) (var) |= (1 << (bit))
2
#define clear_bit(var, bit) (var) &= ~(1 << (bit))
3
#define toggle_bit(var,bit) ((var) ^= (1 << (bit)))
4
5
#include <avr/io.h>
6
#include <avr/interrupt.h>
7
#include <stdio.h>
8
#include <util/delay.h>
9
#include <avr/pgmspace.h>
10
#include "libs/uart.h"
11
12
/* Define UART buad rate here */
13
#define UART_BAUD_RATE      9600   
14
15
16
int  global_i   = 3;
17
int  global_k   = 99;
18
int  global_m   = 11;
19
20
// "__flash" puts the variable into the flash
21
extern const __flash char  __data_start;
22
extern const __flash char  _etext;
23
24
void sendUART(const char*, int);
25
void sendUARTHex(const char*, uint16_t);
26
int initValue(int*);
27
28
int main(void) {
29
30
  uart_init(UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU));
31
  sei();   
32
  
33
  while(1){
34
    uart_puts("\n######## Test ######## \n");
35
    _delay_ms(20);
36
    
37
38
    // send data from RAM
39
    sendUART("global_i", global_i);
40
    sendUART("global_k", global_k);
41
    sendUART("global_m", global_m);
42
    uart_puts("\n");
43
    
44
    // change values
45
    global_k = 3*global_i;
46
    global_i = 2*global_i;
47
    global_m= global_k+global_i;
48
    
49
    // send initial values from flash
50
    sendUART("i_from_flash", initValue(&global_i)); 
51
    sendUART("k_from_flash", initValue(&global_k)); 
52
    sendUART("m_from_flash", initValue(&global_m)); 
53
    uart_puts("\n");
54
    
55
    // send some addresses from flash and RAM
56
    sendUARTHex("_etext", (uint16_t) &_etext); 
57
    sendUARTHex("__data_start", (uint16_t) &__data_start); 
58
    sendUARTHex("global_i", (uint16_t) &global_i); 
59
    uart_puts("\n################## \n");
60
    
61
    _delay_ms(5000);
62
  }
63
64
}
65
66
/* UART send function for int */
67
void sendUART(const char *varName, int val){
68
  uart_puts(varName);
69
  char buffer [30];
70
  _delay_ms(20);
71
  sprintf(buffer," = %d\n",val);
72
  uart_puts(buffer);
73
  _delay_ms(20);
74
}
75
/* UART send function for uint32_t in hex format*/
76
void sendUARTHex(const char *varName, uint16_t val){
77
  uart_puts(varName);
78
  char buffer [30];
79
  _delay_ms(20);
80
  sprintf(buffer," = 0x%04x\n",val);
81
  uart_puts(buffer);
82
  _delay_ms(20);
83
}
84
85
/* "(const __flash int* )" casts a pointer to the flash address space of type int
86
pointer calculated as last .text address + RAM address of number - start address of RAM (0x0100 for ATmega328p)*/
87
int initValue(int* number){
88
  int result   = *((const __flash int* )(&_etext + ((char*)number  - &__data_start)));
89
  
90
  return result;
91
}

Ausgabe:

######## Test ########
global_i = 384
global_k = 576
global_m = 960

i_from_flash = 3
k_from_flash = 99
m_from_flash = 11

_etext = 0x09cc
__data_start = 0x0100
global_i = 0x0104

von Walter S. (avatar)


Lesenswert?

Robert M. schrieb:
> int  global_k   = 99;

wie ist die Ausgabe mit
int  global_k   = 0;

von Wilhelm M. (wimalopaan)


Lesenswert?

Da es im g++ im Gegensatz zum gcc __flash als named-address-space für 
AVR nicht gibt, geht das wie in

Beitrag "Re: Initialwert von globalen Variablen zur Laufzeit verwenden"

beschrieben in C++ natürlich nicht.

Der idiomatische C++-Weg ware dann wohl eher:
1
#include <mcu/avr.h>
2
#include <etl/stringbuffer.h>
3
#include <array>
4
#include <math.h>
5
6
namespace  {
7
    struct Data {
8
        float fl{1.23f};
9
        uint8_t by{123};
10
        etl::StringBuffer<100u> ar{"abc"_pgm};
11
    };
12
    Data ram_data;
13
}
14
15
int main() {
16
    // Veränderungen von ram_data;
17
18
    ram_data = Data{}; // reset all
19
20
}

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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

von Yalu X. (yalu) (Moderator)


Lesenswert?

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:
1
#include <stdint.h>
2
#include <avr/pgmspace.h>
3
4
struct Data {
5
  float fl{1.23f};
6
  uint8_t by{123};
7
  uint8_t s[100]{
8
     1,   2,   3,   4,   5,   6,   7,   8,   9,  10,
9
    11,  12,  13,  14,  15,  16,  17,  18,  19,  20,
10
    21,  22,  23,  24,  25,  26,  27,  28,  29,  30,
11
    31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
12
    41,  42,  43,  44,  45,  46,  47,  48,  49,  50,
13
    51,  52,  53,  54,  55,  56,  57,  58,  59,  60,
14
    61,  62,  63,  64,  65,  66,  67,  68,  69,  70,
15
    71,  72,  73,  74,  75,  76,  77,  78,  79,  80,
16
    81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
17
    91,  92,  93,  94,  95,  96,  97,  98,  99, 100
18
  };
19
};
20
21
Data ram_data;
22
23
int main() {
24
  ram_data = Data{}; // reset all
25
}

1
$ avr-g++ -std=c++2a -mmcu=atmega88 -Wall -Os -save-temps -o avrtest avrtest.cpp
2
$ avr-size avrtest
3
   text    data     bss     dec     hex filename
4
    120     210       0     330     14a avrtest

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:
1
#include <stdint.h>
2
3
struct Data {
4
  float fl;
5
  uint8_t by;
6
  char s[100];
7
};
8
9
__flash const struct Data default_data = {
10
  1.23f, 123, {
11
    1,   2,   3,   4,   5,   6,   7,   8,   9,  10,
12
    11,  12,  13,  14,  15,  16,  17,  18,  19,  20,
13
    21,  22,  23,  24,  25,  26,  27,  28,  29,  30,
14
    31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
15
    41,  42,  43,  44,  45,  46,  47,  48,  49,  50,
16
    51,  52,  53,  54,  55,  56,  57,  58,  59,  60,
17
    61,  62,  63,  64,  65,  66,  67,  68,  69,  70,
18
    71,  72,  73,  74,  75,  76,  77,  78,  79,  80,
19
    81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
20
    91,  92,  93,  94,  95,  96,  97,  98,  99, 100
21
  }
22
};
23
24
struct Data ram_data;
25
26
int main(void) {
27
  ram_data = default_data;
28
}

1
$ avr-gcc -mmcu=atmega88 -Wall -Os -save-temps -o avrtest-c avrtest-c.c
2
$ avr-size avrtest-c
3
   text    data     bss     dec     hex filename
4
    220       0     105     325     145 avrtest-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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

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?

von Wilhelm M. (wimalopaan)


Angehängte Dateien:

Lesenswert?

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
typedef struct {
4
  float fl;
5
  uint8_t by;
6
  char ar[100];
7
} my_struct_t;
8
9
__flash const my_struct_t flash_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
static my_struct_t ram_data = flash_data;
27
28
inline static void load_struct(void) {
29
  ram_data = flash_data;
30
}
31
32
int main( void ) {
33
  load_struct();
34
  
35
  return ram_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.
1
#include <mcu/avr.h>
2
#include <array>
3
#include <cmath>
4
#include <memory>
5
#include <etl/stringbuffer.h>
6
#include <etl/algorithm.h>
7
#include <mcu/pgm/pgmarray.h>
8
#include <mcu/pgm/pgmstring.h>
9
10
namespace  {
11
    struct SineGenerator {
12
        constexpr auto operator()() {
13
            std::array<uint8_t, 100> data{};
14
            for(size_t i{0}; auto& e : data) {
15
                e = std::numeric_limits<decltype(data)::value_type>::max() * (1.0 + sin((2.0 * 3.14 * i++) / data.size())) / 2.0;
16
            }
17
            return data;
18
        }        
19
    };
20
    struct Data {
21
        Data(const Data&) = delete;
22
        Data& operator=(const Data&) = delete;
23
        
24
        inline Data() {}
25
                
26
        using pgm_type = typename AVR::Pgm::Util::Converter<SineGenerator>::pgm_type;
27
        
28
//        using pgm_type = AVR::Pgm::Array<uint8_t, 
29
//            1,   2,   3,   4,   5,   6,   7,   8,   9,  10,
30
//           11,  12,  13,  14,  15,  16,  17,  18,  19,  20,
31
//           21,  22,  23,  24,  25,  26,  27,  28,  29,  30,
32
//           31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
33
//           41,  42,  43,  44,  45,  46,  47,  48,  49,  50,
34
//           51,  52,  53,  54,  55,  56,  57,  58,  59,  60,
35
//           61,  62,  63,  64,  65,  66,  67,  68,  69,  70,
36
//           71,  72,  73,  74,  75,  76,  77,  78,  79,  80,
37
//           81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
38
//           91,  92,  93,  94,  95,  96,  97,  98,  99, 100
39
//        >;
40
        
41
        inline void reset() {
42
            new(this) Data();
43
        }
44
        
45
        float fl{1.23f};
46
        uint8_t by{123};
47
//        etl::StringBuffer<100u> s{"hello"_pgm};
48
        std::array<pgm_type::value_type, pgm_type::size()> ar = std::to_array(pgm_type{});
49
    };
50
    
51
    Data ram_data;
52
}
53
54
int main() {
55
    ram_data.reset(); // statt: ram_data = Data{];
56
    
57
    return ram_data.ar[10];
58
}

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
static my_struct_t ram_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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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
int main() {
2
    new (&ram_data) Data(pointer_to_pgm); // wie reset()
3
}

benutzen und in Data ein ctor haben wie
1
struct Data {
2
    explicit Data(AVR::Pgm::Ptr p) {
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.

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.