Forum: Compiler & IDEs Arduino-Projekt mit Modulen (Quelldateien strukturieren)


von Hänschen K. (Gast)


Lesenswert?

Ich versuche ein mit der Arduino IDE erstelltes Projekt zu 
strukturieren.
Ich programmiere weitestgehend in C, grundsätzlich nutzt Arduino jedoch 
C++.
Derzeit sind alle "Module" ausschließlich in Header files untergebracht 
(jeweils mit #ifdef MODUL_H) und vermeiden so eine einzelne, elend lange 
.ino-Datei.

Der Versuch, diese in Header (.h) und Code (.cpp) aufzuteilen, also etwa 
so:
1
project/
2
  project.ino
3
  module.cpp
4
  module.h
5
  display.cpp
6
  display.h

scheiterte bislang an Compilerfehlern innerhalb eines Arduino-Moduls 
(Bibliothek). Daher habe ich es jetzt erstmal rückgängig gemacht.

Konkrete Hinweise finde ich leider von "offizieller" Seite nicht:

https://www.arduino.cc/en/Guide/Environment#toc8

> Tabs, Multiple Files, and Compilation
> Allows you to manage sketches with more than one file (each of which appears in 
its own tab). These can be normal Arduino code files (no visible extension), C 
files (.c extension), C++ files (.cpp), or header files (.h).

Aber es dürfte sich ja unter der Haube an die C++/C-Compiler-Regeln 
halten.
Für Hinweise oder Verweise auf konkrete Beispiele wäre ich dankbar.

von Veit D. (devil-elec)


Lesenswert?

Hallo,
1
Datei projekt.ino
2
-----------------
3
#include "module.h"
4
#include "display.h"
5
6
7
8
Datei module.h
9
-----------------
10
#pragma once
11
12
13
Datei display.h
14
-----------------
15
#pragma once
16
17
18
19
Datei module.cpp
20
-----------------
21
#include "module.h"
22
23
24
25
Datei display.cpp
26
-----------------
27
#include "display.h"


Die .h/.cpp Dateien legste am Besten in User/Dokumente/Arduino/libraries 
ab inklusive Unterordner. Danach IDE beenden und neu starten. Wenn du 
dann die Lib Dateien zum Bsp. mit Notepad++ bearbeitest, einfach alles 
speichern und in der IDE neu kompilieren lassen, dass klappt.

: Bearbeitet durch User
von Hänschen K. (Gast)


Lesenswert?

Danke, ich probier's heute abend nochmal.
#pragma once hatte ich nicht, das dürfte ja dem Konstrukt #ifndef / 
#define / #endif entsprechen.
Mir ist aufgefallen, dass die IDE ihre temporären Dateien nicht 
aufräumt, d.h. wenn ein Modul im Projektverzeichnis entfernt wird, kann 
es sein, dass es trotzdem noch compiliert wird.
Der Neustart ist also wohl stets fällig.

von leo (Gast)


Lesenswert?

Hänschen K. schrieb:
> Derzeit sind alle "Module" ausschließlich in Header files untergebracht

Das geht vollkommen problemlos und automatisch. Vielleicht hilft 
s/Modul/Library/ ;)
https://www.arduino.cc/en/reference/libraries

leo

von Christoph M. (mchris)


Lesenswert?

Interessant,

pragma once

kannte ich gar nicht.
Ich habe immer die Include-Guards verwendet. In der Wikipedia steht 
"widely supported"

https://en.wikipedia.org/wiki/Pragma_once

Wie ist das zu bewerten? Es scheint also nicht von jedem Compiler 
unterstützt zu werden, aber der GCC kann's wohl.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wie schon erkannt sind pragma Direktive nicht allgemeiner Standard, 
werden aber von den meisten Compilern unterstützt, weshalb der 
Verwendung nichts im Weg steht. Erspart einem auf jeden Fall das Kaos 
mit #if... im Normalfall wenn man nur einen Header einbinden möchte.

von Veit D. (devil-elec)


Lesenswert?

Hänschen K. schrieb:

