[Tests] Temps pour faire changer d'état une pin (niveau Haut/Bas)

Très intéressant comme test, merci bien!

Il fallait s'en douter qu'avec le langage Arduino, ces écritures prennent plus de temps.
Je pense que c'est en grande partie parce que le code n'est pas optimisé à fond pour raccourcir ce temps (que ce soit mal fait, ou dans l'impossibilité de faire mieux).

Alors que dans l'utilisation des registres, le langage se rapproche un peu plus de l'Assembleur.

Il faudrait approfondir ce test en testant avec le langage Assembleur pur, je suis sûr que l'on pourrait encore un peu réduire ce temps.

Motivé? :wink:

Assembleur ->> Jocker

Je débute dans les microcontroleurs à 65 ans et je n'ai pas l'intention de m'emouscailler.
Je n'ai plus de patron sur le dos (1) tout doit rester un plaisir.

(1) A part (comme beaucoup, je pense) à la maison une chef de projet jamais à court d'idées, mais c'est un autre problème.

Je te comprends, l'Assembleur est plus que... rébarbatif à la longue! Surtout quand on commence avec :slight_smile:

Pour info: on doit tourner à quelques microsecondes pour un changement d'état sur une broche en Assembleur.

Je ne suis pas sur que l'on gagne beaucoup avec l'assembleur.
Si on regarde les chiffres 107 ms pour 10^5 cycles cela fait 1,07 µs par cycle soit 0,535 µs pour une écriture en faisant l'hypothèse que les temps pour passer d'un niveau à bas et bas vers haut sont égaux.
Le µcontroleur fonctionne à 16MHz d'horloge soit une période de 62.5 ns.
Avec les registres le temps d'écriture correspondrait à 8 cycles d'horloge (le calcul d'après les résultats de mesure donne 8,56 cycles d'horloge).

Il faudrait entrer dans le détail du fonctionnement du µcontroleur pour déterminer le nombre exact de cycles nécessaires pour faire changer d'état une sortie mais à mon avis le jeu n'en vaux pas la chandelle.

Bonsoir,

Je vous recommande la librairie digitalWriteFast.
C'est un fait une série de grosses macros qui permettent de réduire fortement le temps de commutation et/ou de lecture des pins lorsque le numéro des pins est connu à la compilation.

Il suffit d'ajouter un #include "digitalWriteFast.h" dans les fichiers où vous souhaitez l'utiliser. Ensuite, il faut utiliser les fonctions digitalWriteFast, digitalReadFast, pinModeFast à la place de digitalWrite, digitalRead, pinMode.

Ci-dessous, ma version :

#ifndef DIGITAL_WRITE_FAST_H
#define DIGITAL_WRITE_FAST_H
 /**
 * \file
 * \brief Macros to use digitalWrite style syntax with most of the speed of PORT
 *
 * This library consists of a complex header file that translates
 * digitalWriteFast, pinModeFast, digitalReadFast into the corresponding PORT 
 * commands.
 *
 * It provides syntax that is as novice-friendly as the arduino's pin
 * manipulation commands but an order of magnitude faster.
 *
 * It can speed things up when the pin number is known at compile time, so that 
 * digitalWrite(9,HIGH); is speeded up. On the other hand a loop with
 * digitalWrite(i,HIGH); or a called function with the pin number as a passed
 * argument will not be faster.
 *
 * For more information visit <http://code.google.com/p/digitalwritefast/>.
 */
#include <WProgram.h>
#include <wiring.h>
//------------------------------------------------------------------------------
// bit operations
#define BIT_READ(value, bit) (((value) >> (bit)) & 0x01)
#define BIT_SET(value, bit) ((value) |= (1UL << (bit)))
#define BIT_CLEAR(value, bit) ((value) &= ~(1UL << (bit)))
#define BIT_WRITE(value, bit, bitvalue) \
(bitvalue ? BIT_SET(value, bit) : \BIT_CLEAR(value, bit))
//------------------------------------------------------------------------------
#if !defined(digitalPinToPortReg)
//------------------------------------------------------------------------------
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// ATmega1280, ATmega2560
#define digitalPinToPortReg(P) \
(((P) >= 22 && (P) <= 29) ? &PORTA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PORTB : \
(((P) >= 30 && (P) <= 37) ? &PORTC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PORTD : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PORTE : \
(((P) >= 54 && (P) <= 61) ? &PORTF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PORTG : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PORTH : \
(((P) == 14 || (P) == 15) ? &PORTJ : \
(((P) >= 62 && (P) <= 69) ? &PORTK : &PORTL))))))))))
#define digitalPinToDDRReg(P) \
(((P) >= 22 && (P) <= 29) ? &DDRA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &DDRB : \
(((P) >= 30 && (P) <= 37) ? &DDRC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &DDRD : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &DDRE : \
(((P) >= 54 && (P) <= 61) ? &DDRF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &DDRG : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &DDRH : \
(((P) == 14 || (P) == 15) ? &DDRJ : \
(((P) >= 62 && (P) <= 69) ? &DDRK : &DDRL))))))))))
#define digitalPinToPINReg(P) \
(((P) >= 22 && (P) <= 29) ? &PINA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PINB : \
(((P) >= 30 && (P) <= 37) ? &PINC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PIND : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PINE : \
(((P) >= 54 && (P) <= 61) ? &PINF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PING : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PINH : \
(((P) == 14 || (P) == 15) ? &PINJ : \
(((P) >= 62 && (P) <= 69) ? &PINK : &PINL))))))))))
#define __digitalPinToBit(P) \
(((P) >=  7 && (P) <=  9) ? (P) - 3 : \
(((P) >= 10 && (P) <= 13) ? (P) - 6 : \
(((P) >= 22 && (P) <= 29) ? (P) - 22 : \
(((P) >= 30 && (P) <= 37) ? 37 - (P) : \
(((P) >= 39 && (P) <= 41) ? 41 - (P) : \
(((P) >= 42 && (P) <= 49) ? 49 - (P) : \
(((P) >= 50 && (P) <= 53) ? 53 - (P) : \
(((P) >= 54 && (P) <= 61) ? (P) - 54 : \
(((P) >= 62 && (P) <= 69) ? (P) - 62 : \
(((P) == 0 || (P) == 15 || (P) == 17 || (P) == 21) ? 0 : \
(((P) == 1 || (P) == 14 || (P) == 16 || (P) == 20) ? 1 : \
(((P) == 19) ? 2 : \
(((P) == 5 || (P) == 6 || (P) == 18) ? 3 : \
(((P) == 2) ? 4 : \
(((P) == 3 || (P) == 4) ? 5 : 7)))))))))))))))
#define __digitalPinToTimer(P) \
(((P) == 13 || (P) ==  4) ? &TCCR0A : \
(((P) == 11 || (P) == 12) ? &TCCR1A : \
(((P) == 10 || (P) ==  9) ? &TCCR2A : \
(((P) ==  5 || (P) ==  2 || (P) ==  3) ? &TCCR3A : \
(((P) ==  6 || (P) ==  7 || (P) ==  8) ? &TCCR4A : \
(((P) == 46 || (P) == 45 || (P) == 44) ? &TCCR5A : 0))))))
#define __digitalPinToTimerBit(P) \
(((P) == 13) ? COM0A1 : (((P) ==  4) ? COM0B1 : \
(((P) == 11) ? COM1A1 : (((P) == 12) ? COM1B1 : \
(((P) == 10) ? COM2A1 : (((P) ==  9) ? COM2B1 : \
(((P) ==  5) ? COM3A1 : (((P) ==  2) ? COM3B1 : (((P) ==  3) ? COM3C1 : \
(((P) ==  6) ? COM4A1 : (((P) ==  7) ? COM4B1 : (((P) ==  8) ? COM4C1 : \
(((P) == 46) ? COM5A1 : (((P) == 45) ? COM5B1 : COM5C1))))))))))))))
//------------------------------------------------------------------------------
#else	// ATmega8, ATmega168, ATmega328
#define digitalPinToPortReg(P) \
(((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC))
#define digitalPinToDDRReg(P) \
(((P) >= 0 && (P) <= 7) ? &DDRD : (((P) >= 8 && (P) <= 13) ? &DDRB : &DDRC))
#define digitalPinToPINReg(P) \
(((P) >= 0 && (P) <= 7) ? &PIND : (((P) >= 8 && (P) <= 13) ? &PINB : &PINC))
#define __digitalPinToBit(P) \
(((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14))
//------------------------------------------------------------------------------
#if defined(__AVR_ATmega8__) // ATmega8
#define __digitalPinToTimer(P) \
(((P) ==  9 || (P) == 10) ? &TCCR1A : (((P) == 11) ? &TCCR2 : 0))
#define __digitalPinToTimerBit(P) \
(((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : COM21))
//------------------------------------------------------------------------------
#else // ATmega168, ATmega328
#define __digitalPinToTimer(P) \
(((P) ==  6 || (P) ==  5) ? &TCCR0A : \
(((P) ==  9 || (P) == 10) ? &TCCR1A : \
(((P) == 11 || (P) ==  3) ? &TCCR2A : 0)))
#define __digitalPinToTimerBit(P) \
(((P) ==  6) ? COM0A1 : (((P) ==  5) ? COM0B1 : \
(((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : \
(((P) == 11) ? COM2A1 : COM2B1)))))
#endif  // ATmega8
#endif  // defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#endif  // !defined(digitalPinToPortReg)
//------------------------------------------------------------------------------
#define __atomicWrite__(A,P,V) \
if ( (int)(A) < 0x40) { \
bitWrite(*((volatile uint8_t*) A), __digitalPinToBit(P), (V) );} \
else { \
uint8_t register saveSreg = SREG; \
cli(); \
bitWrite(*((volatile uint8_t*)A), __digitalPinToBit(P), (V) ); \
SREG=saveSreg; \
} 
//------------------------------------------------------------------------------
#ifndef digitalWriteFast
#define digitalWriteFast(P, V) \
do { \
if (__builtin_constant_p(P) && __builtin_constant_p(V)) \
__atomicWrite__(digitalPinToPortReg(P),P,V) \
else  digitalWrite((P), (V)); \
} while (0)
#endif
//------------------------------------------------------------------------------
#if !defined(pinModeFast)
#define pinModeFast(P, V) \
do { \
if (__builtin_constant_p(P) && __builtin_constant_p(V)) \
__atomicWrite__(digitalPinToDDRReg(P),P,V) \
else pinMode((P), (V)); \
} while (0)
#endif
//------------------------------------------------------------------------------
#ifndef noAnalogWrite
#define noAnalogWrite(P) \
do {if (__builtin_constant_p(P) )  __atomicWrite(__digitalPinToTimer(P),P,0) \
else turnOffPWM((P)); \
} while (0)
#endif		
//------------------------------------------------------------------------------
#ifndef digitalReadFast
#define digitalReadFast(P) ( (int) _digitalReadFast_((P)) )
#define _digitalReadFast_(P ) \
(__builtin_constant_p(P) ) ? ( \
( BIT_READ(*digitalPinToPINReg(P), __digitalPinToBit(P))) ) : \
digitalRead((P))
#endif
//------------------------------------------------------------------------------
#endif // DIGITAL_WRITE_FAST

j'ai (comme 68tjs) fait cette remarque. pour ma part, je remplace souvent un digitalwrite() par un PORTB |=..., un pinmode par un DDRD &=...

De plus, il serait intéressant de voir ce que donne la compilation d'un petit programme en arduino et du même en registres, niveau taille flash. je crois qu'on y gagnerait énormément! (je pense à JF qui n'arrive pas à faire tourner une SD sur un 168... :D)

Le moindre appel d'une fonction inclut la pile, le jump, les tests dans la fonction qui la rendent universelle, re la pile et le rtn. tout ça, on le gagne avec une écriture directe dans les registres.

L'assembleur est très intéressant quand on sait exactement ce que doit faire le proc!

Super_Cinci:
je pense à JF qui n'arrive pas à faire tourner une SD sur un 168... :smiley:

J'suis pas loin... et j'ai tout lu XD

Ce qui me gène le plus ce n'est pas l'excès de temps avec l'IDE arduino, ce sont les perturbations des sorties PWM.
Cela sent très fort la rustine sur la rustine sur la rustine.

68tjs:
Ce qui me gène le plus ce n'est pas l'excès de temps avec l'IDE arduino, ce sont les perturbations des sorties PWM.
Cela sent très fort la rustine sur la rustine sur la rustine.

C'est un peu le souci avec l'open source, c'est que celui qui améliore une fonction ne réécrit pas le code, mais se contente trop souvent d'en rajouter. Si aujourd'hui on tourne avec la version 22, il doit y avoir une vingtaine d'intervention sur les fonctions, 20 lignes de code en trop quoi...

Pour le PWM, j'ai ça si tu veux, c'est ce que j'avais développé pour gérer un servo sur MEGA2560, mais c'est transposable sur UNO en changeant le nom des registres. Pour l'exemple, j'ai viré un peu de code, car avec un timer sur méga2560, on peut gérer 3 servos (ou PWM) :

const byte servo1_pin = 6;                   // Servo 1 attahcé à pin N°6 (OC4A)
const word servo1_min = 6500;                   // Servo 1 valeur max
const word servo1_max = 17500;                    // Servo 1 valeur min
volatile word servo1_val = servo1_min;             // contient la valeur actuelle du servo1

void servo_init (){                               // initialise T4 pour gestion servo1 en PWM sur OC4A
  TCCR4B = 0x10;                                          // désactiver timer T4
  TCCR4A = 0x02;                                         // WGM(4) = b1010 = 10 : PWM pahse correct, compte de 0 à ICR4
  pinMode (servo1_pin, OUTPUT); 
  TCCR4A += 0x80;                                      // activer la sortie en PWM sur OC4A
  OCR4A = servo1_max;                               // amener le servo dans sa position de repos au démarrage de T4
  ICR4H = 0xFF;
  ICR4L = 0xFF;                                         // TOP=65536 / T(pwm) = 8,192ms, précision PWM : 0,125µs / env. 14 000 pas servo
  TCCR4B += 0x01;                             // Activer timer4, prescaler = F_CPU/1  

// autre config timer :
//  ICR4H = 0x3A;
//  ICR4L = 0x98;                                      // TOP=15000 / T(pwm) = 15ms, précision PWM : 1 µs / env. 1850 pas servo
//  TCCR4B += 0x02;                         // Activer timer4, prescaler = F_CPU/8  

}

//------------------------
void servo_write(word val){
      if (val < servo1_min) servo1_val = servo1_min;                    // test de fourchette de valeur min / max
        else if (val > servo1_max) servo1_val = servo1_max;
        else servo1_val = val;
      OCR4A = servo1_val;                                                     // écrire la nouvelle valeur sur OCR
}

Le gros avantage de ce code, c'est que pour changer le rapport cyclique, il suffit de faire un OCR4A = valeur (16 bits). Le test de validité de valeur dans servo_write(val) n'est pas nécessaire pour une PWM de 0 à 100%, dans ce cas, on oublie la void et on écrit dans OCR4A...

J'ai tenté d'utiliser la librairie servo, mais l'innocent qui a pondu ça utilise un timer qui appelle une ISR (routine d'interruption) qui est appelée toutes les µs et qui :

  • désactive le timer et toutes les interruptions,
  • incrémente un compteur,
  • et quand le compteur correspond à la valeur servo voulue en appelle une seconde qui fait un digitalwrite() sur la pin du servo,
  • relance le timer et les interruptions.

En gros, le servo tremble dans tous les sens avec cette librairie, avec mon code, la PWM est d'une stabilité à toute épreuve, car le CPU n'intervient jamais dans la génération PWM.

C'est dommage d'avoir des fonctions hardware super stables sous la main et de les supplanter par des ISR à tout va!

Je te remercie, je mets ton code de coté pour le cas ou j'aurais besoin de PWM.
En fait ma remarque était théorique qu'est ce qui peut produire cette perturbation qui de plus n'est pas identique sur toutes les sorties PWM ?
J'aurais besoin d'une tension analogique, vraie, commandable. J'ai regardé la PWM mais à ce que j'ai compris elle se fait à une fréquence d'environ 500Hz, autant dire que c'est infiltrable avec des valeurs raisonnables de composant.
Je pensais ensuite modifier la configuration de l'AtMega328p pour passer à la vitesse maximale de PWM mais je ne suis pas arrivé à voir tout ce qui d'autre pourrait également être modifié.
Comme je n'ai pas besoin d'une grande précision j'ai eu une autre idée : un registre à décalage 8 bits, le réseau de résistances qui va bien, un condo pour filtrer et l'affaire est jouée (tout du moins au jour d'aujourd'hui sur Spice).
Deux pins seulement sont nécessaires (clock et data) il n'est pas utile de "latcher" les sorties, les combinaisons intermédiaires qui apparaîtront à fréquence élevée devraient être lissées par le condo.

C'est un peu le souci avec l'open source, c'est que celui qui améliore une fonction ne réécrit pas le code, mais se contente trop souvent d'en rajouter.

Désolé de te contredire mais c'est un état d'esprit qui n'est pas lié à l'Opensource, au contraire, c'est largement pire dans le milieu professionnel car le patron est toujours pressé de sortir le produit même s'il n'est pas fini, ce qui compte c'est faire rentrer le max de pognon et le plus vite possible.
Avec l'expérience quand je sentais qu'on risquait de m'imposer de faire passer le produit (électronique) en fabrication je maquillais mes résultats pour récupérer quelques mois supplémentaires de développement et au moins quand je lachais le produit il fonctionnait et il n'y avait pas de retour.

avec ce code, on peut très bien faire tourner la PWM à des fréquences intéressantes, il suffit juste de changer la valeur de ICR4. la période de la PWM est directement proportionnelle à ICR4 et au prescaler. Tu peux même doubler la fréquence (T4 est configuré ici pour compter en "triangle"), ce qui fait que si tu veux une simple résolution de 8 bits, tu peux donc mettre :

ICR4H = 0;
ICR4L = 255;

ton ratio cyclique sera sur OC4AL (= 0 à 255), et tu auras une PWM à 31KHz, voire 62KHz si tu mets le timer en dents de scie. là, tu peux commencer à envoyer de l'audio assez proprement (faudrait que j'essaie tiens!)

En jouant avec les deux autres comparateurs du timer 4, ça te fait 3 PWM dont je te garanti la stabilité! Tu peux pousser le bouchon et diminuant la précision (par exemple pour une PWM commandée par une valeur de 0 à 25, tu montes à 310KHz ou 630KHz). mais pour aller jusque là, faut en vouloir!

Super_Cinci:
j'ai (comme 68tjs) fait cette remarque. pour ma part, je remplace souvent un digitalwrite() par un PORTB |=..., un pinmode par un DDRD &=...

De plus, il serait intéressant de voir ce que donne la compilation d'un petit programme en arduino et du même en registres, niveau taille flash. je crois qu'on y gagnerait énormément! (je pense à JF qui n'arrive pas à faire tourner une SD sur un 168... :D)

Justement, les macros que j'ai postées ci-dessus prennent en charge toute la gymnastique de traduction d'un numéro de pin arduino en un couple (Port + Adresse sur le port), de façon transparente ; elles sont strictement équivalentes à écrire PORTB |= (1 << PINBn). Et comme ce sont des macros elles sont résolues à la compilation, donc aucun appel de fonction :smiley:

Oui, mais si tu fais directement PORTB |= 0x04, par exemple, tu gagnes une instruction par rapport à PORTB |= (1 << 2), donc du temps... c'est surtout une question d'habitude... à l'école, on codait en ASM (dans le temps), c'était bien plus transparent...

Super_Cinci:
C'est un peu le souci avec l'open source, c'est que celui qui améliore une fonction ne réécrit pas le code, mais se contente trop souvent d'en rajouter. Si aujourd'hui on tourne avec la version 22, il doit y avoir une vingtaine d'intervention sur les fonctions, 20 lignes de code en trop quoi...
(...)

Sans être au parfum de tout, je plussoie aussi que ce n'est pas ce qui se pratique en général dans le monde de l'Open Source.
Justement car la multiplicité des intervenants fait que le nombre d'heure de développement n'est pas un facteur réducteur; une bibliothèque est souvent totalement réécrite afin d'évoluer ou des partie sont entièrement remplacées afin de corriger le(s) soucis.
A noté que j'ai déjà vu des bibliothèques "rétrécirent" après une mise à jour afin d'optimiser celle-ci.

Sinon, pour en revenir au sujet principal que je trouve très intéressant; super d'avoir fait ses tests -.^

Et pour le code sur la génération de PWM hyper stable, même si je n'ai pas encore tout compris, c'est excellent aussi : Cela fait pas mal de temps que je me *** à trouver quelque chose de très précis pour de la commande de moteur PàP ! Il y a souvent des "irrégularités" qui font que piloter un moteur PàP en micro-pas à haute précision/vitesse est très compliqué. 1/4 de pas cela va, 1/8 cela passe encore à certaines conditions, mais si l'on demande 100000 micro-pas en 1/8 d'un seul coup assez vite "y'a plus personne". Là, si l'on peut directement faire un pilotage en 16bits et très stable, c'est le bonheur.

A chacun d'apprécier...

Pour rester dans le sujet, j'ai fait un petit code qui écrit et lit sur un port parallèle 4 bits + R/W + CLK. tout simple.

boucle :
  mode Write;
  présenter les données sur le port data;
  faire un pulse sur CLK;
  mode Read;
  faire un pulse sur CLK;
  récupérer les données sur le port data;

Le code en passant par des pinMode() et digitalWrite()/Read() :

void setup() {
  pinMode (8, OUTPUT);  // R/W : PB0
  pinMode (9, OUTPUT);  // clock : PB1
}

byte dumyIn;   // variable de lecture
byte dumyOut;  // variable d'écriture

void loop() {
// -----------------Write data---------
  pinMode (2, OUTPUT);  // data 0 : PD2
  pinMode (3, OUTPUT);  // data 1 : PD3
  pinMode (4, OUTPUT);  // data 2 : PD4
  pinMode (5, OUTPUT);  // data 3 : PD5
  digitalWrite (9, LOW);  // Write data
  digitalWrite (2, (dumyOut & 0x01));              // set data
  digitalWrite (3, (dumyOut & 0x02) >> 1);
  digitalWrite (4, (dumyOut & 0x04) >> 2);
  digitalWrite (5, (dumyOut & 0x08) >> 3);
  digitalWrite (8, HIGH);  // clock rising
  delay(2);
  digitalWrite (8, LOW);  // clock falling
// -----------------Read data---------
  pinMode (2, INPUT);
  pinMode (3, INPUT);
  pinMode (4, INPUT);
  pinMode (5, INPUT);
  digitalWrite (9, HIGH);
  digitalWrite (8, HIGH);  // clock rising
  delay(2);
  dumyIn = digitalRead (2);         // read data
  dumyIn |= digitalRead (3) << 1;
  dumyIn |= digitalRead (4) << 2;
  dumyIn |= digitalRead (5) << 3;
  digitalWrite (8, LOW);  // clock falling
  delay(2);
}

compilation : 1456 octets.

Maintenant, je remplace les fonctions Arduino par des accès aux registres :

void setup() {
  DDRB |= 0x03;   // R/W : PB0; clock : PB1
}

byte dumyIn;   // variable de lecture
byte dumyOut;  // variable d'écriture

void loop() {
// -----------------Write data---------
  DDRD |= 0x3C;  // data(3:0) output
  PORTB &= 0xFD; // Write data
  dumyOut &= 0x0F;    // mask dumyOut
  dumyOut << 2;
  PORTD &= 0xC3;      // set data = 0;
  PORTD |= dumyOut;    // set data;
  PORTB |= 0x01; // clock rising
  delay(2);
  PORTB &= 0xFE;  // clock falling
// -----------------Read data---------
  DDRD &= 0xC3;     // data(3:0) input
  PORTB |= 0x02;    // Read data
  PORTB |= 0x01; // clock rising
  delay(2);
  dumyIn = (PIND & 0x3C);
  dumyIn >> 2;
  PORTB &= 0xFE;  // clock falling
  delay(2);
}

compilation : 724 octets j'ai donc divisé par deux la taille de mon programme, et à mon avis (j'ai pas testé), il doit aller... 10 fois plus vite?

Même code registres, mais en remplaçant (je pensais simplifier le code) :

  dumyIn = (PIND & 0x3C);
  dumyIn >> 2;

par :

  dumyIn = (PIND & 0x3C) >> 2;

compilation : 736 octets : j'ai perdu 12 octets (donc 6 instructions ASM supplémentaires...), juste en réécrivant deux lignes en une seule! De plus, cela doit certainement influer sur la RAM en passant par une variable de passage pour calculer l'opération entre parenthèses...
Je remplace alors ce bout de code par :

  dumyIn = PIND;
  dumyIn &= 0x3C;
  dumyIn >> 2;

et reviens à ma compilation de 724 octets.

Conclusion, si on veut du programme rapide et léger en flash, on évitera les fonctions Arduino et les lignes à opérations multiples!

Je n'ai pas le temps de tester pour mesurer les signaux, mais j'essaierai... (un coup d'oscillo sur la pin R/W pour mesurer la période nous donnera le temps que prend la boucle Loop()).

Bonjour,

Super_Cinci:
Conclusion, si on veut du programme rapide et léger en flash, on évitera les fonctions Arduino et les lignes à opérations multiples!

Si tu veux vraiment optimiser ton programme passe directement sur du c++ avr-gcc sans le core arduino :wink: et active l'optimisation lors de la compilation (chose que ne fait pas l'ide de mémoire ...).

Exemple, ton code SANS le core arduino :

#define F_CPU 16000000UL  // 16 MHz
#include <util/delay.h> // for _delay_ms
#include <avr/io.h> // for register 

int main() { // fonction main (aka setup sous arduino)
	DDRB |= 0x03;   // R/W : PB0; clock : PB1

	uint8_t dumyIn = 0;   // variable de lecture
	uint8_t dumyOut = 0;  // variable d'écriture // NB le type byte n'est rien d'autre qu'un unsigned char soit un uint8_t

	while(1) { // boucle infini (aka loop sous arduino)
	// -----------------Write data---------
	  DDRD |= 0x3C;  // data(3:0) output
	  PORTB &= 0xFD; // Write data
	  dumyOut &= 0x0F;    // mask dumyOut
	  dumyOut = dumyOut << 2; // l'instruction dumyOu << 2; seul ne fait rien, dumyOut = dumyOut << 2;  semble plus cohérent
	  PORTD &= 0xC3;      // set data = 0;
	  PORTD |= dumyOut;    // set data;
	  PORTB |= 0x01; // clock rising
	  _delay_ms(2);
	  PORTB &= 0xFE;  // clock falling
	  
	// -----------------Read data---------
	  DDRD &= 0xC3;     // data(3:0) input
	  PORTB |= 0x02;    // Read data
	  PORTB |= 0x01; // clock rising
	  _delay_ms(2);
	  dumyIn = (PIND & 0x3C);
	  dumyIn = dumyIn >> 2; // l'instruction dumyIn >> 2; seul ne fait rien, dumyIn = dumyIn >> 2;  semble plus cohérent
	  PORTB &= 0xFE;  // clock falling
	  _delay_ms(2); 
	}
}

Et comme je suis pas radin le makefile pour compiler le tout sans se prendre la tête :wink:

DEVICE=atmega328p
AVRDUDE = avrdude -c usbtiny -B 1 -p $(DEVICE)
COMPILE = avr-gcc -Wall -Os -I. -mmcu=$(DEVICE) -DF_CPU=16000000 -DDEBUG_LEVEL=0
OBJECTS = main.o

all:	main.hex

.c.o:
	$(COMPILE) -c $< -o $@

.S.o:
	$(COMPILE) -x assembler-with-cpp -c $< -o $@

.c.s:
	$(COMPILE) -S $< -o $@

flash:	all
	$(AVRDUDE) -U flash:w:main.hex

fuse:
	$(AVRDUDE) -U hfuse:w:0xdd:m -U lfuse:w:0xe1:m

readcal:
	$(AVRDUDE) -U calibration:r:/dev/stdout:i | head -1

clean:
	rm -f main.hex main.lst main.obj main.cof main.list main.map main.eep.hex main.bin *.o main.s

main.bin:	$(OBJECTS)
	$(COMPILE) -o main.bin $(OBJECTS)

main.hex:	main.bin
	rm -f main.hex main.eep.hex
	avr-objcopy -j .text -j .data -O ihex main.bin main.hex

disasm:	main.bin
	avr-objdump -d main.bin

cpp:
	$(COMPILE) -E main.c

Et voila ce que donne la compilation :

C:\Users\skywodd\Desktop\tut>avr-size -C --mcu=atmega328p main.bin
AVR Memory Usage

Device: atmega328p

Program: 66 bytes (0.2% Full)
(.text + .data + .bootloader)

Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)

Conclusion : Si on veut un programme léger, rapide et optimisé on utilise avr-gcc SANS le core arduino :wink:
(il n'y a pas que arduino dans la vie, avant arduino il fallait bien faire sans)

Super_Cinci:
Oui, mais si tu fais directement PORTB |= 0x04, par exemple, tu gagnes une instruction par rapport à PORTB |= (1 << 2), donc du temps... c'est surtout une question d'habitude... à l'école, on codait en ASM (dans le temps), c'était bien plus transparent...

Le code final ne sera pas forcément différent, parce que le compilateur se charge justement d'optimiser tout ça. Un compilateur qui ne convertirait pas un (1 << 2) en 0x04 ne serait pas très efficace, c'est justement le genre de choses qu'on attend de lui.

En examinant les listings assembleur produits par avr-gcc on peut voir exactement ce qui change dans le code assembleur généré.

je serais curieux de voir le résultat qui ne prend que 66 octets (donc une 30aine d'instructions ASM)... on diviserait alors la taille d'un prog par 20? hum! intéressant!

J'avais un doute sur dumyOut = dumyOut << 2; ou dumyOut << 2; , je n'ai rien trouvé à ce sujet...

Evidemment, je pense que la fonction delay() de l'arduino doit prendre un bon pourcentage de mes 724 octets... pour en plus ne pas être très précise.

Si j'avais le temps, je passerais le tout en assembleur, mais là, j'avoue que j'ai la flegme.

à suivre tout ça!

Pour aller un peu plus loin, j'ai fait les compilations suivantes :

bareMinimum : setup() et loop() vides : 450 octets,
avec "delay(2);" dans loop() : 640 octets, (+190 octets)
avec "delay(2); delay(3);" dans loop() : 652 octets, (+12 octets)
avec "delay(2); delay(3); delay(5);" dans loop() : 664 octets, (+12 octets)

un appel de fonction delay() prendrait alors 12 octets (6 word instructions?), donc la fonction delay() prendrait 178 octets.

Par ailleurs, (car du coup, je m'intéresse aux pertes de l'arduino core), quand on regarde de près Wiring.c, on voit que timer0 est largement sollicité pour déclencher le compteur pour millis() et micros() qui servent de base à la fonction delay(). Etant donné la trop faible précision annoncée pour ces fonctions, les cli() et les attouchements à SREG... ça donne envie de tout virer, pour écrire sa propre fonction delay().

Je comprends de mieux en mieux pourquoi plus on utilise les fonctions core arduino, plus on perd en précision... Ca donne à réfléchir, car moi, je n'utilise jamais directement millis() ou micros(), et j'aimerais bien utiliser le timer0 de temps en temps pour autre chose.

Pour en revenir aux registres, en utilisant bareMinimum, toujours, le poids des instructions suivantes :

DDRD |= 0x02;    // 2 octets
DDRD &= 0x02;    // 6 octets
DDRD = 0x02;    // 4 octets

Hum hum... je croyais que l'asm de l'atmega comprenait ces instructions en 1 seule WI.

Pour les délais il y a la fonction _delay_ms() de avr-libc qui n'utilise que des cycles processeurs.

Pour l'utiliser, il suffit d'ajouter :

#include <util/delay.h>