Forum: Mikrocontroller und Digitale Elektronik AVR, Structs zur Laufzeit reservieren


von Phillip H. (philharmony)


Lesenswert?

Servus,
Ich möchte dem Benutzer die Möglichkeit geben, eine Art Setup zu machen, 
dessen Einstellungen dann im EEPROM gespeichert werden.
Im Program sollen diese Daten dann dazu verwendet werden, verschiedenen 
Struct-Arrays Elemente hinzuzufügen.
In den Structs selbst verwende ich einerseits Zeiger, die teilweise 
wieder auf elemente anderer Struct-Arrays zeigen als auch variablen mit 
einem, zwei oder drei Bit Länge.
Jetzt bin ich mir nicht ganz sicher, wie ich zur Laufzeit malloc / 
realloc auf Struct-Elemente anwende, also welche Größe ich den 
Funktionen übergebe.
Funktioniert da?
1
xyz = realloc(*array, (sizeof(array) / sizeof(array[0]))+sizeof(array[0]))

Ich würde im Quellcode bereits einen "Prototypen" jedes Arrays 
erstellen, funktioniert dann realloc auch noch? Habe gelesen daß das 
eigentlich nur für Blöcke funktioniert, die per malloc/calloc zugewiesen 
wurden.

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> Jetzt bin ich mir nicht ganz sicher, wie ich zur Laufzeit malloc /
> realloc auf Struct-Elemente anwende, also welche Größe ich den
> Funktionen übergebe.

Die Anzahl der Bytes die du haben möchtest.

> Funktioniert da?
>
1
> xyz = realloc(*array, (sizeof(array) /
2
> sizeof(array[0]))+sizeof(array[0]))
3
>

Das kommt einzige und alleine auf deine Arrays an. Aber so richtig 
sinnvoll sieht das nicht aus.


Zeig ein bischen was von deiner Umgebung.

PS: Gerade wenn man wenig Speicher zur Verfügung hat, ist es oft besser 
einfach eine Obergrenze für den dynamischen Teil einzubauen und alles 
mit dieser Obergrenze statisch zu allokieren.
* malloc und Co benötigen für sich selbst auch Speicher
* Vorhersagen, wann der Speicher voll ist, sind schwierig bis unmöglich
  Dementsprechend steigt die Absturzwahrscheinlichkeit

Mit einer fixen Dimensionierung umgeht man beide Problemkreise.

von Wiesel (Gast)


Lesenswert?

Das ist ohnehin ein wenig zur Seite gedacht.
Erstmal ist malloc/free/realloc für Speicher im Ram gedacht.
Lies Dir mal die entsprechenden Docs bzw. Kapitel in einem C Buch durch. 
:-)
Ob struct-Elemente oder nicht ist eigentlich völlig wurst. Aber das 
liest Du dann ja alles.

Theoretisch wäre es möglich Funktionen zu schreiben, die wie malloc/free 
arbeiten aber in Bezug auf das EEPROM.
Das es noch niemand gemacht hat, nehme ich als Hinweis das es keinen 
Wert hat. Aber das kann ja jeder anders sehen.

Schreib Dir zwei Defines in Deinen Code die Anfang und Ende eines 
Bereiches im EEPROM bezeichnen.
Dann nimm die Schreib-Lesefunktionen aus Deinem C-Compiler der für AVR 
angepasst ist.

von Karl H. (kbuchegg)


Lesenswert?

Wiesel schrieb:

> Theoretisch wäre es möglich Funktionen zu schreiben, die wie malloc/free
> arbeiten aber in Bezug auf das EEPROM.

so wie ich das verstanden habe, liest er aus dem EEPROM irgendwelche 
Konfigurationsinfo, die dann dazu benutzt wird im SRAM irgendwelche 
Datenstrukturen aufzubauen.

Allerdings macht mich die Bezeichnung "Struct-Array" stutzig. Das alles 
klingt irgendwie nach: Er hat sich verrannt.

von Phillip H. (philharmony)


Lesenswert?

