Wie Objekte in einem Array aktuell halten und umgekehrt?

OP #7506345
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
}
OP #7506355
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;
OP #7506359
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?

OP #7506365
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
}
Moderator #7506397
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.

#7506401
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.

#7506405
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 "*".

Moderator #7506438
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.

OP #7506494
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.

Moderator #7506529
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
}
Moderator #7506545
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.

OP #7506890
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.

OP #7506896
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
}
OP #7506906
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
}
OP #7506920
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.

#7506972
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.

#7506973
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);
#7506974
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 ...

OP #7507128
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?

#7507216
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.

OP #7507225
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.

OP #7507449
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.

OP #7508602
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.

#7508745
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.

OP #7508947
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. ;-)

#7512079
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));
OP #7512451
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. :-)

#7512497
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
}
OP #7512646
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. :-)

#7512878
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.

#7513275
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.

OP #7517682
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?

#7518241
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.

OP #7518382
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?

OP #7518431
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 + (objStartIndexlengthStructure) + memberOffset 0 + (218) + 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?

OP #7518806
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. :-)

OP #7519151
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
#7519214
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).

OP #7519299
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
};
OP #7519474
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".

#7519591
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.

OP #7519624
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.

#7519625
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).

OP #7519938
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?

#7519958
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.

OP #7519998
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.

#7520003
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.

(Firma: ab) #7520116
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.

#7520190
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 :-)).

(Firma: ab) #7520402
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
//-------------------------------------------------------------------------------
#7520425
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.

(Firma: ab) #7520532
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.

Angehängte Dateien:
#7520545
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 ;-)

#7525987
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.

OP #7526031
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.

Angehängte Dateien:
#7526033
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.

OP #7526046
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?

#7526049
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.

OP #7526059
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?

#7526516
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.

OP #7526563
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?

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren