Forum: PC-Programmierung Wie Objekte in einem Array aktuell halten und umgekehrt?


von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich frage mich derzeit ob es möglich ist und wenn ja wie, dass man 
einzelne Objekte in einem Array zusammenfassen und automatisch 
gegenseitig aktuell halten kann.
Im Programm ist es für mich günstiger ich arbeite direkt mit einzelnen 
Objektnamen. Zum speichern und lesen hätte ich jedoch gern alles in 
einem Array zusammengefasst, weil das einfacher ist und funktioniert. 
Aktuell fällt mir dazu nur ein 2 Funktionen zu schreiben. Daten von den 
Einzelobjekten in das Array kopieren und dann Array speichern. Bzw. 
Array auslesen, Array Daten in die Einzelobjekte kopieren und damit 
arbeiten.

Frage wäre eben ob man die Einzelobjekte irgendwie als Referenz bei der 
Array Initialisierung mitgeben kann. Vielleicht kennt jemand eine 
Möglichkeit.

Testcode, damit man sieht das sumObj nur Kopien erstellt.
1
#include <iostream>
2
#include <sstream>
3
#include <fstream>
4
#include <array>
5
using namespace std;
6
7
struct Daten {
8
  uint16_t left     {};
9
  uint16_t right    {};
10
  uint16_t current  {};
11
  char name [9] {'\0'};
12
};
13
14
Daten obj0 {100, 200, 300, "obj.0"};
15
Daten obj1 {101, 201, 301, "obj.1"};
16
17
std::array<Daten, 2> sumObj {obj0, obj1};
18
19
int main()
20
{
21
  cout << obj0.name << "  " << obj0.left  << endl;
22
  cout << obj1.name << "  " << obj1.right << endl;
23
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
24
  cout << sumObj[1].name << "  " << sumObj[1].right << '\n' << endl;
25
26
  sumObj[0].left  = 556;
27
  sumObj[1].right = 667;
28
29
  cout << obj0.name << "  " << obj0.left  << endl;
30
  cout << obj1.name << "  " << obj1.right << endl;
31
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
32
  cout << sumObj[1].name << "  " << sumObj[1].right << '\n' << endl;
33
34
  obj0.left  = 778;
35
  obj1.right = 889;
36
37
  cout << obj0.name << "  " << obj0.left  << endl;
38
  cout << obj1.name << "  " << obj1.right << endl;
39
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
40
  cout << sumObj[1].name << "  " << sumObj[1].right << '\n' << endl;
41
}

von Alexander (alecxs)


Lesenswert?

ChatGPT sagt, mach ein Array aus Pointern.
1
Daten* sumObj[] = {&obj0, &obj1};

von Uwe B. (boerge) Benutzerseite


Lesenswert?

Alexander schrieb:
> ChatGPT sagt, ...

...boooooaaaaa..., und was ist deine persönliche Meinung/Antwort dazu?

von Wilhelm M. (wimalopaan)


Lesenswert?

Willst Du wirklich Referenzen (hier: Zeiger oder C++-Referenzen) in 
einem Array halten, oder möchtest Du lediglich eine Möglichkeit, über 
eine variablen Anzahl von Objekten eines beliebigen Typs iterieren?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ Alex: das funktioniert leider nicht so einfach

@ Wilhelm: ich gehe davon aus das ich C++ Referenzen benötige

Bei der weiteren Lösungssuche bin ich auf diese Seite gestoßen
https://www.nextptr.com/question/qa1444367653/array-of-reference_wrapper-an-alternate-array-of-references

Demnach wird ein Konstruktor benötigt. Damit erstmal sachte angefangen 
und probiert. Das funktioniert erstmal wie gewünscht. Variable und Array 
sind jeweils automatisch aktuell wenn man eins von beiden ändert.

