http://habrahabr.ru/post/242485/
Привет, Хабр!
Уже достаточно давно меня интересовала платформа Arduino, но никак до неё не доходили руки. И вот, недавно я приобрёл две платы Arduino и различные радиодетали. Наигравшись с парой диодов, я решил собрать что-нибудь полезное и интересное. Я давно хотел обзавестись бинарными часами и быстро понял, что приобретение Arduino — замечательная возможность сделать бинарные часы по своему вкусу.
С точки зрения электроники, собрать схему бинарных часов не так сложно. Я усложнил себе задачу и решил не отказывать себе в количестве кнопок и светодиодов. По начальному плану, на проект должно было пойти 22 диода, 6 кнопок и 1 пьезопищалка. Сначала я хотел собрать часы на Arduino Mega, т.к. на Arduino Uno недостаточно пинов для управления всем этим напрямую, однако потом я отказался от этой идеи и решил приобрести несколько выходных сдвиговых регистров 77HC595, так как это более рациональное решение проблемы.
Подготовка
Я решил начать с подсчёта того, что понадобится для построения девайса на макетной плате. В конечном итоге, получился вот такой список:
1 Arduino Uno.
2 Breadboard (полноразмерных, на 840 точек).
24 светодиода (я взял 7 красных, 7 зелёных, 6 синих, 2 жёлтых и 2 белых).
25 резисторов на 220 ом.
1 пьезопищалка.
6 тактовых кнопок.
3 выходных сдвиговых регистра 74HC595 в DIP-16 корпусе.
Соединительные провода и/или перемычки (у меня ушло около 90 штук).
Grove RTC — модуль часов реального времени от Seeed Studio на базе RTC-чипа DS1307.
Как это всё будет работать
Есть 10 видов бинарных часов. Одни показывают время в двоично-десятичном (BCD) представлении, вторые — в виде двоичных чисел. BCD-часы мне совсем не нравятся, поэтому я решил делать чисто двоичные часы. Возможно, их немного сложнее читать, чем BCD-часы, но мне кажется, что разница небольшая, так как быстро переводить короткие двоичные числа (до 6 бит) в десятичную систему счисления совсем не сложно. Также, я решил, что обязательно сделаю индикацию секунд на часах.
Схема распределения диодов такая:
Также, я решил сделать 6 кнопок:
Set — переход в режим настройки часов/будильника/таймера и сохранение текущего значения параметра в режиме настройки.
Mode — переключение между режимами часов, будильника и таймера.
Up — при настройке часов/будильника/таймера, увеличивает текущий параметр на единицу. В режиме таймера и будильника отвечает за активацию и отключение соответствующего режима. При срабатывании сигнала — отключает сигнал будильника/таймера.
Down — при настройке часов/будильника/таймера, уменьшает текущий параметр на единицу. В режиме таймера приостанавливает таймер без сброса отсчёта к начальному состоянию. При срабатывании сигнала будильника — будильник откладывается на 5 минут (snooze).
24/12 — переключение между 24-часовым и 12-часовым представлением времени.
Dim — отключает/включает светодиоды (при отключенных светодиодах никакие кнопки кроме Dim не работают).
Подключение компонентов
Все светодиоды должны подключаться последовательно с резистором (я использовал резисторы на 220 Ом), иначе можно сжечь не только сам диод, но и повредить Arduino. Резистор можно подключать как к катоду светодиода, так и к аноду. Общаться с диодами мы будем через сдвиговые регистры 74HC595. Это чип с 16-ю контактами. Они позволяют управлять большим числом выводов, используя всего 3 цифровых пина на Arduino.
Распиновка 74HC595:
Q0-Q7 — это выводы сдвигового регистра. К ним мы будем подключать светодиоды.
Vcc — это пин для питания. На него мы подаём 5В.
GND — земля. Соединяем её с GND на Ардуино.
OE — активация выводов. Этот пин инвертированный, т.е. для активации выводов нужно снять напряжение, а для отключения — наоборот подать. В нашем случаи управлять этим пином не обязательно, поэтому его можно просто замкнуть на землю.
MR — очистка регистра. Этот пин также инвертированный и нам не нужно им управлять, поэтому его можно подключить к 5В.
ST_CP — это пин, отвечающий за обновление состояния сдвигового регистра. Во время записи нового состояния на этот пин следует подать LOW, а после записи — HIGH, чтобы обновить состояние выводов. Его нужно подключить к цифровому пину на Ардуино. Соединить все ST_CP на всех трёх регистрах можно параллельно.
SH_CP — это пин, отвечающий за сдвиг регистра на 1 бит. Его нужно подключить к цифровому пину на Ардуино. Соединить три SH_CP на всех микросхемах также можно параллельно.
DS — Пин, на который мы подаём данные. Его нужно подключить к цифровому пину на Ардуино.
Q7' — Пин для каскадного соединения с другими 74HC595. Нужно соединить Q7' первого регистра с DS второго и Q7' второго с DS третьего регистра. На третьем регистре Q7' никуда подключать не нужно.
Получается примерно такая схема подключения:
Пьезопищалку я подключил к третьему пину Ардуино последовательно с 220-омным резистором. Нужно отметить, что для работы с пьезопищалкой нужен пин, поддерживающий ШИМ (PWM). На Arduino Uno это пины 3, 5, 6, 9, 10 и 11.
С кнопками было два варианта: либо использовать внешние резисторы, либо использовать встроенные в Arduino подтягивающие резисторы, которые включаются так:
pinMode(pin,INPUT_PULLUP);
Единственное отличие такого подхода — что при нажатии кнопки будет считываться LOW, а при отпускании — HIGH, но при этом не потребуются внешние резисторы для подавления дребезга, поэтому я выбрал именно такой вариант. Нужно просто соединить одну сторону кнопок с землёй, а другую — с цифровыми пинами Ардуино.
Итоговая конструкция должна была выглядеть примерно так:
Сборка устройства на Breadboard
Когда я приобрёл все недостающие детали, я приступил к сборке девайса на Breadboard. В принципе, внешний вид получился вполне предсказуемый:
Конечно, результат оказался далёк от того, что я хотел получить. Всё-таки Breadboard сильно ограничивает свободу размещения компонентов. К тому же, пучок проводов, нависающий над макеткой не добавляет эстетического удовольствия. Впрочем, на то макетная плата и существует, чтобы собирать на ней макеты устройств, а не готовые устройства.
Написание кода
Я решил не использовать чужие наработки и реализовать программную часть полностью самостоятельно, с нуля. Начал я разработку с написания подпрограммы, которая при включении мигает всеми светодиодами и пищит пьезопищалкой. Это позволяет убедиться в том, что цепь (не считая кнопок) собрана правильно и не развалилась с момента прошлого включения. Многие устройства делают нечто подобное при включении.
Я не буду подробно рассматривать реализацию отдельных функций, так как скетч получился весьма объёмный. Поэтому я кратко рассмотрю некоторые аспекты реализации.
Работа со светодиодами
Так как мы общаемся с диодами через сдвиговый регистр, первым делом нужно было реализовать подпрограммы для удобной работы с диодами. Состояние всех светодиодов хранится в массиве led из трёх элементов типа unsigned char и выводится через сдвиговые регистры в конце каждой итерации основного цикла. Для упрощения работы с диодами реализован целый ряд вспомогательных функций, позволяющий легко устанавливать нужные биты в led в соответствии со входными аргументами, например часы, минуты и т.д. Также я реализовал различные эффекты анимации диодов. Например, если часы не настроены — часовые и минутные диоды будут мигать (по аналогии с обычными цифровыми часами, которые обычно мигают «0:00» если они не настроены). Для секундных диодов есть анимация, когда один диод бегает влево-вправо по полосе секундных диодов. Она используется, например, в режиме будильника (т.к. секундные диоды там не имеют более рационального применения) или во время настройки часов. Это придаёт более интересный внешний вид часам.
Основной цикл
Основная логика программы, фактически, представляет собой конечный автомат. В зависимости от текущего состояния, часы выводят соответствующую информацию, и переходят из одного состояния в другое при нажатии кнопок и событиях таймера. Реализовано это как большое число вложенных условий. На каждой итерации loop проверяется состояние кнопок и таймеров и вызов их обработчиков, после чего состояние диодов обновляется.
Ввод
Для обработки ввода понадобится массив, содержащий состояние кнопок (для того, чтобы при нажатии кнопки обработчик срабатывал только один раз для каждого нажатия). Когда напряжение на пине кнопки переходит в LOW — мы ставим соответствующий кнопке элемент массива в true (если он уже true — то не делаем ничего) и вызываем обработчик нажатия. Когда напряжение возвращается в HIGH — сбрасываем элемент массива в false. Проверка состояния кнопок реализована в виде одной подпрограммы.
Таймеры
Основная работа выполняется при срабатывании таймеров. В проекте я использовал два таймера. Один — с секундным разрешением (для обработки состояния часов, будильника и таймера, а также некоторых анимаций) и второй — с разрешением в 1/8 секунды. Он используется для вывода текущего времени (чтобы увеличить скорость визуального отклика часов при смене режимов), для анимации секундных светодиодов и подачи сигнала будильника. Оба таймера реализованы достаточно просто. С помощью значения, полученного от millis(), проверяем, прошёл ли определённый интервал времени с момента прошлого срабатывания таймера, и, если интервал прошёл — вызываем обработчик и сохраняем новое время срабатывания таймера. Также, реализована простая корректировка таймера. Если по какой-то причине, таймер сработал на несколько миллисекунд позже, чем нужно — то интервал следующего срабатывания уменьшится на такое же количество миллисекунд для компенсации опоздания.
Полный исходный код скетча доступен на Github:
Смотрим, что получилось
С программной точки зрения, полученный девайс работал идеально (во всяком случаи, на первый взгляд). Всё, что я хотел видеть в девайсе было реализовано и работало стабильно.
Но не обошлось и без ложки дёгтя. При практической проверке выяснилось, что часы отстают примерно на 1 секунду в час, что приводит к накоплению весьма значительной ошибки за короткое время. Подробное изучения вопроса показало, что проблема была в том, что оригинальная Arduino Uno использует для тайминга не кварцевый, а керамический резонатор, который не обладает достаточной точностью для измерения времени на длительных отрезках времени. Получился просчёт — крупный кварцевый резонатор на плате используется только для контроллера USB-To-Serial и использовать его для тайминга не представляется возможным. Было несколько вариантов решения проблемы. Наиболее интересным и удобным мне показался вариант с использованием часов реального времени. Кроме решения проблемы отставания часов они дают приятный бонус — при отключении питания Arduino часы не сбиваются за счёт батарейки-таблетки.
Я приобрёл модуль Grove RTC от Seeed Studio. Это уже готовая для использования плата с чипом часов реального времени DS1307. Также на плате расположен часовой кварц, три резистора и держатель для батарейки. Выглядит она так:
RTC-модуль общается с Arduino по шине I2C. Пины SDA и SCL подключаются на Arduino Uno к пинам A4 и A5 соответственно. GND цепляется к земле. 5V уже занят платой часов, так что подключить Vcc RTC-модуля некуда. Однако, так как RTC-модуль потребляет мало тока (в пределах допустимой нагрузки на цифровые пины) — его можно запитать от одного из цифровых пинов, который будет находиться в HIGH постоянно.
Доработка кода
Для работы с модулем RTC на сайте Seeed Studio доступна готовая библиотека. При реализации работы с RTC в первую очередь возник вопрос, как определять при включении, что нужно считывать текущее время из RTC. Для этих целей было решено использовать флаг в EEPROM. Если значение нулевого байта в EEPROM отличается от нуля — берём время из RTC, если байт равен нулю — то мы выполняем «первый запуск» с ненастроенными часами, мигающими 0:00. Также, было логично заодно реализовать и сохранение в EEPROM последнего установленного времени будильника и таймера. Для возможности отката к «заводским» настройкам я немного расширил процедуру самотестирования при включении — если зажать во время включения кнопку SET — EEPROM будет очищен и девайс произведёт программную перезагрузку (путём выставления счётчика команд в 0).
Затем я реализовал подмножество необходимых функций для работы с RTC в проекте и оставалось только правильно интегрировать их в остальной код. Я перенёс из секундного таймера в отдельный секундный таймер, привязанный к RTC все обработчики, связанные со временем. Старый секундный таймер я оставил под некритичные к точности задачи (например, анимацию мигания светодиодов).
В отличие от остальных таймеров, которые проверяются на каждой итерации основного цикла, проверка RTC-таймера вложена в обработчик таймера на 1/8 секунды. Работа с I2C — достаточно ресурсоёмкая операция и постоянный опрос RTC в цикле приводит к нежелательным эффектам, вроде опаздывания таймеров и искажённого звучания пьезопищалки. Проверка RTC-таймера из секундного таймера привела бы к тому, что часы периодически отсчитывали бы две секунды (т.к. керамический резонатор моей Arduino немного отстаёт от реального времени), а это крайне нежелательно, поэтому обработка RTC в таймере на 1/8 секунды была наиболее оптимальным вариантом.
Придаём устройству завершённый вид
Насколько бы ни была хороша программная часть, внешний вид полученного устройства всё равно оставляет желать лучшего. Поэтому я решил перенести его с Breadboard на полноценную печатную плату. Первым делом, нужно было сделать разводку платы. Для этих целей я использовал Fritzing, так как у меня там уже была построена схема устройства и вид на Breadboard. Я не стал доверять работу автотрассировщику и произвёл трассировку платы вручную. Этот процесс занимает определённое время, но в итоге я получил готовый для производства проект печатной платы:
Я решил заказывать производство печатной платы в китае. У Seeed Studio есть сервис по производству печатных плат Fusion PCB. Fritzing умеет экспортировать проекты печатных плат в формат Extended Gerber (RS-274X), с которым работает большинство производителей печатных плат (в т.ч. и Seeed Studio). Я заказал производство плат и уже через две недели получил посылку с изготовленными платами:
Осталось только припаять все компоненты на плату. Раньше мне никогда не доводилось заниматься пайкой, но я быстро освоился. Как мне кажется, для первого раза получилось довольно неплохо:
Конечный результат выглядит намного лучше, чем макет на Breadboard.
Заключение
Я получил то, что хотел — свои собственные бинарные часы с будильником и таймером. Если использовать батарейный отсек — то получаются вполне автономные часы, которые могут стоять где угодно. Платформа Arduino полностью оправдала мои ожидания и я думаю, что я ещё не раз буду использовать её в своих проектах.
Ссылки
Каскадное соединение нескольких выходных сдвиговых регистров 74HC595Использование встроенных подтягивающих резисторовИсходный код скетча бинарных часов