Programm in mehrere Dateien aufteilen

Hi,

ich möchte gerade meinen Spaghetticode der Übersicht wegen aufteilen.
Hab jetzt meine Sketch.ino und daraus einige Programmteile in eine OLed.h verschoben.
in der Sketch.ino steht folgendes als Verweis und Deklaration:

#include "OLed.h"
SSD1306  display(0x3c, D2, D1);
.....
void loop(){
  oled();
}

in der OLed.h dann wie folgt:

void oled{
.....
}

Beim Kompilieren bekomme ich dann bei den Befehlen in der "void oled()" einen Sack voll Fehler mit ....was not declared in this scope.
Da gehts wohl um lokale und öffentliche Deklaration?
Ich dachte immer, was ich in der Haupt-ino deklariere kennt die andere auch, weil der Compiler eh nur den Code aneinander hängt.

Was passiert wenn du den tab mit dem ausgelagerten Code einfach Oled (ohne .h) nennst und das include weglässt?

Hier zum Nachlesen:

und hier in der Mitte eine nette Grafik wie die IDE die einzelnen Teile zusammensetzt:

https://grahamwideman.wikispaces.com/Arduino+--+Project+structure+and+build+process

Ich dachte immer, was ich in der Haupt-ino deklariere kennt die andere auch, weil der Compiler eh nur den Code aneinander hängt.

Der Weg in die Hölle ist mit falschen Annahmen gepflastert!

Das Zauberwort ist "Übersetzungseinheit".

Der Arduino Builder klatscht alle *.ino Dateien zu einer Übersetzungseinheit aneinander.
Und fummelt sich da nach Gutdünken die Prototypen zusammen.
Das wars.

Wenn du mit *.h und *.cpp Dateien spielst, bist du für alles selber verantwortlich.
Das ist gut und richtig so.

Jede *.cpp Datei ist eine eigene Übersetzungseinheit.

in der OLed.h dann wie folgt:
void oled{
.....
}

Das ist eine (kaputte) Funktionsdefinition, diese richtet in einer *.h Datei Schaden an.
Stopfe sie (repariert) in eine *.cpp, und erstelle in der *.h einen Prototypen.

combie:
Der Weg in die Hölle ist mit falschen Annahmen gepflastert!

Weg in die Hölle beschreibt es richtig, wenn ich mich mit C/C++ auseinander setzen muss :slight_smile:

Oh, sehe gerade, dass ich im ersten Thread noch einen Übertragungsfehler hatte. Klammern vergessen.

void oled() {
...
}

combie:
Das ist eine (kaputte) Funktionsdefinition, diese richtet in einer *.h Datei Schaden an.
Stopfe sie (repariert) in eine *.cpp, und erstelle in der *.h einen Prototypen.

Mit deinen Begriffen kaputt/repariert kann ich atm leider gar nichts anfangen.

Habs jetzt so gelöst:
In der OLed.h nur die definitionen einiger display-images drin gelassen, und ein OLed.ino mit dem Programmcode.
Dann mault der Compiler nicht mehr.

So wies in dem Link von noiasca steht:

All .ino files in the sketch folder (shown in the IDE as tabs with no extension) are concatenated together and the .cpp extension is added to the filename.

Mit deinen Begriffen kaputt/repariert kann ich atm leider gar nichts anfangen.

Hast du doch gefunden...
Und repariert.

Siehe:

Oh, sehe gerade, dass ich im ersten Thread noch einen Übertragungsfehler hatte. Klammern vergessen.

Dann mault der Compiler nicht mehr.

Aber du, nix verstanden, oder?
Dann wird dir das (irgendwann) nochmal auf die Füße fallen.

Noch ne Verständnisfrage:

Wenn ich jetzt einen Sketch mit zwei *.inos (a.ino , b.ino) und einer "c.cpp" erstelle, dann hängt der Compiler beide inos aneinander und macht eine "ab.cpp" draus. Aber die beiden cpps sind dann autark, und wissen nichts voneinander?
Sprich, eine in "ab.cpp" deklarierte Variable kann in "c.cpp" nicht so ohne weiteres verwendet werden?

combie:
Aber du, nix verstanden, oder?

Ich habe so viel verstanden, dass ich mit der o.a. Methode meinen Code strukturieren (auf mehrere Tabs aufteilen) kann. Und das war auch Ziel meiner Aktion.

combie:
Dann wird dir das (irgendwann) nochmal auf die Füße fallen.

