Урок 22. Работа со временем в Ардуино. Проект спортивного секундомера.

Секундомер Ардуино

Рассмотрим функции работы со временем. Разработаем спортивный секундомер на базе платы Ардуино.

Предыдущий урок     Список уроков     Следующий урок

Для работы со временем в системе Ардуино существуют 4 стандартные функции:

  • delay(time);
  • delayMicroseconds(time);
  • millis();
  • micros().

 

Рассмотрим эти функции подробно.

void delay(unsigned long time)

Функция приостанавливает работу программы на время time, заданное в мс.

delay(500);  // пауза на 0,5 сек

Мы уже использовали эту функцию в предыдущих уроках. Использовали в отладочных программах. В практических программах функция применяется редко.

Дело в том, что она останавливает работу программы. Программа ничего не делает в течение заданного отрезка времени. Не позволительная роскошь, допустимая только в отладочных режимах.

Кроме того, функция не позволяет создавать циклы с заданным временем. Например, время следующего цикла не будет точно равно 100 мс.

while(true) {
  // код программного блока
  delay(100);
}

Время цикла равно сумме: времени 100 мс, времени выполнения программного блока и времени перехода на начало цикла while(). Время программного блока может меняться в зависимости от алгоритма выполнения программы. Время перехода на начало цикла тоже не определено точно. В результате время цикла можно определить только приблизительно.

Для организации циклов с заданным временем лучше использовать прерывание по таймеру (урок 10). Надо только понимать, что такой способ обеспечивает стабильное время цикла, но оно может несколько отличаться от заданного. Например, библиотека MsTimer2 задает время в мс. Реальное время может отличаться на 1-2 мкс. В некоторых приложениях, например в часах, ошибка будет накапливаться и приведет к недопустимой погрешности.

 

void delayMicroseconds(int time)

Функция приостанавливает работу программы на время time, заданное в мкс.

delayMicroseconds(50);  // пауза на 50 мкс

Аналог функции delay(), только обеспечивает более короткие остановки программы. Вполне допустима для использования в практических программах по двум причинам.

  • Короткие отрезки времени сложно реализовать другими методами.
  • Остановка программы даже на несколько десятков микросекунд не вызовет катастрофических последствий.

 

unsigned long millis(void)

Функция возвращает время в мс, с момента запуска текущей программы. Значение времени переполняется через 1193 часов, приблизительно 50 суток.

tm = millis();  // чтение времени в tm

По сути это функция чтения системного времени Ардуино. Время считается в параллельном процессе и не зависит от алгоритмов выполнения программы, остановок, в том числе и функцией delay(), и т.п. Для измерения интервалов времени необходимо считать системное время в начале и конце интервала и выполнить разность этих значений. Ниже будет пример программы для работы с временными интервалами.

Точность работы функции millis().

Точность отсчета времени функцией millis() определяется точностью и стабильностью частоты кварцевого резонатора платы Ардуино. Даже для дешевых резонаторов погрешность частоты не превышает 30 ppm. Вместе с температурной нестабильностью, в нормальных условиях, это 50 ppm, что соответствует ± 0,00005 %. Таким образом, суммарная абсолютная ошибка системного времени Ардуино составит:

  • 0,18 сек для 1 часа;
  • 4,32 сек для суток;
  • 129,6 сек для месяца;
  • 26 минут для года.

Наверное, вполне допустимая точность для создания секундомеров и даже часов.

unsigned long micros(void)

Функция возвращает время в мкс, с момента запуска текущей программы. Значение переполняется приблизительно через 70 минут.

tm = micros();  // чтение времени в tm

На платах Ардуино с частотой тактирования 16 мГц разрешение значения функции millis() составляет 4 мкс. Для плат с частотой 8 мГц – 8 мкс.

 

Спортивный секундомер на базе платы Ардуино.

Реализуем достаточно простой проект – секундомер. Наверное, практическое применение такого устройства ограничено. Секундомер с питанием от сети имеет право на существование  только в стационарных устройствах. В табло для спортивных соревнований, в системах для интеллектуальных игр, и т.п. Но разобравшись в программе, Вы легко сможете подкорректировать секундомер под свои задачи или создать совершенно другое устройство.

