Ищу помощь с анимацией RGB LED

Я работаю над проектом, в котором используется Sparkfun Pro Micro 5V. Я пытаюсь выполнить анимацию цикла цветной радуги, которая при срабатывании будет иметь функцию, которая при срабатывании затухает радугу до черного и затухает какой-то белый светодиод. Для этого я использую Adafruit RGBW Neopixel. У меня возникли проблемы с получением некоторого кода, который делает это, поскольку, когда я пытаюсь вызвать затухание, он отключается быстрее, чем ожидалось, из-за того, как я его написал. Я надеюсь, что кто-нибудь может указать мне фрагмент кода или предложить правку, которая поможет.

Ограничения для анимации:

  • Должен выполняться быстро (по возможности избегайте плавающих элементов или других конструкций, которые занимают много времени; относительно)
  • Возможность настроить максимальную яркость, установленную при инициализации (0–255).
  • Не должно быть циклов "задержка" или "пока", предназначенных для удержания кода.
  • При нажатии кнопки радуга должна стать черной (т. е. погаснуть), а ранее отключенный белый светодиод должен исчезнуть.
  • При повторном нажатии кнопки белый цвет должен исчезнуть, а радуга загорится или возобновится.

Мне все равно, как работает цветовая анимация. Он может переключаться между указанными цветами или использовать что угодно. Для простоты я попытался просто циклически менять значения RGB с каждой 1/3 несовпадения по фазе. Мой цикл включает в себя «мертвое» время, когда цвет остается выключенным для части цикла, чтобы цвета оставались более четкими, чтобы они не сливались в белый цвет в результате смешивания цветов. Я специально не использовал какое-либо преобразование HSV или sin/cos/что-то еще для вычисления позиции в цикле, потому что это требует большого количества вычислений относительно увеличения числа.

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

#include <Servo.h>
#include <Adafruit_NeoPixel.h>

#define usToTicks(_us) (( clockCyclesPerMicrosecond()* _us) / 8)

class ColorCycle {
  private: int maxB;
  private: int offH;
  private: int stepD;
  private: int curD;
  private: int rVal;
  private: int gVal;
  private: int bVal;
  private: int wVal;
  private: int rHold;
  private: int gHold;
  private: int bHold;
  private: int wHold;
  private: bool rDir;
  private: bool gDir;
  private: bool bDir;
  private: bool wDir;
  private: bool showWhite;

  public: ColorCycle(int maxBrightness, int offHold, int stepDelay) {
      maxB = maxBrightness;
      offH = offHold;
      rVal = gVal = bVal = wVal = 0;
      rHold = 0;
      gHold = (maxB + offH) * .5;
      bHold = (maxB + offH);
      wHold = 0;
      rDir = gDir = bDir = true;
      wDir = false;
      stepD = stepDelay;
      curD = 0;
    }

    void moveStep() {
      moveSteps(1);
    }

    void moveSteps(int count) {
      if (count == 1) { //пропустим задержку, если вы намеренно пытаетесь переместиться более чем на один шаг.
        if (curD > 0) {
          curD--;
          return;
        }
        curD = stepD;
      }

      for (int s = 0; s < count; s++) {
        if (showWhite) {
          whiteHold();
          wDir = true;
        } else {
          wDir = false;
        }

        //устанавливаем направление и удерживаем при необходимости
        if (rVal == 0)
          rDir = true;
        if (rVal == maxB) {
          rDir = false;
          rHold = offH;
        }

        if (gVal == 0)
          gDir = true;
        if (gVal == maxB) {
          gDir = false;
          gHold = offH;
        }

        if (bVal == 0)
          bDir = true;
        if (bVal == maxB) {
          bDir = false;
          bHold = offH;
        }

        // шаг значений
        if (rDir && rHold == 0)
          rVal++;
        if (!rDir && rVal > 0)
          rVal--;
        if (rVal == 0 && rHold > 0) {
          rHold--;
        }

        if (gDir && gHold == 0)
          gVal++;
        if (!gDir && gVal > 0)
          gVal--;
        if (gVal == 0 && gHold > 0) {
          gHold--;
        }

        if (bDir && bHold == 0)
          bVal++;
        if (!bDir && bVal > 0)
          bVal--;
        if (bVal == 0 && bHold > 0) {
          bHold--;
        }

        if (wDir && wVal < maxB)
          wVal++;
        if (!wDir && wVal > 0)
          wVal--;
      }
    }

    int getRedValue() {
      return rVal;
    }
    int getGreenValue() {
      return gVal;
    }
    int getBlueValue() {
      return bVal;
    }
    int getWhiteValue() {
      return 0;
    }

    void setShowWhite(bool sw) {
      showWhite = sw;
    }

    void whiteHold() {
      rHold = 5;
      gHold = (maxB + offH) * .5;
      bHold = (maxB + offH);
      rDir = gDir = bDir = false;
    }
};



#define BUTTON_LED_PIN 16
#define BUTTON_SWITCH_PIN 14