Da hast du mit Sicherheit recht. Ich weiss selber, dass das nicht alles Gold ist, was ich programmiere.
Und wenn ich mal viiiieeel Zeit habe, dann kann ich mir das, was ihr könnt, auch mal aneignen.
Momentan muss es so reichen.

Aber danke für eure Hilfe

Hallo,

ja das Thema habe ich auch durch. Wollte sogar anfangen C++ zu lernen, Aber die IDE von Arduino ist schon nicht ganz blöd. Folgendes gilt ab 1.8.5 der Arduino IDE, da ich die älteren nicht mehr verwende.

Speichere dein Projekt ab und erzeuge eine Kopie davon mit neuem Namen.
Dann öffnest du diese.
Jetzt erzeugst du mit dem Button am rechten Rand eine neuen Tab. Und benennst in 1_MainLoop.ino
Dann einen nächsten 2_FunktionenAllgemein.ino und einen dritten 3_Spezial.ino
Alle Deklarationen und include Anweisungen bleiben im Sketch der auch den Namen des Verzeichnisses trägt. Der Programm Titel quasi.
Alle Funktionen die Allgemein gültig sind und von allen weiteren Funktionen benutzt werden kommen ins 2_FunktionenAllgemein. Und alle Speziellen Funktionen kommen dann hier hineine 3_Spezial.ino
1_MainLoop.ino enthält die Funktionen Void Setup() und Void Main(). Der Code der da drin ist bleibt dort.
Die Nummerierung hat den Grund das die IDE immer den Haupt Code zuerst und dann in Abhängigkeit des namen die anderen hinterher erst liest. Das kann zu erheblichen Verwirrungen führen.

Auf diese weise musst die nicht dein Programm ändern, und bekommst mehr übersicht.

Gruss Temucin alias TFT

Ja, das kann man so tun!
Aber dennoch muss ich davon ernsthaft abraten.

Denn:
Man geht damit einen proprietären "Arduino only" Weg.

Wählt man den C/C++ üblichen Weg, dann hat man für die Zukunft gelernt!
Und man kann aus dem Gedöns, mit wenigen Klicks, eine Arduino Lib bauen, welche man danach ohne Klimmzüge weitergeben, und auch in alle eigenen Projekte integrieren, kann.

Hallo,

ich rate auch an es richtig zu machen. Auch wenn hin und wieder paar Stolpersteine lauern.

Im Sketch:

include "oled.h"

in der oled.h

#pragma once

void oled_init (byte pin);     // Bsp. Deklaration

in der oled.cpp

#include "oled.h"

void oled_init (byte pin)     // kompletten Code der Funktion
{
  ...
  pin macht ...
  ...
}

@Doc_Arduino,

ok, danke für das Beispiel.
Ich hab mir jetzt mal die "Mühe" gemacht und meinen Code dementsprechend umgeschrieben.
War echt mühsam und steinig.
Obs jetzt lesbarer ist??? Vllt. muss ich mich erst an die Struktur gewöhnen.

Wie macht ihr das, wenn ihr eine globale Variable in mehreren cpps nutzen wollt.
Angenommen ich messe in einer AnalogIn.cpp eine Temperatur.
Die will ich dann in meiner OLed.cpp anzeigen.
Nur mit Übergabeparametern?

Globale Variablen, möchtest du?

Dann in der betreffenden *.h diese Variable als extern deklarieren und in der *.cpp dann definieren.

test.h

#pragma once

extern bool test;

test.cpp

#include "test.h"


bool test=false;

Überall, wo du die test.h einbindest, kannst du die Variable test nutzen.

@combie.

Aha, und in meiner sketch.ini kann ich die dann nutzen, ohne sie nochmal zu definieren?!
So hätte ich es jetzt jedenfalls bemerkt.

Ähhh... und das #pragma once. Noch nie gesehen.
Google sagt

#pragma once is a non-standard but widely supported preprocessor directive designed to cause the current source file to be included only once in a single compilation

Also Code-Optimierung?

Also Code-Optimierung?

Nein, das ist der moderne "Include Guard"
Welchen du verwendest ist wurscht.
Ohne einen "Include Guard" wirst du irgendwann von Fehlermeldungen erschlagen werden.
Also, eher eine Notwendigkeit, als eine Optimierung.
Ok, vielleicht eine optimierte Notwendigkeit.

Aha, und in meiner sketch.ini kann ich die dann nutzen, ohne sie nochmal zu definieren?!

Genau das habe ich gesagt!

Edit:
Habe den Code gerade noch korrigiert.
(hatte ein include vergessen)

Noch ne kleine Frage:

Die Definition meiner Instanz muss aber in die *.cpp?

SSD1306  display(0x3c, D2, D1);

Für mich wäre es logisch gewesen diese in die *.h zu packen, aber da bekomm ich das Programm nicht übersetzt.

So im Nachhinein betrachtet, muss ich euch recht geben. Das ist gar nicht so kompliziert.
Sieht zwar jetzt aus wie eine Bibliothek für eine Bibliothek, aber ich habe es gerade in einen anderen Sketch kopiert. Da geht es dann doch sehr einfach :slight_smile: :slight_smile:

Danke und Gruß/hk007

Das hatten wir doch schon!
Eben noch, bei der globalen test Variablen.
Was ist bei dem Display anders, als bei der test Variablen?

Dann eben nochmal, solange bis es sitzt:

Die Deklaration in die *.h
Die Definition in die *.cpp

Sprich mir nach:

Die Deklaration in die *.h
Die Definition in die *.cpp


Für mich wäre es logisch gewesen diese in die *.h zu packen,

Der Kopf ist rund, damit das Denken die Richtung wechseln kann.
Denke in "Übersetzungseinheiten".
Denke an die "Sichtbarkeit" für andere Übersetzungseinheiten.
Das ist das Maß der Dinge.

Natürlich darfst du von diesem Grundsatz abweichen!

Die Deklaration in die *.h
Die Definition in die *.cpp

Aber...
Dann solltest du wissen, was du tust, und einen besonderen Grund haben, das zu tun, was du dann tust.


Tipp:
An der Semantik scheint es noch zu hapern.
Du solltest dir den Unterschied zwischen Deklaration und Definition klar machen.

.


hk007:
So im Nachhinein betrachtet, muss ich euch recht geben. Das ist gar nicht so kompliziert.
Sieht zwar jetzt aus wie eine Bibliothek für eine Bibliothek, aber ich habe es gerade in einen anderen Sketch kopiert. Da geht es dann doch sehr einfach :slight_smile: :slight_smile:

Schön!
Dann gehts ja in die richtige Richtung.

Weiter machen!

Die Deklaration in die *.h
Die Definition in die *.cpp

Die Deklaration in die *.h
Die Definition in die *.cpp

Die Deklaration in die *.h
Die Definition in die *.cpp

...
Erinnert mich irgendwie an :

Romani ite domum :slight_smile: :slight_smile:

Du solltest dir den Unterschied zwischen Deklaration und Definition klar machen.

Kann durchaus sein.
Worauf spielst du genau an? Ist "SSD1306 display(0x3c, D2, D1);" keine Definition? :confused:
Rekursiv betrachtet funktioniert (bei mir) der Befehl nur in der *.cpp. Also (siehe oben): Definition.
Oder bin ich nur zu doof es in die *.h zu packen?

Worauf spielst du genau an? Ist "SSD1306 display(0x3c, D2, D1);" keine Definition?

Ist es.
Es ist eine Definition!
Darum muss/sollte sie in die *.cpp.

Denn wenn du sie in die *.h stopfen würdest, hättest du dieses Element in jeder Übersetzungseinheit definiert.
Also mehrfach.
Dem Compiler ist das recht.
Der sieht jede Übersetzungseinheit einzeln.

Aber, dann schimpft der Linker mit dir!
Denn der kann bei Mehrdeutigkeiten, nicht die ausführbare Datei zusammen klöppeln.

extern SSD1306  display;

Das ist eine Deklaration.
Die gehört in die *.h, wenn display global sein soll.


Die Alternative:

Oder bin ich nur zu doof es in die *.h zu packen?

Wenn du in der *.h

static SSD1306  display(0x3c, D2, D1);

schreibst...

Dann bekommt jede der Übersetzungseinheiten ihre eigene display Variable.
Das static sorgt dafür, dass der Linker die display Dinger gar nicht mehr zu sehen bekommt.

Ich glaube, bei dem Display, wäre das static ein Fehler.
Denn du hast nur ein Display.

Aber beim fummeln mit Konstanten, ist das sehr sinnvoll!
(auch für inline Funktionen)

In der *.ino
const byte led = 13;

Wird komplett vom Kompiler weg optimiert.
Frist also kein Brot.


in der *.h
const byte led;

in der *.cpp
const byte led = 13;

Frisst 1 Byte im Ram. Da der Linker die Adresse für alle Übersetzungseinheiten auflösen können muss.


in der *.h
static const byte led=13;

Stellt für jede Übersetzungseinheit eine eigene Konstante bereit.
Welche dann vom Compiler wegoptimiert wird.


Erinnert mich irgendwie an :

Dschungelbuch, die Schlange: "Glaube mir ...."