Ist das der Weg den ich für meine Struktur usw. weiter verfolgen kann?
1
#include <iostream>
2
#include <sstream>
3
#include <fstream>
4
#include <array>
5
6
using namespace std;
7
8
struct Daten {
9
  Daten(uint16_t& l): left (l)
10
  {}
11
  uint16_t &left;
12
};
13
14
uint16_t m {101};
15
uint16_t n {202};
16
17
Daten objRef [] {m, n};
18
19
int main()
20
{
21
  cout << "m  " << m << endl;
22
  cout << "n  " << n << endl;
23
  cout << objRef[0].left <<         endl;
24
  cout << objRef[1].left << '\n' << endl;
25
26
  m = 301;
27
  n = 401;
28
29
  cout << "m  " << m << endl;
30
  cout << "n  " << n << endl;
31
  cout << objRef[0].left <<         endl;
32
  cout << objRef[1].left << '\n' << endl;
33
34
  objRef[0].left = 501;
35
  objRef[1].left = 601;
36
37
  cout << "m  " << m << endl;
38
  cout << "n  " << n << endl;
39
  cout << objRef[0].left <<         endl;
40
  cout << objRef[1].left << '\n' << endl;

von Veit D. (devil-elec)


Lesenswert?

Wilhelm M. schrieb:
> Willst Du wirklich Referenzen (hier: Zeiger oder C++-Referenzen) in
> einem Array halten, oder möchtest Du lediglich eine Möglichkeit, über
> eine variablen Anzahl von Objekten eines beliebigen Typs iterieren?

Nochmal über die Frage nachgedacht. Das Array benötige ich zum speichern 
und auslesen. Die Daten im Array werden in einem FRAM gespeichert. Das 
Array übergebe ich Funktionen die sich dann mit dem FRAM bzw. der FRAM 
Klasse usw. beschäftigen. Das mit dem FRAM speichern/lesen funktioniert 
alles. Die Array Übergabe vereinfacht alles.

Über das Array iterieren tue ich vorm speichern nach dem lesen nicht. Da 
möchte ich mit den einzelnen Objekten arbeiten ohne über einen Index 
nachdenken zu müssen.

Beantwortet das deine Frage?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

jetzt tue ich mich schwer, vorausgesetzt der Weg ist überhaupt richtig.
Die Übergabe von Objekten an ein Array funktioniert jedoch nicht mehr. 
Warum? Ist doch gleich der Übergabe der Variablen m, n, b. Nur eben als 
struct Objekte.
1
 could not convert 'obj0' from 'Daten' to 'DatenRef'|
1
#include <iostream>
2
#include <sstream>
3
#include <fstream>
4
#include <array>
5
using namespace std;
6
7
struct Daten {
8
  uint16_t left;
9
  uint16_t right;
10
  uint16_t current;
11
};
12
13
struct DatenRef {
14
  DatenRef(uint16_t& l, uint16_t& r, uint16_t& c):
15
     left    (l),
16
     right   (r),
17
     current (c)
18
  {}
19
  uint16_t &left;
20
  uint16_t &right;
21
  uint16_t &current;
22
};
23
24
DatenRef objRef [2] {
25
  {m, n, b},
26
  {m, n, b},
27
};
28
29
Daten obj0 {1,2,3};
30
Daten obj1 {4,5,6};
31
32
// funktioniert nicht - Warum?
33
DatenRef sumObjRef [2] {obj0, obj1};
34
35
int main()
36
{
37
  cout << "m  " << m << endl;
38
  cout << "n  " << n << endl;
39
  cout << objRef[0].left <<         endl;
40
  cout << objRef[1].left << '\n' << endl;
41
42
  m = 301;
43
  n = 401;
44
45
  cout << "m  " << m << endl;
46
  cout << "n  " << n << endl;
47
  cout << objRef[0].left <<         endl;
48
  cout << objRef[1].left << '\n' << endl;
49
50
  objRef[0].left = 501;
51
  objRef[1].left = 601;
52
53
  cout << "m  " << m << endl;
54
  cout << "n  " << n << endl;
55
  cout << objRef[0].left <<         endl;
56
  cout << objRef[1].left << '\n' << endl;
57
}

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Alexander schrieb:
> Daten* sumObj[] = {&obj0, &obj1};

Veit D. schrieb:
> @ Alex: das funktioniert leider nicht so einfach

Dasselbe wie Alexander hätte ich auch vorgeschlagen, allerdings etwas 
C++isher mit einem std::array statt dem C-Array und mit Referenzen statt 
Zeigern.

Statt eines Arrays von Referenzen könnte man auch ein Array von 
Strukturen nehmen (wie in deinem ersten Beispiel) und dafür die 
Einzelvariablen zu Referenzen machen. Das hätte den Vorteil, dass die 
Strukturen zusammenhängend im Speicher liegen, was evtl. das Kopieren 
vom und ins FRAM etwas vereinfacht.

Um dein Problem besser zu verstehen, hilft es vielleicht, wenn du 
schreibst, warum der Vorschlag von Alexander für dich nicht 
funktioniert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> jetzt tue ich mich schwer, vorausgesetzt der Weg ist überhaupt richtig.
> Die Übergabe von Objekten an ein Array funktioniert jedoch nicht mehr.

Echt. Na, dann fehlen doch ein paar Grundlagen oder es ist schon zu 
spät.

> Warum? Ist doch gleich der Übergabe der Variablen m, n, b. Nur eben als
> struct Objekte.

Nein.
Jetzt bräuchtest Du
1
struct DatenRef {
2
    DatenRef(Daten& d) : v{d} {}
3
    Daten& v;
4
};

Vorher hattest Du einen dreistelligen ctor mit uint16_t, jetzt brauchst 
Du einen einstelligen mit Daten.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wilhelm M. schrieb:
>> Willst Du wirklich Referenzen (hier: Zeiger oder C++-Referenzen) in
>> einem Array halten, oder möchtest Du lediglich eine Möglichkeit, über
>> eine variablen Anzahl von Objekten eines beliebigen Typs iterieren?
>
> Nochmal über die Frage nachgedacht. Das Array benötige ich zum speichern
> und auslesen. Die Daten im Array werden in einem FRAM gespeichert. Das
> Array übergebe ich Funktionen die sich dann mit dem FRAM bzw. der FRAM
> Klasse usw. beschäftigen. Das mit dem FRAM speichern/lesen funktioniert
> alles. Die Array Übergabe vereinfacht alles.

Das hört sich so an, als würdest Du eine Klasse benötigen, die den 
Zugriff auf das FRAM kapselt. Was für DT sollen im FRAM gespeichert 
werden? Homogen ein DT? Dann kannst Du dieser Klasse einen operator[] 
geben, der die Objekte aus dem FRAM liefert. GGf. haben die DT der 
Objekte im FRAM einen geeigneten ctor, und Deine FRAM-Klasse konstruiert 
die Objekte aus ein paar Bytes aus dem FRAM.

Dein Weg mit (abstrakten) Referenzen in einem Array könnte man 
verfolgen, wenn diese Referenzen keine C++-Referenzen oder C++-Zeiger 
sind, sondern z.B. einfach Indizes für Objekte im FRAM. Die Klasse zur 
Abstraktion des FRAM weiß dann damit umzugehen und popelt die Objekte 
aus dem FRAM.

BTW: C++-Referenzen sind sog. non-nullable, non-reseatable, no-object 
Referenzen. Sie müssen also immer auf ein und dasselbe Objekt verweisen 
mit dem sie initialisiert worden sind. Diese (gute) Einschränkung wird 
Dein Vorhaben aber wahrscheinlich unsinnig machen.
Reference-Wrapper beseitigen je nach Implementierung das "no-object" und 
/ oder "non-reseatable"  aus der obigen Liste. Das "reseatable" geht 
nur, wenn sie intern mit Zeiger arbeiten.

In der stdlibc++ gibt es

https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

als template. Dann brauchst Du Deine DatenRef-Klasse nicht.
Das ist einfach eine Wrapper-Klasse um Zeiger, die sich dann so 
"anfühlen" wie direkte Referenzen, d.h. im Gegensatz zu Zeigern entfällt 
beim Zugriff der Indirektionsoperator "*".

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Dasselbe wie Alexander hätte ich auch vorgeschlagen, allerdings etwas
> C++isher mit einem std::array statt dem C-Array und mit Referenzen statt
> Zeigern.

Da C++-Referenzen keine Objekte sind, lassen sie sich nicht in einem 
Array speichern.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Yalu X. schrieb:
>> Dasselbe wie Alexander hätte ich auch vorgeschlagen, allerdings etwas
>> C++isher mit einem std::array statt dem C-Array und mit Referenzen statt
>> Zeigern.
>
> Da C++-Referenzen keine Objekte sind, lassen sie sich nicht in einem
> Array speichern.

Stimmt, das war mir nicht bewusst. Dann eben doch ein Array von Zeigern
oder ein Array von std::reference_wrapper<Daten>. Das ist dann halt der
Zugriff auf die Array-Elemente etwas unelegant, weil man immer noch
einen * bzw. ein get() für die Dereferenzierung braucht.

Andersherum sollte es aber auch mit ungewrappten Referenzen gehen:

Yalu X. schrieb:
> Statt eines Arrays von Referenzen könnte man auch ein Array von
> Strukturen nehmen (wie in deinem ersten Beispiel) und dafür die
> Einzelvariablen zu Referenzen machen.

von Sebastian W. (wangnick)


Lesenswert?

In C würde man wohl eine Union aus einem Array und einem Struct mit 
benamsten Elementen nehmen.

LG, Sebastian

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke euch, ich muss das erstmal neu (ein)sortieren ...

Meine FRAM Klasse kann alles speichern und lesen was ich ihr übergebe. 
Die Übergabe eines Arrays hat wie gesagt den Vorteil das damit alles 
bequem am Stück geschrieben und gelesen werden kann.

> In der stdlibc++ gibt es
> https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

Ist das nicht das Gleiche wie in dem Link?
https://www.nextptr.com/question/qa1444367653/array-of-reference_wrapper-an-alternate-array-of-references

Dort verstehe ich das so das wenn beide Varianten gleichwertig sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Meine FRAM Klasse kann alles speichern und lesen was ich ihr übergebe.

Dann übergebe ihr doch einfach die Objekte, fertig. Stichwort: 
variadische Funktion(template)

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Veit D. schrieb:
> Meine FRAM Klasse kann alles speichern und lesen was ich ihr übergebe.
> Die Übergabe eines Arrays hat wie gesagt den Vorteil das damit alles
> bequem am Stück geschrieben und gelesen werden kann.

Dann also evtl. so:

Yalu X. schrieb:
> Statt eines Arrays von Referenzen könnte man auch ein Array von
> Strukturen nehmen (wie in deinem ersten Beispiel) und dafür die
> Einzelvariablen zu Referenzen machen.
1
#include <iostream>
2
#include <array>
3
#include <cstdint>
4
5
using namespace std;
6
7
struct Daten {
8
  uint16_t left, right, current;
9
  char name[9];
10
};
11
12
array<Daten, 2> sumObj {{
13
  { 100, 200, 300, "obj.0" },
14
  { 101, 201, 301, "obj.1" }
15
}};
16
17
Daten
18
  &obj0 { sumObj[0] },
19
  &obj1 { sumObj[1] };
20
21
void fram_read (uint8_t *data, size_t size) {
22
  cout << "read " << size << " bytes\n";
23
}
24
void fram_write(const uint8_t *data, size_t size) {
25
  cout << "write " << size << " bytes\n";
26
}
27
28
int main() {
29
  fram_read((uint8_t*)&sumObj, sizeof sumObj);
30
31
  cout << obj0.name << "  " << obj0.left  << endl;
32
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
33
  sumObj[0].left  = 556;
34
  cout << obj0.name << "  " << obj0.left  << endl;
35
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
36
  obj0.left  = 778;
37
  cout << obj0.name << "  " << obj0.left  << endl;
38
  cout << sumObj[0].name << "  " << sumObj[0].left  << endl;
39
40
  fram_write((uint8_t*)&sumObj, sizeof sumObj);
41
42
  return 0;
43
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Dann also evtl. so:

Das ist jetzt von hinten durch die Brust ins Auge.

Wenn ich das richtig verstanden habe, fühlst dich zu einem Array 
gezwungen, weil du mehrere Objekte Dr fram Klasse übergeben willst?

Dann mache eine variadische Elementfunktion.

Oder eine Std:: Array mit Zeigern, wobei dies nullable Referenzen sind, 
was ggf.nicht willst.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wilhelm M. schrieb:
> Yalu X. schrieb:
>> Dann also evtl. so:
>
> Das ist jetzt von hinten durch die Brust ins Auge.
>
> Wenn ich das richtig verstanden habe, fühlst dich zu einem Array
> gezwungen, weil du mehrere Objekte Dr fram Klasse übergeben willst?

Der Wunsch nach einem Array kommt vom TE. Ich habe versucht, seinen Code
mit relativ wenig Änderungen in etwas Funktionierendes überzuführen.

> Dann mache eine variadische Elementfunktion.

Wir wissen nicht wie viele Instanzen von Daten in der realen Anwendung
benötigt werden. Die Argumentliste der variadischen Funktion könnte u.U.
sehr lang werden. Schon ab etwa 4 Argumenten würde ich das als ziemlich
hässlich empfinden.

> Oder eine Std:: Array mit Zeigern,

Das wurde ja bereits in der ersten Antwort vorgeschlagen, aber der TE
hat das - leider ohne Nennung von Gründen – abgelehnt. Wahrscheinlich
hängt es damit zusammen, dass die Daten

Veit D. schrieb:
> bequem am Stück geschrieben und gelesen werden

können sollen.

In mir keimt der Verdacht, dass das Ganze wieder einmal ein XY-Problem
ist.

: Bearbeitet durch Moderator
von Wilhelm M. (wimalopaan)


Lesenswert?

Yalu X. schrieb:
> Der Wunsch nach einem Array kommt vom TE.

Ja, natürlich. Und an ihn war auch meine Antwort eigentlich gerichtet.

Aber ob jetzt für die Übergabe an fram eine Array erzeugt wird oder die 
Objekte direkt in der argumentliste stehen, dürfte egal sein.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Yalu X. schrieb:

>> Oder eine Std:: Array mit Zeigern,
>
> Das wurde ja bereits in der ersten Antwort vorgeschlagen, aber der TE
> hat das - leider ohne Nennung von Gründen – abgelehnt. Wahrscheinlich
> hängt es damit zusammen, dass die Daten
>
> Veit D. schrieb:
>> bequem am Stück geschrieben und gelesen werden
>
> können sollen.
>
> In mir keimt der Verdacht, dass das Ganze wieder einmal ein XY-Problem
> ist.

Was soll das werden? Man hat nichts gewonnen wenn man sich danach mit 
der Dereferenzierung rumschlagen muss. Das wird in Summe nur anders und 
noch Fehler anfälliger.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

das geht in die richtige Richtung was ich haben möchte. Die Handhabung 
der Benutzung soll einfach sein. Jetzt bekomme ich noch eine 
Fehlermeldung das  DatenRef keinen Member "left" hat. Wie greift man 
darauf zu?

Wegen der Frage wieviel Objekte das werden. bzw. variadischer Funktion.
Mindestens 8 können auch bis 16 werden.
1
#include <iostream>
2
#include <sstream>
3
#include <fstream>
4
#include <array>
5
6
using namespace std;
7
8
struct Daten {
9
  uint16_t left;
10
  uint16_t right;
11
  uint16_t current;
12
};
13
14
struct DatenRef {
15
  DatenRef(Daten& d) : v{d} {}
16
  Daten& v;
17
};
18
19
Daten obj0 {1,2,3};
20
Daten obj1 {4,5,6};
21
22
DatenRef sumObjRef [2] {obj0, obj1};
23
24
int main()
25
{
26
  cout << obj0.left         << endl;
27
  cout << obj1.left         << endl;
28
29
  // 'struct DatenRef' has no member named 'left'
30
  cout << sumObjRef[0].left << endl;
31
  cout << sumObjRef[1].left << '\n' << endl;
32
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

jetzt habe ich das Beispiel von Yalu getestet und siehe da es 
funktioniert.  :-) Es ist alles automatisch syncron. Ganz stark.
Der Trick besteht demnach im umdrehen der Initialisierungen.
Erst das array und dann die Einzelobjekte.
Und siehe da man benötigt keine Dereferenzierungen und den ganzen Kram.
Vielen Dank dafür!
1
#include <iostream>
2
#include <sstream>
3
#include <fstream>
4
#include <array>
5
6
using namespace std;
7
8
struct Daten {
9
  uint16_t left, right, current;
10
  char name[9];
11
};
12
13
array<Daten, 2> sumObj {{
14
  { 100, 200, 300, "obj.0" },
15
  { 101, 201, 301, "obj.1" }
16
}};
17
18
Daten &obj0 { sumObj[0] };
19
Daten &obj1 { sumObj[1] };
20
21
int main()
22
{
23
  cout << obj0.left      << endl;
24
  cout << obj1.left      << endl;
25
  cout << sumObj[0].left << endl;
26
  cout << sumObj[1].left << '\n' << endl;
27
28
  obj0.left = 400;
29
  obj1.left = 501;
30
31
  cout << obj0.left      << endl;
32
  cout << obj1.left      << endl;
33
  cout << sumObj[0].left << endl;
34
  cout << sumObj[1].left << '\n' << endl;
35
36
  sumObj[0].left = 600;
37
  sumObj[1].left = 601;
38
39
  cout << obj0.left      << endl;
40
  cout << obj1.left      << endl;
41
  cout << sumObj[0].left << endl;
42
  cout << sumObj[1].left << '\n' << endl;
43
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

die Übergabe von 'sumObj' funktioniert auch wie erwartet weiterhin mit 
meinem FRAM. Das ist sehr schön.

Vielleicht noch eine Erklärung warum ich auf ein Array bestanden habe. 
Variadische Funktion (Template) und damit alle Objekte einzeln übergeben 
wäre zwar auch möglich, aber das erzeugt wiederum mehr Overhead auf dem 
I2C Bus. Wenn ich ein Array schreiben oder lesen lassen kann, habe ich 
nur einmalig Zugriffsvorbreitungen auf das FRAM und danach werden egal 
wieviele Bytes hintereinander geschrieben oder gelesen. Das ist sehr 
effektiv.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Jetzt bekomme ich noch eine
> Fehlermeldung das  DatenRef keinen Member "left" hat. Wie greift man
> darauf zu?

Veit D. schrieb:
> cout << sumObjRef[0].left << endl;

Auch hier ist doch auch die Fehlermeldung eindeutig: der Elementtyp des 
Arrays sumObjRef ist DatenRef. Der Type hat kein Element left, sondern 
ein Element v des Typs Daten. Und erst Daten hat das Element v.

Es muss also heißen:
1
sumObjRef[0].v.left

Würdest Du std::reference_wrapper<> einsetzen, wäre diese zusätzliche 
Indirektion nicht notwendig.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Erst das array und dann die Einzelobjekte.

Vorsicht: keine Einzelobjekte, sondern Referenzen (Namen) dafür. Mir 
scheint, Du hast das Konzept der C++-Referenzen nicht verstanden.

Warum willst Du denn dann, wenn Du das Array hast, noch die einzelnen 
Referenzen noch haben? Warum nimmst Du nicht "sprechende" Indexnamen 
stattdessen, etwa als enum?
1
enum Names {A = 0, B, _NumberOfObjects};
2
3
array<Daten, _NumberOfNames> sumObj {{
4
  { 100, 200, 300, "obj.0" },
5
  { 101, 201, 301, "obj.1" }
6
}};
7
8
FRam fram;
9
10
sumObj[A] = ...;
11
12
fram.save(sumObj);

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> die Übergabe von 'sumObj' funktioniert auch wie erwartet weiterhin mit
> meinem FRAM. Das ist sehr schön.
>
> Vielleicht noch eine Erklärung warum ich auf ein Array bestanden habe.
> Variadische Funktion (Template) und damit alle Objekte einzeln übergeben
> wäre zwar auch möglich, aber das erzeugt wiederum mehr Overhead auf dem
> I2C Bus.

Nun, das ist kein Widerspruch: das Array könnte lokal in FRAM erzeugt 
werden. Aber so würde ich es auch nicht machen.

Sondern: falls Du Dich mit Faltungsausdrücken auskennst, dann brauchst 
Du auch intern in FRAM kein Array. Ähnlich einer Iteration über die 
Bytes eines Arrays, kannst Du mit einer Faltung über die Elemente eines 
Parameterpacks iterieren. Damit hast Du dieselbe Effektivität eines 
contiguous-write auf dem I2C.

Weiterhin hast Du beim Array die Einschränkung, dass das ein homogener 
Container ist, bei der variadischen Funktion nicht.

Im übrigen könntest Du alle Deine Objekte in eine Klasse packen, dass 
ist dann ggf. so ähnlich wie ein heterogener Container.
1
struct FRamData {
2
    DataA d0;
3
    DataB d1;
4
    DataA d2;
5
    ...
6
};

und dann
1
FRamData data;
2
3
FRam.save(data);


Andere Frage: es scheint so zu sein, dass Du die Daten aus dem FRAM in 
den Objekten pufferst statt direkt auch das FRAM zuzugreifen?
Daher stimme ich Yalu X. zu, dass wir hier ein Problem haben, was 
eigentlich anders gelöst werden sollte ...

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,
1
sumObjRef[0].v.left

Danke.

Das sieht auch sehr praktisch aus und einfach handhabbar. Beginnt die 
enum Durchnummerierung intern nicht default bei 0? Ich dachte das wäre 
garantiert?
1
enum Names {A = 0, B, numberOfObjects};
2
3
array<Daten, numberOfObjects> sumObj {{
4
  { 100, 200, 300, "obj.0" },
5
  { 101, 201, 301, "obj.1" }
6
}};
7
8
FRam fram;
9
10
sumObj[A] = ...;
11
12
fram.save(sumObj);

Mit Faltungsausdrücken kenne ich mich nicht aus. Ich weiß nur es gehört 
zu variadischen Funktionen.

Ist 'homogen' nicht etwas Gutes? Sollte doch keine Einschränkung sein.

> Andere Frage: es scheint so zu sein, dass Du die Daten aus dem FRAM in
> den Objekten pufferst statt direkt auch das FRAM zuzugreifen?
> Daher stimme ich Yalu X. zu, dass wir hier ein Problem haben, was
> eigentlich anders gelöst werden sollte ...

Aktuelle Überlegung zur Handhabung mit den Daten ist folgende.
Das sind Daten von Servos von meiner Modelleisenbahn.
Die Daten sollen nach Controller Reset bzw. Kaltart aus dem FRAM gelesen 
und danach aktuell gehalten werden. Es kommt mir einfacher vor wenn ich 
zu bestimmten Zeitpunkten alle Daten ins FRAM schreibe und damit aktuell 
halte.
Ich weiß nicht wie eine andere Lösung dafür aussehen könnte die genauso 
einfach handhabbar ist. Was stört euch denn am puffern im RAM?

von Rick (rick)


Lesenswert?

Veit D. schrieb:
> Was stört euch denn am puffern im RAM?
Wie häufig ändern sich denn die Daten?
Und: Nutzt sich das FRAM beim Schreiben bzw. Lesen irgendwie ab?
Oder ist der einzige Nachteil die Zugriffsgeschwindigkeit?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Mit Faltungsausdrücken kenne ich mich nicht aus. Ich weiß nur es gehört
> zu variadischen Funktionen.

Etwas allgemeiner: zu Parameterpacks.

>
> Ist 'homogen' nicht etwas Gutes? Sollte doch keine Einschränkung sein.

Ja, homogen macht es einfacher. Heterogene Container wie std::tuple<> 
sind in ihrer Handhabung es gewöhnungsbedürftig, weil man hier mit einem 
Visitor-Pattern arbeitet.

> Die Daten sollen nach Controller Reset bzw. Kaltart aus dem FRAM gelesen
> und danach aktuell gehalten werden. Es kommt mir einfacher vor wenn ich
> zu bestimmten Zeitpunkten alle Daten ins FRAM schreibe und damit aktuell
> halte.

Objektorientiert wäre jetzt eher ein Beobachter-Pattern.

> Ich weiß nicht wie eine andere Lösung dafür aussehen könnte die genauso
> einfach handhabbar ist. Was stört euch denn am puffern im RAM?

Mich stört daran gar nichts, wenn Du mit dem RAM Verbrauch leben kannst.

Ich würde eher die Objekte, die im FRAM gesichert werden sollen, in eine 
Klasse packen. Und diese Klasse wiederum wird vom FRAM-Controller 
verwendet. Wenn die Objekte modifiziert werden, sind sie "dirty", und 
der FRAM-Controller kann sie in regelmäßigen Abständen sichern. Das ist 
das Prinzip eines Observers/Observables, das kann man mit statischer 
(templates) oder dynamischer Assoziation (klassische OO) lösen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Rick schrieb:
> Veit D. schrieb:
>> Was stört euch denn am puffern im RAM?
> Wie häufig ändern sich denn die Daten?
> Und: Nutzt sich das FRAM beim Schreiben bzw. Lesen irgendwie ab?
> Oder ist der einzige Nachteil die Zugriffsgeschwindigkeit?

Das ist alles nicht das Problem. Ich möchte unnötige Buszugriffe 
vermeiden. Die Hauptaktivität auf dem I2C Bus sind Sensorabfragen. 
Hierbei habe ich zeitlich keine Probleme aber ich möchte mir auch keine 
zusätzlichen Probleme schaffen.

Wenn ich jedem Objekt noch einen Parameter für seine FRAM Zelladdresse 
mitgebe, die man zur Compilerzeit berechnen lässt, dann sollte eine Idee 
keimen. 2 Einzelzugriffe hintereinander (lesen, schreiben) sind  kürzer 
wie wenn das gesamte Array geschrieben wird. Wenn es noch effektiver 
sein soll könnte die Zellberechnung auch schnell aufwendig werden, wenn 
man gezielt auf einen Member eines Objekts zugreifen möchte.

Die Frage ist was es sonst noch für Ideen gibt um effektiv ohne großen 
Aufwand mit einzelnen Objekten direkt aus/zum dem FRAM zu arbeiten.

> Nutzt sich das FRAM beim Schreiben bzw. Lesen irgendwie ab?
Das ist unbedeutend, rein praktisch gesehen nicht. Deswegen nehme ich 
FRAM.

> Oder ist der einzige Nachteil die Zugriffsgeschwindigkeit?
Es gibt keinen Nachteil. Im Vergleich zum EEprom sogar schneller, weil 
es keine Zwangspausen gibt.
Denoch dauert jeder Buszugriff seine Zeit welche sich summieren kann.

Für eine saubere Steuerung muss man sich schon paar Gedanken machen bzw. 
Überlegungen anstellen. Die wie man sieht auch wieder über den Haufen 
geworfen werden können. Bsp. für die Weichensteuerung gibt es 2 
Möglichkeiten. Entweder es werden alle Weichen gestellt bevor ein Zug 
losfährt. Oder die Weichen stellen sich automatisch. Bei automatisch 
muss man Zeiten beachten. Weil die Weiche muss gestellt sein bevor der 
Zug "auf der Weiche" ist.

Edit:

@ Wilhelm:
RAM hat der AVRxDB genügend. Eine FRAM Controller Klasse klingt auch 
gut. Ich habe jetzt die Qual der Wahl.

Danke euch für die Ideen. Ich muss neu überlegen was ich wie machen 
werde.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> RAM hat der AVRxDB genügend.

Software ist wie ein ideales Gas, sie füllt auch die letzten Ecken des 
Speichers ;-)

> Eine FRAM Controller Klasse klingt auch
> gut.

Ich dachte, Du machst das in OO bzw. OB? Deswegen war es für mich keine 
Frage, dass Du eine FRAM-Controller Klasse benötigst.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

bei uns gehört es scheinbar zum festen Programm das immer wieder kleine 
Missverständnisse entstehen.  :-)
Ja ne ist schon klar. Ich wollte damit nur höflich zum Ausdruck bringen 
das ich das mache. Ich benötige noch andere Klassen die dann alle am 
Ende in einer Klasse "Steuerung" o.ä. enden und miteinander agieren. Das 
grundlegende Konzept steht. Einzelhardwaretest sind durch. Jetzt werden 
die Gedanken um das Konzept immer feiner und "nebenbei" wird 
programmiert. Alles wird gut.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich bin dabei paar Optionen auszutüfteln. Sowas macht Spass. Bei 
möglichen 16 Objekten je 18 Bytes sind es in Summe 256. 256 Bytes 
schreiben wenn sich zum Zeitpunkt immer nur zwei Bytes ändern ist nun 
auch nicht so sinnvoll, denke ich, auch wenn prinzipiell möglich.

Wenn ich für jedes Objekt noch die Startadresse berechne, kann ich 
bequem jederzeit einzelne Objekte aktualisieren. Wenn ich bei der 
Initialisierung der Berechnungsfunktion die Indexnummer als Parameter 
händisch eintrage funktioniert das.

Weil das vielleicht fehlerträchtig sein könnte, wollte ich das ohne 
Parameter machen. Siehe unteres Bsp. Weil die Index Inkrementierung 
jedoch nicht sauber konstant ist, kommt mindestens eine Warnung. Für die 
Art der Berechnung benötigt man jedoch soweit ich das erfasse einen sich 
ändernden Zwischenspeicher der letzten Adresse. Sieht jemand eine 
elegante Compilezeit fähige Lösung?

Denn wenn ich statt der Indexnummer die vorher berechnete Adresse 
verwende,
1
constexpr uint16_t calcFramAddr (const uint16_t addr) {
2
  return (addr + objLength);
3
}
4
5
Daten obj0 (100, 200, 300, 0,                           "obj.0");
6
Daten obj1 (101, 201, 301, calcFramAddr(obj0.cellAddr), "obj.1");
7
Daten obj2 (102, 202, 302, calcFramAddr(obj1.cellAddr), "obj.2");
ist das auch nicht besser als die Indexnummer. Eher verwirrender zu 
lesen.

Also meine Frage wäre gibt es eine Möglichkeit sauber zur Comilezeit die 
jeweilige Anfangsadresse des Objekts zuberechnen ohne Parameter?
1
#include <iostream>
2
using namespace std;
3
4
struct Daten {
5
  uint16_t left, right, current;
6
  const uint16_t cellAddr;
7
  char name[9];
8
};
9
10
constexpr uint16_t objLength {sizeof(Daten)};
11
12
constexpr uint16_t calcFramAddr (const uint16_t index) {
13
  return (index * objLength);
14
}
15
16
Daten obj0 (100, 200, 300, calcFramAddr(0), "obj.0");
17
Daten obj1 (101, 201, 301, calcFramAddr(1), "obj.1");
18
Daten obj2 (102, 202, 302, calcFramAddr(2), "obj.2");
19
20
int main()
21
{
22
  cout << obj0.name << "  " << obj0.cellAddr << endl;
23
  cout << obj1.name << "  " << obj1.cellAddr << endl;
24
  cout << obj2.name << "  " << obj2.cellAddr << '\n' << endl;
25
}

oder
1
#include <iostream>
2
using namespace std;
3
4
struct Daten {
5
  uint16_t left, right, current;
6
  const uint16_t cellAddr;
7
  char name[9];
8
};
9
10
uint16_t nextIndex;
11
12
constexpr uint16_t objLength {sizeof(Daten)};
13
14
constexpr uint16_t calcFramAddr (void) {
15
  return (nextIndex++) * objLength;
16
}
17
18
Daten obj0 (100, 200, 300, calcFramAddr(), "obj.0");
19
Daten obj1 (101, 201, 301, calcFramAddr(), "obj.1");
20
Daten obj2 (102, 202, 302, calcFramAddr(), "obj.2");
21
22
int main()
23
{
24
  cout << obj0.name << "  " << obj0.cellAddr << endl;
25
  cout << obj1.name << "  " << obj1.cellAddr << endl;
26
  cout << obj2.name << "  " << obj2.cellAddr << '\n' << endl;
27
}

mit
1
main.cpp|25|warning: the value of 'nextIndex' is not usable in a constant expression [-Winvalid-constexpr]|
2
main.cpp|20|note: 'uint16_t nextIndex' is not const
Was verständlich ist und ich staune das er es dennoch berechnen kann. 
Mit einem avr-gcc < 13 kommt es zum Fehler statt Warnung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wo ist denn Dein Array hin?

von Wilhelm M. (wimalopaan)


Lesenswert?

Ansonsten: alles in eine Klasse und

https://en.cppreference.com/w/cpp/types/offsetof

von Veit D. (devil-elec)


Lesenswert?

Hallo,

> Wo ist denn Dein Array hin?

Durch die Bemerkung "warum nicht direkt mit dem FRAM arbeiten" war ich 
angestachelt in die Richtung zu denken. Aktuell ist das array damit 
erstmal weg. Mittels der Adresse je Objekt liegen die Daten weiterhin 
hintereinander im FRAM.

> https://en.cppreference.com/w/cpp/types/offsetof

Danke.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
>> Wo ist denn Dein Array hin?
>
> Durch die Bemerkung "warum nicht direkt mit dem FRAM arbeiten" war ich
> angestachelt in die Richtung zu denken. Aktuell ist das array damit
> erstmal weg. Mittels der Adresse je Objekt liegen die Daten weiterhin
> hintereinander im FRAM.
>
>> https://en.cppreference.com/w/cpp/types/offsetof
>
> Danke.

Warum sollen die einzelnen Objekte wissen, an welcher Stelle sie im FRAM 
liegen? Das ist nicht ihre Verantwortung. Sondern das ist die 
Verantwortung des FRAM-Controllers: SRP.

Daher: der FRAM-Controller scannt periodisch, welche der Objekte "dirty" 
sind. Nur die schreibt er weg. Dabei berechnet er den Index während des 
Scans. Übrigens muss der Scan auch nicht "am Stück" durchgeführt werden, 
sondern in Form einer "Coroutine" bzw. einer FSM im FRAM-Controller, so 
dass er keine Laufzeit-Hickups verursacht.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich überlege mir das alles nochmal. Nur aktuell sehe ich keinen Grund 
für eine extra array Verwaltung namens FRAM-Controller. Wenn der blind 
scannen muss um irgendwas zu ändern, dann wird das
a) zeitlich größerer Aufwand
b) muss noch ein extra Puffer her damit er zwischen alten und geänderten 
Wert beim Scan unterscheiden kann

