Compilazione condizionale di una libreria con #define nel file ".ino"

Ciao a tutti, conosco abbastanza bene il CPP ma non ho esperienza con Arduino, per cui vengo a chiedere aiuto agli esperti.

Abbiamo scritto una libreria e un protocollo di comunicazione con il PC. L'implementazione è avvenuta senza troppe difficoltà, ma c'è un particolare che non siamo riusciti a risolvere nel modo dovuto. Abbiamo quindi dovuto ricorrere a una soluzione che comporta un maggiore carico di lavoro alla CPU della scheda Arduino. Ora la libreria è completa e funziona bene, ma siccome io sono un tantino pignolo, mi resta il dubbio che possa esistere una soluzione migliore.


Tutto nasce dalla funzione "serialEvent()", che evento non è, ma solo un polling ad ogni giro di "loop()".

Per cui, per rendere la comunicazione seriale asincrona, e indipendente da quel che l'utente scrive nel "loop()", abbiamo preparato un secondo modo di funzionamento chiamato "AsyncMode".

In questo modo di funzionamento il controllo del buffer della seriale viene chiamato dall'interrupt del comparatore del timer zero, cioè da "ISR(TIMER0_COMPA_vect)"

L'"AsyncMode" non è sempre una buona idea perché diminuisce l'efficienza di comunicazione di un 25% e potrebbe anche creare problemi con alcune librerie che utilizzano il TIMER0_COMPA_vect.

Per cui l'AsyncMode andrebbe abilitato solo se necessario.

Quindi la soluzione pulita sarebbe una "#define AsyncMode" nel file ".ino", e una compilazione condizionale nel file CPP della libreria, con alcuni "#ifdef AsyncMode", per compilare solo le parti necessarie.


Ma, a quanto pare, i file vengono compilati separatamente, e niente di quel che si scrive nel file ".ino" riesce a modificare il comportamento della precompilazione di una libreria.

Abbiamo provato di tutto, #define, costanti ecc.. Abbiamo anche provato a includere un file ".h" di definizioni... Ma il problema fondamentale resta, nessuna informazione passa dal file ".ino" alla libreria in fase di precompilazione.

Qualche idea?

Al momento non si può fare, devi mettere il #define nella libreria e dire all'utente di andare a cercarlo lì.

Grazie SukkoPera, all'inizio abbiamo fatto proprio così ma gli utenti sono pigri... ma forse è meglio dire che non hanno conoscenze sufficienti per muoversi facilmente tra le cartelle. Per cui fanno fatica, sia a trovare dove si trovano le librerie che a modificarle.

Per cui abbiamo utilizzato una funzione "setAsyncMode" da chiamare nel file ".ino". Il tutto non è molto efficiente ma funziona.

Cosa intendi con "Al momento"? Pensi che qualcuno stia lavorando per sistemarlo?

Non necessariamente, ma una volta il builder di Arduino aveva un sacco di comportamenti bizzarri, tipo la necessità di dover mettere tutti gli #include nel file .ino, ripetendo anche quelli di eventuali librerie, e altro. Ad un certo punto hanno rilasciato un nuovo builder che ha risolto quasi tutti i problemi, lasciando solo quello in questione qua e pochi altri, per cui è lecito presumere che magari un giorno sistemeranno pure questo. Puoi provare ad aprire un issue, non si sa mai.

Non necessariamente, ma una volta il builder di Arduino aveva un sacco di comportamenti bizzarri, tipo la necessità di dover mettere tutti gli #include nel file .ino, ripetendo anche quelli di eventuali librerie, e altro.

L'ide attuale dovrebbe permettere di aggiungere degli argomenti passati al compilatore, se fosse così, basta aggiungere: -DAsyncMode.

Io non uso da tempo l'ide di arduino, ma qtcreator opportunamente configurato (ma ovviamente non c'è supporto per il core Arduino). Per aggiungere una macro globale, io scrivo nel file di progetto questo:

DEFINES += TEST_MODE

Si ti va di provare QtCreator segui questo link:http://programmersqtcpp.blogspot.it/2016/08/hack-qmake-avanzato.html

Quello che segue è il contenuto del file di progetto di una libreria statica (al cui interno c'è anche una cartella demo con i .hex tipici del firmware:

message($_QMAKE_CACHE_)
message($MAIN_PATH)


include($MAIN_PATH/hwconfig.pri)

DEFINES += BAUD=38400

CONFIG = static clink stdgnu99 save-temps

DEFINES += TEST_MODE
contains(CONFIG, debug) {
DEFINES +=      __ASSERT_USE_STDERR
} else {
# disable assert macro
DEFINES +=      NDEBUG
}

INCLUDEPATH += /usr/avr/include
INCLUDEPATH += .

TEMPLATE = lib

SOURCES += \
    fntoa.c \
    displaykey.c \
    ntcb.c \
    usart.c \
    thermostat.c \
    fsm.c \
    twimaster.c \
    timeout.c \
    #rtcc.c \
    common.c \
    keypad.c

HEADERS += \
    fntoa.h \
    displaykey_p.h \
    displaykey.h \
    ntcb.h \
    usart.h \
    thermostat.h \
    fsm.h \
    common.h \
    twimaster.h \
    timeout.h \  #\
    keypad.h
    #rtcc.h \
    #rtcc_p.h


# nota che installa solo la librerie statiche e ovviamente gli header file.
include($MAIN_PATH/install.pri)

Ciao.

Grazie a tutti e due, i vostri consigli sono preziosi. Ho preso in mano il primo Arduino da pochi mesi e quindi ho mille cose da imparare.

Immagino che qtcreator funzioni bene, ma credo che non potrebbe risolvere il nostro problema. Noi pubblichiamo una libreria che deve essere utilizzabile e configurabile da tutti. Se facessimo delle particolarità che solo qtcreator capisce taglieremmo fuori il 90% degli utenti di Arduino. O mi sono perso qualcosa?

Maurotec: L'ide attuale dovrebbe permettere di aggiungere degli argomenti passati al compilatore, se fosse così, basta aggiungere: -DAsyncMode.

Senza qtcreator... dove lo aggiungiamo?

Maurotec: L'ide attuale dovrebbe permettere di aggiungere degli argomenti passati al compilatore, se fosse così, basta aggiungere: -DAsyncMode.

Nell'IDE attuale 1.8.5 non ho trovato dove "aggiungere argomenti". Forse ti riferisci ad "arduino builder" che c'è dalla versione 1.6.6 ?

Ho letto questo "...la release 1.6.6 di Arduino IDE si caratterizza prima di tutto per l’inclusione di Arduino Builder, nuovo tool... ...che i programmatori possono sfruttare per compilare i loro progetti software direttamente dalla riga di comando..."

Purtroppo anche questa soluzione non è adatta a tutti gli utenti. Sono ben pochi quelli che sarebbero disposti a compilare da riga di comando.

Naturalmente se si trattasse di far funzionare un robottino a casa mia sarebbe tutto facile, userei "qtcreator". Ma ci teniamo particolarmente a trovare soluzioni facili da usare, perché tutti i nostri progetti sono rivolti a utenti non-programmatori.

Per ora con l’IDE di Arduino non mi pare ci sia soluzione.
La MCU viene passata dall’IDE, esempio AVR_ATmega2560 se compili indicando la board Mega. Poche altre info vengono passate da IDE come #define. Dovrebbe essere documentato quali info siano.
Se vedi anche librerie molto usate, hanno lo stesso problema. Esempio la SSD1306 di adafruit per il tft loro che può essere shield o modulino, devi modificare la libreria per mettere il define giusto, se hai modulino o shield, sono define dentro alla libreria stessa. Ovviamente è molto scomodo.
Anche la IRRemote si può modificare con delle define per settare quale timer usare a seconda della MCU (quello lo passa l’IDE). Le define le devi modificare nel file di libreria IRRemoteInt.h, ora ultima versione boarddefs.h

Anche Webbino fa così, ad esempio, col file webbino_config.h. È proprio per questo che conosco abbastanza bene l'argomento.

Grazie a tutti,
i vostri commenti precisi mi hanno tolto ogni dubbio.

In definitiva la soluzione che abbiamo scelto (chiamare la funzione setAsyncMode dal file .ino) è abbastanza buona e la lasceremo così.

A questo punto direi che l’argomento è ben chiarito, do un punto a tutti per le spiegazioni semplici e esaurienti, e grazie ancora.

Grazie a tutti e due, i vostri consigli sono preziosi. Ho preso in mano il primo Arduino da pochi mesi e quindi ho mille cose da imparare.

Immagino che qtcreator funzioni bene, ma credo che non potrebbe risolvere il nostro problema. Noi pubblichiamo una libreria che deve essere utilizzabile e configurabile da tutti. Se facessimo delle particolarità che solo qtcreator capisce taglieremmo fuori il 90% degli utenti di Arduino. O mi sono perso qualcosa?

Ok, dimentica QtCreator.

Senza qtcreator... dove lo aggiungiamo?

Bella domanda :stuck_out_tongue:, faccio una premessa onde evitare malintesi; Non uso l'ide di arduino, non ho più seguito lo sviluppo nel dettaglio, questo vuole dire che non ho messo il naso nel codice di arduino ide e del builder come facevo un tempo. Per il builder in effetti nel codice ci ho messo il naso, esso è scritto in linguaggio GO, purtroppo mancanza tempo non ho più potuto approfondire e sperimentare. Da quel poco che ho letto (c'è da vedere se poi ho capito bene) ci dovrebbe essere la possibilità di copiare un file .json (o qualcosa di simile) nella cartella di progetto, modificarlo manualmente per personalizzare fase di building. Ora come faccio ad essere sicuro di ciò? Infatti non lo sono perché non ho avuto il tempo per sperimentare se avevo capito bene o frainteso documentandomi.

