Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Manufacturing Technology >> Processus de fabrication

Construire une horloge précise avec Arduino Nano et écran LCD 16×2

Composants et fournitures

À propos du projet

J'ai commencé cela comme un exercice académique, mais j'ai fini avec une horloge très précise. Après 5 jours de fonctionnement, elle n'avait ni perdu ni gagné de temps.

Le problème principal avec l'utilisation du seul Arduino est que sa fréquence d'horloge interne n'est pas précise à 100 %. Si on s'y fie uniquement, le comptage des millisecondes s'écarte d'un petit pourcentage et l'horloge créée perd ou gagne du temps. Mon approche a été de tester la précision de l'Arduino utilisé et de déterminer combien de millisecondes il perd ou gagne par heure. Il suffisait ensuite de programmer un ajustement de vitesse pour ajouter ou déduire cette différence du comptage interne des millisecondes chaque heure.

Ma seconde préoccupation était de savoir si l'horloge de l'Arduino était constamment imprécise, mais comme indiqué, l'horloge programmée a maintenu une heure très précise sur 5 jours, il semble donc que l'imprécision soit constante.

Le deuxième problème est que la fonction interne millis() se réinitialise tous les 50 jours environ et qu'on ne peut pas manipuler le compteur de millisecondes. La solution a été de remplacer l'interruption millis() par un compteur que je peux manipuler et qui compte les millisecondes depuis minuit, se réinitialisant chaque jour, supprimant ainsi toute restriction de durée de fonctionnement.

Évaluation de l'imprécision

Pour évaluer l'imprécision, j'ai supposé que l'horloge de mon ordinateur, et donc millis() dans Processing, était précise. J'ai donc créé un programme pour l'Arduino envoyant le nombre de millisecondes écoulées depuis la prise de contact (handshake) vers Processing toutes les 2 secondes, et un script Processing pour lire cela et le comparer à ses propres millisecondes écoulées, affichant un résultat en temps réel et la différence après une heure. Cela a donné le nombre de millisecondes perdues ou gagnées en une heure, donc la valeur à utiliser pour l'ajustement de vitesse dans le programme d'horloge.

Le code du programme Arduino et du script Processing sont fournis ci-dessous.

Si vous n'avez pas Processing installé, visitez processing.org pour le télécharger et en apprendre davantage.

Le code de l'horloge

Les principales zones d'intérêt dans le code de l'horloge sont le réglage de l'interruption, son utilisation et la manière dont la date est stockée et manipulée.

L'interruption

Le code suivant configure une interruption qui se déclenche chaque milliseconde. Cela détourne l'interruption utilisée pour maintenir millis(), donc millis() et delay() ne fonctionneront plus.

// Set up time interrupt - millis() rolls over after 50 days so
// we are using our own millisecond counter which we can reset at
// the end of each day
// Set the CTC mode Compare time and trigger interrupt
TCCR0A = (1 << WGM01);
// Set value for time to compare to OCR0A for 1ms = 249 (8 bits so max is 256)
// [(Clock speed/Prescaler value)*Time in seconds] - 1
// [(16,000,000/64) * .001] - 1 = 249 = 1 millisecond
OCR0A = 0xF9;
// Set timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
// Set the prescale 1/64 clock
// ie 110 for last 3 bits
TCCR0B |= (1 << CS01);
TCCR0B |= (1 << CS00);
// Initialize counter value to 0
TCNT0 = 0;
// Enable interrupt
sei();

Voici le code appelé chaque seconde :

// This interrupt is called when the compare time has been reached
// hence will be called once a millisecond based on the
// OCR0A register setting.
ISR(TIMER0_COMPA_vect) {
  if (currentMode != SET_TIME) currentTime++;
  elapsed++;
}

currentTime et elapsed sont des variables unsigned long. Notez qu'elles sont qualifiées volatile lors de leur définition car nous les manipulons aussi dans le code principal. Cela force le système à lire la variable à chaque utilisation et non à utiliser une valeur en cache.

currentTime stocke le nombre de millisecondes depuis minuit et il y a des routines pour convertir cela en HH:MM:SS et le réinitialiser quand on règle l'heure.

Lorsque 24 heures se sont écoulées, le système déduit le nombre de millisecondes dans une journée du temps et augmente la date de 1 jour. L'horloge n'est donc pas impactée par la valeur maximale que la variable peut stocker, contrairement à millis().

// If at end of the day reset time and increase date
if ((currentMode == SHOW_TIME) && (currentTime > millisecondsInADay)) {
  // Next day
  // Stop interrupts while reset time
  noInterrupts();
  currentTime -= millisecondsInADay;
  interrupts();
  currentDate++;
}

Notez que nous désactivons les interruptions pendant la manipulation de currentTime, sinon l'appel d'interruption pourrait se déclencher au milieu du calcul pour déduire millisecondsInADay et corrompre le calcul.

Après chaque heure écoulée, le système ajuste le nombre de millisecondes écoulées par l'ajustement de vitesse calculé plus tôt, ajustant l'heure courante pour compenser l'horloge interne rapide ou lente.

// At the end of each hour adjust the elapsed time for
// the inaccuracy in the Arduino clock
if (elapsed >= millisecondsInHour) {
  noInterrupts();
  // Adjust time for slow/fast running Arduino clock
  currentTime += speedCorrection;
  // Reset to count the next hour
  elapsed = 0;
  interrupts();
}

Stockage et calcul de la date

La date est stockée sous forme de date julienne, qui est le nombre de jours écoulés depuis le lundi 1 janvier 4713 av. J.-C. Des routines sont incluses pour calculer la date julienne et la reconvertir en calendrier grégorien.

float JulianDate(int iday, int imonth, int iyear) {
  unsigned long d = iday, m = imonth, y = iyear;
  if (m < 3) { m = m + 12; y = y - 1; }
  unsigned long t1 = (153 * m - 457) / 5;
  unsigned long t2 = 365 * y + (y / 4) - (y / 100) + (y / 400);
  return 1721118.5 + d + t1 + t2;
}

void GregorianDate(float jd, int &iday, int &imonth, int &iyear) {
  unsigned long f = jd + 68569.5;
  unsigned long e = (4.0 * f) / 146097;
  unsigned long g = f - (146097 * e + 3) / 4;
  unsigned long h = 4000ul * (g + 1) / 1461001;
  unsigned long t = g - (1461 * h / 4) + 31;
  unsigned long u = (80ul * t) / 2447;
  unsigned long v = u / 11;
  iyear = 100 * (e - 49) + h + v;
  imonth = u + 2 - 12 * v;
  iday = t - 2447 * u / 80;
}

Les boutons de réglage

Le bouton Mode fait avancer le mode courant : Afficher l'heure → Régler l'heure → Régler l'année → Régler la date → Régler l'ajustement de vitesse → retour à Afficher l'heure. Chacun est explicite et utilise les deux autres boutons pour ajuster le paramètre courant.

Une fois l'horloge en marche, si elle avance ou retarde, vous pouvez modifier l'ajustement de vitesse en accédant au mode « Régler l'ajustement de vitesse » et en utilisant les boutons haut/bas pour augmenter ou réduire cet ajustement par pas de 5 secondes.

Code

Programme d'horloge

// Paul Brace - Feb 2021
// Simple Clock with Date created just using a Arduino - no RTC module
// Program incorporates a time correction adjustment to compensate for the internal
// clock speed not being 100% accurate.
// Once correct speed adjustment set the clock is surprisingly accurate.
// In my test it did not lose or gain any time over a 5 day period.
// Displays time on a 16x2 LCD display
// Buttons to set time
// Mode button (pin 2) toggles set time, set date and run
// Button 1 (pin 3) Increments Minutes and Month and decreases Year/speed adj
// Button 2 (pin 4) Increments Hour and Day and increases Year/speed adj
// 24 Hour display

#include 
LiquidCrystal lcd(12, 13, 6, 7, 8, 9);

#define MODE_BUTTON 2
#define HOUR_BUTTON 3
#define UP_BUTTON 3
#define DAY_BUTTON 3
#define MINUTE_BUTTON 4
#define DOWN_BUTTON 4
#define MONTH_BUTTON 4

#define SHOW_TIME 1
#define SET_TIME 2
#define SET_YEAR 3
#define SET_DATE 4
#define SET_SPEED_ADJ 5

int speedCorrection = 3545; // ms mon Nano perd par heure (négatif si rapide)
volatile unsigned long currentTime;
unsigned long lastTime = -1000;
volatile unsigned long elapsed;
unsigned long millisecondsInADay = 24ul * 60 * 60 * 1000;
unsigned long millisecondsInHour = 60ul * 60 * 1000;
int currentMode;
float currentDate;
float lastDate = 0.0;
int currentDay, currentMonth, currentYear;
char *dayArray[] = { "Tue. ", "Wed. ", "Thur. ", "Fri. ", "Sat. ", "Sun. ", "Mon. " };

void setup() {
  TCCR0A = (1 << WGM01);
  OCR0A = 0xF9;
  TIMSK0 |= (1 << OCIE0A);
  TCCR0B |= (1 << CS01);
  TCCR0B |= (1 << CS00);
  TCNT0 = 0;
  sei();
  pinMode(MINUTE_BUTTON, INPUT_PULLUP);
  pinMode(HOUR_BUTTON, INPUT_PULLUP);
  pinMode(MODE_BUTTON, INPUT_PULLUP);
  currentTime = 0;
  currentDate = JulianDate(1, 1, 2021);
  elapsed = 0;
  currentMode = SHOW_TIME;
  lcd.begin(16, 2);
  lcd.noAutoscroll();
  lcd.display();
  lcd.clear();
  ShowTime(currentTime);
}

void loop() {
  if ((currentMode == SHOW_TIME) && (currentTime > millisecondsInADay)) {
    noInterrupts();
    currentTime -= millisecondsInADay;
    interrupts();
    currentDate++;
  }
  if (elapsed >= millisecondsInHour) {
    noInterrupts();
    currentTime += speedCorrection;
    elapsed = 0;
    interrupts();
  }
  CheckButtons();
  switch (currentMode) {
    case SHOW_TIME: ShowTime(currentTime); ShowDate(currentDate); break;
    case SET_TIME: ShowTimeSet(currentTime); break;
    case SET_YEAR: ShowYearSet(currentDate); break;
    case SET_DATE: ShowDDMMSet(currentDate); break;
    case SET_SPEED_ADJ: ShowSpeedSet(); break;
  }
  Wait(150);
}

ISR(TIMER0_COMPA_vect) {
  if (currentMode != SET_TIME) currentTime++;
  elapsed++;
}

// ... fonctions JulianDate, GregorianDate, SplitTime, SetTime, Wait, CheckButtons,
// FormatNumber, ShowTime, ShowDate, ShowDDMMSet, ShowYearSet, ShowTimeSet, ShowSpeedSet
// (voir code complet dans l'original)

Programme Arduino de test du timer

// Paul Brace Feb 2021
// For use with corresponding Processing script
// to compare millis() from here to millis() in
// Processing using the computer clock
int inByte = 0;
unsigned long firstReading = 100000;

void setup() {
  Serial.begin(9600);
  sayHello();
}

void loop() {
  if (Serial.available() > 0) {
    inByte = Serial.read();
    Serial.print(millis() - firstReading);
    Serial.print('E');
    delay(2000);
  }
}

void sayHello() {
  while (Serial.available() <= 0) {
    Serial.print('Z');
    delay(200);
  }
  firstReading = millis();
}

Script Processing de test du timer

// Paul Brace Feb 2021
// Script to accept millis() from Arduino
// and compare it to internal millis() to
// assess inaccuracy of the Arduino clock.
// Assumes that the computer clock is accurate
import processing.serial.*;
Serial theSerialPort;
int[] serialBytesArray = new int[15];
int bytesCount = 0;
boolean init = false;
int fillColor = 255;
long mills = 0, first = 0, now, firstReading = 100000, DiffPerHour = 0;
int inByte;

void setup() {
  size(500, 500);
  background(70);
  noStroke();
  printArray(Serial.list());
  String thePortName = Serial.list()[1];
  theSerialPort = new Serial(this, thePortName, 9600);
}

void draw() {
  background(70);
  fill(fillColor);
  textSize(25);
  text(hour() + ":" + minute() + ":" + second(), 50, 50);
  text("Incoming elapsed: " + mills, 50, 100);
  text("Local elapsed: " + (now - firstReading), 50, 150);
  text("Diff: " + (mills - (now - firstReading)), 50, 200);
  if (((now - firstReading) >= 3600000) && (DiffPerHour == 0)) {
    DiffPerHour = mills - (now - firstReading);
  }
  text("Diff after 1 hour: " + DiffPerHour, 50, 300);
}

void serialEvent(Serial myPort) {
  inByte = myPort.read();
  if (!init) {
    if (inByte == 'Z') {
      myPort.clear();
      init = true;
      myPort.write('Z');
      if (first == 0) first = millis();
    }
  } else {
    if (inByte != 69) {
      if (bytesCount < 14) {
        serialBytesArray[bytesCount] = inByte;
        bytesCount++;
      }
    }
    if (inByte == 69) {
      now = millis();
      mills = 0;
      for (int i = 1; i <= bytesCount; i++) {
        mills += (serialBytesArray[i - 1] - 48) * pow(10, (bytesCount - i));
      }
      if (firstReading == 100000) firstReading = now;
      myPort.write('Z');
      bytesCount = 0;
    }
  }
}

Schémas

Construire une horloge précise avec Arduino Nano et écran LCD 16×2
Construire une horloge précise avec Arduino Nano et écran LCD 16×2

Processus de fabrication

  1. Horloge POV Arduino : Construire un affichage LED à persistance rétinienne
  2. Construire un décodeur DTMF avec un Arduino seul – Aucun CI supplémentaire nécessaire
  3. Créer un système Ambilight spectaculaire pour moniteur avec Arduino
  4. Construire une horloge murale design avec anneaux Neopixel 60 LED Adafruit et Arduino
  5. Horloge à Mots DIY Arduino – Afficheur Temps Réel Élégant et Personnalisable
  6. Horloge Arduino temps réel pour les heures de prière islamiques
  7. Construire une horloge maître LED personnalisée avec alarme – Visible à 12 mètres
  8. Construire un Voltmètre Intelligent avec Arduino et Smartphone – Projet DIY Facile
  9. Moniteur de Fréquence Cardiaque IoT en Temps Réel avec Arduino et MAX30100