Запрограммируйте Arduino Uno как цифровые часы

У меня есть плата Arduino Uno с прикрепленным сверху экраном (http ://www.freetronics.com.au/pages/16x2-lcd-shield-quickstart-guide#.VUf0tMWN0iT Это экран).

Я пытаюсь сделать так, чтобы на экране отображалось время в часах, минутах и секундах и считалось по 24-часовому циклу. Как цифровые часы. У меня есть так, что он может считать, но он просто считает до 99, а затем повторяется.

У меня есть код, который считает миллисекунды, но мне нужны минуты и часы

    #include <Wire.h>
    #include <LiquidCrystal.h>

    LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

    void setup()
{
    lcd.begin( 16, 2 ); 
}

    void loop()
{
    lcd.setCursor ( 0, 1);
    lcd.print(millis());
}

, 👍3


7 ответов


Лучший ответ:

1

Почему бы не попробовать прерывание по таймеру от atmega328 uC. Вы также узнаете что-то полезное и я думаю, что это более интересно, чем использовать библиотеку и некоторые методы (функции), не зная, как они реализованы.

Создать цифровые часы очень просто, просто прерывая их каждую секунду. В ISR вам просто нужно обновить значения секунд/минут/часов. Для отображения на ЖК-дисплее, если вы не хотите возиться с командами и байтами ЖК-дисплея, вы можете использовать LiquidCrystal.

Пример кода: (здесь я также использовал аналоговый вывод для изменения времени)

#define F_CPU   16000000UL
#include <avr/delay.h>
#include <avr/io.h>
#include <string.h>
#include <avr/interrupt.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);


const short buttonPin = 5;
volatile unsigned char seconds;
volatile unsigned char minutes;
volatile unsigned char hours;
void update_clock()
{
    seconds++;
    if (seconds == 60)
    {
        seconds = 0;
        minutes++;
    }
    if(minutes==60)
    {
      minutes=0;
      hours++;
    }
    if(hours>23)
    {
      hours=0;
    }


}

ISR(TIMER1_COMPA_vect)
{

  update_clock(); 

}

void setup() 
{                



  // инициализируем Таймер1
  pinMode(A5, INPUT_PULLUP);
  cli();          // отключаем глобальные прерывания
  TCCR1A = 0;     // установить весь регистр TCCR1A в 0
  TCCR1B = 0;     // то же самое для TCCR1B

  // установить сравнение регистра совпадения с желаемым счетчиком таймера:
  OCR1A = 15624;
  // включить режим CTC:
  TCCR1B |= (1 << WGM12);
  // Установите биты CS10 и CS12 для предделителя 1024:
  TCCR1B |= (1 << CS10);
  TCCR1B |= (1 << CS12);
  // включить прерывание сравнения таймера:
  TIMSK1 |= (1 << OCIE1A);
  sei();          // разрешить глобальные прерывания
  lcd.begin(16, 2);
  lcd.print("HH:MM:SS");
  Serial.begin(9600); 


}

void display_on_lcd()
{

  lcd.setCursor(0, 1);
  lcd.print(hours);
  lcd.setCursor(2,1);
  lcd.print(":");
  lcd.setCursor(3,1);  
  lcd.print(minutes);
  lcd.setCursor(5,1);
  lcd.print(":");
  lcd.setCursor(6,1);
  lcd.print(seconds);


}


void loop()
{

  display_on_lcd();

  Serial.println(analogRead(5));
  if(analogRead(buttonPin)<1000)
  {
    if(minutes==59)
    {
      minutes=0;
      if(hours==23)
        hours=0;
      else
        hours++;
    }
    else
    {
      minutes++;
    }
  }


}

Просто выполните поиск, и вы найдете множество примеров.

,

Хороший образец! Как вы думаете, лучше использовать прерывания по таймеру вместо функции millis()?, @Gonza

Я действительно не знаю, как реализована функция millis(), чтобы сказать, почему мои реализации лучше, чем с помощью millis(). Я подумал, что это более «естественно» для реализации с сравнением прерываний таймера, потому что в реальных встроенных у вас нет функции millis(). Кроме того, millis() возвращает unsigned long, а это много бит. Если у вас есть микроконтроллер с ограниченной памятью, вы не можете объявлять и хранить множество беззнаковых длинных переменных только для расчета времени. Кроме того, подумайте об этом, например, в коде с millis() вы вызываете функцию на каждой итерации в цикле while и обновляете значения., @23ars

эти вызовы функции будут генерировать задержки (тактовые циклы). Кроме того, для этого типа проблемы вам нужно использовать дополнительные переменные? зачем объявлять еще 3 переменные unsigned long currentMillis, previousMillis, elapsedMillis; когда мне нужны только секунды/минуты/часы. дополнительный код! не говоря о том, сколько тактов занимают ветки. Опять же, я не говорю, что лучше сравнивать с прерываниями по таймеру, но, возможно, код чище, используется меньше памяти и т. д. millis() больше похожа на функцию, которую приятно иметь, и она была разработана только для того, чтобы упростить разработку, но не для использования в реальные проекты., @23ars

Благодарю за разъяснение! да, я с вами, это надежнее и экономичнее. еще раз спасибо, @Gonza

Простой, но действительно ненужный. У вас уже есть прерывание по таймеру, делающее это в ядре, зачем вам второе? Теперь у вас есть два привязанных таймера вместо одного. Теперь Timer0 и Timer1 привязаны к отсчету времени, и теперь вы сделали Timer1 непригодным для PWM. Почему бы просто не придерживаться прерывания таймера, которое уже увеличивает миллисекунды, и не вычислять время на основе этого?, @Delta_G


1

Не знаю, о чем вы спрашиваете, но модуль часов реального времени значительно облегчит вам жизнь при работе со временем. Возможно, опубликуйте пример кода, чтобы показать, в чем ваша проблема.

,

1

Ну, вам нужно разобраться с этой проблемой.

Если вы в настоящее время можете считать в миллисекундах, то вы знаете, что 1000 миллисекунд равны 1 секунде, поэтому каждый раз, когда миллисекунды равны 1000, вы сбрасываете его на 0 и увеличиваете количество секунд на 1.

Вы продолжаете двигаться дальше, если секунды равны 60, то вы увеличиваете минуты на 1 и сбрасываете секунды до нуля. Вы получаете картину.

,

4

Я с @arvid-jense в этом вопросе, если вы работаете над серьезными цифровыми часами, вам следует приобрести часы реального времени.

Теперь, если вы изучаете Arduino и хотите поиграть со своим ЖК-мини-дисплеем и построить часы, вы должны знать некоторые важные ограничения millis() в этом проекте:

  • Функция мельницы переполнится примерно через 50 дней.
  • Ваши часы будут неточными, потому что циклы Arduino цикла, вы будете проверять, сколько мельниц прошло в каждом цикле. (он будет отставать примерно на 10 секунд каждый день, поэтому на 7-й день вы опоздаете на одну минуту)
  • параметр для миллиса является беззнаковым длинным, могут быть сгенерированы ошибки если программист пытается выполнять математические операции с другими типами данных, такими как целые числа.

При этом

#include <Wire.h>
#include <LiquidCrystal.h>

/**
 * Clock Variables
 */
unsigned long currentMillis, previousMillis, elapsedMillis;
int seconds, minutes, hours;

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
    lcd.begin( 16, 2 ); 
}

void loop()
{
    setClock();
    /**
     * После установки часов теперь у вас есть 3 переменные типа int с текущим временем
     */
     //секунды
     //минуты
     //часы
     lcd.setCursor ( 0, 1);
     lcd.print(millis());
}

void setClock()
{
    currentMillis = millis();
    elapsedMillis += currentMillis - previousMillis;

/**
* Если мы используем equals 1000, возможно, из-за упомянутого ограничения цикла
* вы проверяете разницу, когда его значение равно (999), а в следующем цикле его значение равно (1001)
*/
    if (elapsedMillis > 999){
        seconds++;
        elapsedMillis = elapsedMillis - 1000;
    }

    if (seconds == 60){
        minutes++;
        seconds = 0;
    }
    if (minutes == 60){
        hours++;
        minutes = 0;
    }
    if (hours == 24){
        hours = 0;
    }

    previousMillis = currentMillis;
}

Хорошее руководство по созданию будильника

,

Ваш MILLIS_OVERFLOW неверен. В последних версиях библиотеки Arduino millis() переполняется при 2^32 (49,7 дня), что означает, что тестирование на переполнение бесполезно., @Edgar Bonet

Не знал, спасибо! я обновил константу MILLIS_OVERFLOW. (это цифровые часы, проверка на переполнение не бесполезна). В любом случае, я думаю, что предложенное @23ars решение с использованием прерываний по таймеру лучше моего., @Gonza

Этот код по-прежнему неверен. Тестирование переполнения **является** определенно бесполезным: согласно правилам [модульной арифметики](https://en.wikipedia.org/wiki/Modular_arithmetic), которые гарантируются для беззнаковых чисел языками C и C++, currentMillis — предыдущаяMillis будет _всегда_ давать правильный ответ, независимо от переполнения., @Edgar Bonet

@EdgarBonet, извини, но я не понимаю. Вы тестировали этот код на Arduino? Пожалуйста, не могли бы вы объяснить, как этот код должен работать без тестирования на переполнение. Спасибо!, @Gonza

Я протестировал это прямо сейчас. Когда millis() переполняется, происходит сумасшедшее изменение времени на каждой итерации цикла. Если убрать тест на переполнение, то работает нормально. Если внимательно прочитать понравившуюся статью о модульной арифметике, должно быть очевидно, что тест бесполезен., @Edgar Bonet

Я понял, я удалил тест на переполнение. Разница всегда будет отражаться двумя беззнаковыми числами., @Gonza


2

Другие упоминают часы реального времени (RTC), и это может показаться очевидным выбором, но, возможно, это не так. Все, что делает RTC, — это ведет счетчики часов, минут, секунд, даты и месяца, но ваш микроконтроллер тоже может это делать.
К сожалению, у Uno не кристалл, а керамический резонатор, который не так точен. Не то чтобы это имело большое значение, даже RTC на основе кристалла может отклоняться на несколько секунд в день. Вы можете измерять ошибку в течение длительного времени и регулярно исправлять ее в программном обеспечении, но это утомительно.

Если вам нужна точность в течение длительного времени, есть два дешевых способа:

  1. используйте приемник атомных часов. В Европе есть DCF77, в Северной Америке это WWVB, который передает очень точную информацию о дате и времени. Модуль приемника для них стоит около 10 долларов.
  2. используйте частоту сети для синхронизации часов. В долгосрочной перспективе это чрезвычайно точно (просто подумайте о своем будильнике: как часто вам приходится его настраивать?). В Европе вы насчитали бы 100 полупериодов в секунду (120 в США). Когда ваш счетчик достигнет этого значения, сбросьте его и увеличьте секунды. Если секунды достигают 60, сбросьте их и увеличьте минуты. И так далее. Если вы не совсем разбираетесь в электронике, вы можете обратиться за помощью на electronics.stackexchange.com. Это не сложно.
,

Получение RTC через Arduino сделает вас намного лучше. Весь смысл RTC заключается в использовании термостабильного кристалла, который гарантированно сохраняет N секунд точности каждый год. Точность зависит от модели, но arduino xtals или керамика дрейфуют намного быстрее, @benathon

@portforwardpodcast: да, керамические резонаторы Arduino будут дрейфовать больше, чем кристалл, но даже RTC, управляемый кристаллом, может дрейфовать на пару секунд в день. Это минута в месяц. Вам все равно придется время от времени синхронизировать свои часы, так почему бы не допустить относительную неточность Arduino и не синхронизировать один раз в день. С RTC вам тоже придется это делать, только реже., @Joris Groosman

Кроме того, несмотря на то, что существуют генераторы с температурной компенсацией, кристаллы с частотой 32 кГц, используемые для RTC, *не* устойчивы к температуре., @Joris Groosman


1

Спасибо всем за помощь! Я получил приведенный ниже код, который работает довольно хорошо, есть странный сбой с секундами, который по какой-то причине исправлен, если в конце добавляются миллисекунды, но в остальном он работает очень хорошо.

#include <Wire.h>
#include <LiquidCrystal.h>

#define MILLIS_OVERFLOW 34359738

/**
* Clock Variables
*/
unsigned long currentMillis, previousMillis, elapsedMillis;
int seconds, minutes, hours;

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup()
{
  Serial.begin(9600);
  lcd.begin( 16, 2 ); 

}

void loop()
{
    setClock();
    /**
   * После установки часов теперь у вас есть 3 переменные типа int с текущим временем
   */
   //секунды
   //минуты
   //часы
     lcd.setCursor ( 0, 1);
     lcd.print(hours);
     lcd.print(":");
     lcd.print(minutes);
     lcd.print(":");
     lcd.print(seconds);
     lcd.print(":");
     lcd.print(elapsedMillis);

}

void setClock()
{
    currentMillis = millis();
    /**
     * The only moment when currentMillis will be smaller than            previousMillis
     * will be when millis() oveflows
    */
    if (currentMillis < previousMillis){
    elapsedMillis += MILLIS_OVERFLOW - previousMillis + currentMillis;
} else {
    elapsedMillis += currentMillis - previousMillis;
}

/**
 * If we use equals 1000 its possible that because of the mentioned loop limitation
 * you check the difference when its value is (999) and on the next loop its value is (1001)
 */
if (elapsedMillis > 999){
    seconds++;
    elapsedMillis = elapsedMillis - 1000;
}

if (seconds == 60){
    minutes++;
    seconds = 0;
}
if (minutes == 60){
    hours++;
    minutes = 0;
}
if (hours == 24){
    hours = 0;
}

previousMillis = currentMillis;
}
,

1

Вывод из предыдущих ответов заключается в том, что лучше всего использовать RTC (часы реального времени). В противном случае вы можете эмулировать RTC используя millis(), за счет значительного дрейфа, особенно если ваш Arduino тактируется керамическим резонатором.

Я просто хотел бы добавить, что вам не нужно писать логику этого millis(), «мягкий RTC» на основе самостоятельно, так как это уже реализовано в класс RTC_Millis из RTClib Adafruit:

#include <RTClib.h>
#include <LiquidCrystal.h>

RTC_Millis rtc;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
    rtc.begin(DateTime(F(__DATE__), F(__TIME__)));
    lcd.begin(16, 2);
}

void loop() {
    static DateTime last_time_printed;
    DateTime now = rtc.now();
    if (now != last_time_printed) {
        lcd.setCursor(0, 1);
        lcd.print(now.timestamp(DateTime::TIMESTAMP_TIME));
        last_time_printed = now;
    }
}

Хорошая особенность этого варианта заключается в том, что если вы позже выберете настоящую RTC, вам нужно будет внести лишь минимальные изменения в свой код. Еще один хороший Дело в том, что если вы измерите дрейф ваших часов, вы можете легко исправьте это: просто замените RTC_Millis на RTC_Micros и вызовите rtc.adjustDrift() в setup(). Вы сможете настроить дрейф с разрешением 1 ppm. Обратите внимание, что оба RTC_Millis и RTC_Micros не подвержены проблемам с опрокидыванием.

,

Насколько стабилен керамический резонатор? Дрейф не меняется? Часы на основе кварцевого кристалла довольно стабильны, поэтому поправочный коэффициент должен быть весьма эффективным, но разве скорость керамического резонатора не отличается намного больше, чем скорость кварцевого резонатора?, @Duncan C

@DuncanC: Это действительно немного различается, но средний дрейф обычно больше, чем вариации дрейфа., @Edgar Bonet