Forum: Compiler & IDEs RAM "doppelt" verwenden


von Mike (Gast)


Lesenswert?

Hallo,

ich habe eine Mikrokotroller-Anwendung, die sich in zwei Teile 
aufspaltet.
Wird der Mikrokontroller gestartet, so wird Anhand eines Port-Pins 
unterschieden welcher Teil laufen soll. Aus beiden Teilen gibt es kein 
zurück mehr. (Der eine Teil übernimmt die Konfiguration, der andere Teil 
ist der eigentliche Einsatzzweck).

Nun würde ich gerne den ganzen Speicher in beiden Teilen verwenden 
können.
Da avr-gcc nicht weiß, dass diese beiden Teile unabhängig voneinander 
existieren werden globale Variablen natürlich so angelegt, dass sie sich 
nicht überschneiden.

Hat jemand eine Idee wie man die Variablen so definiert, dass sie den 
gleichen Speicher belegen können?

Ich hatte an die Definition eigener Data-Segmente gedacht, die an der 
gleichen Adressen definiert sind. Das wäre soweit OK (man muss dann 
jeder Variable explizit sagen wo sie hingehört). Aber ich würde gerne 
vermeiden fixe Adressen zu vergeben, sondern diesen Bereich gerne nach 
dem Bereich beginnen lassen, der von Variablen belegt wird, die keinem 
Bereich zugewiesen sind. (Also globale Variablen die von Kode benutzt 
werden der vor der Pin-Abfrage läuft).

Ansonsten kommt mir noch die Idee, zwei Programme (also 2 mal mit main) 
zu schreiben und diese dann zusammenzumischen. Hier habe ich aber keine 
Idee, wie ich das bewerkstellige (das Mischen). Auch gibt es Konstanten, 
die in beiden Teilen verwendet werden sollen, die dabei natürlich 
gedoppelt werden würden, was nicht so gut wäre.

Hat jemand ein solches Problem schon mal gelöst?
Oder hat jemand andere Ideen?

Grüße
Mike

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Mike schrieb:
> Hat jemand eine Idee wie man die Variablen so definiert, dass sie den
> gleichen Speicher belegen können?

Mach die Variablen halt nicht global, sondern lokal.

Dein main() kennt weiter nichts als die beiden Funktionen, die die
jeweilige Grundfunktionalität darstellen.  In diesen Funktionen sind
die Daten dann jeweils lokal und werden von dort ggf. über Zeiger
weitergereicht.

Damit sind alle Daten auf dem Stack.

von (prx) A. K. (prx)


Lesenswert?

Ungefähr so gehts auch, und ganz ohne Linker-Script:

union {
  struct {
    #include "data1.h"
  };
  struct {
    #include "data2.h"
  };
} u;

Initialisieren ist so freilich nicht drin. Jedenfalls nicht so einfach.

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> Initialisieren ist so freilich nicht drin. Jedenfalls nicht so einfach.

Hier mal die Initialisierung einer union-struct:
1
typedef union {
2
  struct{
3
    uint16_t flow_min;
4
    uint16_t temp_min;
5
    uint16_t temp_max;
6
    uint16_t press_max;
7
    uint16_t mode;
8
  }p;
9
  uint16_t v[4];
10
}ccx_wr;
11
12
ccx_wr ccx_wr_data = {
13
                        .p.flow_min  = 3000,
14
                        .p.temp_min  = 20000,
15
                        .p.press_max = 8000,
16
                        .p.temp_max  = 35000,
17
                        .p.mode = 1,
18
                     };

von oszi40 (Gast)


Lesenswert?

1.Woher weiß der jeweils ANDERE, daß diese Werte gültig sind oder nicht 
irrtümlich überschrieben werden?

2.Wie sind die physikalischen Laufzeiten? Sind die Speicher-Werte 
überhaupt zur rechten Zeit am rechten Ort? Wer schon mal einen Bus 
analysiert hat, weiß was ich meine.

von Peter D. (peda)


Lesenswert?

P.S.:
Es läßt sich aber nur eine struct initialisieren.
Die andere muß man dann bei Benutzung zuweisen.

von cybmorg (Gast)


Lesenswert?

Was fuer Aufwaende manche Leute machen, um ja ihren antiquierten C-Stil 
beibehalten zu koennen. Ein bisschen Objektorientierung: Die globalen 
Variablen jeweils in einen Kontext, je nach Anwendungsfall packt man den 
passenden Kontext auf den Stack, bevor man den Anwendungsfall aufruft. 
Je nach Geschmack uebergibt man den Kontext dorthin, wo er gebraucht 
wird oder macht ihn ueber einen globalen Pointer pro Anwendungsfall 
verfuegbar.

Overhead der Geschichte: Ein Pointer + indirekte Zugriffe auf vorher 
globale Variablen ueber diesen Pointer. Vorteil: Verzicht auf obskure 
Hacks, bessere Lesbarkeit und Wiederverwertbarkeit des Codes.

OO-Hint: Die Kontexte sind Singletons.

von Ben W. (ben_w)


Lesenswert?

hö? scheinbar verstehe ich das irgendwie falsch

du hast eine art bootloader der nach dem start nachschaut ob ein 
bestimmter pin gesetzt ist und springt dann das jeweilige Programm an?
Was hat das mit dem RAM zu tun?
Da immer nur ein Programm zur gleichen Zeit läuft steht diesem Programm 
auch der gesamte RAM zur verfügung

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Die andere muß man dann bei Benutzung zuweisen.

Würde ich in diesem Fall so machen.

const struct Data1 { // oder PROGMEM, soll ins ROM
  ...data1...;
} data1init = { ... };

union {
  struct Data1 d1;
  struct Data2 d2;
} u;

Und dann in main vor dem Dispatch
  u.d1 = data1init;

Das ist dann nicht anders als sonst auch, nur kriegt man das data1init 
im ROM normalerweise nicht zu sehen, weil im Linker+Startup versteckt.

von (prx) A. K. (prx)


Lesenswert?

Ben W. schrieb:
> du hast eine art bootloader der nach dem start nachschaut ob ein
> bestimmter pin gesetzt ist und springt dann das jeweilige Programm an?

Klappt aber nur dann, wenn die Interrupt-Vektoren nach Belieben 
definiert werden können.

von Udo S. (urschmitt)


Lesenswert?

cybmorg schrieb:
> Ein bisschen Objektorientierung: Die globalen
> Variablen jeweils in einen Kontext, je nach Anwendungsfall packt man den
> passenden Kontext auf den Stack, bevor man den Anwendungsfall aufruft.
> Je nach Geschmack uebergibt man den Kontext dorthin, wo er gebraucht
> wird oder macht ihn ueber einen globalen Pointer pro Anwendungsfall
> verfuegbar.

Und das funktioniert (RAM)platzsparend auf einem µC mit sehr begrenztem 
RAM?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Udo Schmitt schrieb:
> Und das funktioniert (RAM)platzsparend auf einem µC mit sehr begrenztem
> RAM?

Ist letztlich die OO-Notation meines Vorschlags.

Warum sollte da irgendwo RAM verschwendet werden?  Es wird halt mit
einem Zeiger hantiert, aber damit sollten aktuelle Controller
umgehen können.

von (prx) A. K. (prx)


Lesenswert?

Udo Schmitt schrieb:
> Und das funktioniert (RAM)platzsparend auf einem µC mit sehr begrenztem
> RAM?

Im Prinzip schon. Weshalb sollte lokales RAM gleichen Layouts mehr Platz 
verbrauchen als globales RAM. Hässlich ist nur, dass man vom RAM 
Verbrauch nicht man eine Vorstellung bekommt.

von Udo S. (urschmitt)


Lesenswert?

