IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

Optimiser son code pour Arduino


Sujet :

Arduino

  1. #1
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut Optimiser son code pour Arduino
    Bonjour à tous,

    J'arrive à faire ce que je veux avec mon Arduino UNO mais la ROM est presque remplie...

    Dommage j'avais quelques idées pour des fonctionnalités supplémentaires mais ça risque de ne pas tenir.

    Il reste la possibilité d'optimiser le code. (J'utilise le c++ Arduino, les librairies également)

    Dans la librairie Ethernet, j'ai pu gagner 600 octets en supprimant le code liée aux puces différentes de celle que j'utilise (la W5500)
    De même, il faut optimiser ses algorithmes et nettoyer son code.

    La question que je me pose c'est comment optimiser le code en lui même pour sauver de la ROM.

    Avant de me mettre aux Arduino j'ai programmé en assembleur 8051 une puce AT89C2051.
    Seulement 2Ko de ROM et 128 octets de RAM, mais l'assembleur permet de faire un code très compact :
    - utilisation maximale des registres (j'ai du me faire un tableau excel pour noter quels fonctions utilisaient quels registres pour éviter les "collisions")
    - que des variables globales (pareil même principe un tableau excel pour noter quels fonctions utilisaient quelles variables en RAM pour éviter les "collisions")
    - fonctions "imbriquées" : en assembleur les fonctions c'est un goto avec un return... du coup on peut faire ça (pseudo code) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    EtiquetteFonction1:
    ...
    ...
    EtiquetteFonction1b:
    ...
    ...
    EtiquetteFonction1c:
    ...
    ...
    Return
    Cela permet d'éviter :
    - de faire trois fonctions avec une partie de code redondant
    - de faire une fonction avec un paramètre et un switch...case

    Cette astuce m'a permis de gagner beaucoup de place dans mon programme assembleur.

    Revenons à l'Arduino...

    Refaire tout mon programme en Assembleur n'est pas forcément une bonne idée :
    - très complexe, surtout qu'il faut aussi s'attaquer aux librairies
    - l'Atmega328 a beaucoup de registres et d'après ce qu'on dit face à un excellent compilateur un être humain n'est pas meilleur
    - il est illusoire de vouloir optimiser le code assembleur généré par le compilateur, c'est de la bouillie par rapport à un code assembleur écrit par un humain

    Cependant je pense que les "facilités" du C++ doivent consommer beaucoup de ressources. Je pense notamment :
    - aux variables locales et paramètres de fonctions
    - aux types de données évolués (classes, ...)
    - aux multiples appels de fonction
    - aux types de données : en assembleur on manipule des octets, en C++ on manipule des types de données et il faut parfois faire des casts

    La question que je me pose c'est comment écrire un code C++ qui génère un binaire plus compact, en déterminant des règles à respecter :
    - utiliser au maximum des variables globales, si possible très peu ou aucune variable locale, de même, limiter les paramètres passés à une fonction
    - éviter l'imbrication de multiples petites fonctions / voir si mon astuce est possible
    - éviter l'utilisateur de classe si une seule instance est utilisées (typiquement : Ethernet Server)

    Qu'en pensez-vous ?

    Il y a aussi la RAM.
    Même sans utiliser la classe String, il faut garder assez de RAM disponible pour la pile et les variables locales...
    Mais c'est complètement pifométrique : impossible de savoir combien de RAM disponible donc :
    - soit on gâche beaucoup de RAM
    - soit on prend le risque d'un plantage.
    Pour optimiser l'usage de la RAM :
    - les variables locales : si on en utilise peut et qu'on épluche tout le code (libairies comprises) on peut savoir ce qu'il faut.
    - la pile c'est plus difficile, il faut là aussi voir quelles fonctions utilisent la pile.
    C'est mission impossible si il y a des dizaines de fonctions qui s'appellent les unes les autres, et bien sûr si il y a de la récursivité (mais qui ferai des fonctions récursive sur Arduino ?)
    En assembleur 8051, je n'utilisait aucune variable locale et très peu la pile, je pouvait donc utiliser ma RAM à l'octet près.

    Mon but n'est pas de passer des mois à trouver l'optimisation ultime, mais, plus pragmatiquement, de trouver une façon faisable de réécrire le code pour gagner de la ROM.

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  2. #2
    Modérateur

    Homme Profil pro
    Ingénieur électricien
    Inscrit en
    Septembre 2008
    Messages
    1 267
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 267
    Points : 4 829
    Points
    4 829
    Par défaut
    Bonjour

    Quelques points pour la ROM:
    - Limiter les chaînes de caractères constantes ou variables, cela prend de la place autant en ROM que en RAM.
    - Utiliser systématique le stockage en mémoire programme pour toutes les constantes dès le moment où de grandes constantes (chaines de caractères, tableaux) sont présentes dans le code, cela divise par 2 l'usage de la ROM, mais ajout le code de récupération, et cela nécessite une gestion intelligente des variables.
    - Ne faire usage de la POO que de manière statique. Peu d'héritage et surtout aucune méthode volatile. L'appel à une fonction de classe doit pouvoir être résolu par le compilateur et non à l’exécution. Si les classes sont intelligemment conçues, elles ne surcharge pas le programme. Par contre le compilateur C++ serait moins bien optimisé que le compilateur C sur AVR du fait de l'usage plus étendu de ce dernier d'après quelques discussions que j'avais lues sur le forum AVR anglophone.
    - Faire attention au niveau d'optimisation. En O2 et O3 le code est dépilé et répété, cela gagne en vitesse mais prend plus de ROM. Laisser en O1 pour limiter l'usage de la ROM.

    Pour la RAM, il faut surtout limiter la taille des variables à ce qui est nécessaire (par pas de 8 bits, inutile de prendre un Int si c'est pour compter jusqu'à 10). Si on a plusieurs booléen, il ne faut pas hésiter à les grouper par 8. (Par variable et usage des masques ou par champs de bits). Utiliser des variables globales ou statiques.

    On peut laisser tomber beaucoup de librairies, on peut aussi laisser tomber les strings dans beaucoup de cas (mais c'est plus de programmation à faire soi-même).

    Et je confirme, il est illusoire de vouloir reprendre le code ASM généré par un compilateur. Mais programmer en ASM n'est pas si compliqué que cela quand on a appris à le faire.

    Bonne suite

    Delias

  3. #3
    Modérateur

    Homme Profil pro
    Ingénieur électricien
    Inscrit en
    Septembre 2008
    Messages
    1 267
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 267
    Points : 4 829
    Points
    4 829
    Par défaut
    Bonsoir à tous

    Un petit complément que j'ai oublié ce matin.
    La pire optimisation c'est le langage Arduino en lui même et ses fonctions de base pinMode(), digitalRead(), digitalWrite().
    De 1 ou 2 instructions en programmation C ou C++ qui chacune fait 2 octets de ROM et 2 cycles d’exécution (mais sans la couche Arduino) on passe à une fonction nécessitant plusieurs dizaines d'octets de ROM pour une table de mappage, des variables et plus de 50 cycles pour être exécuté. La flexibilité d'avoir le N° de la pin en variable et la sécurité de code se paie très très cher.
    C'est éventuellement un peu mieux du côté de analogRead() et analogWrite(), mais le coût de la flexibilité doit se payer encore très cher. (J'avais analysé le code des digital I/O, mais pas celui des Analog I/O).

    Donc la meilleure optimisation c'est d'oublier Arduino et programmer directement en C ou en C++ comme la série de tuto de Francesco Balducci et traduit par f-leb: Arduino et le langage C

    Ayant appris à programmer les AVR d'abord en ASM puis en C, je n'ai jamais compris le délire d'Arduino à ce sujet. Cela casse les performances juste pour offrir une offuscation du fonctionnement d'un micro et donner l'impression que c'est simple.

    Bonne suite

    Delias

  4. #4
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Citation Envoyé par Delias Voir le message
    La pire optimisation c'est le langage Arduino en lui même et ses fonctions de base pinMode(), digitalRead(), digitalWrite().
    ...
    Donc la meilleure optimisation c'est d'oublier Arduino et programmer directement en C ou en C++
    ....
    je n'ai jamais compris le délire d'Arduino à ce sujet. Cela casse les performances juste pour offrir une offuscation du fonctionnement d'un micro et donner l'impression que c'est simple.
    ...
    Pour être précis arduino n’est pas un langage, vous faites référence à des API, une bibliothèque de fonctions ou classes qui effectivement ont pour objet de baisser la barrière à l’entrée. Le langage c’est bien toujours le C++

    Je vous engage à regarder comment on fait un analogRead() ou digitalRead() sur MKR ou ESP. Vous verrez que ce n’est pas aussi simple que lire ou écrire un seul registre. L’avantage des fonctions fournies par Arduino (et je suis d’accord quelles sont peu optimisées) c’est que l’utilisateur n’a pas à se soucier de ce qui se passe sous le capot en changeant de plateforme.

    Pour ceux d’entre nous qui comprennent la programmation en détail ou recherchent la performance sans passer à un modèle plus puissant nous pouvons nous rapprocher du hardware Pour en tirer la quintessence. Mais pour ceux qui n’ont pas cette connaissance et qui veulent simplement s’amuser un peu ces fonctions simplifient leur vie et souvent la performance n’est pas un problème.

    Mais Sur le fond oui, se passer de certaines fonctions qui simplifient la vie du programmeur au détriment de la performance ou place mémoire est une avenue à explorer si on ne veut pas changer de plateforme

  5. #5
    Modérateur

    Homme Profil pro
    Ingénieur électricien
    Inscrit en
    Septembre 2008
    Messages
    1 267
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 267
    Points : 4 829
    Points
    4 829
    Par défaut
    Bonjour Jay

    Citation Envoyé par Jay M Voir le message
    Pour être précis arduino n’est pas un langage, vous faites référence à des API, une bibliothèque de fonctions ou classes qui effectivement ont pour objet de baisser la barrière à l’entrée. Le langage c’est bien toujours le C++
    Et pourtant même l'IEEE parle bien de langage Arduino, langage au sens large, je venais justement de le lire dans la news DVP: Python domine le classement IEEE Spectrum de juillet 2020.

    Bonne journée

    Delias

  6. #6
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Salut

    Oui je comprends (ce que je vois comme ) l’abus de langage et pourquoi on peut faire cette extension. D’ailleurs l’article que vous citez reconnaît que les puristes n’acceptent pas cette définition.

    Dans la publication en anglais ils disent

    Purists may argue that Arduino is not a language but rather a hardware platform that is programmed using a derivative of Wiring, which itself is derived from C/C++. But we have always taken a very pragmatic approach to our definition of “programming language,” and the reality is that when people are looking to use an Arduino-compatible microcontroller, they typically search for “Arduino code” or buy books about “Arduino programming,” not “Wiring code” or “C programming.”
    La traduction française a repris le «*we*» en la traduisant par IEEE alors que c’est l’opinion du magazine pour cette étude.

    En effet IEEE Spectrum est un magazine généraliste, les articles tentent d'être accessibles aux non-spécialistes et les articles n’ont pas de validation normative par IEEE.


    Mon point de vue c’est surtout que le compilateur utilisé c’est GCC (avr-gcc et autres GCC pour les autres microprocesseurs ) et donc le langage sous jacent est ce que le compilateur supporte.

    L’IDE rajoute sa sur couche (génération du main, injection de code etc) mais ça reste du C++

    Mais bon comme dit plus haut - je comprends que l’on puisse l’appeler language - mon esprit «*puriste*» cependant se rebelle et trouve que ça pique les yeux

  7. #7
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Bonjour,

    Merci pour toutes ses réponses bien argumentées

    Petite remarque : l'IDE Arduino utilise par défaut l'option de compilation "-Os" pour générer le code le plus compact possible, au détriment de la vitesse.

    Si je résume : Arduino n'est pas vraiment un langage, c'est du C++ "de base" auquel est ajouté un ensemble de librairies pour
    - rendre la programmation plus accessible
    - rendre le code compatible avec le plus de cartes possibles sans devoir y faire de grosses modifications

    Mais ces avantages se payent avec un code plus long et moins optimisé.
    Ainsi il faut éviter la classe String, mais il faudrait aussi éviter (ou optimiser) d'autres fonctions comme WritePin...

    Question à la con : puis-je faire les optimisations requises en restant avec l'environnement de développement Arduino ?

    Cela impliquerait que je fasse des fork des librairies que j'utilise, y compris celles de base incluses par défaut (Arduino.h), et je ne sais pas si cela est possible.

    Ou alors dois-je laisser tomber l'IDE Arduino et écrire mon code C/C++ avec Notepad++ et utiliser les compilateurs en ligne de commande ?

    A noter que le compilateur n'inclue pas dans le fichier binaire final les fonctions qui ne sont jamais appelées par le code.
    Aussi, si les fonctions sous-performantes ne pas appelées par mon code ni par aucune des fonctions des librairies utilisées par mon code, je n'ai pas besoin de faire une fork pour les supprimer.
    Existe-t-il un moyen de savoir quel code a été ainsi "éliminé" par le compilateur ? Ou à défaut un moyen de savoir quel code a été "inclus" ?

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  8. #8
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Citation Envoyé par electroremy Voir le message
    Question à la con : puis-je faire les optimisations requises en restant avec l'environnement de développement Arduino ?
    Aucune question n'est "à la con"
    bien sûr, il suffit de ne pas appeler ces fonctions et d'écrire les votres. Pas besoin d'un autre IDE. Vous pouvez même écrire votre propre main() si vous voulez.

    Si vous souhaitez changer les options de compilation cependant, il faut modifier des fichiers à la main au moins une fois ==> lire la "Platform specification". En gros si vous voulez une compilation un peu spéciale de temps en temps, vous créez une nouvelle carte (en dupliquant un truc existant) et vous lui donnez un nouveau nom genre "UNO XXX" et vous modifiez le platform.txt associé. Comme cela en choisissant ce type de carte (ça reste un UNO) quand vous lancez la compilation, ce sont vos nouveaux paramètres qui sont pris en compte. C'est lourd, ça fait des années qu'il y a des demandes de la communauté pour que ce soit facilement rajoutable dans les préférences au lieu d'aller tout éditer...

    Citation Envoyé par electroremy Voir le message
    A noter que le compilateur n'inclut pas dans le fichier binaire final les fonctions qui ne sont jamais appelées par le code.
    Oui et heureusement !! (Arduino.h importe toutes les bibliothèques arduinos)


    Citation Envoyé par electroremy Voir le message
    Aussi, si les fonctions sous-performantes ne pas appelées par mon code ni par aucune des fonctions des librairies utilisées par mon code, je n'ai pas besoin de faire une fork pour les supprimer.
    Existe-t-il un moyen de savoir quel code a été ainsi "éliminé" par le compilateur ? Ou à défaut un moyen de savoir quel code a été "inclus" ?

    Pour explorer ce qui a été généré, vous pouvez utiliser les différents outils qui sont dans ...Arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin

    avr-addr2line avr-c++filt avr-gcc avr-gcc-ranlib avr-gprof avr-nm avr-readelf
    avr-ar avr-cpp avr-gcc-7.3.0 avr-gcov avr-ld avr-objcopy avr-size
    avr-as avr-elfedit avr-gcc-ar avr-gcov-dump avr-ld.bfd avr-objdump avr-strings
    avr-c++ avr-g++ avr-gcc-nm avr-gcov-tool avr-man avr-ranlib avr-strip



    par exemple si vous prenez le code de démo toneMelody.ino et que vous compilez avec les préférences qui vous montrent tout ce qu'il se passe, vous verrez qu'il y a création d'un répertoire temporaire dans lequel votre sketch et tout ce qu'il faut est copié et la compilation s'effectue.

    sur mon mac ce répertoire temporaire est /var/folders/96/rsjd0mkd3xn0whm5622w0f500000gn/T/arduino_build_660909 et on voit le .o de la compilation ainsi que un .elf et un .hex

    Nom : objets.png
Affichages : 1621
Taille : 84,4 Ko

    Si je donne un coup de avr-gcc-nm, ça va lister les symboles du ficher (sur le .elf)

    ...Library/Arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-gcc-nm -A toneMelody.ino.elf vous verrez les symboles suivants
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    toneMelody.ino.elf:000000e4 t .do_clear_bss_loop
    toneMelody.ino.elf:000000e6 t .do_clear_bss_start
    toneMelody.ino.elf:         w _Z14serialEventRunv
    toneMelody.ino.elf:000000f8 t _Z6noToneh
    toneMelody.ino.elf:000000b8 t _ZL21tone_pin_to_timer_PGM
    toneMelody.ino.elf:00800100 d _ZL9tone_pins
    toneMelody.ino.elf:0000ffa0 A __DATA_REGION_LENGTH__
    toneMelody.ino.elf:00800060 A __DATA_REGION_ORIGIN__
    toneMelody.ino.elf:00010000 A __EEPROM_REGION_LENGTH__
    toneMelody.ino.elf:00000003 A __FUSE_REGION_LENGTH__
    toneMelody.ino.elf:00000400 A __LOCK_REGION_LENGTH__
    toneMelody.ino.elf:00000400 A __SIGNATURE_REGION_LENGTH__
    toneMelody.ino.elf:0000003e a __SP_H__
    toneMelody.ino.elf:0000003d a __SP_L__
    toneMelody.ino.elf:0000003f a __SREG__
    toneMelody.ino.elf:00020000 A __TEXT_REGION_LENGTH__
    toneMelody.ino.elf:00000400 A __USER_SIGNATURE_REGION_LENGTH__
    toneMelody.ino.elf:000000f4 T __bad_interrupt
    toneMelody.ino.elf:0080013c B __bss_end
    toneMelody.ino.elf:00800122 B __bss_start
    toneMelody.ino.elf:000000ba T __ctors_end
    toneMelody.ino.elf:000000ba T __ctors_start
    toneMelody.ino.elf:00800122 D __data_end
    toneMelody.ino.elf:00000c84 A __data_load_end
    toneMelody.ino.elf:00000c62 A __data_load_start
    toneMelody.ino.elf:00800100 D __data_start
    toneMelody.ino.elf:00000b60 T __divmodhi4
    toneMelody.ino.elf:00000b86 t __divmodhi4_exit
    toneMelody.ino.elf:00000b78 t __divmodhi4_neg1
    toneMelody.ino.elf:00000b80 t __divmodhi4_neg2
    toneMelody.ino.elf:00000bcc T __divmodsi4
    toneMelody.ino.elf:00000bf8 t __divmodsi4_exit
    toneMelody.ino.elf:00000bea t __divmodsi4_neg2
    toneMelody.ino.elf:000000dc T __do_clear_bss
    toneMelody.ino.elf:000000c6 T __do_copy_data
    toneMelody.ino.elf:000000ba T __dtors_end
    toneMelody.ino.elf:000000ba T __dtors_start
    toneMelody.ino.elf:00810000 N __eeprom_end
    toneMelody.ino.elf:000008fe T __fixsfsi
    toneMelody.ino.elf:0000090c T __fixunssfsi
    toneMelody.ino.elf:0000096e T __floatsisf
    toneMelody.ino.elf:0000096a T __floatunsisf
    toneMelody.ino.elf:00000b10 T __fp_inf
    toneMelody.ino.elf:00000b1c T __fp_nan
    toneMelody.ino.elf:00000b22 T __fp_pscA
    toneMelody.ino.elf:00000b30 T __fp_pscB
    toneMelody.ino.elf:00000b3e T __fp_round
    toneMelody.ino.elf:000009e4 T __fp_split3
    toneMelody.ino.elf:000009f4 T __fp_splitA
    toneMelody.ino.elf:00000a2a T __fp_szero
    toneMelody.ino.elf:00000a28 T __fp_zero
    toneMelody.ino.elf:00000000 W __heap_end
    toneMelody.ino.elf:000000ba W __init
    toneMelody.ino.elf:00000a36 T __mulsf3
    toneMelody.ino.elf:00000a62 T __mulsf3_pse
    toneMelody.ino.elf:00000a5c T __mulsf3x
    toneMelody.ino.elf:00000bfa T __negsi2
    toneMelody.ino.elf:000008ff W __stack
    toneMelody.ino.elf:00000c60 t __stop_program
    toneMelody.ino.elf:00000000 a __tmp_reg__
    toneMelody.ino.elf:00000068 T __trampolines_end
    toneMelody.ino.elf:00000068 T __trampolines_start
    toneMelody.ino.elf:00000c18 T __udivmodhi4
    toneMelody.ino.elf:00000c2e t __udivmodhi4_ep
    toneMelody.ino.elf:00000c20 t __udivmodhi4_loop
    toneMelody.ino.elf:00000b88 T __udivmodsi4
    toneMelody.ino.elf:00000bae t __udivmodsi4_ep
    toneMelody.ino.elf:00000b94 t __udivmodsi4_loop
    toneMelody.ino.elf:00000c40 T __umulhisi3
    toneMelody.ino.elf:00000c0a T __usmulhisi3
    toneMelody.ino.elf:00000c0e T __usmulhisi3_tail
    toneMelody.ino.elf:000000f4 W __vector_1
    toneMelody.ino.elf:000000f4 W __vector_10
    toneMelody.ino.elf:000000f4 W __vector_11
    toneMelody.ino.elf:000000f4 W __vector_12
    toneMelody.ino.elf:000000f4 W __vector_13
    toneMelody.ino.elf:000000f4 W __vector_14
    toneMelody.ino.elf:000000f4 W __vector_15
    toneMelody.ino.elf:000002e6 T __vector_16
    toneMelody.ino.elf:000000f4 W __vector_17
    toneMelody.ino.elf:000000f4 W __vector_18
    toneMelody.ino.elf:000000f4 W __vector_19
    toneMelody.ino.elf:000000f4 W __vector_2
    toneMelody.ino.elf:000000f4 W __vector_20
    toneMelody.ino.elf:000000f4 W __vector_21
    toneMelody.ino.elf:000000f4 W __vector_22
    toneMelody.ino.elf:000000f4 W __vector_23
    toneMelody.ino.elf:000000f4 W __vector_24
    toneMelody.ino.elf:000000f4 W __vector_25
    toneMelody.ino.elf:000000f4 W __vector_3
    toneMelody.ino.elf:000000f4 W __vector_4
    toneMelody.ino.elf:000000f4 W __vector_5
    toneMelody.ino.elf:000000f4 W __vector_6
    toneMelody.ino.elf:0000022e T __vector_7
    toneMelody.ino.elf:000000f4 W __vector_8
    toneMelody.ino.elf:000000f4 W __vector_9
    toneMelody.ino.elf:00000000 W __vector_default
    toneMelody.ino.elf:00000000 T __vectors
    toneMelody.ino.elf:00000001 a __zero_reg__
    toneMelody.ino.elf:00000b60 T _div
    toneMelody.ino.elf:00800122 D _edata
    toneMelody.ino.elf:0080013c N _end
    toneMelody.ino.elf:00000c62 T _etext
    toneMelody.ino.elf:00000c5e T _exit
    toneMelody.ino.elf:00000086 t digital_pin_to_bit_mask_PGM
    toneMelody.ino.elf:000000a4 t digital_pin_to_port_PGM
    toneMelody.ino.elf:00000068 t digital_pin_to_timer_PGM
    toneMelody.ino.elf:00000c5e W exit
    toneMelody.ino.elf:0000037a T main
    toneMelody.ino.elf:00800101 d melody
    toneMelody.ino.elf:000001e4 t micros
    toneMelody.ino.elf:00800111 d noteDurations
    toneMelody.ino.elf:0000007c t port_to_mode_PGM
    toneMelody.ino.elf:0000009a t port_to_output_PGM
    toneMelody.ino.elf:00800122 b timer0_fract
    toneMelody.ino.elf:00800123 b timer0_millis
    toneMelody.ino.elf:00800127 b timer0_overflow_count
    toneMelody.ino.elf:0080012f b timer0_pin_mask
    toneMelody.ino.elf:00800138 b timer0_toggle_count
    toneMelody.ino.elf:0080012e b timer1_pin_mask
    toneMelody.ino.elf:00800134 b timer1_toggle_count
    toneMelody.ino.elf:0080012b b timer2_pin_mask
    toneMelody.ino.elf:0080012c b timer2_pin_port
    toneMelody.ino.elf:00800130 b timer2_toggle_count
    si vous rajoutez l'option -g vous ne verrez que les symboles externes.

    si vous faites un .../avr-size toneMelody.ino.elf vous verrez les tailles des segments
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
       text	   data	    bss	    dec	    hex	filename
       3170	     34	     26	   3230	    c9e	toneMelody.ino.elf
    avr-readelf vous donnera aussi plein d'infos suivant les flags utilisés

    avr-objdump -d vous donnera carrément le langage machine ou un -DS vous montrera à la fois le langage machine et le code source

  9. #9
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Merci beaucoup pour ces indications détaillées

    J'ai dupliqué les librairies que j'utilise sous un autre nom dans le sous dossier "libraries" du répertoire où sont mes croquis pour faire mes propres "fork" optimisées sans altérer les originaux.

    J'ai remarqué qu'il y a quelques libraires dans "...\arduino-1.8.12\hardware\arduino\avr\libraries\" ; puis-je les dupliquer de la même façon ?

    Il y a aussi pas mal de fichiers dans le dossier "...\arduino-1.8.12\hardware\arduino\avr\cores\arduino\" qui sont utilisés. Dans le cas de figure où je souhaiterais en optimiser un, comment le "forker" sans endommager l'original ?

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  10. #10
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Vous ne pouvez pas toucher au core en copie, faut modifier le core...
    Je suis curieux cependant de ce que vous voudriez changer?

    Si c’est dans une fonction, n’appelez pas celle ci, créez la votre. Éventuellement en dupliquant et modifiant celle du core sous un autre nom

    Pour les bibliothèques pas la peine de changer de nom, mettez une copie que vous modifierez dans votre répertoire de travail (ou il y a le .ino) et incluez les avec des guillemets’ pas <>.

  11. #11
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Citation Envoyé par Jay M Voir le message
    Pour les bibliothèques pas la peine de changer de nom, mettez une copie que vous modifierez dans votre répertoire de travail (ou il y a le .ino) et incluez les avec des guillemets’ pas <>.
    Merci je ne connaissais pas cette astuce

    Citation Envoyé par Jay M Voir le message
    Vous ne pouvez pas toucher au core en copie, faut modifier le core...
    Je suis curieux cependant de ce que vous voudriez changer?
    De l'optimisation, en simplifiant le code par rapport à ce que j'utilise. Certaines fonctions ont pas mal de paramètres alors qu'elles sont appellées qu'avec toujours les mêmes... Du coup il y a pas mal de code inutile dans la fonction et le compilateur ne peut pas forcément toujours le savoir.

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  12. #12
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Citation Envoyé par electroremy Voir le message
    Merci je ne connaissais pas cette astuce
    De l'optimisation, en simplifiant le code par rapport à ce que j'utilise. Certaines fonctions ont pas mal de paramètres alors qu'elles sont appellées qu'avec toujours les mêmes... Du coup il y a pas mal de code inutile dans la fonction et le compilateur ne peut pas forcément toujours le savoir.
    OK - mais quand vous allez taper dans le core, faut être sûr que ces fonctions ne sont pas appelées par d'autres parties du core...


    Certaines fonctions ont pas mal de paramètres alors qu'elles sont appellées qu'avec toujours les mêmes
    si c'est pour les déclarer en variable locales dans la fonction et ne pas les mettre en paramètre ça ne vous fait rien gagner en mémoire, dans les deux cas elles finissent sur la pile.

    avant d'aller bidouiller le core êtes vous sûr d'avoir optimisé votre code, puis ensuite celui des bibliothèques dont vous dépendez (tierces parties) avant d'aller éventuellement toucher celles standards fournies avec l'IDE. Modifier le core fait que vous êtes coincé dans une version et qu'à chaque nouvelle version de l'IDE ou correction de bug, vous devez refaire vos modifs...

  13. #13
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Citation Envoyé par Jay M Voir le message
    avant d'aller bidouiller le core êtes vous sûr d'avoir optimisé votre code, puis ensuite celui des bibliothèques dont vous dépendez (tierces parties) avant d'aller éventuellement toucher celles standards fournies avec l'IDE. Modifier le core fait que vous êtes coincé dans une version et qu'à chaque nouvelle version de l'IDE ou correction de bug, vous devez refaire vos modifs...
    Très bonne remarque... Oui il faut d'abord optimiser mon code et celui des bibliothèques.

    J'ai commencé à lire ces tutos pour programmer l'Arduino en C :

    https://laboratory-manual-arduino.de...ies-numeriques

    Citation Envoyé par Delias Voir le message
    La pire optimisation c'est le langage Arduino en lui même et ses fonctions de base pinMode(), digitalRead(), digitalWrite().
    De 1 ou 2 instructions en programmation C ou C++ qui chacune fait 2 octets de ROM et 2 cycles d’exécution (mais sans la couche Arduino) on passe à une fonction nécessitant plusieurs dizaines d'octets de ROM pour une table de mappage, des variables et plus de 50 cycles pour être exécuté. La flexibilité d'avoir le N° de la pin en variable et la sécurité de code se paie très très cher.
    Une des bibliothèques que j'utilise (PDQ GFX qui est une fork optimisée d'Adafruit GFX) utilise une bibliothèque "fast pin" dont voici le code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    //
    // This excellent template library is from FastLED http://fastled.io
    //
    // I was considering writing something similar, but FastPin seemed perfect.
    //
    // Thanks FastLED people!  Here is the license from FastLED followed by the
    // header
    //
     
    /*
     
    The MIT License (MIT)
     
    Copyright (c) 2013 FastLED
     
    Permission is hereby granted, free of charge, to any person obtaining a copy of
    this software and associated documentation files (the "Software"), to deal in
    the Software without restriction, including without limitation the rights to
    use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    the Software, and to permit persons to whom the Software is furnished to do so,
    subject to the following conditions:
     
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
     
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     
    */ 
     
    #ifndef __INC_FASTPIN_H
    #define __INC_FASTPIN_H
     
    #include<avr/io.h>
     
    // Arduino.h needed for convinience functions digitalPinToPort/BitMask/portOutputRegister and the pinMode methods.
    #include<Arduino.h>
     
    #define NO_PIN 255 
     
    // Class to ensure that a minimum amount of time has kicked since the last time run - and delay if not enough time has passed yet
    // this should make sure that chipsets that have 
    template<int WAIT> class CMinWait {
    	long mLastMicros;
    public:
    	CMinWait() { mLastMicros = 0; }
     
    	void wait() { 
    		long diff = micros() - mLastMicros;
    		while(diff < WAIT) { 
    			diff = micros() - mLastMicros;
    		}
    	}
     
    	void mark() { mLastMicros = micros(); }
    };
     
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Pin access class - needs to tune for various platforms (naive fallback solution?)
    //
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     
    #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    #define _CYCLES(_PIN) (((_PIN >= 62 ) || (_PIN>=42 && _PIN<=49) || (_PIN>=14 && _PIN <=17) || (_PIN>=6 && _PIN <=9)) ? 2 : 1)
    #else
    #define _CYCLES(_PIN) ((_PIN >= 24) ? 2 : 1)
    #endif
     
    class Selectable {
    public:
    	virtual void select() = 0;
    	virtual void release() = 0;
    	virtual bool isSelected() = 0;
    };
     
    class Pin : public Selectable { 
    	uint8_t mPinMask;
    	uint8_t mPin;
    	volatile uint8_t *mPort;
     
    	void _init() { 
    		mPinMask = digitalPinToBitMask(mPin);
    		mPort = portOutputRegister(digitalPinToPort(mPin));
    	}
    public:
    	Pin(int pin) : mPin(pin) { _init(); }
     
    	typedef volatile uint8_t * port_ptr_t;
    	typedef uint8_t port_t;
     
    	inline void setOutput() { pinMode(mPin, OUTPUT); }
    	inline void setInput() { pinMode(mPin, INPUT); }
     
    	inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; } 
    	inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; }
     
    	inline void strobe() __attribute__ ((always_inline)) { hi(); lo(); }
     
    	inline void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; } 
    	inline void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; } 
    	inline void set(register port_t val) __attribute__ ((always_inline)) { *mPort = val; }
     
    	inline void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port  = val; }
     
    	port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask;  }
    	port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; }
    	port_ptr_t  port() __attribute__ ((always_inline)) { return mPort; }
    	port_t mask() __attribute__ ((always_inline)) { return mPinMask; }
     
    	virtual void select() { hi(); }
    	virtual void release() { lo(); }
    	virtual bool isSelected() { return (*mPort & mPinMask) == mPinMask; }
    };
     
    class OutputPin : public Pin {
    public:
    	OutputPin(int pin) : Pin(pin) { setOutput(); }
    };
     
    class InputPin : public Pin {
    public:
    	InputPin(int pin) : Pin(pin) { setInput(); }
    };
     
    /// The simplest level of Pin class.  This relies on runtime functions durinig initialization to get the port/pin mask for the pin.  Most
    /// of the accesses involve references to these static globals that get set up.  This won't be the fastest set of pin operations, but it
    /// will provide pin level access on pretty much all arduino environments.  In addition, it includes some methods to help optimize access in
    /// various ways.  Namely, the versions of hi, lo, and fastset that take the port register as a passed in register variable (saving a global
    /// dereference), since these functions are aggressively inlined, that can help collapse out a lot of extraneous memory loads/dereferences.
    /// 
    /// In addition, if, while writing a bunch of data to a pin, you know no other pins will be getting written to, you can get/cache a value of
    /// the pin's port register and use that to do a full set to the register.  This results in one being able to simply do a store to the register,
    /// vs. the load, and/or, and store that would be done normally.
    ///
    /// There are platform specific instantiations of this class that provide direct i/o register access to pins for much higher speed pin twiddling.
    ///
    /// Note that these classes are all static functions.  So the proper usage is Pin<13>::hi(); or such.  Instantiating objects is not recommended, 
    /// as passing Pin objects around will likely -not- have the effect you're expecting.
    template<uint8_t PIN> class FastPin { 
    	static uint8_t sPinMask;
    	static volatile uint8_t *sPort;
    	static void _init() { 
    		sPinMask = digitalPinToBitMask(PIN);
    		sPort = portOutputRegister(digitalPinToPort(PIN));
    	}
    public:
    	typedef volatile uint8_t * port_ptr_t;
    	typedef uint8_t port_t;
     
    	inline static void setOutput() { _init(); pinMode(PIN, OUTPUT); }
    	inline static void setInput() { _init(); pinMode(PIN, INPUT); }
     
    	inline static void hi() __attribute__ ((always_inline)) { *sPort |= sPinMask; } 
    	inline static void lo() __attribute__ ((always_inline)) { *sPort &= ~sPinMask; }
     
    	inline static void strobe() __attribute__ ((always_inline)) { hi(); lo(); }
     
    	inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port |= sPinMask; } 
    	inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~sPinMask; } 
    	inline static void set(register port_t val) __attribute__ ((always_inline)) { *sPort = val; }
     
    	inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port  = val; }
     
    	static port_t hival() __attribute__ ((always_inline)) { return *sPort | sPinMask;  }
    	static port_t loval() __attribute__ ((always_inline)) { return *sPort & ~sPinMask; }
    	static port_ptr_t  port() __attribute__ ((always_inline)) { return sPort; }
    	static port_t mask() __attribute__ ((always_inline)) { return sPinMask; }
    };
     
    template<uint8_t PIN> uint8_t FastPin<PIN>::sPinMask;
    template<uint8_t PIN> volatile uint8_t *FastPin<PIN>::sPort;
     
    /// Class definition for a Pin where we know the port registers at compile time for said pin.  This allows us to make
    /// a lot of optimizations, as the inlined hi/lo methods will devolve to a single io register write/bitset.  
    template<uint8_t PIN, uint8_t _MASK, typename _PORT, typename _DDR, typename _PIN> class _AVRPIN { 
    public:
    	typedef volatile uint8_t * port_ptr_t;
    	typedef uint8_t port_t;
     
    	inline static void setOutput() { _DDR::r() |= _MASK; }
    	inline static void setInput() { _DDR::r() &= ~_MASK; }
     
    	inline static void hi() __attribute__ ((always_inline)) { _PORT::r() |= _MASK; }
    	inline static void lo() __attribute__ ((always_inline)) { _PORT::r() &= ~_MASK; }
    	inline static void set(register uint8_t val) __attribute__ ((always_inline)) { _PORT::r() = val; }
     
    	inline static void strobe() __attribute__ ((always_inline)) { hi(); lo(); }
     
    	inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); }
    	inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); }
    	inline static void fastset(register port_ptr_t port, register uint8_t val) __attribute__ ((always_inline)) { set(val); }
     
    	inline static port_t hival() __attribute__ ((always_inline)) { return _PORT::r() | _MASK; }
    	inline static port_t loval() __attribute__ ((always_inline)) { return _PORT::r() & ~_MASK; }
    	inline static port_ptr_t port() __attribute__ ((always_inline)) { return &_PORT::r(); }
    	inline static port_t mask() __attribute__ ((always_inline)) { return _MASK; }
    };
     
    /// Template definition for teensy 3.0 style ARM pins, providing direct access to the various GPIO registers.  Note that this
    /// uses the full port GPIO registers.  In theory, in some way, bit-band register access -should- be faster, however I have found
    /// that something about the way gcc does register allocation results in the bit-band code being slower.  It will need more fine tuning.
    template<uint8_t PIN, uint32_t _MASK, typename _PDOR, typename _PSOR, typename _PCOR, typename _PTOR, typename _PDIR, typename _PDDR> class _ARMPIN { 
    public:
    	typedef volatile uint32_t * port_ptr_t;
    	typedef uint32_t port_t;
     
    	inline static void setOutput() { pinMode(PIN, OUTPUT); } // TODO: perform MUX config { _PDDR::r() |= _MASK; }
    	inline static void setInput() { pinMode(PIN, INPUT); } // TODO: preform MUX config { _PDDR::r() &= ~_MASK; }
     
    	inline static void hi() __attribute__ ((always_inline)) { _PSOR::r() = _MASK; }
    	inline static void lo() __attribute__ ((always_inline)) { _PCOR::r() = _MASK; }
    	inline static void set(register port_t val) __attribute__ ((always_inline)) { _PDOR::r() = val; }
     
    	inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
     
    	inline static void toggle() __attribute__ ((always_inline)) { _PTOR::r() = _MASK; }
     
    	inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); }
    	inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); }
    	inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; }
     
    	inline static port_t hival() __attribute__ ((always_inline)) { return _PDOR::r() | _MASK; }
    	inline static port_t loval() __attribute__ ((always_inline)) { return _PDOR::r() & ~_MASK; }
    	inline static port_ptr_t port() __attribute__ ((always_inline)) { return &_PDOR::r(); }
    	inline static port_t mask() __attribute__ ((always_inline)) { return _MASK; }
    };
     
    /// Template definition for teensy 3.0 style ARM pins using bit banding, providing direct access to the various GPIO registers.  GCC 
    /// does a poor job of optimizing around these accesses so they are not being used just yet.
    template<uint8_t PIN, int _BIT, typename _PDOR, typename _PSOR, typename _PCOR, typename _PTOR, typename _PDIR, typename _PDDR> class _ARMPIN_BITBAND { 
    public:
    	typedef volatile uint32_t * port_ptr_t;
    	typedef uint32_t port_t;
     
    	inline static void setOutput() { pinMode(PIN, OUTPUT); } // TODO: perform MUX config { _PDDR::r() |= _MASK; }
    	inline static void setInput() { pinMode(PIN, INPUT); } // TODO: preform MUX config { _PDDR::r() &= ~_MASK; }
     
    	inline static void hi() __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = 1; }
    	inline static void lo() __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = 0; }
    	inline static void set(register port_t val) __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = val; }
     
    	inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); }
     
    	inline static void toggle() __attribute__ ((always_inline)) { *_PTOR::template rx<_BIT>() = 1; }
     
    	inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port = 1;  }
    	inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port = 0; }
    	inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; }
     
    	inline static port_t hival() __attribute__ ((always_inline)) { return 1; }
    	inline static port_t loval() __attribute__ ((always_inline)) { return 0; }
    	inline static port_ptr_t port() __attribute__ ((always_inline)) { return _PDOR::template rx<_BIT>(); }
    	inline static port_t mask() __attribute__ ((always_inline)) { return 1; }
    };
     
    /// AVR definitions for pins.  Getting around  the fact that I can't pass GPIO register addresses in as template arguments by instead creating
    /// a custom type for each GPIO register with a single, static, aggressively inlined function that returns that specific GPIO register.  A similar
    /// trick is used a bit further below for the ARM GPIO registers (of which there are far more than on AVR!)
    typedef volatile uint8_t & reg8_t;
    #define _R(T) struct __gen_struct_ ## T
    #define _RD8(T) struct __gen_struct_ ## T { static inline reg8_t r() { return T; }};
    #define _IO(L) _RD8(DDR ## L); _RD8(PORT ## L); _RD8(PIN ## L);
    #define _DEFPIN_AVR(PIN, MASK, L) template<> class FastPin<PIN> : public _AVRPIN<PIN, MASK, _R(PORT ## L), _R(DDR ## L), _R(PIN ## L)> {};
     
    // ARM definitions
    #define GPIO_BITBAND_ADDR(reg, bit) (((uint32_t)&(reg) - 0x40000000) * 32 + (bit) * 4 + 0x42000000)
    #define GPIO_BITBAND_PTR(reg, bit) ((uint32_t *)GPIO_BITBAND_ADDR((reg), (bit)))
     
    typedef volatile uint32_t & reg32_t;
    typedef volatile uint32_t * ptr_reg32_t;
     
    #define _RD32(T) struct __gen_struct_ ## T { static __attribute__((always_inline)) inline reg32_t r() { return T; } \
    	template<int BIT> static __attribute__((always_inline)) inline ptr_reg32_t rx() { return GPIO_BITBAND_PTR(T, BIT); } };
    #define _IO32(L) _RD32(GPIO ## L ## _PDOR); _RD32(GPIO ## L ## _PSOR); _RD32(GPIO ## L ## _PCOR); _RD32(GPIO ## L ## _PTOR); _RD32(GPIO ## L ## _PDIR); _RD32(GPIO ## L ## _PDDR);
     
    #define _DEFPIN_ARM(PIN, BIT, L) template<> class FastPin<PIN> : public _ARMPIN<PIN, 1 << BIT, _R(GPIO ## L ## _PDOR), _R(GPIO ## L ## _PSOR), _R(GPIO ## L ## _PCOR), \
    																			_R(GPIO ## L ## _PTOR), _R(GPIO ## L ## _PDIR), _R(GPIO ## L ## _PDDR)> {}; 
     
    // Don't use bit band'd pins for now, the compiler generates far less efficient code around them
    // #define _DEFPIN_ARM(PIN, BIT, L) template<> class Pin<PIN> : public _ARMPIN_BITBAND<PIN, BIT, _R(GPIO ## L ## _PDOR), _R(GPIO ## L ## _PSOR), _R(GPIO ## L ## _PCOR),
    // 																			_R(GPIO ## L ## _PTOR), _R(GPIO ## L ## _PDIR), _R(GPIO ## L ## _PDDR)> {}; 
     
     
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Pin definitions for AVR and ARM.  If there are pin definitions supplied below for the platform being 
    // built on, then much higher speed access will be possible, namely with direct GPIO register accesses.
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    #if defined(FORCE_SOFTWARE_PINS)
    #warning "Softwrae pin support forced pin access will be slightly slower.  See fastpin.h for info."
    #define NO_HARDWARE_PIN_SUPPORT
     
    #elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny45__)
    _IO(B);
     
    _DEFPIN_AVR(0, 0x01, B); _DEFPIN_AVR(1, 0x02, B); _DEFPIN_AVR(2, 0x04, B); _DEFPIN_AVR(3, 0x08, B);
    _DEFPIN_AVR(4, 0x10, B); _DEFPIN_AVR(5, 0x20, B);
     
    #elif(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny25__)
    _IO(A); _IO(B);
     
    _DEFPIN_AVR(0, 0x01, A); _DEFPIN_AVR(1, 0x02, A); _DEFPIN_AVR(2, 0x04, A); _DEFPIN_AVR(3, 0x08, A);
    _DEFPIN_AVR(4, 0x10, A); _DEFPIN_AVR(5, 0x20, A); _DEFPIN_AVR(6, 0x40, A); _DEFPIN_AVR(7, 0x80, A);
    _DEFPIN_AVR(8, 0x04, B); _DEFPIN_AVR(9, 0x02, B); _DEFPIN_AVR(10, 0x01, B);
     
    #elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
    // Accelerated port definitions for arduino avrs
    _IO(D); _IO(B); _IO(C);
    _DEFPIN_AVR( 0, 0x01, D); _DEFPIN_AVR( 1, 0x02, D); _DEFPIN_AVR( 2, 0x04, D); _DEFPIN_AVR( 3, 0x08, D);
    _DEFPIN_AVR( 4, 0x10, D); _DEFPIN_AVR( 5, 0x20, D); _DEFPIN_AVR( 6, 0x40, D); _DEFPIN_AVR( 7, 0x80, D);
    _DEFPIN_AVR( 8, 0x01, B); _DEFPIN_AVR( 9, 0x02, B); _DEFPIN_AVR(10, 0x04, B); _DEFPIN_AVR(11, 0x08, B);
    _DEFPIN_AVR(12, 0x10, B); _DEFPIN_AVR(13, 0x20, B); _DEFPIN_AVR(14, 0x01, C); _DEFPIN_AVR(15, 0x02, C);
    _DEFPIN_AVR(16, 0x04, C); _DEFPIN_AVR(17, 0x08, C); _DEFPIN_AVR(18, 0x10, C); _DEFPIN_AVR(19, 0x20, C);
     
    #define SPI_DATA 11
    #define SPI_CLOCK 13
    #define SPI_SELECT 10
    #define AVR_HARDWARE_SPI
     
    #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    // megas
     
    _IO(A); _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); _IO(G); _IO(H); _IO(J); _IO(K); _IO(L);
     
    _DEFPIN_AVR(0, 1, E); _DEFPIN_AVR(1, 2, E); _DEFPIN_AVR(2, 16, E); _DEFPIN_AVR(3, 32, E); 
    _DEFPIN_AVR(4, 32, G); _DEFPIN_AVR(5, 8, E); _DEFPIN_AVR(6, 8, H); _DEFPIN_AVR(7, 16, H); 
    _DEFPIN_AVR(8, 32, H); _DEFPIN_AVR(9, 64, H); _DEFPIN_AVR(10, 16, B); _DEFPIN_AVR(11, 32, B); 
    _DEFPIN_AVR(12, 64, B); _DEFPIN_AVR(13, 128, B); _DEFPIN_AVR(14, 2, J); _DEFPIN_AVR(15, 1, J); 
    _DEFPIN_AVR(16, 2, H); _DEFPIN_AVR(17, 1, H); _DEFPIN_AVR(18, 8, D); _DEFPIN_AVR(19, 4, D); 
    _DEFPIN_AVR(20, 2, D); _DEFPIN_AVR(21, 1, D); _DEFPIN_AVR(22, 1, A); _DEFPIN_AVR(23, 2, A); 
    _DEFPIN_AVR(24, 4, A); _DEFPIN_AVR(25, 8, A); _DEFPIN_AVR(26, 16, A); _DEFPIN_AVR(27, 32, A); 
    _DEFPIN_AVR(28, 64, A); _DEFPIN_AVR(29, 128, A); _DEFPIN_AVR(30, 128, C); _DEFPIN_AVR(31, 64, C); 
    _DEFPIN_AVR(32, 32, C); _DEFPIN_AVR(33, 16, C); _DEFPIN_AVR(34, 8, C); _DEFPIN_AVR(35, 4, C); 
    _DEFPIN_AVR(36, 2, C); _DEFPIN_AVR(37, 1, C); _DEFPIN_AVR(38, 128, D); _DEFPIN_AVR(39, 4, G); 
    _DEFPIN_AVR(40, 2, G); _DEFPIN_AVR(41, 1, G); _DEFPIN_AVR(42, 128, L); _DEFPIN_AVR(43, 64, L); 
    _DEFPIN_AVR(44, 32, L); _DEFPIN_AVR(45, 16, L); _DEFPIN_AVR(46, 8, L); _DEFPIN_AVR(47, 4, L); 
    _DEFPIN_AVR(48, 2, L); _DEFPIN_AVR(49, 1, L); _DEFPIN_AVR(50, 8, B); _DEFPIN_AVR(51, 4, B); 
    _DEFPIN_AVR(52, 2, B); _DEFPIN_AVR(53, 1, B); _DEFPIN_AVR(54, 1, F); _DEFPIN_AVR(55, 2, F); 
    _DEFPIN_AVR(56, 4, F); _DEFPIN_AVR(57, 8, F); _DEFPIN_AVR(58, 16, F); _DEFPIN_AVR(59, 32, F); 
    _DEFPIN_AVR(60, 64, F); _DEFPIN_AVR(61, 128, F); _DEFPIN_AVR(62, 1, K); _DEFPIN_AVR(63, 2, K); 
    _DEFPIN_AVR(64, 4, K); _DEFPIN_AVR(65, 8, K); _DEFPIN_AVR(66, 16, K); _DEFPIN_AVR(67, 32, K); 
    _DEFPIN_AVR(68, 64, K); _DEFPIN_AVR(69, 128, K); 
     
    #define SPI_DATA 51
    #define SPI_CLOCK 52
    #define SPI_SELECT 53
    #define AVR_HARDWARE_SPI
     
    #elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)
    // Xark: Add ATMega644,644P,1284 and 1284P (using pinout from http://maniacbug.wordpress.com/2011/11/27/arduino-on-atmega1284p-4/)
     
    _IO(A); _IO(B); _IO(C); _IO(D);
     
    _DEFPIN_AVR( 0, (1<<0), B); _DEFPIN_AVR( 1, (1<<1), B); _DEFPIN_AVR( 2, (1<<2), B); _DEFPIN_AVR( 3, (1<<3), B); 
    _DEFPIN_AVR( 4, (1<<4), B); _DEFPIN_AVR( 5, (1<<5), B); _DEFPIN_AVR( 6, (1<<6), B); _DEFPIN_AVR( 7, (1<<7), B); 
     
    _DEFPIN_AVR( 8, (1<<0), D); _DEFPIN_AVR( 9, (1<<1), D); _DEFPIN_AVR(10, (1<<2), D); _DEFPIN_AVR(11, (1<<3), D); 
    _DEFPIN_AVR(12, (1<<4), D); _DEFPIN_AVR(13, (1<<5), D); _DEFPIN_AVR(14, (1<<6), D); _DEFPIN_AVR(15, (1<<7), D); 
     
    _DEFPIN_AVR(16, (1<<0), C); _DEFPIN_AVR(17, (1<<1), C); _DEFPIN_AVR(18, (1<<2), C); _DEFPIN_AVR(19, (1<<3), C); 
    _DEFPIN_AVR(20, (1<<4), C); _DEFPIN_AVR(21, (1<<5), C); _DEFPIN_AVR(22, (1<<6), C); _DEFPIN_AVR(23, (1<<7), C); 
     
    _DEFPIN_AVR(24, (1<<0), A); _DEFPIN_AVR(25, (1<<1), A); _DEFPIN_AVR(26, (1<<2), A); _DEFPIN_AVR(27, (1<<3), A); 
    _DEFPIN_AVR(28, (1<<4), A); _DEFPIN_AVR(29, (1<<5), A); _DEFPIN_AVR(30, (1<<6), A); _DEFPIN_AVR(31, (1<<7), A); 
     
    #define SPI_DATA 5
    #define SPI_CLOCK 7
    #define SPI_SELECT 4
    #define AVR_HARDWARE_SPI
     
    // Leonardo, teensy, blinkm
    #elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY)
    // Leonardo, teensy, blinkm
    #elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY)
     
    // teensy defs
    _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); 
     
    _DEFPIN_AVR(0, 1, B); _DEFPIN_AVR(1, 2, B); _DEFPIN_AVR(2, 4, B); _DEFPIN_AVR(3, 8, B); 
    _DEFPIN_AVR(4, 128, B); _DEFPIN_AVR(5, 1, D); _DEFPIN_AVR(6, 2, D); _DEFPIN_AVR(7, 4, D); 
    _DEFPIN_AVR(8, 8, D); _DEFPIN_AVR(9, 64, C); _DEFPIN_AVR(10, 128, C); _DEFPIN_AVR(11, 64, D); 
    _DEFPIN_AVR(12, 128, D); _DEFPIN_AVR(13, 16, B); _DEFPIN_AVR(14, 32, B); _DEFPIN_AVR(15, 64, B); 
    _DEFPIN_AVR(16, 128, F); _DEFPIN_AVR(17, 64, F); _DEFPIN_AVR(18, 32, F); _DEFPIN_AVR(19, 16, F); 
    _DEFPIN_AVR(20, 2, F); _DEFPIN_AVR(21, 1, F); _DEFPIN_AVR(22, 16, D); _DEFPIN_AVR(23, 32, D); 
     
    #define SPI_DATA 2
    #define SPI_CLOCK 1
    #define SPI_SELECT 3
    #define AVR_HARDWARE_SPI
     
    #elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
    // teensy++ 2 defs
     
    _IO(A); _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); 
     
    _DEFPIN_AVR(0, 1, D); _DEFPIN_AVR(1, 2, D); _DEFPIN_AVR(2, 4, D); _DEFPIN_AVR(3, 8, D); 
    _DEFPIN_AVR(4, 16, D); _DEFPIN_AVR(5, 32, D); _DEFPIN_AVR(6, 64, D); _DEFPIN_AVR(7, 128, D); 
    _DEFPIN_AVR(8, 1, E); _DEFPIN_AVR(9, 2, E); _DEFPIN_AVR(10, 1, C); _DEFPIN_AVR(11, 2, C); 
    _DEFPIN_AVR(12, 4, C); _DEFPIN_AVR(13, 8, C); _DEFPIN_AVR(14, 16, C); _DEFPIN_AVR(15, 32, C); 
    _DEFPIN_AVR(16, 64, C); _DEFPIN_AVR(17, 128, C); _DEFPIN_AVR(18, 64, E); _DEFPIN_AVR(19, 128, E); 
    _DEFPIN_AVR(20, 1, B); _DEFPIN_AVR(21, 2, B); _DEFPIN_AVR(22, 4, B); _DEFPIN_AVR(23, 8, B); 
    _DEFPIN_AVR(24, 16, B); _DEFPIN_AVR(25, 32, B); _DEFPIN_AVR(26, 64, B); _DEFPIN_AVR(27, 128, B); 
    _DEFPIN_AVR(28, 1, A); _DEFPIN_AVR(29, 2, A); _DEFPIN_AVR(30, 4, A); _DEFPIN_AVR(31, 8, A); 
    _DEFPIN_AVR(32, 16, A); _DEFPIN_AVR(33, 32, A); _DEFPIN_AVR(34, 64, A); _DEFPIN_AVR(35, 128, A); 
    _DEFPIN_AVR(36, 16, E); _DEFPIN_AVR(37, 32, E); _DEFPIN_AVR(38, 1, F); _DEFPIN_AVR(39, 2, F); 
    _DEFPIN_AVR(40, 4, F); _DEFPIN_AVR(41, 8, F); _DEFPIN_AVR(42, 16, F); _DEFPIN_AVR(43, 32, F); 
    _DEFPIN_AVR(44, 64, F); _DEFPIN_AVR(45, 128, F); 
     
    #define SPI_DATA 22
    #define SPI_CLOCK 21
    #define SPI_SELECT 20
    #define AVR_HARDWARE_SPI
     
    #elif defined(__AVR_ATmega32U4__)
     
    // leonard defs
    _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); 
     
    _DEFPIN_AVR(0, 4, D); _DEFPIN_AVR(1, 8, D); _DEFPIN_AVR(2, 2, D); _DEFPIN_AVR(3, 1, D); 
    _DEFPIN_AVR(4, 16, D); _DEFPIN_AVR(5, 64, C); _DEFPIN_AVR(6, 128, D); _DEFPIN_AVR(7, 64, E); 
    _DEFPIN_AVR(8, 16, B); _DEFPIN_AVR(9, 32, B); _DEFPIN_AVR(10, 64, B); _DEFPIN_AVR(11, 128, B); 
    _DEFPIN_AVR(12, 64, D); _DEFPIN_AVR(13, 128, C); _DEFPIN_AVR(14, 8, B); _DEFPIN_AVR(15, 2, B); 
    _DEFPIN_AVR(16, 4, B); _DEFPIN_AVR(17, 1, B); _DEFPIN_AVR(18, 128, F); _DEFPIN_AVR(19, 64, F); 
    _DEFPIN_AVR(20, 32, F); _DEFPIN_AVR(21, 16, F); _DEFPIN_AVR(22, 2, F); _DEFPIN_AVR(23, 0, F); 
     
    #define SPI_DATA 16
    #define SPI_CLOCK 15
    #define AVR_HARDWARE_SPI
     
    #elif defined(__MK20DX128__) && defined(CORE_TEENSY)
     
    _IO32(A); _IO32(B); _IO32(C); _IO32(D); _IO32(E);
     
    _DEFPIN_ARM(0, 16, B); _DEFPIN_ARM(1, 17, B); _DEFPIN_ARM(2, 0, D); _DEFPIN_ARM(3, 12, A);
    _DEFPIN_ARM(4, 13, A); _DEFPIN_ARM(5, 7, D); _DEFPIN_ARM(6, 4, D); _DEFPIN_ARM(7, 2, D);
    _DEFPIN_ARM(8, 3, D); _DEFPIN_ARM(9, 3, C); _DEFPIN_ARM(10, 4, C); _DEFPIN_ARM(11, 6, C);
    _DEFPIN_ARM(12, 7, C); _DEFPIN_ARM(13, 5, C); _DEFPIN_ARM(14, 1, D); _DEFPIN_ARM(15, 0, C);
    _DEFPIN_ARM(16, 0, B); _DEFPIN_ARM(17, 1, B); _DEFPIN_ARM(18, 3, B); _DEFPIN_ARM(19, 2, B);
    _DEFPIN_ARM(20, 5, D); _DEFPIN_ARM(21, 6, D); _DEFPIN_ARM(22, 1, C); _DEFPIN_ARM(23, 2, C);
    _DEFPIN_ARM(24, 5, A); _DEFPIN_ARM(25, 19, B); _DEFPIN_ARM(26, 1, E); _DEFPIN_ARM(27, 9, C);
    _DEFPIN_ARM(28, 8, C); _DEFPIN_ARM(29, 10, C); _DEFPIN_ARM(30, 11, C); _DEFPIN_ARM(31, 0, E);
    _DEFPIN_ARM(32, 18, B); _DEFPIN_ARM(33, 4, A);
     
    #define SPI_DATA 11
    #define SPI_CLOCK 13
    #define ARM_HARDWARE_SPI
     
    #elif defined(__SAM3X8E__)
     
    DUE_IO32(A);
    DUE_IO32(B);
    DUE_IO32(C);
    DUE_IO32(D);
     
    _DEFPIN_DUE(0, 8, A); _DEFPIN_DUE(1, 9, A); _DEFPIN_DUE(2, 25, B); _DEFPIN_DUE(3, 28, C);
    _DEFPIN_DUE(4, 26, C); _DEFPIN_DUE(5, 25, C); _DEFPIN_DUE(6, 24, C); _DEFPIN_DUE(7, 23, C);
    _DEFPIN_DUE(8, 22, C); _DEFPIN_DUE(9, 21, C); _DEFPIN_DUE(10, 29, C); _DEFPIN_DUE(11, 7, D);
    _DEFPIN_DUE(12, 8, D); _DEFPIN_DUE(13, 27, B); _DEFPIN_DUE(14, 4, D); _DEFPIN_DUE(15, 5, D);
    _DEFPIN_DUE(16, 13, A); _DEFPIN_DUE(17, 12, A); _DEFPIN_DUE(18, 11, A); _DEFPIN_DUE(19, 10, A);
    _DEFPIN_DUE(20, 12, B); _DEFPIN_DUE(21, 13, B); _DEFPIN_DUE(22, 26, B); _DEFPIN_DUE(23, 14, A);
    _DEFPIN_DUE(24, 15, A); _DEFPIN_DUE(25, 0, D); _DEFPIN_DUE(26, 1, D); _DEFPIN_DUE(27, 2, D);
    _DEFPIN_DUE(28, 3, D); _DEFPIN_DUE(29, 6, D); _DEFPIN_DUE(30, 9, D); _DEFPIN_DUE(31, 7, A);
    _DEFPIN_DUE(32, 10, D); _DEFPIN_DUE(33, 1, C); _DEFPIN_DUE(34, 2, C); _DEFPIN_DUE(35, 3, C);
    _DEFPIN_DUE(36, 4, C); _DEFPIN_DUE(37, 5, C); _DEFPIN_DUE(38, 6, C); _DEFPIN_DUE(39, 7, C);
    _DEFPIN_DUE(40, 8, C); _DEFPIN_DUE(41, 9, C); _DEFPIN_DUE(42, 19, A); _DEFPIN_DUE(43, 20, A);
    _DEFPIN_DUE(44, 19, C); _DEFPIN_DUE(45, 18, C); _DEFPIN_DUE(46, 17, C); _DEFPIN_DUE(47, 16, C);
    _DEFPIN_DUE(48, 15, C); _DEFPIN_DUE(49, 14, C); _DEFPIN_DUE(50, 13, C); _DEFPIN_DUE(51, 12, C);
    _DEFPIN_DUE(52, 21, B); _DEFPIN_DUE(53, 14, B); _DEFPIN_DUE(54, 16, A); _DEFPIN_DUE(55, 24, A);
    _DEFPIN_DUE(56, 23, A); _DEFPIN_DUE(57, 22, A); _DEFPIN_DUE(58, 6, A); _DEFPIN_DUE(59, 4, A);
    _DEFPIN_DUE(60, 3, A); _DEFPIN_DUE(61, 2, A); _DEFPIN_DUE(62, 17, B); _DEFPIN_DUE(63, 18, B);
    _DEFPIN_DUE(64, 19, B); _DEFPIN_DUE(65, 20, B); _DEFPIN_DUE(66, 15, B); _DEFPIN_DUE(67, 16, B);
    _DEFPIN_DUE(68, 1, A); _DEFPIN_DUE(69, 0, A); _DEFPIN_DUE(70, 17, A); _DEFPIN_DUE(71, 18, A);
    _DEFPIN_DUE(72, 30, C); _DEFPIN_DUE(73, 21, A); _DEFPIN_DUE(74, 25, A); _DEFPIN_DUE(75, 26, A);
    _DEFPIN_DUE(76, 27, A); _DEFPIN_DUE(77, 28, A); _DEFPIN_DUE(78, 23, B);
     
    #else
     
    #warning "No pin/port mappings found, pin access will be slightly slower.  See fastpin.h for info."
    #define NO_HARDWARE_PIN_SUPPORT
     
    #endif
     
    #endif
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  14. #14
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Grâce à Notepad++ et sa fonction "rechercher dans tous les documents ouverts" je peux partir à la chasse pour trouver où sont utilisées certaines fonctions dans mon code et les bibliothèques

    digitalWrite n'est utilisé que deux fois par le fichier w5100.h de la bibliothèque Ethernet.h ; et avec un Arduino UNO on passe dans le #if defined(__AVR__) et donc digitalWrite n'est jamais utilisé :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    #if defined(__AVR__)
    	static volatile uint8_t *ss_pin_reg;
    	static uint8_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = portOutputRegister(digitalPinToPort(ss_pin));
    		ss_pin_mask = digitalPinToBitMask(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg) &= ~ss_pin_mask;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg) |= ss_pin_mask;
    	}
    #elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
    	static volatile uint8_t *ss_pin_reg;
    	inline static void initSS() {
    		ss_pin_reg = portOutputRegister(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg+256) = 1;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg+128) = 1;
    	}
    #elif defined(__MKL26Z64__)
    	static volatile uint8_t *ss_pin_reg;
    	static uint8_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = portOutputRegister(digitalPinToPort(ss_pin));
    		ss_pin_mask = digitalPinToBitMask(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg+8) = ss_pin_mask;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg+4) = ss_pin_mask;
    	}
    #elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
    	static volatile uint32_t *ss_pin_reg;
    	static uint32_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = &(digitalPinToPort(ss_pin)->PIO_PER);
    		ss_pin_mask = digitalPinToBitMask(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg+13) = ss_pin_mask;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg+12) = ss_pin_mask;
    	}
    #elif defined(__PIC32MX__)
    	static volatile uint32_t *ss_pin_reg;
    	static uint32_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = portModeRegister(digitalPinToPort(ss_pin));
    		ss_pin_mask = digitalPinToBitMask(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg+8+1) = ss_pin_mask;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg+8+2) = ss_pin_mask;
    	}
     
    #elif defined(ARDUINO_ARCH_ESP8266)
    	static volatile uint32_t *ss_pin_reg;
    	static uint32_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = (volatile uint32_t*)GPO;
    		ss_pin_mask = 1 << ss_pin;
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		GPOC = ss_pin_mask;
    	}
    	inline static void resetSS() {
    		GPOS = ss_pin_mask;
    	}
     
    #elif defined(__SAMD21G18A__)
    	static volatile uint32_t *ss_pin_reg;
    	static uint32_t ss_pin_mask;
    	inline static void initSS() {
    		ss_pin_reg = portModeRegister(digitalPinToPort(ss_pin));
    		ss_pin_mask = digitalPinToBitMask(ss_pin);
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		*(ss_pin_reg+5) = ss_pin_mask;
    	}
    	inline static void resetSS() {
    		*(ss_pin_reg+6) = ss_pin_mask;
    	}
    #else
    	inline static void initSS() {
    		pinMode(ss_pin, OUTPUT);
    	}
    	inline static void setSS() {
    		digitalWrite(ss_pin, LOW);
    	}
    	inline static void resetSS() {
    		digitalWrite(ss_pin, HIGH);
    	}
    #endif
    En revanche, pinMode est utilisé pas mal de fois dans :
    - FastPin.h
    - w5100.h
    - et aussi dans mon code

    Mon code utilise aussi analogWrite et analogRead, mais c'est à moi de faire autrement

    Je ne sais pas en revanche si les fonctions du Core utilisent pinMode, digitalWrite, ...

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  15. #15
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    pour le core vous pouvez aussi rechercher directement dans GitHub

    Si vous saturez sur un UNO, pourquoi ne pas passer à une MEGA... il existe des 'mini mega' si la place est un soucis.

    La maintenance de codes modifiés et que vous êtes le seul à utiliser c'est couteux en temps ou alors vous vous retrouvez ancré dans le passé très vite (en anglais ils ont le terme 'technical debt' et la dette ça coûte cher...)

    Sinon je suis tombé sur ce truc qui pourrait sans doute vous aider. Je n'ai pas essayé

  16. #16
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Bonjour,

    oui toucher au core va "bloquer" mon projet ; en revanche je peux créer mes propres fonctions et versions de librairies

    j'ai pensé moi aussi au Méga... en plus avec cette carte j'aurais vraiment beaucoup plus de ROM et de RAM ce qui permet pas mal d'extras (gros bitmaps, plusieurs polices de caractères...)

    Pour le moment je ne suis pas coincé avec Arduino UNO, la ROM est occupée à 84% et je n'ai plus beaucoup de choses à coder...

    Mais depuis, j'ai eu d'autres idées ambitieuses, comme intégrer à mon projet le programme de calibration de l'écran tactile en sauvegardant les paramètres en EEPROM.
    De même, je voudrais créer un menu "setup" pour saisir directement sur l'écran tactile des paramètres en EEPROM comme les adresses IP client, serveur, DNS... ainsi que quelques paramètres. De cette façon, je n'aurais qu'un seul croquis identique à téléverser dans toutes mes cartes, la calibration de l'écran tactile et le choix des constantes (adresse IP, ...) se faisant directement sur l'Arduino, pas besoin de modifier le code pour chaque exemplaire.

    Cette idée ambitieuse va demander pas mal code et il faut donc :
    - que je fasse de la place en optimisant au maximum
    - que je passe sur Arduino Méga

    Il y a aussi une "troisième voie" : faire non pas un mais DEUX croquis pour mon projet :
    - un croquis de "calibration" : une fois téléversé, il permet de calibrer l'écran, de choisir les paramètres, et sauvegarde tout dans l'EEPROM
    - un croquis "normal", qui est le code correspond à mon projet et qui utilise les valeurs sauvegardées dans l'EEPROM par le croquis calibration

    J'aime bien le défit que représente la possibilité de pouvoir rester avec un Arduino UNO

    Optimiser le code demande de mettre son nez dans les bibliothèques et le core, c'est comme ça qu'on apprend vraiment à maîtriser le matériel et le logiciel.
    La référence Arduino est un peu légère... c'est en épluchant le code qu'on comprend vraiment comment fonctionnent les fonctions.
    Prenons par exemple analogWrite : dans mon programme, je faisais un test avant d'écrire une broche : si la valeur est 0 je fais un digitalWrite(LOW), si la valeur est 255 je fais un digitalWrite(HIGH), sinon un analogWrite.
    En regardant le code de analogWrite j'ai découvert que le code de la fonction faisait déjà ce test ! Donc j'ai pu simplifier le code de mon projet.

    Je peux aussi optimiser le stockage des bitmaps et des polices de caractères :
    - lorsqu'un bitmap a une largeur non multiple de 8 pixels, il y a, pour chaque ligne, entre 1 et 7 bits de gâchés
    - pareil pour les polices de caractères, un caractère fait 5 x 7 pixels mais il consomme 5 octets, il y a 5 bits de gâchés par caractère
    - il y a peut être moyen de "compresser" le stockage des caractères, car toutes les colonnes de pixels utilisées ne recouvrent certainement pas toutes les valeurs possibles pour un octet.
    - la police de caractère définit 256 caractères, ce qui est absurde car même les caractères non imprimables consomment 5 octets chacun... je vais corriger ça en ne stockant que des caractères utiles, et/ou en définissant des caractères "bonus" un peu comme la police WingDings ; ces caractères "bonus" peuvent éviter de faire des bitmaps, et, gros avantage, ils peuvent être affichés en plusieurs taille (ce que la fonction bitmap ne permet pas)

    J'ai pris note de ta bibliothèque alternative pour les entrées/sorties...

    Après ce qui me surprend, c'est que ni Arduino ni Adafruit ne se sont donnés le mal de faire des bibliothèques optimisées...
    Malgré l'utilisation de directives de compilation #if, les codes qui sont conçu pour fonctionner avec toutes les cartes possibles font perdre beaucoup de ROM.
    En commençant à épurer la librairie Ethernet.h pour ne conserver que le code qui concerne la puce W5500 j'ai pu économiser plus de 1ko de ROM. Et on gagne un peu en rapidité et en RAM au passage.
    Certes l'IDE serait plus lourd mais il aurait fallu faire un core séparé et optimisé pour chaque carte. Ou alors au moins pour les cartes avec le moins de ressources.
    De même pour les bibliothèques, il faudrait une version par modèle de shield. En plus elles serait plus courtes et plus faciles à débugguer ; mais cela demanderais aux développeurs d'importer la bibliothèque correspondant exactement à son hardware.
    Il y a aussi dans la librairie des tests redondants... Par exemple on a besoin de tester la présence d'une puce compatible sur le shield Ethernet que au moment de begin()... de toute façon si le shield lâche le programme restera bloqué et ne fera rien, donc une bonne partie de la gestion d'erreur n'est pas vraiment utile dans le projet final.

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  17. #17
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Citation Envoyé par electroremy Voir le message
    Mais depuis, j'ai eu d'autres idées ambitieuses, comme intégrer à mon projet le programme de calibration de l'écran tactile en sauvegardant les paramètres en EEPROM.
    ça va nécessiter pas mal de modifs. Dans URTouch par exemple (je ne sais pas si c'est ce que vous utilisez), ces valeurs de calibrations sont des #define dans le .h et le compilateur fait des pré-calculs dans InitTouch().
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    	_default_orientation	= CAL_S>>31;
    	touch_x_left			= (CAL_X>>14) & 0x3FFF;
    	touch_x_right			= CAL_X & 0x3FFF;
    	touch_y_top				= (CAL_Y>>14) & 0x3FFF;
    	touch_y_bottom			= CAL_Y & 0x3FFF;
    	disp_x_size				= (CAL_S>>12) & 0x0FFF;
    	disp_y_size				= CAL_S & 0x0FFF;
    Là faudra aller les lire en EEPROM dans InitTouch(), ce qui va faire grossir ce code.

    De même, je voudrais créer un menu "setup" pour saisir directement sur l'écran tactile des paramètres en EEPROM comme les adresses IP client, serveur, DNS... ainsi que quelques paramètres. De cette façon, je n'aurais qu'un seul croquis identique à téléverser dans toutes mes cartes, la calibration de l'écran tactile et le choix des constantes (adresse IP, ...) se faisant directement sur l'Arduino, pas besoin de modifier le code pour chaque exemplaire.
    Pourquoi ne pas utiliser DHCP avec des adresses réservées sur le routeur ?


    Il y a aussi une "troisième voie" : faire non pas un mais DEUX croquis pour mon projet :
    - un croquis de "calibration" : une fois téléversé, il permet de calibrer l'écran, de choisir les paramètres, et sauvegarde tout dans l'EEPROM
    - un croquis "normal", qui est le code correspond à mon projet et qui utilise les valeurs sauvegardées dans l'EEPROM par le croquis calibration
    C'est plus laborieux mais si la place est restreinte, vous ne vous trimbalerez pas le code de calibration qui ne servira sans doute qu'une seule fois.

    J'aime bien le défit que représente la possibilité de pouvoir rester avec un Arduino UNO
    Optimiser le code demande de mettre son nez dans les bibliothèques et le core, c'est comme ça qu'on apprend vraiment à maîtriser le matériel et le logiciel.
    Tout à fait, c'est un bon exercice.

    Je peux aussi optimiser le stockage des bitmaps et des polices de caractères :
    - lorsqu'un bitmap a une largeur non multiple de 8 pixels, il y a, pour chaque ligne, entre 1 et 7 bits de gâchés
    - pareil pour les polices de caractères, un caractère fait 5 x 7 pixels mais il consomme 5 octets, il y a 5 bits de gâchés par caractère...
    oui, après jouer sur les bits fait grossir le code et ralentir l'exécution car il faut un calcul pour aller trouver le bon octet qui contient le bon pixel.. faut trouver un juste équilibre et décider ce que vous voulez optimiser, la mémoire RAM ou la FLASH

    Après ce qui me surprend, c'est que ni Arduino ni Adafruit ne se sont donnés le mal de faire des bibliothèques optimisées...
    rappelez vous que c'est quand même fait pour des "bidouilleurs du dimanche" et ils prennent une approche ceinture bretelle pour éviter des soucis. Par exemple un digitalRead() ou digitalWrite() embarque du code qui éteint le PWM si c'est une pin PWM ou ils s'assurent que le N° de la pin existe. Ensuite la portabilité entre carte Arduino est importante dans leurs priorités (quand c'est possible)

    Créer ce genre de code libre coûte cher et les gens achètent plus souvent des clones de produits Adafruit ou Arduino que les originaux... donc ils font ce qu'il faut pour que ça fonctionne avec leurs cartes, mais pas forcément de l'over-engineering qui sert leurs concurrents chinois... Pour nombre de développeurs ça suffit, pour les autres qui veulent aller plus loin, c'est qu'ils ont des compétences et peuvent écrire leur propre bibliothèque et la poster dans le domaine public... (et elle sera peut être adoptée par le projet Arduino comme bibliothèque standard dans le futur si elle est vraiment mieux. ça a été le cas il y a un moment pour SoftwareSerial ou SD).

    Malgré l'utilisation de directives de compilation #if, les codes qui sont conçu pour fonctionner avec toutes les cartes possibles font perdre beaucoup de ROM.
    tout ce qui n'est pas touché par le #if ne sera pas compilé. Mais oui, ils auraient pu prendre une approche plus affinée pour ne pas mélanger tous les codes. C'est sans doute parce que cette bibliothèque a été développée itérativement, et quand un nouveau composant sort, ils font les modifications nécessaire pour le supporter en injectant un peu plus de code. Une refonte complète avec une architecture plus modulaire serait couteuse en temps. (si vous faites un truc sympa qui donne cette modularité, faudra leur proposer !)


    bonne exploration !

  18. #18
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Bonjour,

    On peut aussi optimiser la vitesse, même si cela demande un petit peu plus de code.
    Voilà un autre intérêt d'optimiser le code en ne gardant que ce qui est utile, on libère de la ROM pour ensuite optimiser la vitesse.

    Alors je viens de trouver une optimisation intéressante pour PDQ GFX.

    Elle permet d'imprimer le texte 1,5 fois plus vite et ne coûte que 80 octets de ROM supplémentaires

    PDQ_GFX est un fork optimisé de Adafruit_GFX
    Avec, il y a PDQ_ILI9341 qui est lui aussi un fork optimisée

    PDQ_GFX est la partie "graphique haut niveau" de la librairie TFT
    PDQ_ILI9341 est la partie "hardware" adaptée aux afficheurs utilisant un contrôleur ILI9341

    En épluchant le code de ces librairies, on se rend compte que plusieurs fonctions de PDQ_GFX appellent la fonction drawPixel pour des pixels qui se suivent.

    C'est le cas de drawChar :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    // Draw a character with built-in font
    template<class HW>
    void PDQ_GFX<HW>::drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size)
    {
      if ((x >= _width)  ||
        (y >= _height) ||
        ((x + (6 * size) - 1) < 0) ||
        ((y + (8 * size) - 1) < 0))
        return;
     
      uint8_t is_opaque = (bg != color);
     
      if(!_cp437 && (c >= 176))	// Handle 'classic' charset behavior
        c++;
     
      for (int8_t i=0; i<6; i++)
      {
        uint8_t line;
     
        if (i == 5)
          line = 0x0;
        else
          line = pgm_read_byte(RLucas_glcdfont+(c*5)+i);
     
        if (size == 1)
        {
          for (int8_t j = 0; j < 8; j++)
          {
            if (line & 0x1)
            {
              HW::drawPixel(x+i, y+j, color);
            }
            else if (is_opaque)
            {
              HW::drawPixel(x+i, y+j, bg);
            }
            line >>= 1;
          }
        }
        else
        {
          for (int8_t j = 0; j < 8; j++)
          {
            if (line & 0x1)
            {
              HW::fillRect(x+(i*size), y+(j*size), size, size, color);
            }
            else if (is_opaque)
            {
              HW::fillRect(x+(i*size), y+(j*size), size, size, bg);
            }
            line >>= 1;
          }
        }
      }
     
    }
    Voici le code de drawPixel :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void PDQ_ILI9341::drawPixel(int x, int y, uint16_t color)
    {
    	if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height))
    		return;
     
    	spi_begin();
     
    	setAddrWindow_(x, y, x, y); 
     
    	spiWrite16_preCmd(color);
     
    	spi_end();
    }
    On voit tout de suite que c'est sous-optimal d'appeler drawPixel pour des pixels qui se suivent, car à chaque fois on fait un SPI begin, un setAddrWindow_(x, y, x, y) et un SPI end pour rien !

    La fonction fillRect est bien plus véloce :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    void PDQ_ILI9341::fillRect(int x, int y, int w, int h, uint16_t color)
    {
    	// rudimentary clipping (drawChar w/big text requires this)
    	if ((x >= _width) || (y >= _height))
    		return;
    	if (x < 0)
    	{
    		w += x;
    		x = 0;
    	}
    	if (y < 0)
    	{
    		h += y;
    		y = 0;
    	}
    	if ((x + w) > _width)
    		w = _width  - x;
    	if ((y + h) > _height)
    		h = _height - y;
     
    	spi_begin();
     
    	setAddrWindow_(x, y, x+w-1, _height);
     
    	for (; h > 0; h--)
    	{
    		spiWrite16(color, w);
    	}
     
    	spi_end();
    }
    J'ai donc écrit une fonction "drawFastChar" dans PDQ_ILI9341.h :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // Draw a character with built-in font
    // Il faut trouver moyen de sauter un pixel si texte sur fond transparent
    void PDQ_ILI9341::drawFastChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size)
    {
      if ((x >= _width)  ||
        (y >= _height) ||
        ((x + (6 * size) - 1) < 0) ||
        ((y + (8 * size) - 1) < 0))
        return;
     
      int8_t i;
      int8_t j;
      int8_t k;
      uint8_t is_opaque = (bg != color);
     
      if(!_cp437 && (c >= 176))	// Handle 'classic' charset behavior
        c++;
     
      spi_begin();
     
      int8_t size2 = size * size;
     
      for (i=0; i<6; i++) {
        uint8_t line;
        if (i == 5) line = 0x0;  else  line = pgm_read_byte(RLucas_glcdfont+(c*5)+i);
     
    	// Dommage les caractères s'impriment colonne par colonne, du coup on doit appeller setAddrWindow_ pour chaque colonne
    	// On pourrait optimiser encore plus en modifiant la façon de stocker les caractères (et gagner un peu de ROM aussi)
        setAddrWindow_(x+(i*size), y, x+(i*size)+size-1, _height);
     
        if (size == 1) {
          for (j = 0; j < 8; j++) {
            if (line & 0x1) {
    		  spiWrite16(color);
              //drawPixel(x+i, y+j, color);
            //else if (is_opaque)
            } else {
              spiWrite16(bg);
    		  //drawPixel(x+i, y+j, bg);
            }
            line >>= 1;
          }
        } else {
    		  for (j = 0; j < 8; j++) {
    				for (k = 0; k<size2 ; k++) {
    					if (line & 0x1) {
    					  spiWrite16(color);
    					  //fillRect(x+(i*size), y+(j*size), size, size, color);
    					//else if (is_opaque)
    					} else {
    					  spiWrite16(bg);
    					  //fillRect(x+(i*size), y+(j*size), size, size, bg);
    					}				}
    				line >>= 1;
    		  }
     
        }
      }
      spi_end();
    }
    Et modifié en conséquence PDQ_GFX :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Draw a character with built-in font
    template<class HW>
    void PDQ_GFX<HW>::drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size)
    {
      // Code classique : 26674 octets de ROM - 475 ms pour l'affichage taille 2 -  367 ms pour l'affichage taille 2
      // drawFastChar : 26754 octets de ROM - 320 ms pour l'affichage taille 2 - 237 ms pour l'affichage taille 2 => 1,5 x plus rapide
      HW::drawFastChar(x, y, c, color, bg, size); 
     
    }
    Le gain de temps n'est pas négligeable pour une interface homme-machine - mon test n'a imprimé qu'un texte qui ne rempli qu'un quart de l'écran.

    Le gros inconvénient c'est que mon optimisation ne fonctionne que pour du texte où on imprime le fond.
    Elle ne marche pas pour du texte "transparent", et la fonction classique est plus rapide avec du texte "transparent"

    Mais en même temps, en pratique, on utilise quasiment jamais l'impression de texte transparent dans une interface graphique (c'est même une plainte des utilisateur de polices de caractères proportionnelles qui doivent galérer pour effacer le fond avant d'écrire un nouveau texte car la bibliothèque ne prend pas en charge l'écriture de texte proportionnel avec un fond)

    Alors que PDQ_GFX et PDQ_ILI9341 est déjà un fork optimisé, c'est pas mal d'avoir pu encore augmenté la vitesse d'affichage de 1,5 fois !

    L'explication est simple : setAddrWindow_(x1, y1, x2, y2) sert à définir une "fenêtre" dans l'écran TFT

    Ensuite, avec une suite d'instructions spiWrite16(color), on écrit cette fenêtre pixel par pixel, de gauche à droite et de bas en haut.

    Il est donc très stupide de remplir un rectangle en faisant pour chaque pixel un setAddrWindow_(x, y, x, y)

    Ce qui est un peut dommage c'est que l'afficheur TFT ILI9341 ne semble pas avoir directement une instruction permettant de remplir un rectangle.

    Car dans l'histoire ce sont les échanges sur le bus SPI qui ralentissent tout.

    Je peux optimiser l'affichage de bitmaps sur le même principe

    Autre idée : il est assez facile de modifier un peu le code pour imprimer un texte ou un bitmap monochrome avec une couleur changeante, pour avoir un effet genre dégradé ou arc en ciel, sans devoir stocker en mémoire un bitmap en couleur

    Je pense que je vais quand même voir la datasheet du ILI9341, certaines fonctions utiles sont peut être absentes de la bibliothèque

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  19. #19
    Membre éprouvé Avatar de electroremy
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Juin 2007
    Messages
    934
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2007
    Messages : 934
    Points : 1 274
    Points
    1 274
    Par défaut
    Voici une fonction optimisée qui dessine un bitmap stocké en RAM (adaptable facilement avec un bitmap en PROGMEM) :
    - 1,5 fois plus rapide
    - possible de tourner le bitmap de 0°, 90°, 180°, 270° et de faire un miroir (paramètres angleFM compris entre 0 et 7)
    - stockage du bitmap "sans pertes" : les bits sont tous à la suite, aucun bit de perdu même si la largeur n'est pas multiple de 8 pixels
    - juste 80 octets de ROM en plus par rapport à la fonction de base ; cette perte peut être facilement compensée par le gain apporté par le stockage sans pertes du bitmap

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    //Fonction à la fois plus rapide (vitesse X 1,5) et qui optimise le stockage du bitmap (plus de bits "gaspillés" si la largeur n'est pas multiple de 8)
    //Mais ne fonctionne pas pour bitmap "transparent"
    void PDQ_ILI9341::drawFastBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg, uint8_t angleFM) {
    	coord_t i, j, tmp;
    	unsigned int numPix;
    	bool mirror;
     
    	mirror = angleFM > 3;
    	if (mirror) {
    		angleFM-=4;
    	}
    	mirror^=(angleFM>1);
     
    	if (angleFM==1 || angleFM==3) {
    		tmp = h;
    		h = w;
    		w = tmp;
    	}
     
    	spi_begin();
     
    	setAddrWindow_(x, y, x + w - 1, _height);	
     
    	for (j = 0; j < h; j++) {
    		for (i = 0; i < w; i++) {
    			if (mirror) {
    				tmp = w - i - 1;
    			}else{
    				tmp = i;
    			}
    			switch (angleFM) {
    			case 0:
    				numPix = tmp + j * w;
    				break;
    			case 1: 
    				numPix = h - j - 1 + tmp * h;
    				break;
    			case 2:
    				numPix = tmp + (h - j - 1) * w;
    				break;
    			default:
    				numPix = j + tmp * h;
    				break;
    			}
    			if (bitmap[numPix >> 3] & (0b10000000 >> (numPix % 8)))  spiWrite16(color);  else  spiWrite16(bg); // Dommage on ne peut pas "sauter" un pixel donc impossible de faire un bitmap transparent
    		}
    	}
     
    	spi_end();
    }
    La ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (bitmap[numPix >> 3] & (0b10000000 >> (numPix % 8)))
    peut sembler sous-optimale (on accède plusieurs fois au même octet de bitmap[]...) mais l'écrire de cette façon a permis d'éviter de nombreux tests (les rotations et le miroir font qu'on ne parcours pas les données dans le sens où on doit les envoyer à l'afficheur), et la performance est au rendez vous puisque la fonction est 1,5X plus rapide.

    Le seul truc un peu dommage est de ne pas pouvoir afficher un bitmap avec une couleur de fond transparente, mais là aussi tout dépend de votre application.

    A bientôt
    Quand deux personnes échangent un euro, chacun repart avec un euro.
    Quand deux personnes échangent une idée, chacun repart avec deux idées.

  20. #20
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 711
    Points : 5 390
    Points
    5 390
    Par défaut
    Citation Envoyé par electroremy Voir le message
    La ligne
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    if (bitmap[numPix >> 3] & (0b10000000 >> (numPix % 8)))
    peut sembler sous-optimale
    le modulo 8 d'un entier ce sont les 3 bits de poids faible donc au lieu de (numPix % 8) vous pourriez prendre (numPix & 0b111) (Le compilateur l'a peut-être fait pour vous d'ailleurs)

Discussions similaires

  1. [Livre] Mieux programmer en Java - 68 astuces pour optimiser son code
    Par MarieKisSlaJoue dans le forum Général Java
    Réponses: 0
    Dernier message: 01/07/2014, 15h00
  2. Réponses: 8
    Dernier message: 14/09/2006, 16h43
  3. comment optimiser son code en calcul ???
    Par gronaze dans le forum C
    Réponses: 5
    Dernier message: 21/03/2006, 10h41
  4. Réponses: 9
    Dernier message: 22/02/2006, 11h32
  5. [Perf] Comment optimiser son code ?
    Par Frifron dans le forum Général Java
    Réponses: 12
    Dernier message: 11/08/2005, 09h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo