Наливатор с тостами своими руками пошаговая инструкция

ОБНОВЛЕНИЯ


• 08.03.20 Версия 1.5: Добавлена инверсия сервопривода (ОБНОВИТЕ БИБЛИОТЕКУ ИЗ АРХИВА)

Версия проекта с шаговым мотором и OLED дисплеем от VICLER ссылка на репозиторий

Предыдущие

• 25.10.19 Версия 1.0: первоначальная, вроде бы стабильная версия
• 30.11.19 Версия 1.1:
– Поправлена работа системы при выборе некорректного объёма
– Исправлены ошибки при наливании больших объёмов
– Исправлен баг с остановкой наливания при убирании другой рюмки
• 15.12.19: обновлена библиотека ServoSmooth, перекачайте архив. Улучшена работа на низких скоростях и ускорениях.
• 05.01.20 Версия 1.2:
– Исправлено ограничение выбора объёма
– Исправлены ошибки (обновите библиотеки из архива! servoSmooth v1.8, microLED v2.3)
– Добавлено хранение в памяти выбранного объёма
• 18.01.20 Версия 1.3: Исправлен баг со снятием рюмки в авто режиме (жука поймал Юрий Соколов)
• 08.03.20 Версия 1.4: Добавлена настройка уровня концевиков (для ИК датчиков) и исправлена ошибка с наливанием больших объёмов

ОПИСАНИЕ


Автоматический разливатор-дозатор напитков на Arduino:

  • Сделан из чемоданчика – набора отвёрток из фикс-прайса
  • Остальные компоненты тоже из фикс-прайса
  • Электроника с Алиэкспресс
  • Система рассчитана на 1-6 рюмок
  • Подсветка рюмок:
    • Красный – пустая
    • Жёлтый – в процессе заполнения
    • Зелёный – готово к употреблению
  • Надёжный механический датчик наличия рюмки
  • Складная конструкция
  • Пищевая мембранная помпа
  • Энкодер, дисплей
  • Система “пинания” powerbank’a, не дающая ему уйти в сон
  • Продуманная система энергосбережения: дисплей снижает яркость при простое, серво отключается от питания
  • Используется библиотека для плавного движения сервопривода

Другой проект наливайки от наших ребят на Бумстартер – поддержать

ВИДЕО


КОМПОНЕНТЫ


Стараюсь оставлять ссылки только на проверенные крупные магазины, из которых заказываю сам. Также по первые ссылки ведут по возможности на минимальное количество магазинов, чтобы минимально платить за доставку. Если какие-то ссылки не работают, можно поискать аналогичную железку в каталоге Ардуино модулей. Также проект можно попробовать собрать из компонентов моего набора GyverKIT.

  • Arduino Nano купить в РФ, aliexpress, aliexpress, искать

  • Адресная лента

    • Купить в РФ, 60 свет/метр, 30 свет/метр
    • Купить на Али ссылка, ссылка
    • Black PCB / White PCB – цвет подложки ленты, чёрная / белая. В видео была чёрная
    • 1m/5m – длина ленты в метрах (чтобы заказать 2 метра, берите два заказа 1m, очевидно)
    • 30/60/74/96/100/144 – количество светодиодов на 1 метр ленты. В видео использовалась лента 60 диодов на метр
    • IP30 лента без влагозащиты (как на видео)
    • IP65 лента покрыта силиконом
    • IP67 лента полностью в силиконовом коробе
    • Постфикс ECO – лента чуть более низкого качества, меньше меди, на длинной ленте будет сильно проседать яркость
  • Адресные модули поштучно – искать
    • https://ali.ski/GO8H9y
    • https://ali.ski/9kP-m
    • https://ali.ski/mTeIs
  • Энкодер aliexpress, aliexpress, aliexpress, искать

  • Кнопка – искать
    • https://ali.ski/pLQ30
    • https://ali.ski/Fg4Me-
  • Дисплей TM1637 aliexpress, aliexpress, искать

  • Концевик – искать
    • https://ali.ski/bGZrqX
    • https://ali.ski/GML-x
  • Драйвер MX1508 aliexpress, aliexpress, искать

  • Сервопривод aliexpress, aliexpress, искать

  • Модуль USB – искать
    • https://ali.ski/sBGAh_
    • https://ali.ski/7V34u
    • https://ali.ski/_mUDy
  • Помпа
    • https://ali.ski/ljbp6U
    • https://ali.ski/hRl74
  • Чемодан!
    • https://ali.ski/NjTAxk
    • https://ali.ski/HSikH

СХЕМЫ


ПРОШИВКА


ВНИМАНИЕ!
Максимально подробный гайд по началу работы с платой и загрузке прошивки для проекта находится ЗДЕСЬ. Изучи его внимательно, прежде чем писать на форум или в группу ВК!

УПРАВЛЕНИЕ


КАЛИБРОВКА (РЕЖИМ СЕРВИСА)

  • Подать питание с зажатой большой кнопкой
  • Дождаться надписи SERVICE
  • Энкодер управляет положением крана, на дисплей выводится угол
  • Кнопка энкодера запускает помпу и таймер
  • Удержание большой кнопки – выход из сервиса в обычный режим работы

РУЧНОЙ РЕЖИМ

  • Буква Р в левом краю дисплея
  • Выставляем стаканчики и кликаем по кнопке
  • Во время цикла заполнения можно доставить стакан, он будет заполнен

АВТОМАТИЧЕСКИЙ РЕЖИМ

  • Смена режимов – удержание большой кнопки
  • Буква А в левом краю дисплея
  • Каждый поставленный стаканчик будет заполнен!

ОБЩЕЕ

  • Если поднять стакан до заполнения, помпа отключится и система перейдёт к следующему стакану
  • Если наблюдаются глюки (неправильное положение крана при заливке, промахи) – проблема в питании! Попробуйте добавить конденсаторы как на схеме, попробуйте другой powerbank, а ещё лучше проверить работу системы на нормальном зарядном блоке питания от смартфона. Система многократно протестирована, работа отлажена, неадекватное поведение замечено при плохом питании.

ПОДДЕРЖАНИЕ ПИТАНИЯ

  • Практически все powerbank’и отключают линию питания при отсутствии нагрузки, специально для этого в системе предусмотрено периодическое подёргивание сервопривода с целью создания скачков нагрузки, которые вынуждают powerbank не уходить в сон и не отключать наливатор от питания. В этом режиме система будет каждые 15 секунд дёргать приводом и мигать дисплеем, если вам это не нужно – отключите настройку KEEP_POWER, присвоив ей 0 вместо 1

ПОДДЕРЖАТЬ


Вы можете поддержать меня за создание доступных проектов с открытым исходным кодом, полный список реквизитов есть вот здесь.

Автоматический дозатор напитков или, как его называют, «наливатор» – это устройство, которое разливает напитки по стаканчикам в заданном объёме. Такие электронные бармены быстро обрели популярность. Это неудивительно, ведь наливатор может быть как интересной самоделкой на вашем праздничном столе, так и оригинальным подарком. Их изготавливают и для домашнего использования, и на продажу.

В нашем магазине вы можете приобрести комплект компонентов для сборки наливатора по схеме AlexGyver. Мы решили сами изготовить такое устройство и поделиться с вами нашим опытом.

Выбор материала для корпуса

Здесь, конечно, всё зависит от ваших навыков по работе с тем или иным материалом. В ход идут листовой пластик, оргстекло, фанера. Наиболее доступным и простым в обработке материалом, пожалуй, является фанера. От полимеров её выгодно отличает неповторимый рисунок, который можно подчеркнуть при помощи морилок и лаков. Именно её мы и выбрали для изготовления наливатора.

Какая фанера лучше подойдёт для наших целей? Во-первых, покупая фанеру, обратите внимание на её сорт. Низшим является 4 сорт, он допускает любые производственные дефекты. Тогда как фанера 1 сорта практически не имеет изъянов. Лист фанеры может иметь стороны разной сортности, например, маркировка 2/4 означает, что одна из сторон второго сорта, другая – четвёртого. Нам не принципиален сорт для внутренних сторон наливатора, а вот для внешних предпочтителен сорт повыше.

Также следует заранее определиться с толщиной фанеры. На что она влияет? В нашем случае толщина фанеры 8мм, это позволяет склеивать её торцом без использования шиповых соединений. Но такая толщина фанеры становится минусом, когда требуется установить в ней кнопку или энкодер: длина их резьбы значительно меньше 8мм. Впрочем, это решается путём высверливания в фанере небольшого углубления для устанавливаемого компонента (смотрите на коллаже ниже). При использовании фанеры толщиной 3мм вы не столкнётесь с такой проблемой. Но её уже не получится склеивать торцом. Но, опять же, это не проблема: куски фанеры можно склеить при помощи деревянной рейки, как показано в следующем коллаже справа.

Собираем наливатор

Корпус

Мы решили не усложнять конструкцию наливатора оригинальной формой, и выпилили детали для сборки простого квадратного корпуса:

Детали корпуса для наливатора

«Башню» для излива разместим в верхней части посередине. Также её можно разместить в углу – это дело вкуса. Циркулем проводим радиус и намечаем на нём позиции для отверстий. В них будут ставиться стопки. Для сверления используем набор коронок по дереву.

Верхняя часть наливатора. Отверстия для стопок

Склеиваем башню столярным клеем. В одной из её стенок высверлены углубления для провода, выходящего из сервопривода. Уже можно прикинуть будущий вид наливатора:

Наливатор. Внешний вид будущего корпуса

Излив

Здесь нужно продумать, как соединить качалку сервопривода с трубкой излива, в качестве которого мы используем многоразовую коктейльную трубочку. Набор таких трубочек можно купить в Фикспрайсе. Она сделана из нержавеющей стали и отлично подходит для наших целей. Соединить её с качалкой можно при помощи деревянного брусочка: достаточно просверлить в нём отверстие для трубки и приклеить качалку.

Но мы решили немного заморочиться над внешним видом данной детали. В тонкой дощечке сверлим отверстие диаметром 7мм, вставляем в него качалку и обводим её контур карандашом. По этому контуру нужно вырезать углубление для качалки. 5 минут работы скальпелем, и качалка отлично входит в подготовленную для неё полость. Склеиваем всё вместе и придаём нужную форму напильником. В итоге у нас получилась симпатичная деталь:

Крепление трубки наливатора к сервомотору

Второе дно

Второе дно размещается внутри корпуса под отверстиями, которые мы просверлили для стопок. В нём устанавливаются концевые выключатели и светодиоды. Для изготовления второго дна мы взяли тонкую фанерку, наметили отверстия, затем просверлили и выпилили их:

Финишная обработка

Детали корпуса будущего наливатора готовы. Остаётся отшлифовать их и покрыть лаком. Покрытие лаком защитит древесину от влаги. Если вы планируете выполнять покрытие в жилом помещении, то выбирайте лак на водной основе. Такой лак относится к числу экологически безопасных покрытий, быстро сохнет и практически не имеет запаха. В продаже имеются как бесцветные лаки, так и с различными оттенками.

Установка и пайка компонентов

Пора приступить к сборке. Энкодер и кнопку прикручиваем гайками. Дисплей и концевые выключатели очень плотно вошли в подготовленные для них отверстия, нет необходимости фиксировать их клеем. Для светодиодов были высверлены небольшие углубления, в которых они фиксируются парой капель столярного клея. С лицевой стороны заливаем светодиоды термоклеем. Ардуино Нано установлена в специальную стойку, распечатанную на 3D принтере. Для помпы был изготовлен импровизированный деревянный хомут:

Наливатор. Крепление помпы

Одна часть хомута приклеена внутри корпуса, другая прикручивается к ней саморезами и зажимает помпу.

Соединяем компоненты в соответствии со схемой:

Схема наливатора

Проводов получается довольно много. Чтобы они выглядели более-менее прилично, собираем их в пучки при помощи термоусадки. В итоге внутренности нашего наливатора имеют следующий вид:

Наливатор внутри

Остаётся подсоединить обратный клапан и трубки, и можно переходить к программной части.

Прошивка и калибровка

Наливатор готов к загрузке скетча. Скачать его можно по ссылке https://github.com/AlexGyver/GyverDrink/

В архиве со скетчем вы найдёте используемые библиотеки. Их следует установить в среду разработки Ардуино. Для этого достаточно скопировать содержимое папки libraries в папку для библиотек Ардуино. Что касается самой среды разработки, автор скетча рекомендует использовать версию 1.8.13, скачать её можно с официального сайта https://www.arduino.cc/en/software. На более старых версиях возможно появление ошибок при компиляции.

После загрузки скетча проверяем работу всех компонентов. Если при вращении ручки энкодера влево значение на дисплее увеличивается, поменяйте в скетче местами номера выводов для CLK и DT (строки 60 и 61). Должно получиться:

#define ENC_DT 9
#define ENC_CLK 10

Или можно перепаять сами провода, не изменяя скетч.

Убедившись в корректной работе всех компонентов, приступаем к калибровке наливатора. Для этого зажимаем кнопку розлива и включаем питание. На дисплее отобразится надпись SErViCE, сообщающая нам о том, что мы находимся в сервисном режиме. Сервомотор займёт своё начальное положение. Устанавливаем трубку излива. Вращая ручку энкодера, подбираем положения, в которых излив будет находиться точно над стопками. При этом угол поворота сервомотора отображается на дисплее. Запоминаем углы для каждого найденного положения и вписываем их в скетче в 43 строке:

const byte shotPos[] = {40, 75, 115, 155, 60, 60};

Теперь нужно замерить время, за которое наливается 50мл. Опускаем трубку в ёмкость с водой, под излив ставим стопку и нажимаем кнопку энкодера. Удерживаем её, пока не потечёт вода. Отпускаем кнопку и выливаем воду обратно в ёмкость. Возвращаем стопку и наливаем воду ещё раз, пока не наберётся ровно 50мл. На дисплее отобразится затраченное время. Вписываем его в скетч в 46 строке:

const long time50ml = 6800;

При необходимости можно изменить и другие параметры в скетче, например, изменить количество стопок или инвертировать направление вращения сервомотора.

После внесения изменений в скетч загружаем его в Ардуино. Наш наливатор готов!

Самодельный наливатор напитков

Подключаем питание. На дисплее отображается «Р 50». Буква «Р» говорит о том, что наливатор находится в ручном режиме, и розлив осуществляется при нажатии красной кнопки. Число 50 – это наливаемый объём в миллилитрах, его можно изменять поворотом энкодера.

При удержании кнопки розлива устройство переключается в автоматический режим. При этом буква «Р» на дисплее сменяется буквой «А». В этом режиме наливатор сразу же наполняет поставленные на него стопки. Для возврата в ручной в режим необходимо нажать и удерживать кнопку розлива ещё раз.

Ну и в заключение небольшое видео получившегося устройства:

Наливатор был изготовлен по технологии Алекса Гайвера. На данный момент фан клуб наливатора разросся до неприличных размеров, а сам наливатор приобрел множество модификаций и изменился как внешне, так и по функциональности и комплектующим соответственно. В этой статье рассмотрим процесс изготовления стандартного наливатора без всяких апгрейдов.

Полный перечень материалов + прошивка

Наливатор алкоголя

Итак начнем пожалуй с поиска или изготовления подходящего ящичка. Тот что вы видите был куплен на Алике, ссылочку найдете если откроете «Полный перечень материалов«. Размеры ящичка (23x16x7,5 см) идеально подойдут для наливатора под 4 рюмки, так же есть петли которые отлично удерживают крышку под углом в 90 градусов. Для его дальнейшего использования необходимо немного доработать, а именно приклеить на внутренние стенки небольшие полоски тонкой фанеры и проделать небольшое отверстие сзади корпуса для вывода разъема Micro USB.

Ящик для наливатора

Ящик для наливатора

Вырезаем фанерку по внутреннему размеру ящичка. Устанавливаем ее на место и отмеряем центр. Проводим циркулем полукруг отступив 1-1,5 см от самого края, по этой кривой будет двигаться гусак (трубочка). После чего делаем из бумаги шаблон в виде кружка с круглым и прямоугольным отверстиями под светодиод и концевик. Размечаем места под рюмки (светодиод + концевик), кнопку, энкодер, дисплей и два отверстия под трубочки.

Размечаем места под рюмки в наливаторе

