Автомат световых эффектов с продвинутым функционалом

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Всем привет.

Имеются прямые руки, запас знаний по электронике, а также 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)? То есть сформировать шаг между бегущими огнями? Понимаю, что надо что-то делать с миллисами, но как - не могу понять... Подтолкните плз!

rkit
Offline
Зарегистрирован: 23.11.2016

На due можно поставить rtos и работать с делеями спокойно.

bwn
Offline
Зарегистрирован: 25.08.2014

Не понял, каким боком к готовым проектам, пока отправил в "Программирование".

b707
Offline
Зарегистрирован: 26.05.2017

Dorfman пишет:

Понимаю, что надо что-то делать с миллисами, но как - не могу понять... Подтолкните плз!

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

А читать вам лекцию про миллис без практических занятий - бессмысленно, это будет "обучение игре на баяне без баяна"

rkit
Offline
Зарегистрирован: 23.11.2016

Ну кстати вот, в due есть даже родной какой-то недоделанный костыль

https://www.arduino.cc/en/Tutorial/MultipleBlinks

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

b707 пишет:
А читать вам лекцию про миллис без практических занятий - бессмысленно, это будет "обучение игре на баяне без баяна"

Верно, без баяна хреново. Разобрался с миллисами, с блинком без дилэя на практике. Только теперь если лепить голый тактовый генератор - он работает. А в купе с другими регулировками беспощадно врёт. На контрольной консоли отслеживаю значение задержки (в миллисекундах) - показывает 50 мс. А реально на светодиод выводит 2Гц. Меняю минимальное значение задержки до 1 мс (контролько подтверждает) - получаю 2.3 Гц. Кто подскажет, где я накосячил? Уж больно хочется осуществить давнюю мечту...

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

for_arduforum.jpg



// ВЕРСИЯ ДЛЯ 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;
  }
}
sadman41
Offline
Зарегистрирован: 19.10.2016

А вы побольше делеев накидайте и почаще на LCD выводите - ещё не те спецэффекты получите.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

sadman41 пишет:
А вы побольше делеев накидайте и почаще на LCD выводите - ещё не те спецэффекты получите.

А делеи происходят только при нажатии кнопки энкодера. Я это учитываю и в данном случае они роли не играют, тем более что делеи происходят при двойном условии - вызова одной конкретной функции плюс нажатие кнопки (для дебонса). Сама генерация происходит без единого делея. И обновление информации на LCD происходит тоже только при изменении значения. По факту при "холостом" ходе ничего не мешает тактовому генератору работать

sadman41
Offline
Зарегистрирован: 19.10.2016

При изменении какого значения?

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");
...
}

 

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

При изменении энкодером значения speenNumber происходит корректное изменение значения speedDelay (через map), это подтверждает вспомогательный LCD. Но генератор по-прежнему выдаёт какую-то ересь, хотя я ему к миллисам скармливаю корректное значение speedDelay:

  if (currentMillis - previousMillis > speedDelay)

 

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Поубирал всё дополнительное и оставил только сам генератор с экранами и энкодером - всё просто идеально пашет:

#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();
  }
}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

"И обновление информации на LCD происходит тоже только при изменении значения."

Строку в своем коде покажите, где "только при изменении значения"

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с 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, за это отдельное спасибо. Но ситуация не изменилась.

b707
Offline
Зарегистрирован: 26.05.2017

Dorfman пишет:

Я вынес изменение инфы на экране в каждую соответствующую функцию, так что перечислять долго. Кроме того, я перенёс скетч с Due (88 МГц тактовая) на хиленькую Uno - разницы никакой


вы совсем ничего не соображаете? В коде #5 ничего никуда не вынесено, меню запускается каждый проход loop. Вывод на экран - медленное дело и скорость тут определяется скоростью экрана, поэтому Дуе и Уно будут тормозить одинаково.

b707
Offline
Зарегистрирован: 26.05.2017

Кто придумал взять задержку антидребезга 150 мс? Плюньте ему в рожу, он идиот

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