>Zeig ein bischen was von deiner Umgebung.
Versuchen wir es mal:
1
//struktur für variablen
2
struct variable_memory 
3
{
4
  int var_number; //Variablennummer aus header in setup
5
  int *value; //Zeiger auf Values
6
  unsigned bit:4; //bit 0-15 für Bool-Variablen
7
  unsigned is_bool : 1; //variable bool?
8
  unsigned has_low_part: 1; //variable größer als 16 bit?
9
};
10
11
//Port in
12
struct port_in_devices
13
{
14
  struct variable_memory *mem; //Zeiger auf Variable
15
  volatile unsigned char *virt_port; //Zeiger auf Virtuellen Port
16
  unsigned pin:3; //Pin
17
};
18
19
//Port out
20
struct port_out_devices
21
{
22
  struct variable_memory *mem; //Zeiger auf Variable
23
  volatile unsigned char *virt_port; //Zeiger auf Virtuellen Port
24
  unsigned pin:3; //Pin
25
  unsigned is_member_of_lamptest:1; //Soll bei Lamptest angehen
26
  unsigned is_member_of_dim:1; //Soll bei Dim gedimmt werden
27
};
28
29
30
//Encoder
31
struct encoder_devices
32
{
33
  struct variable_memory *mem; //Zeiger auf Variable
34
  volatile unsigned char   *channel_a_port; //Zeiger auf Port Kanal a
35
  unsigned channel_a_pin:3; //Pin Kanal a
36
  volatile unsigned char *channel_b_port; //Zeiger auf Port Kanal b
37
  unsigned channel_b_pin:3; //Pin Kanal b
38
  unsigned long min_value; //Mindestwert
39
  unsigned long max_value; //Maximalwert
40
  unsigned long increment; //um wieviel erhöhen
41
  unsigned has_modulo:1; //Verhalten bei überlauf
42
};
43
44
45
46
/////////////////////////////////////////////
47
////////*****AB HIER ZUWEISUNGEN*****////////
48
/////////////////////////////////////////////
49
50
//Zentraler Speicher
51
int values[8];  //Hier ist der Zentrale Speicher
52
53
//Variablen Index
54
#define SYSTEM_VAR 0x0000 //Nummer der Systemvariablen
55
#define INTERNAL_VAR 0xFFFF //Nummer der Intern verwendeten Variablen
56
57
struct variable_memory variables[]=          //max 256 variablen
58
{
59
 {01, &values[0], 0, 1, 0}, //Variable 01, Speicherziel Values_0, Bit 0, Länge 01, Kein Lowpart
60
 {10, &values[1], 0, 1, 0}, //Variable 10, Speicherziel Values_1, Bit 0, Länge 01, Kein Lowpart
61
 {20, &values[2], 0, 0, 0}, //Variable 20, Speicherziel Values_1, Bit 0, Länge 16,  Hat Lowpart
62
/*usw...*/
63
 {SYSTEM_VAR, &values[7], 0, 0, 0}, //Variable 0xFFFF, Speicherziel Values_5, Bit 0, Länge 16, Kein Lowpart 
64
};
65
66
//Port Inputs
67
struct port_in_devices in_ports[]=
68
{
69
 {&variables[4], &virtual_port[OFFSET_PORTA + 0], 2}, //verweist auf Variable2, porta ungruppiert, Pin1
70
/*...*/
71
};
72
73
//Port Outputs
74
struct port_out_devices out_ports[]=
75
{
76
 {&variables[0], &virtual_port[OFFSET_PORTB + 1], 0, 1}, //verweist auf Variable3, portb Gruppe1, Pin0, Lampentest1
77
};
78
79
//Encoder Inputs
80
struct encoder_devices encoders[]=
81
{
82
 {&variables[10], &virtual_port[OFFSET_PORTC + 0], 0, &virtual_port[OFFSET_PORTC + 0], 1, 0, 60, 1, 0}, 
83
  /*verweist auf Variable20, portc Ungruppiert, Pin0, portc Ungruppiert Pin1, min 0, max 60, increment 1, kein Modulo*/
84
};