Ich kann mit der eigenen Objektadresse jedes Objekt sich selbst 
verwalten lassen. Nach jeder Änderung schreibt es den einen Wert in das 
FRAM. Das geht dann auch sehr schnell und erlaubt mir das sofortige 
speichern. Selbstverwaltung ist doch eigentlich eine geile Sache. ;-)

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> ich frage mich derzeit ob es möglich ist und wenn ja wie, dass man
> einzelne Objekte in einem Array zusammenfassen und automatisch
> gegenseitig aktuell halten kann.

Ich benutze für sowas structs. Structs kann man beliebig kaskadieren.
Man kann somit beliebig tief auf Elemente zugreifen, trotzdem bleibt das 
immer ein einziger Speicherbereich.
Für den Zugriff auf Arrayelemente benutze ich keine magischen Zahlen 
0,1,2,..n, sondern Enums.

Hier mal ein Beispiel in C:
1
enum
2
{
3
  CON_HVRAMP,
4
  CON_DEBUG,
5
  CON_REPLYWAIT,
6
  CON_ERRTIME,
7
  CON_VFILLIM,
8
  CON_IFILLIM,
9
  CON_IFIL,
10
  CON_IFILSTB,
11
  CON_IFILRAMP,
12
  CON_IEMISS,
13
  CON_AVG_CNT,
14
  CON_REG_TIME,
15
  CON_REG_P,
16
  CON_REG_I,
17
  CON_REG_D,
18
  CON_IEMDEGAS,
19
  CON_VAL_SIZE
20
};
21
22
/* associated EEPROM usage
23
Parameter       .gain   .offset .comp   .lower  .upper  .val
24
ADC               X       X       X       -       -       -
25
DAC               X       X       -       X       X       X
26
Control           -       -       -       X       X       X
27
*/
28
29
typedef struct                          // DAC parameter
30
{
31
  float gain;
32
  float offset;
33
  float lower;
34
  float upper;
35
  float val;
36
} eedacval_t;
37
38
typedef struct                          // ADC parameter
39
{
40
  float gain;
41
  float offset;
42
  float comp;                           // compensation of internal resistor current
43
} eeadcval_t;
44
45
typedef struct                          // Control parameter
46
{
47
  float lower;
48
  float upper;
49
  float val;
50
} eeconval_t;
51
52
typedef struct                          // all parameters including calibration
53
{
54
  uint16_t version;
55
  eeadcval_t adc[ADC_VAL_SIZE];
56
  eedacval_t dac[DAC_VAL_SIZE];
57
  eeconval_t con[CON_VAL_SIZE];
58
  uint16_t crc;
59
} eeppar_t;
60
61
typedef struct                          // copy of calibration
62
{
63
  uint16_t version;
64
  eeadcval_t copy[MAX_CHANNELS];        // sum of used ADC + DAC + reserve for future use
65
  char cal_signature[CAL_SIG_SIZE];
66
  uint16_t crc;
67
} eepcal_t;
68
69
typedef struct
70
{
71
  eepcal_t cal;
72
  eeppar_t par;
73
} eepdata_t;
74
75
extern eepdata_t Eepdata;

Ein Zugriff auf ein konkretes Element kann dann so aussehen:
1
  Eepdata.par.con[CON_IFILRAMP].val = 5e-2;                     // 50mA/s
Gespeichert wird alles mit:
1
  eeprom_write_block ((void*)&Eepdata, (void*)EEP_START, sizeof(Eepdata));

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Ich benutze für sowas structs.

Genau das hatte ich ihm schon oben vorgeschlagen:

Beitrag "Re: Wie Objekte in einem Array aktuell halten und umgekehrt?"

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke Peter, so war und ist es gedacht. So oder so ähnlich.  ;-)
Ich bin derzeit noch an einer anderen Aufgabe dran, in einer 
"Steuerungsklasse" die Sensoren und Weichen miteinander agieren zu 
lassen. Wenn das fertig ist kommt das Thema FRAM dazu und dann wird sich 
zeigen welche Strategie optimal ist die Werte zu speichern und wie ich 
sie speichere. Was ich vermeiden möchte ist, dass immer alle Werte 
gespeichert werden, obwohl sich nur ein Wert zum Zeitpunkt geändert hat. 
Was ich auch vermeiden möchte ist das erst alle Daten aus dem FRAM 
gelesen werden müssen um festzustellen welcher Wert sich geändert hat. 
Welcher Wert sich geändert hat weiß das Programm ja quasi selbst. Wenn 
ich das mit einer "FRAM Control" Klasse mache wie Wilhelm vorgeschlagen 
hat, dann muss ich dieser nur mitteilen was sich geändert hat, dann 
berechnet diese die Adresse im FRAM und überschreibt den Wert. So meine 
aktuelle Theorie. Das wird sich zum Zeitpunkt wenn ich soweit bin 
herauskristallisieren. :-)

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> Was ich auch vermeiden möchte ist das erst alle Daten aus dem FRAM
> gelesen werden müssen um festzustellen welcher Wert sich geändert hat.

Müssen sie nicht. Ein FRAM hat keinerlei Schreibzeit. Am schnellsten 
geht daher zu schreiben, ohne erst zu vergleichen.
Anders beim EEPROM, da braucht der AVR 8ms je Byte. Je nach Größe des 
Blocks kann die Mainloop deutlich verzögert werden. Besonders beim 
ersten Einschalten, wenn im EEPROM alles noch auch 0xFF steht. Ich 
schreibe daher im Hintergrund. Je Mainloop Durchlauf wird geprüft, ob 
überhaupt was zu schreiben ist, das letze Schreiben beendet ist und dann 
ein Byte verglichen und geschrieben.
1
struct
2
{
3
  uint8_t* src;
4
  uint16_t dst;
5
  uint16_t len;
6
} eew;
7
8
/* Attention: using EEPROM interrupt instead polling may cause longer delay of the main loop */
9
void epp_wr_poll(void)                          // called by main loop
10
{
11
  if (eew.len == 0)                             // nothing to do
12
    return;
13
  if (EECR & 1 << EEWE)                         // not ready yet
14
    return;
15
  eew.len--;
16
  EEAR = eew.dst++;
17
  EECR |= 1 << EERE;                            // read
18
  uint8_t val = *eew.src++;
19
  if (val != EEDR)
20
  {
21
    EEDR = val;
22
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
23
    {
24
      EECR |= 1 << EEMWE;                       // Enable Write
25
      EECR |= 1 << EEWE;                        // write
26
    }
27
  }
28
}

von Veit D. (devil-elec)


Lesenswert?

Hallo,

genau, direktes Schreiben beim FRAM war auch meine Überlegung. Der 
letzte Teil der Unterhaltung drehte sich um das Wie. Ob ich das direkt 
in die Weichen-Klasse einbaue, dort wo mit den Werten gearbeitet wird. 
Oder ob ich eine weitere FRAM-Control Klasse schreibe die die Werte von 
der Weichen-Klasse bekommt. Wenn es allein bei den Weichen-Parametern 
bliebe könnte ich das in die Weichen-Klasse direkt einbauen. Wenn ich 
aber später noch andere Werte speichern möchte wird das schnell Essig. 
Also wird es doch auf eine extra FRAM-Control Klasse hinauslaufen. Mach 
ich alles zu seiner Zeit. :-)

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> Ich möchte unnötige Buszugriffe
> vermeiden. Die Hauptaktivität auf dem I2C Bus sind Sensorabfragen.

Ja, Hardwareressourcen sind typisch nicht reentrant, d.h. man muß sich 
was einfallen lassen, wenn sie von mehreren Instanzen benötigt werden. 
Das Problem sind auch nicht unnötige Zugriffe, sondern die Zeit der 
Blockierung anderer Tasks. Eine Möglichkeit ist das Anlegen mehrerer 
Puffer, die dann der I2C-Interrupt Round-Robin abarbeitet.

Für Konfigurationsdaten nehme ich aber auch gerne einen exklusiven I2C 
mit 2 normalen IO-Pins als I2C-Master in Software. Dann kann man ähnlich 
zu meinem EEPROM-Beispiel kleine Häppchen in der Mainloop übertragen, 
ohne daß nennenswert Zeit belegt wird. Da der Bus ja exklusiv ist, muß 
auch zwischendurch ein Paket nicht abgeschlossen sein. Man kann bequem 
einzelne Bytes oder sogar nur Bits übertragen.
Manche MCs haben aber auch 2 oder mehrere I2C-Busse.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

das mit dem Puffer werde ich mir überlegen. An der Hardware ändere ich 
nichts mehr. Die ist fertig aufgebaut und verkabelt.

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> Für eine saubere Steuerung muss man sich schon paar Gedanken machen bzw.
> Überlegungen anstellen.

Für Steuerungen aller Art hat sich bei mir ein Mapping im RAM bewährt.
Alle IOs (Sensoren, Tasten, Aktoren, LEDs) bekommen ein Bit in einem 
Bitarray im RAM.
Die gesamte Steuerlogik arbeitet nur im RAM, wie eine SPS. Und eine 
extra Task sorgt dann für ein Update der Peripherie in beide Richtungen. 
Innerhalb der Statemaschine sind mehrere Wechsel erlaubt, da sie ja nach 
außen keine Glitches erzeugen.
Hier mal eine Task aus einer größeren Statemaschine:
1
void tlc_relays( void )     // switch TLC relays
2
{
3
  REL_SIMS = 0;
4
  if ( LED_PLC_SIMS )
5
    REL_SIMS = 1;
6
  if ( LED_STC )      // HFC/PLC -> STC
7
  {
8
    if ( REL_DBM )
9
    {
10
      REL_DBM = 0;
11
      SWtimer_add( rel_stc_on, DELAYED );
12
    }
13
  }
14
  else          // STC -> HFC/PLC
15
  {
16
    if ( REL_STC )
17
    {
18
      REL_STC = 0;
19
      SWtimer_add( rel_dbm_on, DELAYED );
20
    }
21
  }
22
}
Die Timeraufrufe dienen dazu, daß sich ausschließende Relais nie 
gleichzeitig gezogen sind und es einen Kurzschluß gibt. Das jeweils 
andere Relais ist also nach der Wartezeit sicher abgefallen.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich bin jetzt soweit das ich "die Sache" mit FRAM einbauen kann.
1
enum class Sensorboard {SA1, SB1, SB2, SC1, SC2, SC3, SD1, SD2, SD3, SE1, SF1, NumberOfSensors};
2
enum class Servoname {WBCL, WCDL, WCDR, WDER, SCL, SCR, SDL, SDR, NumberOfServos};
3
4
struct ServoDaten {
5
  uint16_t left     {};
6
  uint16_t right    {}; 
7
  uint16_t current  {}; 
8
  const char name [12] {'\0'};
9
};
10
11
ServoDaten servoObj [] {           // der Datensatz soll ins FRAM
12
  {1311, 1611, 1500, "Weiche.BCL"},
13
  {1322, 1622, 1500, "Weiche.CDL"},
14
  {1333, 1633, 1500, "Weiche.CDR"},
15
  {1344, 1644, 1500, "Weiche.DER"},
16
  {1355, 1655, 1500, "Signal.CL" },
17
  {1366, 1666, 1500, "Signal.CR" },
18
  {1377, 1677, 1500, "Signal.DL" },
19
  {1388, 1688, 1500, "Signal.DR" }  
20
};
21
22
class ControlWeiche { ... };
23
24
ControlWeiche weiche [] {
25
  { 1, (uint8_t)Servoname::WBCL, (uint8_t)Sensorboard::SB1, (uint8_t)Sensorboard::SC1},
26
  { 1, (uint8_t)Servoname::WBCL, (uint8_t)Sensorboard::SB1, (uint8_t)Sensorboard::SD1},
27
  { 1, (uint8_t)Servoname::WCDL, (uint8_t)Sensorboard::SC1, (uint8_t)Sensorboard::SD1},
28
  {-1, (uint8_t)Servoname::WCDR, (uint8_t)Sensorboard::SD3, (uint8_t)Sensorboard::SC3},
29
  {-1, (uint8_t)Servoname::WDER, (uint8_t)Sensorboard::SE1, (uint8_t)Sensorboard::SD3},
30
  {-1, (uint8_t)Servoname::WDER, (uint8_t)Sensorboard::SE1, (uint8_t)Sensorboard::SC3}
31
};
32
33
class ControlSignal { ... };
34
35
ControlSignal signal [] {
36
  (uint8_t)Servoname::SCL,
37
  (uint8_t)Servoname::SCR,
38
  (uint8_t)Servoname::SDL,
39
  (uint8_t)Servoname::SDR
40
};
Die Hauptschleife macht
1
{
2
  sensorBoard.update();
3
  for (auto &w : weiche) { w.checkPosition(); }
4
  for (auto &s : signal) { s.checkPosition(); }
5
}
Das Programm funkioniert, sodass alle Servos entsprechend reagieren und 
langsam ihre Position ändern.

Die Klasse 'ControlWeiche' und 'ControlSignal' enthalten je eine 
Elementfunktion 'isChangedPosition' mit bool Rückgabewert. Die 
informiert mit einmalig 'true' bei Ereignis, wenn das Servo in der neuen 
Endposition ist. Damit kann mittels Trigger der neue Positionswert 
'current' im FRAM gespeichert werden.

Jetzt habe ich mir folgendes überlegt. Jede Instanz von 'ControlWeiche' 
und 'ControlSignal' hat ja schon durch die enum Servoname Nummerierung 
den Indexwert automatisch enthalten. Ich brauch doch jetzt nur noch aus 
der jeweiligen Instanz dem FRAM-Controller die Indexnummer übergeben.
Das macht uint8_t getObjIndex()
Damit weiß der FRAM-Controller 'icFRAM' das ein Wert dieser Instanz 
geändert werden soll. Den Adresse und Offset zum Element kann der 
FRAM-Controller selbst berechnen.

Die Hauptschleife sollte dann folgendes machen
1
{
2
  sensorBoard.update();
3
  
4
  for (auto &w : weiche) { 
5
    w.checkPosition();
6
    if (w.isChangedPosition()) { icFRAM.update(w.getObjIndex() ); }
7
  }
8
9
  for (auto &s : signal) { 
10
    s.checkPosition();
11
    if (s.isChangedPosition()) { icFRAM.update(s.getObjIndex() ); }
12
  }
13
}
Macht das Sinn? Irgendwie schon finde ich. Damit ist das xy Problem aus 
der Welt. Referenznamen der Objekte entfallen. Die Instanzen benötigen 
keine FRAM Adressen mit. Die Werte die zur Laufzeit benötigt werden 
kommen vom einzigen Puffer im RAM, dem struct Array 'ServoDaten 
servoObj'. Ändert sich eine Servoposition, ändert sich das struct 
Element 'current' was dann im FRAM aktualisiert wird.
Dann fehlt mir nur noch eine Kalibrierfunktion für die Servos und das 
nach Controllerreset die Daten vom FRAM eingelesen werden vor der ersten 
Verwendung.
Sollte doch alles der richtige Weg sein?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> for (auto &w : weiche) {
>     w.checkPosition();
>     if (w.isChangedPosition()) { icFRAM.update(w.getObjIndex() ); }
>   }

Ein
1
for (const auto &w : weiche) { 
2
    if (w.isChangedPosition()) { 
3
        icFRAM.update(w); 
4
    }
5
  }

sollte reichen.

Und wenn Du alle Objekte dem FRam-Controller vorher bekannt machst, dann 
reicht:
1
icFRAM.periodic();

was Du eben periodisch aufrufst.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

mit
1
icFRAM.update(w.getObjIndex() )
teile ich dem FRAM-Controller mit welche Instanz ein Update benötigt.

Bevor ich eine längere Antwort gebe muss ich nachfragen was du mit
1
icFRAM.periodic()
meinst? Sprichst erneut die eigenständige FRAM Verwaltung an mit 
scannen, vergleichen und schreiben?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Sprichst erneut die eigenständige FRAM Verwaltung an mit
> scannen, vergleichen und schreiben?

Genau das, was Du in der o.g. for-Schleife machst: das ist ja scannen, 
und wenn nötig: schreiben.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich denke da liegt ein Missverständnis vor.
w.getObjIndex() liefert die Indexnummer des Weichen Array. Das was ich 
in den enums festgelegt habe.
Damit wird im FRAM Controller die Speicheradresse der zu ändernden 
Variable im FRAM der gesamten Struktur berechnet. Damit kann direkt ohne 
Scan oder Vergleich geschrieben werden. Zu dem Zeitpunkt ist bekannt das 
ein Update stattfinden muss.

Aktuell sieht das so. Noch sind das Tests. getAddr() gibt mir testweise 
die korrekte Addresse im FRAM zurück. Zur Kontrolle bevor ich wirklich 
auf den FRAM schreibe.
1
/*                I2C Addr
2
                  |              wire0/wire1  
3
                  |              |              Capacity in kByte       
4
                  |              |              |                    Object structure for length determination                  
5
                  |              |              |                    |           initial address of object in FRAM
6
                  |              |              |                    |           |                              */
7
template <uint8_t addr, TwoWire& line, uint16_t memorySize, typename N, uint32_t initObjAddr>
8
class FramControl 
9
{
10
  private:  
11
    MyFRAM_I2C <addr, line, memorySize> fram;
12
    uint32_t objStartIndex {0};
13
    uint32_t memberOffset  {0};
14
    const size_t lengthStructure {sizeof(N)};
15
        
16
  public:
17
    FramControl() = default;
18
    
19
    uint8_t init (void) const { return fram.init(); }
20
    
21
    void update (const uint32_t i, const uint32_t m) {
22
      objStartIndex = i;
23
      memberOffset = m;
24
    }
25
        
26
    uint32_t getAddr (void) { return initObjAddr + (objStartIndex*lengthStructure) + memberOffset; }
27
};

Hoffentlich sinnvoller Programmauszug
1
struct ServoDaten {
2
  uint16_t left    {};
3
  uint16_t right   {}; 
4
  uint16_t current {};  
5
  const char name [12] {'\0'};
6
};
7
8
enum class Sensorboard {SA1, SB1, SB2, SC1, SC2, SC3, SD1, SD2, SD3, SE1, SF1, NumberOfSensors};
9
enum class Servoname {WBCL, WCDL, WCDR, WDER, SCL, SCR, SDL, SDR, NumberOfServos};
10
11
struct OffsetServoDaten {
12
  const uint32_t left    {offsetof(ServoDaten, left)   };
13
  const uint32_t right   {offsetof(ServoDaten, right)  };
14
  const uint32_t current {offsetof(ServoDaten, current)};
15
  const uint32_t name    {offsetof(ServoDaten, name)   };
16
} const structOffset;
17
18
ServoDaten servoObj [] {
19
  {1311, 1611, 1500, "Weiche.BCL"},
20
  {1322, 1622, 1500, "Weiche.CDL"},
21
  {1333, 1633, 1500, "Weiche.CDR"},
22
  {1344, 1644, 1500, "Weiche.DER"},
23
  {1355, 1655, 1500, "Signal.CL "},
24
  {1366, 1666, 1500, "Signal.CR "},
25
  {1377, 1677, 1500, "Signal.DL "},
26
  {1388, 1688, 1500, "Signal.DR "}  
27
};

Hauptschleife
1
sensorBoard.update();
2
3
for (const auto &w : weiche) { 
4
  w.checkPosition();
5
  if (w.isChangedPosition()) { 
6
    icFRAM.update(w.getObjIndex(), structOffset.current);
7
  }
8
}

Bsp.
Die Servoposition von Weiche WCDR hat sich geändert.
Dann liefert
w.isChangedPosition()
den Trigger
und der FRAM Controller 'icFRAM' bekommt mitgeteilt das er
die Instanz mit ihrem Index 2 und davon den Member 'current' mit Offset 
4 schreiben soll. Die gesamte Struktur beginnt im FRAM bei Adresse 0.
Eine Instanz der Struktur 'ServoDaten' hat eine Einzellänge von 18 
Bytes. Die Formel
getAddr = initObjAddr + (objStartIndex*lengthStructure) + memberOffset
0 + (2*18) + 4 = 40
Bedeutet der zu ändernde Member 'current' der Weiche WCDR liegt auf 
Addresse 40 im FRAM. Resultat ist kürzeste Nutzung des I2C Busses, weil 
nur das direkt geändert wird.

Wenn ich den Aufwand weglasse und zyklisch scanne, muss die gesamte 
Struktur von derzeit 144 Bytes jedesmal gescannt werden. Ziemlich lange 
Blockierung des I2C Busses finde ich.

Wenn ich den Scan auf den Member 'current' einschränke kann ich die 
Servos für ihre Endlagen nicht einstellen. 'left' und 'right' müssen 
jederzeit bei Bedarf geändert werden können. Das ginge wiederum in einer 
Kalibrierroutine recht einfach mit
1
icFRAM.update(s.getObjIndex(), structOffset.left);

Anderer Meinung?

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe mir das alles nochmal überlegt mit der unabhängigen Verwaltung 
eines FRAM Controllers ohne externe Methodenaufrufe. Es gibt ja nur 2 
Möglichkeiten wie der Controller selbst feststellen kann ob sich Werte 
geändert haben die er aktualisieren muss.

a)
Er liest zyklisch alle Daten der Struktur aus dem FRAM und vergleicht 
sie mit der Struktur die schon im RAM liegt.
Das ist sehr Zeitaufwändig. Würde jedoch einen Puffer sparen.

b)
Der FRAM Controller hält noch einen Puffer für sich bereit in der Größe 
der Struktur der den Inhalt des FRAM abbildet.
Wenn der FRAM-Controller die aktuellen Daten der Struktur mit denen 
seines Puffers vergleicht, kann er sehr schnell feststellen ob er 
irgendwas aktualisieren muss. Ginge sehr schnell kostet aber zusätzlich 
RAM je nach Strukturgröße.

Wenn ich das mit meiner Vorgehensweise vergleiche womit ich mittels 
Methode dem FRAM Controller direkt sage ändere bitte genau diesen Wert, 
damit habe keine unnötige Busbelastung durch scannen und keine 
Notwendigkeit eines extra Puffers. Das ist für meine Modelleisenbahn 
perfekt. Nach meiner aktuellen Bewertung der Lage belasse ich das 
erstmal so.

Ich danke Euch für die Denkanstöße. Haben ja letztlich zur aktuellen 
Lösung ohne Umwege geführt. Am Anfang hatte ich zu kompliziert gedacht. 
Genau dafür   ist ein Forum da. :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vielleicht wird es doch nochmal anders. ;-) Nach einer Fehlersuche 
stellte sich heraus das bei einer Objektübergabe als Template Parameter 
nicht nur die Objektlänge erhalten bleibt sondern die gesamte Struktur 
mit allen ihren Eigenschaften. Weil das direkt eingesetzt wird. Wie geil 
ist das denn. Wird mir jetzt erst so richtig bewusst was das für eine 
Magie hat. :-)  Besser spät als nie. Damit lässt sich arbeiten.  :-)
1
/*                I2C Addr
2
                  |              wire0/wire1  
3
                  |              |              Capacity in kByte       
4
                  |              |              |                 Object for length determination                  
5
                  |              |              |                 |             initial address of object in FRAM
6
                  |              |              |                 |             |                              */
7
template <uint8_t addr, TwoWire& line, uint16_t memorySize, auto &obj, uint32_t initObjAddr>                  
8
class FramControl 
9
{
10
  private:  
11
    MyFRAM_I2C <addr, line, memorySize> fram;
12
13
  public:
14
    void showObjData (Stream &cout) { 
15
      const uint32_t lengthObject    {sizeof(obj)};
16
      const uint32_t lengthStructure {lengthObject/sizeof(obj[0])};
17
      cout << ("length Obj    ") << lengthObject    << endl;
18
      cout << ("length Struct ") << lengthStructure << endl;
19
      cout << obj[3].name << endl;
20
      cout << obj[2].name << endl;
21
      cout << obj[1].name << endl;
22
      cout << obj[0].name << endl;
23
    }
24
};
1
length Obj    144
2
length Struct 8
3
Weiche.DER
4
Weiche.CDR
5
Weiche.CDL
6
Weiche.BCL

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Nach einer Fehlersuche
> stellte sich heraus das bei einer Objektübergabe als Template Parameter
> nicht nur die Objektlänge erhalten bleibt sondern die gesamte Struktur
> mit allen ihren Eigenschaften.

Was meinst Du da denn mit?

Also: es gibt drei Arten von template-Parametern:

- type-parameter (also: Datentypen)
- non-type-template-parameter (NTTP)
- template-template-parameter: (also: templates)

Als NTTP functionieren (grob gesagt) Werte (constexpr) und 
lvalue-Referenzen für Objekte mit static-storage. Bei den Datentypen der 
Werte müssen es dann auch noch sog. structural-types sein (die 
wesentliche Einschränkung ist hier, dass die Member dieser 
structural-types public sein müssen: unschön).

von Veit D. (devil-elec)


Lesenswert?

Hallo,

im Nachhinein fiel mir ein das ich das schon mit Template Funktionen 
gemacht hatte. Naja.

> Was meinst Du da denn mit?

Ich hatte erhofft das wäre ersichtlich. Ich übergebe dem FramControl 
Objekt 'icFRAM' das Objekt 'servoObj'. Der Parameter ist 'auto &obj'.
Wobei ich den Namen icFRAM anders wählen sollte.  ;-)
1
struct ServoDaten {
2
  uint16_t left    {};
3
  uint16_t right   {}; 
4
  uint16_t current {};  
5
  const char name [12] {'\0'};
6
};
7
8
ServoDaten servoObj [8] {
9
  {1311, 1611, 1501, "Weiche.BCL"},
10
  {1322, 1622, 1502, "Weiche.CDL"},
11
  {1333, 1633, 1503, "Weiche.CDR"},
12
  {1344, 1644, 1504, "Weiche.DER"},
13
  {1355, 1655, 1505, "Signal.CL "},
14
  {1366, 1666, 1506, "Signal.CR "},
15
  {1377, 1677, 1507, "Signal.DL "},
16
  {1388, 1688, 1508, "Signal.DR "}  
17
};

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
>> Was meinst Du da denn mit?
>
> Ich hatte erhofft das wäre ersichtlich. Ich übergebe dem FramControl
> Objekt 'icFRAM' das Objekt 'servoObj'.

Ja klar. Bin ja nicht blind.

Ich meinte diesen Satz:

> nicht nur die Objektlänge erhalten bleibt sondern die gesamte Struktur
> mit allen ihren Eigenschaften.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

damit war gemeint das ich der Klasse 'FRAM-Control' von
'ServoDaten servoObj [8]'
auf alle Elemente zugreifen kann. Index und Struktur Member. Das hatte 
ich die ganze Zeit völlig außer Acht gelassen. Das ermöglicht mir 
spezifischere Methoden zu schreiben die mit weniger Parameter auskommen. 
In C++ Sprache "Elementfunktionen".

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> damit war gemeint das ich der Klasse 'FRAM-Control' von
> 'ServoDaten servoObj [8]'
> auf alle Elemente zugreifen kann.

Nun, Du hast dich eine lvalue-Referenz auf Dein Array. Also: die 
lvalue-Referenz ist wie "ein neuer Name" (eben eine Referenz) für das 
Array. Sollte also wenig verwunderlich sein.

> Index und Struktur Member.

Was für einen Index? Oder meinst Du die Dimension.

Dir sollte bewusst sein, das der NTTP "auto& obj" auf alles matched.

von Veit D. (devil-elec)


Lesenswert?

Wilhelm M. schrieb:

>> Index und Struktur Member.
> Was für einen Index? Oder meinst Du die Dimension.
Die Frage ist seltsam. Hier habe ich einen Index 0...7.
1
ServoDaten servoObj [8] {
2
  {1311, 1611, 1501, "Weiche.BCL"},
3
  {1322, 1622, 1502, "Weiche.CDL"},
4
  {1333, 1633, 1503, "Weiche.CDR"},
5
  {1344, 1644, 1504, "Weiche.DER"},
6
  {1355, 1655, 1505, "Signal.CL "},
7
  {1366, 1666, 1506, "Signal.CR "},
8
  {1377, 1677, 1507, "Signal.DL "},
9
  {1388, 1688, 1508, "Signal.DR "}  
10
};

> Dir sollte bewusst sein, das der NTTP "auto& obj" auf alles matched.
Ja da muss man aufpassen. Aber das passiert ja hier nur einmal beim 
initialisieren der FRAM-Controller Instanz. Von daher habe ich keine 
Bedenken.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wilhelm M. schrieb:
>
>>> Index und Struktur Member.
>> Was für einen Index? Oder meinst Du die Dimension.
> Die Frage ist seltsam. Hier habe ich einen Index 0...7.
>
1
> ServoDaten servoObj [8] {
2
>   {1311, 1611, 1501, "Weiche.BCL"},
3
>   {1322, 1622, 1502, "Weiche.CDL"},
4
>   {1333, 1633, 1503, "Weiche.CDR"},
5
>   {1344, 1644, 1504, "Weiche.DER"},
6
>   {1355, 1655, 1505, "Signal.CL "},
7
>   {1366, 1666, 1506, "Signal.CR "},
8
>   {1377, 1677, 1507, "Signal.DL "},
9
>   {1388, 1688, 1508, "Signal.DR "}
10
> };
11
>

Dann meinst Du wohl die Dimension (hier: 8).

>
>> Dir sollte bewusst sein, das der NTTP "auto& obj" auf alles matched.
> Ja da muss man aufpassen. Aber das passiert ja hier nur einmal beim
> initialisieren der FRAM-Controller Instanz. Von daher habe ich keine
> Bedenken.

Du kannst Deinen FRAM-Controller für Array-Typen spezialisieren. Dabei 
kannst Du auch gleich die Dimension des Arrays ableiten (ohne sie so 
C-like berechnen zu müssen).

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wichtiger für mich als die Dimension ist zu wissen die Anzahl der Bytes, 
also die Länge des gesamten Objektes. Dafür benötige sowieso sizeof. 
Übrigens ist offsetof ein C Makro. ;-)

Egal. Ich habe versucht den Member für ein Array zu spezialisieren. Es 
wird immer angemeckert das ein Argument nicht stimmt. Bezieht sich hier 
auf den 3. Code 'NEU' Zeile 5.
1
FRamControl.h:32:102: error: template argument 1 is invalid
2
32 | class FramControl <uint8_t addr, TwoWire& line, uint16_t memorySize, T(&obj)[N], uint32_t initObjAddr>
3
   |                                                                                                     ^
1
ALT
2
template <uint8_t addr, TwoWire& line, uint16_t memorySize, auto &obj, uint32_t initObjAddr>  
3
class FramControl 
4
{
1
NEU
2
template <typename T> class FramControl { }; 
3
4
template <typename T, size_t N> 
5
class FramControl <uint8_t addr, TwoWire& line, uint16_t memorySize, T(&obj)[N], uint32_t initObjAddr>
6
{

Wobei das im Trockentest funktioniert.
1
#include <iostream>
2
3
template <typename T>
4
class Test
5
{
6
  public:
7
  ~Test() { std::cout << "Normal \n";}
8
};
9
10
template <typename T, size_t N>
11
class Test<T[N]>
12
{
13
  public:
14
  ~Test() { std::cout << "Array " << N << '\n'; }
15
};
16
17
int main()
18
{
19
    Test<int[1]> a;  
20
    Test<int[2]> b;  
21
}

Was ist bei meinem FramControl falsch?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Wobei das im Trockentest funktioniert.

Leider nein, da machst Du etwas ganz anderes.

> int main()
> {
>     Test<int[1]> a;
>     Test<int[2]> b;
> }
> [/c]

Hier instanziierst Du das template Test<> für den Datentyp(!) (kein 
NNTP!) "int[1]" bzw. "int[2]". Und für diesen Array-Typ hast Du auch 
eine Spezialisierung.

In Deinem FrameControl<> möchtest Du aber einen NTTP (einen Wert oder 
eine lvalue-referenz) eines bestimmten Typs als Ausnahme 
(Spezialisierung) formulieren.

In Deinem Code ist das, was Du schreibst, auch keine Spezialisierung, 
sondern einfach syntaktisch falsch.

Daher:
1
template <uint8_t addr, TwoWire& line, uint16_t memorySize, auto &obj, uint32_t initObjAddr>  
2
struct FramControl { // allg. template
3
};
4
5
template <uint8_t addr, TwoWire& line, uint16_t memorySize, typename T, size_t N, T (&obj)[N], uint32_t initObjAddr> 
6
struct FramControl<addr, line, memorySize, obj, initObjAddr>{ // Spezialisierung
7
};

In der Spezialisierung muss "obj" (die Array-Referenz) von einem 
(generischen) Array-Typ (hier: T[N]) sein.

Ich würde mir das Thema template-parameter (s.a. mein Beitrag oben) 
nochmal genauer "reinziehen" und auch: was sind Typen, was sind Werte.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

das ist aber auch mit den Templates ein durcheinander was wann wofür wo 
stehen muss. Man oh man. Das ist mir noch um zu viele Ecken gedacht. Man 
muss denken wie ein Compiler. Was könnte er wie machen. Das wird 
anstrengend.  ;-)

Ich frage mich schon länger wofür benötigt der Compiler das allgemeine 
Template, wenn das spezialisierte direkt dafür zugeschnitten wurde 
sodass er es verwenden kann? Dann müßte doch bei fehlen des allgemeinen 
Templates und falschen Datentyp auch eine Meldung seitens des Compiler 
kommen das er keine passende Klasse gefunden hat.

Lasse ich das allgemeine Template weg, kann er nicht kompilieren, weil 
ihm an den Argumenten was stört. Er meckert nicht wegen falschen 
Datentyp, sondern wegen der Anzahl der Argumente.
1
error: 'FramControl' is not a class template
2
  | class FramControl <addr, line, memorySize, obj, initObjAddr>  // Spezialisierung
3
  |       ^~~~~~~~~~~
1
error: wrong number of template arguments (5, should be 7)
2
  | FramControl <0x57, wire0, 64, servoObj, FRAM_INITIAL_ADDR> framControlServo;
3
  |                                                          ^

Im übrigen funktioniert das mit deinem Syntax, allgemeines und 
spezialisiertem Template. Vielen Dank.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> das ist aber auch mit den Templates ein durcheinander was wann wofür wo
> stehen muss. Man oh man. Das ist mir noch um zu viele Ecken gedacht. Man
> muss denken wie ein Compiler. Was könnte er wie machen. Das wird
> anstrengend.  ;-)