> Mir ist aufgefallen, dass die IDE ihre temporären Dateien nicht
> aufräumt, d.h. wenn ein Modul im Projektverzeichnis entfernt wird, kann
> es sein, dass es trotzdem noch compiliert wird.
> Der Neustart ist also wohl stets fällig.

Manchmal werden Projektdateien im C ... temp Ordner nicht gelöscht nach 
beenden der IDE. Das ist aber ein sehr seltener Fehler. Hat aber nichts 
mit dem Kompilieren zu tun was du vielleicht meinst.

Kompiliert wird nur das was du als Programmierer eingebunden haben 
möchtest. Wenn du irgendwo ein include entfernst bekommste Mecker.

Wenn ich eine Libdatei lösche während die IDE offen ist und diese 
benötigt, dann meckert der Compiler. Wenn ich eine Libdatei parallel zur 
offenen IDE in Notepad++ bearbeite und nicht neu speichere, kompiliert 
die IDE noch mit dem ihr bekannten alten Dateistand. Drücke ich 
speichern kompiliert sie umgehend mit dem neuen Dateistand. Die IDE holt 
sich demzufolge immer die aktuelle Datei. Deswegen kann ich das nicht 
ganz glauben das die IDE eine nicht mehr vorhandene Datei immer noch 
fehlerfrei kompilieren kann.

von Wilhelm M. (wimalopaan)


Lesenswert?

Christoph M. schrieb:
> Interessant,
>
> pragma once
>
> kannte ich gar nicht.
> Ich habe immer die Include-Guards verwendet. In der Wikipedia steht
> "widely supported"
>
> https://en.wikipedia.org/wiki/Pragma_once
>
> Wie ist das zu bewerten? Es scheint also nicht von jedem Compiler
> unterstützt zu werden, aber der GCC kann's wohl.

Ich würde eher anders herum fragen: welcher Compiler kann es nicht. Die 
großen drei können es alle, der EDG (s.a. MS-Code) auch.

von Christoph M. (mchris)


Lesenswert?

>Ich würde eher anders herum fragen: welcher Compiler kann es nicht. Die
>großen drei können es alle, der EDG (s.a. MS-Code) auch.

Es gibt ja schon noch ein paar andere Argumente gegen die Verwendung.

Caveats

Identifying the same file on a file system is not a trivial task.[6] 
Symbolic links and especially hard links may cause the same file to be 
found under different names in different directories. Compilers may use 
a heuristic that compares file size, modification time and content.[7] 
Additionally, #pragma once can do the wrong thing if the same file is 
intentionally copied into several parts of a project, e.g. when 
preparing the build. Whereas include guards would still protect from 
double definitions, #pragma once may or may not treat them as the same 
file in a compiler-dependent way. These difficulties, together with 
difficulties related to defining what constitutes the same file in the 
presence of hard links, networked file systems, etc. so far prevented 
the standardization of #pragma once.[citation needed]

von Wilhelm M. (wimalopaan)


Lesenswert?

Christoph M. schrieb:
>>Ich würde eher anders herum fragen: welcher Compiler kann es nicht. Die
>>großen drei können es alle, der EDG (s.a. MS-Code) auch.
>
> Es gibt ja schon noch ein paar andere Argumente gegen die Verwendung.
>
> Caveats
>
> Identifying the same file on a file system is not a trivial task.

... Mmh, das muss ein ganz altes Zitat sein. Heute sind Hashes üblich 
dafür.

Gegen old-school include-guards sprechen ja auch viele Dinge. Vor allem 
die Fehleranfälligkeit, weil die Bezeichner selbst gewählt werden. Das 
macht gerade beim Umbenennen von Dateien Probleme. Da ist das pragma 
m.E. wesentlich einfacher.

Beitrag #6011859 wurde vom Autor gelöscht.
von Hänschen K. (Gast)


Lesenswert?

- immer schön .cpp nutzen. Umbenennung von Dateien (.c -> .cpp) bringt 
die IDE durcheinander, genauer, die IDE sieht die "neue" Datei im 
Projektverzeichnis, aktualisiert aber nicht das Temporäre (siehe oben).

