Главная chevron_right Наши уроки chevron_right Arduino chevron_right Подарочная коробочка на 8 марта с таймером и замком

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

Собираем подарочную коробочку на Arduino: она сама откроется в заданное время, подсветит крышку светодиодами NeoPixel и покажет обратный отсчёт на LCD-дисплее

Видео-обложка запускается при наведении

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

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

Видео

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

Модули

Библиотеки

При необходимости, ознакомьтесь с нашей инструкцией по установке библиотек в Arduino IDE.

Подключение

Для удобства подключения мы использовали 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 мм.

Ссылки

Комплект для повторения проекта

Что понадобится

Это быстрый ориентир по основным модулям и деталям. Полный список для повторения проекта смотрите в самой статье.

15 позиций
Piranha Uno R3 Прямой аналог Arduino Uno c микроконтроллером ATmega328P на ядре AVR для разработки электронных устройств на языке C++ Открыть в iArduino Trema Shield Плата расширения для удобного подключения периферийных устройств Открыть в iArduino NeoPixel (Trema-модуль) Модуль с четырьмя адресными RGB светодиодами, их «адресность» позволяет управлять цветом каждого светодиода Открыть в iArduino Часы реального времени RTC DS3231 (Trema-модуль) Электронный энергонезависимый хронометр на чипе DS3231 Открыть в iArduino Датчик вибрации SW-420 (Trema-модуль) Использовать в качестве сигнализаций или для отключения устройств, например паяльника или утюга, которые находятся в состоянии покоя дольше *n* минут. Открыть в iArduino Символьный дисплей LCD1602 I²C (Синяя подсветка) Текстовый экран с управлением по I²C на 2 строки по 16 символов с белым текстом на синей подсветке Открыть в iArduino Силовой ключ N-канал (Trema-модуль) Позволит управлять нагрузкой ШИМ сигналом. Например яркостью светодиодной ленты или скоростью мотора Открыть в iArduino Силовой ключ P-канал (Trema-модуль) Позволит управлять нагрузкой ШИМ сигналом. Например яркостью светодиодной ленты или скоростью мотора Открыть в iArduino i2C Hub Будет полезен в проектах с большим количеством устройств с интерфейсом I2C Открыть в iArduino i2C Hub (Trema-модуль) Будет полезен в проектах с большим количеством устройств с интерфейсом I2C Открыть в iArduino Замок электромеханический невидимка (5 В) Замок электромеханический для шкафчиков, 5V (невидимка) Открыть в iArduino Батарейный отсек 6×АA Держатель для шести «пальчиковых» элементов питания форм-фактора AA Открыть в iArduino Провод питания, DC (Папа) Открыть в iArduino Стойка нейлоновая чёрная «папа-мама» М3 / 20 мм (4 шт.) Комплект из четырёх нейлоновых чёрных стоек длиной 20 мм с наружной и внутренней резьбой М3 Открыть в iArduino Провод питания, DC (Мама) например такой Открыть в iArduino
Все уроки Arduino