Схеми годин з світлодіодними матрицями на мікроконтролерах. Прості багатофункціональний годинник на трьох світлодіодних матрицях. Готові годинник на Arduino

Прості годинник на світлодіодних матрицях. Багато радіоаматори, початківці і не тільки люблять «винаходити велосипед» - будувати СВОЇ електронний годинник. Не оминула ця доля і мене. Конструкцій годин в інеті сьогодні звичайно предостатньо, але ось годин на світлодіодних матрицях чомусь серед них одиниці. У російськомовному інтернеті я знайшов тільки одну повністю закінчену і описану конструкцію. У той же час, світлодіодні матриці зараз дуже сильно подешевшали, і їх вартість не вище, а то і нижче, ніж у семисегментних індикаторів такого ж розміру. Наприклад застосовані мною GNM23881AD при розмірі 60х60мм були куплені за 1,5уе (3 індикатора обійшлися в 4,5уе), за ці гроші навряд чи можна купити чотири семісегментніка таких-же розмірів. А ось інформації, розмістити на матричному індикаторі, можна набагато більше. Крім цифр на них можна відображати будь-які літери, знаки, а за допомогою рядка, що біжить ще й текст.

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

Функціонал у годин такої:

  • Відлік часу, календар, день тижня. (Високосний рік враховується, перехід на літній / зимовий час не здійснюється).
  • Збереження ходу годинника при пропажі зовнішнього живлення (споживання становить 15мка).
  • Корекція ходу + - 59,9сек \\ добу, з кроком 0,1 сек. 9 будильників. 3 з яких «одноразові», і 6 «постійних», індивідуально настроюються по днях тижня.
  • Індивідуально настроюється тривалість звукового сигналу кожного будильника (1-15мін).
  • Звукове підтвердження натискання кнопок (можливо відключити).
  • Щогодинний звуковий сигнал (можливо відключити).
  • З 00-00 до 08-00 сигнал НЕ подається.
  • 1 або 2 датчика температури (Вулиця та будинок).
  • Настроюється рядок, що біжить, за допомогою якої виводиться вся інформація (окрім часу)
  • Величина компенсації ходу, і налаштування «біжучого рядка» - зберігаються навіть при зникненні резервного живлення.

«Серцем» годин обрана AtMega16A, через її доступності, дешевизни і «ногастості». Схему хотілося максимально спростити, тому все що можна, було покладено на контролер. В результаті вдалося обійтися всього двома мікросхемами, контролером і регістром TPIC6B595. Якщо кому то недоступний TPIC6B595, то можна його замінити на 74НС595 + ULN2803. Обидва варіанти були випробувані. Так само можна спробувати застосувати TPIC6С595, вона трохи слабенька, і злегка грілася, але в цілому працювала стабільно. Відлік часу проводиться за допомогою асинхронного таймі - Т2. Хід годин зберігається і при пропажі живлення. У цей час більша частина схеми знеструмлюється, а контролер живиться від батарейки, акумулятора, або від іоністори. Мені було цікаво «по гратися» з іоністорів, тому застосував його. Струм споживання годинами в черговому режимі складає 15мка. При харчуванні від іоністори на 1Ф, годинник «протрималися» чотири доби. Цього цілком достатньо для підтримки ходу під час перебоїв харчування. Якщо застосувати батарейку СR2032, то теоретично, за розрахунками заряду повинно вистачити на 1,5 року. Наявність напруги контролер «слухає» через висновок РВ.3 Цей висновок є инвертирующем входом компаратора. Напруга харчування, через дільник R2-R3 подається на висновок РВ.3, і в нормальному стані дорівнює приблизно 1,5в. Якщо зовнішня напруга впаде нижче 4,1 вольта, то напруга на виводі РВ.3 стане менше 1,23вольта, при цьому згенерує переривання від компаратора, і в обробнику цієї переривання вимикаються всі «зайві» вузли контролера і сам контролер присипляється. В цьому режимі продовжує працювати тільки відлічує час таймер Т2. При появі зовнішнього живлення, напруга на РВ.3 знову підніметься вище 1,23в, контролер «побачивши» це, переведе всі вузли в робочий стан. Якщо замість іоністори, буде використовуватися батарейка СR2032, то її потрібно підключити через діод (переважно діод Шотткі). Анод діода підключається до + батарейки, а катод до катода VD1. У звичайному режимі на екрані відображається час в форматі годинник-хвилини. З інтервалом в одну хвилину відбувається запуск рядка, що біжить. Біжучим рядком відображається день тижня, дата, рік, темп. будинки, і темп. на вулиці. Та, що біжить рядок настроюється, тобто можна включити / вимкнути відображення будь-якого з елементів. (Я наприклад завжди відключаю відображення року). При виключенні всіх елементів, що біжить рядок не запускається, і годинник постійно відображає поточний час. 9 будильників розділені на 3 одноразових і 6 багаторазових. При включенні будильників 1-3, вони спрацьовують тільки один раз. Для того щоб вони спрацювали ще раз, їх потрібно повторно включати вручну. А будильники 4-9 багаторазові, тобто вони будуть спрацьовувати щодня, у встановлений час. Крім того ці будильники можна налаштувати на спрацювання тільки в певні дні тижня. Це зручно, наприклад якщо не хочете щоб будильник розбудив Вас у вихідні. Або наприклад Вам потрібно прокидатися в будні дні в 7-00, а в четвер о 8-00, а на вихідних будильник не потрібен. Тоді налаштовуємо один багаторазовий на 7-00 в понеділок-середу і п'ятницю, а другий на 8-00 в четвер ... .. Крім того всі будильники мають настройку тривалості сигналу, і якщо Вам, для того щоб прокинутися, мало сигналу протягом 1 хвилини , то можна збільшити його на час від 1 до 15 хв. Корекція ходу проводиться один раз на добу, в 00-00. Якщо годинник поспішають наприклад на 5 сек на добу, то в 00-00-00 час встановиться в 23-59-55, якщо ж годинник відстає, то в 00-00-00 час встановиться в 00-00-05. Крок корекції - 0,1 сек. Максимальна корекція - 59,9 сек / добу. З справним кварцом більше навряд чи знадобитися. Корекція здійснюється і в черговому режимі при живленні від батареї. Світлодіодні матриці можна використовувати будь-які 8 * 8 світлодіодів з загальним катодом. Як вже було зазначено, я застосував GNM23881AD. В принципі можна «набрати» матрицю і з окремих світлодіодів. Мікроконтролер AtMega16a можна замінити на «старий» AtMega16 з буквою L. При цьому, теоретично повинен трохи збільшиться струм споживання від батарейки. Напевно буде працювати і просто AtMega16, але можуть виникнути проблеми при роботі від батареї. Діод D1 - бажано будь-який діод Шотткі. З звичайним випрямним теж працює, але щоб убезпечити себе від різних глюків, пов'язаних з тим що частина схеми харчується напругою «до діода», а частина «після діода» краще пошукати Шотткі. Транзистор VT1 - будь-який n-p-n. Управління годинами здійснюється двома кнопками. Їх кількість можна було довести до 8шт, не додаючи більше взагалі жодного компонента, крім самих кнопок, але захотілося спробувати «викрутиться» всього двома. Кнопки умовно названі «ОК» і «КРОК». Кнопкою «КРОК» як правило відбувається перехід до наступного пункту меню, а кнопкою «ОК» зміна параметрів поточного меню. Сигнал спрацював будильника також вимикається кнопками «ОК» або «КРОК». Натискання будь-якої кнопки під час сигналу будильника відключає його. Схема управління вийшла такою:

Відео як все працює!

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

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

З'єднання всіх електронних компонентів проводиться на спеціальній контактної ( «беспаячной») макетної платі, що виключає ризик отримання опіків, порізів та інших травм - тому займатися з конструктором Arduino можна і разом з дітьми. А наочний спосіб представлення принципової схеми допоможе не помилитися при складанні пристрою.

Крок 1. Список компонентів

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

  • платформа Arduino. Підійдуть найпростіші моделі - чи Micro;
  • контактна макетна плата;
  • з'єднувальні дроти для макетної плати;
  • модуль годин реального часу Adafruit DS3231;
  • світлодіодний матричний модуль 32x8 MAX7219;
  • дві кнопки.

Також знадобиться персональний комп'ютер і USB-mini-USB кабель для завантаження програми управління в пам'ять. Ось і все - паяльник, щипці для зняття ізоляції, монтажні ножі та інші професійні інструменти не потрібні: всі операції виконуються руками. Хіба що в деяких випадках зручніше використовувати пінцет, але можна обійтися і без нього.


Крок 2. Збірка електронної схеми

Схема електронного годинника з індикацією на світлодіодах із застосуванням Arduino навіть для недосвідчених радіоаматорів здасться досить простий. Для збірки потрібно всього кілька провідників. Таблиця підключень:

Модуль Arduino → світлодіодна матриця 32x8 MAX7219

Модуль Arduino → годинник реального часу Adafruit DS3231

Модуль Arduino → кнопки

D2 - кнопка 1

D3 - кнопка 2

Другий висновок кнопок з'єднується з землею GND.

Слід лише звернути увагу і запам'ятати, яким чином замкнуті між собою контактні отвори на макетної платі. Наступна схема ілюструє спосіб у внутрішньому сполученні контактних отворів:


Два ряди (1 і 4) з обох сторін замкнуті горизонтально - зазвичай вони використовуються як лінія живлення + 5V і земля GND. Всі внутрішні контакти (2 і 3) замкнуті вертикально. При цьому монтажна плата як вертикально, так і горизонтально розділена на дві незалежні одна від одної симетричні частини. Це дозволяє, наприклад, зібрати два різних пристрої на одній платі.

Схема електронного годинника з індикацією на світлодіодах, а також розташування елементів на платі представлена \u200b\u200bна ілюстрації:

Ретельно перевірте відповідність всіх з'єднань вказаною схемою. Також переконайтеся в тому, що провідники добре закріплені в контактних отворах монтажної плати.


Крок 3. Прошивка Arduino

Після того як складання та перевірка схеми завершена, можна приступати до завантаження керуючої програми (або «прошивки») в пам'ять Arduino.


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

// include libraries: #include "LedControl.h" #include // Font library #include // DS1307 clock #include "RTClib.h" // DS1307 clock #include // Button library by Alexander Brevig // Setup LED Matrix // pin 12 is connected to the DataIn on the display // pin 11 is connected to the CLK on the display // pin 10 is connected to LOAD on the display LedControl lc \u003d LedControl (6, 5, 4, 4); // sets the 3 pins as 12, 11 & 10 and then sets 4 displays (max is 8 displays) // global variables byte intensity \u003d 7; // Default intensity / brightness (0-15) byte clock_mode \u003d 0; // Default clock mode. Default \u003d 0 (basic_mode) bool random_mode \u003d 0; // Define random mode - changes the display type every few hours. Default \u003d 0 (off) byte old_mode \u003d clock_mode; // Stores the previous clock mode, so if we go to date or whatever, we know what mode to go back to after. bool ampm \u003d 0; // Define 12 or 24 hour time. 0 \u003d 24 hour. 1 \u003d 12 hour byte change_mode_time \u003d 0; // Holds hour when clock mode will next change if in random mode. unsigned long delaytime \u003d 500; // We always wait a bit between updates of the display int rtc; // Holds real time clock output char days \u003d ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); // day array - used in slide, basic_mode and jumble modes (The DS1307 outputs 1-7 values \u200b\u200bfor day of week) char daysfull \u003d ( "Sunday", "Monday", "Tuesday", "Wed", "Thursday", "Friday", "Saturday"); char suffix \u003d ( "st", "nd", "rd", "th"); // date suffix array, used in slide, basic_mode and jumble modes. e, g, 1st 2nd ... // define constants #define NUM_DISPLAY_MODES 3 // Number display modes (conting zero as the first mode) #define NUM_SETTINGS_MODES 4 // Number settings modes \u003d 6 (conting zero as the first mode) # define SLIDE_DELAY 20 // The time in milliseconds for the slide effect per character in slide mode. Make this higher for a slower effect #define cls clear_display // Clear display RTC_DS1307 ds1307; // Create RTC object Button buttonA \u003d Button (2, BUTTON_PULLUP); // Setup button A (using button library) Button buttonB \u003d Button (3, BUTTON_PULLUP); // Setup button B (using button library) void setup () (digitalWrite (2, HIGH); // turn on pullup resistor for button on pin 2 digitalWrite (3, HIGH); // turn on pullup resistor for button on pin 3 digitalWrite (4, HIGH); // turn on pullup resistor for button on pin 4 Serial.begin (9600); // start serial // initialize the 4 matrix panels // we have already set the number of devices when we created the LedControl int devices \u003d lc.getDeviceCount (); // we have to init all devices in a loop for (int address \u003d 0; address< devices; address++) { /*The MAX72XX is in power-saving mode on startup*/ lc.shutdown(3-address, false); /* Set the brightness to a medium values */ lc.setIntensity(3-address, intensity); /* and clear the display */ lc.clearDisplay(3-address); } //Setup DS1307 RTC #ifdef AVR Wire.begin(); #else Wire1.begin(); // Shield I2C pins connect to alt I2C bus on Arduino #endif ds1307.begin(); //start RTC Clock if (! ds1307.isrunning()) { Serial.println("RTC is NOT running!"); ds1307.adjust(DateTime(__DATE__, __TIME__)); // sets the RTC to the date & time this sketch was compiled } //Show software version & hello message printver(); //enable red led digitalWrite(13, HIGH); } void loop() { //run the clock with whatever mode is set by clock_mode - the default is set at top of code. switch (clock_mode){ case 0: basic_mode(); break; case 1: small_mode(); break; case 2: slide(); break; case 3: word_clock(); break; case 4: setup_menu(); break; } } //plot a point on the display void plot (byte x, byte y, byte val) { //select which matrix depending on the x coord byte address; if (x >\u003d 0 && x<= 7) { address = 3; } if (x >\u003d 8 && x<= 15) { address = 2; x = x - 8; } if (x >\u003d 16 && x<= 23) { address = 1; x = x - 16; } if (x >\u003d 24 && x<= 31) { address = 0; x = x - 24; } if (val == 1) { lc.setLed(address, y, x, true); } else { lc.setLed(address, y, x, false); } } //clear screen void clear_display() { for (byte address = 0; address < 4; address++) { lc.clearDisplay(address); } } //fade screen down void fade_down() { //fade from global intensity to 1 for (byte i = intensity; i > 0; i--) (for (byte address \u003d 0; address< 4; address++) { lc.setIntensity(address, i); } delay(30); //change this to change fade down speed } clear_display(); //clear display completely (off) //reset intentsity to global val for (byte address = 0; address < 4; address++) { lc.setIntensity(address, intensity); } } //power up led test & display software version number void printver() { byte i = 0; char ver_a = "MADE"; char ver_b = "IN"; char ver_c = "RUSSIA"; //test all leds. for (byte x = 0; x <= 32; x++) { for (byte y = 0; y <= 7; y++) { plot(x, y, 1); } } delay(300); fade_down(); while (ver_a[i]) { puttinychar((i * 4), 1, ver_a[i]); delay(35); i++; } delay(500); fade_down(); i = 0; while (ver_b[i]) { puttinychar((i * 4), 1, ver_b[i]); delay(35); i++; } delay(500); fade_down(); i = 0; while (ver_c[i]) { puttinychar((i * 4), 1, ver_c[i]); delay(35); i++; } delay(500); fade_down(); } // puttinychar // Copy a 3x5 character glyph from the myfont data structure to display memory, with its upper left at the given coordinate // This is unoptimized and simply uses plot() to draw each dot. void puttinychar(byte x, byte y, char c) { byte dots; if (c >\u003d "A" && c<= "Z" || (c >\u003d "A" && c<= "z")) { c &= 0x1F; // A-Z maps to 1-26 } else if (c >\u003d "0" && c<= "9") { c = (c - "0") + 32; } else if (c == " ") { c = 0; // space } else if (c == ".") { c = 27; // full stop } else if (c == ":") { c = 28; // colon } else if (c == "\"") { c = 29; // single quote mark } else if (c == "!") { c = 30; // single quote mark } else if (c == "?") { c = 31; // single quote mark } for (byte col = 0; col < 3; col++) { dots = pgm_read_byte_near(&mytinyfont[c]); for (char row = 0; row < 5; row++) { if (dots & (16 >\u003e Row)) plot (x + col, y + row, 1); else plot (x + col, y + row, 0); ))) Void putnormalchar (byte x, byte y, char c) (byte dots; // if (c\u003e \u003d "A" && c<= "Z" || (c >\u003d "A" && c<= "z")) { // c &= 0x1F; // A-Z maps to 1-26 // } if (c >\u003d "A" && c<= "Z") { c &= 0x1F; // A-Z maps to 1-26 } else if (c >\u003d "A" && c<= "z") { c = (c - "a") + 41; // A-Z maps to 41-67 } else if (c >\u003d "0" && c<= "9") { c = (c - "0") + 31; } else if (c == " ") { c = 0; // space } else if (c == ".") { c = 27; // full stop } else if (c == "\"") { c = 28; // single quote mark } else if (c == ":") { c = 29; // clock_mode selector arrow } else if (c == ">") (C \u003d 30; // clock_mode selector arrow) else if (c\u003e \u003d -80 && c<= -67) { c *= -1; } for (char col = 0; col < 5; col++) { dots = pgm_read_byte_near(&myfont[c]); for (char row = 0; row < 7; row++) { //check coords are on screen before trying to plot //if ((x >\u003d 0) && (x<= 31) && (y >\u003d 0) && (y<= 7)){ if (dots & (64 >\u003e Row)) (// only 7 rows. Plot (x + col, y + row, 1);) else (plot (x + col, y + row, 0);) //)))) // small_mode // show the time in small 3x5 characters with seconds display void small_mode () (char textchar; // the 16 characters on the display byte mins \u003d 100; // mins byte secs \u003d rtc; // seconds byte old_secs \u003d secs; / / holds old seconds value - from last time seconds were updated o display - used to check if seconds have changed cls (); // run clock main loop as long as run_mode returns true while (run_mode ()) (get_time (); / / check for button press if (buttonA.uniquePress ()) (switch_mode (); return;) if (buttonB.uniquePress ()) (display_date (); return;) // if secs changed then update them on the display secs \u003d rtc; if (secs! \u003d old_secs) (// secs char buffer; itoa (secs, buffer, 10); // fix - as otherwise if num has leading zero, eg "03" secs, itoa coverts this to chars with space "3". if (secs< 10) { buffer = buffer; buffer = "0"; } puttinychar(20, 1, ":"); //seconds colon puttinychar(24, 1, buffer); //seconds puttinychar(28, 1, buffer); //seconds old_secs = secs; } //if minute changes change time if (mins != rtc) { //reset these for comparison next time mins = rtc; byte hours = rtc; if (hours > < 1) { hours = hours + ampm * 12; } //byte dow = rtc; // the DS1307 outputs 0 - 6 where 0 = Sunday0 - 6 where 0 = Sunday. //byte date = rtc; //set characters char buffer; itoa(hours, buffer, 10); //fix - as otherwise if num has leading zero, e.g. "03" hours, itoa coverts this to chars with space "3 ". if (hours < 10) { buffer = buffer; //if we are in 12 hour mode blank the leading zero. if (ampm) { buffer = " "; } else { buffer = "0"; } } //set hours chars textchar = buffer; textchar = buffer; textchar = ":"; itoa (mins, buffer, 10); if (mins < 10) { buffer = buffer; buffer = "0"; } //set mins characters textchar = buffer; textchar = buffer; //do seconds textchar = ":"; buffer; secs = rtc; itoa(secs, buffer, 10); //fix - as otherwise if num has leading zero, e.g. "03" secs, itoa coverts this to chars with space "3 ". if (secs < 10) { buffer = buffer; buffer = "0"; } //set seconds textchar = buffer; textchar = buffer; byte x = 0; byte y = 0; //print each char for (byte x = 0; x < 6 ; x++) { puttinychar(x * 4, 1, textchar[x]); } } delay(50); } fade_down(); } // basic_mode() // show the time in 5x7 characters void basic_mode() { cls(); char buffer; //for int to char conversion to turn rtc values into chars we can print on screen byte offset = 0; //used to offset the x postition of the digits and centre the display when we are in 12 hour mode and the clock shows only 3 digits. e.g. 3:21 byte x, y; //used to draw a clear box over the left hand "1" of the display when we roll from 12:59 -> 1:00 am in 12 hour mode. // do 12/24 hour conversion if ampm set to 1 byte hours \u003d rtc; if (hours\u003e 12) (hours \u003d hours - ampm * 12;) if (hours< 1) { hours = hours + ampm * 12; } //do offset conversion if (ampm && hours < 10) { offset = 2; } //set the next minute we show the date at //set_next_date(); // initially set mins to value 100 - so it wll never equal rtc on the first loop of the clock, meaning we draw the clock display when we enter the function byte secs = 100; byte mins = 100; int count = 0; //run clock main loop as long as run_mode returns true while (run_mode()) { //get the time from the clock chip get_time(); //check for button press if (buttonA.uniquePress()) { switch_mode(); return; } if (buttonB.uniquePress()) { display_date(); return; } //check whether it"s time to automatically display the date //check_show_date(); //draw the flashing: as on if the secs have changed. if (secs != rtc) { //update secs with new value secs = rtc; //draw: plot (15 - offset, 2, 1); //top point plot (15 - offset, 5, 1); //bottom point count = 400; } //if count has run out, turn off the: if (count == 0) { plot (15 - offset, 2, 0); //top point plot (15 - offset, 5, 0); //bottom point } else { count--; } //re draw the display if button pressed or if mins != rtc i.e. if the time has changed from what we had stored in mins, (also trigggered on first entering function when mins is 100) if (mins != rtc) { //update mins and hours with the new values mins = rtc; hours = rtc; //adjust hours of ampm set to 12 hour mode if (hours > 12) (hours \u003d hours - ampm * 12;) if (hours< 1) { hours = hours + ampm * 12; } itoa(hours, buffer, 10); //if hours < 10 the num e.g. "3" hours, itoa coverts this to chars with space "3 " which we dont want if (hours < 10) { buffer = buffer; buffer = "0"; } //print hours //if we in 12 hour mode and hours < 10, then don"t print the leading zero, and set the offset so we centre the display with 3 digits. if (ampm && hours < 10) { offset = 2; //if the time is 1:00am clear the entire display as the offset changes at this time and we need to blank out the old 12:59 if ((hours == 1 && mins == 0)) { cls(); } } else { //else no offset and print hours tens digit offset = 0; //if the time is 10:00am clear the entire display as the offset changes at this time and we need to blank out the old 9:59 if (hours == 10 && mins == 0) { cls(); } putnormalchar(1, 0, buffer); } //print hours ones digit putnormalchar(7 - offset, 0, buffer); //print mins //add leading zero if mins < 10 itoa (mins, buffer, 10); if (mins < 10) { buffer = buffer; buffer = "0"; } //print mins tens and ones digits putnormalchar(19 - offset, 0, buffer); putnormalchar(25 - offset, 0, buffer); } } fade_down(); } //like basic_mode but with slide effect void slide() { byte digits_old = {99, 99, 99, 99}; //old values we store time in. Set to somthing that will never match the time initially so all digits get drawn wnen the mode starts byte digits_new; //new digits time will slide to reveal byte digits_x_pos = {25, 19, 7, 1}; //x pos for which to draw each digit at char old_char; //used when we use itoa to transpose the current digit (type byte) into a char to pass to the animation function char new_char; //used when we use itoa to transpose the new digit (type byte) into a char to pass to the animation function //old_chars - stores the 5 day and date suffix chars on the display. e.g. "mon" and "st". We feed these into the slide animation as the current char when these chars are updated. //We sent them as A initially, which are used when the clocl enters the mode and no last chars are stored. //char old_chars = "AAAAA"; //plot the clock colon on the display cls(); putnormalchar(13, 0, ":"); byte old_secs = rtc; //store seconds in old_secs. We compare secs and old secs. WHen they are different we redraw the display //run clock main loop as long as run_mode returns true while (run_mode()) { get_time(); //check for button press if (buttonA.uniquePress()) { switch_mode(); return; } if (buttonB.uniquePress()) { display_date(); return; } //if secs have changed then update the display if (rtc != old_secs) { old_secs = rtc; //do 12/24 hour conversion if ampm set to 1 byte hours = rtc; if (hours > 12) (hours \u003d hours - ampm * 12;) if (hours< 1) { hours = hours + ampm * 12; } //split all date and time into individual digits - stick in digits_new array //rtc = secs //array pos and digit stored //digits_new = (rtc%10); //0 - secs ones //digits_new = ((rtc/10)%10); //1 - secs tens //rtc = mins digits_new = (rtc % 10); //2 - mins ones digits_new = ((rtc / 10) % 10); //3 - mins tens //rtc = hours digits_new = (hours % 10); //4 - hour ones digits_new = ((hours / 10) % 10); //5 - hour tens //rtc = date //digits_new = (rtc%10); //6 - date ones //digits_new = ((rtc/10)%10); //7 - date tens //draw initial screen of all chars. After this we just draw the changes. //compare digits 0 to 3 (mins and hours) for (byte i = 0; i <= 3; i++) { //see if digit has changed... if (digits_old[i] != digits_new[i]) { //run 9 step animation sequence for each in turn for (byte seq = 0; seq <= 8 ; seq++) { //convert digit to string itoa(digits_old[i], old_char, 10); itoa(digits_new[i], new_char, 10); //if set to 12 hour mode and we"re on digit 2 (hours tens mode) then check to see if this is a zero. If it is, blank it instead so we get 2.00pm not 02.00pm if (ampm && i == 3) { if (digits_new == 0) { new_char = " "; } if (digits_old == 0) { old_char = " "; } } //draw the animation frame for each digit slideanim(digits_x_pos[i], 0, seq, old_char, new_char); delay(SLIDE_DELAY); } } } /* //compare date digit 6 (ones) and (7) tens - if either of these change we need to update the date line. We compare date tens as say from Jan 31 -> Feb 01 then ones digit doesn "t change if ((digits_old! \u003d Digits_new) || (digits_old! \u003d Digits_new)) (// change the day shown. Loop below goes through each of the 3 chars in turn eg" MON "for (byte day_char \u003d 0; day_char<=2 ; day_char++){ //run the anim sequence for each char for (byte seq = 0; seq <=8 ; seq++){ //the day (0 - 6) Read this number into the days char array. the seconds number in the array 0-2 gets the 3 chars of the day name, e.g. m o n slideanim(6*day_char,8,seq,old_chars,days); //6 x day_char gives us the x pos for the char delay(SLIDE_DELAY); } //save the old day chars into the old_chars array at array pos 0-2. We use this next time we change the day and feed it to the animation as the current char. The updated char is fed in as the new char. old_chars = days; } //change the date tens digit (if needed) and ones digit. (the date ones digit wil alwaus change, but putting this in the "if" loop makes it a bit neater code wise.) for (byte i = 7; i >\u003d 6; i -) (if (digits_old [i]! \u003d digits_new [i]) (for (byte seq \u003d 0; seq<=8 ; seq++){ itoa(digits_old[i],old_char,10); itoa(digits_new[i],new_char,10); slideanim(digits_x_pos[i],8,seq,old_char,new_char); delay(SLIDE_DELAY); } } } //print the day suffix "nd" "rd" "th" etc. First work out date 2 letter suffix - eg st, nd, rd, th byte s = 3; //the pos to read our suffix array from. byte date = rtc; if(date == 1 || date == 21 || date == 31) { s = 0; } else if (date == 2 || date == 22) { s = 1; } else if (date == 3 || date == 23) { s = 2; } for (byte suffix_char = 0; suffix_char <=1 ; suffix_char++){ for (byte seq = 0; seq <=8 ; seq++){ slideanim((suffix_char*6)+36,8,seq,old_chars,suffix[s]); // we pass in the old_char array char as the current char and the suffix array as the new char delay(SLIDE_DELAY); } //save the suffic char in the old chars array at array pos 3 and 5. We use these chars next time we change the suffix and feed it to the animation as the current char. The updated char is fed in as the new char. old_chars = suffix[s]; } }//end do date line */ //save digita array tol old for comparison next loop for (byte i = 0; i <= 3; i++) { digits_old[i] = digits_new[i]; } }//secs/oldsecs }//while loop fade_down(); } //called by slide //this draws the animation of one char sliding on and the other sliding off. There are 8 steps in the animation, we call the function to draw one of the steps from 0-7 //inputs are are char x and y, animation frame sequence (0-7) and the current and new chars being drawn. void slideanim(byte x, byte y, byte sequence, char current_c, char new_c) { // To slide one char off and another on we need 9 steps or frames in sequence... // seq# 0123456 <-rows of the display // | ||||||| // seq0 0123456 START - all rows of the display 0-6 show the current characters rows 0-6 // seq1 012345 current char moves down one row on the display. We only see it"s rows 0-5. There are at display positions 1-6 There is a blank row inserted at the top // seq2 6 01234 current char moves down 2 rows. we now only see rows 0-4 at display rows 2-6 on the display. Row 1 of the display is blank. Row 0 shows row 6 of the new char // seq3 56 0123 // seq4 456 012 half old / half new char // seq5 3456 01 // seq6 23456 0 // seq7 123456 // seq8 0123456 END - all rows show the new char //from above we can see... //currentchar runs 0-6 then 0-5 then 0-4 all the way to 0. starting Y position increases by 1 row each time. //new char runs 6 then 5-6 then 4-6 then 3-6. starting Y position increases by 1 row each time. //if sequence number is below 7, we need to draw the current char if (sequence < 7) { byte dots; // if (current_c >\u003d "A" && || (Current_c\u003e \u003d "a" && current_c<= "z")) { // current_c &= 0x1F; // A-Z maps to 1-26 // } if (current_c >\u003d "A" && current_c<= "Z") { current_c &= 0x1F; // A-Z maps to 1-26 } else if (current_c >\u003d "A" && current_c<= "z") { current_c = (current_c - "a") + 41; // A-Z maps to 41-67 } else if (current_c >\u003d "0" && current_c<= "9") { current_c = (current_c - "0") + 31; } else if (current_c == " ") { current_c = 0; // space } else if (current_c == ".") { current_c = 27; // full stop } else if (current_c == "\"") { current_c = 28; // single quote mark } else if (current_c == ":") { current_c = 29; //colon } else if (current_c == ">") (Current_c \u003d 30; // clock_mode selector arrow) byte curr_char_row_max \u003d 7 - sequence; // the maximum number of rows to draw is 6 - sequence number byte start_y \u003d sequence; // y position to start at - is same as sequence number. We inc this each loop // plot each row up to row maximum (calculated from sequence number) for (byte curr_char_row \u003d 0; curr_char_row<= curr_char_row_max; curr_char_row++) { for (byte col = 0; col < 5; col++) { dots = pgm_read_byte_near(&myfont); if (dots & (64 >\u003e Curr_char_row)) plot (x + col, y + start_y, 1); // plot led on else plot (x + col, y + start_y, 0); // else plot led off) start_y ++; // add one to y so we draw next row one down)) // draw a blank line between the characters if sequence is between 1 and 7. If we don "t do this we get the remnants of the current chars last position left on the display if (sequence\u003e \u003d 1 && sequence<= 8) { for (byte col = 0; col < 5; col++) { plot(x + col, y + (sequence - 1), 0); //the y position to draw the line is equivalent to the sequence number - 1 } } //if sequence is above 2, we also need to start drawing the new char if (sequence >\u003d 2) (// work out char byte dots; // if (new_c\u003e \u003d "A" && new_c<= "Z" || (new_c >\u003d "A" && new_c<= "z")) { // new_c &= 0x1F; // A-Z maps to 1-26 //} if (new_c >\u003d "A" && new_c<= "Z") { new_c &= 0x1F; // A-Z maps to 1-26 } else if (new_c >\u003d "A" && new_c<= "z") { new_c = (new_c - "a") + 41; // A-Z maps to 41-67 } else if (new_c >\u003d "0" && new_c<= "9") { new_c = (new_c - "0") + 31; } else if (new_c == " ") { new_c = 0; // space } else if (new_c == ".") { new_c = 27; // full stop } else if (new_c == "\"") { new_c = 28; // single quote mark } else if (new_c == ":") { new_c = 29; // clock_mode selector arrow } else if (new_c == ">") (New_c \u003d 30; // clock_mode selector arrow) byte newcharrowmin \u003d 6 - (sequence - 2); // minimumm row num to draw for new char - this generates an output of 6 to 0 when fed sequence numbers 2-8 . This is the minimum row to draw for the new char byte start_y \u003d 0; // y position to start at - is same as sequence number. we inc it each row // plot each row up from row minimum (calculated by sequence number ) up to 6 for (byte newcharrow \u003d newcharrowmin; newcharrow<= 6; newcharrow++) { for (byte col = 0; col < 5; col++) { dots = pgm_read_byte_near(&myfont); if (dots & (64 >\u003e Newcharrow)) plot (x + col, y + start_y, 1); // plot led on else plot (x + col, y + start_y, 0); // else plot led off) start_y ++; // add one to y so we draw next row one down))) // print a clock using words rather than numbers void word_clock () (cls (); char numbers \u003d ( "one "," two "," three "," four "," five "," six "," seven "," eight "," nine "," ten "," eleven "," twelve "," thirteen ", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"); char numberstens \u003d ( "ten", "twenty", "thirty", "forty", "fifty"); // potentially 3 lines to display char str_a; char str_b; char str_c; // byte hours_y, mins_y; // hours and mins and positions for hours and mins lines byte hours \u003d rtc; if (hours\u003e 12) (hours \u003d hours - ampm * 12;) if (hours< 1) { hours = hours + ampm * 12; } get_time(); //get the time from the clock chip byte old_mins = 100; //store mins in old_mins. We compare mins and old mins & when they are different we redraw the display. Set this to 100 initially so display is drawn when mode starts. byte mins; //run clock main loop as long as run_mode returns true while (run_mode()) { //check for button press if (buttonA.uniquePress()) { switch_mode(); return; } if (buttonB.uniquePress()) { display_date(); } get_time(); //get the time from the clock chip mins = rtc; //get mins //if mins is different from old_mins - redraw display if (mins != old_mins) { //update old_mins with current mins value old_mins = mins; //reset these for comparison next time mins = rtc; hours = rtc; //make hours into 12 hour format if (hours > 12) (hours \u003d hours - 12;) if (hours \u003d\u003d 0) (hours \u003d 12;) // split mins value up into two separate digits int minsdigit \u003d rtc% 10; byte minsdigitten \u003d (rtc / 10)% 10; // if mins<= 10 , then top line has to read "minsdigti past" and bottom line reads hours if (mins < 10) { strcpy (str_a, numbers); strcpy (str_b, "PAST"); strcpy (str_c, numbers); } //if mins = 10, cant use minsdigit as above, so soecial case to print 10 past /n hour. if (mins == 10) { strcpy (str_a, numbers); strcpy (str_b, " PAST"); strcpy (str_c, numbers); } //if time is not on the hour - i.e. both mins digits are not zero, //then make first line read "hours" and 2 & 3rd lines read "minstens" "mins" e.g. "three /n twenty /n one" else if (minsdigitten != 0 && minsdigit != 0) { strcpy (str_a, numbers); //if mins is in the teens, use teens from the numbers array for the 2nd line, e.g. "fifteen" //if (mins >\u003d 11 && mins<= 19) { if (mins <= 19) { strcpy (str_b, numbers); } else { strcpy (str_b, numberstens); strcpy (str_c, numbers); } } // if mins digit is zero, don"t print it. read read "hours" "minstens" e.g. "three /n twenty" else if (minsdigitten != 0 && minsdigit == 0) { strcpy (str_a, numbers); strcpy (str_b, numberstens); strcpy (str_c, ""); } //if both mins are zero, i.e. it is on the hour, the top line reads "hours" and bottom line reads "o"clock" else if (minsdigitten == 0 && minsdigit == 0) { strcpy (str_a, numbers); strcpy (str_b, "O"CLOCK"); strcpy (str_c, ""); } }//end worknig out time //run in a loop //print line a "twelve" byte len = 0; while (str_a) { len++; }; //get length of message byte offset_top = (31 - ((len - 1) * 4)) / 2; // //plot hours line byte i = 0; while (str_a[i]) { puttinychar((i * 4) + offset_top, 1, str_a[i]); i++; } //hold display but check for button presses int counter = 1000; while (counter > 0) (// check for button press if (buttonA.uniquePress ()) (switch_mode (); return;) if (buttonB.uniquePress ()) (display_date ();) delay (1); counter--;) fade_down (); // print line b len \u003d 0; while (str_b) (len ++;); // get length of message offset_top \u003d (31 - ((len - 1) * 4)) / 2; i \u003d 0; while (str_b [i]) (puttinychar ((i * 4) + offset_top, 1, str_b [i]); i ++;) // hold display but check for button presses counter \u003d 1000; while (counter\u003e 0) (if (buttonA.uniquePress ()) (switch_mode (); return;) if (buttonB.uniquePress ()) (display_date ();) delay (1); counter--;) fade_down () ; // print line c if there. len \u003d 0; while (str_c) (len ++;); // get length of message offset_top \u003d (31 - ((len - 1) * 4)) / 2; i \u003d 0; while (str_c [i]) (puttinychar ((i * 4) + offset_top, 1, str_c [i]); i ++;) counter \u003d 1000; while (counter\u003e 0) (// check for button press if (buttonA.uniquePress ()) (switch_mode (); return;) if (buttonB.uniquePress ()) (display_date ();) delay (1); counter- -;) fade_down (); // hold display blank but check for button presses before starting again. counter \u003d 1000; while (counter\u003e 0) (// check for button press if (buttonA.uniquePress ()) (switch_mode (); return;) if (buttonB.uniquePress ()) (display_date ();) delay (1); counter- -;)) fade_down (); ) /// scroll message - not used at present - too slow. void scroll () (char message \u003d ( "Hello There"); cls (); byte p \u003d 6; // current pos in string byte chara \u003d (0, 1, 2, 3, 4, 5); // chars from string int x \u003d (0, 6, 12, 18, 24, 30); // xpos for each char byte y \u003d 0; // y pos // clear_buffer (); while (message [p]! \u003d "\\< 6; c++) { putnormalchar(x[c],y,message[ chara[c] ]); //draw a line of pixels turned off after each char,otherwise the gaps between the chars have pixels left in them from the previous char for (byte yy = 0 ; yy < 8; yy ++) { plot(x[c] + 5, yy, 0); } //take one off each chars position x[c] = x[c] - 1; } //reset a char if it"s gone off screen for (byte i = 0; i <= 5; i++) { if (x[i] < -5) { x[i] = 31; chara[i] = p; p++; } } } } //display_date - print the day of week, date and month with a flashing cursor effect void display_date() { cls(); //read the date from the DS1307 byte dow = rtc; // day of week 0 = Sunday byte date = rtc; byte month = rtc - 1; //array of month names to print on the display. Some are shortened as we only have 8 characters across to play with char monthnames = { "January", "February", "March", "April", "May", "June", "July", "August", "Sept", "October", "November", "December" }; //print the day name //get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset byte len = 0; while(daysfull) { len++; }; byte offset = (31 - ((len-1)*4)) / 2; //our offset to centre up the text //print the name int i = 0; while(daysfull[i]) { puttinychar((i*4) + offset , 1, daysfull[i]); i++; } delay(1000); fade_down(); cls(); // print date numerals char buffer; itoa(date,buffer,10); offset = 10; //offset to centre text if 3 chars - e.g. 3rd // first work out date 2 letter suffix - eg st, nd, rd, th etc // char suffix={"st", "nd", "rd", "th" }; is defined at top of code byte s = 3; if(date == 1 || date == 21 || date == 31) { s = 0; } else if (date == 2 || date == 22) { s = 1; } else if (date == 3 || date == 23) { s = 2; } //print the 1st date number puttinychar(0+offset, 1, buffer); //if date is under 10 - then we only have 1 digit so set positions of sufix etc one character nearer byte suffixposx = 4; //if date over 9 then print second number and set xpos of suffix to be 1 char further away if (date > 9) (suffixposx \u003d 8; puttinychar (4 + offset, 1, buffer); offset \u003d 8; // offset to centre text if 4 chars) // print the 2 suffix characters puttinychar (suffixposx + offset, 1, suffix [s ]); puttinychar (suffixposx + 4 + offset, 1, suffix [s]); delay (1000); fade_down (); // print the month name // get length of text in pixels, that way we can centre it on the display by divindin the remaining pixels b2 and using that as an offset len \u200b\u200b\u003d 0; while (monthnames) (len ++;); offset \u003d (31 - ((len-1) * 4)) / 2; // our offset to centre up the text i \u003d 0; while (monthnames [i]) (puttinychar ((i * 4) + offset, 1, monthnames [i]); i ++;) delay (1000); fade_down (); ) // dislpay menu to change the clock mode void switch_mode () (// remember mode we are in. We use this value if we go into settings mode, so we can change back from settings mode (6) to whatever mode we were in. old_mode \u003d clock_mode; char * modes \u003d ( "Basic", "Small", "Slide", "Words", "Setup"); byte next_clock_mode; byte firstrun \u003d 1; // loop waiting for button (timeout after 35 loops to return to mode X) for (int count \u003d 0; count< 35 ; count++) { //if user hits button, change the clock_mode if (buttonA.uniquePress() || firstrun == 1) { count = 0; cls(); if (firstrun == 0) { clock_mode++; } if (clock_mode > NUM_DISPLAY_MODES + 1) (clock_mode \u003d 0;) // print arrown and current clock_mode name on line one and print next clock_mode name on line two char str_top; // strcpy (str_top, "-"); strcpy (str_top, modes); next_clock_mode \u003d clock_mode + 1; if (next_clock_mode\u003e NUM_DISPLAY_MODES + 1) (next_clock_mode \u003d 0;) byte i \u003d 0; while (str_top [i]) (putnormalchar (i * 6, 0, str_top [i]); i ++;) firstrun \u003d 0; ) Delay (50); )) // run clock main loop as long as run_mode returns true byte run_mode () (// if random mode is on ... check the hour when we change mode. If (random_mode) (// if hour value in change mode time \u003d hours. then reurn false \u003d ie exit mode. if (change_mode_time \u003d\u003d rtc) (// set the next random clock mode and time to change it set_next_random (); // exit the current mode. return 0;)) / / else return 1 - keep running in this mode return 1;) // set the next hour the clock will change mode when random mode is on void set_next_random () (// set the next hour the clock mode will change - current time plus 1 - 4 hours get_time (); change_mode_time \u003d rtc + random (1, 5); // if change_mode_time now happens to be over 23, then set it to between 1 and 3am if (change_mode_time\u003e 23) (change_mode_time \u003d random (1 , 4);) // set the new clock mode clock_mode \u003d random (0, NUM_DISPLAY_MODES + 1); // pick new random clock mode) // dislpay menu to change the clock settings void setup_menu () ( char * set_modes \u003d ( "Rndom", "24 Hr", "Set", "Brght", "Exit"); if (ampm \u003d\u003d 0) (set_modes \u003d ( "12 Hr");) byte setting_mode \u003d 0; byte next_setting_mode; byte firstrun \u003d 1; // loop waiting for button (timeout after 35 loops to return to mode X) for (int count \u003d 0; count< 35 ; count++) { //if user hits button, change the clock_mode if(buttonA.uniquePress() || firstrun == 1){ count = 0; cls(); if (firstrun == 0) { setting_mode++; } if (setting_mode > NUM_SETTINGS_MODES) (setting_mode \u003d 0;) // print arrown and current clock_mode name on line one and print next clock_mode name on line two char str_top; strcpy (str_top, set_modes); next_setting_mode \u003d setting_mode + 1; if (next_setting_mode\u003e NUM_SETTINGS_MODES) (next_setting_mode \u003d 0;) byte i \u003d 0; while (str_top [i]) (putnormalchar (i * 6, 0, str_top [i]); i ++;) firstrun \u003d 0; ) Delay (50); ) // pick the mode switch (setting_mode) (case 0: set_random (); break; case 1: set_ampm (); break; case 2: set_time (); break; case 3: set_intensity (); break; case 4: // exit menu break;) // change the clock from mode 6 (settings) back to the one it was in before clock_mode \u003d old_mode; ) // toggle random mode - pick a different clock mode every few hours void set_random () (cls (); char text_a \u003d "Off"; char text_b \u003d "On"; byte i \u003d 0; // if random mode is on , turn it off if (random_mode) (// turn random mode off random_mode \u003d 0; // print a message on the display while (text_a [i]) (putnormalchar ((i * 6), 0, text_a [i]) ; i ++;)) else (// turn randome mode on. random_mode \u003d 1; // set hour mode will change set_next_random (); // print a message on the display while (text_b [i]) (putnormalchar ((i * 6), 0, text_b [i]); i ++;)) delay (1500); // leave the message up for a second or so) // set 12 or 24 hour clock void set_ampm () (// AM / PM or 24 hour clock mode - flip the bit (makes 0 into 1, or 1 into 0 for ampm mode) ampm \u003d (ampm ^ 1); cls ();) // change screen intensityintensity void set_intensity () (cls (); byte i \u003d 0; char text \u003d "Bright"; while (text [i]) (puttinychar ((i * 4) +4, 0, text [i]); i ++;) // wait for button input while (! buttonA.uniquePress ()) ( levelbar (0,6, (intensity * 2) +2,2); // display the intensity level as a bar while (buttonB.isPressed ()) (if (intensity \u003d\u003d 15) (intensity \u003d 0; cls ();) else (intensity ++;) // print the new value i \u003d 0; while (text [i]) (puttinychar ((i * 4) +4, 0, text [i]); i ++;) // display the intensity level as a bar levelbar (0,6, (intensity * 2) + 2,2); // change the brightness setting on the displays for (byte address \u003d 0; address< 4; address++) { lc.setIntensity(address, intensity); } delay(150); } } } // display a horizontal bar on the screen at offset xposr by ypos with height and width of xbar, ybar void levelbar (byte xpos, byte ypos, byte xbar, byte ybar) { for (byte x = 0; x < xbar; x++) { for (byte y = 0; y <= ybar; y++) { plot(x+xpos, y+ypos, 1); } } } //set time and date routine void set_time() { cls(); //fill settings with current clock values read from clock get_time(); byte set_min = rtc; byte set_hr = rtc; byte set_date = rtc; byte set_mnth = rtc; int set_yr = rtc; //Set function - we pass in: which "set" message to show at top, current value, reset value, and rollover limit. set_date = set_value(2, set_date, 1, 31); set_mnth = set_value(3, set_mnth, 1, 12); set_yr = set_value(4, set_yr, 2013, 2099); set_hr = set_value(1, set_hr, 0, 23); set_min = set_value(0, set_min, 0, 59); ds1307.adjust(DateTime(set_yr, set_mnth, set_date, set_hr, set_min)); cls(); } //used to set min, hr, date, month, year values. pass //message = which "set" message to print, //current value = current value of property we are setting //reset_value = what to reset value to if to rolls over. E.g. mins roll from 60 to 0, months from 12 to 1 //rollover limit = when value rolls over int set_value(byte message, int current_value, int reset_value, int rollover_limit){ cls(); char messages = { "Set Mins", "Set Hour", "Set Day", "Set Mnth", "Set Year"}; //Print "set xyz" top line byte i = 0; while(messages[i]) { puttinychar(i*4 , 1, messages[i]); i++; } delay(2000); cls(); //print digits bottom line char buffer = " "; itoa(current_value,buffer,10); puttinychar(0 , 1, buffer); puttinychar(4 , 1, buffer); puttinychar(8 , 1, buffer); puttinychar(12, 1, buffer); delay(300); //wait for button input while (!buttonA.uniquePress()) { while (buttonB.isPressed()){ if(current_value < rollover_limit) { current_value++; } else { current_value = reset_value; } //print the new value itoa(current_value, buffer ,10); puttinychar(0 , 1, buffer); puttinychar(4 , 1, buffer); puttinychar(8 , 1, buffer); puttinychar(12, 1, buffer); delay(150); } } return current_value; } void get_time() { //get time DateTime now = ds1307.now(); //save time to array rtc = now.year(); rtc = now.month(); rtc = now.day(); rtc = now.dayOfWeek(); //returns 0-6 where 0 = Sunday rtc = now.hour(); rtc = now.minute(); rtc = now.second(); //flash arduino led on pin 13 every second //if ((rtc % 2) == 0) { // digitalWrite(13, HIGH); //} //else { // digitalWrite(13, LOW); //} //print the time to the serial port - useful for debuging RTC issues /* Serial.print(rtc); Serial.print(":"); Serial.print(rtc); Serial.print(":"); Serial.println(rtc); */ }

Тепер для завершення роботи над пристроєм буде потрібно виконати лише ряд простих операцій:


Компіляція програмного коду і подальша завантаження в пам'ять мікроконтролера займе деякий час, зазвичай не більше однієї хвилини. Про успішне завершення операції буде повідомлено в консолі Arduino IDE. Після чого залишається лише перезавантажити Arduino за допомогою кнопки Reset на пристрої - прості годинник на світлодіодних матрицях готові!

Готові годинник на Arduino

Годинника здійснюється за допомогою двох кнопок. Пристрій підтримує 12- і 24-годинний формат виведення часу, показ дати і дня тижня, відображення часу з секундами і без. Також є можливість змінювати яскравість світіння світлодіодів.


Ймовірно, надалі вам захочеться додати більше функцій (наприклад, термометр), або ж встановити пристрій в корпус власного дизайну - хороших результатів можна домогтися за допомогою виготовлення на верстатах з лазерною різкою. Але вже зараз ви зможете сміливо сказати, що зібрали повноцінні електронні годинники своїми руками!

Годинники + радіо + метео + ПДУ

Давно хотілося зробити годинник на RGB матрицях. Були знайдені і замовлені матриці GMT2088. Але як то кажуть апетит приходить під час їжі. Т.к навіть просто кольорові годинник це нудно, то вирішено було увіткнути в них FM тюнер, еквалайзер, датчики вологості, температури і тиску. Також зроблена можливість роботи від акумуляторів на випадок пропажі основного напруги.