Nein, meine Frage ist nur löst der Compiler das so effizient auf, wie C 
Code oder braucht er dazu mehr Ram. Ich habe früher viel C programmiert 
aber kaum C++. Jetzt bin ich objektorientiert ausschliesslich in Java 
unterwegs.

So wie ich das verstanden habe, muss der Compiler doch beide Contexte im 
RAM vorhalten weil die Entscheidung erst zur Laufzeit erfolgt.
Klärt mich bitte mal auf :-)

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
> Warum sollte da irgendwo RAM verschwendet werden?  Es wird halt mit
> einem Zeiger hantiert, aber damit sollten aktuelle Controller
> umgehen können.

Die 8-Bitter werden sich damit etwas schwer tun, denn da sind 
Prozessoren, die basierend auf einem Pointer-Register mit Distanzen 
grösser 255 effizient adressieren können, eher selten. AVR beschränkt 
sich da auf maximal 15, was sonst ganz nett aber in diesem Fall zu wenig 
ist.

von (prx) A. K. (prx)


Lesenswert?

Udo Schmitt schrieb:
> Nein, meine Frage ist nur löst der Compiler das so effizient auf, wie C
> Code oder braucht er dazu mehr Ram.

Mehr RAM nicht, aber ggf. mehr ROM für den die Variablen adressierenden 
Code. Die Union-Variante ist im Prizip exakt äquivalent - u.U. sogar 
effizienter, weil der Compiler bei geringen Distanzen zwischen Variablen 
den Zugriff besser optimieren kann als bei normalen globalen Variablen, 
deren Distanzen er nicht kennt.

> So wie ich das verstanden habe, muss der Compiler doch beide Contexte im
> RAM vorhalten weil die Entscheidung erst zur Laufzeit erfolgt.

So wie eben beschrieben muss er nur 2 Kontexte im ROM vorhalten, für die 
Initialisierung. Im RAM liegt nur ein Kontext. Plus, je nach Lösung, ein 
Pointer.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> So wie eben beschrieben muss er nur 2 Kontexte im ROM vorhalten, für die
> Initialisierung.

Das muss man aber, so man eine Initialisierung wünscht, ohnehin.  Ob
nun implizit als Bestandteil von .data, oder explizit über ein
memcpy*(), spielt eine ziemlich untergeordnete Rolle.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Man könnte doch einen Bootloader verwenden und je nach Pin-Status die 
eine oder andere Applikation laden.  Allerdings setzt das mehr Flash 
voraus, im worst case mehr als das doppelte.

von Claude M. (stoner)


Lesenswert?

Entweder ich habe das Problem flasch verstanden, oder aber die 
Diskussionen führen mehrheitlich am Thema vorbei.

Der TO will in den beiden Programmen mit globalen Variabeln arbeiten 
können (damit er nicht eine Unmenge von Parametern oder zumindest einen 
Context-Parameter durch all seine Funktionsaufrufe schleifen muss?). Die 
Variante mit den lokalen Variabeln fällt daher weg.

Die globalen Variabeln sind vermutlich nicht in beiden Programme 
dieselben. Ergo fällt die Variante mit einer gemeinsamen Struktur, die 
zur Laufzeit unterschiedlich befüllt wird ebenfalls weg (dann könnte man 
ja übrigens auch gleich aus beiden Programmen die selben globalen 
Variabeln verwenden).

Variante Bootloader schiesst glaube ich völlig am Ziel vorbei, ausser 
man kriegt es fertig, zwei vollkommen eigenständige Programme (jedes für 
sich compiliert und gelinkt) auf den AVR zu laden und per Bootloader 
eines der beiden auszwählen. Hab ich aber noch nie was davon gehört, 
dass jemand so was gebaut hat.

Den einzigen Kompromiss, den ich sehe, ist eine einzige gemeinsame 
globale Pointer-Variable, die je nach Programm auf eine Struktur a oder 
b - bestehend aus den globalen Variabeln von Programm a oder b - zeigt, 
die man nach dem Programmstart auf eine beim Programmstart allozierte 
und initialisierte Datenstruktur des Typs a oder b zeigen lässt. Der 
Zugriff auf die globalen Variabeln erfolgt dann halt immer indirekt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Claude M. schrieb:

> Der TO will in den beiden Programmen mit globalen Variabeln arbeiten
> können (damit er nicht eine Unmenge von Parametern oder zumindest einen
> Context-Parameter durch all seine Funktionsaufrufe schleifen muss?). Die
> Variante mit den lokalen Variabeln fällt daher weg.

Er hat nicht ausgeschlossen, den Code ggf. zu ändern.  Damit sollte
ein einziger Kontextparameter so schlimm nicht sein (bzw. beim
OO-Ansatz wäre das einfach implizit der this-Zeiger).

> Die globalen Variabeln sind vermutlich nicht in beiden Programme
> dieselben. Ergo fällt die Variante mit einer gemeinsamen Struktur, die
> zur Laufzeit unterschiedlich befüllt wird ebenfalls weg

Nicht „Struktur“, sondern “union”.  Also alle globalen Daten in
einer union vereinigen.

Im Prinzip kann man dann noch mit Präprozessorgewurschtel für jeden
der beiden Teile den Code so hinbiegen, dass man ihn nicht einmal
ändern muss.  Also wenn bislang da stand:
1
uint8_t someglobal_1;
2
char *message;

und man das künftig in einer union unterbringen will:
1
union {
2
  struct {
3
    uint8_t someglobal_1;
4
    char *message;
5
  } context_1;
6
  // ...
7
} context;

dann tut's ein Headerfile für Applikation #1 mit:
1
#define someglobal_1 context.context_1.someglobal_1
2
#define message      context.context_1.message

von (prx) A. K. (prx)


Lesenswert?

Claude M. schrieb:
> Der TO will in den beiden Programmen mit globalen Variabeln arbeiten
> können (damit er nicht eine Unmenge von Parametern oder zumindest einen
> Context-Parameter durch all seine Funktionsaufrufe schleifen muss?). Die
> Variante mit den lokalen Variabeln fällt daher weg.

Die Union-Variante adressiert dann
  u.d1.meine_variable
und die Pointer-Variante statt dessen
  data1p->meine_variable

> dieselben. Ergo fällt die Variante mit einer gemeinsamen Struktur, die
> zur Laufzeit unterschiedlich befüllt wird ebenfalls weg (dann könnte man
> ja übrigens auch gleich aus beiden Programmen die selben globalen
> Variabeln verwenden).

Die oben gezeigte ist keine Struktur, sondern eine Union, die 2 
Strukturen enthält. Eine pro Anwendung. Passt.

> Variante Bootloader schiesst glaube ich völlig am Ziel vorbei, ausser
> man kriegt es fertig, zwei vollkommen eigenständige Programme (jedes für
> sich compiliert und gelinkt) auf den AVR zu laden und per Bootloader
> eines der beiden auszwählen.

Geht schon, ist aber etwas aufwendig.

> die man nach dem Programmstart auf eine beim Programmstart allozierte
> und initialisierte Datenstruktur des Typs a oder b zeigen lässt.

Auch das geht. Aber ob die Allokation in der Top-Funktion der Anwendung 
nun lokal erfolgt oder per malloc() ergibt keinen Unterschied:

main()
{
  if (pin)
    main1();
  else
    main2();
}

main2()
{
  struct Data1 data1;
  data1p = &data1;
  ... code für Anwendung 1 ...
}

Das ist auch eine dynamische Allokation, nur eben auf dem Stack.

von Claude M. (stoner)


Lesenswert?

Jörg Wunsch schrieb:
> Nicht „Struktur“, sondern “union”.  Also alle globalen Daten in
> einer union vereinigen.

