Hi,
Für ein Hobbyprojekt bin ich gerade in der Konzeptphase für eine
Menüsteuerung (Taster + 2-zeiliges LC-Display) um sowol Parameter
darzustellen als auch zu verändern. Da ich auf einem ATmega32 arbeite
steht
nur begrenzt RAM zur verfügung, weshalb ich nach einer RAM-Schonenden
Lösung suche;) Folgendes habe ich mir überlegt. Über eure
Meinungen/Anmerkungen/weiteren Ansätze bin ich euch jetzt schon dankbar
;)
Die Idee:
-->|---------|-->|---------|-->|---------|
|DISP_n-1 | | DISP_n | | DISP_n+1|
<--|---------|<--|---------|<--|---------|
|---------|
|Param_n_0|
|---------|
|---------| |---------|
|Param_n_1|---->|ParamCH |
|---------| |---------|
|---------|
|Param_n_x|
|---------|
Also: Es soll 3 Zustände geben:
- Anzeige (DISP) : Hier können Parametergruppen ausgewählt werden
- Parametergr. (PARAM) : Hier kann jeweils ein Parameter der Gruppe
ausgewählt werden.
- ParamChange (PARAM_CH): Hier kann der Parameter (nach Anwahl) geändert
werden.
Das kleine ASCII-Bildchen dient nur der Veranschaulichung, hoffentlich
verwirrt es nicht mehr als das es hilft ;)
Meide Idee war das mit einer doppelt(bzw.dreifach) verketteten Liste zu
lösen in der Vorgänger u. Nachfolger bekannt sind. So könnte man per
Tastendruck bequem im Menü navigieren. Allerdings ist das sehr
RAM-intensiv bzw. ich weiß nicht genau wie ich das ganze möglichst
RAM-schonend umsetzen kann.
Das Menü wird nicht sehr dynamisch sein, es ist also nicht nötig zur
Laufzeit Anzeigelelemente anzufügen womit ein Großeil der Informationen
konstant und somit aus meiner Sicht in dem ROM abgelegt werden können.
Die einzige Information die zum Compilezeitpunkt nicht bekannt ist ist
der aktuelle Wert des jeweiligen Parameters. In Peusocode hätte ich das
jetzt mal so versucht ob das in C überhaupt möglich ist bin ich mir
nicht sicher ;) ??
1
//Parameter RAM-Variable
2
unsigned char Param_Drehzahl[LCD_NUM_OF_SYMBOLS];
3
4
//Displayobject Datentyp
5
typedef struct
6
{
7
unsigned char Zeile1[LCD_NUM_OF_SYMBOLS]; //Beschreibung des angezeitgen Wertes
Mir wäre sehr geholfen wenn Ihr mir sagen könntet ob das totaler
Schwachsinn ist, oder ob die Idee umsetztbar ist. Gegenüber anderen
Lösungsansätzen bin ich natürlich offen.
Danke u. Gruß
T3Knopap$T
>Allerdings ist das sehr RAM-intensiv
Wieso ? Du kannst die Menüstruktur doch aus dem Flash lesen. Und wenn du
wie oben gesagt nicht viel dynamisch hast (also das sich das Menü zur
Laufzeit ändert) ist es kein Problem. Dann brauchst du lediglich ein
paar Bytes RAM (ein Byte um die Seite zu identifizieren in der du gerade
stehst, bzw einen kleinen Stack von vllt 10 Bytes wenn du beim
Rücksprung in das übergeordnete Menü an der richtigen (sprich zuletzt
verwendeten) Stelle stehst.
Der Ansatz mit der doppelt verketteten Liste ist ok.
Von dem was ich mir durchgelesen habe müsste es funktionieren. Habs aber
auch nur kurz durchgelesen.
Ich habe das in einer Menüstruktur ähnlich gemacht.
Ich habe mir Texte, Textlisten (mit den indizes der verwendeten Texte
innerhalb der Liste), Objekte (die Texte oder Textlisten enthalten
können), Seiten (die aus Objekten zusammengebaut sind) sowie
Seitenlisten (die indirekt aus Textlisten bestehen) gemacht.
Die Verbindung zwischen Objekten und Variablen die sich ändern können
habe ich ganz einfach über Zeiger gemacht. So hat jeder Parameter seinen
eigenen Zeiger.
Funktioniert auch ganz gut. Ist nur ein wenig klobig geraten und nicht
so schön zu erweitern (aufwändig).
Ich würd noch einen Funktionspointer in die Struktur einfügen. Damit
erspart man sich den riesen switch-case um herauszufinden, was du
eigentlich machen musst, wenn der Benutzer ne Taste drückt (ein wenig
vergleichbar mit einem Action-Listener in Java).
Die einzelnen Menueinträge würde ich in ein Array legen. Dann kannst du
dir die Pointer auf vor zurück und parameter auch sparen, indem du
stattdessen den Index des betreffenden Eintrags abspeicherst (wenn dein
Menu nicht mehr als 256 Elemente enthalten soll, reichen dir je ein
uint8_t).
Bei meiner Menu-Struktur habe ich es so gelöst, dass ich immer den
aktuellen Menu-Eintrag in den RAM abgelegt habe (da ich ab und zu die
Texte dynamisch verändern musste). Kann man sich aber auch ersparen und
nur die Index-Variable anspeichern.
grüße
Mobius
P.S.: const speichert nciht im ROM. Zum abspeichern in den Flash, schau
dir avr/progmem.h an (natürlich nur, wenn du WinAVR verwendest ;) ).
Generell nochmal gesagt :
Indizes sind m.M. nach Pointern vorzuziehen. Erstmal erleichtert es beim
Debuggen (Abzählen von einträgen ist einfacher als die Kristallkugel
nach 0xfe23 zu befragen). Und zweitens braucht man auch dadurch weniger
Speicher.
Man erkauft sich das allerdings damit das bei jedem Zugriff der Zeiger
neu berechnet werden muß.
Als ergänzung zu dem was Mobius geschrieben hat :
Funktionszeiger vllt in einer separaten Tabelle ablegen, dann kann man
ebenfalls mit Indizes hantieren. In Verbindung mit Enums ist das dann
wunderbar zu lesen und direkt verständlich.
Ich mache das meist auch per Index, wobei ich in der Zeigertabelle dann
oft noch einen optionalen Integer Wert mit ablege. Wenn ich bei einem
Aufruf über diese Tabelle den Integerwert an die aufzurufende Funktion
mitgebe kann ich noch eine Fallunterscheidung machen. Beispiel :
Die Funktionszeigertabelle speichert mir Darstellungsfunktionen für das
Display (z.b. signed/unsigned integer auf Display ausgeben). Mit dem
Integer unterscheide ich ob ich signed/unsigned Darstellen möchte, und
wieviele Stellen ich haben möchte. Dann kann ich mit einer einzigen
Funktion alle Fälle erschlagen und bestimme mit dem Integer Parameter
meine genaue Darstellung.
Der Index im Menü zeigt dann auf einen Eintrag indem meine Funktion und
die dazugehörige Darstellung steht.
Es ist vllt ein bissl kompliziert erklärt, aber es ist im Prinzip ganz
einfach. Ich habe das z.b. in einer Grafik-UI so gemacht. Da ist eine
Funktion die einen Button Darstellen soll. Über den mitgegebenen Integer
Wert wird das aussehen und Verhalten des Buttons bestimmt (3D/2D,
Rastend/Nicht Rastend, Bitmap/Text, Rahmen/kein Rahmen, usw.). So kann
ich mit einer Funktion 10 verschiedene Objekte implementieren ohne 10
verschiedene Funktionen zu programmieren. Spart Zeit/Nerven/Bytes und
ist einfach erweiterbar.
Moin,
wow, bin positiv von der schnellen Reaktion des Forums überrascht ;)
Dafür erstmal Danke !!
@ Mobius /Rene : Das mit dem Funktionspointern kann ich mir noch nicht
so richtig vorstellen. Könnte einer von euch vllt. ein kurzes C/pseudo
code Beispiel zu Verdeutlichung zu erstellen?
Ich hatte ja vor für Jede Display-Seite ein "Stucktur-Objekt" in den Rom
zu legen. Wenn ich jetzt anstatt der Zeiger für "vor" und "zurück" einen
Funktionspeuter in die Stuktur einbaue der je nach Objekt ein anderen
Index hat, dann hab ich doch in der Funktion trotzdem nen riesen switch
case, oder verstehe ich jetzt grundsätzlich etwas nicht ;) ?
@ Mobius: Danke für den Tip mit dem Progmem ... öfter mal was neues ...
Gruß
T3Knopap$T
Strukturiert und ohne unnötiges Zeigergewurschtel kann man es auch
einfacher lösen, indem man sich eine Funktion bastelt, die einen
beliebeigen Wert auf dem Display anzeigt, mit 2 Tasten verändert und den
neuen Wert als Rückgabewert ausgibt. Als Eingabe braucht diese Funktion
nur den anzuzeigenden Text und den Initialwert der Variabel.
In Pseudocode sähe das Ganze etwa so aus:
Funktion (texte, startwert [,maxwert, minwert]) = Ausgabevariable
Var X
{
X = startwert;
Ausgabe Text auf Display;
Ausgabe X auf Display;
Wenn nicht taste 3 wiederhole
{
Wenn taste 1 dann erhöhe X;
Wenn taste 2 dann erniedrige X;
Wenn X > max wert dann X = maxwert;
Wenn X < min Wert dann X = minwert
Ausgabe X auf Display;
}
Ausgabevariable = X;
}
Damit braucht man den Code für egal wie viele zu verändernde Werte nur
einmal und braucht sich den Code durch Zeiger nicht zu versauen.
Das eigentliche Menü muß dann nur die (in einem Array) abgelegten Texte
anzuzeigen und zu blättern und ruft die Funktion bei einem Druck auf
eine Taste auf.
Der Rückgabewert wird dann einfach in der richtigen Variable
gespeichert.
Frank
@Frank: Interessante Möglichkeit. Aber einen Pointer auf die zu
verändernde Variable im Menu-Eintrag wird man brauchen. Dann noch
min/max Werte und vllt. auch noch step-size (je nachdem wie "allround"
die Funktion sein soll). Die müssen auch als Information in den
einzelnen Menu Einträgen abgespeichert werden. Auch könnte es sein, dass
nicht nummerische Einstellungen verändert werden sollen (mein, wenn man
zB leise/laut, langsam/mittel/schnell, etc. ausgeben muss). Dafür
bräuchte man dann eine weitere Anzeigefunktion und der Aufrufer muss
dann entscheiden, was getan werden soll.
Will nicht sagen, dass die Idee schlecht ist, es hängt halt davon ab,
was man mit dem Menu alles tun will. Sie hat Vorteile und Grenzen und um
Zeiger wirst du auch bei dieser Lösung nicht drum herum kommen ;) (halt
nur um Funktionszeiger ^^).
@T3Knopap$T: Nein, die Zeiger für vor und zurück nicht durch nen
Funktionspointer ersetzten. Die kannst du so lassen, wenn du eine
verkettete Liste aufbauen willst. Wenn du alle Einträge in ein Array
gibts, kannst du die Zeiger durch index-Variable austauschen (und damit
3 byte / Eintrag sparen, nicht viel summiert sich aber ;) )
Der Funktionspointer ist dafür, dass du die Aktion, die bei einem
Tastendruck ausgeführt werden soll (zB. Zeit einstellen, Motor starten,
Variable hochzählen, Roman ausgeben, etc.) einem Menu-Eintrag zuordnest.
Eigentlich genau wie bei
http://www.mikrocontroller.net/articles/FAQ#Men.C3.BCs_mit_Funktionszeigern
^^.
Das Traversieren des Menus erfolgt dann mit einer Action-Funktion, die
nichts anderes zu Tun hat, als den Zeiger/Index (je nachdem ob
verkettete Liste oder Array) entsprechend einem Tastendruck zu
modifizieren.
1
// Ein Zeiger/Index auf den aktuellen Eintrag, das angezeigt wird
ich habe erst durch mein menügebastel funktionszeiger lieben gelernt ^^
man kann unglaubliche schweinereien damit anstellen und sogar sehr
"objekt"basierend arbeiten
für solche menüs die diverse untermenüs haben können hatte ich eine
lösung wo allein das array bestimmt wie das menü aussieht
in der struktur steckt dann nur eine variable die die aktuelle "ebene"
darstellt
1
struct
2
{
3
char*text;
4
unsignedcharebene;
5
void(*f_ptr)(void);
6
}menu_t;
7
8
menu_tmenuarray[]PROGMEM={
9
{"Hauptmenu",0,menu_vor},
10
{"Einstellungen 1",1,menu_vor},
11
{"Parameter 1.1",2,tuwas1},
12
{"Parameter 1.2",2,tuwas2},
13
{"Einstellungen 2",1,menu_vor},
14
{"Parameter 2.1",2,tuwas3},
15
{"Parameter 2.2",2,tuwas4},
16
{"Version",1,tuwas5},
17
};
will man nun einen neuen menüpunkt eintragen setzt man diesen an die
entsprechende position , schreibt die passende funktion der "action"
dazu und fertig
soll es nur einen punkt weiter gehen oder zurück existiren menu_vor()
und menu_zurueck() funktionen die als "action" eingetragen werden
das umsortieren der punkte oder löschen/ hinzufügen ist hier sehr
einfach
>man kann unglaubliche schweinereien damit anstellen und sogar sehr>"objekt"basierend arbeiten
Hehe. Das ist wohl wahr. Es lassen sich eine Menge feiner Sachen damit
umsetzen die sich sehr leicht erweitern lassen. Aber man muß echt
aufpassen bei den Schweinerein. Hat man da nen Fehler drin kann man gut
und gerne etliche Stunden Fehlersuche betreiben.
Daher halt der Hinweis mit den Indizes.
Je nachdem wie groß das ganze ist macht das zustäzliche Byte für einen
Zeiger auch nicht viel aus bzw die Speicherersparnis wird durch die
Inizes wieder zunichte gemacht.
Aber sehr schöner Ansatz den du zuletzt gepostet hast. Werd den bestimmt
mal irgendwie verwursten :-))
in der endgültigen version hab ich anstelle des textes bzw den zeiger
auch nur ein index auf ein textarray mit textzeigern ^^
weil sich dann verschiedene sprachen einstellen lassen mit einem
pointeroffset
an der sache mit den indizes auf zeigerarray stört mich bei
funktionszeigern immer das man ja nicht vergessen darf die zeiger
einzutragen , die indizes zu prüfen usw ..
viele bauen dann zusätzlichen code ein damit vorher alles überprüft
wird..
man editiert sozusagen an 3 baustellen
ist eines vergessen worden haut alles nicht mehr hin
und da wollte ich irgendwie daran vorbei
( das stört mich beim text in mehreren sprachen schon immer ^^ )
zeiger sind immer ein problemchen hier und da ... aber erleichtern einem
vieles und sind gerade in C eine sehr starke waffe ^^
Hi Leute,
danke für die vielen Anregungen ! Die müssen jetzt erstmal verarbeitet
werden ;)
Werde mich dann wieder melden wenn ich es, wie auch immer, umgesetzt
habe!!
Oder, wenn ich auf die ersten Probleme stoße natürlich ;)
Gruß
T3Knopap$T
@gastt
Das Problem mit den Zeigern die man immer nachpflegen muß hab ich ne
(nicht ganz einfache, aber geniale) Lösung. Und zwar lassen sich ja mit
dem Präprozessor schöne Schweinereien machen die die Verweise
automatisch richtig setzen.
Z.B. mit folgendem Codeschnipsel
xxx.h :
-------
enum {
#define TEXT(name,text) name,
#include "xxx_cfg.h"
#undef TEXT
NUM_TEXT_INDEXES
};
#define TEXT(name,text) char Text_Single_##name [] PROGMEM = { text }
;
#include "xxx_cfg.h"
#undef TEXT
char * Text_Zeiger_Array Single_##name [] PROGMEM = {
#define TEXT(name,text) Text_Single_##name,
#include "xxx_cfg.h"
#undef TEXT
};
xxx_cfg.h
---------
#ifdef TEXT
TEXT (MAIN, "Hauprmenu")
TEXT (ENTRY1, "Eintrag 1")
TEXT (ENTRY2, "Eintrag 2")
TEXT (ENTRY3, "Eintrag 3")
#endif
-------
Bei diesem kleinen Beispiel werden Texte angelegt und ein Array in dem
die ganzen Zeiger auf die einzelnen Texte automatische generiert werden.
Man muß sich dabei nicht mal mehr um die Zuordnung kümmern. Hat
automatisch eine Konstante namens "NUM_TEXT_INDEXES" in der steht
wieviele Texte bzw Indizes verwendet werden. Mit der Methode lassen sich
auch sehr komplexe Strukturen automatisch richtig erzeugen.
Ist nicht ganz einfach zu verstehen aber wenn mans einmal verstanden hat
ein echt mächtiges Werkzeug.
hmm danke ^^
das is ma ne lösung :D
geht sogar mehrsprachig ohne dateisalat
#ifdef TEXT
TEXT (MAIN, "Deutch1", "English1", "xxxxxxx1")
TEXT (ENTRY1, "Deutch2", "English2", "xxxxxxx2")
TEXT (ENTRY2, "Deutch3", "English3", "xxxxxxx3")
TEXT (ENTRY3, "Deutch4", "English4", "xxxxxxx4")
.
.
.
.
#endif
witzige idee :)
@gastt
Das ist doch noch gar nix :-)))))))
Mit der Methode kann man sich sehr viel arbeit ersparen und es kommt
immer (sofern man die richtigen Datenstrukturen gewählt hat) die
platzsparenste und zugleich wartungsfreieste Lösung bei raus.
In dem Beispiel habe ich ja nur 1 Ebene (Texte und Zeiger auf Texte)
benutzt. Interessant wird das wenn man mit mehreren Ebenen (Zeiger und
Indizes) "spielt".
Z.b. erstellt man sich (wie oben schon erwähnt) Objekt-Funktionen,
Texte, Text-Listen und Seiten. Wenn man mit Indizes arbeitet hat man
durch die automatische Enumeration auch immer einen sprechenden Namen
den man durch die ganze Konfiguration hinweg benutzen kann.
In den Objekten kann ich Text-Indizes oder Listen-Indizes verwenden, und
die Seiten-Infos beherbergen dann Objekt-Indizes. Alles mit sprechenden
Namen, und kein abzählen mehr.
Ich verwende diese Technik für so ziemlich alles was im entferntesten
nach Parametrierung riecht (ok, ich verwende nicht für jedes kleine
bischen automatisch Listen, aber wenn z.b. Portpins variieren können
bietet sich eine Konfiguration nach dem o.g. Muster an). Egal ob UART,
Timer, Tasten, Analog-Eingänge, Funktionsaufrufe von einem Steuersystem,
meinen hier veröffentlichten Kommando-Interpreter, und und und ...
Z.B. für Tasten :
#ifdef KEYDEF
//KEYDEF (name, PIN_X, Bit
KEYDEF (CURSOR_UP, PINA, BIT0)
KEYDEF (CURSOR_DN, PINA, BIT1)
KEYDEF (CURSOR_LF, PINA, BIT2)
KEYDEF (CURSOR_RT, PINB, BIT4)
#endif
oder für Analoge Eingänge
#ifdef BOARD_UI_AINP
//BOARD_UI_AINP (name, ch, mux
BOARD_UI_AINP (VOLUME, 0, 0)
BOARD_UI_AINP (BASS , 1, 0)
BOARD_UI_AINP (TREBLE, 0, 1)
BOARD_UI_AINP (BALANCE, 1, 2)
#endif
Das schöne ist das man dadurch die eigentliche Header und Code-Datei nur
anpacken muß wenn was wirklich neues implementiert werden soll.
Ansonsten nimmt man einfach die xxx.c, xxx.h für ein anderes Projekt und
passt lediglich die xxx_cfg.h an und schon (sollte) es laufen.
Es gibt noch einiges zu dem Thema zu sagen. Vllt werde ich noch einen
Artikel darüber schreiben. Aber wenn man die generelle Vorgehensweise
erstmal verstanden hat eröffnen sich einem enorme Möglichkeiten.
Die ganze Vorgehensweise mutet erstmal etwas umständlich und schwer
verständlich an, aber der Aufwand lohnt sich. Allein dadurch das wenn
man Datenstrukturen und Enums entsprechend immer konsistent hat (bei 5
Einträgen gehts ja noch, aber ab 20 wirds dann umständlich "mal eben"
einen neuen Eintrag irgendwo in der Mitte reinzufummeln, und wenn dann
noch Kreuzverweise konsistent gehalten werden müssen ...
So genug geschwafelt und gewschwärmt :-)
Bin mal gespannt ob ich jemandem mit dem Post hier genug anregung
gegeben habe mal damit was auf die Beine zu stellen.
Rene Böllhoff schrieb:
> Vllt werde ich noch einen> Artikel darüber schreiben.
Ich währe dir sehr verbunden! Das klingt alles sehr spannen und sinnvoll
was du schreibst. Ein Artikel zur Strukturierten programmierung währe
sehr fein.
Ich arbeite auch lieber mit Indizes anstatt mit Pointern.
Indizes lassen sich einfacher vor und zurück zählen und auf
Bereichsgrenzen prüfen "if( index < 0 || index >= sizeof(menu))".
Außerdem ist es fehlertoleranter.
Ein falscher Index zeigt nur einen falschen Menüpunkt an.
Ein falscher Pointer läßt das komplette Menü in den Wald laufen und es
ist dann schwer den Schuldigen zu ermitteln.
Peter
Mal ein paar Konfigurationsdateien wie ich sie in meinem Audio-Projekt
verwende. Die sind mittlerweile schon ganz ordentlich umfangreich
geworden.
DAP_MOD_CFG.H :
Konfigurationsdatei für den Kern meines modularen Audio-Systems.
Da werden Module deklariert, und die verschaltung der Module zu
Setups, sowie grundlegende Konfiguration des Systems (Preset-Größe,
Anzahl Steuerwerte und und und)
GLCD_UI_CFG.H :
Konfigurationsdatei für die grafische GUI. Hier werden Texte, Objekte
und Seiten definiert.
PARSER_CFG.H :
Konfigurationsdatei für meinen Kommandointerpreter mit automatischer
Hilfegenerierung, Kommandolisten (damit lässt sich viiieeeel machen),
Verarbeitungscodecs usw ...
Ich möchte diese Technik mittlerweile nicht mehr missen, wenn man sich
überlegt was man damit alles anstellen kann. :-))