Im Setup soll jetzt eingestellt werden, wieviele Portin/ Out ("Schalter 
und Lämpchen") sowie Encoder und weitere, aus Übersichtsgründen im o.g. 
Beispiel weggelassenene Geräte am Controller hängen und an welchen Pins 
sie anliegen.
In der Init-Funktion zu Beginn der Programms sollen dann die Arrays
1
in_pots[]
,
1
out_ports[]
 und
1
encoders[]
Um diese Geräte erweitert werden, entsprechend den Daten im EEPROM.

>Allerdings macht mich die Bezeichnung "Struct-Array" stutzig. Das alles
>klingt irgendwie nach: Er hat sich verrannt.

Da ich nicht direkt aus der IT-Sparte komme, sondern mir das ganze 
gezwungenermaßen selbst beibringe drücke ich mich wohl manchmal nicht 
ganz fachlich Korrekt aus. Ich kenne keine richtigere Bezeichnung für 
eine Sammlung an Structs wie zB encoders[], daher bitte entschuldigt die 
manchmal Amateurhafte Ausdrucksweise.

>so wie ich das verstanden habe, liest er aus dem EEPROM irgendwelche
>Konfigurationsinfo, die dann dazu benutzt wird im SRAM irgendwelche
>Datenstrukturen aufzubauen.

Genauso ist es, allerdings will ich die Strukturen nicht direkt aufbauen 
- habe sie ja schon definiert -, sondern zu den entsprechenden "Arrays" 
eben neue Elemente hinzufügen..

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> Genauso ist es, allerdings will ich die Strukturen nicht direkt aufbauen
> - habe sie ja schon definiert -,

:-)

Struktur kann beides bedeuten:
die struct Definition als auch die sich dann tatsächlich im Speicher 
ergebende Datenstruktur in ihrer vollen Pracht.

> sondern zu den entsprechenden "Arrays"
> eben neue Elemente hinzufügen..

Genau das würde ich auf einem AVR nicht machen.
So wie du das machst, macht man das auf einem PC. Na ja fast. Dort würde 
man anstelle der Arrays wahrscheinlich Listen benutzen. Sind einfacher 
zu erweitern ohne dass ständig alles umkopiert werden muss.

Auf einem AVR hast du aber ein Problem. Du hast nur sehr wenig SRAM und 
die Fragmentierung des Speichers kann zu einem Problem werden. Grob 
gesagt: Du hast zwar in Summe zb 200 Bytes frei, aber diese 200 Bytes 
sind nirgend in einem Rutsch verfügbar, sondern alle Speicherlöcher 
zusammen ergeben 200 Bytes. Da wird es dann schwierig ein Array, welches 
200 Bytes benötigt anzulegen.

Das ist das eine. Dem könnte man noch mit einer Liste anstelle von 
Arrays zumindest teilweise entgegen wirken.

Aber das rettet dich nicht vor dem anderen Problem: Abzuschätzen wann 
der Speicher eigentlich voll ist.

