Двійкові годинник з будильником і таймером на Arduino Uno

Привіт, Хабр!



Вже досить давно мене цікавила платформа 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 сильно обмежує свободу розміщення компонентів. До того ж, пучок проводів, що нависає над макеткой не додає естетичного задоволення. Втім, на те макетна плата і існує, щоб збирати на ній макети пристроїв, а не готові пристрої.

Написання коду</h4
Я вирішив не використовувати чужі напрацювання і реалізувати програмну частину повністю самостійно, з нуля. Почав я розробку з написання підпрограми, яка при включенні блимає усіма світлодіодами і пищить пьезопищалкой. Це дозволяє переконатися в тому, що ланцюг (не рахуючи кнопок) зібрана правильно і не розвалилася з моменту минулого включення. Багато пристроїв роблять щось подібне при включенні.

Я не буду детально розглядати реалізацію окремих функцій, так як скетч вийшов досить об'ємний. Тому я коротко розгляну деякі аспекти реалізації.

Робота зі світлодіодами
Так як ми спілкуємося з діодами через сдвиговый регістр, першим ділом потрібно було реалізувати підпрограми для зручної роботи з діодами. Стан всіх світлодіодів зберігається в масиві led з трьох елементів типу unsigned char і виводиться через зсувні регістри в кінці кожної ітерації основного циклу. Для спрощення роботи з діодами реалізований цілий ряд допоміжних функцій, що дозволяє легко встановлювати потрібні біти led згідно з вхідними аргументами, наприклад годинник, хвилини і т.д. Також я реалізував різні ефекти анімації діодів. Наприклад, якщо годинник не налаштовані — годинна і хвилинні діоди будуть мигати (за аналогією зі звичайними цифровими годинами, які зазвичай блимають «0:00» якщо вони не налаштовані). Для секундних діодів є анімація, коли один діод бігає вліво-вправо по смузі секундних діодів. Вона використовується, наприклад, в режимі будильника (т.к. секундні діоди там не мають більш раціонального застосування) або під час налаштування годин. Це надає більш цікавий зовнішній вигляд годинах.

Основний цикл
Основна логіка програми, фактично, являє собою кінцевий автомат. В залежності від поточного стану, годинник виводять відповідну інформацію, та переходять з одного стану в інше при натисканні кнопок і події таймера. Реалізовано це як велике число вкладених умов. На кожній ітерації loop перевіряється стан кнопок і таймерів та виклик їх обробників, після чого стан діодів оновлюється.

Enter
Для обробки введення знадобиться масив, що містить стан кнопок (для того, щоб при натисканні кнопки обробник спрацьовував тільки один раз для кожного натискання). Коли напруга на піне кнопки переходить в 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
Використання вбудованих підтягуючих резисторів
Вихідний код скетчу бінарних годин
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.