Commande des tubes nixie
Logiciel pour lire l'état des boutons et commander les tubes
TechTalk
Root » Serveurs » ElectroNique » Nixie » Le logiciel

Le listing arduino pour la commande de l'horloge
-

-

Sur la page précédente, nous avons décrit la logique générale que nous utilisons.

La programmation est en language C avec quelques fonctions supplémentaires pour lire et écrire aux entrées/sorties. Ce language, contrairement aux languages qui ont suivi (c++, C#, Java) est un language encore proche du language machine et permet d'écrire un code très compact. Cela peut être nécessaire dans les projects complexes, car vous disposez de 32.256 bytes pour le programme compilé et de 2048 bytes pour le stockage des variables. Il faut utiliser des variables de type byte si vous ne stockez que de faibles valeurs, limiter l'appel de fonctions et la transmission de paramètres (qui en C se fait toujours "byval").

Il est également possible de stocker des valeurs en mémoire EEPROM, ces valeurs ne sont pas perdues en cas de reset ou de coupure de courant. La capacité de mémoire EEPROM dépend de la version du processeur.


#include <EEPROM.h>
#include <Wire.h>
#include <RTClib.h>
#define ADD1 2            // pin de l'adresse où envoyer les data
#define ADD2 3
#define ADD3 4

#define DATA 5            // pin qui reçoit un bit à la fois
#define ALARMOUT 9        // sortie alarme

#define HOUR 6            // pin du choix du latch (chip enable)
#define MIN 7
#define SEC 8

L'attribution des entrées et sorties est effectuée par des constantes. Si un port est défaillant, on peut en utiliser un autre (s'il en reste encore) et changer la valeur de la constante. L'utilisation de valeurs nommées rend également le programme plus lisible.


const byte nbBits[6] = {4, 3, 4, 3, 4, 2};   // nombre bcd bits
const byte nsBits[6] = {SEC, SEC, MIN, MIN, HOUR, HOUR}; // selector à utiliser

Les dixaines n'utilisent pas tous les bits d'un nombre BCD. Pour les heures il suffit de deux bits pour coder 0, 1 et 2, pour les minutes et les secondes il suffit de 3 bits (0, 1, 2, 3, 4 et 5). Quand le nombre de bits à utiliser est de 4, c'est qu'il s'agit des unités (ce test est utilisé plus tard pour addresser le latch).

Le second tableau indique le port à utiliser pour le chiffre correspondant.


byte blinkposition = 0;
bool blinkallume = false;   // chiffre allumé
bool alarmset = false;      // mode programmation alarme
byte h, m, s, ah, am, htemp, mtemp;
byte aset;		              // alarme active
bool alarmsonne;	              // signal d'alarme (buzzer...)
bool alarmreset;
bool hourChanged = true;    // heure ne correspond plus à l'affichage
long lastTime = 0;

Les variables globales (disponibles dans tout le programme). Les variables de type "bool" utilisent également un octet en mémoire (aucun gain de place par rapport au type "byte"). Le processeur est un processeur à 8 bits (bus de données).


void setup() {
  Serial.begin(9600);
  pinMode(ADD1, OUTPUT);  pinMode(ADD2, OUTPUT);  pinMode(ADD3, OUTPUT);
  pinMode(DATA, OUTPUT);  pinMode(ALARMOUT, OUTPUT);
  pinMode(HOUR, OUTPUT);  pinMode(MIN, OUTPUT); pinMode(SEC, OUTPUT);
  h = ah = EEPROM.read(0); m = am = EEPROM.read(1); aset = EEPROM.read(2);
  s = 55; rtc.begin();
}

Cette routine est effectuée une fois au démarrage (mise sous tension). Il faut définir si les port numériques sont utilisés en entrée ou en sortie. Les données de l'EEPROM sont également lues (heure de l'alarme et alarme activée).

L'instruction rtc.begin() doit être présente si vous utilisez une horloge externe (real time clock).


void sndAlarm() {
  digitalWrite(ADD3, 0); digitalWrite(ADD2, 1); digitalWrite(ADD1, 1);
  digitalWrite(DATA, aset);
  digitalWrite(HOUR, 0);   delay(1);      digitalWrite(HOUR, 1);
}

Indication de la fonction alarme enclenchée ou non, indiquée par une led alumée. Si l'alarme est enclenchée, l'alarme retentit pendant 5 secondes lors de la mise en route.