Und um dem entgegen zu wirken, ist die einfachste Variante die, die 
überlegst dir sinnvolle Obergrenzen. Zb. Dass es nicht mehr als 20 
Eingänge geben kann und nicht mehr als 30 Ausgänge. Dann hilft dir das 
System zumindest insofern weiter, als dir die WinAvr ToolChain Bescheid 
gibt, ob diese Menge an Devices überhaupt noch in den Speicher passen 
wird.
1
#include <stdio.h>
2
3
#ifndef TRUE
4
#define TRUE 1
5
#define FALSE 0
6
#endif
7
8
//struktur für variablen
9
struct variable_memory 
10
{
11
  int *    value; //Zeiger auf Values
12
  unsigned bit:4; //bit 0-15 für Bool-Variablen
13
  unsigned is_bool : 1; //variable bool?
14
  unsigned has_low_part: 1; //variable größer als 16 bit?
15
};
16
17
//Port in
18
struct port_in_devices
19
{
20
  struct variable_memory *mem; //Zeiger auf Variable
21
  volatile unsigned char *virt_port; //Zeiger auf Virtuellen Port
22
  unsigned pin:3; //Pin
23
};
24
25
//Port out
26
struct port_out_devices
27
{
28
  struct variable_memory *mem; //Zeiger auf Variable
29
  volatile unsigned char *virt_port; //Zeiger auf Virtuellen Port
30
  unsigned pin:3; //Pin
31
  unsigned is_member_of_lamptest:1; //Soll bei Lamptest angehen
32
  unsigned is_member_of_dim:1; //Soll bei Dim gedimmt werden
33
};
34
35
36
//Encoder
37
struct encoder_devices
38
{
39
  struct variable_memory *mem; //Zeiger auf Variable
40
  volatile unsigned char   *channel_a_port; //Zeiger auf Port Kanal a
41
  unsigned channel_a_pin:3; //Pin Kanal a
42
  volatile unsigned char *channel_b_port; //Zeiger auf Port Kanal b
43
  unsigned channel_b_pin:3; //Pin Kanal b
44
  unsigned long min_value; //Mindestwert
45
  unsigned long max_value; //Maximalwert
46
  unsigned long increment; //um wieviel erhöhen
47
  unsigned has_modulo:1; //Verhalten bei überlauf
48
};
49
50
//Zentraler Speicher
51
#define MAX_NR_VALUES       8
52
#define MAX_NR_VARIABLES  256
53
#define MAX_NR_IN_PORTS    20
54
#define MAX_NR_OUT_PORTS   20
55
#define MAX_NR_ENCODERS    10
56
57
int values[ MAX_NR_VALUES ];
58
59
struct variable_memory variables[ MAX_NR_VARIABLES ];
60
61
struct port_in_devices in_ports[ MAX_NR_IN_PORTS ];
62
unsigned char nr_in_ports = 0;
63
64
struct port_out_devices out_ports[ MAX_NR_OUT_PORTS ];
65
unsigned char nr_out_ports = 0;
66
67
struct encoder_devices encoders[ MAX_NR_ENCODERS ];
68
unsigned char nr_encoders = 0;
69
70
unsigned char AddVariable( int Nr, int *Value, unsigned char isBool, unsigned char hasLow )
71
{
72
  if( Nr < MAX_NR_VARIABLES ) {
73
    variables[Nr].value        = Value;
74
    variables[Nr].is_bool      = isBool;
75
    variables[Nr].has_low_part = hasLow;
76
    return TRUE;
77
  }
78
  
79
  return FALSE;
80
}
81
82
unsigned char AddInPort( struct variable_memory * var, volatile unsigned char * port, unsigned char pin )
83
{
84
  if( nr_in_ports < MAX_NR_IN_PORTS - 1 ) {
85
    in_ports[ nr_in_ports ].mem       = var;
86
    in_ports[ nr_in_ports ].virt_port = port;
87
    in_ports[ nr_in_ports ].pin       = pin;
88
    
89
    nr_in_ports++;
90
    
91
    return TRUE;
92
  }
93
  
94
  return FALSE;
95
}
96
97
unsigned char AddOutPort( struct variable_memory * var, volatile unsigned char * port, unsigned char pin,
98
                          unsigned char doLampTest, unsigned char doDim )
99
{
100
  if( nr_out_ports < MAX_NR_OUT_PORTS - 1 ) {
101
    out_ports[ nr_out_ports ].mem                    = var;
102
    out_ports[ nr_out_ports ].virt_port              = port;
103
    out_ports[ nr_out_ports ].pin                    = pin;
104
    out_ports[ nr_out_ports ].is_member_of_lamptest  = doLampTest;
105
    out_ports[ nr_out_ports ].is_member_of_dim       = doDim;
106
    
107
    nr_out_ports++;
108
    
109
    return TRUE;
110
  }
111
  
112
  return FALSE;
113
}
114
115
int main()
116
{
117
  // systemvariablen anlegen
118
  AddVariable(  1, &values[0], FALSE, FALSE );
119
  AddVariable( 10, &values[1], TRUE,  FALSE );
120
  AddVariable( 20, &values[2], FALSE, TRUE );
121
122
  // alle systemweiten Eingänge
123
  AddInPort( &variables[4], &virtual_port[OFFSET_PORTA + 0], 2 );
124
125
  // alle systemweiten Ausgänge
126
  AddOutPort( &variables[0], &virtual_port[OFFSET_PORTB + 1], 0, TRUE, FALSE );
127
}

So blöd es auch klingt. Mit so einer Strategie wirst du wahrscheinlich 
mehr Devices anlegen können, als wie wenn du das ganze dynamisch mit 
realloc aufbaust. Speicherfragmentierung lässt grüßen.

von Phillip H. (philharmony)


Lesenswert?

Klingt wie immer super vernünftig und logisch, vielen Dank erstmal. Ich 
werd mir das heute Abend mal in Ruhe zu Gemüte führen. Soweit ich das 
bisher verstanden habe sehe ich für alle Geräte eine maximal Anzahl vor 
(die dann unter maximaler Ausnutzung aller den Speicher nicht überlaufen 
lässt) und fülle sie einfach eine nach der Anderen mit Daten, wobei die 
"nr_of_..."-Variable mir angibt, bis wohin überhaupt gefüllt ist.
Das Setup soll sowieso von einem PC-Program aus gesteuert werden, daher 
kann ich dort schon vorab eine Menge abfangen und könnte auch mehr 
speicher vorsehen als maximal reinpassen würde.
(Denn ich weiß nicht, ob der Benutzer alle 144 "virtuellen Pins" für 
Schalter, oder für LEDs oder in mischform benutzen will, oder 48 davon 
außen vor lässt um 8 ADCs zu verwenden).
In dieser Flexibilität gegenüber dem Benutzer liegt ja meine 
Hauptschwierigkeit.
Werde das ganze aber mal nach deiner Idee angehen, die MAX_NUMBER_OF 
können ja auch im EEPROM stehen und vom Setup so eingestellt werden, daß 
die Summe wieder passt...

von Phillip H. (philharmony)


Lesenswert?

So, hab das ganze mal angeschaut bin dabei es einzubauen, das sieht 
wirklich nach der vernünftigsten Lösung aus.
Ich habe mal noch eine Frage zum Thema Speicherverbrauch: Wieviel Platz 
braucht eigentlich ein Zeiger? Wird der als Variable angelegt oder 
intern nur verlinkt?
Wenn ich jetzt 144 port_in_devices habe (soviel kann die hardware), 
würde es dann sinn machen, die True und False-Eigenschaften nicht als 
Byte reinzuschreiben, sondern einmal True und einmal False zu speichern 
und dann nur noch darauf zu zeigen? Sind immerhin 18 Byte pro 
Eigentschaft...

von P. S. (Gast)


Lesenswert?

Statt Arrays fuer die verschiedenen Typen bereit zu halten, duerfte es 
besser sein ein Array fuer alle Typen gemeinsam zu haben. Am einfachsten 
geht das indem du eine Union fuer alle Typen erstellst und um die 
nochmal eine struct legst, die einen Identifier hat, welcher Typ 
enthalten ist.

So ungefaehr:

union AllTypesUnion
{
  struct TypeA a;
  struct TypeB b;
  struct TypeC c;
};

struct AllTypesStruct
{
  uint8_t type;

  union AllTypesUnion content;
};

Auf die Pointer wuerde ich nach Moeglichkeit verzichten, die Ports 
kannst du ja auch einfach durchnummerieren.

von Phillip H. (philharmony)


Lesenswert?

Bringt das Speicherplatz? Die Ports sind durchnummeriert und ich greife 
ja sowieso mit verschiedenen Funktionen darauf zu wodurch die 
verschiedenen typen schon vorteilhaft sind.
1
for(i=0; i < (sizeof out_ports / sizeof (struct port_out_devices)); i++)
2
  {
3
    set_port(&out_ports[i]);
4
  }

Ich bin grade am rumrechnen, wieviele Variablen und bytes im 
Zentralspeicher ich wirklich brauche, und daher eben meine frage wie ich 
noch Speicherplatz sparen kann...

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:
> Bringt das Speicherplatz?

In deinem Fall leider nicht, weil die diversen structs unterschiedliche 
Speicheranforderungen haben.

> Ich bin grade am rumrechnen, wieviele Variablen und bytes im
> Zentralspeicher ich wirklich brauche, und daher eben meine frage wie ich
> noch Speicherplatz sparen kann...

Viel wird da nicht mehr gehen.

Eines würde noch gehen. Anstatt der Pointer könntest du Indizes 
verwenden. Wenn du zb nur max. 200 values haben kannst, die noch dazu 
alle in einem Array sind.