Naja, die meisten Programmierer denken eben nur in Werten. Das man auch 
mit Datentypen "rechnen" kann (oder sagen wir: Abbildungen (Funktionen) 
durchführen kann), ist den meisten tatsächlich zu viel Voodoo. Dabei ist 
es eigentlich ganz einfach ;-) Genauso einfach wie Laufzeitfunktionen 
...

> Ich frage mich schon länger wofür benötigt der Compiler das allgemeine
> Template, wenn das spezialisierte direkt dafür zugeschnitten wurde
> sodass er es verwenden kann?

Ein Spezialisierung ist immer eine "Ausnahme" vom allg. Template. 
Deswegen braucht es immer das allg. template, zu dem dann einige 
Ausnahmen formuliert werden. Ggf. können mehrere Spezialisierung 
anwendbar sein, davon wir dann die, die am meisten spezialisiert ist, 
ausgewählt.

> Dann müßte doch bei fehlen des allgemeinen
> Templates und falschen Datentyp auch eine Meldung seitens des Compiler
> kommen das er keine passende Klasse gefunden hat.

Nö. Das allg. template macht ja in Deinem speziellen Fall keine 
Einschränlungen bzgl. obj&, weil Du es mit auto deklariert hast.

> Lasse ich das allgemeine Template weg, kann er nicht kompilieren, weil
> ihm an den Argumenten was stört. Er meckert nicht wegen falschen
> Datentyp, sondern wegen der Anzahl der Argumente.

Ja, das allg. template und die Spezialisierung müssen die gleiche Anzahl 
von Parametern haben.

> Im übrigen funktioniert das mit deinem Syntax, allgemeines und
> spezialisiertem Template.

Ja, freut mich.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wenn du das so schreibst und erklärst verstehe ich das sogar. :-)
Vielen Dank.
Gibt es ein gutes Buch in deutsch welches das Thema Template 
Programmierung näher erklärt?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Gibt es ein gutes Buch in deutsch welches das Thema Template
> Programmierung näher erklärt?

Die Anforderung ("Gut" && "Buch" && "deutsch") erscheint mir fast 
unerfüllbar ;-)

Als Einstieg: http://www.tmplbook.com/

von A. B. (Firma: ab) (bus_eng)


Lesenswert?

Veit D. schrieb:
> Gibt es ein gutes Buch in deutsch welches das Thema Template
> Programmierung näher erklärt?

Nicht vom Titel auf den Inhalt schliessen!! Online:

https://www.codeproject.com/Articles/257589/An-Idiots-Guide-to-Cplusplus-Templates-Part-1

und

https://www.codeproject.com/Articles/268849/An-Idiots-Guide-to-Cplusplus-Templates-Part-2

Mit dem Link kannst du dir das von Google übersetzen lassen.

! Nicht nur lesen, sondern durcharbeiten. !

Ich nutze für templates<> ein spezielles Layout:
1
  template < typename   DIN,                    // serial data input [>Max7219:PIN 1]
2
             typename   LOAD,                   // load data/CS      [>        PIN12]
3
             typename   CLK,                    // clock             [>        PIN13]
4
             uint8_t    digits    = 8,          //
5
             uint8_t    intensity = 5,          //
6
             uint8_t    decode    = 0 >         //
7
//*********************************************
8
  class Max7219 {
9
//*********************************************

Spezialisierungen schreibe ich untereinander formatiert,
1
  template <typename T> struct Signed                      {using Result_t = T          };
2
  template           <> struct Signed <unsigned int>       {using Result_t = int        };
3
  template           <> struct Signed <unsigned char>      {using Result_t = char       };
4
  template           <> struct Signed <unsigned long>      {using Result_t = long       };
5
  template           <> struct Signed <unsigned short>     {using Result_t = short      };
6
  template           <> struct Signed <unsigned long long> {using Result_t = long long  };

Nicht vergessen: template <typename DatenTyp> heisst auf deutsch: 
Vorlage für einen DatenTYP. Man stellt class/struct einen allgemeinen 
DatenTYP zur Verfügung, der später konkret spezialisiert wird.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke Euch. Ich muss das einfach durcharbeiten. Ob ich das englische 
Buch kaufe weiß ich allerdings nicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

A. B. schrieb:
> Spezialisierungen schreibe ich untereinander formatiert,  template
> <typename T> struct Signed                      {using Result_t = T
> };
>   template           <> struct Signed <unsigned int>       {using
> Result_t = int        };
>   template           <> struct Signed <unsigned char>      {using
> Result_t = char       };
>   template           <> struct Signed <unsigned long>      {using
> Result_t = long       };
>   template           <> struct Signed <unsigned short>     {using
> Result_t = short      };
>   template           <> struct Signed <unsigned long long> {using
> Result_t = long long  };

Würde nun vermuten, dass das für eine Plattform ist, für die es keine 
Implementierung der stdlibc++ gibt (andernfalls: std::make_signed<>).

Ich hätte mich da an die übliche Praxis gehalten und den Ergebnistyp der 
Meta-Funcktionen "type"  genannt. Aber das ist ja nicht wirklich 
wichtig.

Wichtig und nicht gut finde ich, dass die Abbildung

Signed<XYZ> -> XYZ

gemacht wird. Das ist mehr als irreführend.
Mindestens lässt man das allg. Template ohne Definition oder leer (gibt 
halt schlechte Fehlermeldungen) oder man setzt ein requirement (ähnlich) 
wie std::is_integer<> dazu (der Mathematiker würde sagen, man schränkt 
die Definitionsmenge der Funktion "Signed<>" auf die ganzzahligen Typen 
ein :-)).

: Bearbeitet durch User
von A. B. (Firma: ab) (bus_eng)


Lesenswert?

Wilhelm M. schrieb:
> Würde nun vermuten, dass das für eine Plattform ist, für die es keine
> Implementierung der stdlibc++ gibt (andernfalls: std::make_signed<>).

Das Beispiel stammt aus McuCPP: /mcucpp/template_utils.h von 2013 
(modifiziert using .. ) und soll nur zeigen, wie ein template<> struct 
und die dazugehörigen Spezialisierungen mit ein paar zusätzlichen 
Leerzeichen gut lesbar werden.

Gut lesbar bedeutet auch einfacher verständlich und besser 
nachvollziehbar, insbesondere für template <> Neulinge.

Ein anderes Beispiel:
1
    template<typename T> struct make_unsigned;
2
    
3
    template<> struct make_unsigned<int8_t> {
4
        typedef uint8_t type;
5
    };
6
    template<> struct make_unsigned<int16_t> {
7
        typedef uint16_t type;
8
    };
9
    template<> struct make_unsigned<int32_t> {
10
        typedef uint32_t type;
11
    };
12
    template<> struct make_unsigned<int64_t> {
13
        typedef uint64_t type;
14
    };
15
//-------------------------------------------------------------------------------
16
    template <typename T> struct make_unsigned;
17
    template <>           struct make_unsigned <int8_t>  { typedef uint8_t  type; };
18
    template <>           struct make_unsigned <int16_t> { typedef uint16_t type; };
19
    template <>           struct make_unsigned <int32_t> { typedef uint32_t type; };
20
    template <>           struct make_unsigned <int64_t> { typedef uint64_t type; }:
21
//-------------------------------------------------------------------------------

von Wilhelm M. (wimalopaan)


Lesenswert?

A. B. schrieb:
> Wilhelm M. schrieb:
>> Würde nun vermuten, dass das für eine Plattform ist, für die es keine
>> Implementierung der stdlibc++ gibt (andernfalls: std::make_signed<>).
>
> Das Beispiel stammt aus McuCPP: /mcucpp/template_utils.h von 2013
> (modifiziert using .. ) und soll nur zeigen, wie ein template<> struct
> und die dazugehörigen Spezialisierungen mit ein paar zusätzlichen
> Leerzeichen gut lesbar werden.

Schön wäre, wenn es gut lesbar und zugleich richtig und sinnvoll ist. 
Die Plattformfrage hast Du nun nicht beantwortet.

> Gut lesbar bedeutet auch einfacher verständlich und besser
> nachvollziehbar, insbesondere für template <> Neulinge.

Im Beispiel des TO waren es allerdings 5 template-Paramater, da wird 
Deine vorgeschlagene Formatierung schon etwas unhandlich.

> Ein anderes Beispiel:
>
1
>     template<typename T> struct make_unsigned;
2
> 
3
>     template<> struct make_unsigned<int8_t> {
4
>         typedef uint8_t type;
5
>     };
6
>     template<> struct make_unsigned<int16_t> {
7
>         typedef uint16_t type;
8
>     };
9
>     template<> struct make_unsigned<int32_t> {
10
>         typedef uint32_t type;
11
>     };
12
>     template<> struct make_unsigned<int64_t> {
13
>         typedef uint64_t type;
14
>     };
15
> //-------------------------------------------------------------------------------
16
>     template <typename T> struct make_unsigned;
17
>     template <>           struct make_unsigned <int8_t>  { typedef 
18
> uint8_t  type; };
19
>     template <>           struct make_unsigned <int16_t> { typedef 
20
> uint16_t type; };
21
>     template <>           struct make_unsigned <int32_t> { typedef 
22
> uint32_t type; };
23
>     template <>           struct make_unsigned <int64_t> { typedef 
24
> uint64_t type; }:
25
> //-------------------------------------------------------------------------------
26
>

Warum hast Du denn jetzt make_unsigned<>, wenn Du schon Signed<> hast? 
Und hier ist der Ergebnistyp (wie üblich) type genannt.

Wobei make_unsigned<> nun korrekt ist, weil das allg. template nun zwar 
deklariert, aber nicht definiert ist. Wobei man den Definitionsbereich 
durchaus auf die vorzeichenlosen Zahlen ausdehnen könnte, dann ist die 
Funktion ggf. allgemeiner verwendbar.

von A. B. (Firma: ab) (bus_eng)


Angehängte Dateien:

Lesenswert?

Wilhelm M. schrieb:
> Die Plattformfrage hast Du nun nicht beantwortet.

McuCPP sollte eigentlich bekannt sein. Google hilft: 
https://github.com/KonstantinChizhov/Mcucpp/blob/dev/mcucpp/template_utils.h

>Warum hast Du denn jetzt make_unsigned<>, wenn Du schon Signed<> hast?

Weil ich .. "etwas Abwechslung in die Disussion bringen wollte."

Das zweite Beispiel sollte auch bekannt sein:
https://sourceforge.net/p/wmucpp/code/ci/master/tree/include0/std/type_traits

>Im Beispiel des TO waren es allerdings 5 template-Paramater,da wird
Deine vorgeschlagene Formatierung schon etwas unhandlich.
1
template < uint8_t  addr,        //
2
           TwoWire& line,        //
3
           uint16_t memorySize,  //
4
           typename N,           //
5
           uint32_t initObjAddr> //
6
//*****************************
7
  class FramControl {
8
9
  private:  
10
    MyFRAM_I2C <addr, line, memorySize> fram;
11
12
    uint32_t     objStartIndex   {0};
13
    uint32_t     memberOffset    {0};
14
    const size_t lengthStructure {sizeof(N)};
15
   ...

Nicht wirklich .. und jedes template kann bei Bedarf erklärend 
kommentiert werden.

Es gibt Fälle, da ist diese Schreibweise hier im Forum eingebettet nicht 
darstellbar. Auf dem eigenen Rechner mit HD-Monitor ist bei 110CH/Zeile 
aber noch lange nicht Schluss. ( Siehe Anhang )

Dieses Layout dient ausschliesslich dem Verständnis. Beide oben 
genannten cpp-Plattformen sind nicht dokumentiert und aus dem Bedürfnis 
den Sourcecode zu verstehen, ist diese Schreibweise entstanden. Ist nur 
ein Hilfsmittel.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

A. B. schrieb:
> Wilhelm M. schrieb:
>> Die Plattformfrage hast Du nun nicht beantwortet.
>
> McuCPP sollte eigentlich bekannt sein. Google hilft:
> https://github.com/KonstantinChizhov/Mcucpp/blob/dev/mcucpp/template_utils.h

Es ging nicht um die Frage, ob ich McuCpp kennen, sondern darum, auf 
welcher Plattform Du das einsetzt?

>>Warum hast Du denn jetzt make_unsigned<>, wenn Du schon Signed<> hast?
>
> Weil ich .. "etwas Abwechslung in die Disussion bringen wollte."

Bingo!

> Das zweite Beispiel sollte auch bekannt sein:
> https://sourceforge.net/p/wmucpp/code/ci/master/tree/include0/std/type_traits

Dann solltest Du das wenigstens auch vollständig in den Beitrag 
kopieren. Hättest Du das gemacht, dann hätte sich mein Kommentar für 
vorzeichenlosen Ganzzahlen erübrigt ;-)

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

