PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Code / Projekt richtig strukturieren



Che Guevara
27.02.2021, 10:16
Hallo,

ich habe ein recht umfangreiches Projekt, welche schon diverse HW-Iterationen durchgemacht hat (angefangen bei AVR, dann XMega, jetzt STM32-wird auch bleiben!).
Um bei einer neuen HW-Version nicht immer allzu viel umschreiben zu müssen (und weil der Code über die Jahre auch ziemlich fett und hässlich geworden ist), möchte ich alles neu strukturieren. Programmiert wird in Plain-C, das soll auch so bleiben.
Momentan siehts so aus:
Verschiedene Bibliotheken lesen zb Sensoren aus, deswegen brauchen sie Funktionen, um auf die Hardware zuzugreifen (meistens SPI). Wenn jetzt eine neue HW kommt, kann es sein, dass Sensor X vorher an SPI1 angeschlossen war, jetzt aber an SPI2 hängt. Dadurch ändern sich aber diverse Dinge, zb. der Bus (wichtig für Clock), die Pins....

Meine Frage ist, wie löse ich das möglichst "professionell" und zukunftssicher?
Die erste Idee war, eine Header Datei zu erstellen "HWBoard.h", in welcher ich alle Peripherien etc. definiere und dann in Zukunft nur noch diese ändern muss.
Dann bekommt jedes Modul, welches auf HW zugreifen muss, nur noch eine Glue-Funktion, welche dann den Zugriff auf die richtige Schnittstelle umleitet.

Wäre das der goto Weg oder gibt es bessere Möglichkeiten?
Der Aufwand der Umsetzung ist für mich zweitrangig, solange es eine einmalige Sache ist und ich in Zukunft neue HW schnell einpflegen kann!

Vielen Dank & Gruß
Chris

Moppi
27.02.2021, 10:37
Hallo!

Ist nicht eindeutig zu beantworten. Kommt auf das Gesamtkonzept an. Eine brauchbare Variante sind Makros in C. Die kannst Du zentral hinterlegen und im Quelltext nur mit den Makronamen arbeiten. Dann kannst Du das jederzeit nachträglich ändern. Wenn sich nicht nur Pins, sondern auch Schnittstellen ändern, musst Du die umleiten, auf die jeweils richtigen Funktionen. Dann hast Du u.U. für jede definierte Schnittstelle in der zentralen Datei einen Wert, der die Schnittstelle angibt, so dass Du dann nach diesem Wert die Umleitung vornehmen kannst. Noch flexibler geht das über Variablen. Die kann man dann auch zur Laufzeit noch ändern, dann ergibt sich schnell die Möglichkeit eines Setup, wie bei einem Mainboard eines x86 z.B., die Werte kann man dann im Flash speichern oder woanders, wo sie nicht verloren gehen; dann bei Systemstart auslesen und die Werte evtl. übertragen.

MfG

Bewehrte Systemlösungen sehen immer so aus, dass man allgemeingültige Funktionen definiert. Z.B. "Read" oder "Write". Dann wird festgelegt, was man dort übertragen kann: Strings (Zeichenketten), Zahlen (Werte), Bytes. Mit Schnittstellenkennung kannst Du dann innerhalb der Funktion die Umleitung auf den jeweils richtigen Code vornehmen, der die Daten über die angegebene Schnittstelle verschickt oder von dort liest. In C(++) selber kann man mehrere Konstruktoren (objektorientiert) erstellen, für jeden speziellen Fall einen, der Compiler sucht sich dann - je nach Übergabeparameter - den Passenden aus, so dass der richtige Code dann am Ende ausgeführt wird. Welches Konzept magst Du lieber? - Das ist die Frage.

Che Guevara
28.02.2021, 13:59
Hi!

Makros werde ich wohl definitiv verwenden, variable Schnittstellen brauche ich nicht, weil sich die HW nicht on the fly ändert! ;) Mir stellt sich aber noch die Frage, wie und wo ich die Markos definiere..
Wo mache ich die HW-Initialisierungen? Alle in einer Datei, oder jede Schnittstelle in dem jeweiligen Modul (also Spi1 in Sensor X Modul zb)?
Definiere ich alle Variablen und ändere in Zukunft nur noch diese oder ändere ich jedes mal den Code selbst? Also definiere ich die Pins zb und schreibe den Code mit den Defines oder ändere ich die Pins im Code direkt?

Gruß
Chris

Holomino
28.02.2021, 14:56
Das ist eine Frage der Vorgehensweise.
Wenn Du beim Erstellen eines neuen Projektes, einer neuen Version oder Variante die Dateien kopierst, kannst Du die Definitionen peux a peux direkt in den Dateien anpassen.
Wenn Du die Dateien verlinkst (Verweis auf einen projektübergreifenden Ordner), wirst Du wohl die Hardwaredefinitionen extrahieren müssen.

Beides hat Vor- und Nachteile. Wenn Du willst, das sich Bugfixes oder Erweiterungen automatisch über all Deine Projekte verteilen lassen, nimmst Du die Dateiverlinkung. Wenn Dich davor graut , kopierst Du besser.

(ohne Versionsverwaltung würd's mich grauen)

Moppi
28.02.2021, 16:20
"define"s sind Textmakros. Wenn Du die verwendest, machst Du das an einer übersichtlichen Stelle, also in einer Datei *.h
Ich benutze z.B. eine "defines.h". Die binde ich dann an geeigneter Stelle ein. Da weiß ich, stehen meine Makros drin. Beispiel: #define name wert
Du verwendest dann überall die Makronamen (-bezeichnungen). Der Compiler ersetzt dann die Namen/Bezeichnungen durch den Text dahinter. Da kannst Du auch Code hinschreiben (nicht nur Werte), manchmal kann das nützlich sein.

MfG




#ifndef __defines__
#define __defines__




.....

#define LiPoIn_12 A3
#define LiPoIn_08 A5
#define LiPoIn_04 A4
#define bps 115200
#define INPUT INPUT_PULLUP

#define setStatePin stateVal = 1; digitalWrite(State, 1);





#endif


"stateVal = 1; digitalWrite(State, 1);" ist Code, der in den Quelltext übernommen wird, wenn ich dort "setStatePin" schreibe.
Mit "#define INPUT INPUT_PULLUP" definiere ich das Makro INPUT neu, als INPUT_PULLUP, das habe ich zum Ausprobieren. Gibt eine Warnung, funktioniert aber.

- - - Aktualisiert - - -

Schau mal im Internet nach "Überladen von Funktionen". Du kannst denselben Funktionsnamen mehrfach verwenden, jedes mal mit andern Übergabeparametern und anderem Code, falls Du so was brauchen kannst.

oberallgeier
01.03.2021, 10:24
Hallo Chris!
.. möchte ich alles neu strukturieren. Programmiert wird in Plain-C, das soll auch so bleiben ..So ähnlich hatte ich es auch vor Jahren. Meine Systematik hatte dann auch mit ner Strukturdarstellung "über alles" angefangen (hier vorzugsweise HW, dort aber schon Verweise auf SW):

......https://dl.dropbox.com/s/hh915ei8msyqtt9/Archie-Topo-kl.JPG?dl=0 (https://dl.dropbox.com/s/4pzc61yk56ug9ej/Archie-Topo.jpg?dl=0)
......© 2021 by oberallgeier - Originalformat im Bild verlinkt

Dazu dann natürlich feinere Aufteilungen (teils nicht wirklich präsentabel hier - aber es hilft bei der Orientierung) für die einzelnen Controller, Controllerquellcodes etc. Vor allem verschiedene Sensoren an verschiedenen Controllern sind so gut im Griff. Wichtig natürlich nicht nur Ort- sondern auch Datumsangaben.

Die Softwareskizzen sind ähnlich und erlauben damit eine schnelle Übersicht über den Code - und dessen Lage auf meinem Computer (wozu gibts denn sonst hyperlinks in Textverarbeitungsprogrammen *gg*).

PS: die Angaben im präsentierten Bild sind teils veraltet - I²C läuft z.B. mittlerweile mit 400 kHz, auch auf den PingPongs habe ich mittlerweile die müden mega8 gegen mega328 mit 20 MHz-Quarz getauscht und UART funzt jetzt mit 512 MBd usf.

Che Guevara
04.03.2021, 09:45
Hallo,

vielen Dank erstmal für die hilfreichen Antworten!
Ich habe mich jetzt dazu entschieden, eben alle Hardware Funktionen und Variablen etc.. in eine eigene Datei zu packen, in welcher ich auch das jeweilige Board definiere, sodass ich dann im Hauptprogramm, falls nötig, auch mit dem Define zwischen versch. Funktionen unterscheiden kann.
Wenn ich das auf die schnelle richtig gesehen habe, gibt es die Überladung von Funktionen nur in objektorientierten Sprachen oder täusche ich mich?
Was mich noch sehr interessieren würde, wäre die Versionsverwaltung. Ich arbeite mit EM:Bitz, da gibt es die Funktion "Autoversioning", allerdings hab ich das noch nie verwendet.
Vielleicht weiß da jemand mehr?

Vielen Dank
Chris

Moppi
04.03.2021, 10:27
nur in objektorientierten Sprachen ....

funktioniert in C++ ... in der Arduino-IDE mit dem Compiler funktioniert das daher auch, in den INO-Dateien.

Leider wohl aber mit dem älteren C nicht.


MfG

alexander_ro
23.05.2021, 08:53
Schon eine weile her aber vielleicht interessiert es Dich: wenn Du Modernes C benutzen magst benutze für die Hardware unabhängigen Teile Funktionstemplates die als Template Parameter die Implementierung der Hardwarespezifischen Teile als Datentyp übergeben bekommen. Dann musst Du nur in Zukunft die Hardware nahen Funktionen neu schreiben und alles andere erledigt der Compiler.

Benutze für Konstanten besser die C Variante und nicht den Präprozessor: const int iPort = 4;
Die sind Typsicher was die defines nicht sind. Kann man genauso in einer Datei Sammeln um sie an Zentraler stelle zu Pflegen.