Размечаем места под рюмки в наливаторе

Переворачиваем ящик вверх ногами, подпираем его чем нибудь в открытом положении и приклеиваем сервопривод при помощи двухкомпонентного клея (тоже китайского если что =).

Приклеиваем сервопривод

Приклеиваем сервопривод

А пока сервопривод клеиться можно высверлить и выпилить отверстия в фанерке, а так же поправить их наждачной бумагой. На обратной стороне, в местах где будут крепиться светодиоды, я расширил отверстия сверлом большего диметра, для того что бы светодиоды плотно прилегали к фанерке (там есть небольшой кондер который выступает и не дает плотно прижать светодиод).

Получилась такая вот фанерка которую обклеиваем виниловой пленкой и вырезаем ниши при помощи скальпеля.

Далее нарезаем светодиоды, пропаиваем и приклеиваем как на картинках.

А с обратной стороны заливаем ниши термоклеем, и удаляем излишки при помощи пластиковой карты или небольшого шпателя.

Устанавливаем концевики и фиксируем их термоклеем на обратной стороне.

Устанавливаем концевики

Устанавливаем концевики

Приклеиваем дисплей, плату ардуино и драйвер на двухстороннюю изоленту. Паяем все по схеме.

Схема подключения

Нажмите что бы увеличить

Готовая разводка

Из трубочки и пластиковых уголков изготавливаем простой гусак и закрепляем на нем качельки которые идут в комплекте с сервоприводом. Вообще тут раздолье для фантазии, кто то использует трубочки от надувных шариков, кто то выгибает гусак из нержавейки или латуни. Только не переборщите и не делайте трубочку слишком тяжелую иначе есть вероятность «кривой» работы сервопривода (будет дергаться и нужно будет придумывать противовес (как один из вариантов исправить)).

Изготавливаем гусак для наливатора

Изготавливаем гусак для наливатора

Загружаем прошивку на плату ардуино ничего не меняя. (Видео инструкция как загрузить прошивку в ардуино)

Загружаем прошивку для наливатора

Загружаем прошивку для наливатора

Далее подключаем питание и ждем пока сервопривод повернется в крайнее положение. Одеваем гусак. Выключаем питание. Зажимаем кнопку и вновь подаем питание, включается сервисный режим. В сервисном режиме можно проверить светодиоды понажимав на соответствующие концевики, покрутить гусак при помощи энкодера и запустить насос если нажать энкодер. В этом режиме необходимо записать углы положения гусака над центрами рюмок. А так же записать время которое понадобиться насосу для перекачивания 50 мл жидкости.

Запускаем сервисный режим

Запускаем сервисный режим

Проверяем концевики и светодиоды

Проверяем концевики и светодиоды

Выставляем гусак над центром рюмки (нужно записать углы для всех 4-х рюмок)

Выставляем гусак над центром рюмки (нужно записать углы для всех 4-х рюмок)

Записываем угол

Записываем угол

Определяем время перекачивания 50-ти мл жидкости

Определяем время перекачивания 50-ти мл жидкости

Записываем время перекачки 50-ти мл

Записываем время перекачки 50-ти мл

Вносим все изменения в прошивку (смотри видео если не понятно)

Вносим все изменения в прошивку (смотри видео если не понятно)

Готово, можно тестировать =)

Как пользоваться

  • Включаем (подаем питание);
  • Опускаем силиконовый шланг в емкость и прокачиваем жидкость нажав на энкодер;
  • Устанавливаем необходимый режим (автоматический или ручной) нажав и удерживая кнопку. При ручном режиме наливатор начинает работать тогда, когда вы поставите стаканчики и нажмете кнопку, в автоматическом режиме просто ставим стаканчик и наливатор тут же его наполняет.
  • После застолья вытаскиваем силиконовый шланг из емкости и нажав на энкодер прокачиваем остатки жидкости.

Если в процессе сборки вы столкнулись с какими либо трудностями, багами или ошибками рекомендую прочитать статью «Проблемы при сборке наливатора и способы их устранения» или же можете посмотреть видео версию.

Пошаговая видео инструкция по сборке + тестирование

Пт, 09/08/2019 — 11:32

#101

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

atom23rus пишет:

Парни, а подскажите как откалибровать насос?

Секундомер есть в каждом сматрфоне, подоединяешь насос, через кнопку подаешь напряжение строго от внешнего источника  которым будеш его питать, делаешь 10-20 замеров, высчитываешь среднее арифметическое. Насос 385 при питании 5В потребляет 150мА, 50 мЛ наливает в среднем за 5,5 сек.(5550 мсек)

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 11:45

#102

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

stpavel пишет:

У меня к сожалению нет mp3 модуля, что бы проверить. Нужно что бы наливатор говорил тосты после каждого налива ? Тогда засовывать надо в процедуру Tost()

Что бы писал «Ну начали» перед наливом , можно засунуть в процедуру oled_naliv.

«Ну начали» надо програть всего один раз, после включения и подсоединения ёмкости, можно даже на дисплей не выводить.

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:31

#103

atom23rus

Offline

Зарегистрирован: 06.08.2019

в этом то и прикол.в магазине был только 360 и на 12 вольт.буду питать от 8в.я о том где в скетче прописывать калибровку.

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:49

#104

stpavel

Offline

Зарегистрирован: 09.10.2018

Forthomo пишет:

stpavel пишет:

У меня к сожалению нет mp3 модуля, что бы проверить. Нужно что бы наливатор говорил тосты после каждого налива ? Тогда засовывать надо в процедуру Tost()

Что бы писал «Ну начали» перед наливом , можно засунуть в процедуру oled_naliv.

«Ну начали» надо програть всего один раз, после включения и подсоединения ёмкости, можно даже на дисплей не выводить.

Ну если один раз при включении, можно засунуть в setup

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:53

#105

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

stpavel пишет:

Ну если один раз при включении, можно засунуть в setup

#include <OLED_I2C.h>
#include <Servo.h>
#include "Adafruit_NeoPixel.h"
#include <SoftwareSerial.h>//добавляем библиотеки
#include <DFPlayer_Mini_Mp3.h>//добавляем библиотеку МП3 плейера

// при необходимости создаем програмный порт для управдения МП3 плейером, если вывод в монитор TX(D0) RX(D1) необходим
//SoftwareSerial mySoftwareSerial(10, 11); // RX, TX  обозначаем програмный порт как mySoftwareSerial
//плейер подключаем D10 D11

OLED  myOLED(SDA, SCL, 8); //Подключение экрана А4, А5
extern uint8_t MegaNumbers[];
extern uint8_t RusFont[];
extern uint8_t SmallFont[];
unsigned long currentTime;
unsigned long loopTime;
unsigned long ledTime;
// Переменные для энкодера -----------
const int pin_A = 2;       // Подключение вывода A (CLK) энкодера
const int pin_B = 3;       // Подключение вывода B (DT) энкодера
const int pin_SW = 4;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev = 0;
unsigned char encoder_sw_prew = 1;
//Массив , обозначаем подключенные оптопары по выводам . Оптопары подключены, A0,A1,A2,A3,A6
const byte  Optics[] = {0, 1, 2, 3, 6};
//Серво
const int PIN_SERVO = 9;
Servo servo;
//Позиция каждой рюмки 
const byte Rumka_pos[] = {0,40,75,105,140};
//-------------------------
byte  Menu = 0;
byte MenuFlag = 0; // Здесь храниться уровень меню. 0 находимся в  Главном меню. 1 Вошли в меню Авто, 2 вошли в  Ручное управление
byte  Drink = 25; // По умолчанию в рюмку наливаем  20 мл.
const byte  max_Drink = 50; // Максимум в рюмку - 50 мл.
byte  DrinkCount = 1; //По умолчанию, для ручного режима - 1 рюмка
const byte  max_DrinkCount = 5; //Максимальное кол-во рюмок - 5
// Насосик
const byte PIN_PUMP = 12;
// Светодиоды
const int PIN_LED = 5;// Сюда подключаются светодиоды
const int LED_COUNT = max_DrinkCount;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, PIN_LED, NEO_GRB + NEO_KHZ800);

//-------

void pump_enable() {
  digitalWrite(PIN_PUMP, 1);
}

void pump_disable() {
  digitalWrite(PIN_PUMP, 0);
}

void pump_timer(byte Drink) {
  digitalWrite(PIN_PUMP, 1);
  delay(map(Drink, 2, 50, 300, 4000));
  digitalWrite(PIN_PUMP, 0);
}

void oled_menu(int Menu) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("Y F K B D F N J H"), CENTER, 0);//Н А Л И В А Т О Р
  myOLED.print(F("F D N J"), CENTER, 15);//А В Т О
  myOLED.print(F("H E X Y J Q "), CENTER, 30);//Р У Ч Н О Й 
  myOLED.print(F("G H J V S D R F"), CENTER, 45);//П Р О М Ы В К А
  myOLED.setFont(SmallFont);
  myOLED.print(F(">"), LEFT, (Menu * 15) + 15);
  myOLED.print(F("<"), RIGHT, (Menu * 15) + 15);
  myOLED.update();

}
//  выводит строчку по чуть чуть, в самый раз и тд. Передается номер строки, на которой выводить сообщение
void DrinkInfo(byte pos) {
  if (Drink < 15) {
    myOLED.print(F("YB J XTV"), CENTER, pos);//НИ О ЧЕМ
  } else if (Drink < 28) {
    myOLED.print(F("GJ XENM - XENM"), CENTER, pos);//ПО ЧУТЬ - ЧУТЬ
  } else if (Drink < 38) {
    myOLED.print(F("D CFVSQ HFP"), CENTER, pos);//В САМЫЙ РАЗ
  } else if (Drink < 48) {
    myOLED.print(F("GJ GJKYJQ"), CENTER, pos);//ПО ПОЛНОЙ
  } else {
    myOLED.print(F("LJ RHFTD"), CENTER, pos);//ДО КРАЕВ
  }

}

	void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("Ye?"), CENTER, 20); //Ну,
  // Рандом - 1
  switch (random(18)) {
    case 0:
      myOLED.print(F("pf dcnhtxe!"), CENTER, 40); //за встречу!
      mp3_play (2);  // Проигрываем "mp3/0002.mp3"
	  delay(100);
	  mp3_stop();
	  break;
    case 1:
      myOLED.print(F("pf rhfcjne!"), CENTER, 40); //за красоту!
      mp3_play (3);  // Проигрываем "mp3/0003.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 2:
      myOLED.print(F("pf lhe;,e!"), CENTER, 40); //за дружбу!
      mp3_play (4);  // Проигрываем "mp3/0004.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 4:
      myOLED.print(F("pf ,hfncndj!"), CENTER, 40); //за братство!
      mp3_play (5);  // Проигрываем "mp3/0005.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 5:
      myOLED.print(F("pf"), CENTER, 38); //за
	  myOLED.print(F("cghfdtlkbdjcnm!"), CENTER, 55); //справедливость!
      mp3_play (6);  // Проигрываем "mp3/0006.mp3"
	  delay(100);
	  mp3_stop();
	  break;
    case 6:
      myOLED.print(F("pf hs,fkre!"), CENTER, 40); //за рыбалку!
      mp3_play (7);  // Проигрываем "mp3/0007.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 7:
      myOLED.print(F("pf bcreccndj!"), CENTER, 40); //за искусство!
      mp3_play (8);  // Проигрываем "mp3/0008.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 8:
      myOLED.print(F("pf hfpev!"), CENTER, 40); //за разум!
      mp3_play (9);  // Проигрываем "mp3/0009.mp3"
	  delay(100);
	  mp3_stop();
	  break; 
	case 9:
      myOLED.print(F("pf bcnbyys["), CENTER, 38); //за истинных
	  myOLED.print(F(";tyoby!"), CENTER, 55); //женщин!
      mp3_play (10);  // Проигрываем "mp3/0010.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 10:
      myOLED.print(F("pf gjybvfybt!"), CENTER, 40); //за понимание!
      mp3_play (11);  // Проигрываем "mp3/0011.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 11:
      myOLED.print(F("pf tlbytybt!"), CENTER, 40); //за единение!
      mp3_play (13);  // Проигрываем "mp3/0013.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 12:
      myOLED.print(F("pf Gj,tle!"), CENTER, 40); //за Победу!
      mp3_play (16);  // Проигрываем "mp3/0016.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 13:
      myOLED.print(F("pf Hjlbye!"), CENTER, 40); //за Родину!
      mp3_play (20);  // Проигрываем "mp3/0020.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 14:
      myOLED.print(F("xnj, ujkjdf"), CENTER, 38); //чтоб голова
	  myOLED.print(F("yt nhtofkf!"), CENTER, 55); //не трещала!
      mp3_play (17);  // Проигрываем "mp3/0017.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 15:
      myOLED.print(F("pf cjkblyjt"), CENTER, 38); //за солидное
	  myOLED.print(F("ve;crjt vjkxfybt"), CENTER, 55); //мужское молчание
      mp3_play (12);  // Проигрываем "mp3/0012.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 16:
      myOLED.print(F("xnj, vjhobkj"), CENTER, 38); //чтоб морщило
	  myOLED.print(F("vtymit!"), CENTER, 55); //меньше!
      mp3_play (18);  // Проигрываем "mp3/0018.mp3"
	  delay(100);
	  mp3_stop();
	  break;
	case 17:
      myOLED.print(F("xnj, d cnjhjye"), CENTER, 38); //чтоб в сторону
	  myOLED.print(F("yt dbkmyekj!"), CENTER, 55); //не вильнуло!
      mp3_play (19);  // Проигрываем "mp3/0019.mp3"
	  delay(100);
	  mp3_stop();
	  break;      
  }
  myOLED.update();

}
/*
void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("DSGMTV"), CENTER, 20); //Выпьем
  // Рандом - 1
  switch (random(11)) {
    case 0:
      myOLED.print(F("PF LHEPTQ!"), CENTER, 40); //За друзей
      break;
    case 1:
      myOLED.print(F("PF VBKS{ LFV!"), CENTER, 40); //За милых дам
      break;
    case 2:
      myOLED.print(F("PF PLJHJDMT!"), CENTER, 40); //За здоровье
      break;
    case 3:
      myOLED.print(F("PF ELFXE!"), CENTER, 40); //За удачу
      break;
    case 4:
      myOLED.print(F("PF VBH DJ DCTV VBHT!"), CENTER, 40); //За мир во всем мире
      break;
    case 5:
      myOLED.print(F("PF NT{ RNJ D VJHT!"), CENTER, 40); //За тех кто в море
      break;
    case 6:
      myOLED.print(F("PF K><JDM !"), CENTER, 40); //За любовь !
      break;
    case 7:
      myOLED.print(F("PF RHFCJNE !"), CENTER, 40); //За красоту !
      break;
    case 8:
      myOLED.print(F("PF DTPTYBT !"), CENTER, 40); //За везение !
      break;
    case 9:
      myOLED.print(F("PF HJLBYE !"), CENTER, 40); //За родину !
      break;
    case 10:
      myOLED.print(F("PF YFC C DFVB"), CENTER, 38); //За нас с вами
      myOLED.print(F("B {HTY C YBVB !"), CENTER, 55); //И хрен с ними
      break;

  }
  myOLED.update();
}
*/

// Меню Авто режим
void oled_auto(int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("F D N J"), CENTER, 0);
  myOLED.print(F("VK   "), RIGHT, 27);
  DrinkInfo(57);
  //  myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(Drink), CENTER, 13);
  myOLED.update();
}

// Меню Ручной режим
void oled_manual(int DrinkCount, int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("H E X Y J Q"), CENTER, 0);
  DrinkInfo(57);
  // myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.print(F("H>V"), 24, 27);
  myOLED.print(F("VK "), RIGHT, 27);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(DrinkCount), LEFT, 13);
  myOLED.print(String(Drink), (Drink < 10) ? 80 : 57, 13);
  myOLED.update();
}


void oled_naliv(int MenuFlag) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);

  myOLED.print(F("Y F K B D F > "), CENTER, 27);
  DrinkInfo(47);
  myOLED.update();
}

void oled_nalito(int MenuFlag, int Nalito) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);
  myOLED.print(F("Y F K B N J"), CENTER, 20);
  if (Nalito == 1) {
    myOLED.print(F("H > V R F"), CENTER, 55);
  } else if (Nalito <= 4 ) {
    myOLED.print(F("H > V R B"), CENTER, 55);
  } else {
    myOLED.print(F("H > V J R"), CENTER, 55);
  }

  myOLED.setFont(SmallFont);
  myOLED.print(String(Nalito), CENTER, 36);
  myOLED.update();
}

void ServoNaliv(byte rumka) {
  servo.attach(PIN_SERVO);
  for (int pos = servo.read(); pos <= Rumka_pos[rumka]; pos += 1) { 
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(10); // ждем 15 миллисекунд, пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();


}

void ServoParking () {
  //Serial.println(servo.read());
  servo.attach(PIN_SERVO);
  for (int pos = servo.read();  pos >= 0; pos -= 1) {
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(10); // ждем 15 миллисекунд, пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();
}

void CvetoMuzik() {
  for (int i = 0; i <= 7; i++) {
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(255, 0, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 255, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 0, 255));
      strip.show();
      delay(30);
    }
  }
}

void setup()  {
  //Serial.begin(9600);
  //  servo.attach(PIN_SERVO);
  pinMode(pin_SW, INPUT); // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW, HIGH); // Поддяжка вывода к 1
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  pinMode(PIN_PUMP, OUTPUT);
  digitalWrite(PIN_PUMP, 0);
  currentTime = millis();
  loopTime = currentTime;
  //---------------
	Serial.begin(9600);//
	//устанавливаем Serial порт МП3 плейера если вывод в монитор TX(D0) и RX(D1)не нужен 
	mp3_set_serial (Serial);//инициализируем Serial порт МП3 плейера
	/*	
	mySoftwareSerial.begin(9600);//инициализируем програмный Serial порт 
	mp3_set_serial (mySoftwareSerial);// указываем програмный порт для МП3 плейера (см. 8)
	//инициализируем Serial с скоростью 115200, если вывод в монитор  TX(D0) RX(D1) необходим 
	Serial.begin(115200);
	*/	
	delay (100);//Между двумя командами необходимо делать задержку 100 миллисекунд, в противном случае некоторые команды могут работать не стабильно.
	mp3_set_volume (25);// устанвливаем громкость 25
	delay (100);
  mp3_play (1); // Проигрываем "mp3/0001.mp3"(0001_get started!.mp3)
  delay (100);
  
	//------------------
  //   Volume=EEPROM.read(0);
  myOLED.begin();
  // выводим привествие после включения перед наливом
  myOLED.clrScr();  
  myOLED.setFont(RusFont);
  myOLED.print(F("Ye? yfxfkb!"), CENTER, 50);// Ну, начали!
  myOLED.update(); 
  //
  oled_menu(0);
  strip.begin();
  for (int i = 0; i < 5; i++) {
    pinMode(Optics[i], INPUT);
  }
  ServoParking();

}
/*//---------------
// выводим привествие после включения перед наливом
  myOLED.clrScr();  
  myOLED.setFont(RusFont);
	myOLED.print(F("Ye? yfxfkb!"), CENTER, 50);// Ну, начали!
	myOLED.update(); 
// и озвучиваем

	mp3_set_volume (28);// устанвливаем громкость 28 (0_30)
	delay (100);
	mp3_play (1); // Проигрываем "mp3/0001.mp3"(0001_get started!.mp3)
	mp3_stop (1); // остановить воспроизведение
	mp3_set_volume (20);// устанвливаем громкость 20 (0_30)
//	delay (2000); //ждем 2 секунды
//----------------------------
*/  
void loop()  {
  currentTime = millis();
  if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс

    //     int  val = analogRead(0);     // считываем значение
    //  Serial.println(val);
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
    if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю

      //Вращение влево
      if (encoder_B) {
        if (MenuFlag == 0) {
          (Menu <= 0 ) ? Menu = 2 : Menu--; // Перемещение курсора по главному меню назад
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink <= 2 ) ? Drink = max_Drink : Drink--; // Уменьшаем кол-во милилитров в рюмку
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (DrinkCount >= max_DrinkCount ) ? DrinkCount = 1 : DrinkCount++; // Влево увечичиваем рюмки для ручного режима
          oled_manual(DrinkCount, Drink);
        }
        //Вращение вправо
      } else {
        if (MenuFlag == 0) {
          (Menu >= 2 ) ? Menu = 0 : Menu++; // Перемещение курсора по главному меню вперед.
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink >= max_Drink ) ? Drink = 2 : Drink++;
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (Drink >= max_Drink ) ? Drink = 2 : Drink++;
          oled_manual(DrinkCount, Drink);
        }
      }

    }

    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла

    int encoder_sw = digitalRead(pin_SW);
    if  (encoder_sw == 0 && encoder_sw != encoder_sw_prew)  { // Нажата кнопка

      int pause_sw = 0;
      boolean promivka = false;
      while (digitalRead(pin_SW) == 0) { // Держим кнопку. Считаем сколько времени прошло...
        delay(100);
        pause_sw++;
        if (pause_sw > 20 && Menu != 2 ) break;

        if (pause_sw > 20 && Menu == 2 && promivka == false) { // Если пункт меню промывка и держим кнопку больше 2 секунд.
          promivka = true;
          pump_enable(); // Включаем насос
          myOLED.clrScr();
          myOLED.setFont(RusFont);
          myOLED.print(F("G H J V S D R F"), CENTER, 15);//П Р О М Ы В К А
          myOLED.print(F(". . ."), CENTER, 45);
          myOLED.update();
        }
      }

      //После отпускания кнопки , обрабатываем
      if (promivka == true) { //Отпустили кнопку. Если включена промывка, выключаем насос и возвращаемся в главное меню
        promivka = false;
        pump_disable() ; //Выключаем насос
        oled_menu(2);

      } else {
        //Обработка всех нажатий кнопки
        if (Menu == 0 && MenuFlag == 0 &&  pause_sw < 10) { //Нажатие кнопки меню авто
          MenuFlag = 1;
          oled_auto(Drink);
        } else if (MenuFlag == 1 && pause_sw > 20) { //Выход из меню авто в главное
          MenuFlag = 0;
          oled_menu(0);
        } else if (MenuFlag == 1 ) { //Начинается автоматический разлив
          Serial.println("Начало автоматического разлива");
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          byte drink_count = 0;
          for (int y = 0; y < max_DrinkCount; y++) {
            if (analogRead(Optics[y]) > 1000 ) {
              strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
              strip.show();
              ServoNaliv(y); // Перемещяемся к рюмке
              pump_timer(Drink); // Налив.
              strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
              strip.show();
              drink_count++;
            }
          }
          if (drink_count > 0) {
            oled_nalito(MenuFlag, drink_count );
            ServoParking();
            delay(1000);
            Tost();
            CvetoMuzik();
            oled_auto(Drink);
          } else {
            myOLED.clrScr();
            myOLED.setFont(RusFont);
            myOLED.print(F("YTN H>VJR !"), CENTER, 25);//НЕТ РЮМОК !
            myOLED.update();
            delay(2000);
            oled_auto(Drink);

          }
        } else if (Menu == 1 && MenuFlag == 0 &&  pause_sw < 10) { // Нажатие меню ручное
          MenuFlag = 2;
          oled_manual(DrinkCount, Drink);
        } else if (MenuFlag == 2 && pause_sw > 20) { //Выход из меню ручное в главное
          MenuFlag = 0;
          oled_menu(1);
        } else if (MenuFlag == 2 ) { //Начинается ручной разлив
          //  Serial.println("Начало ручного разлива " + String(DrinkCount));
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          for (int y = 0; y < DrinkCount; y++) {
            strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
            strip.show();
            ServoNaliv(y); // Перемещяемся к рюмке
            pump_timer(Drink); // Налив.
            strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
            strip.show();
          }
          oled_nalito(MenuFlag, DrinkCount );
          ServoParking();
          Tost();
          CvetoMuzik();
          oled_manual(DrinkCount, Drink);
        }
      }
    }

    if (currentTime >= (ledTime + 300)) {
      //Опрашиваем оптопары ... Если рюмка поставлена , светодиод светится синим, нет ничего - не светится
      for (int i = 0; i < max_DrinkCount; i++) {
        
        int val = analogRead(Optics[i]);     // считываем значение
        Serial.println(val);
        if (val > 1000) {
          strip.setPixelColor(i, strip.Color(0, 0, 255));
        } else {
          strip.setPixelColor(i, strip.Color(0, 0, 0));
        }
    //    delay(20);

      }
      strip.show();
      ledTime = currentTime;
    }
    encoder_sw_prew = encoder_sw;
    loopTime = currentTime;

  }
}

Скетч использует 16430 байт (53%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1419 байт (69%) динамической памяти, оставляя 629 байт для локальных переменных. Максимум: 2048 байт.

Может кто в железе проверит?

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:55

#106

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

atom23rus пишет:

в этом то и прикол.в магазине был только 360 и на 12 вольт.буду питать от 8в.я о том где в скетче прописывать калибровку.

Не видел такого в скетче

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:55

#107

stpavel

Offline

Зарегистрирован: 09.10.2018

atom23rus пишет:

в этом то и прикол.в магазине был только 360 и на 12 вольт.буду питать от 8в.я о том где в скетче прописывать калибровку.

Если речь о моем скетче то калибровка в процедуре  pump_timer

	  delay(map(Drink, 2, 50, 300, 4000));

Здесь задается соотношение милилитров и задержки.

Т.е. в процедуру передается количество наливаемых милилитров , и с помощью команды map, пропорционально переноситься значение из милилитров в требуемую задержку.

Если устраивает диапазон от 2 до 50 мл, меняй значения 300 ( подразумевает 2 мл. ) и 4000 ( 50 мл) , остальное расчитается само .

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:56

#108

atom23rus

Offline

Зарегистрирован: 06.08.2019

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 12:59

#109

atom23rus

Offline

Зарегистрирован: 06.08.2019

да,про твой.спс. Задержка в милисекундах?

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 13:01

#110

stpavel

Offline

Зарегистрирован: 09.10.2018

atom23rus пишет:

да,про твой.спс. Задержка в милисекундах?

конечно.

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 13:03

#111

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

void pump_timer(byte Drink) {
  digitalWrite(PIN_PUMP, 1);
  delay(map(Drink, 10, 50, 1100, 5550));
  digitalWrite(PIN_PUMP, 0);
}

У меня так 10 мЛ — 1,1сек (1100 милисекунд), 50 мл- 5500 милисекунд.

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 13:09

#112

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 13:55

#113

atom23rus

Offline

Зарегистрирован: 06.08.2019

да я бы и с китая заказал,просто мне срочно нужен был

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 14:43

#114

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

stpavel, какие датчики используешь что у тебя на входах порог  val > 1000?
Уменя вот так:
A0    70,8    405,8
A1    51       324,4
A2    83       528,8
A3    131    652
A6    281    933,2
 Итого: val > 300

схема датчика на второй странице
 

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 16:12

#115

stpavel

Offline

Зарегистрирован: 09.10.2018

Forthomo пишет:

stpavel, какие датчики используешь что у тебя на входах порог  val > 1000?
Уменя вот так:
A0    70,8    405,8
A1    51       324,4
A2    83       528,8
A3    131    652
A6    281    933,2
 Итого: val > 300

схема датчика на второй странице
 

Использую вот такие датчики https://ru.aliexpress.com/item/4000036567119.html?spm=a2g0s.9042311.0.0.528c33ed9tRBaQ

На каждом установлен инвертирующий триггер Шмидта 74HC14D, который поидее должен хорошо подавлять дребезг.  У меня нет обычных  TCRT5000 что бы проверить как будет работать без этой микросхемы, но думаю с ней однозначно будет лучше. 

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

//----- Минимальные и максимальные значения наполняемой жидкости и задержки для наполнения. 
const byte  min_Drink = 2; // Минимум в рюмку - 2 мл.
const byte  max_Drink = 50; // Максимум в рюмку - 50 мл.
// Калибровка работы насосика. Значения для налива min_Drink и max_Drink соотвественно 
const unsigned int min_Drink_delay = 300; 
const unsigned int max_Drink_delay = 4000;
//--------

Добавил настройку порога срабатывания оптического датчика для каждой рюмки

// Значения порога срабатывания датчика для каждой рюмки
const unsigned int Optics_porog[] = {1000,1000,1000,1000,1000};
#include <OLED_I2C.h>
#include <Servo.h>
#include "Adafruit_NeoPixel.h"

OLED  myOLED(SDA, SCL, 8); //Подключение экрана А4, А5
extern uint8_t MegaNumbers[];
extern uint8_t RusFont[];
extern uint8_t SmallFont[];
unsigned long currentTime;
unsigned long loopTime;
unsigned long ledTime;
// Переменные для энкодера -----------
const int pin_A = 2;       // Подключение вывода A (CLK) энкодера
const int pin_B = 3;       // Подключение вывода B (DT) энкодера
const int pin_SW = 4;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev = 0;
unsigned char encoder_sw_prew = 1;
//Массив , обозначаем подключенные оптопары по выводам . Оптопары подключены, A0,A1,A2,A3,A6
const byte  Optics[] = {0, 1, 2, 3, 6};
// Значения порога срабатывания датчика для каждой рюмки
const unsigned int Optics_porog[] = {1000,1000,1000,1000,1000};
//Серво
const int PIN_SERVO = 9;
Servo servo;
//Позиция каждой рюмки 
const byte Rumka_pos[] = {0,40,75,105,140};
//-------------------------
byte  Menu = 0;
byte MenuFlag = 0; // Здесь храниться уровень меню. 0 находимся в  Главном меню. 1 Вошли в меню Авто, 2 вошли в  Ручное управление
byte  Drink = 25; // По умолчанию в рюмку наливаем  20 мл.
//----- Минимальные и максимальные значения наполняемой жидкости и задержки для наполнения. 
const byte  min_Drink = 2; // Минимум в рюмку - 2 мл.
const byte  max_Drink = 50; // Максимум в рюмку - 50 мл.
// Калибровка работы насосика. Значения для налива min_Drink и max_Drink соотвественно 
const unsigned int min_Drink_delay = 300; 
const unsigned int max_Drink_delay = 4000;
//--------
byte  DrinkCount = 1; //По умолчанию, для ручного режима - 1 рюмка
const byte  max_DrinkCount = 5; //Максимальное кол-во рюмок - 5
// Насосик
const byte PIN_PUMP = 12;
// Светодиоды
const int PIN_LED = 5;// Сюда подключаются светодиоды
const int LED_COUNT = max_DrinkCount;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, PIN_LED, NEO_GRB + NEO_KHZ800);
//-------



void pump_enable() {
  digitalWrite(PIN_PUMP, 1);
}

void pump_disable() {
  digitalWrite(PIN_PUMP, 0);
}

void pump_timer(byte Drink) {
  digitalWrite(PIN_PUMP, 1);
  delay(map(Drink, min_Drink,  max_Drink, min_Drink_delay, max_Drink_delay));
  digitalWrite(PIN_PUMP, 0);
}

void oled_menu(int Menu) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("Y F K B D F N J H"), CENTER, 0);
  myOLED.print(F("F D N J"), CENTER, 15);
  myOLED.print(F("H E X Y J Q "), CENTER, 30);
  myOLED.print(F("G H J V S D R F"), CENTER, 45);
  myOLED.setFont(SmallFont);
  myOLED.print(F(">"), LEFT, (Menu * 15) + 15);
  myOLED.print(F("<"), RIGHT, (Menu * 15) + 15);
  myOLED.update();

}
//  выводит строчку по чуть чуть, в самый раз и тд. Передается номер строки, на которой выводить сообщение
void DrinkInfo(byte pos) {
  if (Drink < 15) {
    myOLED.print(F("YB J XTV"), CENTER, pos);
  } else if (Drink < 28) {
    myOLED.print(F("GJ XENM - XENM"), CENTER, pos);
  } else if (Drink < 38) {
    myOLED.print(F("D CFVSQ HFP"), CENTER, pos);
  } else if (Drink < 48) {
    myOLED.print(F("GJ GJKYJQ"), CENTER, pos);
  } else {
    myOLED.print(F("LJ RHFTD"), CENTER, pos);
  }

}

void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("DSGMTV"), CENTER, 20); //Выпьем
  // Рандом - 1
  switch (random(11)) {
    case 0:
      myOLED.print(F("PF LHEPTQ!"), CENTER, 40); //За друзей
      break;
    case 1:
      myOLED.print(F("PF VBKS{ LFV!"), CENTER, 40); //За милых дам
      break;
    case 2:
      myOLED.print(F("PF PLJHJDMT!"), CENTER, 40); //За здоровье
      break;
    case 3:
      myOLED.print(F("PF ELFXE!"), CENTER, 40); //За удачу
      break;
    case 4:
      myOLED.print(F("PF VBH DJ DCTV VBHT!"), CENTER, 40); //За мир во всем мире
      break;
    case 5:
      myOLED.print(F("PF NT{ RNJ D VJHT!"), CENTER, 40); //За тех кто в море
      break;
    case 6:
      myOLED.print(F("PF K><JDM !"), CENTER, 40); //За любовь !
      break;
    case 7:
      myOLED.print(F("PF RHFCJNE !"), CENTER, 40); //За красоту !
      break;
    case 8:
      myOLED.print(F("PF DTPTYBT !"), CENTER, 40); //За везение !
      break;
    case 9:
      myOLED.print(F("PF HJLBYE !"), CENTER, 40); //За родину !
      break;
    case 10:
      myOLED.print(F("PF YFC C DFVB"), CENTER, 38); //За нас с вами
      myOLED.print(F("B {HTY C YBVB !"), CENTER, 55); //И хрен с ними
      break;

  }
  myOLED.update();

}

// Меню Авто режим
void oled_auto(int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("F D N J"), CENTER, 0);
  myOLED.print(F("VK   "), RIGHT, 27);
  DrinkInfo(57);
  //  myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(Drink), CENTER, 13);
  myOLED.update();
}

// Меню Ручной режим
void oled_manual(int DrinkCount, int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("H E X Y J Q"), CENTER, 0);
  DrinkInfo(57);
  // myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.print(F("H>V"), 24, 27);
  myOLED.print(F("VK "), RIGHT, 27);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(DrinkCount), LEFT, 13);
  myOLED.print(String(Drink), (Drink < 10) ? 80 : 57, 13);
  myOLED.update();
}


void oled_naliv(int MenuFlag) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);

  myOLED.print(F("Y F K B D F > "), CENTER, 27);
  DrinkInfo(47);
  myOLED.update();
}

void oled_nalito(int MenuFlag, int Nalito) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);
  myOLED.print(F("Y F K B N J"), CENTER, 20);
  if (Nalito == 1) {
    myOLED.print(F("H > V R F"), CENTER, 55);
  } else if (Nalito <= 4 ) {
    myOLED.print(F("H > V R B"), CENTER, 55);
  } else {
    myOLED.print(F("H > V J R"), CENTER, 55);
  }

  myOLED.setFont(SmallFont);
  myOLED.print(String(Nalito), CENTER, 36);
  myOLED.update();
}

void ServoNaliv(byte rumka) {
  servo.attach(PIN_SERVO);
  for (int pos = servo.read(); pos <= Rumka_pos[rumka]; pos += 1) { 
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(10); // ждем 15 миллисекунд, пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();


}

void ServoParking () {
  //Serial.println(servo.read());
  servo.attach(PIN_SERVO);
  for (int pos = servo.read();  pos >= 0; pos -= 1) {
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(10); // ждем 15 миллисекунд, пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();
}

void CvetoMuzik() {
  for (int i = 0; i <= 7; i++) {
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(255, 0, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 255, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 0, 255));
      strip.show();
      delay(30);
    }
  }
}

void setup()  {
  //Serial.begin(9600);
  //  servo.attach(PIN_SERVO);
  pinMode(pin_SW, INPUT); // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW, HIGH); // Поддяжка вывода к 1
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  pinMode(PIN_PUMP, OUTPUT);
  digitalWrite(PIN_PUMP, 0);
  currentTime = millis();
  loopTime = currentTime;
  //   Volume=EEPROM.read(0);
  myOLED.begin();
  oled_menu(0);
  strip.begin();
  for (int i = 0; i < 5; i++) {
    pinMode(Optics[i], INPUT);
  }
  ServoParking();



}

void loop()  {
  currentTime = millis();
  if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс

    //     int  val = analogRead(0);     // считываем значение
    //  Serial.println(val);
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
    if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю

      //Вращение влево
      if (encoder_B) {
        if (MenuFlag == 0) {
          (Menu <= 0 ) ? Menu = 2 : Menu--; // Перемещение курсора по главному меню назад
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink <= min_Drink ) ? Drink = max_Drink : Drink--; // Уменьшаем кол-во милилитров в рюмку
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (DrinkCount >= max_DrinkCount ) ? DrinkCount = 1 : DrinkCount++; // Влево увечичиваем рюмки для ручного режима
          oled_manual(DrinkCount, Drink);
        }
        //Вращение вправо
      } else {
        if (MenuFlag == 0) {
          (Menu >= 2 ) ? Menu = 0 : Menu++; // Перемещение курсора по главному меню вперед.
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_manual(DrinkCount, Drink);
        }
      }

    }

    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла

    int encoder_sw = digitalRead(pin_SW);
    if  (encoder_sw == 0 && encoder_sw != encoder_sw_prew)  { // Нажата кнопка

      int pause_sw = 0;
      boolean promivka = false;
      while (digitalRead(pin_SW) == 0) { // Держим кнопку. Считаем сколько времени прошло...
        delay(100);
        pause_sw++;
        if (pause_sw > 20 && Menu != 2 ) break;

        if (pause_sw > 20 && Menu == 2 && promivka == false) { // Если пункт меню промывка и держим кнопку больше 2 секунд.
          promivka = true;
          pump_enable(); // Включаем насос
          myOLED.clrScr();
          myOLED.setFont(RusFont);
          myOLED.print(F("G H J V S D R F"), CENTER, 15);
          myOLED.print(F(". . ."), CENTER, 45);
          myOLED.update();
        }
      }

      //После отпускания кнопки , обрабатываем
      if (promivka == true) { //Отпустили кнопку. Если включена промывка, выключаем насос и возвращаемся в главное меню
        promivka = false;
        pump_disable() ; //Выключаем насос
        oled_menu(2);

      } else {
        //Обработка всех нажатий кнопки
        if (Menu == 0 && MenuFlag == 0 &&  pause_sw < 10) { //Нажатие кнопки меню авто
          MenuFlag = 1;
          oled_auto(Drink);
        } else if (MenuFlag == 1 && pause_sw > 20) { //Выход из меню авто в главное
          MenuFlag = 0;
          oled_menu(0);
        } else if (MenuFlag == 1 ) { //Начинается автоматический разлив
          Serial.println("Начало автоматического разлива");
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          byte drink_count = 0;
          for (int y = 0; y < max_DrinkCount; y++) {
            if (analogRead(Optics[y]) > Optics_porog[y] ) {
              strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
              strip.show();
              ServoNaliv(y); // Перемещяемся к рюмке
              pump_timer(Drink); // Налив.
              strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
              strip.show();
              drink_count++;
            }
          }
          if (drink_count > 0) {
            oled_nalito(MenuFlag, drink_count );
            ServoParking();
            delay(1000);
            Tost();
            CvetoMuzik();
            oled_auto(Drink);
          } else {
            myOLED.clrScr();
            myOLED.setFont(RusFont);
            myOLED.print(F("YTN H>VJR !"), CENTER, 25);
            myOLED.update();
            delay(2000);
            oled_auto(Drink);

          }
        } else if (Menu == 1 && MenuFlag == 0 &&  pause_sw < 10) { // Нажатие меню ручное
          MenuFlag = 2;
          oled_manual(DrinkCount, Drink);
        } else if (MenuFlag == 2 && pause_sw > 20) { //Выход из меню ручное в главное
          MenuFlag = 0;
          oled_menu(1);
        } else if (MenuFlag == 2 ) { //Начинается ручной разлив
          //  Serial.println("Начало ручного разлива " + String(DrinkCount));
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          for (int y = 0; y < DrinkCount; y++) {
            strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
            strip.show();
            ServoNaliv(y); // Перемещяемся к рюмке
            pump_timer(Drink); // Налив.
            strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
            strip.show();
          }
          oled_nalito(MenuFlag, DrinkCount );
          ServoParking();
          Tost();
          CvetoMuzik();
          oled_manual(DrinkCount, Drink);
        }
      }
    }

    if (currentTime >= (ledTime + 300)) {
      //Опрашиваем оптопары ... Если рюмка поставлена , светодиод светится синим, нет ничего - не светится
      for (int i = 0; i < max_DrinkCount; i++) {
        
        int val = analogRead(Optics[i]);     // считываем значение
        Serial.println(val);
        if (val > Optics_porog[i]) {
          strip.setPixelColor(i, strip.Color(0, 0, 255));
        } else {
          strip.setPixelColor(i, strip.Color(0, 0, 0));
        }
    //    delay(20);

      }
      strip.show();
      ledTime = currentTime;
    }
    encoder_sw_prew = encoder_sw;
    loopTime = currentTime;

  }
}

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 18:55

#116

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Теперь вообще все понятно! Вставлю пятак скетче для проверки/калибровки датчиков А0, А1, А2, А3, А6, А7. Все выводится в монитор порта.

void setup() {

  // Объявляем работу с последоватлеьным портом в самом начале
  Serial.begin(9600);
  // Теперь мы можем писать сообщения
  Serial.println ("Hello, Arduino Master");
}

void loop() {// Выводим таблицу с информацией о текущих значениях портов

  Serial.print("Port #tt");
  Serial.println("Value");
  Serial.print("A0tt");
  Serial.println(analogRead(A0));
  Serial.print("A1tt");
  Serial.println(analogRead(A1));
  Serial.print("A2tt");
  Serial.println(analogRead(A2));
  Serial.print("A3tt");
  Serial.println(analogRead(A3));
  Serial.print("A6tt");
  Serial.println(analogRead(A6));
  Serial.print("A7tt");
  Serial.println(analogRead(A7));
  Serial.println("--------");
  delay(5000);
}

  • Войдите на сайт для отправки комментариев

Пт, 09/08/2019 — 22:13

#117

stpavel

Offline

Зарегистрирован: 09.10.2018

  • Войдите на сайт для отправки комментариев

Сб, 10/08/2019 — 14:35

#118

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

У Афанасьева В. много поделок в стиле стимпанк, есть подробные ворклоги на технари.ру, в эту тему видел «насТРОение»,  на Ютубе есть.

  • Войдите на сайт для отправки комментариев

Втр, 13/08/2019 — 13:31

#119

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

atom23rus пишет:

Парни, а подскажите как откалибровать насос?

Вот простой секундомер для калибровки, насос через силоврй ключ/реле к 12 пину, вывовд в монитор порта. Считает милисекунды. Нажал кнопку секундомер запустился, насос включился, отмерил сколько надо, нажал второй раз насос отключился — в мониторе время, оч удобно.

/* StopWatch
 * Paul Badger 2008
 * Demonstrates using millis(), pullup resistors,
 * making two things happen at once, printing fractions
 *
 * Physical setup: momentary switch connected to pin 4, other side connected to ground
 * LED with series resistor between pin 13 and ground
 */


#define ledPin  13                  // LED connected to digital pin 13
#define buttonPin 4                 // button on pin 4
#define pumpPin 12 // pump on pin 12

int value = LOW;                    // previous value of the LED
int buttonState;                    // variable to store button state
int lastButtonState;                // variable to store last button state
int blinking;                       // condition for blinking - timer is timing
long interval = 100;                // blink interval - change to suit
long previousMillis = 0;            // variable to store last time LED was updated
long startTime ;                    // start time for stop watch
long elapsedTime ;                  // elapsed time for stop watch
int fractional;                     // variable used to store fractional part of time

void setup()
{
   Serial.begin(9600);

   pinMode(ledPin, OUTPUT);         // sets the digital pin as output

   pinMode(buttonPin, INPUT);       // not really necessary, pins default to INPUT anyway
   digitalWrite(buttonPin, HIGH);   // turn on pullup resistors. Wire button so that press shorts pin to ground.

}

void loop()
{
    // check for button press
   buttonState = digitalRead(buttonPin);                   // read the button state and store

   if (buttonState == LOW && lastButtonState == HIGH  &&  blinking == false){     // check for a high to low transition
      // if true then found a new button press while clock is not running - start the clock

      startTime = millis();                                   // store the start time
      blinking = true;                                     // turn on blinking while timing
      delay(5);                                               // short delay to debounce switch
      digitalWrite(pumpPin, HIGH); //pump ON
	  lastButtonState = buttonState;                          // store buttonState in lastButtonState, to compare next time

   }

   else if (buttonState == LOW && lastButtonState == HIGH && blinking == true){     // check for a high to low transition
      // if true then found a new button press while clock is running - stop the clock and report

        elapsedTime =   millis() - startTime;              // store elapsed time
        blinking = false;                                  // turn off blinking, all done timing
        digitalWrite(pumpPin, LOW); // pump OFF
		lastButtonState = buttonState;                     // store buttonState in lastButtonState, to compare next time

       // routine to report elapsed time
        Serial.print( (int)(elapsedTime / 1000L));         // divide by 1000 to convert to seconds - then cast to an int to print

        Serial.print(".");                             // print decimal point

        // use modulo operator to get fractional part of time
       fractional = (int)(elapsedTime % 1000L);

       // pad in leading zeros - wouldn't it be nice if
       // Arduino language had a flag for this? :)
       if (fractional == 0)
          Serial.print("000");      // add three zero's
       else if (fractional < 10)    // if fractional < 10 the 0 is ignored giving a wrong time, so add the zeros
          Serial.print("00");       // add two zeros
       else if (fractional < 100)
          Serial.print("0");        // add one zero

       Serial.println(fractional);  // print fractional part of time

   }

   else{
      lastButtonState = buttonState;                         // store buttonState in lastButtonState, to compare next time
   }

   // blink routine - blink the LED while timing
   // check to see if it's time to blink the LED; that is, the difference
   // between the current time and last time we blinked the LED is larger than
   // the interval at which we want to blink the LED.

   if ( (millis() - previousMillis > interval) ) {

      if (blinking == true){
         previousMillis = millis();                         // remember the last time we blinked the LED

         // if the LED is off turn it on and vice-versa.
         if (value == LOW)
            value = HIGH;
         else
            value = LOW;
         digitalWrite(ledPin, value);
      }
      else{
         digitalWrite(ledPin, LOW);                         // turn off LED when not blinking
      }
   }

}

  • Войдите на сайт для отправки комментариев

Втр, 13/08/2019 — 16:44

#120

atom23rus

Offline

Зарегистрирован: 06.08.2019

спасибо. на днях выложу систему как я расположил трубку!

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 12:14

#121

atom23rus

Offline

Зарегистрирован: 06.08.2019

короче,по человечески загрузить не получилось.кидаю ссылки на картинки реализации поворота наливной головки.

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 15:43

#122

den-a2rh

Offline

Зарегистрирован: 07.01.2018

Добрый день всем…. У кого есть рабочий скетч с озвучкой тостов, схема подключения и список комплектующих. Хочу тоже попробовать собрать эту чудо машину … Кто может помочь ….

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 17:51

#123

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

atom23rus пишет:

короче,по человечески загрузить не получилось.кидаю ссылки на картинки реализации поворота наливной головки.

Нe не писающий мальчик, а так не плохо.

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 17:55

#124

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

den-a2rh, прочитайте две последних страницы там все разъяснено. «Говорилка» пока в железе не опробована. Скетч с  МП3 в 105 сообщении.

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 18:01

#125

atom23rus

Offline

Зарегистрирован: 06.08.2019

да боюсь писающего мальчика мужики не оценят

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 18:11

#126

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Может кто нибудь подсказать как шрифт в кейсе тост побольше сделать?

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 18:16

#127

den-a2rh

Offline

Зарегистрирован: 07.01.2018

Спасибо большое за подсказку…

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 18:42

#128

den-a2rh

Offline

Зарегистрирован: 07.01.2018

А не могли бы вы помочь со схемой подключения и списком комплектующих… Я в этом не селен… Соответственно за вознаграждение…. Моя почта [email protected] Заранее спасибо

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 21:58

#129

stpavel

Offline

Зарегистрирован: 09.10.2018

den-a2rh пишет:

А не могли бы вы помочь со схемой подключения и списком комплектующих… Я в этом не селен… Соответственно за вознаграждение…. Моя почта [email protected] Заранее спасибо

Схема подключения :
https://pikabu.ru/story/arduino_i_mp3_modul_uchim_arduino_govorit_3939974 

Где купить
https://ru.aliexpress.com/item/32659645208.html?spm=a2g0o.productlist.0.0.2d3b259fcpKTyq&algo_pvid=36ea7e3b-d25c-4ec1-a354-661eba1c9a7d&algo_expid=36ea7e3b-d25c-4ec1-a354-661eba1c9a7d-0&btsid=c29baa8c-f371-4e11-aa8f-2a3b5a9c2dd9&ws_ab_test=searchweb0_0,searchweb201602_1,searchweb201603_53 

Нужна еще sd карточка. 
На нее записываем файлы в папку mp3 файлы 0001.mp3 , 0002.mp3 итд
Это файлы с тостами.

Библиотека https://github.com/DFRobot/DFPlayer-Mini-mp3

​Последняя библиотека от DFRobot чето жрет памяти не хило. Если эта будет работать , почему бы и нет. 

В коде закоментировал процедуру tost которая выводила тосты на экран, и добавил маленькую процедурку tost которая просто проигрывает рандомный тост с сд карты. 

В настройках нужно поправить 

//mp3
byte mp3_count=11; //Количество голосовых файлов на SD карте

Код не тестировал, не на чем.  тут ничего сложного , должен работать, если не будет , поправим. 

#include <OLED_I2C.h>
#include <Servo.h>
#include "Adafruit_NeoPixel.h"
#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>

OLED  myOLED(SDA, SCL, 8); //Подключение экрана А4, А5
extern uint8_t MegaNumbers[];
extern uint8_t RusFont[];
extern uint8_t SmallFont[];
unsigned long currentTime;
unsigned long loopTime;
unsigned long ledTime;
// Переменные для энкодера -----------
const int pin_A = 2;       // Подключение вывода A (CLK) энкодера
const int pin_B = 3;       // Подключение вывода B (DT) энкодера
const int pin_SW = 4;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev = 0;
unsigned char encoder_sw_prew = 1;
//Массив , обозначаем подключенные оптопары по выводам . Оптопары подключены, A0,A1,A2,A3,A6
const byte  Optics[] = {0, 1, 2, 3, 6};
// Значения порога срабатывания датчика для каждой рюмки
const unsigned int Optics_porog[] = {1000,1000,1000,1000,1000};
//Серво
const int PIN_SERVO = 9;
Servo servo;
//Позиция каждой рюмки ( mg995 max 250)
const byte Rumka_pos[] = {0,40,75,105,140};
const byte servo_speed=10; // Скорость поворота серво,  10 - норм, 20 медленно, 30 очень медленно
//-------------------------
byte  Menu = 0;
byte MenuFlag = 0; // Здесь храниться уровень меню. 0 находимся в  Главном меню. 1 Вошли в меню Авто, 2 вошли в  Ручное управление
byte  Drink = 25; // По умолчанию в рюмку наливаем  20 мл.
//----- Минимальные и максимальные значения наполняемой жидкости и задержки для наполнения. 
const byte  min_Drink = 2; // Минимум в рюмку - 2 мл.
const byte  max_Drink = 50; // Максимум в рюмку - 50 мл.
// Калибровка работы насосика. Значения для налива min_Drink и max_Drink соотвественно 
const unsigned int min_Drink_delay = 300; 
const unsigned int max_Drink_delay = 4000;
//--------
byte  DrinkCount = 1; //По умолчанию, для ручного режима - 1 рюмка
const byte  max_DrinkCount = 5; //Максимальное кол-во рюмок - 5
// Насосик
const byte PIN_PUMP = 12;
// Светодиоды
const int PIN_LED = 5;// Сюда подключаются светодиоды
const int LED_COUNT = max_DrinkCount;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, PIN_LED, NEO_GRB + NEO_KHZ800);
//mp3
byte mp3_count=11; //Количество голосовых файлов на SD карте



void pump_enable() {
  digitalWrite(PIN_PUMP, 1);
}

void pump_disable() {
  digitalWrite(PIN_PUMP, 0);
}

void pump_timer(byte Drink) {
  digitalWrite(PIN_PUMP, 1);
  delay(map(Drink, min_Drink,  max_Drink, min_Drink_delay, max_Drink_delay));
  digitalWrite(PIN_PUMP, 0);
}

void oled_menu(int Menu) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("Y F K B D F N J H"), CENTER, 0);
  myOLED.print(F("F D N J"), CENTER, 15);
  myOLED.print(F("H E X Y J Q "), CENTER, 30);
  myOLED.print(F("G H J V S D R F"), CENTER, 45);
  myOLED.setFont(SmallFont);
  myOLED.print(F(">"), LEFT, (Menu * 15) + 15);
  myOLED.print(F("<"), RIGHT, (Menu * 15) + 15);
  myOLED.update();

}
//  выводит строчку по чуть чуть, в самый раз и тд. Передается номер строки, на которой выводить сообщение
void DrinkInfo(byte pos) {
  if (Drink < 15) {
    myOLED.print(F("YB J XTV"), CENTER, pos);
  } else if (Drink < 28) {
    myOLED.print(F("GJ XENM - XENM"), CENTER, pos);
  } else if (Drink < 38) {
    myOLED.print(F("D CFVSQ HFP"), CENTER, pos);
  } else if (Drink < 48) {
    myOLED.print(F("GJ GJKYJQ"), CENTER, pos);
  } else {
    myOLED.print(F("LJ RHFTD"), CENTER, pos);
  }

}
/*
void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("DSGMTV"), CENTER, 20); //Выпьем
  // Рандом - 1
  switch (random(11)) {
    case 0:
      myOLED.print(F("PF LHEPTQ!"), CENTER, 40); //За друзей
      break;
    case 1:
      myOLED.print(F("PF VBKS{ LFV!"), CENTER, 40); //За милых дам
      break;
    case 2:
      myOLED.print(F("PF PLJHJDMT!"), CENTER, 40); //За здоровье
      break;
    case 3:
      myOLED.print(F("PF ELFXE!"), CENTER, 40); //За удачу
      break;
    case 4:
      myOLED.print(F("PF VBH DJ DCTV VBHT!"), CENTER, 40); //За мир во всем мире
      break;
    case 5:
      myOLED.print(F("PF NT{ RNJ D VJHT!"), CENTER, 40); //За тех кто в море
      break;
    case 6:
      myOLED.print(F("PF K><JDM !"), CENTER, 40); //За любовь !
      break;
    case 7:
      myOLED.print(F("PF RHFCJNE !"), CENTER, 40); //За красоту !
      break;
    case 8:
      myOLED.print(F("PF DTPTYBT !"), CENTER, 40); //За везение !
      break;
    case 9:
      myOLED.print(F("PF HJLBYE !"), CENTER, 40); //За родину !
      break;
    case 10:
      myOLED.print(F("PF YFC C DFVB"), CENTER, 38); //За нас с вами
      myOLED.print(F("B {HTY C YBVB !"), CENTER, 55); //И хрен с ними
      break;

  }
  myOLED.update();

}
*/

void Tost() {
 randomSeed(currentTime);
 mp3_play(random(mp3_count));
 delay (5000);
}

// Меню Авто режим
void oled_auto(int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("F D N J"), CENTER, 0);
  myOLED.print(F("VK   "), RIGHT, 27);
  DrinkInfo(57);
  //  myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(Drink), CENTER, 13);
  myOLED.update();
}

// Меню Ручной режим
void oled_manual(int DrinkCount, int Drink) {

  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("H E X Y J Q"), CENTER, 0);
  DrinkInfo(57);
  // myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.print(F("H>V"), 24, 27);
  myOLED.print(F("VK "), RIGHT, 27);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(DrinkCount), LEFT, 13);
  myOLED.print(String(Drink), (Drink < 10) ? 80 : 57, 13);
  myOLED.update();
}


void oled_naliv(int MenuFlag) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);

  myOLED.print(F("Y F K B D F > "), CENTER, 27);
  DrinkInfo(47);
  myOLED.update();
}

void oled_nalito(int MenuFlag, int Nalito) {
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print((MenuFlag == 1) ? F("F D N J") : F("H E X Y J Q") , CENTER, 0);
  myOLED.print(F("Y F K B N J"), CENTER, 20);
  if (Nalito == 1) {
    myOLED.print(F("H > V R F"), CENTER, 55);
  } else if (Nalito <= 4 ) {
    myOLED.print(F("H > V R B"), CENTER, 55);
  } else {
    myOLED.print(F("H > V J R"), CENTER, 55);
  }

  myOLED.setFont(SmallFont);
  myOLED.print(String(Nalito), CENTER, 36);
  myOLED.update();
}

void ServoNaliv(byte rumka) {
  servo.attach(PIN_SERVO);
  for (int pos = servo.read(); pos <= Rumka_pos[rumka]; pos += 1) { 
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(servo_speed); // ждем , пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();


}

void ServoParking () {
  //Serial.println(servo.read());
  servo.attach(PIN_SERVO);
  for (int pos = servo.read();  pos >= 0; pos -= 1) {
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(servo_speed); // ждем , пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();
}

void CvetoMuzik() {
  for (int i = 0; i <= 7; i++) {
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(255, 0, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 255, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 0, 255));
      strip.show();
      delay(30);
    }
  }
}

void setup()  {
  Serial.begin(9600);
  mp3_set_serial (Serial); 
  delay(100);
  mp3_set_volume (25);
  delay(100);
  pinMode(pin_SW, INPUT); // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW, HIGH); // Поддяжка вывода к 1
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  pinMode(PIN_PUMP, OUTPUT);
  digitalWrite(PIN_PUMP, 0);
  currentTime = millis();
  loopTime = currentTime;
  //   Volume=EEPROM.read(0);
  myOLED.begin();
  oled_menu(0);
  strip.begin();
  for (int i = 0; i < 5; i++) {
    pinMode(Optics[i], INPUT);
  }
  
  ServoParking();



}

void loop()  {
  currentTime = millis();
  if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс

    //     int  val = analogRead(0);     // считываем значение
    //  Serial.println(val);
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
    if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю

      //Вращение влево
      if (encoder_B) {
        if (MenuFlag == 0) {
          (Menu <= 0 ) ? Menu = 2 : Menu--; // Перемещение курсора по главному меню назад
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink <= min_Drink ) ? Drink = max_Drink : Drink--; // Уменьшаем кол-во милилитров в рюмку
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (DrinkCount >= max_DrinkCount ) ? DrinkCount = 1 : DrinkCount++; // Влево увечичиваем рюмки для ручного режима
          oled_manual(DrinkCount, Drink);
        }
        //Вращение вправо
      } else {
        if (MenuFlag == 0) {
          (Menu >= 2 ) ? Menu = 0 : Menu++; // Перемещение курсора по главному меню вперед.
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_manual(DrinkCount, Drink);
        }
      }

    }

    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла

    int encoder_sw = digitalRead(pin_SW);
    if  (encoder_sw == 0 && encoder_sw != encoder_sw_prew)  { // Нажата кнопка

      int pause_sw = 0;
      boolean promivka = false;
      while (digitalRead(pin_SW) == 0) { // Держим кнопку. Считаем сколько времени прошло...
        delay(100);
        pause_sw++;
        if (pause_sw > 20 && Menu != 2 ) break;

        if (pause_sw > 20 && Menu == 2 && promivka == false) { // Если пункт меню промывка и держим кнопку больше 2 секунд.
          promivka = true;
          pump_enable(); // Включаем насос
          myOLED.clrScr();
          myOLED.setFont(RusFont);
          myOLED.print(F("G H J V S D R F"), CENTER, 15);
          myOLED.print(F(". . ."), CENTER, 45);
          myOLED.update();
        }
      }

      //После отпускания кнопки , обрабатываем
      if (promivka == true) { //Отпустили кнопку. Если включена промывка, выключаем насос и возвращаемся в главное меню
        promivka = false;
        pump_disable() ; //Выключаем насос
        oled_menu(2);

      } else {
        //Обработка всех нажатий кнопки
        if (Menu == 0 && MenuFlag == 0 &&  pause_sw < 10) { //Нажатие кнопки меню авто
          MenuFlag = 1;
          oled_auto(Drink);
        } else if (MenuFlag == 1 && pause_sw > 20) { //Выход из меню авто в главное
          MenuFlag = 0;
          oled_menu(0);
        } else if (MenuFlag == 1 ) { //Начинается автоматический разлив
         // Serial.println("Начало автоматического разлива");
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          byte drink_count = 0;
          for (int y = 0; y < max_DrinkCount; y++) {
            if (analogRead(Optics[y]) > Optics_porog[y] ) {
              strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
              strip.show();
              ServoNaliv(y); // Перемещяемся к рюмке
              pump_timer(Drink); // Налив.
              strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
              strip.show();
              drink_count++;
            }
          }
          if (drink_count > 0) {
            oled_nalito(MenuFlag, drink_count );
            ServoParking();
            delay(1000);
            Tost();
            CvetoMuzik();
            oled_auto(Drink);
          } else {
            myOLED.clrScr();
            myOLED.setFont(RusFont);
            myOLED.print(F("YTN H>VJR !"), CENTER, 25);
            myOLED.update();
            delay(2000);
            oled_auto(Drink);

          }
        } else if (Menu == 1 && MenuFlag == 0 &&  pause_sw < 10) { // Нажатие меню ручное
          MenuFlag = 2;
          oled_manual(DrinkCount, Drink);
        } else if (MenuFlag == 2 && pause_sw > 20) { //Выход из меню ручное в главное
          MenuFlag = 0;
          oled_menu(1);
        } else if (MenuFlag == 2 ) { //Начинается ручной разлив
          //  Serial.println("Начало ручного разлива " + String(DrinkCount));
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          for (int y = 0; y < DrinkCount; y++) {
            strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
            strip.show();
            ServoNaliv(y); // Перемещяемся к рюмке
            pump_timer(Drink); // Налив.
            strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
            strip.show();
          }
          oled_nalito(MenuFlag, DrinkCount );
          ServoParking();
          Tost();
          CvetoMuzik();
          oled_manual(DrinkCount, Drink);
        }
      }
    }

    if (currentTime >= (ledTime + 300)) {
      //Опрашиваем оптопары ... Если рюмка поставлена , светодиод светится синим, нет ничего - не светится
      for (int i = 0; i < max_DrinkCount; i++) {
        
        int val = analogRead(Optics[i]);     // считываем значение
     //   Serial.println("A"+String(Optics[i])+"="+val);
        if (val > Optics_porog[i]) {
          strip.setPixelColor(i, strip.Color(0, 0, 255));
        } else {
          strip.setPixelColor(i, strip.Color(0, 0, 0));
        }
    //    delay(20);

      }
      strip.show();
      ledTime = currentTime;
    }
    encoder_sw_prew = encoder_sw;
    loopTime = currentTime;

  }
}

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 22:20

#130

den-a2rh

Offline

Зарегистрирован: 07.01.2018

Спасибо вам большое…. Буду пробовать

  • Войдите на сайт для отправки комментариев

Ср, 14/08/2019 — 22:26

#131

stpavel

Offline

Зарегистрирован: 09.10.2018

Небольшая поправочка

В процедуре tost вместо

 mp3_play(random(mp3_count));

должно быть

 mp3_play(random(mp3_count)+1);

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 11:55

#132

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

den-a2rh ! В скетче предоставленного stpavel все подробно описано:
-Arduino pro mini 328 5v или аналогичный;
-дисплей OLED 0,96 I2C 128×64, подключается к А4 (SDA), А5 (SCL), VCC, GND;
-энкодер РЕС11 с кнопкой или аналогичный, подключается к D2 (A), D3 (B), D3 (E), C и D (GND),подтягивающие резисторы 2шт. 10кОм к D2, D3 одним концом, другим к VCC;
-лента светодиодная пиксельная WS2812B 5V используется 5 светодиодов, подключается к D5 (Din), GND  (GND), +5V и GND к внешнему источнику питания;
-серва (я использую SG90), подключается D9 (желтый), VCC (красный), GND (коричневый);
-насос (386 6-12V, нормально работает от 5 вольт, потребление 150мА), через силовой ключ или реле,

