Общие сведения
Коробочка с секретом - отличная идея подарка на 8 марта, или любой другой праздник. Особенность устройства в том, что коробочка сама открывается только в заданное время. Можно подарить её заранее, но откроется она лишь в нужный момент, а установленные внутри светодиоды подсветят приоткрывшуюся крышку, и сделают момент открытия подарка еще более интересным.
Видео
Нам понадобится
Модули
- 1x Piranha UNO (можно использовать и другой контроллер)
- 1x Trema Shield (для удобства подключения модулей)
- 2х NeoPixel (Trema-модуль)
- 1х Часы реального времени, RTC
- 1х Датчик вибрации SW-420 (Trema-модуль)
- 1х Символьный дисплей LCD1602, I2C
- 1х Силовой ключ N-канал или P-канал
- 1х I2C Hub или I2C Hub (Trema модуль) для удобного подключения модулей к шине I2C
- 1х Замок электромеханический для шкафчиков, 5V (невидимка)
- 1х Батарейный отсек 6*АА + 6 батареек АА
- 1х Провод питания, DC (Папа)
- 2х Петли для крышки
- Также понадобится крепёж: стойки 20мм, винты, гайки, шайбы.
Библиотеки
- iarduino_RTC
- LiquidCrystal_I2C
- iarduino_NeoPixel При необходимости, ознакомьтесь с нашей инструкцией по установке библиотек в Arduino IDE.
Подключение
Для удобства подключения мы использовали Trema Shield и I2C Hub. Также можно использовать любой имеющийся у вас контроллер, и поставить только один модуль NeoPixel. Всё это позволит сделать ваш проект более бюджетным.
Установим Trema Shield на Piranha UNO.
Подключим модули согласно схеме.
Силовой ключ
Вывод модуля | Вывод Piranha |
IN S | 9 |
IN V | Vcc |
IN G | GND |
Vin + | + питания (батарейный отсек) |
Vin - | - питания (батарейный отсек) |
Vout + | + Электромеханического замка |
Vout - | - Электромеханического замка |
Датчик вибрации
Вывод модуля | Вывод Piranha |
IN S | 10 |
IN V | Vcc |
IN G | GND |
NeoPixel
Вывод модуля | Вывод Piranha |
IN S | 11 |
IN V | Vcc |
IN G | GND |
Второй модуль колодкой IN подключается к колодке OUT первого модуля. Дальнейшие модули (при необходимости) подключаются аналогично.
I2C Hub
Вывод модуля | Вывод Piranha |
IN GND | GND |
IN Vcc | Vcc |
IN SDA | SDA |
IN SCL | SCL |
RTC
Вывод модуля | Вывод I2C Hub |
GND | GND |
Vcc | Vcc |
SDA | SDA |
SCL | SCL |
LCD дисплей
Вывод модуля | Вывод I2C Hub |
IN GND | GND |
IN Vcc | Vcc |
IN SDA | SDA |
IN SCL | SCL |
Скетч проекта
Не забудьте заранее установить необходимые библиотеки.
Инструкция по загрузке:
Найдите закомментированную строчку в настройках и раскомментируйте её. Загрузите скетч в контроллер (схема уже должна быть собрана). После этого закомментируйте и сова загрузите. Это позволит правильно установить время в RTC модуль (подробнее см. ниже)
#include <Wire.h> // Подключаем библиотеку для работы с аппаратной шиной I2C. #include <iarduino_RTC.h> // Подключаем библиотеку для работы с модулем реального времени #include <LiquidCrystal_I2C.h> // Подключаем библиотеку для работы с LCD дисплеем по шине I2C #include <iarduino_NeoPixel.h> // Подключаем библиотеку для работы со светодиодами NeoPixel iarduino_NeoPixel led(11,8); // Объявляем объект LED указывая (№ вывода Arduino к которому подключён модуль NeoPixel, количество используемых светодиодов) LiquidCrystal_I2C lcd(0x27,16,2); // Объявляем объект библиотеки, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2) iarduino_RTC watch(RTC_DS3231); // Объявляем объект watch для модуля на базе чипа DS3231 // Определяем системное время: // Время загрузки скетча. const char* strM="JanFebMarAprMayJunJulAugSepOctNovDec"; // Определяем массив всех вариантов текстового представления текущего месяца. const char* sysT=__TIME__; // Получаем время компиляции скетча в формате "SS:MM:HH". const char* sysD=__DATE__; // Получаем дату компиляции скетча в формате "MMM:DD:YYYY", где МММ - текстовое представление текущего месяца, например: Jul. // Парсим полученные значения sysT и sysD в массив i: // Определяем массив «i» из 6 элементов типа int, содержащий следующие значения: секунды, минуты, часы, день, месяц и год компиляции скетча. const int i[6] {(sysT[6]-48)*10+(sysT[7]-48), (sysT[3]-48)*10+(sysT[4]-48), (sysT[0]-48)*10+(sysT[1]-48), (sysD[4]-48)*10+(sysD[5]-48), ((int)memmem(strM,36,sysD,3)+3-(int)&strM[0])/3, (sysD[9]-48)*10+(sysD[10]-48)}; ////////////////НАСТРОЙКИ////////////////////////////// // ВНИМАНИЕ! Строчку ниже необходимо раскомментировать, загрузить скетч в контроллер, после чего закомментировать и снова загрузить в контроллер. Иначе, при отключения питания отсчёт времени начнётся сначала. // watch.settime(i[0],i[1],i[2],i[3],i[4],i[5]); // Устанавливаем время в модуль: i[0] сек, i[1] мин, i[2] час, i[3] день, i[4] месяц, i[5] год, без указания дня недели. uint8_t timeZone = 3; // Часовой пояс (UTC + 3 - для московского времени) uint32_t unixFinalTime = 1614783600; // Время открытия замка в секундах UNIX - Mar 03 2021 18:00:00 UTC+0300 (Москва, стандартное время) https://time.is/ru/Unix_time_converter uint32_t timeOnLCD = 5000; // Время включения подсветки дисплея после тряски, мс #define growthTimeOn 3 // Во сколько раз увеличить время подсветки дисплея и светодиодов после открытия коробочки (если не увеличивать: 1) /////////////////////////////////////////////////////// uint8_t x1 = 0, x2 = 90, x3 = 180; // Определяем углы смещения для перелива цветов светодиодов long periodTime; // Время для определения скорости изменения яркости светодиодов uint8_t oldS; // переменная для отслеживания изменения секунд uint8_t symbol[7][8] = { // Объявляем массив из 7 собственных символов для LCD, каждый символ состоит из 8 байт {B01111,B00101,B00101,B01001,B10001,B11111,B10001,B00000,}, // Буква "Д" {B10001,B10001,B10001,B11001,B10101,B10101,B11001,B00000,}, // Буква "Ы" {B10001,B10011,B10011,B10101,B11001,B11001,B10001,B00000,}, // Буква "И" {B01111,B10001,B10001,B01111,B00101,B01001,B10001,B00000,}, // Буква "Я" {B10001,B10001,B10001,B01111,B00001,B00001,B00001,B00000,}, // Буква "Ч" {B00110,B01111,B11111,B11111,B01111,B00111,B00011,B00001,}, // Сердце - лево {B01100,B11110,B11111,B11111,B11110,B11100,B11000,B10000,} // Сердце - право }; bool vibration; // Значения с датчика вибрации bool oldVibration; // Старые значения с датчика вибрации uint32_t millisOnLCD; // Время срабатывания датчика вибрации bool openFlag; // Флаг того, что произошло открытие коробочки void setup(){ pinMode(9, OUTPUT); // 9 пин - замок pinMode(10, INPUT); // 10 пин - датчик вибрации lcd.init(); // Инициируем работу с LCD дисплеем lcd.backlight(); // Включаем подсветку LCD дисплея led.begin(); // Инициируем работу с модулем NeoPixel Serial.begin(9600); // Инициируем передачу данных в монитор последовательного порта watch.begin(); // Инициируем RTC модуль led.setColor(NeoPixelAll, 0, 0, 0); // Выключаем светодиоды (если вдруг оказались включены_ led.write(); lcd.createChar(1, symbol[0]); // Загружаем символы в ОЗУ дисплея lcd.createChar(2, symbol[1]); lcd.createChar(3, symbol[2]); lcd.createChar(4, symbol[3]); lcd.createChar(5, symbol[4]); lcd.createChar(6, symbol[5]); lcd.createChar(7, symbol[6]); lcd.createChar(8, symbol[7]); lcd.setCursor(0, 0); // Устанавливаем курсор в позицию (0 столбец, 0 строка) lcd.print("\1O OTKP\2T\3\4:"); // Выводим текст "До открытия:" } void loop(){ watch.gettime(); // Обновление времени с RTC-модуля uint32_t unixResTime = unixFinalTime + timeZone*60*60 - watch.Unix; // Пересчёт UNIX-времени (оставшегося) с учетом часовой зоны uint32_t unixS = unixResTime/60/60/24; // Определение оставшихся дней (D), часов (H), минут (M) и секунд (S) uint8_t D = trunc (unixS); unixS = unixResTime - (uint32_t)D*24*60*60; uint8_t H = trunc(unixS/60/60); unixS = unixS - (uint32_t)H*60*60; uint8_t M = trunc (unixS/60); uint8_t S = unixS-(M*60); if (watch.Unix - timeZone*60*60 > unixFinalTime){// если было открытие (условие для ситуации отключения питания после открытия) openFlag = true; } else{ if (S != oldS){ // Если прошла секунда с последнего обновления дисплея oldS = S; Serial.print("Текущее время: "); Serial.println(watch.gettime("d-m-Y, H:i:s")); // Вывод информации в монитор порта Serial.print("Текущее UNIX время: "); Serial.println(watch.Unix); Serial.print("Осталось Дней: "); Serial.print(D); Serial.print(", Часов: "); Serial.print(H); Serial.print(", Минут: "); Serial.print(M); Serial.print(", Секунд: "); Serial.println(S); Serial.println("----------------"); lcd.setCursor(2, 1); // Устанавливаем курсор lcd.print("\1H \5 M C"); // Выводим текст для дней, часов, минут, секунд lcd.setCursor(0, 1); // Устанавливаем курсор if (D<10) { // Корректируем отображение (добавляем 0) если параметр меньше 10 lcd.print("0"); lcd.setCursor(1, 1); lcd.print(D); // выводим параметр } else lcd.print(D); lcd.setCursor(5, 1); if (H<10) { lcd.print("0"); lcd.setCursor(6, 1); lcd.print(H); } else lcd.print(H); lcd.setCursor(9, 1); if (M<10) { lcd.print("0"); lcd.setCursor(10, 1); lcd.print(M); } else lcd.print(M); lcd.setCursor(13, 1); if (S<10) { lcd.print("0"); lcd.setCursor(14, 1); lcd.print(S); } else lcd.print(S); } if ( D<= 0 && H <= 0 && M <= 0 && S <= 0){ // Если время вышло openFlag = true; // Флаг открытия коробочки } } vibro(); // Функция включения подсветки дисплея при обнаружении вибрации if (openFlag){ digitalWrite(9, HIGH); // Открываем замок lcd.clear(); // Очищаем дисплей lcd.setCursor(6,0); // Устанавливаем курсор lcd.print("YPA!"); // Пишем "УРА!" lcd.setCursor(0,1); // Устанавливаем курсор lcd.print("\6\7"); // Рисуем сердце lcd.setCursor(4,1); // Устанавливаем курсор lcd.print("8 MAPTA"); // Пишем "8 марта" lcd.setCursor(14,1); // Устанавливаем курсор lcd.print("\6\7"); // Рисуем сердце delay(10); // Задержка для уверенного срабатывания замка digitalWrite(9, LOW); // Выключаем электромагнит замка lcd.backlight(); // Включаем подсветку LCD дисплея oldVibration = !oldVibration; timeOnLCD *= growthTimeOn; // Увеличиваем время горения светодиодов и подсветки дисплея после открытия while(true){ // Бесконечный цикл vibro(); // Проверяем наличие вибрации if (millisOnLCD + timeOnLCD >= millis()){ // Если не вышло время работы подсветки ledOn(); // Перелив светодиодами } } } } void vibro(){ // Функция обнаружения вибрации vibration = digitalRead(10); // Читаем показания с датчика вибрации (0 или 1) if (vibration != oldVibration){ // Если было изменение oldVibration = vibration; // Записываем в предыдущее значение lcd.backlight(); // Включаем подсветку LCD дисплея millisOnLCD = millis(); // Обновляем время включения подсветки } if (millisOnLCD + timeOnLCD < millis()){ // Если пришло время выключить подсветку lcd.noBacklight(); // Выключаем подсветку LCD дисплея led.setColor(NeoPixelAll, 0, 0, 0); // Выключаем светодиоды (если вдруг оказались включены) led.write(); } } void ledOn(){ // Функция перелива цветов светодиодов if (periodTime + 20 <= millis()){ // 20 - задержка, мс, определяющая скорость перелива цветов periodTime = millis(); x1++; x2++; x3++; // увеличиваем значения x1 >= 255 ? x1 = 0 : x1; // сбрасываем при достижении максимального значения x2 >= 255 ? x2 = 0 : x2; x3 >= 255 ? x3 = 0 : x3; int res1 = (sin((2*PI*x1)/128)+1)*127; // Вычисляем значение (0...255) по синусу угла int res2 = (sin((2*PI*x2)/128)+1)*127; int res3 = (sin((2*PI*x3)/128)+1)*127; for(uint8_t i = 0; i < led.count()/2; i++){ // Зажигаем светодиоды led.setColor(i, res1, res2, res3); } for(uint8_t i = led.count()/2; i < led.count(); i++){ led.setColor(i, res2, res3, res1); } led.write(); } }
Описание работы
Особенности загрузки
Перед загрузкой программы в контроллер, необходимо раскомментировать строку с установкой времени в RTC-модуль watch.settime(i[0],i[1],i[2],i[3],i[4],i[5]);
После загрузки необходимо закомментировать эту строку и снова загрузить прошивку. Это необходимо для корректной установки времени в RTC-модуль. В противном случае, отсчёт времени начнётся заново при возобновлении питания после его отключения.
Настройки
Обратите внимание на настройки в скетче. Текущее время в RTC-модуль установится автоматически (см. предыдущий пункт). Время открытия замка задаётся в секундах Unix (это количество секунд прошедших с начала эпохи Unix). Перевести дату и время в Unix число можно на любом из подобных сервисов.
В скетче указано число 1614783600
, что соответствует 08.03.2021 18:00:00 (московское время).
Работа устройства
После установки необходимых настроек и загрузки скетча, начинается обратный отсчёт, который будет длиться до момента открытия замка. На дисплее появится надпись "До открытия: <оставшееся время>".
Подсветка на дисплее будет включаться на указанное в настройках время после срабатывания датчика вибрации. Это позволит уменьшить энергопотребление.
При достижении указанного времени произойдёт открытие замка (при этом крышка коробки приоткроется) и включится перелив светодиодов NeoPixel. Также включится подсветка на дисплее, а надпись на нём измениться на "Ура, 8 марта".
Подсветка дисплея и светодиоды будут включаться при каждом срабатывании датчика вибрации, так же как и при обратном отчёте. При этом в настройках есть константа, позволяющая увеличить это время.
Сборка корпуса
Вы можете использовать любой корпус для вашего устройства, например, обычную подарочную коробку. Мы изготовили коробку из фанеры, можете поступить так же. Ниже вы найдёте наши чертежи.
Внимание! Мы настоятельно рекомендуем предусмотреть систему "аварийного" открывания вашего подарка.
Вся электроника установлена внутри устройства, так же как и замок. В случае, если батарейки или аккумуляторы разрядятся, вы не сможете открыть коробку. Для того, чтобы этого избежать, можно параллельно питанию замка припаять разъём (например такой), и вывести его на на корпус устройства. При необходимости вы сможете подать питание прямо на замок и открыть коробочку.
Также можно изменить конструкцию устройства, например сделать коробочку без дна, так появится доступ к электронике. Просто помните про эту особенность и учитывайте возможность отказа устройства.
Чертежи для фанеры 8 мм.
Обсуждение