b707 пишет:
В коде #5 ничего никуда не вынесено, меню запускается каждый проход loop

Блин, согласен. Тогда как же вывести на экран одноразово? В Сетапе это не сделать, т.к. те пункты меню, которые справа (Smart, Laser, DMX и Setup) будут "нырять" в другие экраны. Куда они потом вернутся? Вынести все принты главного экрана в каждую функцию? Или в PROGMEM, а потом выдёргивать при необходимости?

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Прогмем тут не поможет никак. Отслеживайте реальное изменение отображаемого на экране параметра. If (oldValue != newValue) { lcd... }

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

sadman41 пишет:
Прогмем тут не поможет никак. Отслеживайте реальное изменение отображаемого на экране параметра. If (oldValue != newValue) { lcd... }

Тогда как главный экран вывести однократно при запуске вне основного лупа? И при необходимости снова к нему вернуться?

sadman41
Offline
Зарегистрирован: 19.10.2016

Подумайте, как заставить оператор if() сработать по дополнительному условию. Разработчик тут Вы, а не мы.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

sadman41 пишет:
Разработчик тут Вы, а не мы.

Я это понимаю. Но я ранее писал:

Dorfman пишет:
Я знаком с программингом, но не Wire и не С++, поэтому за какие-то элементарные (на ваш взгляд) вопросы сильно не пинайте. Сразу объявлю константу, что перед каждым вопросом был поиск в гугеле с невнятными или отрицательными результатами.

За подсказку с тормозами LCD огромное спасибо. Но я сейчас в ступоре, поэтому прошу помощи. Пытался использовать готовые библиотеки менюшек - но все они занимают весь луп, и генератор ваще не работает в них. Наверняка есть простой способ, который уже давно до меня придумали. Я не могу понять, как выплёвывать на экран информацию однократно, но с возможностью возврата (если ныряю вглубь меню и надо вынырнуть).

Duino A.R.
Offline
Зарегистрирован: 25.05.2015

Dorfman пишет:
Я не могу понять, как выплёвывать на экран информацию однократно, но с возможностью возврата (если ныряю вглубь меню и надо вынырнуть).

Пронумеруйте все менюшки (экраны) и заведите флаг обновления. Сделайте небольшую подпрограммку, которая будет в лупе смотреть за флагом. Если флаг взведен, то она будет отрисовывать ту менюшку, номер которой задан в соответствующей переменной. Отрисовала, флаг сбросила. Все последующие циклы при сброшенном флаге будут пулей пролетать.

Внешнее воздействие, которое будет требовать вызова нового меню, должно будет задать номер этого меню и взвести флаг отрисовки.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Главный экран вынес в отдельную функцию, запросил её в Сетапе. Вроде всё заработало, осталось разобраться с отрисовкой значений при их смене (косяки вылезли). Из внутренних экранов (меню) буду выныривать снова в функцию Главного экрана.

Спасибо!

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Dorfman пишет:

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

В одном коротком утверждении сразу две ошибки.

Во-первых "все придумали до нас" работает только в простейших случаях. Если бы это было так, то было бы вообще бессмысленно что-то пытаться сделать - ведь уже все сделано. Ну и, кроме того, учитывая огромное количество частных случаев, все равно, если бы решение и существовало раньше, найти его было бы дольше, чем придумать самому.

И во-вторых, "простой способ" существует далеко не всегда. Например, мне как-то понадобилось добиться, чтобы задержки от вывода на экран ни в каком случае не превосходили 0.3 мс. Желаемого удалось добиться, лишь хорошо поработав сразу по двум направлениям:

- сократить время вывода одного символа - удалось ускорить раз в 15 по сравнению со стандартной библиотекой,

- организовать вывод так, чтобы за один раз выводилось не более одного символа.

Увы, "простым способом" ни первого, ни второго не добиться.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Никогда ыб не подумал, что вывод на экран может НАСТОЛЬКО тормозить. Век живи - век учись. И огромное спасибо 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;
  }
}

 