Силовой ключ (5 А; 24 В) на полевом транзисторе (IRF520 MOSFET) для Arduino

подключается к D12 (in), GND и к +5V и GND к внешнему источнику питания ;
-оптодатчики ( см. схему 1) 5 шт, подключаются к аналоговым входам А0, А1,А2,А3,А6  и VCC, GND

соответственно, схема 1для датчиков можно использовать пару  ИК светодиод + ИК фототранзистор с одинаковоу длинной волны, резистор для светодиода подбирается в зависимости от тока 470Ом,  резистор фототранзистора 10кОм, или купить готовую как на рисунке ;
-организация питания: литий 18680 8800ммА (Реально 3300мА), с зарядником ТР4056, и повышающим регулируемым модулем (Преобразователь DC-DC MT3608)  настроен на 5 вольт, выход модуля . Если лента и насос запитывается от 12В, то эти 12в подаются на пин RAW Ардуины.
*- DFPlayer mini (MP3-TF-16P) подключается:

VCC  DFP (1) к  5v внешнего источника питания,GND DFP (7) c GND Arduino и внешнему источнику питания,RX DFP (2)c TX Arduino (D0) через резистор 1кОм, TX DFP (3) c RX Arduino (D1) через резистор 1кОм, SPK_1 DFP(6) и SPK_2  DFP(8) к  динамику.

Рекомендую вам самому попробовать начертить схему наливатора в SPlan70, представить сюда, а результат обсудим.

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 11:57

#133

den-a2rh

Offline

Зарегистрирован: 07.01.2018

Спасибо большое…. Всё доступно обьяснили… Буду пробовать

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 12:10

#134

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

den-a2rh пишет:

Спасибо большое…. Всё доступно обьяснили… Буду пробовать

а плейер есть?

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 12:21

#135

den-a2rh

Offline

Зарегистрирован: 07.01.2018

На днях должен прийти с алиэкспресс… Ещё не все комплектующие пришли

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 15:06

#136

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

den-a2rh, отправил вам на почту нарезку тостов, если кому еще понадобиться, почта моя  [email protected] пишите.

  • Войдите на сайт для отправки комментариев

Чт, 15/08/2019 — 15:43

#137

den-a2rh

Offline

Зарегистрирован: 07.01.2018

Спасибо большое за нарезку тостов

  • Войдите на сайт для отправки комментариев

Пт, 16/08/2019 — 09:57

#138

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Вот такая ерунда происходит:

//		                     	задаю			на самом деле	 			так должно быть	
const byte Rumka_pos[] = {3,50,98,145,179}; // 12 - 48 - 90 - 135 - 174 (8 - 49 - 90 -132 - 173 - по чертежам)
//				                                 36   32    45    29       41   41  41    41 

 серва самая дешевая.   У кого какие мысли?

  • Войдите на сайт для отправки комментариев

Пт, 16/08/2019 — 12:21

#139

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Поправил скетч для калибровки насоса

#define ledPin  13                  // LED connected to digital pin 13
#define buttonPin 4                 // button on pin 4
#define pumpPin 12 // pump on pin 12

int value = LOW;                    // previous value of the LED
int buttonState;                    // variable to store button state
int lastButtonState;                // variable to store last button state
int blinking;                       // condition for blinking - timer is timing
long interval = 100;                // blink interval - change to suit
long previousMillis = 0;            // variable to store last time LED was updated
long startTime ;                    // start time for stop watch
long elapsedTime ;                  // elapsed time for stop watch
int fractional;                     // variable used to store fractional part of time

void setup()
{
   Serial.begin(9600);

   pinMode(ledPin, OUTPUT);         // sets the digital pin as output
   pinMode(pumpPin, OUTPUT); 
   pinMode(buttonPin, INPUT);       // not really necessary, pins default to INPUT anyway
   digitalWrite(buttonPin, 1);   // turn on pullup resistors. Wire button so that press shorts pin to ground.

}

void loop()
{
    // check for button press
   buttonState = digitalRead(buttonPin);                   // read the button state and store

   if (buttonState == LOW && lastButtonState == HIGH  &&  blinking == false){     // check for a high to low transition
      // if true then found a new button press while clock is not running - start the clock

      startTime = millis();                                   // store the start time
      blinking = true;                                     // turn on blinking while timing
      delay(5);                                               // short delay to debounce switch
      digitalWrite(pumpPin, 1); //pump ON
	  lastButtonState = buttonState;                          // store buttonState in lastButtonState, to compare next time

   }

   else if (buttonState == LOW && lastButtonState == HIGH && blinking == true){     // check for a high to low transition
      // if true then found a new button press while clock is running - stop the clock and report

        elapsedTime =   millis() - startTime;              // store elapsed time
        blinking = false;                                  // turn off blinking, all done timing
        digitalWrite(pumpPin, 0); // pump OFF
		lastButtonState = buttonState;                     // store buttonState in lastButtonState, to compare next time

       // routine to report elapsed time
        Serial.print( (int)(elapsedTime / 1000L));         // divide by 1000 to convert to seconds - then cast to an int to print

        Serial.print(".");                             // print decimal point

        // use modulo operator to get fractional part of time
       fractional = (int)(elapsedTime % 1000L);

       // pad in leading zeros - wouldn't it be nice if
       // Arduino language had a flag for this? :)
       if (fractional == 0)
          Serial.print("000");      // add three zero's
       else if (fractional < 10)    // if fractional < 10 the 0 is ignored giving a wrong time, so add the zeros
          Serial.print("00");       // add two zeros
       else if (fractional < 100)
          Serial.print("0");        // add one zero

       Serial.println(fractional);  // print fractional part of time

   }

   else{
      lastButtonState = buttonState;                         // store buttonState in lastButtonState, to compare next time
   }

   // blink routine - blink the LED while timing
   // check to see if it's time to blink the LED; that is, the difference
   // between the current time and last time we blinked the LED is larger than
   // the interval at which we want to blink the LED.

   if ( (millis() - previousMillis > interval) ) {

      if (blinking == true){
         previousMillis = millis();                         // remember the last time we blinked the LED

         // if the LED is off turn it on and vice-versa.
         if (value == LOW)
            value = HIGH;
         else
            value = LOW;
         digitalWrite(ledPin, value);
      }
      else{
         digitalWrite(ledPin, LOW);                         // turn off LED when not blinking
      }
   }

}

  • Войдите на сайт для отправки комментариев

Сб, 17/08/2019 — 11:02

#140

RW3

RW3 аватар

Offline

Зарегистрирован: 07.08.2019

  • Войдите на сайт для отправки комментариев

Сб, 17/08/2019 — 20:54

#141

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Коллеги есть предложение использовать LCD 1602 I2C, на OLED шрифт мелкий, а «дедушка старенький, глазки слабенькие». Русский шрифт есть, вчера пытал.

  • Войдите на сайт для отправки комментариев

Вс, 18/08/2019 — 20:03

#142

stpavel

Offline

Зарегистрирован: 09.10.2018

Да, тоже давно об этом думал. Ломать глаза на мизерном олед дисплейчике как то не айс

Заказал сегодня , жду когда придет, буду переделывать под него. 

Вобще много идей , как должен будет выглядить мой будущий наливатор. Идею с поворачивающимся кранчиком отбросил, не нравиться мне она. Будет вращаться стол. Обязательно будет сосуд сверху, в который будет предварительно заливаться алкоголь. Никаких там трубочек торчащих и засунутых в бутылку. Вдохновил меня этот видос, который я выкладывал несколько сообщений назад. 

  • Войдите на сайт для отправки комментариев

Пнд, 19/08/2019 — 17:33

#143

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

stpavel пишет:

Да, тоже давно об этом думал. Ломать глаза на мизерном олед дисплейчике как то не айс

Помоги менюху написать для LCD1602 заготовку прилагаю , блок ТОСТ еще не готов.

#include <LCD_1602_RUS.h>
#include <Servo.h>
#include "Adafruit_NeoPixel.h"
#include <SoftwareSerial.h>//добавляем библиотеки
#include <DFPlayer_Mini_Mp3.h>//добавляем библиотеку МП3 плейера

LCD_1602_RUS lcd(0x27, 16, 2); //Подключение экрана А4-SDA-зеленый, А5-SCL-желтый

unsigned long currentTime;
unsigned long loopTime;
unsigned long ledTime;

// Переменные для энкодера -----------
const int pin_A = 2;       // Подключение вывода A (CLK) энкодера
const int pin_B = 3;       // Подключение вывода B (DT) энкодера
const int pin_SW = 4;       // Подключение вывода кнопки (SW) энкодера
unsigned char encoder_A;
unsigned char encoder_B;
unsigned char encoder_A_prev = 0;
unsigned char encoder_sw_prew = 1;
//Массив , обозначаем подключенные оптопары по выводам . Оптопары подключены, A0,A1,A2,A3,A6
const byte  Optics[] = {0, 1, 2, 3, 6};
// Значения порога срабатывания датчика для каждой рюмки
const unsigned int Optics_porog[] = {100,200,200,200,100};
//Серво
const int PIN_SERVO = 9;
Servo servo;
//Позиция каждой рюмки 
const byte Rumka_pos[] = {3,50,98,145,179}; //12 - 48 - 90 - 135 - 174 
const byte servo_speed=20; // Скорость поворота серво,  10 - норм, 20 медленно, 30 очень медленно
byte  Menu = 0;
byte MenuFlag = 0; // Здесь храниться уровень меню. 0 находимся в  Главном меню. 1 Вошли в меню Авто, 2 вошли в  Ручное управление
byte  Drink = 25; // По умолчанию в рюмку наливаем  20 мл.
//----- Минимальные и максимальные значения наполняемой жидкости и задержки для наполнения. 
const byte  min_Drink = 2; // Минимум в рюмку - 2 мл.
const byte  max_Drink = 50; // Максимум в рюмку - 50 мл.
// Калибровка работы насосика. Значения для налива min_Drink и max_Drink соотвественно 
const unsigned int min_Drink_delay = 222; 
const unsigned int max_Drink_delay = 5500;
//--------
byte  DrinkCount = 1; //По умолчанию, для ручного режима - 1 рюмка
const byte  max_DrinkCount = 5; //Максимальное кол-во рюмок - 5
// Насосик
const byte PIN_PUMP = 12;
// Светодиоды
const int PIN_LED = 5;// Сюда подключаются светодиоды
const int LED_COUNT = max_DrinkCount;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_COUNT, PIN_LED, NEO_GRB + NEO_KHZ800);
//-------



void pump_enable() {
  digitalWrite(PIN_PUMP, 1); //вкл реле
}

void pump_disable() {
  digitalWrite(PIN_PUMP, 0); //выкл реле
}

void pump_timer(byte Drink) {
  digitalWrite(PIN_PUMP, 1); //вкл реле
  delay(map(Drink, min_Drink,  max_Drink, min_Drink_delay, max_Drink_delay));
  digitalWrite(PIN_PUMP, 0); //выкл реле
}

void oled_menu(int Menu) {
  lcd.setCursor(3, 0);
  lcd.print("HАЛИВАТОР");
  lcd.setCursor(2, 0);
  lcd.print("<   АВТО   >");
//lcd.print("<  РУЧНОЙ  >");
//lcd.print("< ПРОМЫВКА >");
 
}
//  выводит строчку по чуть чуть, в самый раз и тд. Передается номер строки, на которой выводить сообщение
void DrinkInfo(byte pos) {
  lcd.setCursor(0, 0);
  lcd.print("HАЛИТЬ  ПО");
  lcd.setCursor(11, 0);
  lcd.print(Drink);
  lcd.setCursor(14, 0);
  lcd.print("мЛ"); 
  if (Drink < 15) {
    lcd.setCursor(5, 1);
    lcd.print("НИ О ЧЕМ");
  } else if (Drink < 28) {
    lcd.setCursor(2, 1);
    lcd.print("ПО ЧУТЬ - ЧУТЬ");
  } else if (Drink < 38) {
    lcd.setCursor(2, 1);
    lcd.print("В САМЫЙ  РАЗ");
  } else if (Drink < 48) {
    lcd.setCursor(3, 1);
    lcd.print("ПО  ПОЛНОЙ");
  } else {
    lcd.setCursor(4, 1);
    lcd.print("ДО КРАЕВ");
  }
}

/*
void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("DSGMTV"), CENTER, 20); //Выпьем
  // Рандом - 1
  switch (random(11)) {
    case 0:
      myOLED.print(F("PF LHEPTQ!"), CENTER, 40); //За друзей
      break;
    case 1:
      myOLED.print(F("PF VBKS{ LFV!"), CENTER, 40); //За милых дам
      break;
    case 2:
      myOLED.print(F("PF PLJHJDMT!"), CENTER, 40); //За здоровье
      break;
    case 3:
      myOLED.print(F("PF ELFXE!"), CENTER, 40); //За удачу
      break;
    case 4:
      myOLED.print(F("PF VBH DJ DCTV VBHT!"), CENTER, 40); //За мир во всем мире
      break;
    case 5:
      myOLED.print(F("PF NT{ RNJ D VJHT!"), CENTER, 40); //За тех кто в море
      break;
    case 6:
      myOLED.print(F("PF K><JDM !"), CENTER, 40); //За любовь !
      break;
    case 7:
      myOLED.print(F("PF RHFCJNE !"), CENTER, 40); //За красоту !
      break;
    case 8:
      myOLED.print(F("PF DTPTYBT !"), CENTER, 40); //За везение !
      break;
    case 9:
      myOLED.print(F("PF HJLBYE !"), CENTER, 40); //За родину !
      break;
    case 10:
      myOLED.print(F("PF YFC C DFVB"), CENTER, 38); //За нас с вами
      myOLED.print(F("B {HTY C YBVB !"), CENTER, 55); //И хрен с ними
      break;

  }
  myOLED.update();

}
*/

/*
  void Tost() {
  randomSeed(currentTime);
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("YE?"), CENTER, 20); //НУ,
   Рандом - 1
  switch (random(18)) { // 0...17
    case 0:
      myOLED.print(F("PF DCNHTXE!"), CENTER, 40); //ЗА ВСТРЕЧУ!
      mp3_play (2);  // Проигрываем "mp3/0002.mp3"
    delay(100);
    mp3_stop();
    break;
    case 1:
      myOLED.print(F("PF RHFCJNE!"), CENTER, 40); //ЗА КРАСОТУ!
      mp3_play (3);  // Проигрываем "mp3/0003.mp3"
    delay(100);
    mp3_stop();
    break;
  case 2:
      myOLED.print(F("PF LHE;,E!"), CENTER, 40); //ЗА ДРУЖБУ!
      mp3_play (4);  // Проигрываем "mp3/0004.mp3"
    delay(100);
    mp3_stop();
    break;
  case 4:
      myOLED.print(F("PF ,HFNCNDJ!"), CENTER, 40); //ЗА БРАТСТВО!
      mp3_play (5);  // Проигрываем "mp3/0005.mp3"
    delay(100);
    mp3_stop();
    break;
  case 5:
      myOLED.print(F("PF"), CENTER, 38); //за
    myOLED.print(F("CGHFDTLKBDJCNM!"), CENTER, 55); //СПРАВЕДЛИВОСТЬ!
      mp3_play (6);  // Проигрываем "mp3/0006.mp3"
    delay(100);
    mp3_stop();
    break;
    case 6:
      myOLED.print(F("PF HS,FKRE!"), CENTER, 40); //ЗА РЫБАЛКУ!
      mp3_play (7);  // Проигрываем "mp3/0007.mp3"
    delay(100);
    mp3_stop();
    break;
  case 7:
      myOLED.print(F("PF BCRECCNDJ!"), CENTER, 40); //ЗА ИСКУССТВО!
      mp3_play (8);  // Проигрываем "mp3/0008.mp3"
    delay(100);
    mp3_stop();
    break;
  case 8:
      myOLED.print(F("PF HFPEV!"), CENTER, 40); //ЗА РАЗУМ!
      mp3_play (9);  // Проигрываем "mp3/0009.mp3"
    delay(100);
    mp3_stop();
    break; 
  case 9:
      myOLED.print(F("PF BCNBYYS["), CENTER, 38); //ЗА ИСТИННЫХ
      myOLED.print(F(";tyoby!"), CENTER, 55); //ЖЕНЩИН!
      mp3_play (10);  // Проигрываем "mp3/0010.mp3"
    delay(100);
    mp3_stop();
    break;
  case 10:
      myOLED.print(F("PF GJYBVFYBT!"), CENTER, 40); //ЗА ПОНИМАНИЕ!
      mp3_play (11);  // Проигрываем "mp3/0011.mp3"
    delay(100);
    mp3_stop();
    break;
  case 11:
      myOLED.print(F("PF TLBYTYBT!"), CENTER, 40); //ЗА ЕДИНЕНИЕ!
      mp3_play (13);  // Проигрываем "mp3/0013.mp3"
    delay(100);
    mp3_stop();
    break;
  case 12:
      myOLED.print(F("PF GJ,TLE!"), CENTER, 40); //ЗА ПОБЕДУ!
      mp3_play (16);  // Проигрываем "mp3/0016.mp3"
    delay(100);
    mp3_stop();
    break;
  case 13:
      myOLED.print(F("PF HJLBYE!"), CENTER, 40); //ЗА РОДИНУ!
      mp3_play (21);  // Проигрываем "mp3/0021.mp3"
    delay(100);
    mp3_stop();
    break;
  case 14:
      myOLED.print(F("XNJ, UJKJDF"), CENTER, 38); //ЧТОБ ГОЛОВА
    myOLED.print(F("YT NHTOFKF!"), CENTER, 55); //НЕ ТРЕЩАЛА!
      mp3_play (17);  // Проигрываем "mp3/0017.mp3"
    delay(100);
    mp3_stop();
    break;
  case 15:
      myOLED.print(F("PF CJKBLYJT"), CENTER, 38); //ЗА СОЛИДНОЕ
    myOLED.print(F("VE;CRJT VJKXFYBT"), CENTER, 55); //МУЖСКОЕ МОЛЧАНИЕ
      mp3_play (12);  // Проигрываем "mp3/0012.mp3"
    delay(100);
    mp3_stop();
    break;
  case 16:
      myOLED.print(F("XNJ, VJHOBKJ"), CENTER, 38); //ЧТОБ МОРЩИЛО
    myOLED.print(F("YFC VTYMIT!"), CENTER, 55); //НАС МЕНЬШЕ!
      mp3_play (18);  // Проигрываем "mp3/0018.mp3"
    delay(100);
    mp3_stop();
    break;
  case 17:
      myOLED.print(F("XNJ, D CNJHJYE"), CENTER, 38); //ЧТОБ В СТОРОНУ
    myOLED.print(F("YT DBKMYEKJ!"), CENTER, 55); //НЕ ВИЛЬНУЛО!
      mp3_play (19);  // Проигрываем "mp3/0019.mp3"
    delay(100);
    mp3_stop();
    break;      
  }
  myOLED.update();

}
*/
// Меню Авто режим
void oled_auto(int Drink) {
  lcd.setCursor(0, 0);
    lcd.print("HАЛИТЬ ПО");
    lcd.setCursor(11, 0);
    lcd.print(Drink);
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
    DrinkInfo(57);
  //  myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  
}

// Меню Ручной режим
void oled_manual(int DrinkCount, int Drink) {
  lcd.setCursor(0, 0);
    lcd.print("HАЛИТЬ ПО");
    lcd.setCursor(11, 0);
    lcd.print(Drink);
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
    DrinkInfo(57);
}
  
/*
  myOLED.clrScr();
  myOLED.setFont(RusFont);
  myOLED.print(F("H E X Y J Q"), CENTER, 0); //Р У Ч Н О Й 
  DrinkInfo(57);
  // myOLED.print(DrinkInfo[map(Drink, 2, max_Drink, 0, 4)], CENTER, 57);
  myOLED.print(F("H>V"), 24, 27);
  myOLED.print(F("VK "), RIGHT, 27);
  myOLED.setFont(MegaNumbers);
  myOLED.print(String(DrinkCount), LEFT, 13);
  myOLED.print(String(Drink), (Drink < 10) ? 80 : 57, 13);
  myOLED.update();
}
*/

void oled_naliv(int MenuFlag) {
  lcd.setCursor(0, 0);
    lcd.print("HАЛИВАЮ ПО");
    lcd.setCursor(11, 0);
    lcd.print(Drink);
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
  lcd.setCursor(3, 1);
    lcd.print("В");
    lcd.setCursor(5, 1);
    lcd.print(DrinkCount);  
  if (DrinkCount == 1) {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКУ");
  } else if (DrinkCount <= 4 ) {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКИ");
  } else {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКИ");
  }
}

void oled_nalito(int MenuFlag, int Nalito) {
    lcd.setCursor(0, 0);
    lcd.print("HАЛИТО  ПО");
    lcd.setCursor(11, 0);
    lcd.print(Drink);
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
  lcd.setCursor(3, 1);
    lcd.print("В");
    lcd.setCursor(5, 1);
    lcd.print(Nalito);  
  if (Nalito == 1) {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКУ");
  } else if (Nalito <= 4 ) {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКИ");
  } else {
    lcd.setCursor(7, 1);
    lcd.print("РЮМКИ");
  }

}
void ServoNaliv(byte rumka) {
  servo.attach(PIN_SERVO);
  for (int pos = servo.read(); pos <= Rumka_pos[rumka]; pos += 1) { 
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(servo_speed); // ждем , пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();


}

void ServoParking () {
  //Serial.println(servo.read());
  servo.attach(PIN_SERVO);
  for (int pos = servo.read();  pos >= 0; pos -= 1) {
    // с шагом в 1 градус
    servo.write(pos); // даем серве команду повернуться в положение, которое задается в переменной 'pos'
    delay(servo_speed); // ждем , пока ротор сервы выйдет в заданную позицию
  }
  servo.detach();
}

void CvetoMuzik() {
  for (int i = 0; i <= 7; i++) {
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(255, 0, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 255, 0));
      strip.show();
      delay(30);
    }
    for (int y = 0; y < max_DrinkCount; y++) {
      strip.setPixelColor(y, strip.Color(0, 0, 255));
      strip.show();
      delay(30);
    }
  }
}

void setup()  {
  Serial.begin(9600);//
  //устанавливаем Serial порт МП3 плейера если вывод в монитор TX(D0) и RX(D1)не нужен 
  mp3_set_serial (Serial);//инициализируем Serial порт МП3 плейера
  /*  
  при необходимости создаем програмный порт для управдения МП3 плейером, если вывод в монитор TX(D0) RX(D1) необходим
  SoftwareSerial mySoftwareSerial(10, 11); // RX, TX  обозначаем програмный порт как mySoftwareSerial
  //плейер подключаем D10 D11
  mySoftwareSerial.begin(9600);//инициализируем програмный Serial порт 
  mp3_set_serial (mySoftwareSerial);// указываем програмный порт для МП3 плейера
  //инициализируем Serial с скоростью 115200, если вывод в монитор  TX(D0) RX(D1) необходим 
  Serial.begin(115200);
  */  
  delay (100);//Между двумя командами необходимо делать задержку 100 миллисекунд, в противном случае некоторые команды могут работать не стабильно.
  mp3_set_volume (25);// устанвливаем громкость 25
  delay (100);
  mp3_play (1); // Проигрываем "mp3/0001.mp3"(0001_get started!.mp3)
  delay (100);
  //   Volume=EEPROM.read(0);
  
  /*
  
  myOLED.begin(); // Инициализация дисплея
  // выводим привествие после включения перед наливом
  myOLED.clrScr();  
  myOLED.setFont(RusFont);
  myOLED.print(F("Ye? yfxfkb!"), CENTER, 50);// Ну, начали!
  myOLED.update(); 
  
  */
  lcd.init();// initialize the lcd
  lcd.backlight();
  pinMode(pin_SW, INPUT); // устанавливаем pin pin_SW как вход
  digitalWrite(pin_SW, HIGH); // Поддяжка вывода к 1
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  pinMode(PIN_PUMP, OUTPUT);
  digitalWrite(PIN_PUMP, 0);
  currentTime = millis();
  loopTime = currentTime;
  //---------------

  oled_menu(0);
  strip.begin();
  for (int i = 0; i < 5; i++) {
    pinMode(Optics[i], INPUT);
  }
  ServoParking();

}

void loop()  {
  currentTime = millis();
  if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс

    //     int  val = analogRead(0);     // считываем значение
    //  Serial.println(val);
    encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
    encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
    if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю

      //Вращение влево
      if (encoder_B) {
        if (MenuFlag == 0) {
          (Menu <= 0 ) ? Menu = 2 : Menu--; // Перемещение курсора по главному меню назад
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink <= min_Drink ) ? Drink = max_Drink : Drink--; // Уменьшаем кол-во милилитров в рюмку
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (DrinkCount >= max_DrinkCount ) ? DrinkCount = 1 : DrinkCount++; // Влево увечичиваем рюмки для ручного режима
          oled_manual(DrinkCount, Drink);
        }
        //Вращение вправо
      } else {
        if (MenuFlag == 0) {
          (Menu >= 2 ) ? Menu = 0 : Menu++; // Перемещение курсора по главному меню вперед.
          oled_menu(Menu);
        } else if (MenuFlag == 1) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_auto(Drink);
        } else if (MenuFlag == 2) {
          (Drink >= max_Drink ) ? Drink = min_Drink : Drink++;
          oled_manual(DrinkCount, Drink);
        }
      }

    }

    encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла

    int encoder_sw = digitalRead(pin_SW);
    if  (encoder_sw == 0 && encoder_sw != encoder_sw_prew)  { // Нажата кнопка

      int pause_sw = 0;
      boolean promivka = false;
      while (digitalRead(pin_SW) == 0) { // Держим кнопку. Считаем сколько времени прошло...
        delay(100);
        pause_sw++;
        if (pause_sw > 20 && Menu != 2 ) break;

        if (pause_sw > 20 && Menu == 2 && promivka == false) { // Если пункт меню промывка и держим кнопку больше 2 секунд.
          promivka = true;
          pump_enable(); // Включаем насос
          lcd.setCursor(0, 0);
      lcd.print("П Р О М Ы В К А");
          lcd.setCursor(2, 0);
      lcd.print(">>>>>>>>>>>>");
        }
      }

      //После отпускания кнопки , обрабатываем
      if (promivka == true) { //Отпустили кнопку. Если включена промывка, выключаем насос и возвращаемся в главное меню
        promivka = false;
        pump_disable() ; //Выключаем насос
        oled_menu(2);

      } else {
        //Обработка всех нажатий кнопки
        if (Menu == 0 && MenuFlag == 0 &&  pause_sw < 10) { //Нажатие кнопки меню авто
          MenuFlag = 1;
          oled_auto(Drink);
        } else if (MenuFlag == 1 && pause_sw > 20) { //Выход из меню авто в главное
          MenuFlag = 0;
          oled_menu(0);
        } else if (MenuFlag == 1 ) { //Начинается автоматический разлив
          Serial.println("Начало автоматического разлива");
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          byte drink_count = 0;
          for (int y = 0; y < max_DrinkCount; y++) {
            if (analogRead(Optics[y]) > Optics_porog[y] ) {
              strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
              strip.show();
              ServoNaliv(y); // Перемещяемся к рюмке
              pump_timer(Drink); // Налив.
              strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
              strip.show();
              drink_count++;
            }
          }
          if (drink_count > 0) {
            oled_nalito(MenuFlag, drink_count );
            ServoParking();
            delay(1000);
//           Tost();
            CvetoMuzik();
            oled_auto(Drink);
          } else {
            lcd.setCursor(7, 1);
            lcd.print("НЕТ РЮМОК!");
            delay(2000);
            oled_auto(Drink);

          }
        } else if (Menu == 1 && MenuFlag == 0 &&  pause_sw < 10) { // Нажатие меню ручное
          MenuFlag = 2;
          oled_manual(DrinkCount, Drink);
        } else if (MenuFlag == 2 && pause_sw > 20) { //Выход из меню ручное в главное
          MenuFlag = 0;
          oled_menu(1);
        } else if (MenuFlag == 2 ) { //Начинается ручной разлив
          //  Serial.println("Начало ручного разлива " + String(DrinkCount));
          oled_naliv(MenuFlag); // Выводим на экран наливаем ...
          for (int y = 0; y < DrinkCount; y++) {
            strip.setPixelColor(y, strip.Color(255, 0, 0)); // Подствечиваем красным цветом
            strip.show();
            ServoNaliv(y); // Перемещяемся к рюмке
            pump_timer(Drink); // Налив.
            strip.setPixelColor(y, strip.Color(0, 255, 0)); // Подствечиваем зеленым , налито.
            strip.show();
          }
          oled_nalito(MenuFlag, DrinkCount );
          ServoParking();
          //Tost();
          CvetoMuzik();
          oled_manual(DrinkCount, Drink);
        }
      }
    }

    if (currentTime >= (ledTime + 300)) {
      //Опрашиваем оптопары ... Если рюмка поставлена , светодиод светится синим, нет ничего - не светится
      for (int i = 0; i < max_DrinkCount; i++) {
        
        int val = analogRead(Optics[i]);     // считываем значение
        Serial.println(val);
        if (val > Optics_porog[i]) {
          strip.setPixelColor(i, strip.Color(0, 0, 255));
        } else {
          strip.setPixelColor(i, strip.Color(0, 0, 0));
        }
    //    delay(20);

      }
      strip.show();
      ledTime = currentTime;
    }
    encoder_sw_prew = encoder_sw;
    loopTime = currentTime;

  }
}
/*
Скетч использует 13484 байт (43%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 974 байт (47%) динамической памяти, оставляя 1074 байт для локальных переменных. Максимум: 2048 байт.
*/

  • Войдите на сайт для отправки комментариев

Втр, 20/08/2019 — 16:08

#144

stpavel

Offline

Зарегистрирован: 09.10.2018

Forthomo пишет:

Помоги менюху написать для LCD1602 заготовку прилагаю , блок ТОСТ еще не готов.

void oled_menu(byte Menu) {
  lcd.clear();
  lcd.setCursor(3, 0);
  lcd.print("НАЛИВАТОР+");
  lcd.setCursor(0, 1);
  lcd.print(F(">"));
  lcd.setCursor(15, 1);
  lcd.print(F("<"));
  switch (Menu) {
    case 0:
      lcd.setCursor(6, 1);
      lcd.print(F("АВТО"));
      break;
    case 1:
      lcd.setCursor(2, 1);
      lcd.print(F("РУЧНОЙ РЕЖИМ"));
      break;
    case 2:
      lcd.setCursor(4, 1);
      lcd.print(F("ПРОМЫВКА"));
      break;
  }
}

  • Войдите на сайт для отправки комментариев

Втр, 20/08/2019 — 16:36

#145

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

Павел, спасибо! Выкладываю скетч где можно посмотреть Экраны LCD:

#include <LCD_1602_RUS.h>
// https://codeload.github.com/ssilver2007/LCD_1602_RUS/zip/master
LCD_1602_RUS lcd(0x27, 16, 2);

int mll=25;
int temp=3;

void setup()
{
  lcd.init();  // initialize the lcd
  lcd.backlight();
}
 