Riguardo al file .json prima menzionato, io ricordo che l'ide lo scrive nella cartella di building temporanea. Non avendo un IDE arduino funzionante non posso sperimentare.

In base a quanto scritto da @nid69ita e @SukkoPera, il problema sembra non risolvibile, diversamente "adafruit" e gli altri ci sarebbero già arrivati. Tuttavia, se esiste una alternativa, magari è complessa, non ancora pienamente funzionante ecc.

Io però ricordo sia possibile aggiungere argomenti al compilatore, forse non tramite ide direttamente. Alla fine basterebbe aggiungere la define globale tramite la variabile CXXFLAGS.

Purtroppo non avendo in mano la soluzione, permettimi comunque di sfruttare il tuo topic per argomentare. Se ad esempio potessimo considerare il file .ino principale come un file di progetto (oltre il consueto), al suo interno potremmo ad esempio inserire delle direttive verso il preprocessore arduino, il quale dovrà interpretarle.

Esempio:

!! 
{
DEFINE += AsyncMode

} 

void setup() {

}

void loop() {

}

Quando il preprocessore di arduino trova il token !! seguito dal blocco di codice, si attiva, interpreta e rimuove le direttive dal file .cpp che genera.

Scusate la prospettiva di un visionario, ma sembra una soluzione elegante o strampalata?

Ciao.

Sicuramente alcune #define che dipendono da MCU, quale board, etc. vengono passate. Penso in parte sono prese da boars.txt Qualcuno ha un link a qualche documentazione per sapere quali define l'IDE passa ai sorgenti ? Esempio AVR_ATmega2560 che nelle lib Adafruit usa. Lo avranno saputo da qualche doc di Arduino team, immagino. Qui qualcosa ma poche info e comunque non mi pare ufficiali. Compilando il blink.ino per la Uno, si legge su riga comandi di compilazione: -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR Quindi ci sono 4 define e basta ?

Da platform.txt si legge:

preproc.macros.flags=-w -x c++ -E -CC
#preproc.macros.compatibility_flags={build.mbed_api_include} {build.nRF51822_api_include} {build.ble_api_include} {compiler.libsam.c.flags} {compiler.arm.cmsis.path} {build.variant_system_include}
#recipe.preproc.macros="{compiler.path}{compiler.cpp.cmd}" {compiler.cpreprocessor.flags} {compiler.cpp.flags} {preproc.macros.flags}
-DF_CPU={build.f_cpu}
-DARDUINO={runtime.ide.version}
-DARDUINO_{build.board} 
-DARDUINO_ARCH_{build.arch}
{compiler.cpp.extra_flags} {build.extra_flags} {preproc.macros.compatibility_flags} {includes} "{source_file}" -o "{preprocessed_file_path}"

EDIT: https://github.com/arduino/Arduino/wiki/Build-Process

AVR_ATmega2560

Quella sopra viene viene impiegata in avr/io.h, dove viene definita non lo so, ma credo che sia il compilatore, comunque è legata all'argomento -mmcu=

Nel mio caso il buiding mostra questa la seguente riga:

avr-gcc -c -Wall -Os -fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -fdata-sections -ffunction-sections -mmcu=atmega644a -mmcu=atmega644a -save-temps -std=gnu99 -D__AVR_ATmega644A__ -DF_CPU=8000000UL -DBAUD=38400 -DTEST_MODE -DNDEBUG -DF_CPU=8000000UL -I. -I/usr/avr/include -I. -I/home/mauro/.avrspecs/mkspecs/linux-avr8-gcc -o fntoa.o fntoa.c

Ops, ho appeno scoperto un bug nel processo di building che ho messo in piedi;

-mmcu=atmega644a -mmcu=atmega644a; perché due volte e non una?

PS: Nota che questa -D_AVR_ATmega644A_, la aggiungo io, normalmente non dovrebbe essere visibile, almeno credo.

Ciao.

Nid ... come vedi l'IDE prende le informazioni da platform.txt, da boards.txt, le mette assieme e costruisce la linea comando da lanciare.

Nulla di eclatante ...

Guglielmo

