Возможно ли сделать таймер с точностью до 1 миллисекунды ? Использование светодиода MAX7219

Я использую светодиодный дисплей Max7219 (8 цифр, 7 бит), чтобы попытаться сделать таймер. Я загрузил пример кода со следующего сайта для установки светодиодных цифр:

https://www.brainy-bits.com/arduino- таймер обратного отсчета с использованием дисплея max7219/

Однако исходный код просто зацикливается настолько быстро, насколько это возможно, и искажает таймер, так что это не истинное время. Я попытался добавить задержку в 10 мс, чтобы уменьшить 10 мс в самой правой цифре, чтобы третья цифра справа была секундами, однако я обнаружил, что это неточно и занимает немного больше 1 секунды.

Мой вопрос: можно ли сделать таймер с точностью до 1 мс с помощью светодиодного дисплея, и если да, то как мне это сделать?

/* CountDown Timer version 2 using Max7219

Created by Yvan / https://Brainy-Bits.com
This code is in the public domain...
You can: copy it, use it, modify it, share it or just plain ignore it!
Thx!

*/

#define Max7219DIN 7  // Контакт 7 подключен к DIN (DataIN)
#define Max7219CLK 6  // Контакт 6 подключен к CLK
#define Max7219CS 5   // Контакт 5 подключен к CS

#define Buzzer 8  // Контакт 8 подключен к Buzzer + положительный контакт
int BuzzTrigger=0;  // Переменная для хранения значения срабатывания зуммера

#include "LedControl.h"  // Библиотека LedControl, созданная Эберхардом Фале на http://playground.arduino.cc/Main/LedControl

LedControl lc=LedControl(Max7219DIN,Max7219CLK,Max7219CS,1);   // Последнее число представляет количество подключенных модулей Max7219

long int countnumber=24001000 ; // Начальное значение таймера обратного отсчета ЧЧ:ММ:СССС

// Переменные для хранения отдельных чисел
int  firstnum=0;
int  secondnum=0;
int  thirdnum=0;
int  fournum=0;
int  fivenum=0;
int  sixnum=0;
int  sevennum=0;
int  eightnum=0;

void setup() {
    lc.shutdown(0,false);  // Разбудить дисплей
    lc.setIntensity(0,7); // Установить яркость 0-15
    lc.clearDisplay(0);  // Очистить дисплей
    pinMode(Buzzer, OUTPUT);
    digitalWrite(Buzzer, LOW);  //Зуммер выключен при запуске


}

void loop() {

    for (countnumber; countnumber != -1; countnumber--) {
        String mystring = String(countnumber); // Преобразование Counter Int в String для обработки

        // Преобразование числа в значение времени
        for (int z = 0; z < 6; z++) {

            if (mystring.substring(z) == "999999") {
                countnumber = (countnumber - 400000);
            }

            if (mystring.substring(z) == "9999") {
                countnumber = (countnumber - 4000);
            }
        }

        // Отображать число на дисплее в зависимости от количества оставшихся цифр
        if (countnumber > 9999999) {
            firstnum = ((countnumber / 10000000) % 10);
            secondnum = countnumber / 1000000 % 10;
            thirdnum = countnumber / 100000 % 10;
            fournum = countnumber / 10000 % 10;
            fivenum = countnumber / 1000 % 10;
            sixnum = countnumber / 100 % 10;
            sevennum = countnumber / 10 % 10;
            eightnum = countnumber % 10;

            lc.setDigit(0, 7, firstnum, false);
            lc.setDigit(0, 6, secondnum, false);
            lc.setDigit(0, 5, thirdnum, false);
            lc.setDigit(0, 4, fournum, false);
            lc.setDigit(0, 3, fivenum, false);
            lc.setDigit(0, 2, sixnum, false);
            lc.setDigit(0, 1, sevennum, false);
            lc.setDigit(0, 0, eightnum, false);
            delay(10); //<<<<<<<<<< Я ДОБАВИЛ ЭТО
        } else {
            if (countnumber > 999999) {
                // firstnum = ((countnumber/10000000)%10);
                secondnum = countnumber / 1000000 % 10;
                thirdnum = countnumber / 100000 % 10;
                fournum = countnumber / 10000 % 10;
                fivenum = countnumber / 1000 % 10;
                sixnum = countnumber / 100 % 10;
                sevennum = countnumber / 10 % 10;
                eightnum = countnumber % 10;

                lc.setChar(0, 7, '-', false);
                lc.setDigit(0, 6, secondnum, false);
                lc.setDigit(0, 5, thirdnum, false);
                lc.setDigit(0, 4, fournum, false);
                lc.setDigit(0, 3, fivenum, false);
                lc.setDigit(0, 2, sixnum, false);
                lc.setDigit(0, 1, sevennum, false);
                lc.setDigit(0, 0, eightnum, false);
                delay(10); //<<<<<<<<<< Я ДОБАВИЛ ЭТО
            } else {
                if (countnumber > 99999) {
                    // firstnum = ((countnumber/10000000)%10);
                    // число секунд = число_счетчиков/1000000%10;
                    thirdnum = countnumber / 100000 % 10;
                    fournum = countnumber / 10000 % 10;
                    fivenum = countnumber / 1000 % 10;
                    sixnum = countnumber / 100 % 10;
                    sevennum = countnumber / 10 % 10;
                    eightnum = countnumber % 10;

                    lc.setChar(0, 7, '-', false);
                    lc.setChar(0, 6, '-', false);
                    lc.setDigit(0, 5, thirdnum, false);
                    lc.setDigit(0, 4, fournum, false);
                    lc.setDigit(0, 3, fivenum, false);
                    lc.setDigit(0, 2, sixnum, false);
                    lc.setDigit(0, 1, sevennum, false);
                    lc.setDigit(0, 0, eightnum, false);
                    delay(10); //<<<<<<<<<< Я ДОБАВИЛ ЭТО
                } else {
                    if (countnumber > 9999) {
                        // firstnum = ((countnumber/10000000)%10);
                        // число секунд = число_счетчиков/1000000%10;
                        // третье число = число_счетчиков/100000%10;
                        fournum = countnumber / 10000 % 10;
                        fivenum = countnumber / 1000 % 10;
                        sixnum = countnumber / 100 % 10;
                        sevennum = countnumber / 10 % 10;
                        eightnum = countnumber % 10;

                        lc.setChar(0, 7, '-', false);
                        lc.setChar(0, 6, '-', false);
                        lc.setChar(0, 5, '-', false);
                        lc.setDigit(0, 4, fournum, false);
                        lc.setDigit(0, 3, fivenum, false);
                        lc.setDigit(0, 2, sixnum, false);
                        lc.setDigit(0, 1, sevennum, false);
                        lc.setDigit(0, 0, eightnum, false);
                        delay(10);//<<<<<<<<<< Я ДОБАВИЛ ЭТО
                    } else {
                        if (countnumber > 999) {
                            // firstnum = ((countnumber/10000000)%10);
                            // число секунд = число_счетчиков/1000000%10;
                            // третье число = число_счетчиков/100000%10;
                            // четвёрка = количество/10000%10;
                            fivenum = countnumber / 1000 % 10;
                            sixnum = countnumber / 100 % 10;
                            sevennum = countnumber / 10 % 10;
                            eightnum = countnumber % 10;

                            lc.setChar(0, 7, '-', false);
                            lc.setChar(0, 6, '-', false);
                            lc.setChar(0, 5, '-', false);
                            lc.setChar(0, 4, '-', false);
                            lc.setDigit(0, 3, fivenum, false);
                            lc.setDigit(0, 2, sixnum, false);
                            lc.setDigit(0, 1, sevennum, false);
                            lc.setDigit(0, 0, eightnum, false);
                            delay(10);//<<<<<<<<<< Я ДОБАВИЛ ЭТО
                        } else {
                            // firstnum = ((countnumber/10000000)%10);
                            // число секунд = число_счетчиков/1000000%10;
                            // третье число = число_счетчиков/100000%10;
                            // четвёрка = количество/10000%10;
                            // Fivenum = countnumber/1000%10;
                            sixnum = countnumber / 100 % 10;
                            sevennum = countnumber / 10 % 10;
                            eightnum = countnumber % 10;
a
                            lc.setChar(0, 7, '-', false);
                            lc.setChar(0, 6, '-', false);
                            lc.setChar(0, 5, '-', false);
                            lc.setChar(0, 4, '-', false);
                            lc.setChar(0, 3, '-', false);
                            lc.setDigit(0, 2, sixnum, false);
                            lc.setDigit(0, 1, sevennum, false);
                            lc.setDigit(0, 0, eightnum, false);
                            delay(10);//<<<<<<<<<< Я ДОБАВИЛ ЭТО
                        }

                    }
                }
            }
        }


    }
}