Ja, die "union" hatte ich übersehen. Funktioniert natürlich auch (wenn 
auch mit dem Nachteil, dass man Speicher verschwendt wenn die beiden 
Programme unterschiedlich grosse globale Daten haben).

A. K. schrieb:
> Die oben gezeigte ist keine Struktur, sondern eine Union, die 2
> Strukturen enthält. Eine pro Anwendung. Passt.

Ja, hatte ich übershen (siehe Kommentar oben)

> Auch das geht. Aber ob die Allokation in der Top-Funktion der Anwendung
> nun lokal erfolgt oder per malloc() ergibt keinen Unterschied:
>
> main()
> {
>   if (pin)
>     main1();
>   else
>     main2();
> }
>
> main2()
> {
>   struct Data1 data1;
>   data1p = &data1;
>   ... code für Anwendung 1 ...
> }
>
> Das ist auch eine dynamische Allokation, nur eben auf dem Stack.

Ja, das ist natürlich richtig (und meiner Meinung nach sogar eleganter 
als mit malloc()).

von (prx) A. K. (prx)


Lesenswert?

Claude M. schrieb:
> Ja, die "union" hatte ich übersehen. Funktioniert natürlich auch (wenn
> auch mit dem Nachteil, dass man Speicher verschwendt wenn die beiden
> Programme unterschiedlich grosse globale Daten haben).

Nur gegenüber einer Variante, die alternativ 2 Programme flasht. Wenn 
die beiden aber vom Grössenverbrauch nicht schweinemässig verschieden 
sind und deshalb verschiedene Controller verlötet werden können, dann 
bringt das exakt garnichts, denn auf den Versuch, beim Händler nicht 
verbrauchtes RAM zurückzugeben, wird der wohl eher irritiert reagieren.

von Mike (Gast)


Angehängte Dateien:

Lesenswert?

Vielen Dank erst einmal für die vielen Anregungen.

Am besten und einfachsten erscheint mir die Union-Variante. Nachteil 
ist, dass die Modularisierung verloren geht und dass die Variablen nicht 
mehr einzeln in der Linker Map erscheinen, sondern nur die Union an 
sich.

Das "keinen Überblick" haben spricht auch gegen alles dynamisch auf dem 
Stack allokierte. Ich bekomme erst zur Laufzeit mit, ob es passt oder 
nicht. Die Anwendung benötigt diese Flexibilität auch nicht.

Ich habe mal den Ansatz mit den zwei Sektionen verfolgt. Damit kann ich 
markieren was in welche Welt gehört. Diese Markierung kann ich über den 
Präprozessor auch flexibel in Module bzw. Bibliotheken hinein nehmen.

Ich habe mal ein kleines Programm erstellt, dass das gewünschte 
Verhalten ungefähr nachbildet. Natürlich ist bei dieser einfachen 
Darstellung die Verlockung groß einfach mit dem Union zu arbeiten. Aber 
wie gesagt, die Configuration verwendet ein Modul, das auch in anderen 
Projekten eingesetzt werden soll und wo die globalen so weggekapselt 
sind, dass diese im Hauptprogramm nicht sichtbar sind.

Wenn man das einfach so übersetzt (wie in makeit.sh), dann sieht man (in 
APP.map):
COMMON         0x0000000000800060      0x300 /tmp/ccIL3KOn.o
                0x0000000000800060                runBuffer
                0x0000000000800260                cfgBuffer

D.h. beide Buffer sind hintereinander (in der Realität ist da natürlich 
mehr als diese Buffer).

Schiebe ich jetzt die Variablen in verschiedene Sections (main2.c) und 
setzte diese Sections auf die gleiche Startadresse, dann sieht das 
eigentlich ganz gut aus:
.config         0x0000000000800074      0x100
 .config        0x0000000000800074      0x100 /tmp/ccRS0uUf.o
                0x0000000000800074                cfgBuffer