В результаті вийшли такі годинник:




У червоних світлодіодах можна помітити 4 крапки, що світяться, це не у вас в очах четверо і не глюк фотоапарата просто перед матрицями вставлені дві плівки які стоять перед ЖК матрицею монітора і світіння діода розбивається на 4 частини. Якщо погратися розташуванням плівок відносно один одного то можна отримати багато різних відображень. Якість фото кульгає але основи видно.

Можливості налаштування годин:

1. Колір відображення прийнятої радіостанції. Для цілої та дрібного значення станції можна встановити свій колір.

2. Налаштування кольору рядка, що біжить.

3. Зміна швидкості біжучого рядка.

4. Налаштування кольору відображення годин. Можна кожному символу встановити свій колір.

5. Радіо діапазон 65мГц до 108МГц. Зберігає в пам'яті до 20 станцій радіо.

6. Звук для кожної станції зберігається окремо також як і установки еквалайзера.

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

8. Регулювання НЧ і ВЧ частот.

9. Вибір шрифту відображення годин до 8 шрифтів, можна завантажувати свої шрифти.

10. Автоматична або ручне регулювання яскравості матриці.

А) Повністю автоматична в залежності від освітленості

Б) Ручна клавішами на ПДУ або самих годинах

В) За встановленим часу. Встановлюється час включення мінімальної і максимальної яскравості.

