Une petite expérience inspirée de :
organisation clair d'un code
Recette de cuisine pour la compilation séparée
Quand on compile un .ino, comment fait le compilateur pour :
Trouver les fonctions appelées dans setup() ou loop() si elles sont définies en fin de fichier ?
Trouver les fonctions situées dans d'autres .ino sans prototype ?
Trouver les variables situées dans d'autres .ino sans déclaration "extern" ?
Normalement, en compilation C classique il faudrait que les fonctions appelées soient définies en début de fichier, avant leur appel donc, ou qu'un prototype soit présent en début de fichier ou dans un .h inclus.
Idem pour les variables.
Quand on compile un ou plusieurs .ino que se passe t-il ?
Prenons un exemple :
main.ino :
int counter;
void setup() {
initialize();
}
void loop() {
test();
go();
}
void go()
{
delay(1000);
}
init.ino :
void initialize()
{
}
test.ino :
void test(void)
{
Serial.println(counter++);
}
D'un point de vue C classique, on peut remarquer :
- aucun prototype nulle part
- la variable counter est utilisée par la fonction test() sans déclaration "extern"
Normalement, la compilation devrait échouer.
Mais non :
Detecting libraries used...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/dev/null"
Generating function prototypes...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/tmp/arduino_build_934234/preproc/ctags_target_for_gcc_minus_e.cpp"
"/mnt/sdc1/riton/arduino-1.8.5/tools-builder/ctags/5.8-arduino11/ctags" -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives "/tmp/arduino_build_934234/preproc/ctags_target_for_gcc_minus_e.cpp"
Compilation du croquis...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/cores/arduino" "-I/home/riton/.arduino15/packages/arduino/hardware/avr/1.6.207/variants/eightanaloginputs" "/tmp/arduino_build_934234/sketch/main.ino.cpp" -o "/tmp/arduino_build_934234/sketch/main.ino.cpp.o"
Compiling libraries...
Compiling core...
Using precompiled core
Linking everything together...
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-gcc" -w -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega328p -o "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/sketch/main.ino.cpp.o" "/tmp/arduino_build_934234/../arduino_cache_411670/core/core_arduino_avr_nano_cpu_atmega328_cb8b63e61353f69f40ab69233ebc372f.a" "-L/tmp/arduino_build_934234" -lm
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/main.ino.eep"
"/home/riton/.arduino15/packages/arduino/tools/avr-gcc/5.4.0-atmel3.6.1-arduino2/bin/avr-objcopy" -O ihex -R .eeprom "/tmp/arduino_build_934234/main.ino.elf" "/tmp/arduino_build_934234/main.ino.hex"
Le croquis utilise 1728 octets (5%) de l'espace de stockage de programmes. Le maximum est de 30720 octets.
Les variables globales utilisent 190 octets (9%) de mémoire dynamique, ce qui laisse 1858 octets pour les variables locales. Le maximum est de 2048 octets.
Tout d'abord, on peut remarquer quelques petites chose :
init.ino et test.ino n'apparaissent nulle part.
Generating function prototypes... (tiens, tiens ...)
Compilation du croquis... (on parle de croquis, pas de plusieurs ...)
Allons voir ce croquis :
/tmp/arduino_build_934234/sketch/main.ino.cpp :
#include <Arduino.h>
#line 1 "/mnt/sdc1/riton/Arduino/main/main.ino"
#line 1 "/mnt/sdc1/riton/Arduino/main/main.ino"
int counter;
#line 4 "/mnt/sdc1/riton/Arduino/main/main.ino"
void setup();
#line 8 "/mnt/sdc1/riton/Arduino/main/main.ino"
void loop();
#line 13 "/mnt/sdc1/riton/Arduino/main/main.ino"
void go();
#line 2 "/mnt/sdc1/riton/Arduino/main/init.ino"
void initialize();
#line 2 "/mnt/sdc1/riton/Arduino/main/test.ino"
void test(void);
#line 4 "/mnt/sdc1/riton/Arduino/main/main.ino"
void setup() {
initialize();
}
void loop() {
test();
go();
}
void go()
{
delay(1);
}
#line 1 "/mnt/sdc1/riton/Arduino/main/init.ino"
void initialize()
{
}
#line 1 "/mnt/sdc1/riton/Arduino/main/test.ino"
void test(void)
{
Serial.println(counter++);
}
Quand on compile un projet comprenant plusieurs .ino, l'IDE regroupe tout dans un seul .cpp, en ajoutant les petites choses qui manquent :
- #include <Arduino.h>
- les prototypes des fonctions
Par contre il n'ajoute aucune déclaration "extern" des variables.
On peut en tirer quelques conclusions :
Toutes les fonctions pourront être appelées à partir de n'importe fonction de n'importe quel fichier.
La variable counter ne sera vue de tous les autres fichiers et fonctions que si elle est placée dans le .ino principal et pas ailleurs.
Une variable déclarée dans un autre .ino que le .ino principal ne pourra être vue que par les fonction du même fichier ou des fichiers suivants (dans l'ordre des onglets), à moins de déclarer un "extern" de cette variable dans les fichiers précédents.
Assez pittoresque non ?
Et assez contraignant somme toute.
Assez difficile d'imaginer une vraie modularité dans ces conditions.
Il est clair que cette façon de travailler ne fonctionne que dans l'IDE ARDUINO et ne marchera pas dans un autre IDE, PlatformIO entre autres.
La seule façon correcte de procéder est celle qui consiste à créer non pas plusieurs .ino dans son projet mais un seul .ino (ou main.cpp dans PlatformIO) et plusieurs .cpp et .h
Cela permet entre autres de développer ses logiciels avec PlatformIO, un IDE pourvu de toutes les fonctionnalités que l'on attend d'un outil moderne, et que j'utilise pour tous mes développements conséquents.
Et cela permet aussi d'exporter facilement un .cpp avec son .h associé pour en faire une librairie.
Cordialement
Henri