Forum: Mikrocontroller und Digitale Elektronik Look-Up Table wie speichern


von Sebastian R. (lange_leitung)


Lesenswert?

Hallo Leute.

Mir fällt einfach keine zufriedenstellende Lösung ein. Da brauch ich mal 
eure Ideen/Rat:

Ich habe einen ATmega32U4. In meinem Programm (C) möchte ich eine 
Look-Up-Tabelle anlegen. Der Controller bekommt vom Benutzer einen Wert 
übergeben, der die Anzahl der Einträge in der LUT darstellt. Im 
Initialisierungsprozess sollen dann die Werte für die LUT berechnet 
werden. Das so erhaltene Array soll dann allen Funktionen zur Verfügung 
stehen (soll also global sein).

Meine Ideen:
1) Das Array wird erst in der Init-Funktion erzeugt; da ist die Anzahl 
der Elemente schon bekannt. Aber wie bekomme ich es hin, dass das Array 
beim Beenden der Funktion nicht verschwindet? static.... klappt nicht.

2) Das Array außerhalb der Funktionen erzeugen. Da kenn ich aber die 
Länge noch nicht, und muss es immer mit der maximalen Länge anlegen.

Fällt euch was gutes ein?

Danke!!!!
Sebastian

von Dr. Sommer (Gast)


Lesenswert?

Sebastian R. schrieb:
> Aber wie bekomme ich es hin, dass das Array beim Beenden der Funktion
> nicht verschwindet? static.... klappt nicht.

Als globale Variable anlegen, und in der Funktion nur den benötigten 
Teil füllen.

Sebastian R. schrieb:
> Der Controller bekommt vom Benutzer einen Wert übergeben, der die Anzahl
> der Einträge in der LUT darstellt

Warum kannst du die LUT nicht fix im Flash anlegen für die maximale 
Anzahl an Einträgen?

Sebastian R. schrieb:
> Da kenn ich aber die Länge noch nicht, und muss es immer mit der
> maximalen Länge anlegen

Na und? Würdest du, wenn die LUT kleiner ist, den Speicher für 
irgendetwas anderes benutzen, oder wäre er dann leer? Für unbenutzten 
Speicher gibt es kein Geld zurück...

von MaWin (Gast)


Lesenswert?

Sebastian R. schrieb:
> Aber wie bekomme ich es hin, dass das Array
> beim Beenden der Funktion nicht verschwindet?

Globalen Pointer auf Array und global die Anzahl der Einträge speichern 
?

Das ist das Prinzip globaler Variablen: Sie überleben das Ende der 
Funktion und stehen allen Funktionen zur Verfügung.

von Markus F. (mfro)


Lesenswert?

Wenn Du in main() alloca() aufrufst, bleibt der Speicher erhalten, bis 
main() verlassen wird (was ja üblicherweise nicht passiert) und Du 
kannst ihn genauso groß machen, wie Du ihn haben willst.

Disclaimer: bevor ich jetzt wieder Haue bekomme, weise ich darauf hin, 
daß ich keine Ahnung habe, wie Stack-Allocation beim AVR funktioniert.

von Sebastian R. (lange_leitung)


Lesenswert?

Dr. Sommer schrieb:
> Warum kannst du die LUT nicht fix im Flash anlegen für die maximale
> Anzahl an Einträgen?

Oh, da fehlt noch eine Erklärung meinersteits:
Ich würde es gerne so halten, dass ich im Array immer zum nächsten Feld 
gehen kann, und nicht in Intervallen springen muss. Das kommt daher, 
dass das Array immer durchlaufen werden muss. Im Grunde soll eine 
vorgegebene Spannungskurve abgefahren werden; und diese Kurve ist eben 
in der LUT abgelegt.

> Sebastian R. schrieb:
>> Da kenn ich aber die Länge noch nicht, und muss es immer mit der
>> maximalen Länge anlegen
>
> Na und? Würdest du, wenn die LUT kleiner ist, den Speicher für
> irgendetwas anderes benutzen, oder wäre er dann leer? Für unbenutzten
> Speicher gibt es kein Geld zurück...

Gutes Argument!! Ich mach´s so, dass ich immer den maximalen Platz 
reserviere, und nur den benötigten fülle, um obigen Punkt zu erreichen.

DANKE!!!
Sebastian

von Dr. Sommer (Gast)


Lesenswert?

Sebastian R. schrieb:
> Ich würde es gerne so halten, dass ich im Array immer zum nächsten Feld
> gehen kann, und nicht in Intervallen springen muss