- in den Headern die Variablen ggf. extern deklarieren.

- #defines aus der .ino sind nicht global und damit den Modulen nicht 
bekannt. Also entweder in die jeweilige Header-Datei oder eine 
gemeinsame nutzen.

- #pragma once habe ich jetzt noch nicht benutzt. Gewohnheitssache...

Damit müsste es klappen. Ggf. an der Hierarchie/Struktur feilen.

Danke für die Motivation und den anderen gleichfalls Geduld und 
Erfolg...

von Hänschen K. (Gast)


Lesenswert?

Hänschen K. schrieb:
> - #pragma once habe ich jetzt noch nicht benutzt. Gewohnheitssache...

Jetzt doch umgestellt. :-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Hänschen K. schrieb:

> - #defines aus der .ino sind nicht global und damit den Modulen nicht
> bekannt. Also entweder in die jeweilige Header-Datei oder eine
> gemeinsame nutzen.

Präprozessor-Macros sind immer(!) global, sie kennen keinen 
Gültigkeitsbereich (Scope). Sie gelten ab dem Punkt der Defintion 
überall. Das ist ja gerade eines der fatalen Merkmale dieser Macros.

von Wolfgang (Gast)


Lesenswert?

Hänschen K. schrieb:
> ... scheiterte bislang an Compilerfehlern innerhalb eines Arduino-Moduls
> (Bibliothek).

Hast du die Fehlermeldung mal durchgelesen und auch versucht zu 
verstehen?

Warum hälst du nicht an die Arduino Konvention, gibst deinen Dateien den 
Nachnamen ".ino" und packst sie zusammen mit deinen Header Dateien in 
deinen Projektordner?
Dann klappt das auch mit dem Kompilieren.

von Hänschen K. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Präprozessor-Macros sind immer(!) global, sie kennen keinen
> Gültigkeitsbereich (Scope). Sie gelten ab dem Punkt der Defintion
> überall. Das ist ja gerade eines der fatalen Merkmale dieser Macros.

Probier das mal aus:

sketch.ino
1
#define STIMMTNICHT
2
void setup() {
3
}
4
void loop() {
5
}

test.cpp
1
#ifndef STIMMTNICHT
2
#error ich hatte recht
3
#else
4
#error du hattest recht
5
#endif

von Hänschen K. (Gast)


Lesenswert?

Wolfgang schrieb:
> Hast du die Fehlermeldung mal durchgelesen und auch versucht zu
> verstehen?
>
> Warum hälst du nicht an die Arduino Konvention, gibst deinen Dateien den
> Nachnamen ".ino" und packst sie zusammen mit deinen Header Dateien in
> deinen Projektordner?
> Dann klappt das auch mit dem Kompilieren.

Ja.

Es gibt keine "Arduino Konvention", und ich hatte noch keine "Header 
Dateien". ".ino" erhält im temporären Verzeichnis übrigens die Endung 
".cpp".
Es klappt ja auch mit dem Kompilieren, wie ich weiter oben schrieb.

von Wilhelm M. (wimalopaan)


Lesenswert?

Hänschen K. schrieb:
> Wilhelm M. schrieb:
>> Präprozessor-Macros sind immer(!) global, sie kennen keinen
>> Gültigkeitsbereich (Scope). Sie gelten ab dem Punkt der Defintion
>> überall. Das ist ja gerade eines der fatalen Merkmale dieser Macros.
>
> Probier das mal aus:
>
> sketch.ino
>
1
> #define STIMMTNICHT
2
> void setup() {
3
> }
4
> void loop() {
5
> }
6
>
>
> test.cpp
>
1
> #ifndef STIMMTNICHT
2
> #error ich hatte recht
3
> #else
4
> #error du hattest recht
5
> #endif
6
>

Wer inkludiert hier was? Ich sehe nicht, das test.cpp etwas inkludiert.

Hast Du verstanden, was der Präprozessor ist, wann er aufgerufen wird? 
Ich denke, das hast Du nicht.

von S. R. (svenska)


Lesenswert?

Wilhelm M. schrieb:
> Hast Du verstanden, was der Präprozessor ist, wann er aufgerufen wird?
> Ich denke, das hast Du nicht.

Er wollte dir mitteilen, dass eine Präprozessor-Definition mitnichten 
überall global gilt. Der Compiler muss sie in dieser Translation Unit 
auch sehen, sonst tut sie das nämlich nicht.

Eine globale Variable kann ich immer von überall erreichen, ein Makro 
nicht. Daraus folgt, dass Makros schonmal nicht global sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

S. R. schrieb:
> Wilhelm M. schrieb:
>> Hast Du verstanden, was der Präprozessor ist, wann er aufgerufen wird?
>> Ich denke, das hast Du nicht.
>
> Er wollte dir mitteilen, dass eine Präprozessor-Definition mitnichten
> überall global gilt. Der Compiler muss sie in dieser Translation Unit
> auch sehen, sonst tut sie das nämlich nicht.

Der Compiler sieht sie gar nicht, sondern der Präprozessor.

Die meisten Leute verstehen eben nicht, dass der Präprozessor ein 
nicht-interaktiver Editor ist.

> Eine globale Variable kann ich immer von überall erreichen, ein Makro
> nicht. Daraus folgt, dass Makros schonmal nicht global sind.

Nein, das gilt nur für programm-globale Variablen. Für TU-globale gilt 
das auch nicht, die kannst Du auch nicht von überall aus Deinem Programm 
erreichen, sondern eben nur als der definierenden TU ab 
Definitionspunkt.

Und ein Präprozessor-Macro ist in dem Sinne global, dass es natürlich ab 
Definitionspunkt überall gilt. Lässt man das #include weg, kann das 
natürlich nicht gehen - soviel Erkenntnis hatte ich auch vom TO 
erwartet. Man kann zu einem Präprozessor-Macro aber auch nicht TU-global 
sagen, weil es eben auch in andere TUs inkludiert werden kann, und dann 
auch dort gilt. Deswegen habe ich ja auch unscoped als Erläuterung dazu 
geschrieben.

von Hänschen K. (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Wer inkludiert hier was? Ich sehe nicht, das test.cpp etwas inkludiert.

Habe ich nicht behauptet.

Du hattest dieser Feststellung widersprochen:

Hänschen K. schrieb:
> - #defines aus der .ino sind nicht global und damit den Modulen nicht
> bekannt.

Deine Annahmen kenne ich nicht.
Schwamm drüber.

von Frank L. (Firma: Flk Consulting UG) (flk)


Lesenswert?

Hallo,

ich habe mir gerade mal die Kombination von Visual Studio Code und 
platformIO ausprobiert. Nette Kombination.

Vielleicht eine bessere Alternative als die blöde Arduino IDE.

Gruß
Frank

von Veit D. (devil-elec)


Lesenswert?

Hallo,

es geht hier im Thread nicht um die IDE und welche jemanden besser 
gefällt. Es geht darum wie man seine Libs eingebunden bekommt. Das ist 
vollkommen IDE unabhängig.

von S. R. (svenska)


Lesenswert?

Wilhelm M. schrieb:
> Nein, das gilt nur für programm-globale Variablen.

Richtig. Sowas verstehe ich unter "global".

> Für TU-globale gilt das auch nicht, die kannst Du auch
> nicht von überall aus Deinem Programm erreichen,

Was soll denn bitte TU-global sein? Es gibt "global" (von überall aus 
erreichbar) und "lokal" (an einen Ort gebunden).

Das sind Gegenstücke: Was nicht global ist, ist lokal.

> sondern eben nur als der definierenden TU ab Definitionspunkt.

Wenn du "innerhalb der TU" meinst, dann ist das lokal. TU-lokal.

Und wenn du "erreichbar in dieser TU sowie allen weiteren TUs" meinst, 
dann ist das einfach nur seltsam, ähnlich wie deine Definition.

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.