Anstelle von
1
struct variable_memory 
2
{
3
  int *    value; //Zeiger auf Values
4
  unsigned bit:4; //bit 0-15 für Bool-Variablen
5
  unsigned is_bool : 1; //variable bool?
6
  unsigned has_low_part: 1; //variable größer als 16 bit?
7
};

hast du dann
1
struct variable_memory 
2
{
3
  unsigned char valueNr;
4
  unsigned bit:4; //bit 0-15 für Bool-Variablen
5
  unsigned is_bool : 1; //variable bool?
6
  unsigned has_low_part: 1; //variable größer als 16 bit?
7
};

was dir bei jedem struct variable_memory Objekt 1 Byte einbringt. Für 
das einzelne Objekt ist das nicht viel, aber in Summe läppert es sich.
Für die anderen Structs dann sinngemäss genauso: Überall dort wo du 
einen Pointer in ein Array hast UND du weißt das das Array weniger als 
255 Elemente haben wird, kannst du den Pointer durch den Index ersetzen.
255 deshalb, weil du dann den Indexwert 0xFF für 'kein Eintrag' (also 
das was vorher der NULL Pointer war) reservierst.

(Auf der anderen Seite erhebt sich die Frage, warum du überhaupt einen 
Verweis aus einem variable_memory Objekt auf einen value hast. Warum 
nicht den value direkt in die struct variable_memory Objekt einbauen?)

von Phillip H. (philharmony)


Lesenswert?

>>Auf der anderen Seite erhebt sich die Frage, warum du überhaupt einen
>>Verweis aus einem variable_memory Objekt auf einen value hast. Warum
>>nicht den value direkt in die struct variable_memory Objekt einbauen?

Das hat den Hintergrund, daß ich teilweise nur Bits (state von 
Port-Pins) speichere, und dann einfach 16 Pins auf den selben Value 
zeigen lasse plus der Bit-Nummer.
Auf der anderen Seite sind manche Variablen länger als 16 Bit, denen 
gebe ich dann einen Low-Part und nehme den Valuie an der "bezeigten" 
Adresse und den an der nächsten. Damit habe ich dann 32bit.
Das mit den Indizes wäre eine Idee, also anstatt dem Pointer auf Int 
einfach die nummer des Values im Array reinschreiben? Ich dachte immer 
Pointer wären schön klein bzw würde sowieso angelegt werden, 
aberanscheinend fressen die auch ganz schön Speicher...

So lange ich selbst die Hardware in meinem eigenen Projekt konfiguriere 
kann ich das alles wunderbar "Hard-Coded" in den Quelltext aufnehmen und 
brauche die ganze Initialisierung nicht, sobald das aber jemand anderes, 
im besten Fall später ein Kunde nach seinen Wünschen zuweisen soll bin 
ich z.Zt. einfach am Ende mit meinem Wissen.
Ich sollte
Eventuell könnte man noch so etwas wie beim Paparazzi-Projekt (bekannt?) 
machen, wo ein Compiler im Paparazzi-Center mit dabei ist und man darin 
per Code-Generator die Zuweisung macht.

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> Das mit den Indizes wäre eine Idee, also anstatt dem Pointer auf Int
> einfach die nummer des Values im Array reinschreiben? Ich dachte immer
> Pointer wären schön klein

Neben double und long sind Pointer normalerweise einer der längsten 
Dtaentypen, die es in C gibt. Schliesslich muss in einem Pointer ja auch 
die auf dem System größte möglcihe Speicheradresse in Zahlenform 
gespeichert werden können. Kann eine Architektur mehr als 256 Bytes 
adressieren, muss ein Pointer schon mal mindestens 2 Bytes groß sein.

> bzw würde sowieso angelegt werden,

Nö, warum sollen sie?
Eine Pointervariable ist auch nur eine Variable in der ein Zahlenwert 
gespeichert wird. Definierst du keine Variable, wird sie auch nicht 
angelegt.
Jedes Objekt hat zwar eine Adresse im Speicher, so wie jedes Haus in 
einer Strasse eine Hausnummer hat. Aber wenn niemand da ist, der sich 
die Hausnummer notiert, muss man auch keinen Zettel reservieren, auf dem 
man sich die Hausnummer aufschreiben kann.

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.