Das fände ich jetzt aber nicht ist schlimm. So ein Sprung (d.h. 
Addition) ist auch nicht so teuer. Da auf dem AVR sowieso keine 
DMA-Angelegenheiten mit rein spielen...

Markus F. schrieb:
> Wenn Du in main() alloca() aufrufst, bleibt der Speicher erhalten, bis
> main() verlassen wird (was ja üblicherweise nicht passiert) und Du
> kannst ihn genauso groß machen, wie Du ihn haben wi

Aber wozu? Auf diese Art nicht reservierter Speicher ist kaum 
anderweitig nutzbar, und somit auch praktisch "belegt". Daher ist das 
i.A. eher nutzlos.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falls

1) Nur ein solches Array bzw. Objekt benötigt wird,

2) Kein malloc o.ä. verwendet wird (alloca() ist kein Problem),

dann kann das so gemacht werden:
1
// values.h
2
3
typedef struct
4
{
5
    int value;
6
} value_t;
7
8
extern value_t values[] __asm ("__heap_start");
9
10
// values .c
11
12
void compute_values (int n_values)
13
{
14
    for (int i = 0; i < n_values; i++)
15
    {
16
        values[i].value = i;
17
    }
18
}

Erklärung: Das Speicherlayout bei den avr-Tools ist folgendes:  Ganz 
unten wird Static Storage lokatiert (.data, .bss, .rodata).  Darauf 
folgt der Heap (verwendet von malloc() etc.), der startend ab 
__heap_start nach oben hin dem Stack entgegenwächst.  Darauf folgt Platz 
für den Stack, der startend von RAMEND nach unten wächst.

__heap_start ist ein Symbol, das im Linker-Skript definiert wird.  Es 
markiert das untere Ende des Heaps.  Die obige Deklaration nutzt diesen 
Speicherbereich für ein Array values[], dessen Größe zur Designzeit 
nicht bekannt sein muss.

Wie bereits erwähnt, muss immer ein Array der Maximalgröße in den 
Speicher passen, ohne dass der Stack zu weit nach unter wächst und so 
Werte im Heap oder im Static Storage überschreibt (oder umgekehrt).

Auf den Speicherbereich könnte man auch zugreifen per
1
extern values_t __heap_start[]; // Defined in Linker Skript.
Die Deklaration über __asm hat den Vorteil, dass dann bequem über die 
Arrayvariable values[] zugegriffen werden kann.

__asm legt den Assembler-Name eines Objekts / einer Funktion fest, d.h. 
den Symbolnamen, den der Compiler im Assemblercode für das Objekt / die 
Funktion verwendet.  Für den Code von oben:
1
compute_values:
2
  ldi r30,lo8(__heap_start)
3
  ldi r31,hi8(__heap_start)
4
  ldi r19,0
5
  ldi r18,0
6
  rjmp .L2
7
.L3:
8
  st Z+,r18
9
  st Z+,r19
10
  subi r18,-1
11
  sbci r19,-1
12
.L2:
13
  cp r18,r24
14
  cpc r19,r25
15
  brlt .L3
16
  ret

Weil das Array außerhalb der Funktion gültig bleiben soll, ist alloca() 
keine Option.  malloc() hat den einzigen Vorzeil, dass es (im Gegensatz 
zu __asm) standardkonform ist, aber das Problem mit der Maximalgröße 
löst malloc() natürlich auch nicht sondern verschärft es eher.

Static Storage löst das Problem nur insofern, als man sich bei 
Festlegung der (Maximal-)Größe zur Designzeit (scheinbar) keinen 
Gedanken über den sonstigen Speicherverbrauch — insbesondere Stack — zu 
machen braucht.  Aber natürlich brauch man dieses Wissen bei allen 
Ansätzen, um zu einer soliden Maximalgröße zu kommen.

von Maxim B. (max182)


Lesenswert?

Sebastian R. schrieb:
> Fällt euch was gutes ein?

1
const __flash uint8_t deinding[] = {
2
1,2,3,......0ff
3
};

Noch besser:
in einem Modul:
1
// deinding.h
2
3
uint8_t hole_deinding(uint16_t argument);
4
5
6
// deinding.c
7
...
8
#include "max7219_zif.h"
9
10
static const __flash uint8_t deinding[] = {
11
1,2,3,......0ff
12
};
13
14
uint8_t hole_deinding(uint16_t argument){
15
   return deinding[argument];
16
}

Dafür muß du aber keine frühere GCC haben, die unbedingt PROGMEM braucht 
und __flash nicht versteht. Dann geht es natürlich auch mit PROGMEM, nur 
ein bißchen aufwendiger.

