Forum: Mikrocontroller und Digitale Elektronik Mikrocontroller konsistente Datenhaltung


von Stefanus (Gast)


Lesenswert?

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.

von ai (Gast)


Lesenswert?

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

von PICklig (Gast)


Lesenswert?

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.

von Alexxx (Gast)


Lesenswert?

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>;

von Ben S. (bensch123)


Lesenswert?

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
}

von Michael U. (amiga)


Lesenswert?

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

von Ben S. (bensch123)


Lesenswert?

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) {};

von Pandur S. (jetztnicht)


Lesenswert?

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.

von Olaf (Gast)


Lesenswert?

> 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

von tnzs (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Εrnst B. (ernst)


Lesenswert?

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.

von Ben S. (bensch123)


Lesenswert?

Ε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!

von Einer K. (Gast)


Lesenswert?

Ben S. schrieb:
> Beispiel bitte!

Ein kleienes Beispiel:
Beitrag "Re: Entprellen (kein AVR)"

Findet allerdings keine ungeteilte Zustimmung.

von Ben S. (bensch123)


Lesenswert?

Arduino Fanboy D. schrieb:
> Findet allerdings keine ungeteilte Zustimmung.

Was soll man da besonderes sehen? Wie man Templates verwendet weiß ich.

von Εrnst B. (ernst)


Lesenswert?

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.

von Einer K. (Gast)


Lesenswert?

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.

von Ben S. (bensch123)


Lesenswert?

Ε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.

von Pandur S. (jetztnicht)


Lesenswert?

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

von Εrnst B. (ernst)


Lesenswert?

Ben S. schrieb:
> Wer benutzt denn heutzutage noch eine Softusart :D.

Wenn's dann leichter vorzustellen ist:

Ersetze Softuart durch USB CDC-ACM.

von Markus F. (mfro)


Lesenswert?

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.

von PittyJ (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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
Noch kein Account? Hier anmelden.