void loop(){
	// меню авто
	lcd.setCursor(3, 0);
	lcd.print("HАЛИВАТОР");
	lcd.setCursor(2, 1);
	lcd.print("<   АВТО   >");
	delay(3000);
	lcd.clear();
	// меню ручной
	lcd.setCursor(3, 0);
	lcd.print("HАЛИВАТОР");
	lcd.setCursor(2, 1);	
	lcd.print("<  РУЧНОЙ  >");
	delay(3000);
	lcd.clear();
	// меню промывка
	lcd.setCursor(3, 0);
	lcd.print("HАЛИВАТОР");
	lcd.setCursor(2, 1);	
	lcd.print("< ПРОМЫВКА >");
	delay(3000);
	lcd.clear();
	// промывка
	lcd.setCursor(3, 0);
	lcd.print("ПРОМЫВКА");
	lcd.setCursor(4, 1);	
	lcd.print(">>>>>>");
	delay(3000);
	lcd.clear();
	//
    lcd.setCursor(0, 0);
    lcd.print("HАЛИТЬ ПО");
    lcd.setCursor(10, 0);
    lcd.print(mll); //ПЕРЕМЕННАЯ
    lcd.setCursor(13, 0);
    lcd.print("мЛ?");
    lcd.setCursor(1, 1);
    lcd.print("ПО ЧУТЬ - ЧУТЬ");
	delay(3000);
	lcd.clear();
	// итог

	
	//АВТОНАЛИВ
	lcd.setCursor(3, 0);
	lcd.print("АВТОНАЛИВ");
	lcd.setCursor(0, 1);
	lcd.print("ПО");
	lcd.setCursor(3, 1);
    lcd.print(mll); //ПЕРЕМЕННАЯ
    lcd.setCursor(6, 1);
    lcd.print("мЛ В РЮМКУ");
	delay(3000);
	lcd.clear();
	
	
	// ручной налив
    lcd.setCursor(0, 0);
    lcd.print("HАЛИВАЮ ПО");
    lcd.setCursor(11, 0);
    lcd.print(mll); //ПЕРЕМЕННАЯ
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
    lcd.setCursor(3, 1);
    lcd.print("В");
    lcd.setCursor(5, 1);
    lcd.print(temp);//ПЕРЕМЕННАЯ
    lcd.setCursor(7, 1);
    lcd.print("РЮМ");
    lcd.setCursor(10, 1);
    lcd.print("КИ");//ПЕРЕМЕННАЯ "ОК"   "КУ"
	delay(3000);
	lcd.clear();
	// итог
    lcd.setCursor(0, 0);
    lcd.print("HАЛИТО  ПО");
    lcd.setCursor(11, 0);
    lcd.print(mll); //ПЕРЕМЕННАЯ
    lcd.setCursor(14, 0);
    lcd.print("мЛ");
    lcd.setCursor(3, 1);
    lcd.print("В");
    lcd.setCursor(5, 1);
    lcd.print(temp);//ПЕРЕМЕННАЯ
    lcd.setCursor(7, 1);
    lcd.print("РЮМ");
    lcd.setCursor(10, 1);
    lcd.print("КИ");//ПЕРЕМЕННАЯ 5 - "ОК", 1 - "КУ", 2..4 -"КИ"	
	delay(3000);
	lcd.clear();
	//
	
/*  void Tost() {
  randomSeed(currentTime);
   Рандом - 1
  switch (random(18)) { // 0...17  
  */
  //case 0:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА ВСТРЕЧУ!");
  delay(3000);
  lcd.clear();
  //case 1:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА КРАСОТУ!");   
  delay(3000);
  lcd.clear();
  //case 2:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");//
  lcd.setCursor(3, 1);
  lcd.print(L"ЗА ДРУЖБУ!");    
  delay(3000);
  lcd.clear();
  //case 3:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА БРАТСТВО!");    
  delay(3000);
  lcd.clear();
  //case 4:
  lcd.setCursor(5, 0);
  lcd.print(L"НУ, ЗА");
  lcd.setCursor(1, 1);
  lcd.print(L"СПРАВЕДЛИВОСТЬ!");
  delay(3000);
  lcd.clear();
  //case 5:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(3, 1);
  lcd.print(L"ЗА РЫБАЛКУ!");
  delay(3000);
  lcd.clear();
  //case 6:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА ИСКУССТВО!");
  delay(3000);
  lcd.clear();
  //case 7:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(3, 1);
  lcd.print(L"ЗА РАЗУМ!");
  delay(3000);
  lcd.clear();
  //case 8:
  lcd.setCursor(5, 0);
  lcd.print(L"НУ, ЗА");
  lcd.setCursor(0, 1);
  lcd.print(L"ИСТИННЫХ ЖЕНЩИН!!");
  delay(3000);
  lcd.clear();
  //case 9:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА ПОНИМАНИЕ!");
  delay(3000);
  lcd.clear();  
  //case 10:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(2, 1);
  lcd.print(L"ЗА ЕДИНЕНИЕ!");
  delay(3000);
  lcd.clear();
  //case 11:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(3, 1);
  lcd.print(L"ЗА ПОБЕДУ!");
  delay(3000);
  lcd.clear();  
  //case 12:
  lcd.setCursor(7, 0);
  lcd.print(L"НУ,");
  lcd.setCursor(3, 1);
  lcd.print(L"ЗА РОДИНУ!");
  delay(3000);
  lcd.clear(); 
  //case 13:
  lcd.setCursor(0, 0);
  lcd.print(L"НУ, ЧТОБ ГОЛОВА");
  lcd.setCursor(2, 1);
  lcd.print(L"НЕ ТРЕЩАЛА!");
  delay(3000);
  lcd.clear(); 
  //case 14:
  lcd.setCursor(0, 0);
  lcd.print("НУ, ЗА  СОЛИДНОЕ");//НУ,
  lcd.setCursor(0, 1);
  lcd.print("МУЖСКОЕ МОЛЧАНИЕ!");
  delay(3000);
  lcd.clear();
  //case 15:
  lcd.setCursor(0, 0);
  lcd.print(L"НУ, ЧТОБ МОРЩИЛО");
  lcd.setCursor(3, 1);
  lcd.print(L"НАС МЕНЬШЕ");
  delay(3000);
  lcd.clear();    
  //case 16:
  lcd.setCursor(0, 0);
  lcd.print(L"НУ,ЧТОБ В СТОРО-");
  lcd.setCursor(0, 1);
  lcd.print(L"НУ НЕ  ВИЛЬНУЛО!");
  delay(3000);
  lcd.clear();
  //case 17:
  lcd.setCursor(2, 0);
  lcd.print("НУ ВЫ БЛИН");
  lcd.setCursor(5, 1);
  lcd.print("ДАЁТЕ!");  
  delay(3000);
  lcd.clear();

}
/*
Скетч использует 8682 байт (28%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1171 байт (57%) динамической памяти, оставляя 877 байт для локальных переменных.
Максимум: 2048 байт.
*/

блок с тостами

  void Tost() {
  randomSeed(currentTime);
//Рандом - 1
  switch (random(18)) { // 0...17
	case 0: //ЗА ВСТРЕЧУ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА ВСТРЕЧУ!");
		mp3_play (2);  // Проигрываем "mp3/0002.mp3"
		delay(100);
    case 1: //ЗА КРАСОТУ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА КРАСОТУ!");   
		delay(4000);
		mp3_play (3);  // Проигрываем "mp3/0003.mp3"
		delay(100);
	case 2: //"ЗА ДРУЖБУ!"
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");//
		lcd.setCursor(3, 1);
		lcd.print(L"ЗА ДРУЖБУ!");  
		mp3_play (4);  // Проигрываем "mp3/0004.mp3"
		delay(100);
	case 3: //"ЗА БРАТСТВО!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА БРАТСТВО!");   
		mp3_play (5);  // Проигрываем "mp3/0005.mp3"
		delay(100);
	case 4: //ЗА СПРАВЕДЛИВОСТЬ!
		lcd.setCursor(5, 0);
		lcd.print(L"НУ, ЗА");
		lcd.setCursor(1, 1);
		lcd.print(L"СПРАВЕДЛИВОСТЬ!");
		mp3_play (6);  // Проигрываем "mp3/0006.mp3"11
		delay(100);
    case 5: //ЗА РЫБАЛКУ!
	lcd.setCursor(7, 0);
	lcd.print(L"НУ,");
	lcd.setCursor(3, 1);
	lcd.print(L"ЗА РЫБАЛКУ!");
		mp3_play (7);  // Проигрываем "mp3/0007.mp3"
		delay(100);
	case 6: //ЗА ИСКУССТВО!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА ИСКУССТВО!");
		mp3_play (8);  // Проигрываем "mp3/0008.mp3"
		delay(100);
	case 7: //ЗА РАЗУМ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(3, 1);
		lcd.print(L"ЗА РАЗУМ!");
		mp3_play (9);  // Проигрываем "mp3/0009.mp3"
		delay(100);
    break; 
	case 8: //ЗА ИСТИННЫХ ЖЕНЩИН!
		lcd.setCursor(5, 0);
		lcd.print(L"НУ, ЗА");
		lcd.setCursor(0, 1);
		lcd.print(L"ИСТИННЫХ ЖЕНЩИН!!");
		mp3_play (10);  // Проигрываем "mp3/0010.mp3"
		delay(100);
    break;
	case 9: //ЗА ПОНИМАНИЕ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА ПОНИМАНИЕ!");
		mp3_play (11);  // Проигрываем "mp3/0011.mp3"
		delay(100);
    break;
	case 10: //ЗА ЕДИНЕНИЕ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(2, 1);
		lcd.print(L"ЗА ЕДИНЕНИЕ!");
		mp3_play (13);  // Проигрываем "mp3/0013.mp3"
		delay(100);
    break;
	case 11: //ЗА ПОБЕДУ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(3, 1);
		lcd.print(L"ЗА ПОБЕДУ!");
		mp3_play (16);  // Проигрываем "mp3/0016.mp3"
		delay(100);
    break;
	case 12: //ЗА РОДИНУ!
		lcd.setCursor(7, 0);
		lcd.print(L"НУ,");
		lcd.setCursor(3, 1);
		lcd.print(L"ЗА РОДИНУ!");
		mp3_play (21);  // Проигрываем "mp3/0021.mp3"
		delay(100);
    break;
	case 13: //ЧТОБ ГОЛОВА НЕ ТРЕЩАЛА!
		lcd.setCursor(0, 0);
		lcd.print(L"НУ, ЧТОБ ГОЛОВА");
		lcd.setCursor(2, 1);
		lcd.print(L"НЕ ТРЕЩАЛА!");
		mp3_play (17);  // Проигрываем "mp3/0017.mp3"
		delay(100);
    break;
	case 14: //ЗА СОЛИДНОЕ МУЖСКОЕ МОЛЧАНИЕ
		lcd.setCursor(0, 0);
		lcd.print("НУ, ЗА  СОЛИДНОЕ");//НУ,
		lcd.setCursor(0, 1);
		lcd.print("МУЖСКОЕ МОЛЧАНИЕ!");
		mp3_play (12);  // Проигрываем "mp3/0012.mp3"
		delay(100);
    break;
	case 15: //ЧТОБ МОРЩИЛО НАС МЕНЬШЕ!
		lcd.setCursor(0, 0);
		lcd.print(L"НУ,ЧТОБЫ МОРЩИЛО");
		lcd.setCursor(3, 1);
		lcd.print(L"НАС МЕНЬШЕ ЧЕМ");
		mp3_play (18);  // Проигрываем "mp3/0018.mp3"
		delay(100);
    break;
	case 16: //ЧТОБ В СТОРОНУ НЕ ВИЛЬНУЛО!
		lcd.setCursor(0, 0);
		lcd.print(L"НУ,ЧТОБ В СТОРО-");
		lcd.setCursor(0, 1);
		lcd.print(L"НУ НЕ  ВИЛЬНУЛО!");
		mp3_play (19);  // Проигрываем "mp3/0019.mp3"
		delay(100);
    break; 
	case 17: //НУ ВЫ БЛИН ДАЁТЕ!
		lcd.setCursor(2, 0);
		lcd.print("НУ ВЫ БЛИН");
		lcd.setCursor(5, 1);
		lcd.print("ДАЁТЕ!");
		mp3_play (20);  // Проигрываем "mp3/0020.mp3"
		delay(100);	
    break;		
  }
  mp3_stop();
  lcd.clear();

}

  • Войдите на сайт для отправки комментариев

Пнд, 26/08/2019 — 20:44

#146

RW3

RW3 аватар

Offline

Зарегистрирован: 07.08.2019

что то тема заглохла….. Может схемы хватает? кто нибудь полностью привязал скетч к железу?

  • Войдите на сайт для отправки комментариев

Пнд, 26/08/2019 — 20:52

#147

stpavel

Offline

Зарегистрирован: 09.10.2018

RW3 пишет:

что то тема заглохла….. Может схемы хватает? кто нибудь полностью привязал скетч к железу?

А какие проблемы привязки скетча к железу ? Там же все расписано к какому пину что соеденять. 

  • Войдите на сайт для отправки комментариев

Пнд, 26/08/2019 — 21:05

#148

RW3

RW3 аватар

Offline

Зарегистрирован: 07.08.2019

это понятно, но в скетч вносятся коррективы . я пока жду комплектующие по этому ничего попробовать не могу.вот и спрашиваю может кто уже воплотил идею целиком . интересен результат

  • Войдите на сайт для отправки комментариев

Втр, 27/08/2019 — 11:05

#149

aleks_raichel

Offline

Зарегистрирован: 27.08.2019

День добрый. А не перерабатывали скетч под Вашу схему?

  • Войдите на сайт для отправки комментариев

Втр, 27/08/2019 — 12:38

#150

Forthomo

Forthomo аватар

Offline

Зарегистрирован: 10.04.2019

RW3 пишет:

что то тема заглохла….. Может схемы хватает? кто нибудь полностью привязал скетч к железу?

Замечания по предоставленной схеме:

питания от внутреннего стабилизатора не хватит для запитки DFPlayer и ленты.

DFPlayer —  подкдючение динамика SPK1(6)  SPK2 (8) (земля не используется).

паралельно конденсатору помпа включить диод (чтобы не палить контакты реле и если используется силовой ключ не спалить мосфет).

Описание в 182 сообщении.

То же жду посылку с Али с МП3, все остальное работает, осталось в культурный корпус запихнуть.

Сейчас буду собирать с ЛСД1602 дисплеем, внутренней ёмкостью на 500мЛ и тонким поворачивающимся краником.

  • Войдите на сайт для отправки комментариев
  • « первая
  • ‹ предыдущая
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • следующая ›
  • последняя »

 Всех приветствую. Очень меня затянула тема ARDUINO и 3D печати. Решил вот тоже разработать свою модель. Все метался с датчиками рюмок, остановился на герконах. Распечатал и собрал, каждый. С ИК датчиками сразу возникли проблемы, на улице уходили сразу в засвет, система пригодна только в домашних условиях. Пришли от китайцев микрики, сделал корпус под микрики, всё устраивает, но ! Но после недельной эксплуатации, каждый экземпляр вскрыл и обнаружил на шилде , что там не хило все окислилось ( хотя на работу это ни как не сказалось). Последствия пролива спиртного ( когда «принимающий уже на качерге). Зазоры в светодиодах, ик датчиках и микрухах, дали о себе знать. И тут осенило, надо поставить геркон, на рюмку сделать донышко 2мм с местом под магнит и закрыть стол, или стеклом или пленкой ПВХ ( прозрачной)

Вариант 1 ( ИК датчики) :

Авто бармен (Наливатор)

Авто бармен (Наливатор)

Может не те датчики брал, не знаю, но от этого варианта ушел.

Вариант 2 Микрухи:

Авто бармен (Наливатор)Вертикальные перемычки на панели поддержки для чистовой печати, т.к. панель съемная, под разные виды дисплеев.

1602

Авто бармен (Наливатор)

1637

Авто бармен (Наливатор)

Авто бармен (Наливатор)

Ну и окончательный вариант, Герконы

Авто бармен (Наливатор)

На столе выбрано место под стекло или пленку, проливы в таком исполнении не страшны. Герконы отрабатывают на ура ! И к стати, отказался от датчиков с герконами ( от китайцев), поставил только сами герконы, проблем в работе не вижу совсем.

Ну и как продолжение, решил исполнить всё в мобильной версии, пока только в разработке

Авто бармен (Наливатор)

Ну и набор деталей, используемых в проектах ( меняются только датчики)

Авто бармен (Наливатор)Версию в чумадане, хочу упаковать по полной….:)))))))))) .

Очень много почерпнул у Гайвера, Алекс ваще красава, куча благодарностей за его труды !

ЗЫ

До апреля. я вовсе не имел ( слышал вернее) понятия о , ARDUINO и  3D печать, и работу в редакторах объемного моделирования, с вилами кидался. Очень увлекло !

Понравилась статья? Поделить с друзьями:
  • Мазь флеминга при геморрое инструкция по применению цена
  • Где находится руководство оверлея стим
  • Шаг вперед руководство
  • Гербицид римлян вдг инструкция по применению
  • Мультиварка хоум элемент инструкция по применению пошагово