von Markus F. (mfro)


Lesenswert?

Johann L. schrieb:
> Weil das Array außerhalb der Funktion gültig bleiben soll, ist alloca()
> keine Option.

alloca() muß nur innerhalb einer Funktion aufgerufen werden, die nie 
verlassen wird. main() erfüllt üblicherweise diese Anforderung.

von Maxim B. (max182)


Lesenswert?

Ups... ein Fehler:
statt #include "max7219_zif.h" sollte natürlich #include "deinding.h" 
stehen.

Vorteil: die Tabelle wird von keinem anderen Modul gesehen und Zugang 
ist nur über Funktion uint8_t hole_deinding(uint16_t argument) möglich. 
Das macht mehr Ordnung.

Sebastian R. schrieb:
> Ich würde es gerne so halten, dass ich im Array immer zum nächsten Feld
> gehen kann, und nicht in Intervallen springen muss.

Was sagt hier aber gegen einer Tabelle in Flash?

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Maxim B. schrieb:
> Sebastian R. schrieb:
>> Fällt euch was gutes ein?
1
> const __flash uint8_t deinding[] = {
2
> 1,2,3,......0ff
3
> };

Es soll eine von außen vorgebbare Spannungskurve abgefahren werden.

Mit __flash braucht mal also einen Bootloader-Mechanismus, um die Daten 
ins Flash zu bekommen. Und das Array müsste dann const volatile sein.

> Noch besser: in einem Modul:
1
> // deinding.h
2
> 
3
> uint8_t hole_deinding(uint16_t argument);

Ob das wirklich besser ist sei dahingestellt.  Es gibt Call-Overhead für 
eine triviale Funktion, und hold_deinding(0) o.ä. können nicht mehr zu 
Konstanten gefaltet werden (ok, bei volatile eh kein Thema mehr).

> Dafür muß du aber keine frühere GCC haben, die unbedingt PROGMEM braucht
> und __flash nicht versteht.

Mit progmem kann man das gleiche Interface implementieren wenn man denn 
will.  __flash gibt's seit avr-gcc v4.7.  Ohne __flash-Support ist der 
Compiler also schon mindestens 8 Jahre alt...

von Falk B. (falk)


Lesenswert?

Warum überhaupt ein Array im Flash? Reicht das nicht im RAM? Oder, wenn 
die Funktion zur Arrayberechnung einfach und die Zeitanforderungen 
unkritisch sind, warum nicht einfach eine Formel mit Parameter? Der kann 
dann vielleicht im EEPROM gespeichert werden.

von Oliver S. (oliverso)


Lesenswert?

2 zillionen abstruse Lösungsvorschläge, nur weil der TO malloc nicht 
kennt.

Wenn die Tabellen so groß sind, daß sie nicht mehr ins RAM passen, dann 
ist der ganze Ansatz sowieso Murks, egal, wie man das Array ins RAM 
packt.

Oliver

von Dr. Sommer (Gast)


Lesenswert?

Falk B. schrieb:
> Warum überhaupt ein Array im Flash? Reicht das nicht im RAM?

Flash ist doch meist in größeren Mengen verfügbar als RAM.

Oliver S. schrieb:
> nur weil der TO malloc nicht
> kennt.

malloc() (und alloca) ist nur unter einer Bedingung überhaupt sinnvoll: 
Wenn man den Speicher, wenn man nur wenig allokiert, anderweitig 
verwendet! Das Programm muss sowieso so konzipiert sein, dass der RAM 
ausreicht, wenn man die maximale Array-Größe allokiert. Wenn man weniger 
allokiert, kann man den Speicher dann für einen anderen Zweck 
gebrauchen? Oder liegt er brach? Da die Aufgaben der meisten Controller 
ziemlich fix sind (ala 7 Eingänge überwachen, 3 Ausgänge schalten, 1 
UART bedienen) wird man Speicher selten um-widmen wollen; es sei denn 
man baut etwas sehr flexibles (ala N Eingänge, M Ausgänge mit N+M <= 
10). Selbst in diesem Fall ist es manchmal besser/einfacher den Speicher 
via "union" umzuwidmen.

von Sebastian R. (lange_leitung)


Lesenswert?

Ich seh schon, beim Thema Speicher sollte man sich Gedanken machen....

Ich begrenze jetzt die Länge der LUT auf 256 Elemente. Als Schrittgrößen 
beim Abfahren lasse ich nur 2^x zu. Dadurch lässt sich das Springen von 
Feld zu Feld schön umsetzen.
Ein float Array würde somit 1kB belegen. Das passt noch gut in den RAM 
(2kB).
Wie merke ich, wenn es im RAM Konflikte gibt? Also wenn ich z.B. 95% des 
RAM belegen würde, und dann im Programmablauf der RAM nicht mehr reicht? 
Absturz?