Si. Inoltre ho letto su github che ci sono delle richieste per poter passare delle proprie define, esempio uno che suggerisce un file build_props.txt con dentro "compiler.c.extra_flags=-DDEBUG=1" nella cartella dello sketch, ma sono rimaste richieste.

Dunque ricordavo bene circa il file .json, tuttavia mi servirebbe conferma della presenza di questo file nella cartella di build temporanea e se esiste che qualcuno ne posti il contenuto.

Il file in questione è generato da arduino-builder: https://github.com/arduino/arduino-builder.

In fondo alla pagina c'è la seguente sezione:

What is and how to use build.options.json file

Every time you run this tool, it will create a build.options.json file in build path. It's used to understand if build options (such as hardware folders, fqbn and so on) were changed when compiling the same sketch. If they changed, the whole build path is wiped out. If they didn't change, previous compiled files will be reused if the corresponding source files didn't change as well. You can save this file locally and use it instead of specifying -hardware, -tools, -libraries, -fqbn, -pref and -ide-version.

Ora se ricordo bene, l'ide usa questo tool di default per avviare il processo di compilazione e quindi il file "build.options.json" dovrebbe esistere nella cartella temporanea.

Comunque non risolve il problema messo in evidenza da @theremino

Ritorno sulla mia prospettiva da visionario; per evitare di confondere il principiante con tutte quelle direttivi al preprocessore di arduino, l'ide dovrebbe avere in preference una flag "Advanced User" o "Show Arduino preprocessor directive", se abilitata l'editor mostra le direttive.

Caspita ma sono proprio uno stratega. :sunglasses:

Ciao.

Maurotec:
… l’ide dovrebbe avere in preference una flag “Advanced User” o “Show Arduino preprocessor directive”, se abilitata l’editor mostra le direttive…

Io troverei più semplice poter mettere un “#define AsyncMode” nel file “.ino” e poterlo testare nei file CPP e H della libreria.

Tra l’altro questo è il comportamento normale di tutti gli IDE che utilizzano CPP.

Per programmare i PIC utilizziamo il GCC (che se non sbaglio è lo stesso compilatore usato per Arduino). Eppure nell’IDE di Microchip si comporta diversamente, i “#define” sono globali e tutti i file li vedono senza problemi.

Qui è spiegato il processo di build e viene indicato il GCC come compilatore.

Ho cercato di seguire il processo ma non sono riuscito a capire per quale motivo i “#define” si perdono per strada.

Per programmare i PIC utilizziamo il GCC (che se non sbaglio è lo stesso compilatore usato per Arduino) eppure nell'IDE di Microchip si comporta diversamente. I "#define" sono globali e tutti i file li vedono senza problemi.

Capisco, ma l'ide di Microchip è ben più complesso di arduino ide, il principiante assoluto difronte allo sviluppo classico dovrebbe studiare tanto prima di fare lampeggiare un led. Arduino nasce come strumento da dare in mano agli artisti digitali che di C/C++, interrupt, non ne capiscono nulla e non ne vogliono capire nulla, vogliono soltanto uno strumento che gli permetta di esprimersi. La visione di Arduino è questa e bene o male ci sono riusciti.

Comunque per precisione riguardo alle #define globali, sono ritenute tali solo quelle presenti nella variabile CXXFLAGS per g++, CFLAGS per il C (esempio di define globale; F_CPU). Comunque ho capito cosa intendevi per "globali".

Io troverei più semplice poter mettere un "#define AsyncMode" nel file ".ino" e poterlo testare nei file CPP e H della libreria.

Il problema che il .ino e tutte le altre tab sono accorpate in un unico file .cpp(1) che si trova dentro la cartella temporanea di building, viene compilato in formato object file e poi collegato con le librerie ad opera del linker "avr-ld", tool facente parte della toolchain. Tra l'altro tutte le librerie mi pare vengano compilate prima rispetto al .ino.

1) Questo file viene analizzato da preprocessore di arduino, che provvede a inserire le dichiarazioni di prototipi di funzioni, classi, template ecc. Come spiegato nella doc, alle volte il preprocessore non è in grado di produrre il prototipo desiderato dal programmatore e quindi quando accade la compilazione fallisce e l'alternativa è quella di inserire il prototipo manualmente.

Ciao.

Maurotec: ... tutte le librerie mi pare vengano compilate prima rispetto al .ino.

Credo proprio che hai individuato il problema. Se vengono compilate prima inevitabilmente non vedono i "#define" scritti nell' ".ino".

Comunque va bene anche così, una soluzione l'abbiamo trovata ed è sia facile per gli utenti che abbastanza efficiente. Nelle ultime versioni abbiamo ridotto l'overhead ad una unica "if" su un "uint8" e ho verificato che si perdono solo pochi microsecondi.

Ringrazio tutti per il tempo che mi avete dedicato.