Управление секундомером осуществляется двумя кнопками:

  • ПУСК/СТОП;
  • СБРОС.

После нажатия на кнопку ПУСК/СТОП секундомер начинает отсчитывать время. Повторное нажатие на эту кнопку останавливает счет. Следующее нажатие продолжает отсчет времени с остановленного значения. Таким образом, секундомер может быть использован для отсчета ”чистого ” времени спортивных соревнований.

Для сброса значения времени необходимо нажать кнопку СБРОС.

Время отображается на четырех разрядном семисегментном индикаторе в следующих форматах.

Значение времени Разряд 3 Разряд 2 Разряд 1 Разряд 0
0 … 59 сек секунды сотые доли секунды
1 … 10 минут минуты секунды десятые доли секунды
10 … 99 минут минуты секунды
Больше 99 минут - - - -

Формат вывода данных меняется автоматически в зависимости от значения времени. Минуты, секунды и дробная часть отделяются на индикаторе децимальными точками. Нажатия на кнопки сопровождаются звуковыми сигналами.

 

Схема спортивного секундомера на базе платы Arduino UNO R3.

По уже хорошо известным нам схемам подключаем к плате Ардуино:

  • 4х разрядный семисегментный светодиодный индикатор GNQ-3641BUE;
  • две кнопки;
  • звуковой пьезоизлучатель.

Схема спортивного секундомера на Ардуино

Все эти компоненты могут быть другими. Если Вы разрабатываете секундомер для спортивного табло, то индикаторы должны быть большими. Можете их собрать  даже из отдельных светодиодов. Как подключить индикаторы к плате можно посмотреть в уроке 19.

Если кнопки управления секундомером физически расположены на значительном расстоянии от платы, то лучше их подключить по схеме из охранной сигнализации, урок 17. Или хотя бы соединить входы кнопок с питанием + 5 В через резисторы 1 кОм.

Но для отладки и проверки программы достаточно приведенной схемы. Я собрал ее на макетной плате.

Спортивный секундомер на Ардуино

Программа для спортивного секундомера на Ардуино.

Скетч программы можно загрузить по этой ссылке. Не забудьте установить библиотеки из предыдущих уроков: MsTimer2.h (урок 10), Led4Digits.h (урок 20) и Button.h (урок 9).

Скетч програмы для секундомера выглядит так.

// спортивный секундомер
#include <MsTimer2.h>
#include <Led4Digits.h>
#include <Button.h>

#define SOUND_PIN 18     // звуковой излучатель, вывод 18 (A4)

// тип индикатора 1; выводы разрядов 5,4,3,2; выводы сегментов 6,7,8,9,10,11,12,13
Led4Digits disp(1, 5,4,3,2, 6,7,8,9,10,11,12,13);

Button buttonReset(16, 10);  // кнопка СБРОС, вывод 16 (A2)
Button buttonStartStop(17, 10);  // кнопка ПУСК/СТОП, вывод 17 (A3)

byte mode= 0; // режим, 0 - СТОП, 1 - ПУСК
unsigned long msTime=0; // время интервала, милисекунды
byte  minTime=0;  // время интервала, минуты
byte  decMinTime=0;  // время интервала, десятки минуты
unsigned long prevTime;  // предыдущее значение времени
unsigned long curentTime;  // текущее значение времени
byte soundCount=0;  // счетчик времени звука

void setup() {
  MsTimer2::set(2, timerInterrupt); // прерывания по таймеру 2 мс
  MsTimer2::start();               // разрешение прерывания
  pinMode(SOUND_PIN, OUTPUT); // вывод звукового излучателя 
}

void loop() {

//----------------------- переключение режима СТАРТ/СТОП
  if ( buttonStartStop.flagClick == true ) {
    buttonStartStop.flagClick= false; // сброс признака
    // инверсия режима
    if ( mode == 0) {
      mode= 1;                  // СТАРТ
      soundCount= 250; // звук на старт 250*2 мс
    }
    else {  // СТОП     
      mode= 0;    
      soundCount= 50; // звук на стоп 50*2 мс
    }
  }

//----------------------- кнопка СБРОС
  if ( buttonReset.flagClick == true ) {
    buttonReset.flagClick= false; // сброс признака
    msTime=0;
    minTime=0;
    decMinTime=0;
    soundCount= 50; // звук на сброс 50*2 мс
  }

//----------------------- отсчет времени
  if ( mode == 0 ) {
    // СТОП
    prevTime= millis();
  }
  else {
    // ПУСК   
    curentTime= millis(); // чтение текущего времени
    msTime += curentTime - prevTime;  // прибавление времени к милисекундам
    if ( msTime > 59999 ) { 
      // милисекунды переполнились, больше минуты
      msTime -= 60000;
      minTime ++; // +1 к единицам минут
      if ( minTime > 9 ) {
        // единицы минут переполнились
        minTime -= 10;
        decMinTime ++;  // +1 к десяткам минут
      }     
    }               
    prevTime= curentTime; // перегрузка предыдущего времени
  }

//--------------------- отображение времени 
  if ( (minTime == 0) && (decMinTime == 0)) {
    // меньше минуты
    disp.print(msTime / 10, 4, 0);  // вывод четырех разрядов милисекунд
    // точки
    disp.digit[0] &= 0x7f;  // погасить
    disp.digit[1] &= 0x7f;  // погасить
    disp.digit[2] |= 0x80;  // зажечь
    disp.digit[3] &= 0x7f;  // погасить
  }
  else if ( decMinTime == 0 ) {
    // меньше 10 минут
    disp.print(msTime / 100, 3, 0);  // вывод трех разрядов милисекунд   
    disp.tetradToSegCod(3, minTime);  // в старший разряд вывод единиц минут
    // точки
    disp.digit[0] &= 0x7f;  // погасить
    disp.digit[1] |= 0x80;  // зажечь
    disp.digit[2] &= 0x7f;  // погасить
    disp.digit[3] |= 0x80;  // зажечь    
  }
  else if ( decMinTime < 10 ) {
    // меньше 100 минут
    disp.print(msTime / 1000, 2, 0);  // вывод двух разрядов милисекунд
    disp.tetradToSegCod(3, decMinTime);  // в старший разряд вывод десятков минут
    disp.tetradToSegCod(2, minTime);  // в 3 разряд вывод единиц минут
    // точки
    disp.digit[0] &= 0x7f;  // погасить
    disp.digit[1] &= 0x7f;  // погасить
    disp.digit[2] |= 0x80;  // зажечь
    disp.digit[3] &= 0x7f;  // погасить    
  }
  else {
    // больше 100 минут
    // ----
    disp.digit[0]= 0x40;
    disp.digit[1]= 0x40;
    disp.digit[2]= 0x40;
    disp.digit[3]= 0x40;   
  }
}

//----------- обработчик прерывания 2 мс
void  timerInterrupt() {
  disp.regen(); // регенерация индикатора
  buttonReset.filterAvarage();  // сканирование кнопки, метода фильтрации по среднему
  buttonStartStop.filterAvarage();  // сканирование кнопки, метода фильтрации по среднему

  // звук
  if (soundCount != 0) {
    digitalWrite(SOUND_PIN, ! digitalRead(SOUND_PIN));
    soundCount--;
  }
}

Большая часть программы разработана по принципам, подробно описанным в предыдущих уроках.

  • Реализовано прерывание по таймеру с периодом 2 мс.
  • В обработчике прерывания вызываются:
    • метод регенерации индикатора;
    • методы сканирования сигналов кнопок;
    • блок формирования звукового сигнала.
  • По признакам нажатия кнопок происходит:
    • переключение режима (СТАРТ/СТОП);
    • сброс показаний времени.
  • В зависимости от значения времени в блоке отображения меняется формат вывода данных на индикатор.

Подробно хочется остановиться на программном блоке “отсчет времени”. Счет времени можно реализовать разными способами. Например, можно считать время в миллисекундах, а в блоке отображения делать перевод в нужный формат. Я решил, что намного проще считать время в формате:

  • миллисекунды;
  • минуты;
  • десятки минут.

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

 

Думаю, Вы без труда разберетесь в этой программе и сможете создать устройство под свои задачи, будь это спортивный секундомер, система для интеллектуальных игр ”Что? Где? Когда?” или ”Брейн ринг” и многое другое. Мы вернемся к секундомеру и работе со временем при разработке спортивных табло на базе светодиодных модулей. Надеюсь, уже этим летом.

Предыдущий урок     Список уроков     Следующий урок

Один комментарий на «Урок 22. Работа со временем в Ардуино. Проект спортивного секундомера.»

  1. Доброго времени, подскажите пожалуйста, Как изменить программу что бы секундомер работал при удержании кнопки? А при отпускании останавливался?

    • Здравствуйте. Я могу сходу ошибиться, но попробуйте изменить блок «переключение режима СТАРТ/СТОП» на простой алгоритм: если кнопка нажата, то режим ПУСК (mode=1), если кнопка отжата — СТОП (mode=0).

      if ( buttonStartStop.flagPress == true ) mode=1;
      else mode=0;

  2. я не разбираюсь но помоему так как кнопка при работе всегда нажата ее же опрашивает программа — вот и вылазит погрешность — точнее не то что опрашивает а то что при каждом цикле обрабатывает условие

    • Нет. Вы все правильно сделали? Там независимый программный блок, который вырабатывает единственный признак mode и счетчик звука. Все должно работать. Я подумаю. Можете проверить поставить просто mode=1; И посмотреть как идет время.

  3. //———————— переключение режима СТАРТ/СТОП
    if ( buttonStartStop.flagPress == true ) {
    buttonStartStop.flagClick= false; // сброс признака
    // инверсия режима
    if ( mode == 0) {
    mode= 1; // СТАРТ

    }
    else { // СТОП
    mode= 0;
    }
    }

    //———————— кнопка СБРОС

    вот эта часть

  4. ну уже измененный=)
    и вот при постоянном удерживании кнопки в два раза медленнее работает секундомер=)
    мне просто нужно управлять им с помощью реле — то есть при появлении 24вольт на определенном разъеме

    • Нет Вы не поняли. Надо весь блок заменить на
      if ( buttonStartStop.flagPress == true ) mode=1;
      else mode=0;

      • Раньше по нажатию кнопки инвертировался режим mode. А Вам надо если кнопка нажата, то mode=1, отжата mode=0

  5. по всякому пробывал…..не откомпилировать — ошибки, я в синтаксисе не особо понимаю…..но все равно спасибо что потратили на меня время=) буду учить матчасть

  6. спасибо Вам огромное, все работает — я нарушил синтаксис программы, вот и ругалось!!!!!!!

  7. Здравствуйте ещё раз) Эдуард.
    У меня просто нет слов, как Вы это делаете?
    Чтобы мне было интересней изучать МК товарищ подкинул идею сделать ему таймер для игры в шахматы (убывающее время). Я там такого нагородил чтобы вычленить разряды минут секунд прочего, а тут просто переполнение и вывод на индикацию…
    Восхищаюсь!
    Не знаю читать дальше нет.
    Чтобы совсем интерес не потерять на фоне того что всё без моего участия так хорошо придумано)

  8. ДОБРЫЙ ДЕНЬ!
    КОПИРУЮ СКЕТЧ, ПРОВЕРЯЮ….
    Arduino: 1.8.2 (Windows 7), Плата:»Arduino Nano, ATmega328″

    fatal error: MsTimer2.h: No such file or directory

    compilation terminated.

    exit status 1
    Ошибка компиляции для платы Arduino Nano.

    КАК ЖИТЬ ДАЛЬШЕ?

    • Здравствуйте!
      Проверьте, что установлена библиотека MsTimer2.h.
      Убедитесь, что проект не сохранен в папке названной русскими буквами.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *