Arduino: светодиодные часы с синхронизацией времени
Захотелось к себе в комнату повесить цифровые часы на стену. Покупать готовые было не интересно, хотелось собрать что-нибудь самому. Встал вопрос об отображении цифр. Было два варианта: либо покупать семисегментные индикаторы, либо паять матрицу из светодиодов. Первый вариант отпал по причине отсутствия больших индикаторов в магазинах, а заказывать и ждать долго не хотелось. К тому же они недешёвые. Второй вариант требовал большого количества светодиодов и огромного времени на их последующую пайку.
Но тут я увидел в магазине светодиодные ленты и всё сразу решилось. В статье фотоотчёт по изготовлению таких часов и схемы с исходным кодом.

Итак, вот она, светодиодная лента:

Питается от постоянного напряжения 12 вольт. Лента состоит из соединённых между собой светодиодов размещённых на полимерной основе, с обратной стороны — самоклейка.
Ленту можно резать! И это самое главное. Места реза обозначены меткой и имеют с двух сторон контакты для пайки. Каждое такое звено состоит из трёх светодиодов и резистора, соединённых последовательно. Звенья же соединяются вместе параллельно. Таким образом, каждое звено питается от напряжения 12 вольт. Длина отрезка ленты равна 5 сантиметрам.

Ленты есть разные по мощности. Соответственно, если нужны часы для уличного применения, то необходимо брать помощнее. Для домашнего — самые тусклые, иначе в комнате будет светло как днём (не шучу).
Список того, что нам понадобится для сборки часов:
- корпус;
- 29 отрезков светодиодной ленты;
- Arduino;
- RTC (часы реального времени) на микросхеме DS1307;
- Ethernet-shield Wiznet W5100;
- 4 микросхемы PCF8574;
- 4 микросхемы ULN2003A;
- 2 резистора по 1.5 кОм;
- 1 резистор на 3 кОм;
- npn-транзистор, любой на напряжение питания 12 вольт и более;
- блок питания 12 вольт;
- макетная плата (либо можете вытравить свою), монтажные провода;
- синяя изолента 🙂
Теперь надо всё подключить по следующим схемам:


Ethernet-shield нам понадобится для подключения к Интернету, чтобы можно было синхронизировать часы с NTP-сервером.

Микросхемы PCF8574 — 8-битные расширители для шины I2C:

Их распиновка следующая:

- VDD — питание 5 вольт;
- SDA, SCL — шина I2C;
- A0..2 — пины для установки адреса микросхемы;
- P0..7 — входы/выходы;
- VSS — земля.
Адресация PCF8574:
Вход | Адрес на шине I2C | |||
---|---|---|---|---|
A2 | A1 | A0 | DEC | HEX |
0 | 0 | 0 | 32 | 0x20 |
0 | 0 | 1 | 33 | 0x21 |
0 | 1 | 0 | 34 | 0x22 |
0 | 1 | 1 | 35 | 0x23 |
1 | 0 | 0 | 36 | 0x24 |
1 | 0 | 1 | 37 | 0x25 |
1 | 1 | 0 | 38 | 0x26 |
1 | 1 | 1 | 39 | 0x27 |
ULN2003A — сборка Дарлингтона. Распиновка:

- COM — питание 12 вольт;
- E — земля;
- 1..7B — входы;
- 1..7C — выходы.
Микросхема является инвертирующей. То есть при подаче логической единицы на вход, на соответствующем выходе появляется земля.
Корпус часов я сделал из дерева:

Светодиодную ленту нарезал на кусочки и приклеил в виде восьмёрок на пластик:

Примеряем:

Теперь собираем схему и припаиваем «восьмёрки». Плюсовые выводы ленты подключаем все вместе к +12 вольт, минусовые к ULN2003A:



Пробуем включить:

Всё светится, отлично.
Для подключения точек нам понадобится любой npn-транзистор на напряжение не менее 12 вольт. Я взял первый попавшийся под руку. Это оказался C5027F-R. Ищем даташит на него, определяем местоположение коллектора, эмиттера и базы. Подключаем по схеме. Светодиоды LED1, LED2, LED3 и резистор R4 — расположены на отрезке ленты. Плюсовой вывод подключаем к питанию 12 вольт, минусовой — к коллектору транзистора. Средний светодиод заклеиваем чёрной изолентой, чтобы его не было видно.
Чтобы в комнате ночью не стало светло от таких часов, я заклеил все светодиоды двумя слоями синей изоленты и листом полупрозрачного синего пластика:


А затем всё сверху закрыл стеклом. Получились вот такие часы:


