Hallo, wie aus anderen Themen von mir erkennbar versuche ich mich gerade daran Bibliotheken für mich selbst aufzubauen. Jetzt noch einen Schritt weiter mit Objektorientierter Programmierung in C++. Ich bin gerade an ein Problem gestoßen wo ich keinen "hübschen" Lösungsweg finde. Da ich mit DMA arbeite, muss ich irgendwie organisieren wann welches Gerät an der Reihe ist, damit nicht das nächste sendet obwohl das erste noch nicht fertig ist. Außerdem noch ein paar weiter Features, wie Priorität, Schnittstelle sperren, etc.. Das geht noch mit einer einfachen Klasse zu lösen. //Memberdeklaration Gerät portexpander1(); Gerät portexpander2(); //Abfrage in mainloop if (portexpander1.darfIchSenden() == true) ... if (portexpander2.darfIchSenden() == true) ... o.ä. Jetzt hat aber so ein STM32 je nach Modell etliche Schnittstellen, wobei mehrere Geräte an einer Schnittstelle hängen können. Wie bekommt man es hin, dass alle Geräte an Schnittstelle i2c1 auf die Freigabevariable von i2c1 zugreifen, die von i2c2 auf i2c2? ungefähr so stelle ich mir das vor: //Memberdeklaration Gerät portexpander1(Schnittstelle1); Gerät portexpander2(Schnittstelle1); Gerät portexpander3(Schnittstelle2); Schnittstelle i2c1(); Schnittstelle i2c2(); //Abfrage in mainloop if (portexpander1.darfIchSenden() == true) ... if (portexpander2.darfIchSenden() == true) ... //in Bibliothek Gerät::darfIchSenden() { SchnittstelleX.wieSchautsMitDemTrafficAus(); } Aber wie sage ich dem Gerät dass es zu Schnittstelle x gehört, ohne die Vorteile der endlosen Erweiterbarkeit bei Objektorientierter Programmierung zu verlieren?
Gast
#5847784
Heinz M. schrieb: > Aber wie sage ich dem Gerät dass es zu Schnittstelle x gehört Indem du die zugehörige Schnittstelle beim Konstruktor übergibst.
1 | |
2 | |
3 | |
4 | |
5 | |
Niemand hindert dich daran, die Schnittstelle zu erweitern. Du kannst den Portexpander auch (einfach so) mit einer erweiterten Schnittstelle konstruieren. Kleiner Tipp: Ich würde mich bei Bezeichnern auf US-ASCII zeichen beschränken. Das erspart einem Stress mit Zeichensätzen, wenn du mal das Betriebssystem wechselst oder die Dateien jemand anderem gibst, dessen Rechner anders konfiguriert ist.
Das oben ist nur Pseudocode und zur besseren Lesbarkeit mit ä geschrieben. Als was soll i2c1 bzw. i2c2 übergeben werden? String, Variable, Pointer? Würde es große Probleme machen Member einfach nur mit Zahlen zu bezeichnen? Mit Variablen hantieren ist einfacher und schneller, als mit strings. 255 Schnittstellen mit je 64 Geräten sind mehr als ausreichend. Im Beispiel unten müsste ich dann noch "uint8_t schnittstelle" als Variable/String bei PriorityDevice ergänzen. Die Abfragen würden dann über PriorityDevice geschehen, welches einen Funktionsaufruf auf PrioritySchnittstelle in Abhängigkeit der gespeicherten Schnittstelle enthält. Bei i2c1.readFlag(); müsste das i2c1 als Pointer oder Variable übergeben werden. Konnte bis jetzt nichts im Internet dazu finden. Hat evtl. jemand einen Begriff wonach ich da suchen könnte? Hier mal die aktuelle Klassendefinition:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
Habe inzwischen herausgefunden, dass Aggregation das richtige sein könnte. Aber eine richtig gute Anleitung bzw. Beispiel konnte ich noch nicht finden.
Gast
#5847897
Heinz M. schrieb: > Als was soll i2c1 bzw. i2c2 übergeben werden? Als Referenz auf das Objekt. Pointer würde auch gehen. Mit deinem Priority Zeugs hast du mich abgehängt, kann Dir gedanklich nicht mehr folgen.
Priorität ist ganz einfach: An einer Schnittstelle hängen mehrere Geräte, wovon aber nur eins gleichzeitig angesteuert werden kann. Einige Geräte müssen in weicher Echtzeit laufen, bei anderen ist es egal. Die mit Echtzeitanforderung brauchen Vorrang. Jetzt kann ich nicht die Übertragung unterbrechen um das zeitkritische zu übertragen. Möchte aber gewährleisten, dass das zeitkritische als nächstes kommt. Deswegen die Priorität. Beispiel: An i2c1 hängt besagter Portexpander. Daran mehrere Drehencoder. Damit es nicht zum Eingabelag kommt müssen diese zeitnah abgefragt werden. Am gleichen Bus hängt ein Display, wo die per Drehencoder eingestellten Werte als Zahlen angezeigt werden. Eine halbe Sekunde Verzögerung ist da völlig egal. Deswegen möchte ich bei der Memberdeklaration jedem Gerät pro Schnittstelle eine Priorität zuweisen können, die ja nach Anwendung unterschiedlich sein kann.
Gast
#5848338
Heinz M. schrieb: > Als was soll i2c1 bzw. i2c2 übergeben werden? https://en.wikipedia.org/wiki/Dependency_injection (der deutsche Artikel ist Mist) Heinz M. schrieb: > Deswegen möchte ich bei der Memberdeklaration jedem Gerät pro > Schnittstelle eine Priorität zuweisen können, die ja nach Anwendung > unterschiedlich sein kann. Nicht zielführend. Eine Klasse/Modul/Funktion etc. sollte genau nur ein Thema verarbeiten. Du hast zwei Themen: a.) Abstraktion der Hardware für den Portexpander b.) Reihenfolge/Priorität welche Ports abgefragt werden. Zwei Aufgaben, zwei Klassen: https://de.wikipedia.org/wiki/Single-Responsibility-Prinzip
Gast
#5848359
Heinz M. schrieb: > Beispiel: > An i2c1 hängt besagter Portexpander. Daran mehrere Drehencoder. Damit es > nicht zum Eingabelag kommt müssen diese zeitnah abgefragt werden. Am > gleichen Bus hängt ein Display, wo die per Drehencoder eingestellten > Werte als Zahlen angezeigt werden. Eine halbe Sekunde Verzögerung ist da > völlig egal. Da steckt schon eine Denkfehler drin. Input und Output gehören nicht an den gleichen Bus. Wenn Du mehrere Busse betreibst macht es Sinn die Aufgaben zu verteilen. Inputs pollen an dem einen, und nur Inputs. Outputs bei Änderungen schreiben auf einem anderen. Und natürlich darfst Du der Klassen die Daten im Konstruktor übergeben. Pro Aufgabe ne Klasse mag theoretisch toll sein aber in der Praxis muß man die Infos auch irgendwie zusammen führen. Ergo wird es ein Objekt mit mehreren Attributen geben, is schon ok.
@ unausgelasteter Programmierer: Ok für den Wikiartikel brauch ich länger. Eine Klasse soll doch ein in sich funktionales Objekt darstellen, wovon verschiedene ähnliche Member erzeugt werden. Schnittstelle und Geräte bekommt man doch unmöglich in eine Klasse. Zwei Themen stimmt, aber andere Aufteilung: a) Schnittstellen und die Verwaltung der Zugriffe und Prioritäten dieser. b) Geräte und die Verwaltung der Eigenschaften dieser (Wozu Zugriffswunsch und Prioritätslevel zählt). @ NichtWichtig: Genau da liegt der Haken, warum ich es universell per OOP möchte. Die Bibliothek soll bei verschiedenen Mikrocontrollern (alles STM32) zum Einsatz kommen. Wie viele Schnittstellen der jeweilige hat, weiß ich jetzt noch nicht. Der STM32F042F6P6 hat zum Beispiel nur 1x I2C. Außerdem macht es keinen Sinn generell alle Inputs an einen Bus und alle Outputs an einen anderen. Das muss je nach Situation entschieden werden. - Wieviele Schnittstellen gibt es - Welche Geschwindigkeit können die Geräte - Zu erwartende I/O Last - Kabelführung - ...
Gast
#5848506
Jupp, das Umfeld und die Möglichkeiten sind wichtig bei der Zuordnung. Ich mußte an einem ARM7 (LPC2119) mit einem i2c Bus ca. 30 Chips managen, da kam die Trennung via Bus-Switches In/Out sehr gelegen. Und OOPs bietet sich an diese Details zu verstecken. Sauberes Software-Design ist der Schlüssel zum Erfolg.
Gast
#5848756
Ich verstehe noch nicht, warum du überhaupt mit Konflikten rechnest, und somit eine Prioritäten-Steuerung brauchst. Ich gehe doch mal stark davon aus, dass du ein single-master System hast. Und außerdem schreibst du von einer Hauptschleife, also wohl kein RTOS. Wie können da überhaupt Zugriffskonflikte auftreten?
Andersrum. Es geht nicht darum welches am Bus angeschlossene Gerät das Senderecht erhält um einen Konflikt zu vermeiden, sondern es geht darum welches am Bus angeschlossene Gerät der Master zuerst bedient. DMA heißt mein Konflikt. Damit ist die Abarbeitung nicht mehr Linear. Während der DMA Controller sendet arbeitet die CPU an anderen Sachen weiter, so dass es vorkommen kann, dass bis der DMA Controller fertig ist mehrere Sachen anfallen, die über ein und die selbe Schnittstelle übertragen werden sollen. Und da soll zuerst das dringliche verschickt werden und dann das unwichtige. Wenn bei DMA munter drauf los gesendet wird, dann hängt sich der µC verständlicherweise auf, weil sich die Nachrichten überschneiden. Man könnte natürlich auch hintereinander senden wie es anfällt oder eine feste Reihenfolge, aber Priorität bringt bessere Reaktionszeiten.
Gast
#5850048
Heinz M. schrieb: > da soll zuerst das dringliche verschickt werden und dann das unwichtige. > Man könnte natürlich auch hintereinander senden wie es anfällt oder eine > feste Reihenfolge, aber Priorität bringt bessere Reaktionszeiten. Ok, jetzt habe ich es verstanden. In diesem Fall würde ich auch dazu raten, die Steuerung der Prioritäten in eine separate Klasse zu schreiben. Ich würde darin alle Anfragen in Form einer Warteschlange verwalten, wo jeder Eintrag eine Priorität bekommt. Diese werden dann entsprechend ihrer Priorität abgearbeitet. Diese Logik gehört weder in die I²C Schnittstellenklasse rein, noch in deren Konsumenten. Es muss eine zentrale Klasse sein.
Bei der Abgrenzung haben wir uns wohl etwas missverstanden. Die Schnittstellen nehme ich von HAL und werden auch nicht in der Prioritätenklasse aufgerufen und die Geräteklassen kommen extra. Also so wie du es schreibst. Was du gerade beschrieben hast wäre dann für eine Schnittstelle, da jede Schnittstelle ihren DMA Channel hat und daher die Schnittstellen unabhängig arbeiten können. Deswegen die zwei Klassen. Übergeordnet: Wie viele Schnittstellen (egal was das für welche sind) gibt es. Untergeordnet: Wieviele Geräte (egal was die tun) hängen an einer Schnittstelle. Ich hatte erst vor die Schnittstellenklasse nur dazu zu verwenden, dass da eine große Variable drin steht (uint64_t) und jedes Bit ist die Priorität eines anderen Gerätes. Danach wird beim Gerät nachgeschaut was es ist. Gerade überlege ich aufgrund deines Posts, ob es nicht doch mit einer Klasse bzw. mit zwei getrennten Klassen geht. Wenn die Priorität gleichzeitig die ID ist, dann müsste beim Zugriff nur geprüft werden, ob die ID passt und ob es eine größere gibt. Ich denke das ist mit Code einfacher:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
Wenns unübersichtlicher ist, könnte ich die zweite Klasse noch nachprogrammieren und die Funktionen der 2 Klassen im Doppelpack schreiben: i2c1.darfIchSenden(Portexpander1.meineID()) Zwar nicht ganz so geil, wie wenn er selbst nachschauen würde, welche Schnittstelle ein Gerät hat, aber eine akzeptable Lösung.
Gast
#5851046
Ich habe das Gefühl, dass du dir da zu viel Gedanken über die Struktur machst. Schreibe erstmal ein funktionierendes Programm und überdenke danach die Struktur. Wenn sie richtig schlecht ist, schreibe den Teil nochmal neu. Ansonsten lasse ihn so. Du läufst sonst Gefahr, vor lauter Perfektionismus nicht fertig zu werden. Wenn man nicht aufpasst, beschäftigt man sich zu sehr mit der OOP an sich, anstatt mit der Aufgabe die man eigentlich damit erledigen wollte. OOP verleitet leider viele Leute dazu. Ich frage dann: Warum hast du das so unnötig komplex gemacht? Die erhliche Antwort lautet dann oft: "Weil ich dachte, man muss das so machen." Lernt man ja so auf der Uni. Oder auch: "Weil ich duplizierten Code vermeiden wollte." Nur: Was nützt die Vermeidung von ein bisschen copy-paste, wenn man sich dafür einen Wolf planen und tippen muss? Niemand wird für perfekt durchgestylte Programmstrukturen bezahlt, sondern dafür, dass das Produkt gut funktionierend zum Zieltermin abgeliefert wird. Ich kenne kein beeindruckend gutes Programm, das von Anfang an tippitoppi durchgeplant und in einem Rutsch entwickelt wurde. Im echten Berusleben ist das stets ein Prozess von mehreren Verbesserungs-Runden, der sich nicht selten über Jahre verteilt.
Als Copy&Paste funktioniert es bereits. Etwas mehr Gedanken wollte ich reinstecken, weil ich es als Bibliothek nutzen möchte und das halt "die" Grundfunktion in Zukunft wird. Deswegen schrieb ich ja, dass das so akzeptabel sein könnte. Weil Aggregation, Komposition, etc. ist mir jetzt doch etwas zu viel.
Weiß jemand wo hier der Fehler ist?
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
Die Fehler würden mir sagen, dass er es nicht findet. Eine einfache Funktion in allgemein.cpp funktioniert. Also müsste die Bibliothek richtig eingebunden sein. Tippfehler konnte ich auch keine finden.
Gast
#5852399
So langsam gefallen mir die Code Guide lines im Job immer besser, was
hier mitunter an kryptischen Zeugs präsentiert wird ist haarsträubend.
Wozu
static uint8_t priorityflag; //später 64
static uint8_t device; //Gerät das sperrt (0 ist frei)
?
Wozu braucht die Klasse
#include <stdio.h>
?
Was soll device als Parameter?
void PrioritySchnittstelle::setFlag(uint8_t device)
{
priorityflag = 10;
}
Was soll device als Parameter ?
uint8_t PrioritySchnittstelle::readFlag(uint8_t device)
{
return priorityflag;
}
Viel Erfolg!
Wenn du static meinst: Damit er sich merkt, welche Geräte gerade Bedarf haben. stdio.h wegen uint8_t. Parameter device hat noch keine Funktion. Macht keinen Sinn da weiter zu programmieren, so lange ich den eigentlichen Fehler nicht gefunden habe. Ich verdiene nicht mein Geld mit Programmierung, möchte es aber dennoch halbwegs sauber machen. Als Programmierer wäre sowas inakzeptabel ist klar.
Hab rausgefunden, wo der Fehler liegt, aber finde nichts, wie ich es lösen kann. Das static ist das Problem, weil static in der Klasse eine völlig andere Bedeutung hat als das static außerhalb der Klasse (wer denkt sich diesen Mist aus?) Ich brauche jedoch pro Objekt eine Variable, die auch nach der Abarbeitung der Funktion erhalten bleibt, weil später wieder drauf zugegriffen wird. Also eben eine statische (bzw. nicht flüchtige) Variable. Die Variable muss von allen Funktionen der Klasse gelesen werden können, deshalb funktioniert es nicht, diese in die Funktion zu schreiben. Und die Variablen der Objekte in main zu speichern wäre am OOP Konzept vorbei. Wenn ich im Internet suche, kommt alles mögliche und sehr oft eben das static für Klassen. Aber static für Objekte finde ich absolut nichts.
Gast
#5854689
Bei der ersten Variante fehlen die instanziierungen der static variablen in der cpp Datei.
Bitte etwas genauer. Oben im Code ein Beispiel wäre super. Aber ich denke das es nicht das richtig ist, weil wie gesagt steht static bei OOP dafür, dass eine Variable für alle Objekte einer Klasse identisch ist. Ich möchte jedoch für jedes Objekt eine eigene Variable, die ihren Wert nicht nur zur Laufzeit der Funktion, sondern zur Laufzeit des gesamten Programms behält.
Gast
#5854702
Eine normale Klassenvariable also?
1. Für jedes Objekt eine eigene Variable. 2. Von jeder Funktion aus schreib und lesbar (für das jeweilige Objekt). 3. Werte bleiben erhalten. Wenn damit eine "normale Klassenvariable" gemeint ist. Problem ist der dritte Punkt. Wenn ich static im Beispiel oben weg machen, kann ich alles machen wie es sein soll, nur eben dass egal was ich reinschreibe immer 0 rauskommt, wenn ich es lese, weil er nach der Funktion die Variable gleich wieder verwirft.
Vergiss es, Fehler war in der Funktion des Objekts. Da war noch eine Testvariable drin. Danke
Gast
#5854723
Heinz M. schrieb: > wer denkt sich diesen Mist (mit static) aus? Das habe ich mich auch mal gefragt. Ich denke, das kommt daher, dass C/C++ den Spagat gewagt hat, einerseits hardwarenah zu sein, und andererseits eine Hochsprache sein wollte. Das Ergebnis davon ist nichts halbes und nichts ganzes. Betrachte das mal aus der Hardware Sicht: Statische Variablen liegen in dem Speichersegment, dass nur einmal beim Programmstart initialisiert wird. Nicht statische Variablen werden dynamisch erzeugt, beim Aufruf einer Funktion/Methode oder beim Erzeugen einer Objekt-Instanz. Wenn man das so betrachtet, ergibt es ein bisschen Sinn. Wie man jedoch auf die Idee kam, das gleiche Schlüsselwort für einen völlig anderen Zweck bei Methoden-Deklarationen zu benutzen und nochmal wieder für einen anderen Zweck um die Sichtbarkeit von Variablen zu beschränken, ist mir ein Rätsel. Irgendwer muss es toll gefunden haben, da es teilweise in Java übernommen wurde. Das ist die Sprache, die mit den Fallstricken aller älteren Sprachen Schluss machen wollte.
Danke für die Erklärung. Inzwischen funktioniert das mit den Prioritäten. Einmal Priorität festlegen und man muss sicht nicht mehr drum kümmern, auf wieviele Geräte gleichzeitig zugegriffen werden soll. Optimierungsbedarf besteht aber noch. Dazu bräuchte ich mal eine Idee. Weil die HAL Bibliotheken je nach µC leicht unterschiedlich sind (i2c vs. i2c1) und ich auch nicht die Bibliotheken für das Maximum an möglichen Schnittstellen schreiben wollte, hatte ich vor die HAL Funktionsaufrufe in der main zu lassen und dadurch meine Bibliothek vollständig unabhängig zu lassen. Das führt dazu, dass das löschen eines Displays schon mal 10 Zeilen in Anspruch nimmt (verschachtelte for Schleifen wo ganz innen drin die HAL Funktion steht). Schöner wäre es natürlich, wenn ich die for Schleifen in die Bilbiothek packen könnte, ohne die HAL Funktionen da rein packen zu müssen. Meine Ideen wären: (1) Aus der Bibliothek eine Funktion im Projekt aufrufen. Die Funktion beinhaltete lediglich die HAL Funktion. Ich weiß aber nicht, wie die Bibliothek die Funktion kennt, ohne dass ich alle Projekte da rein packen muss. (2) Am Ende der while Schleife in der mainloop für alle Schnittstellen den Sendebefehl ein mal schreiben und vorher eine Abfrage, ob es was zu schicken gibt. Jedoch müssen dafür die Bibliotheken recht umfangreich sein. Außerdem wird es bei SPI ätzend mit den zusätzlichen Steuerpins.
1 | |
2 | |
(3) Eine do-while Schleife, die immer wieder die Bibliothek aufruft, was als nächstes gemacht werden soll. Was gemacht werden soll ist binär in einer Variable abgelegt. In der Bilbiothek selbst werden die Schritte runtergezählt, was hintereinander passieren soll. Geht nicht mit DMA, es sei denn es wird mit Variante 2 verknüpft.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
Gibt es eine bessere Möglichkeit? Wenn nicht wird es wohl eine Kombination aus 2 und 3. Bei anderen Bibliotheken wird das ja knallhart da rein gepackt und bei z.B. SPI die Steuerpins vorgegeben, was die Flexibilität enorm einschränkt.
Gast
#5855657
Calls per Adapterklasse plus Interfaceklasse abstrahieren, deiner Schnittstellenklasse eine Referenz auf das Interface verpassen, Adapter in der main mit instanziieren und deiner Schnittstellenklasse übergeben. Mehrere verschiedene Adapter können dann auch auf deine verschiedenen HAL funktionen adaptieren und trotzdem das gleiche Interface fur deine Schnittstellenklasse implementieren. Der kann dann völlig Rille sein, welchen Adapter sie bekommt.
So mal wieder etwas weiter gemacht und gleich ein Problem, zudem ich keine Lösung finde. Und zwar möchte ich in der main ein Array deklarieren, welches die Bibliothek dann nutzen soll. An die Funktion ein Array übergeben funktioniert. Es wäre jedoch schöner, wenn ich das nur ein mal in der Memberdeklaration machen müsste und im weiteren Programm alle Funktionen des Objekts auf das Array zugreifen könnten. Das heißt ich muss den Zeiger übergeben. In der Funktion mit den Speicheradressen rechnen und auf die Daten hinter dem Zeiger zugreifen.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
Wenn ich Zeiger richtig verstanden habe: i2c1TxDaten ist ein Zeiger -> Adresse datenArray ist ein Zeiger -> Adresse &(datenarray + i) sind die Daten von i2c1TxDaten Aber warum meckert der da rum wenn ich das & davor setze "lvalue required as left operand of assignment"
Gast
#5863397
Wo ist datenArray denn ein Zeiger? Ich sehe da: uint8_t datenArray;
Gast
#5863404
Warum übergibst du nicht eine referenz auf das array?
1 | |
Oder du nimmst ein std::array
Hatte gestern leider keine Zeit. "*initDatenArray" ist ein Zeiger, also die Adresse Also müsste ich mit "datenArray = *initDatenArray;" in datenArray die Adresse stehen haben oder nicht? Oder kann man Adressen gar nicht speichern und es muss immer ein zeiger bleiben? Dann wäre meine Frage, wie man einen Zeiger einem anderen übergibt oder wie man den Konstruktor anders macht. Wäre sowieso meine Frage, wie man den Konstruktor evtl. kürzer schreiben kann. Alle bisherigen Versuche haben nicht funktioniert und ich finde es etwas umständlich extra Übergabevariablen zu nehmen. Auch wenn ich datenArray als Zeiger mache meckert er rum. Soweit ich rausgefunden habe ist "&array)[10]" das gleiche wie "array". Direkt an die Funktion übergeben funktioniert. Ich möchte jedoch ein array einem Objekt in der Memberdeklaration zuweisen, so dass im laufe der Mainloop das array nicht mehr zugewiesen werden muss. Bzw. auf das Programm bezogen erklärt: Ein I2C Portexpander braucht für die Kommunikation einen Sendepuffer mit 2 Stellen (was tun, Werte). Bei einem Grafikdisplay braucht man für 4 Zeichen schon ein Array der Größe 24. Um RAM zu sparen soll die Arraygröße je nach Mikrocontroller und je nach Projekt angegeben werden können. Bei I2C steht in der mainloop dann mal gut und gerne 50x die Übergabe des Arrays mit drin. Wenn es nicht geht, dann würde ich es jedes Mal übergeben. Wie bekomme ich die Speicheradresse in das Objekt rein, damit ich es von jeder Funktion nutzen kann?
Gast
#5865469
Heinz M. schrieb: > Also müsste ich mit "datenArray = *initDatenArray;" in > datenArray die Adresse stehen haben oder nicht? Nein. Einem Zeiger weist man Adressen zu. Und die Adresse einer Variable bekommst du mit dem "&" Zeichen.
1 | |
2 | |
Und dann gibt es noch Referenzen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Mit Arrays geht das ebenso.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
Bei Arrays benutze ich persönlich lieber Zeiger. Aber das soll jeder so machen, wie er mag.
Mal ein paar allgemeine unsortierte Anregungen und Hintergründe. _ Du lernst offensichtlich gerade C++ und benötigst noch ein wenig fundamentales Grundwissen rund um Zeiger und Referenzen. Ich würde vorschlagen da erst ein paar Stunden zu investieren um unnötigen Frust zu vermeiden. _ Wenn du C++ nutzt und eine Standard-Bibliothek zur Verfügung steht, dann nutze sie. Es gibt keinen einzigen Grund ein built-in [] Array einem std::array vorzuziehen. _ Versuche möglichst gleich jenen "Dialekt" von C++ zu lernen den man als modernes C++ bezeichnet. Ein klassisches Beispiel dafür ist etwa die for-Schleife die du gepostet hast:
1 | |
2 | |
3 | |
4 | |
Modernes C++ besitzt sogenannte range-based for die in Kombinationen mit den Container der Standard-Bibliothek (z.b. std::array) folgende wesentlich simplere Syntax zulassen:
1 | |
2 | |
3 | |
4 | |
Klassische off-by-1 Fehler lassen sich damit leicht vermeiden. _ Externe Abhängigkeiten an Objekte zu übergeben heißt im Fachjargon "dependency injection". Unter diesem Begriff lässt es sich leichter googlen. Bedenke jedoch, auch in Hinblick auf den vorherigen Punkt, woher soll die Display-Klasse wissen wo das I2C-Daten Array aufhört wenn du ledigliche einen Zeiger übergibst?
@Stefanus: Bei Funktionen funktioniert es mit Zeigern. Aber wie bei Objekten? Zu Funktionen und Referenzen oder Zeigern findet man haufenweise Beispiele im Internet. Aber wie man ein Array mit einem Objekt verknüpft, dazu findet man absolut nichts. Bei Funktionen reicht ein einfacher Zeiger und kann danach das Array in der Funktion verwenden. Bei Objekten muss der Zeiger (oder was auch immer) im Konstruktor sein. Und das so, dass man von der Funktion auf diesen Zeiger (oder was auch immer) zugreifen kann. Mal mit Referenz probiert:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
liefert bei *1 "unused variable 'datenArray'". Wenn ich dort das uint8_t weglasse, dann sagt er mir, dass etwas links vom "&" stehen muss. In der Funktion geht es dann weiter mit Fehlern. Was sich alles darum dreht, dass er "datenArray" nicht findet, inkompatible Typen oder oder oder @Vincent: Mein Problem gerade ist, dass ich vom hundertsten ins tausendste komme. Daher lasse ich das mit dem std::array. Und wenn ich bis Freitag keine Möglichkeit gefunden habe ein Array einem Objekt zuzuweisen, dann wird halt 50x das gleiche array der gleichen Funktion zugewiesen. Es nützt ja nichts wenn man sich alles durchliest was möglich ist, aber ohne Praxis zwischendrin festigt sich nichts.
1 | |
Liefert "Symbol 'End' could not be resolved" "Symbol 'Begin' could not be resolved" Die Größe des Arrays ist egal, solange es groß genug ist. Im Beispiel des Displays wird der Text in der mainloop eingegeben. Die Funktion (hier wird die Textlänge übergeben) arbeitet die Buchstaben einfach der Reihe nach ab. Wenn die Größe automatisch ginge wäre das natürlich auch toll. Aber ich muss wohl mal einen Strich ziehen und mit den jetzigen Mitteln etwas vorwärts kommen. "dependency injection" scheint es zwar zu sein, aber was ich da zu Arrays gefunden habe ist mir zu hoch.
Gast
#5866681
Heinz M. schrieb: > Aber ich muss wohl mal einen Strich ziehen und mit den jetzigen Mitteln > etwas vorwärts kommen. Da würde ich dir doch widersprechen. Lesen, verstehen, umsetzen. So endlos kompliziert sind arrays und Zeiger jetzt nicht. Was du wahrscheinlich möchtest: Private Klassenvariable: uint8_t * datenArray; Konstruktor Parameter: uint8_t * DatenArray Zuweisung im Konstruktor: datenArray = DatenArray; Aufruf des Konstruktors: SSD1306display display1(0x78, 0, &i2c1TxDaten[0]); Nutzen der privaten Klassenvariable: for (uint8_t i = 1; i < anzahlSpalten; i++) { datenArray[i] = 0x0f; }
Gast
#5866759
Beim Deklarieren einer Variable gilt: typ *name oder typ* name oder typ * name ist ein Zeiger auf ein Ding vom angegebenen typ. Das Ding kann eine einfache Variable sein, oder ein Array. Irgendwas halt. Einem Zeiger weist man die Adresse von dem Ding zu. typ &name oder typ& name oder type & name ist eine Referenz auf ein Ding. technisch (im Maschinencode) ist das mit einem Pointer identisch. Aber im Quelltext werden Referenzen anders benutzt, nämlich: Einer Referenz weist man das Ding zu.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
Die Schreibweise "&meinInteger" liefert die Adresse der Variable "meinInteger". Mit Objekten funktioniert das ebenso.
Gast
#5866790
Wenn schon OOP warum dann nicht Vererbung? Eine Klasse welche das Array enthält als Basis für die Klassen welche es brauchen?
@nfet: Danke das ist genau das was ich gebraucht habe. Es funktioniert, er zeigt mir jedoch beim Konstruktoraufruf
1 | |
2 | |
den Fehler
1 | |
2 | |
3 | |
4 | |
5 | |
an. Alle Variablen sind uint8_t. @NichtWichtig: Wenn C, warum nicht gleich C++. Wenn C++, warum nicht gleich OOP. Wenn OOP, warum nicht gleich Vererbung. ... und dann wird der Code so kompliziert, dass man nicht mal eine LED zum Blinken bekommt ;-) Deswegen werde ich jetzt erst mal Kot aufräumen. Das sieht nach dem Zeigermassacker aus wie auf dem Schlachtfeld.
Gast
#5867389
Du hast zwei Konstruktoren mit jeweils einem oder zwei Parametern. Aber du rufst einen (nicht existierenden) Konstruktor mit drei Parametern auf. Konkreten können wir werden, wenn du den betroffenen Quelltext zeigt.
[code]
//Konstruktoraufruf
SSD1306display display1(0x78, 0, &i2c1TxDaten[0]);
//Klasse
class SSD1306display
{
public:
void init();
uint8_t addrTx();
void clear(uint8_t zeile, uint8_t anzahlZeilen, uint8_t spalte,
uint8_t anzahlSpalten);
void ssd1306SendeBefehl(uint8_t zeile, uint8_t spalte);
SSD1306display(uint8_t initAdresse, uint8_t initSchnittstelle, uint8_t
*initDatenArray)
{
adresse = initAdresse;
schnittstelle = initSchnittstelle;
datenArray = initDatenArray;
};
private:
uint8_t adresse;
uint8_t schnittstelle;
uint8_t *datenArray;
};
[code]
Gibt doch nur einen Konstruktor.
Bei 4 Parametern zeigt er exakt den gleichen Fehler an.
Desweiteren zeigt er mir an, dass er eine Methode nicht mehr findet. Ich
habe die Methode von ssd1306Befehl in ssd1306SendeBefehl umbenannt, weil
es Aussagekräftiger ist. Wie kann ich Eclipse veranlassen das neu
einzulesen? Clean und Build hat nicht funktioniert.
Gast
#5867459
Heinz M. schrieb: > Gibt doch nur einen Konstruktor. > Bei 4 Parametern zeigt er exakt den gleichen Fehler an. Dann compilierst du wohl nicht den Code, den du glaubst zu compilieren. Ich würde mir mal zuerst die zugehörige Header Datei anschauen.
Was ich bei Klasse geschrieben habe ist aus der Header Datei. Der Rest darin ist von mir geschrieben und hat keinen Zusammenhang. Kompilieren und Ausführen funktioniert. Macht auch das was er soll. Er zeigt es mir nur als Fehler an.
Gast
#5867474
Heinz M. schrieb: > Was ich bei Klasse geschrieben habe ist aus der Header Datei. In Header Dateien gehören aber keinesfalls die Funktionsrümpfe.
1 | |
2 | |
ist ja auch kein Funktionsrumpf.
Gast
#5867547
Heinz M. schrieb: > class SSD1306display ist ja auch kein Funktionsrumpf. Aber das ist einer:
1 | |
2 | |
3 | |
4 | |
5 | |
Eventuell erkennt dein Build Prozessor Änderungen in *.h Dateien nicht automatisch. Make verhält sich auch so. Ein Clean&Build könnte in deinem Fall helfen.
Gast
#5867550
Für den Fehler: in Eclipse Rechtsklick auf das Projekt im Project Explorer, Index->Rebuild. Warten. Kann funktionieren, muss aber nicht. Der Indexer von Eclipse ist eine Diva. Da kann man nicht so viel drauf geben.
Gast
#5867553
Noch eine Anmerlung dazu: > SSD1306display(const SSD1306display &) Ich glaube das ist der implizit generierte Copy-Construktor: https://en.cppreference.com/w/cpp/language/copy_constructor
Das ist der Konstruktor. Ob der in der Header Datei oder in der cpp steht hat nichts mit dem Fehler zu tun. Ohne Zeiger bringt er auch keinen Fehler.
Wieder vielen Dank an nfet. Der index Rebuild hat beide Fehler behoben.
Ich schreib einfach mal hier weiter Mit der Bibliothek das funktioniert alles schon hervorragend. Die mainloop ist inzwischen wieder extrem angewachsen, mit Inhalt den ich nicht in die Bibliothek schieben kann (weil projektspezifisch). Einen Teil möchte ich in eine extra Datei auslagern, aber so, als wenn es in der mainloop stehen würde. Hintergrund ist, dass ich an mehreren Stellen in der mainloop auf die Objekte zugreife, bzw. verschiedenste Variablen beschreibe und diese am Ende ausgeben lassen möchte. Dadurch müsste ich sehr viele Objekte bzw. Variablen übergeben, was das Ganze zu unübersichtlich und arbeitsintensiv macht. Ich suche also eine Art Sprungmarke, include main.cpp oder ähnliches, um direkt auf die main.cpp zugreifen zu können. Aktuell ist so eine Ausgabe 250 Zeilen lang. Ich rechne später mit 2000 Zeilen und mehr (Menüs mit verschiedenen Textbausteinen, die je nach Menü andere Werte ausgeben oder die Werte anders aufbereiten). Beispiel in Pseudocode:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
Gast
#5890635
globaleObjekte.h
1 | |
2 | |
3 | |
4 | |
5 | |
globaleObjekte.cpp
1 | |
2 | |
3 | |
4 | |
5 | |
Oder so ähnlich völlig ungetesteter Pseudocode
Danke, aber ist nicht das was ich suche. Ich müsste sämtliche Variablen und sämtliche Objekte, die ich in der main.cpp deklariere in der globaleObjekte.h erneut deklarieren. Und jedes Mal in beiden Dateien ändern. weiteres Problem ist, dass ich in die ausgelagerten Dateien auch die Aufrufe der Peripherie packen möchte. z.B.: HAL_I2C_Master_Transmit_DMA(&hi2c1, display1.ssd1306_getAddrTx(), i2c1TxDaten, i2c1TxLaenge); Den Error Handler &hi2c1 bekomme ich nicht eingebunden. extern I2C_HandleTypeDef hi2c1; I2C_HandleTypeDef does not name a type. Ich suche mehr etwas, wo der die ausgabe1.cpp, ausgabe2.cpp, ... vor dem Kompilieren in die mainloop verschoben werden, als wenn die dort stehen würden. Nur wegen der Übersicht in einer extra Datei. Zusätzliche Funktionen und Objekte verkomplizieren in dem Fall eher, als das sie helben.
Gast
#5891043
Heinz M. schrieb: > Ich suche mehr etwas, wo der die ausgabe1.cpp, ausgabe2.cpp, ... vor dem > Kompilieren in die mainloop verschoben werden, als wenn die dort stehen > würden. Nunja... Ich bin nicht dein "Wünsch dir was" Flaschengeist! Kann dir nur zeigen, was geht.... (hoffentlich)
Heinz M. schrieb: > Ich suche mehr etwas, wo der die ausgabe1.cpp, ausgabe2.cpp, ... vor dem > Kompilieren in die mainloop verschoben werden, als wenn die dort stehen > würden. Nur wegen der Übersicht in einer extra Datei. Zusätzliche > Funktionen und Objekte verkomplizieren in dem Fall eher, als das sie > helben. Rat mal was #include macht. Ansonsten sollte man bei solchen Wünschen langsam realisieren dass am Design was nicht stimmt.
Gast
#5891088
Beim Mikrocontroller sind Hardware Schnittstellen in einer ganz bestimmten Anzahl verfügbar. Dynamik ist hier selten bis nie gefragt. Ich habe noch keine serielle Schnittstelle gesehen, die zwischendurch mal weg läuft und durch eine andere Schnittstelle vertreten wird. Deswegen macht es absolut Sinn, die zugehörigen Objekt-Instanzen (wie bei Arduino) global und statisch verfügbar zu haben. In C++ müssen Globale Variablen in einer *.cpp Datei stehen und alle anderen Quelltexte binden sie mittels "external" Schlüsselwort ein. Damit man hier Fehler und Code-Duplizierung vermeidet, erstellt man am Besten eine dazu passende *.h Datei. Das von Arduino Fanboy gezeigte Pattern mit globaleObjekte.cpp und globaleObjekte.h hat sich seit zig Jahren bewährt, man hatte es schon vor C++ so gemacht und tut es ebenso in einigen anderen Programmiersprachen.
Die .h Datei habe ich erstellt und wenn ich dort alles als extern hinterlege funktioniert es auch. Was er aber nicht macht, ist wenn ich die Variablendefinition aus der von CubeMX erzeugten main.cpp in die von CubeMX erzeugte main.h verschiebe. "First defined here" "Multiple definitions of ..." Wenn ich es bei der selbst erstellten .h so mache kommt das gleiche. Die globale Suche findet auch keine weiteren Definitionen der Variable. -------------------------- Hab den Grund gefunden. Wenn man in .h etwas definiert, wird in jeder .c/.cpp die darauf zugreift eine eigene Variable definiert. Dann geht das wohl leider nicht und ich muss mit der einen Dopplung leben.
C++17 hat inline Variablen. Das inline Keyword erlaubt es Variablen in Header zu definieren.
Super genau das was ich gesucht habe und funktioniert auch mit Objekten. Jetzt kann ich 150 Zeilen Definition und 300 Zeilen Code aus der main.cpp auslagern. Er zeigt mir noch einen Warnhinweis an: "inline variables are only available with -std=c++1z or -std=gnu++1z" Ist damit die Version gemeint?
Gast
#5891163
Definiere die Instanzen in der main und übergebe sie per Referenz den Konstruktoren der Klassen, die sie brauchen. Damit musst du sie nirgends global definieren und erhöhst gleichzeitig die Testbarkeit. Dazu sollten die Referenzen dann noch vom Typ einer Interface Klasse sein damit fürs testen auch Mock Instanzen verwendet werden können.
Gast
#5891174
Heinz M. schrieb: > Hab den Grund gefunden. Wenn man in .h etwas definiert, wird in jeder > .c/.cpp die darauf zugreift eine eigene Variable definiert. > > Dann geht das wohl leider nicht und ich muss mit der einen Dopplung > leben. Darum steht ja bei mir auch bei der Deklaration extra das extern davor! Damit die Variablen nur einmal in einer *.cpp definiert werden können. Aber in allen Übersetzungseinheiten verwendet werden können. --- Wenn dich inline Variablen glücklich manchen, dann schön... Aber irgendwie passt das nicht zu deiner vorherigen Ansage, meine ich... Was solls, du glücklich, und gut.
@ bdugfgj: Das macht keinen Sinn. Wie bereits geschrieben möchte ich die main.cpp komprimieren. Wenn ich alle Parameter an die ausgelagerte Datei übergebe, nur um es dort 1:1 an die Library weiterzugeben, habe ich weder Platz gespart noch ist es komfortabler. Ich müsste Funktionen schreiben mit ca. 100 Parametern, wovon einiges Arrays sind. Das wird also nicht nur umständlich sondern auch langsam, weil ich immer alle Variablen übergeben müsste. Mit inline oder extern greift er einfach auf die Variablen zu, die er braucht. Vielleicht mal zur Erklärung: Die ausgelagerte Datei kann man statt als reinen Quellcode mehr als Konfigurationsdatei verstehen. Hier wird das Menü und die komplette Ausgabe aufgebaut, aber ohne die konkrete Berechnung der Pixel für die Anzeige, Menüsteuerung, Textbausteine, etc.. Diese Berechnung findet in der Library statt und die Konfigurationsdatei vermittelt zwischen den beiden. Im Anhang ist ein Bild mit Pseudocode zur Verdeutlichung. Hier ein Beispiel für ein einziges Menü mit gerade mal 4 Textbausteinen:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
Auf die kleinen 128x64 OLED Displays passen gut 16 bis 32 Textbausteine drauf. Bei den großen entsprechend mehr. Menüs funktionieren in 4 Ebenen á 256 Menüs. Im Beispiel oben sind gerade mal 2 Variablen die ausgegeben werden sollen. Da wäre ein Übergeben noch sinnvoll. Bei 200 ... Arduino Fanboy D.: extern nutze ich schon lange. Wenn es nur dafür ist, von der stm32fxxx_it.cpp zur main.cpp eine Hand voll Variablen zu übergeben ist das auch sehr praktisch. Wenn man 200 Variablen bzw. Objekte übergeben möchte, macht das keinen Spaß mehr.
Gast
#5891250
Deine Variablen in der referenzen.h sind dort falsch platziert. Mit dem Ansatz kann das Ganze nur scheitern. In Header Dateien gehören nur Sachen rein, die keinen Speicher belegen und keinen Code erzeugen. Variablen belegen aber Speicher. Die gehören alle in eine *.c (oder *.cpp) Datei. Andere Dateien (gerne auch diese referenzen.h) müssen mit extern auf die Variablen verweisen. Weil: Wenn du diese fehlerhafte Header Dateil mehr als einmal direkt oder indirekt in c/c++ Quelltexte einbindest, bekommst du mehrfache Speicherbelegung (multiple definitions of...) mit gleichen Namen. Und das ist unzulässig, das kann der Linker nicht verarbeiten. Erinnerst du dich, dass ich Dir davon abgeraten hatte, allzu komplexe "Frameworks" zu bauen, solange du noch keine Erfahrung hast? Ich bemerke hier, dass du nicht einmal die Grundlegendsten Grundlagen der Programmiersprache verstanden hast. Ich rate Dir erneut, ein gutes Buch über C und noch ein über C++ zu kaufen und zu studieren. Schau Dich mal beim Addison Wesley Verlag nach Bjarne Stroustrup um.
"Der 2. Punkt ist das Keyword inline. Das macht nichts anderes als dir zu erlauben die Funktion in einer header Datei zu definieren. [...]" https://www.c-plusplus.net/forum/topic/338032/inline-funktionen/5 Das ist typisch Mikrocontroller.net: "Wie kannst du nur sowas machen? Das ist falsch!", "Kauf dir ein Buch!", "Such dir ein anderes Hobby!" Irgendwelche Meinungen von irgend jemandem, wie man etwas zu tun oder zu lassen hätte sind mir sowas von egal. Ich möchte sauberen Code, mit dem ich gut und schnell arbeiten kann. Hinweise und Tipps nehme ich gerne auf, aber bei offensichtlich kontraproduktiven Tipps gebe ich Kontra. Mit inline und .h habe ich die Möglichkeit alles was ich global brauche in wenige zentrale Dateien zu packen. Dadurch kann ich die .h Datei neben der .c Datei öffnen und muss Änderungen nur an einer Stelle eintragen. Es frisst nach meinem Test auch weder mehr Speicher, noch ist es langsamer. Mit global habe ich auch kein Problem, weil ich mir dazu eine ziemlich strikte Verwaltung ausgedacht habe und bis jetzt 0 Probleme mit sich plötzlich verändernden globalen Variablen. Bei extern muss ich es in jeder Datei ändern. Mindestens aber in der Datei, wo es definiert wird und in einer globalen .h. Warum sollte ich den zusätzlichen Aufwand machen? Außerdem spare ich aktuell ca. 150 Zeilen in der main.cpp durch das inline. Funktionen und Objekte sind eine super Sache. Aber zum große Datenmengen hin und her schubsen werden zum einen die Funktionsaufrufe unübersichtlich und zum anderen müsste ich bei Änderungen an jedem Funktionsaufruf, an der Definition und an der Deklaration rumschrauben. Fehler vorprogrammiert. Meiner Meinung nach hat jede der 3 Methoden Vor- und Nachteile und seinen Einsatzzweck. Deswegen verwende ich auch alle 3. Aber in dem Fall ist inline das Maß der Dinge und funktioniert super. Danke Vincent H. Und bezüglich der Größe: Ich könnte natürlich ausschließlich LEDs blinken lassen, solange ich nicht das nötige Wissen habe. Aber ohne anspruchsvolle Objekte lernt man nichts dazu.
Heinz M. schrieb: > Super genau das was ich gesucht habe und funktioniert auch mit Objekten. > Jetzt kann ich 150 Zeilen Definition und 300 Zeilen Code aus der > main.cpp auslagern. > > Er zeigt mir noch einen Warnhinweis an: > "inline variables are only available with -std=c++1z or -std=gnu++1z" > > Ist damit die Version gemeint? Damit sagt dein Compiler, daß er "inline"-Variable prinzipiell kennt, sein Default-Modus aber noch nicht C++17 ist und er deshalb eine der beiden Optionen gesetzt haben möchte. "c++1z" zeigt, daß es keine ganz aktuelle GCC-Version ist, aus einer Zeit, als die 7 von 17 noch nicht fest stand.
Gast
#5891361
Heinz M. schrieb: > Bei extern muss ich es in jeder Datei ändern. Nein, musst du gar nicht. Pass auf: Datei globals.h:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
Datei globals.cpp:
1 | |
2 | |
3 | |
4 | |
5 | |
Alle anderen Quelltexte, z.B. main.cpp:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
Hier siehst du zwei Varianten der Initialisierung. Die Variable
"emergencyStop" wird in der globals.cpp initialisiert. Die beiden
seriellen Ports werden in der main.cpp initialisiert. "Zu hause" sind
sie aber alle in der globals.cpp. Wo immer du sie brauchst, bindest du
nur die globals.h ein.
Es sind also nur zwei Dateien, die beim Hinzufügen neuer globaler
Variaben synchron bearbeitet werden müssen.
> Warum sollte ich den zusätzlichen Aufwand machen?
Weil das kein zusätzlicher Aufwand ist, sondern dem Paradigma "es gibt
keine undefinierten Variablen" geschuldet ist. C/C++ ist alt und
hässlich, aber immer noch ein solides Arbeitsmittel. Aber wenn du dich
an solchen Sprach-Details ernsthaft störst, dann wechsle die Sprache,
z.B. nach Python.
C ist halt so. Punkt.
Spätestens wenn du mal echte Libraries benutzt (nicht das war Arduino
"Library" nennt), und diese 5 Jahre nach Programmerstellung
aktualisierst, wirst du den Sinn der Header Dateien einsehen. Dann wirst
du dankbar sein, dass der Compiler dir (fast) alle Inkompatibilitäten
meldet.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.
