КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Подарочная коробочка на 8 марта с таймером и замком

Общие сведения

Коробочка с секретом - отличная идея подарка на 8 марта, или любой другой праздник. Особенность устройства в том, что коробочка сама открывается только в заданное время. Можно подарить её заранее, но откроется она лишь в нужный момент, а установленные внутри светодиоды подсветят приоткрывшуюся крышку, и сделают момент открытия подарка еще более интересным. 

Видео

Нам понадобится

Модули

    Библиотеки

    Подключение

    Для удобства подключения мы использовали Trema Shield и I2C Hub. Также можно использовать любой имеющийся у вас контроллер, и поставить только один модуль NeoPixel. Всё это позволит сделать ваш проект более бюджетным.

    Установим Trema Shield на Piranha UNO.

    Подключим модули согласно схеме. 

    Силовой ключ

    Вывод модуляВывод Piranha
    IN S9
    IN VVcc
    IN GGND
    Vin ++ питания (батарейный отсек)
    Vin -- питания (батарейный отсек)
    Vout ++ Электромеханического замка
    Vout -- Электромеханического замка

    Датчик вибрации

    Вывод модуляВывод Piranha
    IN S10
    IN VVcc
    IN GGND

    NeoPixel

    Вывод модуляВывод Piranha
    IN S11
    IN VVcc
    IN GGND

    Второй модуль колодкой IN подключается к колодке OUT первого модуля. Дальнейшие модули  (при необходимости) подключаются аналогично. 

    I2C Hub

    Вывод модуляВывод Piranha
    IN GNDGND
    IN VccVcc
    IN SDASDA
    IN SCLSCL

    RTC

    Вывод модуляВывод I2C Hub
    GNDGND
    VccVcc
    SDASDA
    SCLSCL

    LCD дисплей

    Вывод модуляВывод I2C Hub
    IN GNDGND
    IN VccVcc
    IN SDASDA
    IN SCLSCL

    Скетч проекта

    Не забудьте заранее установить необходимые библиотеки

    Инструкция по загрузке:

    Найдите закомментированную строчку в настройках и раскомментируйте её. Загрузите скетч в контроллер (схема уже должна быть собрана). После этого закомментируйте и сова загрузите. Это позволит правильно установить время в 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 мм. 

    Ссылки




    Обсуждение

    Гарантии и возврат Используя сайт Вы соглашаетесь с условями