[Discussion close]Equivalent digitalRead

Bonjour,

Etape 1:
Je suis toujours avec mon capteur AM2302(DHT22). Toutes les différentes bibliothèques présentent les mêmes défauts. Les utilisateurs se plaignent de trop d’erreurs de somme de contrôle ou de TIMEOUT dépassé.

Certes au début la seule version de la datasheet était en chinois :open_mouth: ce qui ne simplifiait pas le travail. Maintenant une datasheet officielle existe en anglais mais l’architecture de la bibliothèque reste le même.

Ce que je reproche :

  1. attente de réponse du capteur après le relâchement : la data sheet nous dit que le capteur réagit en moyenne au bout de 20µs <Tgo <200µs.
    La sortie du capteur est un collecteur ouvert chargé par 5k ce qui s’appelle aussi un montage émetteur commun. Au repos la sortie n’est pas activée donc on a un état permanent 5V soit 1Logique.
    La librairie impose une pause fixe de 40 µs. Ce qui fait que si le capteur a un temps de réponse de 100µs il est dans la spec et on va droit dans une erreur TIMEOUT dans la réception du premier élément binaire d’acquiescement (0L).
    La solution : utiliser une boucle while (etat haut) { // ne rien faire } Dès que le capteur enverra son premier élément binaire d’asquiessement, un 0L, on sortira de la boucle et on entrera dans la boucle de détection du 0L.

  2. On arrive au point faible : le temps de réponse d’un digitalRead.
    Les temps des éléments binaire 0L et 1L sont les suivants :
    Acquiescement : 0L et 1L = 80 µs (soit 1280 cycles horloge)
    Bit 1L : élément binaire 0L = 50 µs soit 800 cycles horloge
    élément binaire 1L = 70 µs soit 1120 cycles horloge.
    Bit 0L :élément binaire 0L = 50 µs soit 800 cycles horloge
    élément binaire 1L = 26 µs soit 416 cycles horloge.

On voit de suite que la détection d’un élément binaire 1L court va être délicate avec un digitalRead.
Il ne faut pas oublier que le micro et le capteur ne sont pas synchrone et qu’il est toujours possible de rater un début de détection. On a donc intérêt a ce que la boucle soit le plus véloce possible.

  1. Utilisation de la fonction millis() pour déterminer si l’élément binaire 1L est court ou long.
    Millis() comme toutes les fonctions arduino est prévue pour être simple d’emploi, il ne faut pas en plus lui demander d’être optimisée.
    Qu’a -ton besoin exactement : on se fiche de la mesure exacte en µs, on a juste besoin d’avoir une image des longueurs respectives de l’élément binaire 0L et du 1L qui le suit.
    Cette image on l’a déjà dans le code :
loopCnt = DHTLIB_TIMEOUT;
    while(digitalRead(pin) == HIGH)
    {
        if (--loopCnt == 0) return DHTLIB_ERROR_TIMEOUT;
    }

Si on déclare deux variables loop ;
uint8_t loopCnt_0 et uint8_t loopCnt_1

et qu’on transforme très légèrement le code précédent:

loopCnt_0 = 0;
    while(digitalRead(pin) == HIGH)
    {
        if (loopCnt_0++ == DHTLIB_TIMEOUT)  return DHTLIB_ERROR_TIMEOUT;
    }

Il suffira de comparer si loopCnt_1 > loppCnt_0 → on a affaire à un bit 1L sinon c’est un bit 0L.
Cette solution permet de faire gagner un temps non négligeable.

Etape 2 digitalRead et autre: Message suivant

Etape 2

Quel remplaçant pour digitalRead ?

Il y a bien sûr la manipulation des registres, la solution est imbattable mais on est obligé de coder “en dur”, exit la facilité de désigner n’importe quelle pin.

Il y a la solution digitalReadFast mais je n’ai eu que des problèmes avec :

  1. il existe plusieurs versions que rien ne différencie. J’ai eu du mal à en trouver une qui compile avec gcc 4.8.
    GitHub - watterott/Arduino-Libs: Arduino Libs & Examples: ADS1147, ADS7846, DAC8760, DS1307, RV8523, MCP2515, WS2812, S65-Display, MI0283QT-2/-9/-11, HX8347D, ILI9341, SSD1331
  2. utilisée dans un seul fichier cela fonctionne mais avec plusieurs fichiers je n’ai pas compris.

Et il y a une solution dont je voudrais vous parler et qui m’interpelle : je ne suis pas sur de bien l’avoir compris.

En faisant des recherches je suis tombé sur un certain blog d’un certain Skywodd qui traitait du sujet et qui avant moi (2013) émettait des gros doutes sur la compatibilité de digitalRead avec le capteur DHT11. Encore une fois Sky si je t’avais lu avant les autres j’aurais gagné du temps ! Mais comme on dit mieux vaut tard que jamais.

En résumé notre ami Skywodd a retenu l’essentiel de la gestion arduino pin ? PORT et a supprimé tout le superflu.

J’ai voulu m’en inspirer pour créer mes propres fonctions cela fonctionne mais les mesures que j’ai fait ne montrent pas un gain considérable.

Mon instrument de mesure :
le fichier asm et …. NON l’assembleur je n’y connais RIEN.
Mais par contre j’ai des timers. Sur un 328p j’ai pris un timer 8bits puisque j’ai un micro 8 bits, le T2 parce que le T0 sert déjà a trop de chose.
J’ai réglé le préscaler à 1 (TCCR2B= 1) comme cela un incrément sur le compteur correspond à 1 cycle horloge (j’espère que ce n’est pas 2 ? avec la logique synchrone il subsiste un doute).
Pour éviter à avoir à gérer les débordements de compteur comme le fait milllis() je fait systématiquement une raz du compteur TCNT2 avant toute utilisation.

En comparant digitalRead, les fonction inspirées du travail de Skywodd et l’action directe sur les registres j’obtiens les résultats suivant :
Test de rapidité d’accès aux E/S
|Opération |Nbre Cycles horloge|
| | Ard |Rapide| Reg |
|Ecriture 0 | 83 | 54 | 3 |
|Ecriture 1 | 81 | 54 | 3 |
|Lecture 0 | 64 | 48 | 2 |
|Lecture 1 | 62 | 48 | 2 |

Je trouve le gain un peu faible.

Me suis-je planté dans mon instrument de mesure ?
Me suis je planté dans mon code ?

test_sky.ino (4.07 KB)

Bonjour,

68tjs: Me suis-je planté dans mon instrument de mesure ?

Non, ça m'a l'air correct.

68tjs: Me suis je planté dans mon code ?

Un peu.

Tout d'abord, pour le plus évident, les lignes :

volatile uint8_t *ddr = portModeRegister(port);

dans ecritPin() et litPin() ne servent à rien, et comme elles sont déclarée "volatile" le compilateur n'a pas le droit de les virer de lui-même -> perte de performance.

Deuxio, ce qui prend le plus de temps dans un digitalRead(), c'est d'aller chercher en mémoire à quel port correspond le numéro de broche passé en paramètre, puis d'aller chercher l'adresse du registre pour lire ce port et le masque binaire permettant d'en extraire la broche recherchée. Donc pour gagner beaucoup en temps d'exécution il faudrait sortir tout cela de la fonction de lecture pour les "mettre en cache". En utilisant les fonction Arduino type digitalPinToBitMask() tu peux conserver la souplesse/facilité d'utilisation Arduino tout en obtenant une vitesse de lecture égale (ou très proche) de la manipulation de ports pure.

Salut,

+1 pour récupérer au départ les ports/pins en utilisant les fonctions fournies par Arduino. Dans l'absolu toute bonne librairie devrait le faire ...

A l'instanciation récupération des différentes combinaisons port/pin puis il suffit de pointer dessus lorsqu'on a besoin d'un équivalent des digitalRead()write()

Eeeeeeeeuh............ tu peux en dire plus. Parce que gain ,taux d'harmoniques, fréquence de transition, cela me parle clair mais là c'est un peu flou.

Suffit de s'inspirer de digitalRead() ;)

int digitalRead(uint8_t pin)
{
    uint8_t timer = digitalPinToTimer(pin);
    uint8_t bit = digitalPinToBitMask(pin);
    uint8_t port = digitalPinToPort(pin);

    if (port == NOT_A_PIN) return LOW;

    // If the pin that support PWM output, we need to turn it off
    // before getting a digital reading.
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);

    if (*portInputRegister(port) & bit) return HIGH;
    return LOW;
}

En imaginant une lib maLib :

maLib::maLib(int pin) {

    _bit = digitalPinToBitMask(pin);
    _port = digitalPinToPort(pin);
       _inputPort=portInputRegister(_port); // jsuis  pas sûr de mon coup ici, à déclarer (volatile uint8_t *)
}

maLib::read(int pin) {

    return ( *_inputPort &  _bit) ;

}

Il y a bien sûr la manipulation des registres, la solution est imbattable mais on est obligé de coder "en dur", exit la facilité de désigner n'importe quelle pin.

si ton souci n'est pas d'ecrire un lib, mais de connaitre l'etat d'une pin le plus vite possible alors

(extrait de http://forum.arduino.cc/index.php?topic=269527.0)

rxRead1 = (PIND & B10000000) >> 7; //aka read pin 7, the fast way
rxRead2 = (PIND & B00010000) >> 4; //aka read pin 4, the fast way

ne me semble pas une formule magique indécodable.

Le problème n'est pas la manipulation des registre en soi, mais de rendre la lib aussi simple et versatile pour l'utilisateur mais optimisée derrière

@Jean Je n'ai absolument aucune difficulté à manipuler les bits des registres. Simplement je cherche une solution plus rapide que la fonction arduino mais qui permette de passer un numéro de pin en paramètre, car quand on gère du matériel la vitesse d'exécution est importante. Une autre raison est pour la réalisation de montages autonomes : la méthode la plus efficace pour réduire la consommation est de réduire la fréquence horloge. Clairement je ne vois pas comment exploiter un DHT22 avec une horloge de 1 MHz sans passer par les registres alors qu'avec une horloge de 16Mhz et digitalRead() on a déjà 30% d'erreur de somme de contrôle. Et je ne pense pas que le DHT22 soit un cas à part.

@B@tto En quelque sorte c'est ce que je cherche car je pense que les applications sont nombreuses.

Message subliminal : :grin: tout bout de code fonctionnel avec "quelques" explications est le bienvenu.

dans ce cas, le model maLib de b@tto devrai etre quasi universel.

le digitalRead officiel est censé parer/corriger toute erreur de debutant. les deux test if sont inutil si t'es sur du code. t'as qu'a les virer dans wiring_digital.c et tu regarde ce que ca donne.

Je testerai sur UNO et MEGA.

68tjs:
@B@tto
En quelque sorte c’est ce que je cherche car je pense que les applications sont nombreuses.

Message subliminal : :grin: tout bout de code fonctionnel avec “quelques” explications est le bienvenu.

En fait y’a rien de bien complexe : dans le pin_arduino.h de ton choix situé dans /hardware/arduino/variants, il y a (entre autre) la correspondance pin et port pour un numéro de pin Arduino donné.

Pour une lecture par registre, on écrirait par exemple etat=PIND & 0b00000001; pour le pin PD0. On a donc besoin de 2 infos : le port et le masque pour le pin. Pour le second pas vraiment de soucis, pour le premier en fait il faut le faire en deux fois : récupérer le port, puis récupérer le registre input de ce port (concrètement sur le port D il y a DDRD, PIND et PORTD, il faut donc pointé vers le bon).

Il suffit alors de récupérer le masque de pin avec digitalPinToBitMask(pin), le port avec digitalPinToPort(pin) et un pointeur vers le registre input avec portInputRegister(_port).

Ainsi à l’instanciation on mets ces infos en mémoire et on exécute ainsi tout ce qui est long. Et à l’appel de read() il n’y a plus qu’à faire comme si on avait directement manipulé les registres.

j’ai fait un essai sur UNO et ide 1.0.5, avec une simple resistance 10K entre pin 2 et 4.

pour 50000 iterations, le digitalRead officiel donne 861ms.
lorsque je modifie la wiringdigital.c, on descend a ~700 et des bananes.
lorsque je passe une routine digitalRead2 directement dans le script, je tombe a 530ms.

unsigned int count =0;
unsigned long time = millis();

void setup() {
  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  pinMode(4, INPUT);
  digitalWrite(4, HIGH);
  Serial.begin(9600);
}

void loop() {
  while (count<50000){
    if (digitalRead2(4)==LOW) {
      digitalWrite(2, HIGH);
      count++;
    } 
    else {
      digitalWrite(2,LOW); 
    }
  }
  
  time = millis() - time;
  Serial.println(time);

  while (true){
  }
}

char digitalRead2(uint8_t pin)
{
	//uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);

	//if (port == NOT_A_PIN) return LOW;

	// If the pin that support PWM output, we need to turn it off
	// before getting a digital reading.
	//if (timer != NOT_ON_TIMER) rien();

	if (*portInputRegister(port) & bit) return HIGH;
	return LOW;
}

Perso je verrai bien un truc dans ce goût là:

class DigitalPin
{
  private:
    uint8_t _arduino_pin;
    uint8_t _bitmask;
    volatile uint8_t *_out;
    volatile uint8_t *_in;
    volatile uint8_t *_ddr;
    
  public:
    DigitalPin(uint8_t arduino_pin);
    void setMode(uint8_t mode);
    int8_t read();
    void set(uint8_t value);
    uint8_t getArduinoPin();
};

// Constructeur, prend en paramètre un numéro de broche "arduino"
DigitalPin::DigitalPin(uint8_t arduino_pin) {
  uint8_t port = digitalPinToPort(arduino_pin);
  _arduino_pin = arduino_pin;
  _bitmask = digitalPinToBitMask(arduino_pin);
  _out = portOutputRegister(port);
  _in = portInputRegister(port);
  _ddr = portModeRegister(port);
}

// Lit l'état actuelle de la broche, retourne HIGH ou LOW
int8_t DigitalPin::read() {
  if (*_in & _bitmask) return HIGH;
  return LOW;
}

// Modifie l'état d'une broche (si c'est une sortie). Prend HIGH ou LOW en paramètre
// Si la broche est une entrée, active ou désactive la résistance de pullup
void DigitalPin::set(uint8_t value) {
  uint8_t sreg_backup = SREG;
  cli();

  if (value) {
    *_out |= _bitmask;
  }
  else {
    *_out &= ~_bitmask;
  }
  SREG = sreg_backup;
}

// Modifie le mode de fonctionement de la broche : INPUT, INPUT_PULLUP ou OUTPUT
void DigitalPin::setMode(uint8_t mode) {
  uint8_t sreg_backup;
  if (mode == INPUT) {
    sreg_backup = SREG;
    cli();
    *_ddr &= ~_bitmask;
    *_out &= ~_bitmask;
    SREG = sreg_backup;
  }
  else if (mode == INPUT_PULLUP) {
    sreg_backup = SREG;
    cli();
    *_ddr &= ~_bitmask;
    *_out |= _bitmask;
    SREG = sreg_backup;
  }
  else {
    sreg_backup = SREG;
    cli();
    *_ddr |= _bitmask;
    SREG = sreg_backup;
  } 
}

//  Renvoie le numéro de broche "Arduino" associé à l'objet
uint8_t DigitalPin::getArduinoPin() {
  return _arduino_pin;
}

Pas testé en grandeur réelle, juste la compilation qui est ok.

Qu'on peut utiliser comme ceci par exemple :

DigitalPin d5(5);

void setup() {
  d5.setMode(INPUT);
}

void loop() {
  // ...
  if (d5.read()) {
    // ...
  }
  // ...
}

EDIT: modifié un peu la classe pour ne pas conserver en cache la variable 'port' qui n'est pas utile en dehors du constructeur. Commenté un peu le code...

68tjs: Eeeeeeeeuh............ tu peux en dire plus. Parce que gain ,taux d'harmoniques, fréquence de transition, cela me parle clair mais là c'est un peu flou.

:grin: bonjour J'ai (eu) quasi la meme reaction que toi 8)

mais avec les explications/essais des "traqueurs" , je comprend déjà (un peu) mieux :grin:

tu es sur quelle carte/controleur a quelle frequence? A0 et A5 sont ils relié directement?

ton test_sky avec IDE 1.0.5 pour une UNO a 16Mhz: | | Ard |Rapide| Reg | |Ecriture 0| 65 | 48 | 3 | |Ecriture 1| 63 | 48 | 3 | |Lecture 0| 60 | 14 | 2 | |Lecture 1| 61 | 27 | 2 |

pour une MEGA2650 a 16Mhz: |Ecriture 0| 78 | 49 | 3 | |Ecriture 1| 76 | 49 | 3 | |Lecture 0| 69 | 14 | 2 | |Lecture 1| 69 | 27 | 2 |

comparé a tes resultats, le gain est suffisement significatif, et la différence me fait dire qu'il y a autre chose. certainement du coté de timer0.

Mmmm, tes résultats sont étonnants jean-l. À voir le code publié, il n’y a aucune raison que le temps d’exécution de litPin() soit différent lorsqu’on lit un 0 ou un 1 (contrairement à digitalRead() qui possède une condition sur la valeur de retour qui entraîne en écart de 2 cycles).

Je n’ai pas de quoi tester moi-même à l’instant, mais cette constatation me laisse penser que le tableau de 68tjs est “plus correct” que le tiens…

@Jean-1

Ma carte est une nano sortie de l’emballage c’est à dire ATMega328p, 5V ,16 MHz.

A0 et A5 sont reliés directement et rien d’autre n’y est raccordé.

Salut,

Ton code de test n'est pas représentatif ;)

Tu as ré-écris les fonctions pinMode, digitalRead et digitalWrite, ni plus ni moins. Dans mon code, je met dans des variables les différents pointeurs que je vais utiliser en masse par la suite (dans des boucles à plus de 10000 itérations avec dans des tests inline). Donc au niveau des boucles et des tests c'est comme si je faisait de la manipulation de ports.

Si tu gardes dans des variables statiques (ou globales) les pointeurs que te retournes les différentes fonctions bas niveau et que tu ne fait l'initialisation de ces variables qu'une seule fois tu auras un gain de temps notable ;)

Ouh là je vais chercher du doliprane, j'attend qu'il fasse effet, et je relis mais je ne suis par sûr de comprendre.

Dans mon vocabulaire : pointeur = gros mot :grin:

Dommage une fois qu'on a pigé les pointeurs c'est quand même vachement utile ;)