новая реализация с использованием миллиса

#define Max7219DIN 7  // Контакт 7 подключен к DIN (DataIN)
#define Max7219CLK 6  // Контакт 6 подключен к CLK
#define Max7219CS 5   // Контакт 5 подключен к CS


#include "LedControl.h"  // Библиотека LedControl, созданная Эберхардом Фале на http://playground.arduino.cc/Main/LedControl

LedControl lc=LedControl(Max7219DIN,Max7219CLK,Max7219CS,1);   // Последнее число представляет количество подключенных модулей Max7219

unsigned long countnumber;

// Переменные для хранения отдельных чисел
int  firstnum=0;
int  secondnum=0;
int  thirdnum=0;
int  fournum=0;
int  fivenum=0;
int  sixnum=0;
int  sevennum=0;
int  eightnum=0;

void setup() {
  lc.shutdown(0,false);  // Разбудить дисплей
  lc.setIntensity(0,7); // Установить яркость 0-15
  lc.clearDisplay(0);  // Очистить дисплей
  Serial.begin(9600);
}

void loop() {

  countnumber = millis();

  firstnum = countnumber / 10000000 % 10;
  secondnum = countnumber / 1000000 % 10;
  thirdnum = countnumber / 100000 % 10;
  fournum = countnumber / 10000 % 10;
  fivenum = countnumber / 1000 % 10;
  sixnum = countnumber / 100 % 10;
  sevennum = countnumber / 10 % 10;
  eightnum = countnumber % 10;

  lc.setDigit(0, 7, firstnum, false);
  lc.setDigit(0, 6, secondnum, false);
  lc.setDigit(0, 5, thirdnum, false);
  lc.setDigit(0, 4, fournum, false);
  lc.setDigit(0, 3, fivenum, false);
  lc.setDigit(0, 2, sixnum, false);
  lc.setDigit(0, 1, sevennum, false);
  lc.setDigit(0, 0, eightnum, false); 

}

, 👍1

Обсуждение

Ваш код также требует некоторого времени для выполнения. Таким образом, ваш цикл занимает 10 мс плюс время, которое занимает код. Чтобы сделать это 10 мс независимо от объема кода, вы сохраняете значение millis() в начале цикла, а их в конце цикла, ждете, пока разница между millis() и сохраненным значение миллис больше или равно 10., @Gerben

Требование «точность в 1 мс» не имеет смысла, если вы не укажете, в течение какого промежутка времени вам нужна эта точность. Получение точности в 1 мс тривиально, если вы измеряете временной интервал в 100 мс. Это далеко не тривиально, если вы измеряете интервал в 1 год. Точность, которую вы можете получить на Arduino из коробки, зависит от измеряемого интервала, от того, есть ли в вашей Arduino кристалл или резонатор, и от того, хотите ли вы ее откалибровать. Пожалуйста, прочитайте http://jorisvr.nl/article/arduino-frequency, чтобы получить общее представление о том, что может означать «точность» в отношении синхронизации Arduino., @Edgar Bonet

@EdgarBonet как насчет более 5 минут тривиально или нет?, @bakalolo

1 мс ÷ 5 мин = 3,33 промилле. Не тривиально, но выполнимо. Вы не получите такой точности от внутренних часов Arduino, если только это не кристалл, и вы не откалибруете его по надежному эталону. Самым простым решением, вероятно, было бы использование высокоточного RTC, такого как [DS3231](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf), который интегрирует кварцевый генератор с температурной компенсацией (TCXO). Вездесущий DS1307, скорее всего, не будет достаточно точным. Используйте выход 32 кГц или SQW и один из счетчиков вашего MCU. Пожалуйста, отметьте свой вопрос типом Arduino, который вы используете., @Edgar Bonet


2 ответа


1

Как правило, вы не можете полагаться на процессор для правильного ведения времени без часов реального времени где-то в вашей схеме. Тем не менее, лучший способ сделать что-то в определенное время — это вызвать функцию millis(), которая вернет число миллисекунд, прошедших с момента запуска программы . С помощью этого числа вы можете определить, сколько времени что-то заняло (например, 1 секунду), а затем сделать что-то в ответ. Вы можете, например, распечатать, сколько времени занимает ваш цикл, установив длинное целое число в millis() в начале скетча, а затем вычтя его из mills() в конце цикла и распечатав результат.

void loop() {
   long start_time = millis();
   delay(500);
   long end_time = mills();
   Serial.println(end_time - start_time);
}
,

Как вы думаете, это точность без часов реального времени? в течение 20 мс?, @bakalolo

Из [этой статьи](https://ucexperiment.wordpress.com/2012/03/16/examination-of-the-arduino-millis-function/) я понимаю, что миллис увеличивается каждые 1,024 мс, но если ошибка приближается 1 мс, затем значение автоматически корректируется. Таким образом, за 20 мс ошибка может накопиться до половины миллисекунды., @Filip Franik

@bakalolo Попробуйте micros()?, @ElectronSurf


0

Вы можете подумать, что использование примера BlinkWithoutDelay с периодом в 1 мс даст достаточно точный таймер обратного отсчета, но на самом деле это не так. Если вы запустите этот скетч, вы увидите результат 10-секундного теста. Он показывает прошедшее время 10240 мс.

// 2024 хранилища, 251 память.
// Время вышло. миллис () = 10240
unsigned long countDownTime = 10000;   // Время в мс.
unsigned long displayUpdateDelay = 50; // Время в мс.
unsigned long loopDelay = 1;           // Время в мс.
unsigned long currentMillis = 0;
unsigned long previousMillisLoopDelay = 0;
unsigned long previousMillisDisplayUpdate = 0;
byte doOnce = 0;

void setup(){
  Serial.begin(9600);
  Serial.println("Start countdown timer...");
}

void loop(){

  currentMillis = millis();

  if(currentMillis - previousMillisLoopDelay >= loopDelay){
    previousMillisLoopDelay = currentMillis;
    countDownTime--;
    if(countDownTime == 0){
      doOnce = 1;
      Serial.print("Times up. millis() = ");
      Serial.println(currentMillis);
    }
  }

  if(currentMillis - previousMillisDisplayUpdate >= displayUpdateDelay){
    previousMillisDisplayUpdate = currentMillis;
    if(doOnce == 0){
      displayTimeRemaining();
    }
  }

}

void displayTimeRemaining(){
  Serial.println(countDownTime);
}

Если вы измените метод обновления переменной «previousMillis», вы можете значительно улучшить результаты. Вот еще один скетч, который, по крайней мере, дает правильное количество mills даже через 60 секунд. Единственный способ узнать наверняка, насколько точны результаты, — это сравнить таймер обратного отсчета Arduino с тем, который, как известно, очень точен.

// 2032 хранилища, 251 память.
// Время вышло. миллис () = 10000. 60-секундный тест = 60000.
unsigned long countDownTime = 10000;   // Время в мс.
unsigned long displayUpdateDelay = 50; // Время в мс.
unsigned long loopDelay = 1;           // Время в мс.
unsigned long currentMillis = 0;
unsigned long previousMillisLoopDelay = 0;
unsigned long previousMillisDisplayUpdate = 0;
byte doOnce = 0;

void setup(){
  Serial.begin(9600);
  Serial.println("Start countdown timer...");
}

void loop(){

  currentMillis = millis();

  if(currentMillis - previousMillisLoopDelay >= loopDelay){
    previousMillisLoopDelay += loopDelay;
    countDownTime--;
    if(countDownTime == 0){
      doOnce = 1;
      Serial.print("Times up. millis() = ");
      Serial.println(currentMillis);
    }
  }

  if(currentMillis - previousMillisDisplayUpdate >= displayUpdateDelay){
    previousMillisDisplayUpdate += displayUpdateDelay;
    if(doOnce == 0){
      displayTimeRemaining();
    }
  }

}

void displayTimeRemaining(){
  Serial.println(countDownTime);
}

Вот ссылка на хорошую библиотеку для вашего дисплея: библиотека HCMAX7219 Github.

Этот скетч включает в себя код из предыдущих скетчей, за исключением того, что я использовал библиотеку HCMAX7219 вместо кода с brainy-bits.com.

Обновление 2

Вот обновленный скетч, отображающий таймер обратного отсчета в формате ЧЧММССмм с ведущими нулями. Если вы используете/собираете Arduino с хорошим кварцевым генератором, этот скетч может работать достаточно хорошо.

// 2376 байт для хранения, 154 байт для памяти.
// МОДУЛЬ.....УНО/НАНО.......МЕГА
// VCC.........+5В..........+5В
// ЗАЗЕМЛЕНИЕ..........ЗЕМЛЯ..........ЗЕМЛЯ
// CS (ЗАГРУЗКА)....10...........10
// DIN.........11..........51
// CLK.........13...........52
#include <HCMAX7219.h>

const byte CS_PIN = 10;
unsigned long countDownTime = (86400UL * 1000UL) - 10UL; // Время в мс.
unsigned long displayUpdateDelay = 25; // Время в мс.
unsigned long loopDelay = 1;           // Время в мс.
unsigned long currentMillis = 0;
unsigned long previousMillisLoopDelay = 0;
unsigned long previousMillisDisplayUpdate = 0;
byte hours, minutes, seconds;
unsigned int milliseconds;
byte doOnce = 0;

HCMAX7219 HCMAX7219(CS_PIN);

void setup(){

  // Установить яркость дисплея. Допустимые значения: от 0 (мин.) до 15 (макс.).
  HCMAX7219.Intensity(2, 0); // 0 — это первый модуль MAX7219.

}

void loop(){

  currentMillis = millis();
  HCMAX7219.Clear();

  if(currentMillis - previousMillisLoopDelay >= loopDelay){
    previousMillisLoopDelay += loopDelay;
    countDownTime--;
    if(countDownTime == 0){
      doOnce = 1;
      HCMAX7219.print7Seg("TIMES UP", 8);
      HCMAX7219.Refresh();
    }
  }

  if(currentMillis - previousMillisDisplayUpdate >= displayUpdateDelay){
    previousMillisDisplayUpdate += displayUpdateDelay;
    if(doOnce == 0){
      displayTimeRemaining();
    }
  }

}

void displayTimeRemaining(){

  milliseconds = (countDownTime % 1000UL) / 10UL;
  seconds = (countDownTime / 1000UL) % 60UL;
  minutes = (countDownTime / (1000UL * 60UL)) % 60UL;
  hours = (countDownTime / (1000UL * 60UL * 60UL)) % 24UL;

  if(hours < 10){
    HCMAX7219.print7Seg("0", 8);
    HCMAX7219.print7Seg(hours, 7);
  }
  else{HCMAX7219.print7Seg(hours, 8);}

  if(minutes < 10){
    HCMAX7219.print7Seg("0", 6);
    HCMAX7219.print7Seg(minutes, 5);
  }
  else{HCMAX7219.print7Seg(minutes, 6);}

  if(seconds < 10){
    HCMAX7219.print7Seg("0", 4);
    HCMAX7219.print7Seg(seconds, 3);
  }
  else{HCMAX7219.print7Seg(seconds, 4);}

  if(milliseconds < 10){
    HCMAX7219.print7Seg("0", 2);
    HCMAX7219.print7Seg(milliseconds, 1);
  }
  else{HCMAX7219.print7Seg(milliseconds, 2);}

  HCMAX7219.Refresh();
}
,