sadman41
Offline
Зарегистрирован: 19.10.2016

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

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Итак, все прошлые трудности осилены. Теперь новый ступор.

Имеем:

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-го шага опять вернуться к первому. Вот на этом месте ступор наступил, в трёх соснах блуждаю сутки :(

 

b707
Offline
Зарегистрирован: 26.05.2017

в чем блуждаете-то, я не понял

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

b707 пишет:

в чем блуждаете-то, я не понял

как сделать защёлку. То есть как производить переключения по событию от тактового генератора. Понимаю, что надо читать 20-ю ногу, но если я её буду считывать с тактовой частотой Ардуины, то за один импульс генератора считаю первую единицу миллионы раз, а мне нужно только регистрировать смену события (без разницы по фронту или спаду), считать нужную строку массива и перезаписать состояние пинов, и ждать следующего события. 

sadman41
Offline
Зарегистрирован: 19.10.2016

А проблема в чем - не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

sadman41 пишет:

не знаете, как записать в переменную состояние пина чтобы на следующем лупе понять - изменилось оно или нет ?

Именно. Как заставить ардуину ждать до следующего изменения без зацикливания

sadman41
Offline
Зарегистрирован: 19.10.2016

Это прискорбно. Думаю, что тема не для раздела "программирование".

b707
Offline
Зарегистрирован: 26.05.2017

Dorfman пишет:

Именно. Как заставить ардуину ждать до следующего изменения без зацикливания

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту

b707
Offline
Зарегистрирован: 26.05.2017

Dorfman пишет:

А то... Всё знать нереально. Я Ардуинку месяц как в руки взял, и то руки дошли только "благодаря" карантину, так что 2 недели по факту

вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

b707 пишет:

вопрос Садмана из сообщения 29 внимательно прочитайте - в нем полный ответ, как решить вашу проблему

Вот, блин, это ж как обычная триггерная кнопка! Сенкс!

mrtester
Offline
Зарегистрирован: 26.02.2015

Ну ка получился автомат эффектов?

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

mrtester пишет:

Ну ка получился автомат эффектов?

основа только заложена, сейчас с ШИМ-ом разбираюсь, потом ещё в работе dmx и ws2812.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Хех... Снова в трёх соснах блуждаю... Начал писать простейшие эффекты для бегушки из 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 канала и обнуляется. Что не так?

sadman41
Offline
Зарегистрирован: 19.10.2016

X и Y путать не надо.

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

sadman41 пишет:
X и Y путать не надо.

йЕС! Сенкс!

sadman41
Offline
Зарегистрирован: 19.10.2016

Наверняка, где не работает?

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

Строка 29 в последнем коде...

Должно быть

int k = effect6[y][x-2];

 

mrtester
Offline
Зарегистрирован: 26.02.2015

наверное лучше так

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;
}

 

mrtester
Offline
Зарегистрирован: 26.02.2015
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);

и ШИМ

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

mrtester пишет:

наверное лучше так

Ну да, полаконичнее будет. Я не претендую на красивость кода - начал писать только благодаря карантину... Так что всё из головы, а там пока что каша ))

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

mrtester пишет:

и ШИМ

О, а я как раз начал копаться с ним, мудрить по-всякому, а ларчик просто открывался!

mrtester, а есть идея как реализовать плавное перетекание с помощью ШИМа? А то я тут такого нагородил... Оно ещё и виснет ((

mrtester
Offline
Зарегистрирован: 26.02.2015

выход на 220 вольт?

mrtester
Offline
Зарегистрирован: 26.02.2015

проще на выходах сделать 

Dorfman
Dorfman аватар
Offline
Зарегистрирован: 02.02.2020

mrtester пишет:

выход на 220 вольт?

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

 

mrtester пишет:

проще на выходах сделать 

Как на выходах??? Даже сути не уловил

mrtester
Offline
Зарегистрирован: 26.02.2015