Как сделать счетчик на ардуино

Обновлено: 07.07.2024

Serial.println(hag);
delay(10);
>
У меня вот такой код, но он бесконечно считает когда кнопка включена (у меня подключено к земле и через рез. к 5 вольтам, так при отжатой кнопке имеем 1 и при нажатой имеем 0)

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

Работает на Atmega8, Atmega168, Atmega328, Atmega2560

Upd Декабрь 2019 добавлена поддершка Лянарды.

Много таймеров из одного

Основная задача, стоящая перед горе-программистом AVR это не строгий математический анализ каких-нить бестолковых данных, не быстрое преобразование Фурье и даже не унылое мигание светлодиодиком. Основная задача, занимающая 80% программы - измерение временных интервалов. Чаще всего требуется узнать, скока времени прошло перед/после какого-нить события, чтобы не пропустить и вовремя откликнуться на него. Кто-то (90% новичков) жизни своей не мыслят без функции delay(), кто-то постоянно теребонькает несчастную millis(), зачастую, делая это неправильно и со священным ужасом ожидая страшного переполнения. Сложность в том, что временных интервалов по ходу программы требуется много, часто и, как правило, разных. Например, время конверсии даччика DS18B20 при максимальном 12-битовом разрешении составляет 750 миллисекунд, и раньше, чем вычислится температура опрашивать снова его бессмысленно. Для других даччиков время опроса будет совершенно другое, и временной интервал для него, соотвецтвенно - тоже. На экран информацию чаще 1 раза в 50 миллисекунд выводить тоже неприлично, только лишняя трата ресурсов, нервов и самого экранчика, значит опять надо где-то хранить время, когда вывод осуществлялся последний раз, чтоб не обращаться к нему чаще положенного. Везде, куда не посмотришь - время, время и время.

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

Более продвинутые открыли для себя функцию millis() и поглядывают/поплевывают на делейщиков с высоты своего воображаемого Олимпа как исполненные благодати Свидетели Переполнения. И пестрят их программы чуть более чем полностью конструкциями вида

но последние, обычно, к исходу 49-х суток взрываются вместе с переполнившимся millis-om() и угрозы больше не представляют.

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

millis() в этом отношении гораздо гуманнее. Он похож на раба, которому хозяин вручил наручные часы, и строго настрого наказал следить за временем, если не хочется получить дубиной по башке, как в прошлый раз. И вот человечек, выполняя свою нехитрую работу, всё время пялится на часы, чтоб узнать не прошло ли достаточно времени, чтоб включить/выключить что то важное. Иначе, не уследишь/забудешь и улетишь вабнимку с газовым котлом Марс заселять вместе с родными и близкими (и котом), а жалка. Такое можно, канеш, потерпеть, если временной интервал один, но как только их становится несколько, да еще и друг от друга зависящих, выполнение задания превращается в атнюдь не тривиальную задачу (мошт, лучше дубиной, а?).

То что предлагаю я, можно назвать будильником. Человек заводит себе несколько будильников на разное время и спокойно занимается своими делами. По мере срабатывания каждого, можно ненадолго отвлечься, включить/выключить то что нужно/ненужно и спокойно продолжить заниматься своими делами дальше. Если действие было однократное, будильник можно отключить, а если совсем-совсем однократное - то и выбросить. Если действие периодическое, а так чаще всего и бывает, ничего делать не нужно, через ранее заданный интервал будильник сам по себе сработает снова.

В основе всего этого священнодействия лежит самодостаточный класс TCounterDown, который, как ясно из названия является счетчиком вниз, до нуля. Класс самодостаточный, его можно применять самостоятельно для подсчета внешних событий. Каких? Да каких угодно, например, сколько раз человек нажал на кнопку, допустим, звонка. Если больше 10, то выйти и спросить, чо звонишь-то? видишь дома нет никто? или активировать механическую руку в боксерской перчатке, которая автоматически колотит слишком настойчивого посетителя по темечку (да, это я Том и Джерри насмотрелса за время сынова децтва).

У класса есть конструктор, который принимает начальное значение счетчика событий и адрес функции, которая вызовется когда это количество событий произойдёт.

тип pvfCallback - это указатель на функцию ничего не принимающую и ничего не отдающую

ее задача только в том, чтобы просигнализировать основной программе, что требуемое количество событий случилось (прозвонить будильником).

еще, из главных, у класса есть функция Tick() которая уменьшает заданный в конструкторе счетчик событий, и если он достиг 0, т.е требуемое кол-во событий случилось - то она и вызывает пользовательскую функцию CallbackFunc. После этого, внутренний счетчик вновь заполняется первоначальным значением автоматически и подсчёт событий начинается сначала. Для удобства программирования Tick() завернут в перегруженный оператор --, чтоб при наступлении события вызывать его както так

у класса есть еще несколько вспомогательных функций, которые могут пригодиться. Если нужно изменить счетчик событий, например, считать не 10 а 20 нажатий, есть функция

счетчик можно остановить, запустить снова с того же места или сбросить счетчик на первоначальное значение

кроме того, функцией isActive() можно проверить, что счетчик не остановлен, а функцией isEmpty() проверить, что назначена функция обратного вызова.
Полностью публичный интерфейс класса выглядит так

Повторюсь, класс самодостаточен, и может использоваться для подсчёта любых событий, не обязательно временнЫх. Но далее он понадобится, чтобы считать именно время.

Поэтому следующий класс для рассмотрения - TTimerList, которыйхранит в себе список программных счетчиков TCounterDown, которые считают число миллисекунд. Каждое срабатывание аппаратного таймера, TTimerList пробегает по списку сохраненных счетчиков и выполняет декремент каждого. При достижении 0 счетчик сам вызовет функцию обратного вызова (уведомление, будильник), и отсчет пойдет сначала. Интерфейс TTimerList прост как 2 копейки, у него есть функции как для всего списка (остановить/запустить), так и для каждого отдельного таймера.

основную идею я корявенько донес, а комментарии в тексте подскажут что там к чему (надеюсь)

Использование: скопировать все файлы в свою директорию. В своем .ino файле написать

и всё. Можно добавлять, удалять, останавливать и вновь запускать нужные счётчики.

рабочий Blink без delay(), millis() и loop() выглядит так

а если вдруг нам надо, чтобы светодиод мигал неравномерно, например 200 мс горел, а 4800 мс не горел, то просто на лету меняем интервалы отсчета.

пробуем, спрашиваем, не надоедаем.

Я НЕ ЗАНИМАЮСЬ встраиванием таймеров в ваш код, не надо присылать мне свои простыни для "анализа" как туда прикрутить это. Алгоритм работы ВАШЕЙ программы известен только ВАМ, я в нем разбираца не буду. Сможете прикрутить туда таймеры, буду рад, не сможете - значит еще не пришло время, пользуйтесь другими решениями. И материть меня за это по электропочте не надо, мы с котом расстраиваемся, плачем и сильнапьём.

В одном из предыдущих уроков мы научились зажигать сегменты светодиодного индикатора. Хотя это можно сделать только с помощью Ардуино, мы использовали в нашем уроке дополнительный компонент — сдвиговый регистр. Эта полезная микросхема сэкономила нам несколько ценных выводов микроконтроллера.

Но один семисегментный индикатор почти бесполезен. Он ведь отображает всего одну цифру. А что если мы хотим вывести большое число, скажем, от 0 до 500? Нам потребуется целых три цифры, а значит и три индикатора.

Как будем подключать их к контроллеру? Можем напрямую, но тогда мы займем 7*3 = 21 вывод! Это очень расточительно. К тому же, нужна будет уже другая плата, так как у Ардуино Уно просто не хватит цифровых выводов.

Попробуем использовать сдвиговый регистр? Уже лучше. Теперь нам понадобится три регистра, объединенных в цепочку, а также три вывода Ардуино для управления ими. В общем то на этом можно бы было и остановить оптимизацию нашей схемы, но мы пойдем дальше. Обойдемся всего одним сдвиговым регистром!

