Автомат световых эффектов с продвинутым функционалом
- Войдите на сайт для отправки комментариев
Всем привет.
Имеются прямые руки, запас знаний по электронике, а также 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 Гц. Кто подскажет, где я накосячил? Уж больно хочется осуществить давнюю мечту...
Для понимания кладу визуалицацию (увеличить) и полностью закоменченный код.
// ВЕРСИЯ ДЛЯ UNO С МЕНЮ-ЛИБОЙ ЖКИ (printAt) #include <Wire.h> #include <EEPROM.h> #include <LiquidCrystal_I2C_Menu.h> LiquidCrystal_I2C_Menu lcd1(0x27, 20, 4); // ИНИЦИАЛИЗАЦИЯ ОСНОВНОГО ДИСПЛЕЯ LiquidCrystal_I2C_Menu lcd2(0x26, 16, 2); // ИНИЦИАЛИЗАЦИЯ КОНТРОЛЬНОГО ДИСПЛЕЯ #include <EncoderStepCounter.h> // БИБЛА ЭНКОДЕРА ЧЕРЕЗ ПРЕРЫВАНИЯ // МИГАНИЕ ЧЕРЕЗ МИЛЛИСЫ const int ledPin = 13; // номер выхода, подключенного к светодиоду int ledState = LOW; // этой переменной устанавливаем состояние светодиода long previousMillis = 0; // храним время последнего переключения светодиода // ====== ЭНКОДЕР ====== #define pinCLK 2 #define pinDT 3 #define pinSW 4 // ПРЕРЫВАНИЯ ЭНКОДЕРА #define ENCODER_INT0 digitalPinToInterrupt(pinCLK) #define ENCODER_INT1 digitalPinToInterrupt(pinDT) EncoderStepCounter encoder(pinCLK, pinDT); // Call tick on every change interrupt *** ТИК ЭНКОДЕРА void interrupt() { encoder.tick(); } // СТАРТОВЫЕ ЗНАЧЕНИЯ ЭЛЕМЕНТОВ МЕНЮ byte mainMenuPosition = 0; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ПУНКТА ГЛАВНОГО МЕНЮ byte mainMenuState = 9; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СОСТОЯНИЯ ГЛАВНОГО МЕНЮ (ДЕАКТИВАЦИЯ ВЫБРАННОГО ПУНКТА МЕНЮ) byte powerState = 90; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ЯРКОСТИ byte sceneNumber = 1; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СЦЕНЫ byte effectNumber = 1; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ПРОГРАММЫ БЕГУШКИ unsigned long speedNumber = 48; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ unsigned long speedDelay; // ЗНАЧЕНИЕ ТЕКУЩЕЙ ЗАДЕРЖКИ unsigned long minSpeedDelay = 500; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ unsigned long maxSpeedDelay = 50; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ byte debounce = 150; // ВРЕМЯ ЗАДЕРЖКИ ДЛЯ УСТРАНЕНИЯ ДРЕБЕЗГА void setup() { Wire.begin(); lcd1.begin(); lcd2.begin(); pinMode(ledPin, OUTPUT); // задаем режим выхода для порта, подключенного к светодиоду pinMode(pinDT, INPUT_PULLUP); pinMode(pinCLK, INPUT_PULLUP); pinMode(pinSW, INPUT_PULLUP); // ИНИЦИАЛИЗИРУЕМ ЭНКОДЕР И ЕГО ПРЕРЫВАНИЯ encoder.begin(); attachInterrupt(0, interrupt, CHANGE); attachInterrupt(1, interrupt, CHANGE); } void loop() { // МАПИМ ЗНАЧЕНИЯ С МЕНЮ В ЗНАЧЕНИЯ ДЛЯ ОБРАБОТКИ И ОГРАНИЧИВАЕМ ============== speedNumber = constrain(speedNumber, 1, 64); speedDelay = map(speedNumber, 1, 64, minSpeedDelay, maxSpeedDelay); mainMenuPosition = constrain(mainMenuPosition, 0, 7); sceneNumber = constrain(sceneNumber, 1, 16); effectNumber = constrain(effectNumber, 1, 64); powerState = constrain(powerState, 5, 99); // СОБСТВЕННО ТЕЛО ГЕНЕРАТОРА unsigned long currentMillis = millis(); // ПРОВЕРЯЕМ, НЕ ПРОШЕЛ ЛИ НУЖНЫЙ ИНТЕРВАЛ, ЕСЛИ ПРОШЕЛ, ТО if (currentMillis - previousMillis > speedDelay) { // СОХРАНЯЕМ ВРЕМЯ ПОСЛЕДНЕГО ПЕРЕКЛЮЧЕНИЯ previousMillis = currentMillis; // ЕСЛИ СВЕТОДИОД НЕ ГОРИТ, ТО ЗАЖИГАЕМ, И НАОБОРОТ if (ledState == LOW) ledState = !ledState; else ledState = !ledState; // УСТАНАВЛИВАЕМ СОСТОЯНИЯ ВЫХОДА, ЧТОБЫ ВКЛЮЧИТЬ ИЛИ ВЫКЛЮЧИТЬ СВЕТОДИОД digitalWrite(ledPin, ledState); } // ********** ГЛАВНОЕ МЕНЮ ********** lcd1.printAt(1, 0, "SCENE:"); lcd1.printAt(9, 0, sceneNumber); lcd1.printAt(1, 1, "EFFECT:"); lcd1.printAt(9, 1, effectNumber); lcd1.printAt(1, 2, "SPEED:"); lcd1.printAt(9, 2, speedNumber); lcd1.printAt(1, 3, "POWER:"); lcd1.printAt(9, 3, powerState); lcd1.printAt(15, 0, "SMART"); lcd1.printAt(15, 1, "DMX"); lcd1.printAt(15, 2, "LASER"); lcd1.printAt(15, 3, "SETUP"); // ВЫВОД ПРОМЕЖУТОЧНЫХ ДАННЫХ НА ВСПОМ. ДИСПЛЕЙ lcd2.printAt(0, 0, "MNU:"); lcd2.printAt(0, 1, "DLY:"); lcd2.printAt(5, 0, " "); lcd2.printAt(5, 0, mainMenuState); lcd2.printAt(5, 1, " "); lcd2.printAt(5, 1, speedDelay); if (mainMenuState == 9) { mainMenuSelector(); } else if (mainMenuState == 0) { sceneSelector(); } else if (mainMenuState == 1) { effectSelector(); } else if (mainMenuState == 2) { speedSelector(); } else if (mainMenuState == 3) { powerSelector(); } else if (mainMenuState == 4) { smartSubmenu(); } else if (mainMenuState == 5) { dmxSubmenu(); } else if (mainMenuState == 6) { laserSubMenu(); } else if (mainMenuState == 7) { setupSubMenu(); } } // ПОДПРОГРАММА ВЫБОРА ГЛАВНОГО МЕНЮ void mainMenuSelector() { signed char mmm = encoder.getPosition(); if (mmm != 0) { mainMenuPosition = mainMenuPosition + mmm; // ЗАЧИЩАЕМ СТАРЫЕ СТРЕЛКИ СЛЕВА lcd1.printAt(0, 0, " "); lcd1.printAt(0, 1, " "); lcd1.printAt(0, 2, " "); lcd1.printAt(0, 3, " "); lcd1.printAt(14, 0, " "); lcd1.printAt(14, 1, " "); lcd1.printAt(14, 2, " "); lcd1.printAt(14, 3, " "); encoder.reset(); } // СТРЕЛКА НА ЛЕВУЮ ПЕРЕКЛЮЧАЛКУ МЕНЮХИ if (mainMenuPosition == 0) { lcd1.printAt(0, 0, "\x7E"); } if (mainMenuPosition == 1) { lcd1.printAt(0, 1, "\x7E"); } if (mainMenuPosition == 2) { lcd1.printAt(0, 2, "\x7E"); } if (mainMenuPosition == 3) { lcd1.printAt(0, 3, "\x7E"); } if (mainMenuPosition == 4) { lcd1.printAt(14, 0, "\x7E"); } if (mainMenuPosition == 5) { lcd1.printAt(14, 1, "\x7E"); } if (mainMenuPosition == 6) { lcd1.printAt(14, 2, "\x7E"); } if (mainMenuPosition == 7) { lcd1.printAt(14, 3, "\x7E"); } // ЗАКРЕПЛЯЕМ ВЫБРАННЫЙ ПУНКТ МЕНЮ if (digitalRead(pinSW) == LOW) { delay(debounce); mainMenuState = mainMenuPosition; } } // ПОДПРОГРАММА ВЫБОРА СЦЕНЫ void sceneSelector() { lcd1.printAt(8, mainMenuState, "\x7E"); lcd1.printAt(8, 1, " "); lcd1.printAt(8, 2, " "); lcd1.printAt(8, 3, " "); signed char scn = encoder.getPosition(); if (scn != 0) { sceneNumber = sceneNumber + scn; encoder.reset(); lcd1.printAt(10, mainMenuState, " "); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } } // ПОДПРОГРАММА ВЫБОРА БЕГУШКИ void effectSelector() { lcd1.printAt(8, 0, " "); lcd1.printAt(8, 2, " "); lcd1.printAt(8, 3, " "); lcd1.printAt(8, mainMenuState, "\x7E"); signed char eff = encoder.getPosition(); if (eff != 0) { effectNumber = effectNumber + eff; encoder.reset(); lcd1.printAt(10, mainMenuState, " "); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } } // ПОДПРОГРАММА ВЫБОРА СКОРОСТИ void speedSelector() { lcd1.printAt(8, 0, " "); lcd1.printAt(8, 1, " "); lcd1.printAt(8, 3, " "); lcd1.printAt(8, mainMenuState, "\x7E"); signed char spd = encoder.getPosition(); if (spd != 0) { speedNumber = speedNumber + spd; encoder.reset(); lcd1.printAt(10, mainMenuState, " "); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } } // ПОДПРОГРАММА ВЫБОРА ЯРКОСТИ ===================================== void powerSelector() { lcd1.printAt(8, 0, " "); lcd1.printAt(8, 1, " "); lcd1.printAt(8, 2, " "); lcd1.printAt(8, mainMenuState, "\x7E"); signed char pwr = encoder.getPosition(); if (pwr != 0) { powerState = powerState + pwr; encoder.reset(); lcd1.printAt(10, mainMenuState, " "); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } } // ПОДПРОГРАММА РАБОТЫ С WS2812 ================================== (в работе) void smartSubmenu() { if (digitalRead(pinSW) == LOW) { delay(debounce); mainMenuState = 9; } } // ПОДПРОГРАММА РАБОТЫ С DMX ===================================== (в работе) void dmxSubmenu() { if (digitalRead(pinSW) == LOW) { delay(debounce); mainMenuState = 9; } } // ПОДПРОГРАММА РАБОТЫ С ЛАЗЕРАМИ ================================ (в работе) void laserSubMenu() { if (digitalRead(pinSW) == LOW) { delay(debounce); mainMenuState = 9; } } // ПОДПРОГРАММА РАБОТЫ С НАСТРОЙКАМИ ============================= (в работе) void setupSubMenu() { if (digitalRead(pinSW) == LOW) { delay(debounce); mainMenuState = 9; } }А вы побольше делеев накидайте и почаще на LCD выводите - ещё не те спецэффекты получите.
А делеи происходят только при нажатии кнопки энкодера. Я это учитываю и в данном случае они роли не играют, тем более что делеи происходят при двойном условии - вызова одной конкретной функции плюс нажатие кнопки (для дебонса). Сама генерация происходит без единого делея. И обновление информации на LCD происходит тоже только при изменении значения. По факту при "холостом" ходе ничего не мешает тактовому генератору работать
При изменении какого значения?
void loop() { // МАПИМ ЗНАЧЕНИЯ С МЕНЮ В ЗНАЧЕНИЯ ДЛЯ ОБРАБОТКИ И ОГРАНИЧИВАЕМ ============== speedNumber = constrain(speedNumber, 1, 64); speedDelay = map(speedNumber, 1, 64, minSpeedDelay, maxSpeedDelay); mainMenuPosition = constrain(mainMenuPosition, 0, 7); sceneNumber = constrain(sceneNumber, 1, 16); effectNumber = constrain(effectNumber, 1, 64); powerState = constrain(powerState, 5, 99); ... // ********** ГЛАВНОЕ МЕНЮ ********** lcd1.printAt(1, 0, "SCENE:"); lcd1.printAt(9, 0, sceneNumber); lcd1.printAt(1, 1, "EFFECT:"); lcd1.printAt(9, 1, effectNumber); lcd1.printAt(1, 2, "SPEED:"); lcd1.printAt(9, 2, speedNumber); lcd1.printAt(1, 3, "POWER:"); lcd1.printAt(9, 3, powerState); lcd1.printAt(15, 0, "SMART"); lcd1.printAt(15, 1, "DMX"); lcd1.printAt(15, 2, "LASER"); lcd1.printAt(15, 3, "SETUP"); ... }При изменении энкодером значения speenNumber происходит корректное изменение значения speedDelay (через map), это подтверждает вспомогательный LCD. Но генератор по-прежнему выдаёт какую-то ересь, хотя я ему к миллисам скармливаю корректное значение speedDelay:
Поубирал всё дополнительное и оставил только сам генератор с экранами и энкодером - всё просто идеально пашет:
#include <LiquidCrystal_I2C_Menu.h> LiquidCrystal_I2C_Menu lcd1(0x27, 20, 4); // ИНИЦИАЛИЗАЦИЯ ОСНОВНОГО ДИСПЛЕЯ LiquidCrystal_I2C_Menu lcd2(0x26, 16, 2); // ИНИЦИАЛИЗАЦИЯ КОНТРОЛЬНОГО ДИСПЛЕЯ #include <EncoderStepCounter.h> // БИБЛА ЭНКОДЕРА ЧЕРЕЗ ПРЕРЫВАНИЯ // МИГАНИЕ ЧЕРЕЗ МИЛЛИСЫ const int ledPin = 13; // номер выхода, подключенного к светодиоду int ledState = LOW; // этой переменной устанавливаем состояние светодиода long previousMillis = 0; // храним время последнего переключения светодиода // ====== ЭНКОДЕР ====== #define pinCLK 2 #define pinDT 3 #define pinSW 4 // ПРЕРЫВАНИЯ ЭНКОДЕРА #define ENCODER_INT0 digitalPinToInterrupt(pinCLK) #define ENCODER_INT1 digitalPinToInterrupt(pinDT) EncoderStepCounter encoder(pinCLK, pinDT); // Call tick on every change interrupt *** ТИК ЭНКОДЕРА void interrupt() { encoder.tick(); } // СТАРТОВЫЕ ЗНАЧЕНИЯ ЭЛЕМЕНТОВ МЕНЮ long speedNumber = 32; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ long speedDelay; // ЗНАЧЕНИЕ ТЕКУЩЕЙ ЗАДЕРЖКИ long minSpeedDelay = 250; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ long maxSpeedDelay = 50; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ byte debounce = 150; // ВРЕМЯ ЗАДЕРЖКИ ДЛЯ УСТРАНЕНИЯ ДРЕБЕЗГА void setup() { lcd1.begin(); lcd2.begin(); pinMode(ledPin, OUTPUT); // задаем режим выхода для порта, подключенного к светодиоду // ПОДТЯГИВАЕМ ЭНКОДЕР pinMode(pinDT, INPUT_PULLUP); pinMode(pinCLK, INPUT_PULLUP); pinMode(pinSW, INPUT_PULLUP); // ИНИЦИАЛИЗИРУЕМ ЭНКОДЕР encoder.begin(); attachInterrupt(0, interrupt, CHANGE); attachInterrupt(1, interrupt, CHANGE); } void loop() { // МАПИМ ЗНАЧЕНИЯ СКОРОСТИ В ЗНАЧЕНИЕ ЗАДЕРЖКИ И ОГРАНИЧИВАЕМ speedNumber = constrain(speedNumber, 0, 64); speedDelay = map(speedNumber, 0, 64, minSpeedDelay, maxSpeedDelay); // СОБСТВЕННО ТЕЛО ГЕНЕРАТОРА unsigned long currentMillis = millis(); // ПРОВЕРЯЕМ НЕ ПРОШЕЛ ЛИ НУЖНЫЙ ИНТЕРВАЛ, ЕСЛИ ПРОШЕЛ ТО if (currentMillis - previousMillis > speedDelay) { // СОХРАНЯЕМ ВРЕМЯ ПОСЛЕДНЕГО ПЕРЕКЛЮЧЕНИЯ previousMillis = currentMillis; // ЕСЛИ СВЕТОДИОД НЕ ГОРИТ, ТО ЗАЖИГАЕМ, И НАОБОРОТ if (ledState == LOW) ledState = !ledState; else ledState = !ledState; // УСТАНАВЛИВАЕМ СОСТОЯНИЯ ВЫХОДА, ЧТОБЫ ВКЛЮЧИТЬ ИЛИ ВЫКЛЮЧИТЬ СВЕТОДИОД digitalWrite(ledPin, ledState); } speedSelector(); lcd1.printAt(0,0,speedNumber); lcd1.printAt(4,0,"/"); lcd1.printAt(6,0,speedDelay); } // ПОДПРОГРАММА ВЫБОРА СКОРОСТИ void speedSelector() { signed char spd = encoder.getPosition(); if (spd != 0) { speedNumber = speedNumber + spd; encoder.reset(); } }"И обновление информации на LCD происходит тоже только при изменении значения."
Строку в своем коде покажите, где "только при изменении значения"
Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с Due (88 МГц тактовая) на хиленькую Uno - разницы никакой. Неужели Due не справилась бы?
UPD Согласен, правильнее было бы в функциях сделать так:
// ПОДПРОГРАММА ВЫБОРА СКОРОСТИ void speedSelector() { signed char spd = encoder.getPosition(); if (spd != 0) { lcd1.printAt(8, 0, " "); lcd1.printAt(8, 1, " "); lcd1.printAt(8, 3, " "); lcd1.printAt(8, mainMenuState, "\x7E"); speedNumber = speedNumber + spd; encoder.reset(); lcd1.printAt(10, mainMenuState, " "); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } }Чтобы только при изменении данных продёргивался LCD, за это отдельное спасибо. Но ситуация не изменилась.
Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с Due (88 МГц тактовая) на хиленькую Uno - разницы никакой
вы совсем ничего не соображаете? В коде #5 ничего никуда не вынесено, меню запускается каждый проход loop. Вывод на экран - медленное дело и скорость тут определяется скоростью экрана, поэтому Дуе и Уно будут тормозить одинаково.
Кто придумал взять задержку антидребезга 150 мс? Плюньте ему в рожу, он идиот
Блин, согласен. Тогда как же вывести на экран одноразово? В Сетапе это не сделать, т.к. те пункты меню, которые справа (Smart, Laser, DMX и Setup) будут "нырять" в другие экраны. Куда они потом вернутся? Вынести все принты главного экрана в каждую функцию? Или в PROGMEM, а потом выдёргивать при необходимости?
У меня энкодер под руками старый был, кнопа дребезжит аж верещит. Поэтому экспериментально подобрал, и вынес в сетап. У нас карантин везде, не купить сейчас
Прогмем тут не поможет никак. Отслеживайте реальное изменение отображаемого на экране параметра. If (oldValue != newValue) { lcd... }
Тогда как главный экран вывести однократно при запуске вне основного лупа? И при необходимости снова к нему вернуться?
Подумайте, как заставить оператор if() сработать по дополнительному условию. Разработчик тут Вы, а не мы.
Я это понимаю. Но я ранее писал:
За подсказку с тормозами LCD огромное спасибо. Но я сейчас в ступоре, поэтому прошу помощи. Пытался использовать готовые библиотеки менюшек - но все они занимают весь луп, и генератор ваще не работает в них. Наверняка есть простой способ, который уже давно до меня придумали. Я не могу понять, как выплёвывать на экран информацию однократно, но с возможностью возврата (если ныряю вглубь меню и надо вынырнуть).
Пронумеруйте все менюшки (экраны) и заведите флаг обновления. Сделайте небольшую подпрограммку, которая будет в лупе смотреть за флагом. Если флаг взведен, то она будет отрисовывать ту менюшку, номер которой задан в соответствующей переменной. Отрисовала, флаг сбросила. Все последующие циклы при сброшенном флаге будут пулей пролетать.
Внешнее воздействие, которое будет требовать вызова нового меню, должно будет задать номер этого меню и взвести флаг отрисовки.
Главный экран вынес в отдельную функцию, запросил её в Сетапе. Вроде всё заработало, осталось разобраться с отрисовкой значений при их смене (косяки вылезли). Из внутренних экранов (меню) буду выныривать снова в функцию Главного экрана.
Спасибо!
Наверняка есть простой способ, который уже давно до меня придумали.
В одном коротком утверждении сразу две ошибки.
Во-первых "все придумали до нас" работает только в простейших случаях. Если бы это было так, то было бы вообще бессмысленно что-то пытаться сделать - ведь уже все сделано. Ну и, кроме того, учитывая огромное количество частных случаев, все равно, если бы решение и существовало раньше, найти его было бы дольше, чем придумать самому.
И во-вторых, "простой способ" существует далеко не всегда. Например, мне как-то понадобилось добиться, чтобы задержки от вывода на экран ни в каком случае не превосходили 0.3 мс. Желаемого удалось добиться, лишь хорошо поработав сразу по двум направлениям:
- сократить время вывода одного символа - удалось ускорить раз в 15 по сравнению со стандартной библиотекой,
- организовать вывод так, чтобы за один раз выводилось не более одного символа.
Увы, "простым способом" ни первого, ни второго не добиться.
Никогда ыб не подумал, что вывод на экран может НАСТОЛЬКО тормозить. Век живи - век учись. И огромное спасибо b707 за эту инфу. Ну, и всем остальным тоже огромное спасибо!
Тем более что флаги для обновления данных у меня уже прописаны - это изменение состояния энкодера в каждой функции. Достаточно было однократно вывести инфу в буфер LCD главного экрана. Что я уж только не придумывал... В самом начале лупа делал проверку с миллисами, чтобы однократно вывести главное меню, пока не понял, что можно отрисовку главного меню тоже вынести в отдельную функцию, изначально загрузить её в Сетапе, а потом можно нырять и выныривать из подменю как угодно...
Вот так уже всё корректно работает, обновляя экран только при изменении значения (хотя и не претендую на лучший код):
// ПОДПРОГРАММА ВЫБОРА СКОРОСТИ void speedSelector() { lcd1.printAt(8, 0, " "); lcd1.printAt(8, 1, " "); lcd1.printAt(8, 3, " "); lcd1.printAt(8, mainMenuState, "\x7E"); signed char spd = encoder.getPosition(); if (spd != 0) { speedNumber = speedNumber + spd; lcd1.printAt(9, 2, speedNumber); if (speedNumber < 10) { lcd1.printAt(10, mainMenuState, " "); } encoder.reset(); } if (digitalRead(pinSW) == LOW) { delay(debounce); lcd1.printAt(8, mainMenuState, " "); mainMenuState = 9; } }Немного ускорить вывод можно перейдя с I2C на параллельное подключение дисплея, например..
Итак, все прошлые трудности осилены. Теперь новый ступор.
Имеем:
1. На 20-м пине Ардуины чёткий строб регулируемой частоты с заполнением 50% (это будет тактовый генератор).
2. Аналоговые пины Ардуины со 2 по 13 включены как wpm выходы (подключены, для примера, к светодиодам, в дальнейшем - к симисторным ШИМ-драйверам).
3. Переменную powerPWM с управляемыми значениями от 22 до 4096 (от 22, чтобы спирали ламп накаливания постоянно были нагретыми).
4. Группу массивов с записанными значениями, где каждая строка - это состояния аналоговых выходов на каждом такте. Соответственно, сколько тактов (шагов) у эффекта - столько строк. Для примера приведён только один массив, обычный "бегущий огонь" на 12 каналов. Для большего визуального удобства пропишем значения в массиве в виде 1 (включено) и 0 (выключено), в дальнейшем преобразуем в числа, используя map.
if effectNumber == 1 prog1(); void prog1() { byte effect1[12][12] { (1,0,0,0,0,0,0,0,0,0,0,0), // шаг 1 (0,1,0,0,0,0,0,0,0,0,0,0), // шаг 2 (0,0,1,0,0,0,0,0,0,0,0,0), // шаг 3 (0,0,0,1,0,0,0,0,0,0,0,0), // шаг 4 (0,0,0,0,1,0,0,0,0,0,0,0), // шаг 5 (0,0,0,0,0,1,0,0,0,0,0,0), // шаг 6 (0,0,0,0,0,0,1,0,0,0,0,0), // шаг 7 (0,0,0,0,0,0,0,1,0,0,0,0), // шаг 8 (0,0,0,0,0,0,0,0,1,0,0,0), // шаг 9 (0,0,0,0,0,0,0,0,0,1,0,0), // шаг 10 (0,0,0,0,0,0,0,0,0,0,1,0), // шаг 11 (0,0,0,0,0,0,0,0,0,0,0,1) // шаг 12 } for (byte i = 2; i <= 13; i++) { // номера пинов Ардуины со 2 по 12 analogWrite();И вот теперь нужно по команде от тактового генератора считать из массива данные и закинуть на аналоговые выходы. Таким образом, никаких задержек делать не нужно, т.к. тактовый генератор запилен на миллисах. Первый такт генератора - считываем и записываем в пины 1-ю строку массива, второй импульс генератора - вторую строку, и т.д... И сделать зацикливание, чтобы после прохождения 12-го шага опять вернуться к первому. Вот на этом месте ступор наступил, в трёх соснах блуждаю сутки :(
в чем блуждаете-то, я не понял
в чем блуждаете-то, я не понял
как сделать защёлку. То есть как производить переключения по событию от тактового генератора. Понимаю, что надо читать 20-ю ногу, но если я её буду считывать с тактовой частотой Ардуины, то за один импульс генератора считаю первую единицу миллионы раз, а мне нужно только регистрировать смену события (без разницы по фронту или спаду), считать нужную строку массива и перезаписать состояние пинов, и ждать следующего события.
А проблема в чем - не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?
не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?
Именно. Как заставить ардуину ждать до следующего изменения без зацикливания
Это прискорбно. Думаю, что тема не для раздела "программирование".
Именно. Как заставить ардуину ждать до следующего изменения без зацикливания
А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту
А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту
вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему
вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему
Вот, блин, это ж как обычная триггерная кнопка! Сенкс!
Ну ка получился автомат эффектов?
Ну ка получился автомат эффектов?
основа только заложена, сейчас с ШИМ-ом разбираюсь, потом ещё в работе dmx и ws2812.
Хех... Снова в трёх соснах блуждаю... Начал писать простейшие эффекты для бегушки из 12 каналов, бля начала без ШИМа... Пока не дошёл до многошагового, где и споткнулся. Ткните носом, где косяк? Первый код с комментариями, остальные по аналогии.
Работает:
// ОГНИ БЕГУТ К ЦЕНТРУ БЕЗ РЕВЕРСА boolean flag = 0; int x = 0; int y = 0; void effect1Loop() { boolean effect1[6][12]{ // МАССИВ ИЗ 6 ШАГОВ БЕГУШКИ И 12 СВЕТОДИОДОВ НА ВЫХОДЕ {1,0,0,0,0,0,0,0,0,0,0,1}, // ШАГ 1 {0,1,0,0,0,0,0,0,0,0,1,0}, // ШАГ 2 {0,0,1,0,0,0,0,0,0,1,0,0}, // ШАГ 3 {0,0,0,1,0,0,0,0,1,0,0,0}, // ШАГ 4 {0,0,0,0,1,0,0,1,0,0,0,0}, // ШАГ 5 {0,0,0,0,0,1,1,0,0,0,0,0}, // ШАГ 6 }; if (digitalRead(ledPin) == HIGH && flag == 0) { // НА НОГЕ LEDPIN - ТАКТОВЫЙ ГЕНЕРАТОР, ЧИТАЕМ ЕГО for (x = 2; x <= 13; x++) { // НОМЕРА ПИНОВ СО 2 ПО 13 int k = effect1[y][x-2]; // ЧИТАЕМ И ПИШЕМ В К ЗНАЧЕНИЕ ИЗ МАССИВА digitalWrite(x,k); // ПИШЕМ В НУЖНЫЙ ПИН ПОЛУЧЕННОЕ } y++; if (y > 5) y = 0; // ОБНУЛЯЕМ СЧЁТЧИК ШАГОВ flag = 1; // ТРИГГЕР ДЛЯ РАБОТЫ С ВНЕШНИМ ТАКТОВЫМ ГЕНЕРАТОРОМ } if (digitalRead(ledPin) == LOW && flag == 1) flag = !flag; }Работает:
// ОГНИ БЕГУТ К ЦЕНТРУ С РЕВЕРСОМ void effect2Loop() { boolean effect2[10][12]{ {1,0,0,0,0,0,0,0,0,0,0,1}, // ШАГ 1 {0,1,0,0,0,0,0,0,0,0,1,0}, // ШАГ 2 {0,0,1,0,0,0,0,0,0,1,0,0}, // ШАГ 3 {0,0,0,1,0,0,0,0,1,0,0,0}, // ШАГ 4 {0,0,0,0,1,0,0,1,0,0,0,0}, // ШАГ 5 {0,0,0,0,0,1,1,0,0,0,0,0}, // ШАГ 6 {0,0,0,0,1,0,0,1,0,0,0,0}, // ШАГ 7 {0,0,0,1,0,0,0,0,1,0,0,0}, // ШАГ 8 {0,0,1,0,0,0,0,0,0,1,0,0}, // ШАГ 9 {0,1,0,0,0,0,0,0,0,0,1,0}, // ШАГ 10 }; if (digitalRead(ledPin) == HIGH && flag == 0) { for (x = 2; x <= 13; x++) { int k = effect2[y][x-2]; digitalWrite(x,k); } y++; if (y > 9) y = 0; flag = 1; } if (digitalRead(ledPin) == LOW && flag == 1) flag = !flag; }Работает:
// БЕГУЩИЙ ПО КРУГУ ОГОНЬ БЕЗ РЕВЕРСА void effect5Loop() { boolean effect5[12][12]{ {1,0,0,0,0,0,0,0,0,0,0,0}, // ШАГ 1 {0,1,0,0,0,0,0,0,0,0,0,0}, // ШАГ 2 {0,0,1,0,0,0,0,0,0,0,0,0}, // ШАГ 3 {0,0,0,1,0,0,0,0,0,0,0,0}, // ШАГ 4 {0,0,0,0,1,0,0,0,0,0,0,0}, // ШАГ 5 {0,0,0,0,0,1,0,0,0,0,0,0}, // ШАГ 6 {0,0,0,0,0,0,1,0,0,0,0,0}, // ШАГ 7 {0,0,0,0,0,0,0,1,0,0,0,0}, // ШАГ 8 {0,0,0,0,0,0,0,0,1,0,0,0}, // ШАГ 9 {0,0,0,0,0,0,0,0,0,1,0,0}, // ШАГ 10 {0,0,0,0,0,0,0,0,0,0,1,0}, // ШАГ 11 {0,0,0,0,0,0,0,0,0,0,0,1} // ШАГ 12 }; if (digitalRead(ledPin) == HIGH && flag == 0) { for (x = 2; x <= 13; x++) { int k = effect5[x-2][y]; digitalWrite(x,k); } y++; if (y > 11) y = 0; flag = 1; } if (digitalRead(ledPin) == LOW && flag == 1) flag = !flag; }Сабака, не работает:
// БЕГУЩИЙ ПО КРУГУ ОГОНЬ С РЕВЕРСОМ void effect6Loop() { boolean effect6[22][12]{ {1,0,0,0,0,0,0,0,0,0,0,0}, // ШАГ 1 {0,1,0,0,0,0,0,0,0,0,0,0}, // ШАГ 2 {0,0,1,0,0,0,0,0,0,0,0,0}, // ШАГ 3 {0,0,0,1,0,0,0,0,0,0,0,0}, // ШАГ 4 {0,0,0,0,1,0,0,0,0,0,0,0}, // ШАГ 5 {0,0,0,0,0,1,0,0,0,0,0,0}, // ШАГ 6 {0,0,0,0,0,0,1,0,0,0,0,0}, // ШАГ 7 {0,0,0,0,0,0,0,1,0,0,0,0}, // ШАГ 8 {0,0,0,0,0,0,0,0,1,0,0,0}, // ШАГ 9 {0,0,0,0,0,0,0,0,0,1,0,0}, // ШАГ 10 {0,0,0,0,0,0,0,0,0,0,1,0}, // ШАГ 11 {0,0,0,0,0,0,0,0,0,0,0,1}, // ШАГ 12 {0,0,0,0,0,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,0,0,1,0,0}, {0,0,0,0,0,0,0,0,1,0,0,0}, {0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,1,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0}, {0,0,0,1,0,0,0,0,0,0,0,0}, {0,0,1,0,0,0,0,0,0,0,0,0}, {0,1,0,0,0,0,0,0,0,0,0,0} }; if (digitalRead(ledPin) == HIGH && flag == 0) { for (x = 2; x <= 13; x++) { int k = effect6[x-2][y]; digitalWrite(x,k); } y++; if (y > 21) y = 0; flag = 1; } if (digitalRead(ledPin) == LOW && flag == 1) flag = !flag; }UPD Оно работает, но вместо реверса проходит сначала полный цикл в одну сторону, а потом в эту же сторону до 9 канала и обнуляется. Что не так?
X и Y путать не надо.
йЕС! Сенкс!
Наверняка, где не работает?
Строка 29 в последнем коде...
Должно быть
наверное лучше так
boolean ledState=0; // СОБСТВЕННО ТЕЛО ГЕНЕРАТОРА unsigned long currentMillis = millis(); // ПРОВЕРЯЕМ НЕ ПРОШЕЛ ЛИ НУЖНЫЙ ИНТЕРВАЛ, ЕСЛИ ПРОШЕЛ ТО if (currentMillis - previousMillis > speedNumber) { // СОХРАНЯЕМ ВРЕМЯ ПОСЛЕДНЕГО ПЕРЕКЛЮЧЕНИЯ previousMillis = currentMillis; // ЕСЛИ СВЕТОДИОД НЕ ГОРИТ, ТО ЗАЖИГАЕМ, И НАОБОРОТ ledState = !ledState;} //если нужен digitalWrite(ledPin, ledState); // БЕГУЩИЙ ПО КРУГУ ОГОНЬ С РЕВЕРСОМ void effect6Loop() { boolean effect6[22][12]{ {1,0,0,0,0,0,0,0,0,0,0,0}, // ШАГ 1 {0,1,0,0,0,0,0,0,0,0,0,0}, // ШАГ 2 {0,0,1,0,0,0,0,0,0,0,0,0}, // ШАГ 3 {0,0,0,1,0,0,0,0,0,0,0,0}, // ШАГ 4 {0,0,0,0,1,0,0,0,0,0,0,0}, // ШАГ 5 {0,0,0,0,0,1,0,0,0,0,0,0}, // ШАГ 6 {0,0,0,0,0,0,1,0,0,0,0,0}, // ШАГ 7 {0,0,0,0,0,0,0,1,0,0,0,0}, // ШАГ 8 {0,0,0,0,0,0,0,0,1,0,0,0}, // ШАГ 9 {0,0,0,0,0,0,0,0,0,1,0,0}, // ШАГ 10 {0,0,0,0,0,0,0,0,0,0,1,0}, // ШАГ 11 {0,0,0,0,0,0,0,0,0,0,0,1}, // ШАГ 12 {0,0,0,0,0,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,0,0,1,0,0}, {0,0,0,0,0,0,0,0,1,0,0,0}, {0,0,0,0,0,0,0,1,0,0,0,0}, {0,0,0,0,0,0,1,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0,0,0}, {0,0,0,0,1,0,0,0,0,0,0,0}, {0,0,0,1,0,0,0,0,0,0,0,0}, {0,0,1,0,0,0,0,0,0,0,0,0}, {0,1,0,0,0,0,0,0,0,0,0,0} }; if (ledState == 1 && flag == false) { for (x = 2; x <= 13; x++) { int k = effect6[y][x-2]; digitalWrite(x,k); } y++; if (y > 21) y = 0; flag = true; } if (ledState == 0 && flag == true) flag = false; }if (ledState == 1 && flag == false) { for (x = 2; x <= 13; x++) { int k = effect6[y][x-2]; if (k == 1) k=powerState; analogWrite(x,k);и ШИМ
наверное лучше так
Ну да, полаконичнее будет. Я не претендую на красивость кода - начал писать только благодаря карантину... Так что всё из головы, а там пока что каша ))
и ШИМ
О, а я как раз начал копаться с ним, мудрить по-всякому, а ларчик просто открывался!
mrtester, а есть идея как реализовать плавное перетекание с помощью ШИМа? А то я тут такого нагородил... Оно ещё и виснет ((
выход на 220 вольт?
проще на выходах сделать
выход на 220 вольт?
Пока на led, в отладке. Но буду делать на симисторах с опторазвязкой.
проще на выходах сделать
Как на выходах??? Даже сути не уловил
https://www.chipdip.ru/product/k1182pm1r