Sebastian

von Marten Morten (Gast)


Lesenswert?

Sebastian R. schrieb:
> 2) Das Array außerhalb der Funktionen erzeugen. Da kenn ich aber die
> Länge noch nicht, und muss es immer mit der maximalen Länge anlegen.

Macht doch nichts. Um mal einen alten Spruch zu bringen, für gesparten 
Speicher im Controller gibt es keine Rückerstattung.

Sebastian R. schrieb:
> Wie merke ich, wenn es im RAM Konflikte gibt? Also wenn ich z.B. 95% des
> RAM belegen würde, und dann im Programmablauf der RAM nicht mehr reicht?
> Absturz?

Genau das bei einfachen Mikrocontrollern.

Wenn man sehr sorgfältig arbeiten möchte oder muss überlegt und 
berechnet man vorher den Stack-Bedarf, verzichtet auf malloc() und 
Konsorten und prüft den Stack-Bedarf zur Laufzeit. Sei es wenigstens 
einmalig mit dem Debugger, oder im Programm zur Laufzeit.

Die Technik zur Überprüfung zur Laufzeit bei einfachen Mikrocontrollern 
ist leider etwas primitiv. Man verwendet eine Pufferzone, in die man ein 
Datenmuster schreibt. Das Datenmuster sollte so gewählt werden, dass es 
nicht typischen Stack-Daten entspricht. Was schwer ist, aber zumindest 
sollte es nicht nur 0x00, 0x01 oder 0xFF enthalten.

Das Vorhandensein des Datenmusters überprüft man regelmäßig. Ist es 
zerstört ist der Stack zu groß geworden oder irgendwas hat wild im RAM 
rumgeschrieben. Je nach Anwendung stoppt man dann den Mikrocontroller 
oder startet ihn neu.

Um das Risiko zu minimieren sollte man auf Funktionen, die viel Stack 
oder dynamischen Speicher benötigen, verzichten. Zum Beispiel kein 
printf() und keine Rekursion. Daten per Pointer, nicht per Kopie 
übergeben. Funktionen mit wenigen Argumenten verwenden. Wenige lokale 
Variablen verwenden.

Eine brutale Variante ist es, für Daten grundsätzlich nur statische und 
globale Variablen zu verwenden. Der Preis dafür ist, dass jede 
vernünftige Programmstruktur zum Teufel geht. Richtig wild wird es, wann 
man die statischen und globalen Variablen mehrfach verwendet.

von Dieter R. (drei)


Lesenswert?

Sebastian R. schrieb:

> Wie merke ich, wenn es im RAM Konflikte gibt? Also wenn ich z.B. 95% des
> RAM belegen würde, und dann im Programmablauf der RAM nicht mehr reicht?
> Absturz?

Da: Beitrag "RAM Verbrauch auch von lokalen variablen ermitteln"

wurde das gerade diskutiert. Die richtig elegante Lösung, eingebettet in 
die Atmel-Entwicklungsumgebung, fehlt noch. Schade, ich hätte sie (die 
Lösung) auch gerne.

von Maxim B. (max182)


Lesenswert?

Johann L. schrieb:
> Ob das wirklich besser ist sei dahingestellt.  Es gibt Call-Overhead für
> eine triviale Funktion, und hold_deinding(0) o.ä. können nicht mehr zu
> Konstanten gefaltet werden (ok, bei volatile eh kein Thema mehr).

Wenn man die Tabelle in RAM hält, dann besetzt die Tabelle zweimal 
Speicherplatz: in RAM und in Flash, woher sie beim Starten in RAM 
geladen wird. Call-Overhead kann man vermeiden:
1
 static __inline__ uint8_t hole_deinding(uint16_t argument) __attribute__((__always_inline__));
Dann sollte die Funktion aber in *.h stehen.


Notfalls gibt es noch EEPROM

Johann L. schrieb:
> Mit progmem kann man das gleiche Interface implementieren wenn man denn
> will.

Ja, Assembler-Code bekommt man identisch. Nur kann man bei PROGMEM nicht 
die gleichen Funktionen wie für RAM benutzen, man sollte immer über eine 
besondere Funktion gehen. Das ist weniger bequem.

: Bearbeitet durch User
von Sebastian R. (lange_leitung)


Lesenswert?

Maxim B. schrieb:
> Wenn man die Tabelle in RAM hält, dann besetzt die Tabelle zweimal
> Speicherplatz: in RAM und in Flash

Das habe ich auch gerade festgestellt. Ich lager die Berechnung jetzt 
doch aus (auf PC) und leg nur eine konstante LUT ab. Das kann dann ja 
ruhig in den Flash. Da ist mir aber noch was unklar:

Die Tabelle zeigt den Speicherbedarf (gerundet) bei den 
unterschiedlichen Arten, die Variable anzulegen (immer außerhalb jeder 
Funkntion!). Die Werte zeigt mit Atmel Studio an.

                                         Flash [byte]   RAM [byte]
nur Programm                             400            0
int Array[256]                           900            500

const int Array[256]                     400            0
const __flash int Array[256]             400            0

volatile const int Array[256]            900            500
volatile const __flash int Array[256]    900            0

Ok, volatile brauch ich nicht, weil die Variable ja nicht mehr geändert 
werden soll, aber ich versteh den Speicherbedarf nicht.

Die ersten und die letzten beiden Varianten sind klar. Aber wo wird die 
Variable bei den mittleren Varianten gespeichert???? In main hab ich 
immer Zugriff drauf....

Sebastian

von Maxim B. (max182)


Lesenswert?

Sebastian R. schrieb:
> Aber wo wird die
> Variable bei den mittleren Varianten gespeichert????

Sie wird erst gespeichert, wenn in Programm benutzt.
Du hast sicher die Tabelle gelegt, aber noch keine Funktion, die die 
Tabelle liest.

Der Compiler ist gar nicht dumm!

Was mir aber komisch erscheint: warum für int Array[256] nur 500 bytes 
verbraucht wird und nicht 512 ?

: Bearbeitet durch User
von Sebastian R. (lange_leitung)


Lesenswert?

Maxim B. schrieb:
> Was mir aber komisch erscheint: warum für int Array[256] nur 500 bytes
> verbraucht wird und nicht 512 ?

Das liegt dadran, dass ich die Werte gerundet hab! Es sind eigentlich 
schon 512 bytes!!!

Maxim B. schrieb:
> Sie wird erst gespeichert, wenn in Programm benutzt.
> Du hast sicher die Tabelle gelegt, aber noch keine Funktion, die die
> Tabelle liest.
>
> Der Compiler ist gar nicht dumm!

Das ist mir auch aufgefallen, darum hab ich in
1
main()
2
{
3
....
4
wert=Array[1];
5
wert=Array[2];
6
}
stehen. Und wenn ich das Programm simuliere, ändert sich der Wert von 
wert auch passend. Die Variable muss also bekannt sein. Aber wo liegt 
sie????

von Maxim B. (max182)


Lesenswert?

Sebastian R. schrieb:
> Das ist mir auch aufgefallen, darum hab ich inmain()
> {
> ....
> wert=Array[1];
> wert=Array[2];
> }
> stehen. Und wenn ich das Programm simuliere, ändert sich der Wert von
> wert auch passend. Die Variable muss also bekannt sein. Aber wo liegt
> sie????

Der Compiler macht hier höchstwahrscheinlich keine Arrays, sondern 
assembliert etwa so:
ldi r16, 5
ldi r17, 51

Wenn alle Werte schon vor dem Ablauf bekannt sind, kuckt Compieler, was 
ihm besser ist: wirkliche Tabelle zu machen oder direkt auf notwendigen 
Stellen direkte Zahlen zu laden.
Anders wird es, wenn an diese Tabelle gerufen wird mit vor dem 
Programmablauf unbekannten Index.
Z.B.:

wert=Array[i];
wert=Array[x];

dabei sollte i und x erst im Programm berechnet werden.

Ja, außer Zauberwort "volatile" gibt es in GCC noch fantastische Mittel: 
Inline-Assembler. Man kann Stückchen von Code genau so gestalten wie man 
braucht.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian R. schrieb:
> Ich seh schon, beim Thema Speicher sollte man sich Gedanken machen....
>
> Ich begrenze jetzt die Länge der LUT auf 256 Elemente. Als Schrittgrößen
> beim Abfahren lasse ich nur 2^x zu. Dadurch lässt sich das Springen von
> Feld zu Feld schön umsetzen.

Wenn es keine 2er-Potenz ist, ist das auch kein Problem:

Hat das Array N Elemente, ist der letzte Zugriff auf Element Index 
gewesen und soll auf (Index + Step) mod N zugegriffen werden, dann 
bekommt man den neuen Index einfach durch
1
Index := Index + Step
2
if Index >= N
3
   Index := Index - N