wie bekomme ich es hin das es trotz 2 Spezialisierungen für array und 
non array Objekte nutzbar ist?

Ich verwende in 3 Methoden den typename N für den Index. Den gibt es ja 
bei non array Objekten nicht. Wie unterscheide ich das in den Methoden? 
Überladung funktioniert ja nicht.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> wie bekomme ich es hin das es trotz 2 Spezialisierungen für array und
> non array Objekte nutzbar ist?

Genau für diese beiden Fälle hast Du doch Spezialisierungen. Irgendwie 
kann ich Deinen Einwand nicht verstehen. Wobei Du in dem Code die 
non-Array Variante nur deklariert hast, ein template-Definition fehlt.

Abgesehen davon sollte Dein Code gar nicht compilieren, weil Du eine 
Semikolon zuviel entfernt hast.

Also: was willst Du machen, und wo ist die Fehlermeldung dazu. Im Moment 
sehe ich nur Code, der funktionieren sollte (bis auf das Semikolon).

Edit: sehe gerade, dass Du ja ein allg. Template und eine 
"vermeintliche" Spezialisierung. Eigentlich solltest Du da eine Warnung 
bekommen wegen ambiguos template-instantiation. Also, was da steht ist 
vollkommen inkonsistent.

: Bearbeitet durch User
von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

wo fehlt ein Semikolon?

Ich habe einmal alles in die Datei kopiert. Ich hoffe das geht so.

Ich erhalte folgende Fehlermeldung.
1
Example_FramControl_Lib_Error.ino: In function 'void setup()':
2
Example_FramControl_Lib_Error:35:15: error: 'class MyFramControl<87, Wire, 64, obj0, 500>' has no member named 'isConnected'
3
   35 |   controlObj0.isConnected();
4
      |               ^~~~~~~~~~~
5
6
exit status 1
7
'class MyFramControl<87, Wire, 64, obj0, 500>' has no member named 'isConnected'

Die Methode isConnected() ist jedoch vorhanden. Funktioniert ja mit 
array Objekten.

Ändere ich
1
Daten obj0 { 1311, 1611, 1501, "Weiche.A" };
in ein array
1
Daten obj0 [] { 1311, 1611, 1501, "Weiche.A" };
kompiliert es.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ähm, jetzt fehlt noch die angesprochene Definition für non array Objekte 
und um Codedopplungen zu vermeiden muss ein "Mutterklasse" her die dann 
vererbt wird? Korrekt?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> wo fehlt ein Semikolon?

Das war Dein Code:
1
#pragma once
2
3
#include <Arduino.h>
4
#include <Wire.h>
5
#include "MyFRAM_I2C.h"
6
7
namespace MY_FRAMCONTROL
8
{ 
9
    consteval uint32_t sizeofObject (const auto &data) { return sizeof(data); }
10
    consteval uint32_t lengthStructure (const auto &data, const size_t N) { return sizeofObject(data)/N; }
11
    constinit uint8_t TWI_BUFFER_SIZE {BUFFER_LENGTH-2}; // 'BUFFER_LENGTH' in twi.h abzüglich 2 Bytes Speicherzellenadressierung
12
}
13
14
/*                I2C Addr
15
                  |                 wire0/wire1  
16
                  |                 |              Capacity in kBit       
17
                  |                 |              |                 Object for length determination etc.                 
18
                  |                 |              |                 |               initial address of object in FRAM
19
                  |                 |              |                 |               |                              */
20
template <uint8_t i2cAddr, TwoWire& line, uint16_t memorySize, auto &myObj, uint32_t initObjAddr>  
21
class MyFramControl { };  // allg. Template
22
23
template <uint8_t i2cAddr, TwoWire& line, uint16_t memorySize, typename T, T(&myObj), uint32_t initObjAddr> 
24
class MyFramControl <i2cAddr, line, memorySize, myObj, initObjAddr> 
25
26
(***) hier fehlt das Semikolon
27
28
 // non array Template Spezialisierung
29
30
template <uint8_t i2cAddr, TwoWire& line, uint16_t memorySize, typename T, size_t N, T(&myObj)[N], uint32_t initObjAddr> 
31
class MyFramControl <i2cAddr, line, memorySize, myObj, initObjAddr>  // array Template Spezialisierung
32
{
33
  static_assert((10<=BUFFER_LENGTH), "TWI buffer size smaller than 10 is possible but not practical, Wire src twi.h" );
34
  static_assert(((initObjAddr+sizeof(myObj)) < (1024UL*memorySize/8)), "Object to large or Addr to high" );
35
  private:  
36
    MyFRAM_I2C <i2cAddr, line, memorySize> fram;
37
   
38
    uint32_t calcMemberAddr (const uint32_t objStartIndex, const uint32_t memberOffset) {
39
      using namespace MY_FRAMCONTROL;
40
      return initObjAddr + (objStartIndex*lengthStructure(myObj,N)) + memberOffset;
41
    }
42
    
43
  public:
44
    MyFramControl() = default;
45
      
46
    void readTo (T(&otherObj)[N]) { fram.read(initObjAddr, otherObj); }  
47
};


> Die Methode isConnected() ist jedoch vorhanden. Funktioniert ja mit
> array Objekten.

Nein, das allg. Template ist vollkommen leer.

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> ähm, jetzt fehlt noch die angesprochene Definition für non array Objekte

rischtisch

> und um Codedopplungen zu vermeiden muss ein "Mutterklasse" her die dann
> vererbt wird?

Ja, kann man machen, wenn es sinnvoll ist. Muss man nicht.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ehrlich gesagt komme ich nicht mehr mit.
Mache ich das Semikolon dort hin kompiliert nichts mehr, egal welche 
Version.

Wegen Codedopplung. Wenn die Klassendefinition fehlt muss man den Code 
doch wiederholen oder vererben. Ich verstehe die Aussage nicht "kann, 
muss nicht".

Was ich glaube verstanden zu haben ist, dass meine "non array 
Spezialisierung" nonsens ist, weil das das allgemeine Template 
übernimmt. Nur fehlt dafür die komplette Definition. Würdest du den Code 
in jeder Klasse doppelt schreiben?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> ehrlich gesagt komme ich nicht mehr mit.
> Mache ich das Semikolon dort hin kompiliert nichts mehr, egal welche
> Version.

Wie ich schon unter "edit" schrieb: es ist eine "vermeintliche" 
Spezialisierung, die aber dasselbe aussagt wir das allg. template mit 
"auto& obj". Damit wird eine mögliche Instanziierung mehrdeutig.

> Wegen Codedopplung. Wenn die Klassendefinition fehlt muss man den Code
> doch wiederholen oder vererben. Ich verstehe die Aussage nicht "kann,
> muss nicht".

Wie schon gesagt: eine Spezialisierung stellt eine "Ausnahme" vom 
allgemeinen Fall da. Wie unterschiedlich dann die Implementierung 
ausfällt, kann man so allg. ja nicht beantworten.

> Was ich glaube verstanden zu haben ist, dass meine "non array
> Spezialisierung" nonsens ist, weil das das allgemeine Template
> übernimmt.

Genau.

>Nur fehlt dafür die komplette Definition. Würdest du den Code
> in jeder Klasse doppelt schreiben?

Wahrscheinlich nicht, kann ich aber so nicht beurteilen.

Kommt wie gesagt darauf an: ein Mixin (Basisklasse) wäre denkbar, oder 
einfach nur andere template-Klassen, die eine sinnvolle Teilfunktion 
übernehmen.

Kleinere Variationen im Code kannst Du auch mit constexpr-if machen.

Und warum muss Du überhaupt eine Unterscheidung in ein Skalar und Array 
machen. Man kann doch das Skalar als ein Array[1] auffassen.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich weiß nicht wie man ohne 2. Klassendefinition und ohne Vererbung zwei 
Deklarationen (allg. Template und array Spezialisierung) in eine Klassen 
Definition bringen soll?

Wenn das irgendwie funktioniert und man dann constexpr-if anwenden 
könnte, muss man dafür Type Traits verwenden und auf Datentyp Array 
abfragen?

von Veit D. (devil-elec)


Lesenswert?

Wilhelm M. schrieb:

> Und warum muss Du überhaupt eine Unterscheidung in ein Skalar und Array
> machen. Man kann doch das Skalar als ein Array[1] auffassen.

Du meinst ich soll alles so lassen wie es ist und mich damit begnügen?
1
Daten obj0 [] { 1311, 1611, 1501, "Weiche.A" };

Das kann nicht die Lösung sein. Es könnten auch einfache Variablen sein.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

aktuell habe ich eine Idee gegen Codedopplung. Ich könnte alle Methoden 
in zwei namespaces auslagern und das was ich in der jeweiligen Klasse 
benötige verwenden. Mal sehen ob das klappt ...

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

es ist anders geworden. Habe nun 2 komplette Definitionen. Nur gefallen 
mir die Codedopplungen irgendwie nicht. Wenn ich mir vorstelle man hätte 
noch mehr Spezialisierungen mutiert das zum Wahnsinn. Kann man die 
Codedopplung wirklich nicht vermeiden?

von Wilhelm M. (wimalopaan)


Lesenswert?

Veit D. schrieb:

> es ist anders geworden. Habe nun 2 komplette Definitionen. Nur gefallen
> mir die Codedopplungen irgendwie nicht. Wenn ich mir vorstelle man hätte
> noch mehr Spezialisierungen mutiert das zum Wahnsinn. Kann man die
> Codedopplung wirklich nicht vermeiden?

1. Frage: Was soll dieses Template abstrahieren, wenn doch alles zu 
MyFRAM_I2C deligiert wird?

2. Frage: Welchen Unterschied macht das Template für Skalare und 
Objekte?

Summa summarum: mir erscheint es komplett überflüssig, Du hast doch 
alles schon in MyFRAM_I2C drin.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

MyFRAM_I2C
stellt das Interface dar und setzt auf Wire.h auf. Es kümmert sich um 
die Berechnung der Device Adresse und die Speicherzellenadressen. 
Beinhaltet einen Monitor für das gesamte FRAM. Kümmert sich um das lesen 
und schreiben von Daten auch wenn diese größer sind wie der eigentliche 
TWI Buffer. Hier geht es mehr darum wie kommen und gehen die Daten zum 
und vom FRAM.

MyFramControl
kümmert sich nur um das Objekt welches als Parameter angegeben wurde. 
Sieht man schön an read und write. Man kann einfach read() oder write() 
aufrufen ohne eine Orgie von Parameter mitgeben zu müssen, weil diese 
sowieso gleich bleiben.

Das Ganze funktioniert so und ist gleichzeitig noch Spielwiese. Deswegen 
gibt es bspw. eine Methode readTo(). Dafür ist auch die Spezialisierung 
gedacht. Man kann mit readTo einen Datensatz in ein anderes Objekt 
auslesen welches den gleichen Datentyp haben muss, sonst geht das ja 
schief. Habe ich genutzt zum überprüfen ob die Daten richtig geschrieben 
und gelesen werden. Die update() Methode ist auch noch in MyFramControl 
damit ich Member einer Struktur gezielt überschreiben kann.

Die Frage war, kann man die Codedopplung der MyFramControl Definitionen 
vermeiden?

: Bearbeitet durch User
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.