.runner         0x0000000000800074      0x200
 .runner        0x0000000000800074      0x200 /tmp/ccRS0uUf.o
                0x0000000000800074                runBuffer
Allerdings meckert der Linker das sich Sektionen überlappen würden. Was 
ja auch korrekt ist. Mein Ziel hätte ich also erreicht, wenn ich den 
Linker davon überzeugen könnte, dass das so OK ist und wenn er mir 
netterweise selbst die Startadressen berechnen würde (nach .bss z.B.) 
oder wenn ich in der Kommandozeile auf __bss_end referenzieren könnte.

Ich habe aber gerade noch ein bischen recherchiert und festgestellt, 
dass so etwas wohl nicht funktioniert. Es sei denn jemand hat noch eine 
geniale Idee ...

Mit dem ldscript habe ich auch mal experimentiert aber komme auf keine 
Lösung. Wobei ich von Linker-Scripts auch keine Ahnung habe.

Grüße
Mike

von (prx) A. K. (prx)


Lesenswert?

Die Modularisierung lässt sich problemlos retten. Ich hatte oben nicht 
ganz zufällig in die beiden Structs ein #include einfliessen lassen, um 
genau dies zu betonen.

Etwas vollständiger ist die Variante mit der Initialisierung:

data1.h
1
const struct Data1 {
2
  int var1;
3
  char var2[];
4
} d1init = { .var1 = 1, ... };

data2.h
1
const struct Data2 {
2
  double var3;
3
  wchar_t var4[];
4
} d2init = { .var3 = 1.1415, ... };

data.h
1
#include "data1.h"
2
#include "data2.h"
3
union {
4
 struct Data1 d1;
5
 struct Data2 d2;
6
} u;

Hier sind beide Datendefinitionen blitzsauber getrennt. Der Code dazu 
geht analog. Es kommt nur für Code und Daten ein kleiner Layer hinzu, 
der beides zusammenklebt.

von (prx) A. K. (prx)


Lesenswert?

Wenn die Zugriffe als u.d1.var optisch nerven, dann lässt sich das per 
#define für jede Variable lösen, wie Jörg schon zeigte.

Eine Alternative zu dieser C Lösung geht über C++. Ein stark 
vereinfachtes C++, das kaum etwas davon verwendet, also fast wie C 
aussieht. Aber ohne #define auskommt. Ist aber aufgrund der 
nicht-statischen Adressierung nur bei 16/32-Bittern ratsam, dort aber 
tendentiell besser.

app1.h
1
const struct Data1 {
2
  int var1;
3
  char var2[10];
4
  ...
5
  void run();
6
  void function1();
7
} d1init = { .var1 = 1; }

app1.cpp
1
#include "app1.h"
2
3
void Data1::run()
4
{
5
  var1 = var1 * 3;
6
  function1();
7
}
8
9
void Data1::function1()
10
{
11
  sprintf(var2, "%d", var1);
12
}

main.cpp
1
#include "app1.h"
2
#include "app2.h"
3
4
union U {
5
 Data1 d1;
6
 Data2 d2;
7
} u;
8
9
main()
10
{
11
  if (pin) {
12
    u.d1 = d1init;
13
    u.d1.run();
14
  } else {
15
    u.d2 = d2init;
16
    u.d2.run();
17
  }
18
}

In den Funktionen - die aber sämtlich in der struct deklariert werden 
müssen wie bei funktion1() gezeigt - sind nun alle Variablen ganz normal 
zu schreiben, ohne Kopf vorneweg. Technisch betrachtet wird hier jeweils 
ein versteckter Pointer durchgereicht.

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
> data1.h
>
1
> const struct Data1 {
2
>   int var1;
3
>   char var2[];
4
> } d1init = { .var1 = 1, ... };
5
>

Fix: d1init muss dort als "extern const" hin, in in app1.c landet
  const Data1 d1init = { ... };
aber ich hoffe das Bild wird auch so klar. C++ analog.

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.