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 :

Arduino Nano et millis()


Sujet :

Arduino

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    955
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 955
    Par défaut Arduino Nano et millis()
    Bonjour à tous

    je dois entrainer à l'aide d'un petit moteur pas à pas un mécanisme d'horloge
    pour ce faire, la programmation se base sur la fonction millis() dans ce programme

    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
     
    #define MILLIS_PER_MIN 60000 // millisecondes par minute
    int port[4] = {7, 6, 5, 4};
     
    // Paramètres du moteur et de l'horloge
    #define STEPS_PER_ROTATION 1536 
     
    // attendre un seul pas du stepper
    int delaytime = 3;
     
    // séquence de la commande du moteur pas à pas
    int seq[4][4] = {
      {  LOW,  LOW, HIGH,  LOW},
      {  LOW,  LOW,  LOW, HIGH},
      { HIGH,  LOW,  LOW,  LOW},
      {  LOW, HIGH,  LOW,  LOW}
    };
     
    void rotate(int step) {
      static int phase = 0;
      int i, j;
      int delta = (step > 0) ? 1 : 3;
      int dt = 20;
     
      step = (step > 0) ? step : -step;
      for(j = 0; j < step; j++) {
        phase = (phase + delta) % 4;
        for(i = 0; i < 4; i++) {
          digitalWrite(port[i], seq[phase][i]);
        }
        delay(dt);
        if(dt > delaytime) dt--;
      }
      // power cut
      for(i = 0; i < 4; i++) {
        digitalWrite(port[i], LOW);
      }
    }
     
    void setup() {
      Serial.begin(9600);
      pinMode(port[0], OUTPUT);
      pinMode(port[1], OUTPUT);
      pinMode(port[2], OUTPUT);
      pinMode(port[3], OUTPUT);
      rotate(-10); // pour la course d'approche
      rotate(10); // l'approche fonctionne sans charge lourde
      rotate(STEPS_PER_ROTATION);
    }
     
    void loop() {
      static long prev_min = 0, prev_pos = 0;
      long min;
      static long pos;
     
      min = millis() / MILLIS_PER_MIN;
      if(prev_min == min)
      {
        return;
      }
      prev_min = min;
      pos = (STEPS_PER_ROTATION * min);
      rotate(-10); // pour la course d'approche
      rotate(10); // l'approche fonctionne sans charge lourde
      if(pos - prev_pos > 0) {
        rotate(pos - prev_pos);
      }
      prev_pos = pos;
    }
    Je constate une dérive légère sur 24h à savoir 120s
    le premier réflexe serait de diminuer
    #define MILLIS_PER_MIN 60000 =>> 59916 ( calcul estimé) pour compenser

    mais n'existe-t-il pas une autre approche plus fiable ?

    pascal
    Images attachées Images attachées  

  2. #2
    Modérateur

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

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 282
    Par défaut
    Bonjour Pascal

    La photo c'est le montage en question?

    Si oui, il faut dans l'ordre:
    - remplacer le résonateur céramique par un quartz, ce qui devrait supprimer ou au moins fortement réduire la dérive de temps en lien avec la température ambiante.
    - calibrer le quartz, comme le font les vrais horloges, et qualibrer le quartz revient à ajuster le décompte de mills (ce que tu proposes).

    Delias

  3. #3
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    955
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 955
    Par défaut
    Merci Delias


    La photo c'est le montage en question?
    oui , c'est le moteur en question

    remplacer le résonateur céramique par un quartz, ce qui devrait supprimer ou au moins fortement réduire la dérive de temps en lien avec la température ambiante
    .
    Concrètement quel quartz utiliser stp ?

    En fouillant je suis tombé sur çà :
    https://passionelectronique.fr/intro...timer-arduino/
    en réglant la base de temps à l'aide des registres çà serait une bonne approche ?

    pascal

  4. #4
    Modérateur

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

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 282
    Par défaut
    J'ai peut-être répondu un peu vite.
    Je parlais de la platine électronique de la photo, j'ai cru que la puce c'était un ATTiny et le composant jaune un résonateur céramique. Alors que c'est probablement juste le driver moteur, et son condensateur céramique de découplage.
    Un Arduino Nano officiel utilise déjà un quartz, une copie chinoise par contre ce n'est pas certain.

    millis() utilise les registres, si c'est correctement utilisé, cela aura la même précision.

    Après ton code me paraît très compliqué pour ce que tu cherches à faire. Surtout s'il ne fait que cela.

    J'avais déjà répondu à un problème similaire il y a longtemps sur le forum. Le problème c'est que tu perds beaucoup de précision dans ton calcul.
    Une des bonnes méthodes est expliquée là: https://www.developpez.net/forums/d2.../#post11374505
    J'en avais une plus détaillé, mais pas possible de la retrouver rapidement.

  5. #5
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    955
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 955
    Par défaut
    En regardant le site précédent, j'ai incorporé l'utilisation des registres
    et je me suis dis que j'allais peut-être gagné en précision

    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
     
    #define MILLIS_PER_MIN 60000 // milliseconds per a minute
     
    // ports utilisés pour contrôler le moteur pas à pas
    // si votre moteur tourne dans le sens inverse, 
    // changez l'ordre en {4, 5, 6, 7} ;
    // pour Arduino nano
    int port[4] = {7, 6, 5, 4};
    // for ESP32 (example)
    // int port[4] = {12, 32, 25, 27};
     
    // Paramètres du moteur et de l'horloge
    #define STEPS_PER_ROTATION 1536 // steps for a minute
     
    // attendre un seul pas du stepper
    int delaytime = 3;
     
    // séquence de la commande du moteur pas à pas
    int seq[4][4] = {
      {  LOW,  LOW, HIGH,  LOW},
      {  LOW,  LOW,  LOW, HIGH},
      { HIGH,  LOW,  LOW,  LOW},
      {  LOW, HIGH,  LOW,  LOW}
    };
     
    //***********************************
    void rotate(int step) {
      static int phase = 0;
      int i, j;
      int delta = (step > 0) ? 1 : 3;
      int dt = 20;
     
      step = (step > 0) ? step : -step;
      for(j = 0; j < step; j++) {
        phase = (phase + delta) % 4;
        for(i = 0; i < 4; i++) {
          digitalWrite(port[i], seq[phase][i]);
        }
        delay(dt);
        if(dt > delaytime) dt--;
      }
      // power cut
      for(i = 0; i < 4; i++) {
        digitalWrite(port[i], LOW);
      }
    }
     
    // ======================
    // Setup
    // ======================
    void setup() {
      Serial.begin(9600);
      pinMode(port[0], OUTPUT);
      pinMode(port[1], OUTPUT);
      pinMode(port[2], OUTPUT);
      pinMode(port[3], OUTPUT);
      rotate(-10); // pour la course d'approche
      rotate(10); // l'approche fonctionne sans charge lourde
      rotate(STEPS_PER_ROTATION);
      pinMode (LED_BUILTIN, OUTPUT);
     
      // ======================================================================================================================================================
      // Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
      // ======================================================================================================================================================
      noInterrupts();                 // On désactive les interruptions, pour commencer
     
      // On règle les bits WGM10, WGM11, WGM12, WGM13 pour fonctionner en mode "CTC" (c'est à dire, en mode "comparaison timer <-> valeur de référence")
      bitClear(TCCR1A, WGM10);        // On met le bit WGM10 à 0 (contenu dans le registre TCCR1A)
      bitClear(TCCR1A, WGM11);        // On met le bit WGM11 à 0 (contenu dans le registre TCCR1A)
      bitSet(TCCR1B, WGM12);          // On met le bit WGM12 à 1 (contenu dans le registre TCCR1B)
      bitClear(TCCR1B, WGM13);        // On met le bit WGM13 à 0 (contenu dans le registre TCCR1B)
     
      // Ensuite, on règle les bits CS10, CS11, et CS12 pour que le prédiviseur du timer1 fasse une division par 256
      bitClear(TCCR1B, CS10);         // On met le bit CS10 à 0 (contenu dans le registre TCCR1B)
      bitClear(TCCR1B, CS11);         // On met le bit CS11 à 0 (contenu dans le registre TCCR1B)
      bitSet(TCCR1B, CS12);           // On met le bit CS12 à 1 (contenu dans le registre TCCR1B)
     
      // Puis on active l'interruption du timer1, qui test en permanence s'il y a égalité entre la valeur courant du timer, et la valeur
      // stockée dans un registre de comparaison. En pratique, pour ce faire, on va mettre à 1 le bit OCIE1A dans le registre TIMSK1,
      // afin qu'une interruption soit générée, à chaque fois que la valeur du timer1 sera égale à la valeur qu'on aura renseigné dans le registre OCR1A
      bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)
     
            /* Pour info, on aura pu simplifier l'écriture des lignes ci-dessus, en entrant directement la valeur de tous les bits à la fois, 
             * dans leurs registres correspondants. Pour cela, il aurait fallut écrire les lignes suivantes (au lieu de bitClear/bitSet) : 
             * 
             *      TCCR1A = 0b00000000;      // pour WGM11=0 et WGM10=0
             *      TCCR1B = 0b00001100;      // pour WGM12=1 et WGM13=0, puis CS12=1/CS11=0/CS10=0 pour prédiviseur réglé sur division par 256
             *      TIMSK1 = 0b00000010;      // pour OCIE1A=1, afin d'activer l'interruption par comparaison "A" (test d'égalité entre timer et valeur de registre OCIE1A)
             */
     
     
      // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
      TCNT1 = 0;            // Mise du timer1 à zéro
      OCR1A = 31250;        // Valeur correspondant à 500ms (car 31250 fois 16 µS donne bien 500ms ; pour rappel, ces 16 µS proviennent du calcul 1/(16MHz/256),
                            // avec les 16 MHz correspondant à la fréquence du quartz de l'ATmega328P, et le 256 au réglage du prédiviseur du timer1 fait précédemment)
      interrupts();         // Et, on ré-active les interruptions
     
    }
     
    // ======================
    // Loop
    // ======================
    void loop() {
      static long prev_min = 0, prev_pos = 0;
      long min;
      static long pos;
     
      min = millis() / MILLIS_PER_MIN;
      if(prev_min == min)
      {
        return;
      }
      prev_min = min;
      pos = (STEPS_PER_ROTATION * min);
      Serial.println();
      rotate(-10); // pour la course d'approche
      rotate(10); // l'approche fonctionne sans charge lourde
      if(pos - prev_pos > 0) {
        rotate(pos - prev_pos);
      }
      prev_pos = pos;
    }
     
    // ======================
    // Routine d'interruption
    // ======================
    ISR(TIMER1_COMPA_vect) {
      // À chaque appel d'interruption, on inverse l'état de la LED
      // En bref : si elle était allumée, alors on l'éteint, et si elle était éteinte, alors on l'allume !
      digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }

    les résultats sont les 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
     
    11:29:30.716 -> 
    11:30:30.571 -> 
    11:31:30.491 -> 
    11:32:30.364 -> 
    11:33:30.257 -> 
    11:34:30.147 -> 
    11:35:30.081 -> 
    11:36:29.972 -> 
    11:37:29.859 -> 
    11:38:29.737 -> 
    11:39:29.637 -> 
    11:40:29.502 -> 
    11:41:29.422 -> 
    11:42:29.294 -> 
    11:43:29.231 -> 
    11:44:29.113 -> 
    11:45:28.983 -> 
    11:46:28.900 -> 
    11:47:28.777 -> 
    11:48:28.668 -> 
    11:49:28.575 -> 
    11:50:28.434 -> 
    11:51:28.334 -> 
    11:52:28.268 ->
    les écarts varient quelques peu entre chaque minute , peut-être qu'en finalisant avec millis() on peut arriver à une précision acceptable ?

    pascal

  6. #6
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 899
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 899
    Par défaut
    Citation Envoyé par cobra38 Voir le message
    ...
    OCR1A = 31250; // Valeur correspondant à 500ms (car 31250 fois 16 µS donne bien 500ms ; pour rappel, ces 16 µS proviennent du calcul 1/(16MHz/256),
    // avec les 16 MHz correspondant à la fréquence du quartz de l'ATmega328P, et le 256 au réglage du prédiviseur du timer1 fait précédemment)
    ...
    ==> tout le code dépend au final de la précision de l'horloge et du respect des 16MHz exactement.

  7. #7
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    13 196
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 13 196
    Billets dans le blog
    47
    Par défaut
    Bonsoir,

    Citation Envoyé par cobra38 Voir le message
    Je constate une dérive légère sur 24h à savoir 120s
    le premier réflexe serait de diminuer
    #define MILLIS_PER_MIN 60000 =>> 59916 ( calcul estimé) pour compenser
    À noter que la fonction millis() fait déjà un calcul pour compenser :
    millis() is incremented (for 16 MHz AVR chips and some others) every 1.024 milliseconds, then incrementing by 2 (rather than 1) every 41 or 42 ticks, to pull it back into synch; thus some millis() values are skipped.
    La fonction millis() s'incrémente en fait toutes les 1,024 millisecondes, il y a donc un incrément de 2 (au lieu de 1) toutes les 41-42 pulsations de l'horloge 16MHz pour rattraper le retard et se resynchroniser.

Discussions similaires

  1. Conflit Arduino nano "clone" Yosemite 10.10.5
    Par Kogoro dans le forum Arduino
    Réponses: 0
    Dernier message: 20/06/2017, 12h57
  2. Réponses: 5
    Dernier message: 27/04/2017, 09h44
  3. A propos de LED_BUILTIN sur Arduino Nano
    Par Chamac dans le forum Arduino
    Réponses: 10
    Dernier message: 10/01/2017, 11h59
  4. Réponses: 8
    Dernier message: 23/12/2016, 19h06
  5. Thermocouple avec Arduino Nano v3.0
    Par redui dans le forum Arduino
    Réponses: 13
    Dernier message: 13/12/2016, 12h23

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