Hallo alle zusammen, ich bin gerade dabei mein persönliches Mikrocontroller-Projekt zu schreiben und dabei ist mir zum ersten mal aufgefallen, dass viele Hobby-Programmierer für die Daten wie zum Beispiel Analogwerte, Messwerte, Regelungs- und Steuerungsparameter und Rechendaten einfach kreuz und quer durch den Sourcecode liegen haben und auch die internen (booleschen) Hilfsvariablen im Sourcecode verstreut sind und sogar extern in anderen Objects verlinkt werden. Nun ist meine Frage, wie machen es Profis, was für Datenstrukturen nutzen sie, um ihre Daten mit verschiedenen Typen übersichtlich, zentral, ganzheitlich und vor allem konsistent zu verwalten. Ich meine eine Datenhaltung mit Default-Werten zur Initialisierung, Ober- und Untergrenze, Speicherort im EEPROM, Getter und Setter, Beschreibung mit einem Kommentar und auch Schnittstellen zur Anbindung an UART-, CAN-, TCP-Interfaces und auch Zugriff vom Modbus-Stack. Hat jemand da guten Open-Source C-Code zur Hand oder selbst was programmiert? Wie sind die "Good Practices" bei so einem SW-Modul? Man findet so wenig darüber im Internet, weil das Thema nicht so ernst genommen wird und ich auch noch keine Begriffe für so eine praktische Datenstruktur kenne.
Naja, das ist ganz einfach warum das so ist. Die meisten Beispiele, die man im Netz findet, sind halt recht einfache Programme. Wie solls auch anders sein, alles was komplex ist, dauert Jahre bis man es verstanden hat (U-Boot, Linux etc.). Das ist sozusagen ein Henne-Ei Problem. Für kleine Programme macht es einfach wenig Sinn sich die tollsten Konstrukte zu überlegen und aufzubauen, weil es letztlich einfache Programme bleiben. Und für komplexe Programme gibt es keine universelle Aussage, wie man es dort konkret machen kann/soll. Grundsätzlich gilt allerdings (IMHO), dass die Daten dort erzeugt, initialisiert und benutzt werden, wo sie gebraucht werden. Es ist nur für sehr, sehr wenige Daten überhaupt notwendig, dass man die physikalische Addresse kennt, wichtiger ist das man sich vernünftige Linker sections überlegt und diese konsequent nutzt, dann braucht man da nicht mehr viel händisch machen und hat das an einer definierten Stelle (Linker-script).
Das liegt nur an zu vielem verfuegbarem Speicher. in einem kleinen PIC kann man jedes Register noch persoenlich gruessen. Selbst in einem C64 hatten prominente Speicheradressen ihr eigenes Tuerschild. Durch die schiere Masse ist das heute nur ein graues gesichtsloses Etwas.
Das mache ich z.B. mit typedef struct xyz { uint8_t x; float y; bool z; } xyz_type_t; (evtl. mit extern xyz_type_t my_struct_var;) in einer Header-Datei. Die kann man ja mit #include in die c. Datei einbinden und dort dann eine Variable deklarieren und/oder definieren: xyz_type_t my_struct_var; Feste werte kann man u.a. in einer Init-Prozedur festlegen: my_struct_var.x = <Wert>;
Dem lässt sich aushelfen, indem man auch aufm Mikrocontroller C++ verwendet und alles in Klassen abbildet. Static Variablen vermeiden. Variablen die nur kurz benötigt werden dort erzeugen wo sie gebraucht werden - hier jedoch keine endlos langen Funktionen erzeugen (Zeilenlänge) und Variablen dann sogar am Anfang der Methode deklarieren. Das erhöht die Sichtbarkeit schonmal enorm. Und alle Variablen, die über den Ablauf einer Methode / Funktion benötigt werden, erhalten einen sinnvollen sprechenden Namen in der Klasse. Das alles geht natürlich auch im klassischen C, ist dann nur etwas mehr Tipparbeit. Die einzige globale Variable wird dann auf der main.cpp erzeugt die so aussehen könnte. Mehr steht in meinen main.cpp's in der Regel nicht drin.
1 | #include "mainklasse.h" |
2 | |
3 | Mainklasse meinProgramm; |
4 | |
5 | int main(){ |
6 | while(1){ |
7 | meinProgramm.loop(); |
8 | }
|
9 | |
10 | return 0; |
11 | }
|
Hallo, naja, dann sollte man aber auch die Microcontroller-Familie dazuschreiben. Auf einem AVR z.B. kann das schnell im Crash enden, weil der Ram fragmetiert ist, der hat davon meist nicht allzuviel. Gruß aus Berlin Michael
Michael U. schrieb: > Auf einem AVR z.B. kann das schnell im Crash enden, weil der Ram > fragmetiert ist, der hat davon meist nicht allzuviel. Falls du das auf Bezug von C++ nennst: Natürlich verwendet man kein new, delete oder malloc. Keine virtuellen Methoden, kein std::map, std::string, std::vector oder sonstige Monster der Standard Bibliothek. Ich verwende es ausschließlich (in der Regel) um meinen Code zu strukturieren und dafür ist es super geeignet. Dazukommt, dass die meisten µC inzwischen mit C++ programmierbar sind (AVR, sämtliche ARMs,...). Achja, die Mainklasse beinhaltet natürlich als Membervariablen sämtliche Peripherie als Objekte von Klassen abgeleitet. Alles wird sinnigerweise über die Konstruktoren initialisiert.
1 | class Mainklasse{ |
2 | public:
|
3 | Mainklasse(); |
4 | |
5 | private:
|
6 | USART oUsart1; |
7 | USART oUsart2; |
8 | SPI oSPI1; |
9 | };
|
10 | |
11 | Mainklasse::Mainklasse() : oSPI(SPI3), oUsart1(USART4), oUsart2(USART2) {}; |
Bei mir gibt es jeweils duzende Konfigurationswerte, welche eigentlich fast alle als theoretisch aenderbar angenommen werden. Die stehen im EEPROM, Zentral im Code aufgefuehrt als EEPROM content, funktionell sortiert. Nach diesen Variablen kann man dann auch in der source suchen. Grad unten dran stehen die entsprechenden RAM Variablen. Selbe Reihenfolge. Die werden bei powerup erst mal aus dem EEPROM uebernommen. Dabei steht auch noch welche Commands auf welche Variablen lesen oder schreiben.
> Nun ist meine Frage, wie machen es Profis, was für Datenstrukturen > nutzen sie, um ihre Daten mit verschiedenen Typen übersichtlich, > zentral, ganzheitlich und vor allem konsistent zu verwalten. Sie haben dafuer Datenbanken, spezielle Software und koennen ganze Abteilungen damit beschaeftigen. Die generiert dir dann automatisch Teile deines Codes und die notwendigen Tools fuer Service und Diagnose. Glaub mir, du willst gar nicht wissen wie viel Zeit man darin versenken kann. :) Olaf
Ich hasse das wilde Sammelsurium an verstreuten Variablen in den diversen Quellcodedateien. In C gibt es doch Strukturen. Da kann man mit einer globalen Struktur und weiteren Unterstukturen die Daten schön anordnen. Jede Variable, die nicht nur temporär in einer Funktion benutzt wird, ordne ich in einer Struktur an an.
Variablen die zusammengehören, gehören zusammen. Strukturen, gleicher Ort, Namenbildungsregeln, etc. Variablen, die nicht zusammen gehören, gehören nicht zusammen. Variablen, die gleichartigen Zugriff benötigen, sind von gleicher Struktur (nicht selbe, nur gleicher type), haben gleiche Namensbestandteile und mechanismen. Aber natürlich lokal zum Modul, zu dem sie gehören. Zur Sichtbarkeit: natürlich so klein wie möglich. Ein Schleifenzähler i im innersten Block. Es macht aber keinen Sinn, ein zusammengehöriges flagfeld in 3 zu zerreißen, nur weil einige Global, andere lokal und wieder andere woanders Und mit den getter/Setter: es gibt so Junkies, die machen die überall hin. Dann braucht man über Strukturen und Sinn aber auch nicht wirklich zu diskutieren. Das sind religiöse Eiferer, die jeden steinigen, der Jehova sagt.
Ben S. schrieb: > ...Membervariablen sämtliche > Peripherie als Objekte von Klassen abgeleitet. > Alles wird sinnigerweise > über die Konstruktoren initialisiert. Überleg mal das als Template-Parameter zu machen. Dann hat der Compiler den ganzen Aufwand es Flexibel zu halten, der Optimizer kann seine Stärken entfalten, und am Schluss kommt ein Compilat raus, was dann fast wie Hand-Optimierter ASM-Code aussieht.
Εrnst B. schrieb: > Überleg mal das als Template-Parameter zu machen. > Dann hat der Compiler den ganzen Aufwand es Flexibel zu halten, der > Optimizer kann seine Stärken entfalten, und am Schluss kommt ein > Compilat raus, was dann fast wie Hand-Optimierter ASM-Code aussieht. Ich steh grad aufm Schlauch. Beispiel bitte!
Ben S. schrieb: > Beispiel bitte! Ein kleienes Beispiel: Beitrag "Re: Entprellen (kein AVR)" Findet allerdings keine ungeteilte Zustimmung.
Arduino Fanboy D. schrieb: > Findet allerdings keine ungeteilte Zustimmung. Was soll man da besonderes sehen? Wie man Templates verwendet weiß ich.
Ben S. schrieb: > Ich steh grad aufm Schlauch. Beispiel bitte! Stell dir vor, deine "Mainklasse" soll auch mit einem Soft-UART funktionieren. So: Ben S. schrieb: > class Mainklasse{ > private: > USART oUsart1; > }; geht das dann nicht mehr, du müsstest eine UART-Basisklasse/interface verwenden, die Member als Pointer, Methodenaufruf virtuell mit vtable-lookup usw. Wenn du diese Flexibilität nur zur Compilezeit brauchst, dann kannst du den vtable Overhead wegsparen, und deine UARTs als Template-Parameter einsetzen. aus "oUsart1.write(0x55);" könnte der Optimizer dann z.B. ein ge-inlintes "UDR1=0x55;" oder eben ein "softuart_putchar(0x55);" machen.
Ben S. schrieb: > Εrnst B. schrieb: >> Überleg mal das als Template-Parameter zu machen. >> Dann hat der Compiler den ganzen Aufwand es Flexibel zu halten, der >> Optimizer kann seine Stärken entfalten, und am Schluss kommt ein >> Compilat raus, was dann fast wie Hand-Optimierter ASM-Code aussieht. > > Ich steh grad aufm Schlauch. Beispiel bitte! Ben S. schrieb: > Wie man Templates verwendet weiß ich. Gut. Ben S. schrieb: > Was soll man da besonderes sehen? Besonderes? Nix.... Nur das, wonach du gefragt hast. Im Kompilat würde man sehen, dass der dortige Umgang mit den IOs in wenige ASM Statements zerfällt. Die Instanzen selber verschwinden. Eben dank der Templates und der Optimierung. Die Timer halten einen Status, und zerfallen von daher nicht so weit.
Εrnst B. schrieb: > Stell dir vor, deine "Mainklasse" soll auch mit einem Soft-UART > funktionieren. Wer benutzt denn heutzutage noch eine Softusart :D. Arduino Fanboy D. schrieb: > Im Kompilat würde man sehen, dass der dortige Umgang mit den IOs in > wenige ASM Statements zerfällt. Was ja auch Sinn ergibt, oder? Wenn ich quasi für jeden GPIO mittels Template eine eigene Klasse generiere, dann kriegen dessen Methoden immer das gleiche gefüttert. Ich erstze quasi viele Variablen durch Konstanten. Das ist doch der Grund, oder? Ich finde es nur hchst bedauerlich, dass Templates nur als Headeronly funktioniert. Das ruiniert die ganze Gestaltung der Programmierung.
> Wer benutzt denn heutzutage noch eine Softusart :D.
Nicht alle koennen 2 Euro fuer einen Arm mit 12 konfigurierbaren
Schnittstellen rauswerfen.
Es gibt Leute, fuer die ist alles groesser als ein ATTiny voellig
groessenwahnsinniger Overkill und sowieso viel zu teuer.
Die verwenden dann gerne hochoptimierende Compiler, welche aus 100
Zeilen C++ eine einzige ASM Zeile machen koennen.
Ben S. schrieb: > Wer benutzt denn heutzutage noch eine Softusart :D. Wenn's dann leichter vorzustellen ist: Ersetze Softuart durch USB CDC-ACM.
Stefanus schrieb: > Wie sind die "Good Practices" bei so einem SW-Modul? Man findet so wenig > darüber im Internet, weil das Thema nicht so ernst genommen wird und ich > auch noch keine Begriffe für so eine praktische Datenstruktur kenne. Da scheint die Frage: "wie räum' ich das schön auf" zu sein. M.E. lautet die Antwort: gar nicht. Variablen gehören nicht "schön und geordnet aufgeräumt", katalogisiert und ins Regal gestellt, sondern immer so eng (lokal) wie möglich dahin geschrieben, wo sie benutzt werden. Sie sind Teil des Algorithmus, deswegen gehören sie auch zu dem. Wenn sie in einem Block benutzt werden, lokal zum Block Wenn sie in einer Funktion benutzt werden, lokal zur Funktion. Wenn sie in einem Modul benutzt werden, lokal zum Modul. Wenn sie (ausnahmsweise) global benutzt werden, dann - und nur dann - halt global Wenn man in dieser Liste um eine Zeile nach unten muss, sollte man immer einen sehr guten Grund dafür haben.
Ben S. schrieb: > Michael U. schrieb: >> Auf einem AVR z.B. kann das schnell im Crash enden, weil der Ram >> fragmetiert ist, der hat davon meist nicht allzuviel. ... > private: > USART oUsart1; > USART oUsart2; > SPI oSPI1; > }; > > Mainklasse::Mainklasse() : oSPI(SPI3), oUsart1(USART4), oUsart2(USART2) > {}; So ähnlich mache ich das auch. Wobei ein new bei mir erlaubt ist. Manchmal, je nach Konfiguration, brauche ich eben diese oder jene Objekte. Die werden dann beim Start erzeugt, aber nie wieder gelöscht.
Markus F. schrieb: > Wenn man in dieser Liste um eine Zeile nach unten muss, sollte man immer > einen sehr guten Grund dafür haben. Dem stimme ich natürlich zu. Doch auch die Regel wird zuweilen missbraucht, um Strukturen zu zerreißen. In global und static local. Oder noch schlimmer: statt einer sinnvollen mehrstufigen globalen Struktur mit 10 Elementen werden 30 setter/getter kreiert, um lokal und konsistent zu bleiben.
A. S. schrieb: > Doch auch die Regel wird zuweilen > missbraucht Regeln, die den völligen Verzicht auf gesunden Menschenverstand erlauben, gibt's nicht.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.