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
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.
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.
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.
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.
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
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.
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.
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?
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.
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.
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 :-)
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.
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.
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.
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.
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.
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_tsomeglobal_1;
2
char*message;
und man das künftig in einer union unterbringen will:
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.
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()).
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.
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
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
conststructData1{
2
intvar1;
3
charvar2[];
4
}d1init={.var1=1,...};
data2.h
1
conststructData2{
2
doublevar3;
3
wchar_tvar4[];
4
}d2init={.var3=1.1415,...};
data.h
1
#include"data1.h"
2
#include"data2.h"
3
union{
4
structData1d1;
5
structData2d2;
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.
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
conststructData1{
2
intvar1;
3
charvar2[10];
4
...
5
voidrun();
6
voidfunction1();
7
}d1init={.var1=1;}
app1.cpp
1
#include"app1.h"
2
3
voidData1::run()
4
{
5
var1=var1*3;
6
function1();
7
}
8
9
voidData1::function1()
10
{
11
sprintf(var2,"%d",var1);
12
}
main.cpp
1
#include"app1.h"
2
#include"app2.h"
3
4
unionU{
5
Data1d1;
6
Data2d2;
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.