Servo lidServo;
int lidServoPosition;
Servo leftServo;
int leftServoPosition;
Servo rightServo;
int rightServoPosition;

#define LID_LED_CONTROL_PIN 3
#define LID_LED_COUNT 8
Adafruit_NeoPixel lidPixel(LID_LED_COUNT, LID_LED_CONTROL_PIN, NEO_RGBW + NEO_KHZ800);
ColorCycle ccLid(50, 50, 10);


#define BASE_LED_CONTROL_PIN 4
#define BASE_LED_COUNT 8
Adafruit_NeoPixel basePixel(BASE_LED_COUNT, BASE_LED_CONTROL_PIN, NEO_RGBW + NEO_KHZ800);
ColorCycle ccBase(50, 50, 10);


void setup() {
  pinMode(BUTTON_LED_PIN, OUTPUT);
  digitalWrite(BUTTON_LED_PIN, LOW);
  pinMode(BUTTON_SWITCH_PIN, INPUT);
  lidServo.attach(5);
  leftServo.attach(6);
  rightServo.attach(9);
  lidPixel.begin();
  lidPixel.show();
  basePixel.begin();
  basePixel.show();

  Serial.begin(9600); //Это канал к последовательному монитору
  Serial.println("Starting");
}

void loop() {
  if (digitalRead(BUTTON_SWITCH_PIN) == true)  {
    digitalWrite(BUTTON_LED_PIN, HIGH);
    lidServoPosition = 0;
    leftServoPosition = 0;
    rightServoPosition = 0;
    ccLid.setShowWhite(true);
    ccBase.setShowWhite(true);
  } else {
    digitalWrite(BUTTON_LED_PIN, LOW);
    lidServoPosition = 180;
    leftServoPosition = 180;
    rightServoPosition = 180;
    ccLid.setShowWhite(false);
    ccBase.setShowWhite(false);
  }
  lidServo.write(lidServoPosition);
  leftServo.write(leftServoPosition);
  rightServo.write(rightServoPosition);

  ccLid.moveStep();
  ColorCycle tempCc = ccLid;
  for (int i = 0; i < lidPixel.numPixels(); i++) {
    lidPixel.setPixelColor(i, lidPixel.Color(tempCc.getGreenValue(), tempCc.getRedValue(), tempCc.getBlueValue(), tempCc.getWhiteValue()));
    tempCc.moveSteps(10);
  }

  ccBase.moveStep();
  tempCc = ccBase;
  for (int i = 0; i < lidPixel.numPixels(); i++) {
    basePixel.setPixelColor(i, basePixel.Color(tempCc.getGreenValue(), tempCc.getRedValue(), tempCc.getBlueValue(), tempCc.getWhiteValue()));
    tempCc.moveSteps(10);
  }

  if (TCNT1 > usToTicks(8192)) {
    lidPixel.show();
    basePixel.show();
  };
}

, 👍0

Обсуждение

Вы обновляете светодиоды по времени через TCNT1, но настройка пикселей не является частью этого, так что это будет сделано очень быстро. Я думаю, что этот код также должен быть синхронизирован. Затем вы меняете значения только тогда, когда вы действительно обновляете цвета., @chrisl

Таймер TCNT1 используется только для того, чтобы убедиться, что команда «show» не выполняется во время обновления сервопривода, так как это может вызвать дрожание сервопривода., @William


1 ответ


1

Вы пишете неблокирующий код, но не вводите какой-либо способ синхронизации кода. Таким образом, вычисление новых значений цвета происходит настолько быстро, насколько может выполняться код. Вы можете написать синхронизированное действие неблокирующим способом, используя стиль кодирования из примера BlinkWithoutDelay, который поставляется с Arduino IDE. Тогда светодиодный код будет выполнен только в том случае, если наступило правильное время. Вы сохраняете временную метку последнего выполнения вычисления в глобальной переменной, а затем проверяете, прошло ли достаточно времени с тех пор. Только после этого вы снова выполняете расчет и обновляете метку времени.

unsigned long timestamp = 0;
unsigned int interval = 10;

void loop(){
    //--------
    // Опущен код сервопривода
    //--------
    if(millis() - timestamp > interval){
        timestamp += interval; // обновить отметку времени до следующего значения
        //--------
        // Опущен расчет и обновление светодиода (метод show())
        //--------
    }
}

Обратите внимание, что здесь я использовал millis(), который вернет количество миллисекунд, прошедших с момента запуска Arduino. Я использовал интервал в 10 миллисекунд, так что светодиоды будут обновляться каждые 10 миллисекунд. Вы можете изменить это значение по мере необходимости (также во время выполнения кода). Если вам нужно более быстрое выполнение, чем несколько миллисекунд, вы можете использовать micros() вместо millis(), чтобы работать с микросекундами. Конечно, вы не можете работать быстрее, чем работает остальной код.

Дополнительные разъяснения этого принципа можно найти в Google. Это неоднократно описывалось в Интернете и на этом сайте.

,