Автомат световых эффектов с продвинутым функционалом
- Войдите на сайт для отправки комментариев
Всем привет.
Имеются прямые руки, запас знаний по электронике, а также Arduino Due, текстовый ЖКИ с "квадратной" i2c шиной (тут, слава Богу, разобрался), 16-канальная плата ШИМ расширения выходов по i2c, энкодер (с ним мы тоже подружились), светодиоды, кнопки, ну, и желание СДЕЛАТЬ ЭТО. По факту должен получиться неплохой автомат типа "бегущих огней" с весьма расширенными возможостями.
BEGIN // Я знаком с программингом, но не Wire и не С++, поэтому за какие-то элементарные (на ваш взгляд) вопросы сильно не пинайте. Сразу объявлю константу, что перед каждым вопросом был поиск в гугеле с невнятными или отрицательными результатами. И ещё одну: я не прошу написать для меня код. Мне нужно понять, как составить чёткую последовательность действий, и разобраться с не задокументированными фичами.
OUTPUT // На 16 ШИМ-выходах платы имею желание получать попеременные наборы сигналов (будем выбирать их из массива) с частотой переключения наборов, регулируемой энкодером. Как управлять скоростью переключения (через энкодер) без delay - пока ума не приложу.
Каждый массив - это "программа" бегущего огня.
Для простоты возьмём не 16, а 4 ШИМ выхода. Тогда массив бегущего в одну сторону огня будет выглядеть так:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
"Бегущая тень" - соответственно, инверсия предыдущего массива:
0 1 1 1
1 0 1 1
1 1 0 1
1 1 1 0
Когда массив заканчивается - возвращаемся снова к первой строке. То есть бесконечный цикл, пока мы кнопкой или энкодером (через прерывание) не подсунем основному лупу другой массив. Базовый набор массивов будет "вшит" жёстко, но будет ещё возможность пользовательской записи "пресетов", управляемой энкодером и контролируемой на ЖКИ (2004). Область записи пресетов - EEPROM (ну, или SD - жизнь покажет).
Для чего ШИМ?
Ну, во-первых, эконом-вариант расширения пинов по i2c. Во-вторых, возможность регулировки общей и поканальной яркости. В-третьих, возможность создавать, к примеру, "бегущую искру" - т.е. каждый выход впыхивает, плавно гаснет, затем переключается на следующий шаг.
Теперь, собственно, основной вопрос на данный момент *(как бы базовый). Есть серии импульсов, завёрнутые в массивы. Как правильно нужно сформировать интервал между ними (регулировка энкодером, значения которого будут например от 0 до 64, при этом каждому значению энкодера можно было бы подбирать чёткую визуальную частоту), не прибегая к прямым задержкам (delay)? То есть сформировать шаг между бегущими огнями? Понимаю, что надо что-то делать с миллисами, но как - не могу понять... Подтолкните плз!
На due можно поставить rtos и работать с делеями спокойно.
Не понял, каким боком к готовым проектам, пока отправил в "Программирование".
Понимаю, что надо что-то делать с миллисами, но как - не могу понять... Подтолкните плз!
пример "блинк без делей" изучили? попробуйте применить к своему кодк (эффекты на ленту упростите до предела на первом этапе). Код, что получится - рабочий или нет - выкладывайте сюда, будем разбираться.
А читать вам лекцию про миллис без практических занятий - бессмысленно, это будет "обучение игре на баяне без баяна"
Ну кстати вот, в due есть даже родной какой-то недоделанный костыль
https://www.arduino.cc/en/Tutorial/MultipleBlinks
Верно, без баяна хреново. Разобрался с миллисами, с блинком без дилэя на практике. Только теперь если лепить голый тактовый генератор - он работает. А в купе с другими регулировками беспощадно врёт. На контрольной консоли отслеживаю значение задержки (в миллисекундах) - показывает 50 мс. А реально на светодиод выводит 2Гц. Меняю минимальное значение задержки до 1 мс (контролько подтверждает) - получаю 2.3 Гц. Кто подскажет, где я накосячил? Уж больно хочется осуществить давнюю мечту...
Для понимания кладу визуалицацию (увеличить) и полностью закоменченный код.
А вы побольше делеев накидайте и почаще на LCD выводите - ещё не те спецэффекты получите.
А делеи происходят только при нажатии кнопки энкодера. Я это учитываю и в данном случае они роли не играют, тем более что делеи происходят при двойном условии - вызова одной конкретной функции плюс нажатие кнопки (для дебонса). Сама генерация происходит без единого делея. И обновление информации на LCD происходит тоже только при изменении значения. По факту при "холостом" ходе ничего не мешает тактовому генератору работать
При изменении какого значения?
При изменении энкодером значения speenNumber происходит корректное изменение значения speedDelay (через map), это подтверждает вспомогательный LCD. Но генератор по-прежнему выдаёт какую-то ересь, хотя я ему к миллисам скармливаю корректное значение speedDelay:
Поубирал всё дополнительное и оставил только сам генератор с экранами и энкодером - всё просто идеально пашет:
"И обновление информации на LCD происходит тоже только при изменении значения."
Строку в своем коде покажите, где "только при изменении значения"
Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с Due (88 МГц тактовая) на хиленькую Uno - разницы никакой. Неужели Due не справилась бы?
UPD Согласен, правильнее было бы в функциях сделать так:
Чтобы только при изменении данных продёргивался LCD, за это отдельное спасибо. Но ситуация не изменилась.
Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с Due (88 МГц тактовая) на хиленькую Uno - разницы никакой
вы совсем ничего не соображаете? В коде #5 ничего никуда не вынесено, меню запускается каждый проход loop. Вывод на экран - медленное дело и скорость тут определяется скоростью экрана, поэтому Дуе и Уно будут тормозить одинаково.
Кто придумал взять задержку антидребезга 150 мс? Плюньте ему в рожу, он идиот
Блин, согласен. Тогда как же вывести на экран одноразово? В Сетапе это не сделать, т.к. те пункты меню, которые справа (Smart, Laser, DMX и Setup) будут "нырять" в другие экраны. Куда они потом вернутся? Вынести все принты главного экрана в каждую функцию? Или в PROGMEM, а потом выдёргивать при необходимости?
У меня энкодер под руками старый был, кнопа дребезжит аж верещит. Поэтому экспериментально подобрал, и вынес в сетап. У нас карантин везде, не купить сейчас
Прогмем тут не поможет никак. Отслеживайте реальное изменение отображаемого на экране параметра. If (oldValue != newValue) { lcd... }
Тогда как главный экран вывести однократно при запуске вне основного лупа? И при необходимости снова к нему вернуться?
Подумайте, как заставить оператор if() сработать по дополнительному условию. Разработчик тут Вы, а не мы.
Я это понимаю. Но я ранее писал:
За подсказку с тормозами LCD огромное спасибо. Но я сейчас в ступоре, поэтому прошу помощи. Пытался использовать готовые библиотеки менюшек - но все они занимают весь луп, и генератор ваще не работает в них. Наверняка есть простой способ, который уже давно до меня придумали. Я не могу понять, как выплёвывать на экран информацию однократно, но с возможностью возврата (если ныряю вглубь меню и надо вынырнуть).
Пронумеруйте все менюшки (экраны) и заведите флаг обновления. Сделайте небольшую подпрограммку, которая будет в лупе смотреть за флагом. Если флаг взведен, то она будет отрисовывать ту менюшку, номер которой задан в соответствующей переменной. Отрисовала, флаг сбросила. Все последующие циклы при сброшенном флаге будут пулей пролетать.
Внешнее воздействие, которое будет требовать вызова нового меню, должно будет задать номер этого меню и взвести флаг отрисовки.
Главный экран вынес в отдельную функцию, запросил её в Сетапе. Вроде всё заработало, осталось разобраться с отрисовкой значений при их смене (косяки вылезли). Из внутренних экранов (меню) буду выныривать снова в функцию Главного экрана.
Спасибо!
Наверняка есть простой способ, который уже давно до меня придумали.
В одном коротком утверждении сразу две ошибки.
Во-первых "все придумали до нас" работает только в простейших случаях. Если бы это было так, то было бы вообще бессмысленно что-то пытаться сделать - ведь уже все сделано. Ну и, кроме того, учитывая огромное количество частных случаев, все равно, если бы решение и существовало раньше, найти его было бы дольше, чем придумать самому.
И во-вторых, "простой способ" существует далеко не всегда. Например, мне как-то понадобилось добиться, чтобы задержки от вывода на экран ни в каком случае не превосходили 0.3 мс. Желаемого удалось добиться, лишь хорошо поработав сразу по двум направлениям:
- сократить время вывода одного символа - удалось ускорить раз в 15 по сравнению со стандартной библиотекой,
- организовать вывод так, чтобы за один раз выводилось не более одного символа.
Увы, "простым способом" ни первого, ни второго не добиться.
Никогда ыб не подумал, что вывод на экран может НАСТОЛЬКО тормозить. Век живи - век учись. И огромное спасибо b707 за эту инфу. Ну, и всем остальным тоже огромное спасибо!
Тем более что флаги для обновления данных у меня уже прописаны - это изменение состояния энкодера в каждой функции. Достаточно было однократно вывести инфу в буфер LCD главного экрана. Что я уж только не придумывал... В самом начале лупа делал проверку с миллисами, чтобы однократно вывести главное меню, пока не понял, что можно отрисовку главного меню тоже вынести в отдельную функцию, изначально загрузить её в Сетапе, а потом можно нырять и выныривать из подменю как угодно...
Вот так уже всё корректно работает, обновляя экран только при изменении значения (хотя и не претендую на лучший код):
Немного ускорить вывод можно перейдя с I2C на параллельное подключение дисплея, например..
Итак, все прошлые трудности осилены. Теперь новый ступор.
Имеем:
1. На 20-м пине Ардуины чёткий строб регулируемой частоты с заполнением 50% (это будет тактовый генератор).
2. Аналоговые пины Ардуины со 2 по 13 включены как wpm выходы (подключены, для примера, к светодиодам, в дальнейшем - к симисторным ШИМ-драйверам).
3. Переменную powerPWM с управляемыми значениями от 22 до 4096 (от 22, чтобы спирали ламп накаливания постоянно были нагретыми).
4. Группу массивов с записанными значениями, где каждая строка - это состояния аналоговых выходов на каждом такте. Соответственно, сколько тактов (шагов) у эффекта - столько строк. Для примера приведён только один массив, обычный "бегущий огонь" на 12 каналов. Для большего визуального удобства пропишем значения в массиве в виде 1 (включено) и 0 (выключено), в дальнейшем преобразуем в числа, используя map.
И вот теперь нужно по команде от тактового генератора считать из массива данные и закинуть на аналоговые выходы. Таким образом, никаких задержек делать не нужно, т.к. тактовый генератор запилен на миллисах. Первый такт генератора - считываем и записываем в пины 1-ю строку массива, второй импульс генератора - вторую строку, и т.д... И сделать зацикливание, чтобы после прохождения 12-го шага опять вернуться к первому. Вот на этом месте ступор наступил, в трёх соснах блуждаю сутки :(
в чем блуждаете-то, я не понял
в чем блуждаете-то, я не понял
как сделать защёлку. То есть как производить переключения по событию от тактового генератора. Понимаю, что надо читать 20-ю ногу, но если я её буду считывать с тактовой частотой Ардуины, то за один импульс генератора считаю первую единицу миллионы раз, а мне нужно только регистрировать смену события (без разницы по фронту или спаду), считать нужную строку массива и перезаписать состояние пинов, и ждать следующего события.
А проблема в чем - не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?
не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?
Именно. Как заставить ардуину ждать до следующего изменения без зацикливания
Это прискорбно. Думаю, что тема не для раздела "программирование".
Именно. Как заставить ардуину ждать до следующего изменения без зацикливания
А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту
А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту
вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему
вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему
Вот, блин, это ж как обычная триггерная кнопка! Сенкс!
Ну ка получился автомат эффектов?
Ну ка получился автомат эффектов?
основа только заложена, сейчас с ШИМ-ом разбираюсь, потом ещё в работе dmx и ws2812.
Хех... Снова в трёх соснах блуждаю... Начал писать простейшие эффекты для бегушки из 12 каналов, бля начала без ШИМа... Пока не дошёл до многошагового, где и споткнулся. Ткните носом, где косяк? Первый код с комментариями, остальные по аналогии.
Работает:
Работает:
Работает:
Сабака, не работает:
UPD Оно работает, но вместо реверса проходит сначала полный цикл в одну сторону, а потом в эту же сторону до 9 канала и обнуляется. Что не так?
X и Y путать не надо.
йЕС! Сенкс!
Наверняка, где не работает?
Строка 29 в последнем коде...
Должно быть
наверное лучше так
и ШИМ
наверное лучше так
Ну да, полаконичнее будет. Я не претендую на красивость кода - начал писать только благодаря карантину... Так что всё из головы, а там пока что каша ))
и ШИМ
О, а я как раз начал копаться с ним, мудрить по-всякому, а ларчик просто открывался!
mrtester, а есть идея как реализовать плавное перетекание с помощью ШИМа? А то я тут такого нагородил... Оно ещё и виснет ((
выход на 220 вольт?
проще на выходах сделать
выход на 220 вольт?
Пока на led, в отладке. Но буду делать на симисторах с опторазвязкой.
проще на выходах сделать
Как на выходах??? Даже сути не уловил
https://www.chipdip.ru/product/k1182pm1r