void sndCh(byte posit, byte valeur) {
  static byte i;
  blinkallume = true;
  for (i = 0; i < nbBits[posit]; i++) {
    digitalWrite(ADD3, bitRead(i, 0));
    digitalWrite(ADD2, bitRead(i, 1));
    if (nbBits[posit] == 4) { digitalWrite(ADD1, 0); }
    else                    { digitalWrite(ADD1, 1); }
    digitalWrite(DATA, bitRead(valeur, i));
    digitalWrite(nsBits[posit], 0);   delay(1);
    digitalWrite(nsBits[posit], 1);
  }
}

Routine pour écrire un chiffre à une position donnée. Nous écrivons 2, 3 ou 4 bits selon la position. La fonction bitRead(i, j) produit un 1 ou un 0 selon qu'il y a un 1 ou un 0 à la position j de la valeur i. La variable blinkallume est mise à la valeur true pour indiquer que le chiffre est allumé: s'il doit clignoter, la variable est testée plus tard pour effacer le chiffre.

L'adresse (ADD1, ADD2 et ADD3) est l'adresse à écrire, on écrit en fait un bit à la fois de la valeur BCD. S'il s'agit de dixaines, on utilise une adresse 1xx. On place ensuite la donnée sur le bus des données (en fait un bus d'un seul bit de large) et on active le latch correspondant.


void incrHour() {
  hourChanged = true
  s++;
  if (s > 59) {
    s = 0; m++;
      if (m > 59) {
        m = 0, h++;
        if (h > 23) {
          h = 0;
        }
      }
    }
  }

Cette routine est utilisée pendant la période de test avec augmentation des minutes et secondes, quand nous ne disposons pas encore d'horloge externe (real time clock).

Sans synchronisation avec l'horloge, mon arduino avance de 30 secondes par jour. La routine peut être éliminée quand on dispose d'une horloge externe.


void loop() {
  static int inpx, inpxa;
  static bool sela, selb, plua, plub, alaa, alab;
  static long mils;
  mils = millis();
  if (mils - lastTime > 1000) {
    lastTime = mils;
    // incrHour(); (routine de test tant qu'il n'y a pas de RTC
    hourChanged = true;
    s++;
    if (s > 59) {
      DateTime now = rtc.now();
      h = now.hour(); m = now.minute(); s = now.second();
    }
  }

Routine principale qui est effectuée en boucle. On définit d'abord quelques variables locales statiques (elles gardent leur valeur à chaque passage et ne sont pas détruites quand l'exécution a terminé la routine). L'horloge interne du processeur étant suffisamment précise, on ne resynchronise le processeur qu'une fois par minute. Cette dernière partie (indiquée en rouge) ne doit être ajoutée que quand on dispose de l'horloge externe.


  // --- buttonread -----------------------------
  inpx = analogRead(0);
  if (abs(inpx - inpxa) < 5) {
    alaa = inpx > 750;
    sela = inpx > 450 && inpx < 750;
    plua = inpx > 200 && inpx < 450;
  }
  inpxa = inpx;

Lecture des boutons, effectué via une entrée analogique. Les boutons ont chacun une résistance différente et produisent donc une tension différente. Pour éviter les erreurs de lecture, une valeur n'est acceptée que si elle reste pratiquement identique pendant deux cycles.

Nous détectons ensuite quel bouton est activé.


  // ------------------- A L A R M ----------
  if (alaa & !alab) {
    if (alarmsonne) { alarmreset = true; }
    else if (alarmset) {
      if (EEPROM.read(0) != ah) { EEPROM.write(0, ah); }
      if (EEPROM.read(1) != am) { EEPROM.write(1, am); }
      if (EEPROM.read(2) != aset) { EEPROM.write(2, aset); }
    }
    alarmset = !alarmset; hourChanged = true;
  }

Pour détecter un bouton enfoncé, nous utilisons uniquement le flanc montant pour éviter que la fonction soit activée, desactivée, activée, etc à chaque passage de la boucle.

La programmation du bouton alarme est simple: on bascule l'état de la variable alarmset et quand on quitte l'état alarmset on stocke les valeurs de l'alarme dans la mémoire EEPROM (si elles ont été modifiées).

Le bouton d'alarme permet également de stopper l'alarme quand elle sonne.


  // -------------------- S E L E C T I O N -----
  if (sela && !selb)  {    // bouton selection activé
    if (blinkposition == 0)   { blinkposition = 5;  } // minutes
    else if (blinkposition == 2)   { blinkposition = 0;  }
    else                           { blinkposition--;    }
  }

La programmation du bouton de sélection est encore plus simple: on sélectionne simplement le chiffre suivant.


  // -------------------- + ----------
  if (plua && !plub) {      // bouton plus activé
    if (blinkposition == 0) {
      if (alarmset) {
        if (aset == 0) { aset = 1; }
        else           { aset = 0; }
        digitalWrite(ADD3, 0);
        digitalWrite(ADD2, 1);
        digitalWrite(ADD1, 1);
        digitalWrite(DATA, aset);
        digitalWrite(HOUR, 0);   delay(1);
        digitalWrite(HOUR, 1);
	  }	
    }

En mode alarmset, si aucune sélection n'est activée (chiffre qui clignote), appuyer sur le + active ou desactive la fonction d'alarme, indiquée par un led allumé.


    else {
      if (alarmset) { htemp = ah; mtemp = am; }
      else          { htemp = h;  mtemp = m;  }
      switch (blinkposition) {  // blinkposition va de 5 à 2
        case 2:  // unités de minutes
          if (mtemp % 10 == 9)  { mtemp -= 9; }
          else                  { mtemp++;    }
          break;
        case 3:  // dixaines de minutes
          if (mtemp / 10 == 5)  { mtemp -= 50;}
          else                  { mtemp += 10;}
          break;
        case 4:  // unités d'heures
          if (htemp % 10 == 9)  { htemp -= 9; }
          else if (htemp == 23) { htemp = 20; }
          else                  { htemp++;    }
          break;
        case 5:  // dixaines d'heures
          if (htemp / 10 == 2)  { htemp -= 20;}
          else                  { htemp += 10;}
          break;
      }
      if (htemp > 23)     { htemp = 23; }
      if (alarmset) { ah = htemp; am = mtemp; }
      else          {
        h = htemp; m = mtemp;
        DateTime now = rtc.now(); s = now.second();
        rtc.adjust(DateTime(2018, 1, 1, h, m, s));
		   }
       hourChanged = true;
      }
    }
  }
  

Si une sélection est activée, on met soit les données d'alarme (mode programmation d'alarme) soit l'heure actuelle dans deux variables temporaires et on modifie le chiffre indiqué par la variable blinkposition.

En mode mise à l'heure normale, on adapte également le temps de l'horloge externe. La librairie que j'utilise ne permet pas de modifier une valeur à la fois: je mets donc à chque modification la date sur le premier janvier 2018 (la date n'est pas utilisée). Les secondes sont lues de l'horloge pour les réécrire (mais cette partie n'est pas strictement nécessaire).


  selb = sela; plub = plua; alab = alaa;

  // fonction blink
  if (mils - lastTime > 800 && blinkallume && blinkposition > 0) {
    sndCh(blinkposition, 15);
    blinkallume = false;
  }
  

Le traitement des boutons est terminé. On met les valeurs actuelles des boutons dans une variable pour la détection de flanc.

Si un chiffre doit clignoter, on teste le temps écoulé, on teste si le chiffre est allumé et on teste si un chiffre doit clignoter. Pour l'éteindre, on envoie une valeur "15" qui ne correspond aà aucune valeur BCD valable, ce qui produit l'extinction du chiffre.


  // afficher l'heure
  if (hourChanged) {
    if (alarmset) {
      sndCh(5, ah / 10); sndCh(4, ah % 10);
      sndCh(3, am / 10); sndCh(2, am % 10);
      sndCh(1, 15);      sndCh(0, 15);
    }
    else {
      sndCh(5, h / 10); sndCh(4, h % 10);
      sndCh(3, m / 10); sndCh(2, m % 10);
      sndCh(1, s / 10); sndCh(0, s % 10);
    }
    hourChanged = false;  blinkallume = true;
  }

Affichage de l'heure normale ou de l'heure d'alarme selon le mode de fonctionnement: normal ou aset (mise à l'heure de l'alarme).


  alarmsonne = aset == 1 && h == ah && m == am;
  if (!alarmsonne) { alarmreset = false;   }
  if (alarmsonne && !alarmreset)  { 
    htemp++;  digitalWrite(ALARMOUT, bitRead(htemp, 0));
  }
  delay(1);   // allez fieu, on va un peu faire reposer le processeur
}

Reste encore la détection de l'alarme, qui est activée si l'heure correspond à l'heure d'alarme (l'alarme sonne donc exactement une minute).

alarmreset coupe le son de l'alarme si true, alarmreset est remise à false quand l'alarme ne sonne pas.

Le buzzer est crée par une des sorties numériques qui est mise alternativement à un niveau 0 ou 1: increment à chaque passage et lecture lu bit de poids le plus bas. La variable statique htemp n'est utilisée que lors de la mise à l'heure.

Links to relevant pages - Liens vers d'autres pages au contenu similaire - Links naar gelijkaardige pagina's