11. Контроль заряду резервної батарейки годин.

12. Управління годинами за допомогою ПДУ. Зроблена можливість навчання ПДУ (формати NEC, RC5, SAMSUNG)

13. Зроблено 6 варіанту заміни символу при зміні часу. (Пізніше будуть додані ще варіанти)

14. Контроль основного напруги харчування. При пропажі перемикається на резервний акумулятор, при появі відключає акумулятор.

15. Таймер виключення з максимальним інтервалом 99 хвилин. Вимикає звук радіо.

16. Встановлення меж яскравості мінімального і максимального.

17. Примусовий висновок рядка, що біжить з інформацією про температуру в приміщенні і на вулиці

Програмне забезпечення повністю написано на асемблері а AVR Studio. Може десь -то і не дуже грамотно програма написана, т.к зайнявся ассемблером трохи більше року і то тільки у вільний час, але головне стабільно працює і швидко.

Серцем пристрою виступає AVR Atmega32 на частоті 16 мГц. Годинник зроблений на двох платах. На одній матриці з обв'язкою. На другий все інше.

Управління матрицями стовпцями віддано регістрів STP16CP05 в корпусі TSSOP-24. У цих регістрів 16 виходів і навантаження на кожен вихід до 80мА.Управління рядками дешифратор 74HC138. На вихід дешифратора встановлені польові транзистори IRF7314. Модулі RGB НЕ впаюються в плату, а були встановлені в роз'єми.

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

FM радіо реалізовано а мікросхемі RDA5807. У цій мікросхеми досить хороша чутливість.

Як датчик вологості застосований готовий модуль AM2321. Як датчик тиску готовий модуль BMP180. У цих датчиках також є датчик температури.

Датчик BMP180:

Модуль AM2321:


Як еквалайзера застосована мікросхема TEA6330T. Вона відповідає за регулювання гучності і налаштування НЧ і ВЧ частот.

Підсилювач потужності зроблений на PAM8403. Маленька мікросхема але видає достатню потужність. На вихід підключені динаміки 8 Ом 2Вт. Можна підключати і динаміки на 4 Ом. При 8 Ом вихідна потужність приблизно 1.6 Вт. При 4 Ом динаміці 3 Вт.

У годиннику передбачена автономна робота від акумулятора (АКБ). Було встановлено АКБ марки L12T1P31 3.7В 3700 мАг.

При пропажі напруги яскравість матриць автоматично налаштовується на мінімум. Якщо був режим автоматичного регулювання яскравості то він замінюється на ручний. Можливість регулювання яскравості при відсутності напруги зберігається. А також можна включити автоматичний режим. При відновлення напруги яскравість повернеться на той рівень яка була встановлена \u200b\u200bдо зникнення мережевої напруги.

Час роботи від АКБ в режимі годинника зі зниженою яскравістю приблизно 15 годин. Час роботи з максимальною яскравістю, шрифт широкий, гучність радіо середня приблизно 5 годин.

Реалізовано контроль напруги АКБ. Т.к внутрішній контролер АКБ відключає її при напрузі нижче 2.5 В, а при зниженні напруги нижче 3.2 В перетворювач починає працювати не правильно і на його виході його всього 5В. Годинники йдуть в нескінченний Reset. Тому і був обраний поріг напруги 3.2В що б не опускати до мінімального напруги для перетворювача. Якщо напруга опуститися нижче 3.2В годинник будуть знеструмлені.

На платі передбачена можливість установки спеціалізованою мікросхеми контролю напруги типу BU48xx. В даній схемі BU4832 контроль 3.2 В.

Контроль заряду акумулятора зібраний на мс TP4056. Перетворювач напруги з 3.7В до 12в зібраний на LM3488.

Передбачена можливість установки як 3-х так і 2-х контактних АКБ. Якщо не потрібен контроль температури АКБ то частина елементів не встановлюємо (детально див. Схему) .Все схеми плати і прошивка в кінці статті.

На елементах R13 R14 зібраний дільник для контролю напруги резервної батарейки.

На елементах R1 R10 R11 R12 зібраний дільник для контролю освітленості в автоматичному режимі.

На елементах Q1 Q2 R19-R22 зібраний согласователь рівнів т.к деякі мс живляться напругою 3В, а деякі 5В.

Для початку схеми основного модуля, модуля RGB матриць і модуля даткіка зовнішньої температури

Кілька фотографій готової плати RGB матриць. Плати з двостороннього склотекстоліти. Деякі доопрацювання робилися в процесі налагодження. Тому на деякі фото трохи відрізняються від плат які в архіві. В архіві всі зміни враховані.

Нижня сторона плати:


На нижньому боці резистори на 2.4 кОм просто напаяні на висновки.

Верхня сторона без встановлених матриць:


Вид збоку:


Вид зверху:


Ефекти зміни цифр (на відео не всі ефекти)

управління годинами


При першому включенні годин управляти ними можна тільки кнопками. Що б була можливість управляти ПДУ потрібно навчити годинник сигналам з ПДУ. Для цього затискаємо будь-яку з кнопок управління і натискаємо кнопку "Скидання".Пробіжить біжучий рядок "Налаштування ПДУ" і побачимо відображення першого формату ПДУ - NEC. За замовчуванням на матрицях висвічується перший протокол NEC. Перебір протоколів натисканням будь-якої з кнопок на задній панелі годин. Протоколи змінюються по колу NEC - Samsung - RC5.




Як я вже написав вище за замовчуванням виставлений протокол NEC. Беремо ПДУ і натискаємо на ньому будь-яку кнопку. Якщо обраний протокол і протокол ПДУ збіглися то напис стане зеленою і далі пристрій перейде в режим навчання ПДУ. Якщо протоколи не співпали то на матрицях не буде ніяких змін і напис так і залишиться блакитний. Переставляємо на наступний протокол і тиснемо знову будь-яку кнопку ПДУ.
Визначення протоколу має спрацювати з першого натискання кнопки ПДУ. Якщо спрацьовує з другого натискання кнопки ПДУ або більше то протоколи не збігаються і треба вибрати інший протокол.
Т.к протоколи NEC і Samsung трохи схожі то пульт формату NEC можна навчити в режимі протоколу Samsung але потрібно по два рази натискати одну й ту ж саму кнопку. Надалі спробую підкоригувати інтервали вимірів імпульсів і можливо це забереться.

Протокол RC5 найпростіший. У цьому режимі можна навчити будь-ПДУ але працювати він нормально не буде. У цьому режимі буде нормально працювати тільки ПДУ протоколу RC5.
Про відміну протоколів ПДУ дуже багато написано статей і описувати тут все це не буду.

Після визначення протоколу програма перейде в режим навчання кнопок ПДУ. На матрицях буде висвітлюватися яку кнопку треба натиснути. У режимі очікування колір символу буде блакитний.


Натискаємо кнопку на ПДУ. Якщо дані прийняті вдало то колір символу стане зеленим:


Якщо дані прийняті з помилкою то колір на пару секунд стане червоним, а потім пристрій перейде в повторне очікування натискання кнопки.


Після того як буде натиснута остання запитувана кнопка. Годинники перейдуть в основний режим роботи. Якщо не буде встановлено жодного датчика ні мікросхеми реального часу, то буде постійно виводиться рядок, що біжить "Жодного датчика, не знайдено". Якщо не встановлювати DS3231 і встановити будь-який з датчиків, то на матрицях будуть виводитися тільки миготливі точки.

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