Динамическая индикация

Как известно, наше зрение обладает свойством инерции, или персистенции. Это способность глаза соединять быстро сменяющиеся изображения в одно. Таким образом, чтобы человек видел на индикаторе трехзначное число, вовсе необязательно зажигать все цифры разом. Достаточно в один момент времени включать только один отдельный индикатор. Переключение между соседними индикаторам должно происходить с большой частотой, чтобы получить эффект персистенции.

Такой подход к выводу данных называется динамической индикацией. В действительности, многие символьные и матричные светодиодные и газоразрядные индикаторы работают именно по такому принципу. Попробуем и мы собрать схему динамической индикации для управления тремя семисегментными индикаторами.

Подключение к Ардуино

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

Кстати, вместо трёх отдельных индикаторов лучше применять один трехцифровой индикатор. Суть схемы от этого не изменится, но проводов будет меньше!

Принципиальная схема

Принципиальная схема для динамической индикации на Ардуино

Внешний вид макета

Макетная схема для динамической индикации на Ардуино

В качестве электронных ключей рекомендуем использовать транзисторы в корпусе TO92, например 2N7000. Для подключения каждого транзистора понадобится два резистора: на 100-150 Ом и на 2.2-10 кОм. Первый резистор призван защитить вывод контроллера от бросков тока, возникающих на затворе во время создания поля. Второй же резистор поможет быстро выключить ключ, когда мы подадим низкий уровень на соответствующий вывод контроллера (через него на землю сольется остаточный заряд затвора).

На каждой линии от регистра к индикатору необходим токозадающий резистор 200-300 Ом, чтобы светодиоды в индикаторе не перегорели. Этот нюанс работы со светодиодами мы рассмотрели на одном из самых первых уроков про светодиоды.

Тщательно собираем схему и переходим к программе.

Программа для динамической индикации

Часть этой программы, включая переменные data_pin, sh_pin, st_pin и функцию fill уже известны нам из урока про сдвиговый регистр.

Массив digits хранит десять комбинаций сегментов для цифр от 0 до 9. Для экономии памяти мы храним каждую комбинацию в одном байте, а для лучшего понимания мы записали их в бинарном виде.

Чтобы переключать индикаторы воспользуемся таймером. Будем каждую миллисекунду заполнять регистр нужной комбинацией и включать соответствующий индикатор. Для этого мы используем функцию setDigit. Аргумент digit — это индекс включаемого в данный момент индикатора, а counter — трехзначное число, которые мы хотим высветить.

Параметр to_flick отвечает за период переключения индикатора. Он равен 1, а значит смена цифр для отображения происходит каждую миллисекунду. Что если увеличить этот параметр? Скажем до 100мс, или даже до 500мс. Эффект инерции зрения пропадет и мы начнем замечать смену цифр.

Программа счетчика с динамической индикацией

В предыдущем примере переменная counter хранила число 125 по-умолчанию. Попробуем теперь добавить в программу счетчик секунд, чтобы counter увеличивался на единицу каждую секунду, вплоть до числа 999.

Загружаем программу на Ардуино и наблюдаем работу счетчика!

Задания

  1. Цифровой секундомер. Собрать схему с трехцифровым индикатором. Добавить в схему кнопку. При нажатии на кнопку, секундомер должен запускать отсчет. При повторном нажатии — останавливать. Дополнительно, к секундомеру можно добавить дробную часть, отображаемую на третьем индикаторе через точку.
  2. Цифровой вольтметр для напряжений от 0 до 10 Вольт. Собрать схему с трехцифровым индикатором. Добавить в схему делитель напряжения из двух резисторов на 10 кОм, подключенный к аналоговому входу Ардуино. Написать программу, которая будет каждые 100 мс считывать значение на аналоговом входе, переводить его в Вольты и выводить на индикатор. Для правильного отображения дробной части, необходимо подключить восьмой сегмент — точку.

Заключение

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

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


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

Каждый из четырёх каналов нашего реле времени может выдавать не только логические уровни (1/0 - вкл/выкл), но и сигналы ШИМ (включать приборы на определённую мощность).

В реле времени имеется 20 таймеров (их количество можно уменьшить или увеличить до 128, указав нужное число в строке 16 скетча). Один таймер включает только одно устройство (канал) на заданный промежуток времени, не влияя на работу остальных устройств (каналов). Каждому устройству (каналу) можно назначить несколько таймеров, следовательно, включать и выключать каждое из устройств можно несколько раз в сутки и на разную мощность. При отключении питания, таймеры реле не сбиваются, так как их настройки хранятся в энергонезависимой памяти Arduino. Текущее время также не сбивается, так как оно берётся из модуля часов реального времени, который снабжен батарейкой.

Нам понадобится:

    х 1шт.
  • Дисплей LCD1602 I2C зелёный или синий x 1шт.
  • Trema I2C HUB прямоугольный или квадратный x 1шт. x 1шт. x 1шт. x 1шт.

Для реализации проекта нам необходимо установить библиотеки:

    для работы с символьными ЖК дисплеями. для работы с энкодерами через аппаратный таймер. для работы с модулями реального времени.
  • Библиотеки EEPROM, Wire и pgmspace используемые в скетче, входят в стандартный набор Arduino IDE.

О том как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE .

Видео:


Схема подключения:

Trema модуль RTC и дисплей LCD1602 I2C подключаются к аппаратной шине I2C через Trema I2C HUB, а Trema энкодер можно подключать к любым (цифровым или аналоговым) выводам Arduino, их номера указываются в скетче (в примере использованы выводы D4, D7 и D8). Для удобства подключения используется Trema Shield.

Реле времени: включение нагрузки по времени

Приборы подключаются к каналам 1-4:

Алгоритм работы:

Режим просмотра времени: При включении питания на индикаторе отображается текущее время, дата и день недели. Номера включённых каналов отображаются в правом верхнем углу дисплея.

Меню>часы>время: Этот раздел меню предназначен для установки текущего времени. Устанавливаемый в данный момент параметр времени (часы, минуты, секунды) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>часы>дата: Этот раздел меню предназначен для установки текущей даты и дня недели. Устанавливаемый в данный момент параметр даты (день, месяц, год, день недели) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>таймеры>00:00-00:00-0>время и канал: Этот раздел меню предназначен для установки (редактирования) времени старта/сброса таймера и номера канала которым он управляет. Устанавливаемый в данный момент параметр (час старта, минута старта, час сброса, минута сброса, номер канала) должен мигать. Выбор значения осуществляется поворотом энкодера, а переход к следующему значению, нажатием на энкодер.

Меню>таймеры>00:00-00:00-0>повторы: Этот раздел меню предназначен для установки (редактирования) повторов таймера по дням недели, в которые он должен срабатывать. Под устанавливаемым в данный момент параметром (ПН, ВТ, СР, ЧТ, ПТ, СБ, ВС) должен мигать курсор. Поворот энкодера устанавливает или сбрасывает стрелочку под устанавливаемым параметром, если она установлена значит в этот день недели таймер будет срабатывать, иначе он срабатывать не будет. Переход к следующему дню недели осуществляется нажатием на энкодер.

Меню>таймеры>00:00-00:00-0>уровень сигнала: Этот раздел меню предназначен для установки (редактирования) уровня сигнала на выбранном канале при срабатывании таймера. Выбор уровня сигнала от 5% до 100% осуществляется поворотом энкодера с шагом 5%, а нажатие на энкодер приведёт к выходу из данного раздела.

Примеры:

Создание таймера который по будням, между 18:00 и 20:00, будет включать 4 канал с уровнем сигнала 100%:
Создание таймера который между 19:00 и 21:00 каждого дня, будет включать 3 канал с уровнем сигнала 50%:

Примечание:

Код программы:

Библиотека iarduino_Encoder_tmr использует второй аппаратный таймер, НЕ ВЫВОДИТЕ СИГНАЛЫ ШИМ НА 3 ИЛИ 11 ВЫВОД!

Читайте также: