[Discussion close]Equivalent digitalRead

Je suis persuadé que tu as raison mais dans 2 ans j'aurais 70 alors même s'il me reste plus que 2 neurones et que je les entretiens c'est quand même plus difficile que 50 ans plus tôt.

Mais promis je vais faire un effort :grin:

haifger a raison.
y'a quand meme un truc qui m'echappe.
ca vient de la facon de compter le temps. la moindre variation de code, et les resultats divergent.
j'ai pas toucher au code originale test_sky dans les resultats postés.

je ne sait pas ou t'as pris le code de skywodd, mais je vois en quoi t'as divergé.

bon... quelques heures de someil en moins plus tard....
la grosse difference entre le fastRead()/Write() et la manip directe des ports, c'est le passage par une subroutine avec parametres.
j'ai meme tenté des trucs du genre

  asm volatile(
    //"push r18 \n\t"
    //"push r19 \n\t"
    "lds r18, portA0 \n\t"
    "lds r19, bitA0 \n\t"
    "and r18,r19 \n\t"
    "sts pinvalA0, r18 \n\t"
    //"pop r19 \n\t"
    //"pop r18 \n\t"
    );

gcc fait plutot bien son job, et ca sera jamais aussi rapide que l'execution linéaire du code sans parametres.
il faut donc prédefinir ses adresses de pin->port/bit, si on cherche a aller vraiment vite.

les variations de temps des qu'on modifi le code, le simple fait de rajouter un Serialprint entre les test, viens tres probablement de timer0.

ce que je m'explique toujours pas, c'est :

  • je pensais que les temps de lecture entre 0 et 1 peuvent etre different a cause de timer0, mais meme apres un cli() en debut de test et un sei() juste apres, en fonction du code intermediaire des test, ca peut etre different pour la lecture uniquement.
    j'ai pas fait l'essai sur des port non-ana, ca vient peut etre de ca.
  • pourquoi 68tjs a des temps significativement differents des miens et pourquoi il descend pas en dessous de 20 en appel fonctions lecture.

@68tjs, tu peut tenter un cli() en debut de test et un sei() juste apres ?

L'adresse du code de Skywodd est dans le baratin au début du fichier source.que j'ai transmis.
Pour les interruptions je regarde.

Le choix du portC est un choix de feignasse : c'était plus simple à câbler.

revenons en a ton probleme de base: pb de com avec le capteur DTH.
(j'en ai pas, jamais testés)
la lib que j'ai est plutot mal faite (meme pour moi qui n'ai jamais apprécié le C a sa juste valeur). en dehors des temps de digitalRead/Write properment dit, j'y vois plusieur source de probleme potentiel:

  // now pull it low for ~20 milliseconds
  pinMode(_pin, OUTPUT);
  digitalWrite(_pin, LOW);
  delay(20);
  cli();
  digitalWrite(_pin, HIGH);
  delayMicroseconds(40);
  pinMode(_pin, INPUT);

ah bon? on fait un delay(20) puis cli() pour ensuite attendre avec delayMicroseconds()?
et pourquoi on ferai pas un cli tout de suite et plusieurs delayMicroseconds() apres?
se baser sur millis alors qu'on fait des cli... hem, hem...

for ( i=0; i< MAXTIMINGS; i++) {
devrait etre
for ( i=MAXTIMINGS; i>0; i--) {
et ajuster if ((i >= 4) && (i%2 == 0)) {
par if ((i =< MAXTIMINGS-4) && (i%2 == 0)) { //att=si MAXTIMINGS est pair/impair...?

et le plus gros, a mon avis:

    while (digitalRead(_pin) == laststate) {
      counter++;
      delayMicroseconds(1);
      if (counter == 255) {
        break;
      }
    }
    laststate = digitalRead(_pin);

counter-- et if(counter) aurai été plus efficace, mais c'est pas le pire.

...
set mode(softeux) = off;
lds r18, mode_hardeux \n\t
sts r18, on \n\t
...

    while (digitalRead(_pin) == laststate) {
...
    }
    laststate = digitalRead(_pin);

c'est pas bon du tout!!! si on arrive a laststate, c'est que laststate != digitalRead(_pin) au moment de l'execution de while.
refaire un digitalRead derriere, c'est prendre le risque de se tromper (surtout s'il y a de l'effet miller).

il aurait donc fallu ecrire laststate = !laststate;
largement plus rapide, et surtout aucun risque de mauvaise interpretation.

voila pour le Hardeux.

set mode(softeux) = on;

un dernier point:
si on sort du for/while par timeout, le capteur peut encore etre en train de transmettre des données.
il aurai fallu donc attendre la fin theorique de transmition avant de rendre la main.

genre delay((nb_bits_attendu - nb_de_bits_recus) * tps_max_bits)

sans quoi, la prochaine com risque d'etre mauvaise si le programme principale reitere trop tot.

Bon, j'ai réfléchi un peu moi aussi et finalement je reviens sur ce que j'ai dit à propos de tes résultats jean-I : le fait que la lecture du 1 et du 0 soit différentes est simplement dû au compilateur qui a optimisé le tout en passant cette fonction "inline", et en gardant une bonne partie des données en cache pour le deuxième appel : du coup ce dernier est nettement plus court que le premier.

En désactivant les interruptions avant les tests et en passant les diverses fonctions en noinline, voici ce que j'obtiens avec ma Uno :

           |  Ard |Rapide| Reg |
|Ecriture 0|  65  | 48   |  3  | 16
|Ecriture 1|  63  | 48   |  3  | 15
|Lecture  0|  60  | 27   |  2  |  7
|Lecture  1|  61  | 27   |  2  |  7

La dernière colonne présente les résultats obtenus avec le code que j'avais proposé un petit peu plus haut.

ce que je m'explique toujours pas, c'est :

  • je pensais que les temps de lecture entre 0 et 1 peuvent etre different a cause de timer0, mais meme apres un cli() en debut de test et un sei() juste apres, en fonction du code intermediaire des test, ca peut etre different pour la lecture uniquement.
    j'ai pas fait l'essai sur des port non-ana, ca vient peut etre de ca.
  • pourquoi 68tjs a des temps significativement differents des miens et pourquoi il descend pas en dessous de 20 en appel fonctions lecture.

bonne analyse, et ca repond a la 1ere question.
comment t'as senti que le compilateur avait passé les fonctions en inline? juste en faisant l'essai du noinline?

il reste la deuxieme question.

jean-I:
comment t'as senti que le compilateur avait passé les fonctions en inline? juste en faisant l'essai du noinline?

En désassemblant le résultat.

il reste la deuxieme question.

Effectivement (enfin "nous" non plus nous ne descendons pas en dessous de 20, mais bon y'a de grandes disparités quand même). Si les interruptions sont bien désactivées au moment des tests, je ne m'explique pas trop ces résultats. À moins peut-être d'avoir des options de compilation totalement différentes. Je suis à cours d'idées pour l'instant.

@Jean-1
J'ai mis cli() et sei() je n'ai pas vu de différence

@haifger.
J'ai essayé ta solution. Comme cela coinçait au début j'ai réparti dans un fichier h et un cpp pour me retrouver dans "du connu" bien j'avais conscience que ce n'était pas indispensable.
Je trouve les résultats suivants

Pins déclarées
Nombre de cycles pour | globales | dans loop()
positionner en sortie : | 33 | 35
positionner en entrée : | 44 | 46
écrire un 0 : | 37 | 39
écrire un 1 : | 37 | 39
lire un 0 : | 30 | 32
lire un 1 : | 30 | 32

Rappel Debian Testing Arduino 1.05 gcc 4.81

Idem avec IDE téléchargée sur le site Arduino gcc 4.3.2

Pins déclarées
Nombre de cycles pour | globales | dans loop()
positionner en sortie : | 45 | 41
positionner en entrée : | 62 | 58
écrire un 0 : | 31 | 31
écrire un 1 : | 31 | 31
lire un 0 : | 35 | 35
lire un 1 : | 35 | 35

Je peux essayer sur une Debian stable Arduino 1.01 mais pour cela il faut que je reboute le PC

Edit je joins les fichiers

test_haifger.ino (1.51 KB)

digitalpin.cpp (1.51 KB)

digitalpin.h (407 Bytes)

j'ai les meme resultats que toi 68tjs avec ton nouveau sketch,
néanmoins tu gagne quelques cycles en faisant

return (*_in & _bitmask);

au lieu de

if (*_in & _bitmask) return HIGH;
return LOW;

et le resultat de haifger est obtenu par un simple copié/collé de son code en tete de sketch au lieu du include.

t'as corrigé la lib DTH?

C'est mieux que l'arduinerie mais ce n'est pas encore ça.

J'ai une idée du coté de l'Eeprom. On peut y copier des couples (Port, Bit)
exemple :
enregistrement_0 = D
enregistrement_1 = 0
enregistrement_2 = D
enregistrement_3 = 1
etc

En utilisant les registres la lecture sur Eeprom interne est excessivement rapide (rien à voir avec la classe EEPROM ni même avec l'utilisation directe des fonctions avr-libc) mais je ne vois pas comment réussir à reconstituer les noms de registre comme "DDRD".
Il me semble avoir compris (?) que DDRD pointe en fait sur l'adresse du registre et que le début des adresses des registres bouge avec la taille du bootloader, bref c'est trop compliqué pour moi.

Quand j'ai posté mon premier message j'avais déjà expurgé et modifié la bibli (origine playground) en fonction de ce que j'avais découvert en lisant la datasheet AOSONG AM2302. Pour la lecture j'utilise directement les registres.
Par contre c'est resté à l'état d'œuvre inachevée de brouillon parce que d'une part je voulais quantifier les marges et que d'autre part j'espérais bien trouver une solution pour ne pas être obligé de coder les ports en dur dans le source.

J'ai vu la lib dont tu fait état mais je l'ai trouvé inutilement bien compliquée. Il me semble qu'elle interdit de faire des mesures à moins de 2mn d'intervalle. Ce n'est ma conception de ce que doit faire une bibli qui n'est qu'un outil. C'est à la personne qui code le programme utilisateur à savoir ce qu'elle fait .

tu veut dire que t'as celle ci (from Arduino Playground - DHTLib):
Arduino/libraries/DHTlib at master · RobTillaart/Arduino · GitHub
version 0.1.13?

ben elle me parrait pas mal du tout cette lib.
t'aurai juste a t'adapter au capteur que tu as, si j'ai la bonne doc:

50µs pour start
26 à 28µs pour 0
70µs pour 1

il y a effectivement un probleme dans la facon de compter le temps dans la lib:

// max timeout is 100usec.
// For a 16Mhz proc that is max 1600 clock cycles
// loops using TIMEOUT use at least 4 clock cycli
// so 100 us takes max 400 loops
// so by dividing F_CPU by 40000 we "fail" as fast as possible
#define DHTLIB_TIMEOUT (F_CPU/40000)

hors, nous avons vu que cette structure

        loopCnt = DHTLIB_TIMEOUT;
        while(digitalRead(pin) == HIGH)
        {
            if (--loopCnt == 0) return DHTLIB_ERROR_TIMEOUT;
        }

ne peut pas ne prendre que 4 cycles, a cause du digitalRead. ca ne devrait pas gener le timeout, mais ca peut decaler l'ensemble de la trame.

j'ai donc testé ( dans test_sky.ino)

  uint16_t loopCnt = 3;  //ne pas depasser car overflow
  TCNT2 = 0;
  while(digitalRead(A0) == LOW)
    {
        if (--loopCnt == 0) break;
    }
  cycle_L_ard1 = TCNT2 ;

fixe a 66 pour HIGH
fixe a 179 pour LOW
on a quand meme le temps minimum de ~59cycles soit ~4µs

d'autre part, 70-28=42, 28+(42/2) = 49
if ((micros() - t) > 40)
aurai du etre plus proche des 50, meme + 4µs, et il manque encore "if ((micros() - t) > 40)" qui prend du temps. soit ~30% d'ecart a la moyenne de ton capteur.

68tjs, tu devrai tester 55 ou 60 comme valeur.

Je hais micro() dans cette application :grin:
C'est parfaitement inutile, complication pour le plaisir et perte de temps.
Plus un montage électronique est simple plus il est performant, en programmation c'est pareil.

Relis le premier message et tu verras qu'avec un jeu de deux variables "loopCnt1" pour les boucles détectrices de l'état haut et "loopCnt0" pour l'état bas il suffit de faire :
si loopCnt1 plus grand que loopCnt0 alors on a affaire à un eb "1" long donc un bit "1"
si ce n'est pas le cas c'est bit 0.
(Pour l'application DHT22 1 bit au sens de quantité d'information = 1 élément binaire (eb) 0 suivi d'un eb 1 court ou long)
Les deux appels à micro() bouffent au minimum 160 cycles au total pour strictement rien.

Dans la connerie "les méprises" aussi il y a aussi le temps constant juste après le relachement de la ligne par le micro.
Ce temps correspond au temps que met le capteur pour commencer à émettre ses deux eléments binaires (eb) d'acquiessement.
Ce temps peut varier selon le capteur de 20µs à 200 µs avec "un centrage" à 30µs.
Le fait de se dire le typique c'est 30 , je prend 40 et je suis tranquille est une erreur.
Si le capteur prend 100µs pour envoyer son eb 0 d'acquiessement la ligne restera à l'état haut pendant 100µs. Avec le delai fixe, au bout de 40µ on cherchera à détecter la fin d'un état bas alors que la ligne restera à l'état haut pendant encore 100-40 = 60 µs. Ce qui fait que tous les bits seront faux. Il faut remplacer le delai fixe par une boucle while détectant la fin de l'état haut.

Avec digitalRead + millis() on court le risque de prendre trop de temps pour discriminer les 1 des 0 ce qui fait que la mesure de l'eb 0 suivant peut être plus courte qu'un eb 1 court, qui de ce fait sera vu comme un eb 1 long

Tests réels :
Si je fais tourner pendant 1 heure la lib du playground j'ai entre 20 et 30 % d'erreur checksumm qui se produisent moitié aléatoirement, moitié par rafale. Alors qu'en utilisant les registres et en virant millis() je n'ai strictement aucune erreur.

Autre piste de réflexion ? je précise que je n'ai jamais utilisé de capteur type DHTxx donc ce que je vais dire se base uniquement sur les informations fournies dans ce fil ? pourquoi ne pas utiliser la fonctionnalité «input capture» du timer1?? Il me semble qu'elle est faite presqu'exactement pour ce genre de cas.

Inconvénients :

  • Une seule broche disponible (8 / PB0) pour cette fonctionnalité
  • Monopolise le timer1 (du moins pendant la lecture)
  • Pas de fonctions Arduino directement disponibles pour le mettre en place (mais pas super compliqué non plus pour quelqu'un qui sait manipuler les registres)

Avantages :

  • Le travail est presque exclusivement fait par le hardware
  • En utilisant les interruptions la lecture devient non bloquante du point de vue du CPU
  • Alignement «automatique» sur le début de trame
  • Gestion du bruit/parasites incorporée

Bien sûr ceci n'est valable que pour la lecture. Si jamais il y a aussi des phases d'écriture avec les mêmes timings l'idée tombe un peu à l'eau :slight_smile:

Je pense qu'on peut clore le débat en disant que nous n'avons pas trouvé de méthode vraiment efficace pour conserver les avantages de la dénomination arduino des sorties tout en ayant une rapidité proche de celle des registres.

J'ai trouvé la discussion très enrichissante et la méthode que tu as proposé me plaît bien. On gagne un rapport 2 ce n'est pas négligeable, et ensuite c'est très propre.
C'est d'ailleurs très proche ( et plus universel :grin: ) de ce qui se fait avec les bibliothèques Mbed où 3 objets sont mis à disposition:

  • DigitalIn
  • DigitalOut
  • DigitalInOut

Quant à la gestion du DHT22 à l'aide des timers, c'est sûrement efficace mais avec un micro comme le 328p qui ne dispose que de 3 timers qui plus est qui sont déjà utilisés par différentes bibliothèques on se dirige droit vers des conflits.

Pour la gestion d'un DHT22 avec une horloge à 16 MHz ta solution devrait être suffisante pour éliminer les erreur de somme de controle. Pour une utilisation à 8 ou 1MHz d'horloge je ne vois que la solution registre.

Merci à tous.

68tjs:
Pour la gestion d'un DHT22 avec une horloge à 16 MHz ta solution devrait être suffisante pour éliminer les erreur de somme de controle. Pour une utilisation à 8 ou 1MHz d'horloge je ne vois que la solution registre.

Juste une dernière remarque avant de clore définitivement, je pense au contraire qu'à 1MHz la seule solution viable serait le timer1 en mode input capture. Faire du polling de lecture des registres serait à mon avis trop lent. Mais ce n'est bien sûr qu'une intuition :slight_smile: Faudra que je me commande un de ces joujoux un jour pour tester.

je vois...

DDRD n'est pas un registre au sens ALU.
(voir DS summary)
c'est un emplacement periferique ou le CPU peut y faire un acces rapide.
ces registres sont toujours dans la 1ere page memoire, le nombre possible, souvent 0x00 à 0xFF. les acces se font par le bus de CONTROL du CPU (IOREQ) ou de maniere transparente s'il sont mappé en memoire.
si t'as deja monté des processeurs + ROM + RAM + UART + ..., tu devrais savoir a quoi je fais reference.
le nombre reel depend de ce que le fondeur met comme peripherique dans son controleur (si j'envoi un courrier a une adresse, je suis censé savoir que l'adresse existe).
l'adresse de ces registres ne peut pas changer, puisqu'ils sont gravés en dur et aussi dur que le silicium. ca ne depend pas de la taille du code bootloader ni quoi que ce soit d'autre.
venir lire l'eeprom signifie faire une requete a cet equipement et ne sera pas plus court (voir lus long) que d'acceder a la ram ou la flash ou le registre du port.
(voir DS complete)

on en reviens donc toujours a la meme chose: definir a la compilation ce que doit faire digitaleReadA0/A1/A2...., chacune etant un code different, et pas une subroutine avec parametres.
autre solution pour continuer a utiliser l'ide, serai de hacker le capteur et rallonger les temps de maniere compatible au digitaleRead officiel.

bien vu, les interruptions! le must, mais dixit pour utiliser n'importe quelle pin.