Після настройки пульта ми побачимо відображається час. З цього режиму можна перейти в різні меню. У програмі передбачений контроль встановлених мікросхем TEA6330 і RDA5807. Якщо якась мікросхема ні буде встановлена \u200b\u200bто неможливо буде перейти в меню її налаштування і буде введена відповідна рядок, що біжить або "Еквалайзер не найден" або "Радіо не знайдено".

Опис дії кнопок ПДУ:

Кнопка вмикання / вимикання - включення - вимикання матриці

Кнопка стрілка вгору і стрілка вниз - регулювання яскравості матриці або зміни даних в режимі настройки. Яскравість цими кнопка регулюється в усіх меню.

Кнопка стрілка вліво або стрілка вправо - зміна шрифту в режимі відображення часу, а в режимі настройки будильника переміщення по підміню будильника

Кнопка "1" - меню "Годинник"

Кнопка "2" - меню "Радіо"

Кнопка "3" - меню "Будильники"

Кнопка "4" - меню "Еквалайзер"

Кнопка "5" - висновок рядка, що біжить

А) одне натискання висновок інформації з усіх датчиків

Б) два натискання на протязі 3 сек висновок тільки вуличної температури.

Кнопка "6" - меню "Таймер вимкнення"

Кнопка "8" - установка / скидання мінімальної і максимальної яскравості

Кнопка "9" - скидання секунд і корекція часу через інтернет

Кнопка "0" - автоматична або ручне регулювання яскравості

Кнопка CH + та CH - перегортання радіо станцій в режимі FM

Кнопка звук + і звук - регулювання гучності

Кнопка вмикання / вимикання звуку - включення вимикання звуку

Кнопка "МЕНЮ" - перехід в більшість параметрів в режимі часу, перехід в настройки радіо в режимі радіо і настройки будильників в режимі будильників

Кнопка "ESC" - вихід з підменю

Перехід в залежності від того в якому режимі знаходиться пристрій. Якщо знаходиться в режимі "Час", то перехід буде в основні настройки. Якщо в режимі "Радіо", то перехід в настройки радіо. Якщо в режимі "Будильники", то в налаштування будильника

Кнопка "ОК" - підтвердження змін в режимах налаштування

У годиннику 8 шрифтів для відображення часу. Перемикання між шрифтами кнопками "Вліво" або "Вправо" ПДУ або S6 на платі годин.

Приклад широкого шрифту:


Приклад вузького шрифту:


Додавання своїх шрифтів

Шрифти зберігаються в EEprom. Максимально можна завантажити 8 шрифтів.

Шрифт створював за допомогою PixelFontEdit-2.7
Сам шрифт і исходник Eprom для Atmel studuo6.2 в архіві в кінці сторінки.
Шрифти додаємо з мітки eeFront1:
Кількість шрифтів не більше 8, більше не помістяться. Програма сама визначить кількість завантажених шрифтів.
В кінці шрифтів обов'язково повинна бути рядок .db "E", "N", "D" що означає кінець шрифтів.
букви END можуть бути великі чи маленькі але латинські.
Компілюємо файл EEprom і прошиває його. Можна відразу в EEprom забити всі свої значення в ньому докладні коментарі до кожного рядка.
Також в шрифті задається які будуть точки розділяють годинник і мінути.Устанавлівается тільки в першому символі кожного шрифту.
У першому символі якщо встановлений біт0 першого байта то буде подвійна, якщо скинуто то одинарна.

налаштування в режимі відображення часу

У цьому меню зміни параметрів кнопками "Вгору" та "Вниз" підтвердження змін кнопка "ОК". Вийти з налаштування можна в будь-який момент натиснувши кнопку "ESC".

Натискаємо кнопку "Меню" пробіжить біжучий рядок «Основні параметри" далі рядок "Колір радіо". Буде блимати ціла частина діапазону FM, кнопками "Вгору" або "Вниз" встановлюємо потрібний колір і натискаємо кнопку "ОК". Почне блимати дрібна частина діапазону FM. Також встановлюємо потрібний колір і натискаємо "ОК".



Якщо буде встановлено варіант кольору для рядка, що біжить як на фото:


Те при виведенні рядка, що біжить колір її буде кожен раз новий.

Далі пробіжить рядок "Установка годин". Почнуть блимати значення годин. Кнопками "Вгору" або "Вниз" встановлюємо поточну годину і натискаємо "ОК". Почнуть блимати хвилини. Кнопками "Вгору" або "Вниз" встановлюємо поточні хвилини і натискаємо "ОК".


Далі пробіжить рядок "Колір годин". У цьому меню для кожного символу годин можна налаштувати свій колір. Вибір кольору кнопками "Вгору" або "Вниз". Всього 7 варіантів кольорів. Восьмий варіант це різнобарвний символ з чергуються квітами. Як тільки вибрали потрібний колір натискаємо кнопку "ОК". Далі налаштовуємо колір для наступних символів і тиснемо "ОК".


Кнопками "Вгору" або "Вниз" встановлюємо поточну дату і натискаємо "ОК".




Далі пробіжить рядок "Установка дня тижня". Дні тижня відображаються в скороченні ВС воскресіння, СБ - субота, ПТ - п'ятниця і т.д. Кнопками "Вгору" або "Вниз" встановлюємо поточний день тижня і натискаємо "ОК". На цьому основна настройка завершена.


Далі пробіжить біжучий рядок з поточному днем \u200b\u200bтижня, датою, роком, тиск, температурою і вологістю. Пристрій перейде в режим відображення часу. В цьому режимі біжучий рядок з виведенням інформації про температуру, тиск та вологість в приміщенні буде виводиться приблизно через 4 хвилини. Виведення інформації з усіх датчиків кнопка "5" ПДУ. Рядок про температуру і вологість на вулиці виводиться 1 раз в 15 хвилин (можна самостійно встановити потрібний інтервал, про це трохи нижче.) Якщо з якихось причин якийсь датчик не буде встановлено то інформація з цього датчика не з'явиться на в рухомому рядку. Т.к датчик температури є у всіх трьох датчиках, то дані будуть читатися з того датчика який встановлений. За замовчуванням температура читається з DS3221.

Режим Радіо.

Перехід в цей режим здійснюється натисканням кнопки "2" ПДУ. Діапазон радіо 65мГц - 108МГц.

В цьому режимі біжучий рядок також пробігає приблизно через 4 хвилини.

З'явиться таке меню:


Перші чотири цифри це частота прийнятої станції. Буква "М" або "С" сигнал, що приймається моно або стерео. Під буквою червоним кольором рівень сигналу. У самому низу відображається громкость.Перелістиваніе налаштованих станцій кнопками "СН +" і "СН", а регулювання гучності кнопками "Гр +" і "Гр". Значення гучності зберігається для кожної станції окремо. Так само для кожної станції окремо зберігаються настройки еквалайзера ВЧ і НЧ. Для переходу в режим еквалайзера натискаємо кнопку "4" ПДУ (опис налаштування трохи нижче).

Для переходу в режим настройки радіо натискаємо кнопку "Меню" ПДУ. Буде виведена рядок, що біжить "Налаштування радіо".

Буква позначає Стерео або моно стане червоного кольору. У режимі відтворення вона синього кольору. Якщо протягом 15 секунд не натиснути жодної клавіші то пристрій перейде в режим відтворення радіо.


Зміна частоти станції здійснюється кнопками "СН +" і "СН" і кнопками "Вліво" "Вправо" ПДУ.

В діапазоні 76-108 мГц клавішами "Ліворуч" та "Праворуч" зміни діапазону на 1МГц, а клавіші "CH-" і "CH +" ПДУ зміни на 0.1мГц.
У діапазоні 65-76 мГц клавішами "Ліворуч" та "Праворуч" зміни діапазону на 0.1мГц, а клавіші "CH-" і "CH +" ПДУ зміни на 0.01мГц.

Регулювання гучності кнопками "Гр +" і "Гр". Після вибору потрібної частоти і рівня гучності натискаємо кнопку "ОК". Буде відображена осередок для запису поточної станції. Якщо в осередку вже є записана станція то номер її буде червоним кольором.



Якщо осередок вільна то вона буде зеленим кольором


Вибір потрібної осередку кнопками "СН +" і "СН". Усього доступно 20 осередків для запису. Після вибору потрібної осередку натискаємо кнопку "ОК". У осередок буде записана частота станції, і рівень гучності. Пристрій повернеться в режим настройки. Що б вийти з режиму настройки натискаємо кнопку "ESC" або нічого не натискаємо на протязі 15 секунд. Пристрій перейде в режим відтворення радіо.

Налаштування будильників

Перехід в режим настройки будильників кнопкою "3" ПДУ. Буде виведена рядок, що біжить "Будильники". Якщо жодна кнопка не натискати на протязі 15 секунд пристрій перейде в режим відображення часу.

Після переходу в меню будильників побачимо наступне:


Б1 означає номер будильника. Всього їх сім. Прочерки після номера означають що будильник не включений. Кнопками "Ліворуч і" Вправо "змінюємо номер будильника. Якщо будильник вимкнений буде не можливо переглянути його налаштування. Для включення будильника натискаємо кнопку" ОК ". Включиться світлодіод позначає що є включені будильники і з'явиться дзвіночок.


Тепер якщо можна буде переглянути налаштування будильника. Меню кожного будильника поділено на чотири частини. У першій частині налаштовується час включення, гучність. У другій частині налаштовується час вимикання і режим звуку лінійний або наростаючий. У третій частині налаштовується включається станція або вибирається зумер. У четвертій частині вибираються дні включення будильника. Натиснемо кнопку "Вправо" перед нами буде перша частина.


Тут відображається час включення будильника. У правому нижньому кутку гучність включення. Щоб змінити установки натискаємо кнопку "ОК". Почне блимати годинник включення. Кнопками "Ліворуч" та "Праворуч" змінюємо значення і натискаємо кнопку "ОК". Так налаштовуємо все чотири параметри.

Після настройки рівня гучності тиснемо "ОК". Цим закінчуємо настройку в першій частині будильника.

У цій частині налаштовується час вимикання і гучність наростаюча при спрацьовуванні будильника або лінійна.

Лінійна гучність відображається праворуч у вигляді прямокутника:


наростаюча громкость відображається у вигляді трикутника:


Всі дії для налаштування такі ж як і в першій частині.

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

Після настройки другій частині натискаємо кнопку "Вправо" і потрапляємо в третю частину настройки будильника.


У цій частині налаштовується включається станція радіо або вибираємо зумер гортаючи до напису BEEP.


Налаштування тими ж кнопками що і в попередніх частинах. Станції вибираються тільки з тих що були налаштовані в режимі "Радіо".

Після настройки третьої частини натискаємо кнопку "Вправо" і потрапляємо в четветрі частина настройки будильника.

Тут налаштовуємо дні включення будильника.


Позначення днів тижня зроблено цифрами. 1-понеділок, 2 - вівторок, 3 середа і т.д

Натискаємо "ОК" ПДУ почне блимати перший символ. Кнопками "Ліворуч" або "Вправо" ПДУ включаємо день тижня або вимикаємо. Зелений колір це задіяний день, червоний не задіяне. Внизу під задіяним днем \u200b\u200bзагоряється світлодіод. Зроблено для того хто збирав годинник на звичайних світлодіодах що б бачити який з день тижня задіяний.

Після настройки натискаємо кнопку "Вправо" на ПДУ і переходимо до наступного будильнику або натискаємо "ESC" і виходимо в режим відображення часу.

При спрацьовуванні будильника світлодіод блимає. Якщо під час роботи будильника натиснути кнопку "Вимкнути" ПДУ світлодіод перестане блимати і будильник не вимкнеться по досягненню часу вимикання тобто відбувається скасування виключення будильника.

Налаштування еквалайзера

Перехід в меню настройки еквалайзера натисканням кнопки "4" ПДУ. Пробіжить біжучий рядок "Еквалайзер" .У цьому режимі настроюються НЧ і ВЧ частоти. Для кожного каналу радіо настройки еквалайзера зберігаються окремо. Якщо жодна з кнопок не натискати на протязі 15 секунд то буде здійснений перехід в режим відображення часу.

Для настройки потрібних частот натискаємо кнопку "Вліво" або "Вправо" ПДУ.

Налаштування НЧ і ВЧ максимум:



Для зміни фільтра НЧ або ВЧ натискаємо кнопку "ОК". Почне блимати смуга зліва. Кнопками "Ліворуч" та "Праворуч" міняємо значення. Для збереження встановленого значення тиснемо "ОК" ПДУ. Залежно від величини значення також змінюється колір покажчики і цифри позначають рівень. Ближче до максимального значення будуть червоними. Ближче до мінімального значення синім квітів, а в середньому діапазоні зеленим кольором.

Налаштування НЧ мінімум:


Налаштування НЧ середнє значення:


Установка-зняття порога мінімальної яскравості

За умовчанням яскравість регулюється від максимальної до повного гасіння індикаторів. Але це не дуже зручно в автоматичному режимі. При повній темряві індикатори згаснуть і нічого не буде видно. Для цього і був зроблений цей режим що б мінімальна яскравість не опускалася нижче або вище встановленої

Натискаємо кнопку 8 ПДУ потрапляємо в меню установки меж яскравості.

Перше встановлює межу мінімальної яскравості.


Кнопками "Вгору" "Вниз" ПДУ встановлюємо потрібну яскравість і натискаємо "ОК" ПДУ. Буде тривалий сигнал зумера і записаний поріг мінімальної яскравості.


Кнопками "Вгору" "Вниз" ПДУ встановлюємо потрібну яскравість і натискаємо "ОК" ПДУ.

Буде тривалий сигнал зумера і записаний поріг мінімальної яскравості.

У наступному розділі встановлюється час включення мінімальної яскравості, а далі час включення максимальної яскравості.

Вихід їх меню настройки "ESC" ПДУ. Тепер при регулювання яскравості не вийде зробити темніше або світліше встановленого порога і в автоматичному режимі яскравість не буде нижчою або вище встановленої.

Що б задіяти режим автоматичного регулювання яскравості на ПДУ натискаємо "0" переводячи в автоматичний режим роботи. На передній панелі годин загориться світлодіод.

Що б працювала регулювання яскравості в реальному часі в залежності відосвітленості час включення мінімальної і максимальної яскравості повинні бути рівні. В іншому випадки яскравість буде встановлюватися за обраним часу. Зміна яскравості з мінімального на максимальний рівень і на оборот відбувається плавно.

Для скидання порога яскравості просто входимо знову в меню настройки порога яскравості і все межі будуть скинуті.

Для установки порога з кнопок читаємо нижче 8 пункт.

управління кнопками

На задній панелі годин розміщені кнопки S4-S9. Залежно в якому меню знаходимося функція кнопок змінюється.

Кнопка S8 перехід в різні меню. Для того що б перейти в потрібне меню натискаємо S8 і утримуємо. Після запланованого сигналів зумера відпускаємо і потрапляємо в вибране меню. Кількість сигналів зумера буде відповідати номеру кнопки ПДУ. Якщо буде 4 сигналу то значить потрапимо в меню "Еквалайзер", якщо 1 сигнал то меню відображення часу і т.д.

1. Функції кнопок в меню годин:

S6 зміна шрифту

S7 скидання секунд і оновлення часу через інтернет

S9 перехід в більшість параметрів.

При натисканні S9 переходимо в меню настройки. У цьому меню кнопками S4 і S5 змінюємо параметр, кнопка S6 перехід до наступного настроюється параметру. Вихід з меню неможливий доведеться пройтися по всіх параметрам.

Після настройки останнього параметра пробіжить біжучий рядок і пристрій повернеться в режим відображення часу.

2. Функції кнопок в меню радіо:

S4 і S5 регулювання звуку "+" і "-"

S6 і S7 перехід по настроєних каналів "+" і "-"

S9 перехід в режим настройки радіо

В меню налаштувань дію кнопок:

S4 і S5 регулювання звуку "+" і "-"

S6 і S7 зміна частоти радіостанції "+" і "-"

S9 перехід в меню запису станції в пам'ять. Кнопками S6 і S7 міняємо номер комірки пам'яті, а кнопка S9 підтвердження запису.

Вихід з меню настроек- не натискайте ні які кнопки і вихід буде автоматичний через 15-20 сек.

3. Функції кнопок в меню настройки будильників:

S4 і S5 перегортання будильників "+" і "-"

S6 і S7 не задіяні

S9 Включення і вимикання будильника

Якщо будильник включений натискаємо S4 потрапляючи в основні настройки включеного будильника. Перехід по підміню будильника S4 і S5. Для настройки параметра тиснемо S9 і S4 і S5 змінюємо параметр. Наступне натискання S9 перехід до наступного настроюється параметру. Пристрій вийде з налаштувань будильника автоматично якщо протягом 15 сек. і не натискати будь-яка з кнопок.

4. Функції кнопок в меню еквалайзера:

S4 і S5 перегортання параметрів, що настроюються.

S6 вхід в режим зміни обраного параметра. В цьому режимі S4 і S5 змінює параметр, а S6 підтвердження зміни.

S7 не задіяна.

Вихід з режиму автоматичний через 15 сек якщо жодна з кнопок не натискалася.

6. Функції кнопок в меню таймера вимкнення:

S4 і S5 установка інтервалу часу виключення з кроком в 5 хвилин. Інтервал 0 хвилин означає, що таймер вимкнений

S6 вихід в меню відображення часу.

7. Вільно. Можливо буде використано в подальшому.

8. Функції кнопок в меню установки межі яскравості:

S4 і S5 зміна яскравості.

S6 підтвердження обраної яскравості.

S7 вихід з налаштування.

9. Вільно. Можливо буде використано в подальшому.

Трохи про застосовані деталях і їх заміна та налагодження.

Всі SMD елементи типорозміру 0805. Стабілізатор U1 LM317ADJ можна замінити на будь-який з напругою стабілізації 3 Вольта наприклад ASM1117-30. У цьому випадки R18 замінити перемичкою, а R17 не встановлювати. Стабілізатор на 5В U8 LM2576-5 можна замінити на LM2596-5. Світлодіоди D6 D7 D11 D12 D13 типорозміру 2835. Резистором R24 встановлюємо гучність роботи зумера. Не забуваємо виставити струм заряду для застосовуваного акумулятора резисторами R44 R45 (див. Таблицю на схемі). При великому струмі заряду рекомендую на TP4056 встановити радіатор. Резисторами R38 і R49 можна обмежити максимальну гучність або ж їх замінити перемичками. Конденсатори С28 С29 С42 С43 в обв'язки TEA6330T встановлювати які вказані на схемі. Відхід від ємності в ту чи іншу сторону змінити діапазон регулювання частот. С28 і С43 відповідають за НЧ, а С29 і С42 за ВЧ. Пробував ставити 6.8nF C29 і С42 ВЧ частоти практично не змінюються. Резисторами R28 R33 R31 можна змінити напругу на виході перетворювача. При номіналах зазначених на схемі напруга 11.86В.

Розраховується напруга за формулою:

U вих \u003d 1.26 * (1 + ((R33 + R28) / R31))

Діоди Шотки D4 D5 D1 D8 D10 можна замінити будь-якими на ток 2А. Стабілітрон D3 можна зупинити будь-який на напругу стабілізації 4.3В 4.7В або 5В.

Монтаж краще починати з установки всіх стабілізаторів напруги і перевірити їх на працездатність, що б не було прикро за згорілі контролери і датчики. Потім краще змонтувати перетворювач і перевірити його подавши на 8 ногу LM3488 напруга в межах 3.2-5В. На виході має бути близько 12В.

Далі монтуємо елементи для заряду АКБ і контролю напруги АКБ і перевіряємо на працездатність. Для 2-х проводового АКБ не встановлює R39, а R43 замінюємо перемичкою. Також чинимо для 3-х проводового якщо не потрібен контроль температури АКБ. Середній висновок АКБ залишається бовтатися. Якщо ж контроль температури потрібен, то встановлюємо всі елементи, хоча R43 можна і не ставити т.к воно буде підключено паралельно терморезистору АКБ і сильно впливати на роботу не буде.

Контролер напруги акумулятора зібраний на TL431 і LM358. На TL431 зібраний джерело зразкового напруги на 2.5 В. Ця напруга надходить на 2 висновок LM358, а на 3 вавод LM358 надходить напруга від АКБ через дільник R58 R62 як тільки напруга на АКБ буде нижче 3.2 на 3 виведення також напруга стане менше 2.5 вольт і на 1 виведення буде 0 Q9 закриється. Позитивне напруга через R56 R51 надійде на базу Q8. Q8 Закриється тим самим і закриє транзистор Q7.1 знеструмлюючи всю плату. Після відключення запуск годин буде можливий тільки при підключенні зовнішнього джерела живлення т.к LM358 харчується від зовнішнього джерела живлення.

Якщо є мікросхема BU4832 то TL431 LM358 їх обв'язку і R56 не встановлює. Якщо ж збираємо на TL431 і LM358 щось не встановлюємо R53 і можливо доведеться підлаштувати поріг спрацьовування дільником R58 R62. При наявності основного харчування повинен йти заряд АКБ, а на виході перетворювача не повинно бути напруги. Якщо напруга АКБ вище 3.2 вольт то на базі транзистора Q9 має бути приблизно 0.7- 0.8В.

Тепер перевіряємо роботу резервного джерела живлення. Спочатку схему заживлюємо від основного джерела. Потім його висмикуємо з мережі і на виході перетворювача має з'явиться 12В. Якщо перетворювач не запуститься то дивимося що на базі Q9. Якщо там напругу менше 0.7В, а АКБ заряджена, то вимірюємо напруга на 2 виведення LM358 має бувальщина 2.5В і на 3 виведення більше 2.5В. Якщо все нормально, то несправна LM358 або не має з'єднання ній харчування. Поріг відключення можна відрегулювати R62. При збільшенні його номіналу поріг відключення збільшується тобто пристрій буде повністю знеструмлено при більш глибокому розряді АКБ. При зменшенні номіналу поріг зменшується.

Конденсатори C6 C11 C21 C20 C37 рекомендується застосовувати з низьким ESR.

Якщо все працює то монтуємо процесор прошиває його і монтуємо всі інші елементи .. На платі для зручності прошивки передбачені контакти для ISP роз'єму. Висновок GND для ISP паяем в будь-якому зручному місці.

індикація світлодіодів

Світлодіод D11 світиться, то значить встановлено режим автоматичного регулювання яскравості. Ручне регулювання не працює. Після зміни режиму кнопкою "0" ПДУ.

Світлодіод D12 світиться, то значить є включені будильники. Якщо блимає, то спрацював будильник.

Світлодіод D13 світиться, то значить потрібно замінити резервну батарейку годин. Навіть якщо витягнути батарейку часом не скинеться т.к за рахунок заряду конденсаторів деякі час буде харчуватися DS3231. Так що хвилин 5 є на заміну батарейки.

Світлодіод D6 світиться, то значить йде заряд АКБ

Світлодіод D7 світиться, то значить АКБ заряджена.

Якщо обидва світлодіода D6 і D7 не світяться, то це означає, що годинник працює від внутрішнього АКБ.

Виготовляємо друковані плати (ПП)

Були невеликі прорахунки в кріпленні динаміків, вирізи зробив більше ніж потрібно і довелося мудрувати з їх кріпленням. В архіві враховані всі зміни в носившиеся в плату.

Версія плати з WI-FI.



Друковані плати виготовляю за допомогою негативного фоторезиста. Друкую шаблон на плівці струменевим принтером Epson L800в негативному вигляді. Очищаю заготовку за допомогою порошку "Пемолюкс". На мокру заготівлю наношу плівковий фоторезист. забрак ламінатора пропрасовуйте праскою виставленим на розподіл 1. Накладаю шаблон і засвічує трьома УФ лампами по 20W кожна протягом 1 хв 10 сек. Далі знову під праску, потім змиваю НЕ задубленого ділянки і труїти. Свердлю кілька отворів в крайніх контактних майданчиках і роблю проколи в цих же місцях в шаблоні. Плата і шаблон поєднується за допомогою голок. Труїться друга сторона. Перша заклеюється скотчем.

Свердлимо отвори. Перехідні отвори зазвичай свердлю діаметром 0.4 мм і запаюють в них зволікання.Запаює елементи.

Фото готової плати з WI-FI



До монтажу ставимося уважніше що б потім не шукати чому не працює. Деякі вивідні елементи потрібно запаювати з двох сторін. Висновки цих елементів як перехідні отвори. Під кварц на плату потрібно наклеїти скотч або ізоляційну стрічку що б не було контакту з доріжками або перепаяти на сторону Atmega32.

Корпус був виготовлений з 4 мм м'якого пластику. Ріжеться відмінно канцелярським ножем. Т.к світлодіоди знаходяться глибоко то потрібні світлопроводи. Як світловодів використана кришка коробки від автомобільних ламп. Корпус був накреслений в CorelDRAW роздрукований на звичайному папері і за допомогою двостороннього скотча закріплений на пластиці і вирізаний. Ховаємо все в корпус, прикручуємо антену:


Датчики стирчать зверху:


Що б був нормальний вигляд і не стирчали просто датчики з корпусу був куплений в автомагазині набір для КРІПЛЕНІЯ номера і світловідбиваючі ковпачки приклеєні зверху на корпус.

Вийшло так:


Ось ніби і все. У планах додати можливість виведення інформації з RDS. Так само зробити ще один варіант під енкодери.

Написана програма для модуля ESP8266. Також змінена прошивка для Atmega32 в архіві все оновлено. Так само були внесені зміни в схему. Були був належним чином під'єднаний сигнали TX RX від Atmega до ESP8266. Всі зміни описані тут враховані у схемі в архіві.

У схемі зроблені невеликі доробки. Об'єднані сигнали Reset Atmega і ESP8266. Але для цього потрібно узгодити сигнали по напрузі. Доданий резистор на 9.1 кОм між Reset Atmega і GND. Резистор 10кОм між Reset ESP8266 і шиною 3V видалений. Напруга на виводі Reset повинно стати в межах 3V-3.3V.

Додано узгодження рівнів TX RX. Хоча можна обійтися і без нього але зробив так як повинно бути за правилами.

Для прошивки модуля ESP8266 необхідно:

1.Качаем (якщо не встановлена) Arduino IDE з офф сайту (https://www.arduino.cc/en/Main/Software), Встановлюємо.

2.Запустіть Arduino IDE, далі Файл - Налаштування - в полі Additional Boards Manager URLs вставити посилання на стабільну версіюhttp://arduino.esp8266.com/package_esp8266com_index.json

4 В Boards Manager в поле фільтра введіть esp8266 або вручну перегорніть список і натисніть ESP8266 by ESP8266 Community Forum
Натисніть Install і дочекайтеся закінчення завантаження (близько 130 Мегабайт).
Якщо завантаження сталася дуже швидко, можливо, що ви вже встановлювали Arduino IDE для ESP8266 і потрібно почистити кеш Boards Manager, інакше у вас залишиться встановленої стара версія. Потрібно спочатку деінсталювати стару версію, а потім необхідно видалити файли кешу. Для Win7 x64 видаліть файли з папки C: UsersПользовательAppDataRoamingArduino15 і повторіть все, починаючи з п.2
Закрийте Boards Manager і в меню Інструменти виберіть Плата - Generic ESP8266

Виберіть послідовний порт, до якого підключена плата. Відкриваємо файл прошивки.

Для прошивки модуля використовував PL2303. Драйвера на неї нижче в архіві на Win 8.1 працює. Драйвер встановлюємо в ручну.

Можна використовувати будь-який USB-COM перехідник.

Модуль ESP 8266 запаює на плату. З'єднуємо модуль з USB-COM переходникомз'єднання сигналів TX RX перехресне тобто TX ESP8266 на RX USB-COM і RX ESP8266 на TX USB-COM.Встановлюємо перемичку на XP10 і прибираємо перемички XP11 і XP12. Натискаємо Reset.

У прошивці для модуля який в годиннику потрібно зробити зміна.

У прошивці вписуємо ім'я своєї мережі WI-FI і пароль до неї.

Натискаємо іконку "Стрілка вправо" почнеться прошивка модуля.

Після завершення прошивки видаляємо перемичку XP10 і встановлюємо назад XP11 і XP12.

Рекомендую перевірити відкриті у Вас порти 123 на ПК і в роутері.
Простіше це зробити на ПК, в настройках часу вкладка Час по інтернету - Змінити параметри. В поле вбиваємоtime.nist.gov і тиснемо "Оновити зараз". Якщо напише, що час було успішно оновлено то у Вас порти відкриті. Якщо помилка то відкриваємо порти в роутері.
У мене вдома на роутері ASUS RT16 з прошивкою Tomato все працювало за замовчуванням відмінно.
На роботі на роутері Zuxel Keenetic Giga II з рідною прошивкою довелося відкривати порти, хоча в самому роутері час оновлювалося з інтернету.

Час оновлюється натисканням кнопки "9" ПДУ. Єдине, що треба враховувати при оновленні часу, якщо не буде доступу до інтернету то секунди все рівно обнуляться.
Після включення годин або натисканням кнопки Reset для поновлення часу через інтернет необхідно почекати секунд 30. Цей час необхідний модулю для підключення до мережі або для повернення помилки якщо немає підключення. Якщо раніше натискати кнопку "9" ПДУ просто нічого не буде відбуватися. Під час синхронізації часу матриці на 1-2 секунди будуть погашені.
Якщо не буде доступу до інтернету то виведеться рядок"ППСС Немає мережі".
Якщо буде доступ до мережі але не буде доступу до сервера точного часу то виведеться рядок"Немає доступу до сервера часу"

Також з'явитися нова мережа c ім'ям "Datchik" .Її назва також можете змінити на будь-який в Arduino і змінити пароль до неї. До цієї мережі буде підключатися другий модуль з вуличними датчиками.

PS. Від автоматичної синхронізації вирішив відмовитися т.к у DS3231 дуже маленький догляд за часом і синхронізація потрібна не частіше 1 разу на місяць і то догляд кілька секунд. Так що простіше зробити це в ручну.

Була написана програма тесту матриць. Фьюз як для основної прошивки.
Все контролюється візуально.

Світлодіоди статусу резервної батареї, включеного будильника і авто регулювання яскравості повинні горіти. Зумер повинен мовчати.

1. Перший тест включає всі світлодіоди матриць. Тест проганяє по всім кольорам. Візуально можна визначити які світлодіоди не світяться або їх колір відрізняється від інших значить десь не пропало. Тест починається з повністю погашених матриць.

2.Второй частина тесту запалює тільки один світлодіод і проганяє його по всіх рядках в кожному з трьох кольорів R G і B. Повинен запалюватися тільки один світлодіод. Якщо будуть горіти два світлодіода означає десь КЗ на виводах регістрів матриць.

Блок живлення повинен забезпечувати струм хоча б 2A в режимі тесту т.к немає регулювання яскравості і споживання досить велике (особливо коли білий колір). Якщо блок не зможе забезпечити такий струм то тест не запуститися тобто будуть спроби старту і тут же відбуватися скидання.

Модуль датчика зовнішньої температури.

Модуль датчика зовнішньої температури так само зроблений на ESP8266 і в якості датчика застосований AM2321. Так само в цьому модулі встановлено датчик кімнатної температури. Зроблено це для того якщо будуть збиратися кілька однакових годин і тоді дані вони можуть отримувати з одного модуля. У зовнішній модуль вбудована USB зарядка для акумулятора, а також контроль напруги АКБ. При зниженні напруги АКБ нижче 3.2 вольта загориться світлодіод.

Також доданий візуальний контроль помилок.

1. Ні датчика AM2321 горить зелений світлодіод

2. Ні датчика BME280 горить червоний світлодіод

3. Невдалий підключення до WI-FI мережі горить синій світлодіод



Прошивка робиться також як і для модуля в годиннику. Єдине деякі рядки потрібно змінити в програмі.

Встановлюємо час опитування датчика температури. За умовчанням встановлено 900 сек \u003d 15 хв
Міняємо значення в рядку:

ESP.deepSleep (900 * 1000000, WAKE_RFCAL); // час 900 секунд \u003d 15 хвилин
Міняємо на своє значення. Зберігаємо і прошиває.

У мене модуль живиться від акумулятора по 1000мА.

На тему саморобних годин і обіцяв продовження, так ось вибачте. Годинники на саморобних світлодіодних матрицях.

Ці світлодіоди були замовлені ще ДО того як я вирішив робити годинник на матрицях
Я замовив три різних кольори світлодіодів в одного продавця:, і
Робилося це для того щоб спробувати виготовити сегментні годинник, з розрахунку по три світлодіода на сегмент 7 * 4 * 3 \u003d 84 + 2 \u003d 86 штук
Ось приблизно за таким принципом:

Хотілося спробувати різний колір і обов'язково яскраві (а як інакше то?)
З цього партії по 100 штук мене цілком влаштовували, тим більше ціна цілком прийнятна, а так-же, замовив ще в цьому ж магазині - зробити різнокольоровими цифри, щоб можна було порівняти кольору на одному дисплеї - якою буде гарніше, ну і так - поприколюватися ... :)

Товар замовлений і оплачений був 20 серпня 2016, А прибув 15 вересня 2016, За нашими мірками досить оперативно. АЛЕ! Світлодіоди червоного кольору - не прийшли! По закінченню терміну захисту покупця був відкритий суперечка і продавець повернув гроші. Було просто шкода витраченого часу ...

Товар прийшов в стандартному жёлтопупирчатом поштовому конверті, самі світлодіоди були упаковані в окремий целофановий пакет, кількість штук в сині, зелені - було трохи більше 100 (точно зараз вже не пам'ятаю, але пам'ятаю що було на 4-5 штук більше)
Всі робочі (тобто світяться)
У тих, що різнокольорові - було упаковано в окремі пакетики, на яких було маркером позначено колір RGBWY - колір всюди відповідав, і так-же по 2-3 зайвих - складалося враження, що так напевно у всіх, але пізніше переконався, що не завжди так - буває і навпаки ...

Якби не косяк з червоним кольором, продавцеві можна було-б поставити жирну п'ятірку з плюсом за щедрість.

З іншого боку, гроші повернуті - проблема просто в втрачений час і ошуканих очікуваннях
А ще він надіслав лист, де він писав, що мовляв повертає мені СВОЇ гроші, і якщо товар прийде - просив повернути йому цю суму
дослівно:

Hi, my friend, I am now a refund, when you receive a package, please return my money, Please give me a five-star high praise.
На що йому довелося накатати лист, що це не ЙОГО гроші, а просто він повертає мені в результаті суперечки МОЇ гроші, тому що я його товару так і не отримав і при цьому втратив багато часу чекаючи його товар

До речі, коли прийшли всі інші замовлення від нього, через 10 днів, я написав йому, що мовляв - переживаю, тому що це дивно - всі прийшли, а один з замовлень не прийшов
На що він без всяких «хелоу френд» - сухо відповів «60 днів ще не пройшло»
А тут раптом і «другом» став, і ніби як ЙОГО гроші поимел ...

Але взагалі-то - продавець свої обов'язки виконує, на листи реагує, від відправленого товару - емоції тільки позитивні, гроші за невідправлений повернув - все згідно ПРАВИЛ

Ну а тепер про те заради чого це купувалося