Ну и конечно исходный код скетча:
// Скетч для Arduino. Светодиодные часы с синхронизацией. // Версия 1.0 (29.07.2014) // // Автор: Гладышев Дмитрий (2014) // http://student-proger.ru/2014/07/arduino-svetodiodnye-chasy-s-sinhronizaciej-vremeni/ #include <SPI.h> //для Ethernet-shield #include <Ethernet.h> // --//-- #include <EthernetUdp.h> #include <Wire.h> //I2C для RTC #include <DS1307RTC.h> //RTC #include <Time.h> #include <Metro.h> //для задания интервалов времени (аля Cron) //**************************************************************************************** byte mac[] = { 0xDE, 0xAD, 0x00, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino byte clock_addr[] = {0x20, 0x21, 0x22, 0x23}; //адреса PCF8574 IPAddress timeServer(132, 163, 4, 101); // IP-адрес NTP сервера const int timeZone = 8; // TimeZone //**************************************************************************************** unsigned int localPort = 53; Metro TimeSync = Metro(86400000); //Частота синхронизации (1 сутки) char clockbuf[4]; EthernetClient client2; EthernetUDP Udp; unsigned long lastTimeUpdate = 0; bool DotTimeState = false; void setup() { for (int i=0; i<4; i++) { clockbuf[i]='-'; } UpdateClock(); // Ethernet connection: do { delay(1000); } while (Ethernet.begin(mac) == 0); Wire.begin(); Udp.begin(localPort); //Запрашиваем время с NTP сервера setSyncProvider(getNtpTime); //Если время получили успешно, то записываем данные в RTC if (timeStatus() != timeNotSet) { RTC.set(now()); } } //============================== void loop() { tmElements_t tm; if (TimeSync.check() == 1) //Синхронизация по времени { //Запрашиваем время с NTP сервера setSyncProvider(getNtpTime); //Если время получили успешно, то записываем данные в RTC if (timeStatus() != timeNotSet) { RTC.set(now()); } } if (client2.available()) { char c = client2.read(); } if (abs(millis() - lastTimeUpdate) > 500) { lastTimeUpdate = millis(); if (RTC.read(tm)) { DotTimeState=!DotTimeState; clockbuf[0] = (char)(tm.Hour/10)+'0'; if (tm.Hour<10) { clockbuf[0] = ' '; } clockbuf[1] = (char)(tm.Hour%10)+'0'; clockbuf[2] = (char)(tm.Minute/10)+'0'; clockbuf[3] = (char)(tm.Minute%10)+'0'; UpdateClock(); } } } //================================================ void reverse(char s[]) { int i, j; char c; for (i = 0, j = strlen(s)-1; i<j; i++, j--) { c = s[i]; s[i] = s[j]; s[j] = c; } } void itoa(int n, char s[]) { int i, sign; if ((sign = n) < 0) /* записываем знак */ n = -n; /* делаем n положительным числом */ i = 0; do { /* генерируем цифры в обратном порядке */ s[i++] = n % 10 + '0'; /* берем следующую цифру */ } while ((n /= 10) > 0); /* удаляем */ if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); } int len(char *buf) { int i=0; do { i++; } while (buf[i]!='\0'); return i; } void UpdateClock() { for (int i=0; i<4; i++) { Wire.beginTransmission(clock_addr[i]); Wire.write(SegmentValue(clockbuf[i])); Wire.endTransmission(); } } byte SegmentValue(char k) { byte u; switch (k) { case '0': u=B1111110; break; case '1': u=B0110000; break; case '2': u=B1101101; break; case '3': u=B1111001; break; case '4': u=B0110011; break; case '5': u=B1011011; break; case '6': u=B1011111; break; case '7': u=B1110000; break; case '8': u=B1111111; break; case '9': u=B1111011; break; case '-': u=B0000001; break; case ' ': u=B0000000; break; case 'E': u=B1001111; break; case 'U': u=B0111110; break; case 'u': u=B0011100; break; case 'I': u=B0110000; break; case 'i': u=B0010000; break; case 'O': u=B1111110; break; case 'o': u=B0011101; break; case 'P': u=B1100111; break; case 'A': u=B1110111; break; case 'S': u=B1011011; break; case 'F': u=B1000111; break; case 'H': u=B0110111; break; case 'J': u=B0111100; break; case 'L': u=B0001110; break; case 'C': u=B1001110; break; case 'B': u=B1111111; break; case 'b': u=B0011111; break; case 'd': u=B0111101; break; case '°': u=B1100011; break; default: u=B0000000; } byte t=0; //переворачиваем биты for (byte i=0; i<7; i++) { if ((u&(1<<i))!=0) { t=t|(1<<(6-i)); } } u=t; u=u<<1; if (DotTimeState) { u=u+1; } return u; } /*-------- NTP code ----------*/ const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { while (Udp.parsePacket() > 0) ; // discard any previously received packets sendNTPpacket(timeServer); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } return 0; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); } /*-------- NTP code END ----------*/
Смотрим строки 16-20:
byte mac[] = { 0xDE, 0xAD, 0x00, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino byte clock_addr[] = {0x20, 0x21, 0x22, 0x23}; //адреса PCF8574 IPAddress timeServer(132, 163, 4, 101); // IP-адрес NTP сервера const int timeZone = 8; // TimeZone
mac — MAC-адрес Ethernet-shield. В принципе, можете оставить таким же.
clock_addr — адреса микросхем PCD8574 на шине I2C. Если не меняли, то должны быть такими же. Определить адреса можно с помощью скетча i2c_scanner, который есть в конце статьи.
timeServer — IP-адрес NTP-сервера.
timeZone — номер часового пояса.
Видео посетителей сайта, оставленные ими в комментариях:
Павел:
AlexSoft:
Скетч
Библиотеки:
DS1307RTC
Time
Metro
I2C scanner
Работа проверялась с Arduino IDE v1.0.5-r2.
Библиотеки являются собственностью их авторов!