Falls 0 <= Step < N.

Bei den allermeisten Anwendungen kommt es nicht auf die handvoll Ticks 
mehr an.  Damit gibt es dann weder Einschränkung bezüglich Schrittweite 
noch bezüglich Anzahl Elemente.

Ist die Elementanzahl eine Zweierpotenz, kann man den Wrap-Around 
erreichen durch
1
Index := (Index + Step) & (N - 1)
2er-Potenzen als Schrittweite bringen hier keinerlei Vorteil.

> Wie merke ich, wenn es im RAM Konflikte gibt?

Auf den Thread neulich wurde ja schon verwiesen.  Konkret für avr-gcc 
lässt sich get_mem_unused() von hier nutzen:

https://rn-wissen.de/wiki/index.php?title=Speicherverbrauch_bestimmen_mit_avr-gcc#Dynamischer_RAM-Verbrauch

Ist zwar hausbacken und nicht für kritische Anwendungen geeignet, aber 
immerhin gibt es eine gute Vorstellung vom Stackverbrauch und belegt nur 
ein paar Bytes Code (42 wenn ich mich recht erinnere).  Ist allerdings 
nicht kompatibel mit malloc().


Sebastian R. schrieb:
> Die Tabelle zeigt den Speicherbedarf (gerundet) bei den
> unterschiedlichen Arten, die Variable anzulegen (immer außerhalb jeder
> Funkntion!). Die Werte zeigt mit Atmel Studio an.
1
>                                          Flash [byte]   RAM [byte]
2
> nur Programm                             400            0
3
> int Array[256]                           900            500
4
> 
5
> const int Array[256]                     400            0
6
> const __flash int Array[256]             400            0
7
> 
8
> volatile const int Array[256]            900            500
9
> volatile const __flash int Array[256]    900            0
> Ok, volatile brauch ich nicht, weil die Variable ja nicht mehr geändert
> werden soll, aber ich versteh den Speicherbedarf nicht.
>
> Die ersten und die letzten beiden Varianten sind klar. Aber wo wird die
> Variable bei den mittleren Varianten gespeichert???? In main hab ich
> immer Zugriff drauf....

Bei Variante 2 und 3 wurden die Arrays wegoptimiert, weil sie (effektiv) 
nicht referenziert werden.  Ob bereits der Compiler das Objekt rauswirft 
oder der Linker mit --gc-sections hängt vom konkreten Fall ab.  Der 
Linker listet rausgekickte Objekte im Map-File auf.

GCC lokatiert volatile const in die .data Section, also ins RAM.  Wenn 
das Objekt ins Flash soll brauch's ein extra Section-Attribut nach 
.rodata (bei Devices mit linearem Speichermodell wie ATtiny40 oder 
ATmega4808) oder nach .progmem.data o.ä. (bei nicht-linearem 
Speichermodell wie bei deinem ATmega32U4) wenn die Daten kein RAM 
belegen sollen.

Sebastian R. schrieb:
> Das ist mir auch aufgefallen, darum hab ich in
1
> main()
2
> {
3
> ....
4
> wert=Array[1];
5
> wert=Array[2];
6
> }
> stehen. Und wenn ich das Programm simuliere, ändert sich der Wert von
> wert auch passend. Die Variable muss also bekannt sein. Aber wo liegt
> sie????

Kommt drauf an; hier kennt der Compiler die Werte und braucht keine 
Zugriffe ausführen (das ist ein Vorteil von __flash gegenüber PROGMEM: 
Im Gegensatz zu __flash ist pgm_read_xxx() nicht transparent).

Wenn Array[] in main() bekannt ist, dann passiert das:

Der Compiler faltet die Zugriffe, und aus Array[1] wird z.B. 123 wenn 
Array[1] den Wert 123 hat.  Der Compiler referenziert das Array im 
Assembler-Code also nicht.

Ist das Array lokal, kann GCC nachweisen, dass es nicht gebraucht wird 
und legt es erst garnicht an.

Ist das Array global, wird es angelegt aber nirgends referenziert.  Der 
Linker wirft was Objekt bei -data-sections -Wl,--gc-sections raus. (Bei 
so einem kleinen Beispiel braucht's noch nichma -data-sections, weil 
kein Objekt in .rodata referenziert wird: .rodata enthält dann nämlich 
nur das eine Objekt Array[].)

Damit das Array sicher referenziert wird, geht folgender Code:
1
volatile int wert;
2
3
int main (void)
4
{
5
    wert = Array[wert];
6
}

Der Zugriff auf Array[] kann nicht gefaltet werden, und der (auf 
C-Ebene) gelesene Wert wird verwendet, so dass der Zugriff stattfinden 
muss.  Auf Compiler-Ebene bedeutet dies, dass main() auf Array[] 
zugreift und der Compiler Array[] anlegen muss.  Auf Linker-Ebene 
bedeutet es, dass die Section, die Symbol Array definiert, von der 
Section, die Symbol main definiert, referenziert wird.  Und die Section, 
die Symbol main definiert, wird im Endeffekt vom Entry-Symbol 
referenziert und kann damit nicht vom Linker eliminiert werden. (Bei den 
GNU-Tools für AVR etwas anders: Symbol main wird im Endeffekt von 
Section .vectors referenziert, also von der Vektor-Tabelle; und diese 
ist KEEP im Linker-Skript.)

von Dieter R. (drei)


Lesenswert?

Johann L. schrieb:

> Auf den Thread neulich wurde ja schon verwiesen.  Konkret für avr-gcc
> lässt sich get_mem_unused() von hier nutzen:
>
> 
https://rn-wissen.de/wiki/index.php?title=Speicherverbrauch_bestimmen_mit_avr-gcc#Dynamischer_RAM-Verbrauch

Ich hab das eben überflogen und verstehe nicht, von wo und wie 
init_mem() aufgerufen wird. Kannst du das kurz erläutern? Danke!

von Falk B. (falk)


Lesenswert?

Dieter R. schrieb:
> Ich hab das eben überflogen und verstehe nicht, von wo und wie
> init_mem() aufgerufen wird. Kannst du das kurz erläutern? Danke!

Das wird automatisch vor dem start von main() aufgerufen, da kümmert 
sich der Compiler drum.

von Dieter R. (drei)


Lesenswert?

Falk B. schrieb:
> Dieter R. schrieb:
>> Ich hab das eben überflogen und verstehe nicht, von wo und wie
>> init_mem() aufgerufen wird. Kannst du das kurz erläutern? Danke!
>
> Das wird automatisch vor dem start von main() aufgerufen, da kümmert
> sich der Compiler drum.

Das heißt, die Funktion init_mem() ist ursprünglich nicht definiert, der 
Compiler lauert aber darauf, und wenn er sie irgendwo findet, dann 
ruft er sie auf? Oder müssen dabei spezielle Voraussetzungen/Mechanismen 
beachtet werden?

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Dieter R. schrieb:
> Das heißt, die Funktion init_mem() ist ursprünglich nicht definiert, der
> Compiler lauert aber darauf, und wenn er sie irgendwo findet, dann
> ruft er sie auf?

Quasi.

> Oder müssen dabei spezielle Voraussetzungen/Mechanismen
> beachtet werden?

Naja, der "Trick" liegt in den Attributen, das ist geheimes 
Compiler-Druiden Wissen ;-)
1
static void __attribute__ ((naked, used, section(".init3")))
2
init_mem (void)

von Dieter R. (drei)


Lesenswert?

Danke für den Zaubertrank. Vielleicht nützt er (mir) mal bei 
Gelegenheit.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Dieter R. schrieb:
> Falk B. schrieb:
>> Dieter R. schrieb:
>>> Ich hab das eben überflogen und verstehe nicht, von wo und wie
>>> init_mem() aufgerufen wird. Kannst du das kurz erläutern? Danke!
>>
>> Das wird automatisch vor dem start von main() aufgerufen, da kümmert
>> sich der Compiler drum.
>
> Das heißt, die Funktion init_mem() ist ursprünglich nicht definiert, der
> Compiler lauert aber darauf, und wenn er sie irgendwo findet, dann
> ruft er sie auf? Oder müssen dabei spezielle Voraussetzungen/Mechanismen
> beachtet werden?

Falk B. schrieb:
> Dieter R. schrieb:
>> Das heißt, die Funktion init_mem() ist ursprünglich nicht definiert, der
>> Compiler lauert aber darauf, und wenn er sie irgendwo findet, dann
>> ruft er sie auf?

Nicht wirklich.

Es ist Tool-abhängiges Zusammenspiel von Compiler und Linkerskript der 
Binutils:

> Naja, der "Trick" liegt in den Attributen, das ist geheimes
> Compiler-Druiden Wissen ;-)
1
> static void __attribute__ ((naked, used, section(".init3")))
2
> init_mem (void)

Nicht geheim :-)

static: Funktion wird nicht aufgerufen, braucht also nicht global zu 
sein.

used: Die Funktion wird nicht referenziert, soll aber nicht vom Compiler 
entsorgt werden.

naked: Für die Funktion wird kein Prolog / Epilog erzeugt, insbesondere 
wird die Funktion nicht mit RET beendet (*).

section(".init3"): Die Funktion wird in Section .init3 gelegt.

Dies verwendet das Codelayout des Linkerskripts für den Startup-Code der 
avr-libc und der libgcc:

http://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=ld/scripttempl/avr.sc;h=05c0b890f050fe3cb2bc3db04178fef69cfbb02a;hb=HEAD#l151

Der Code der .initN Sections wird also einfach hintereinandergeklatscht, 
daher brauch es (*). Die avr-libc definiert die Vector-Table so, dass 
Vector 0 (Reset) nach __init springt:

http://svn.savannah.nongnu.org/viewvc/avr-libc/trunk/avr-libc/crt1/gcrt1.S?revision=2519&view=markup#l50

Ab dann rauscht das Programm durch die .initN Sections von .init0 bis 
.init9 und ruft in letzterer main() auf und springt dann nach exit():

http://svn.savannah.nongnu.org/viewvc/avr-libc/trunk/avr-libc/crt1/gcrt1.S?revision=2519&view=markup#l199

.init3 wird vom C-Startup nicht verwendet, man kann also eigenen Code an 
definierter Stelle im Startup-Code platzieren und vor main() ausführen 
lassen, indem man bestimmte Sections verwendet.  libgcc steuert so die 
Initialisierungen von .data und .bss bei (.init4) sowie Aufruf 
statischer Konstruktoren (.init6), exit() und _exit() (.fini0), 
statische Destruktoren (.fini6) und eine Endlosschleife als Programmende 
in .fini0:

http://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/avr/lib1funcs.S?revision=267494&view=markup#l2349

Alternative wäre also
1
static void __attribute__((constructor))
2
init_mem (void)

und init_mem() könnte eine normale Funktion mit Prolog und Epilog sein. 
Allerdings dürfte das Muster, das ins RAM geschrieben wird, dann nicht 
bis RAMEND reichen, weil dadurch die Rücksprungadresse von init_mem() 
auf dem Stack überschrieben werden würde.  Dito für Variablen von 
init_mem(), die im Frame der Funktion leben, was etwa passiert wenn ohne 
Optimierung compiliert (und die Funktion lokale auto-Variablen hätte).

: Bearbeitet durch User
von Sebastian R. (lange_leitung)


Lesenswert?

Maxim B. schrieb:
> Der Compiler macht hier höchstwahrscheinlich keine Arrays, sondern
> assembliert etwa so:
> ldi r16, 5
> ldi r17, 51

Danke Maxim und Johann. Jetzt versteh ich das. Jaja, da denkt der 
Compiler o. Ä. ordentlich mit; das muss man allerdings schon wissen, wie 
das geht.

Wenn ich jetzt immer auf das gesamte Array zugreife, sind auch alle 
Speicherangaben logisch. Nur eine Sache ist mir noch aufgefallen: wenn 
ich das Array int Array[256] anlege und dann nur wie im Beispiel auf 
einzelen Arraywerte zugreife, müsste der Compiler o. Ä. doch auch 
merken, dass der Rest vom Array weder gelesen, noch irgendwo verändert 
wird, und damit auch alle ungenutzten Arrayelemente rausschmeißen.
Oder macht er das nicht, weil die Variable global ist, und er daher 
nicht überblicken kann, ob irgendwo eine Änderung stattfindet. Aber dann 
bräuchte man doch kein volatile, oder?

Danke für eure Erklärungen!!!!
Sebastian

von Falk B. (falk)


Lesenswert?

Sebastian R. schrieb:
> Wenn ich jetzt immer auf das gesamte Array zugreife, sind auch alle
> Speicherangaben logisch. Nur eine Sache ist mir noch aufgefallen: wenn
> ich das Array int Array[256] anlege und dann nur wie im Beispiel auf
> einzelen Arraywerte zugreife, müsste der Compiler o. Ä. doch auch
> merken, dass der Rest vom Array weder gelesen, noch irgendwo verändert
> wird, und damit auch alle ungenutzten Arrayelemente rausschmeißen.

Nur in SEHR speziellen Situationen.

> Oder macht er das nicht, weil die Variable global ist, und er daher
> nicht überblicken kann, ob irgendwo eine Änderung stattfindet.

Richtig.

> Aber dann
> bräuchte man doch kein volatile, oder?

Braucht man auch nicht. Nur wenn es um das Thema Interrupt oder 
Hardwareregister geht.

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.