Переключатель режима светодиода RGB — попытка избежать использования основного цикла ()

Начнем с того, что я занимаюсь электроникой (не только Arduino) всего около трех дней.

Я пытаюсь написать скетч, который изменяет светодиод RGB при нажатии кнопки.

Есть 6 режимов -

0 = off
1 = dim
2 = mid brightness
3 = full brightness
4 = cycle through RGB colours
5 = pause the colour cycle at the current colour

Мне удалось заставить это работать, используя код ниже этого объяснения. Что я хотел бы знать, так это - Поскольку светодиод меняет состояние только при нажатии кнопки, можно ли полностью избежать использования основного цикла()?

Будет нормально работать для режимов 0, 1, 2 и 3, но прерывание не может срабатывать, пока режим 4 не завершит 1 полный цветовой цикл.

/*
 * LED MODES
 * 0 = off
 * 1 = dim
 * 2 = mid
 * 3 = bright
 * 4 = colour cycle
 * 5 = colour select
 */
volatile int ledMode = 0; // выключенный
const int maxLEDMode = 5;
volatile bool buttonPressed = false;

int rPin = 9;
int gPin = 10;
int bPin = 11;
int ledPin = 5;
int buttonPin = 2;
int currentVals[] = {0, 0, 0};


void setup() {
  Serial.begin(9600);
  pinMode(rPin, OUTPUT);
  pinMode(gPin, OUTPUT);
  pinMode(bPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT);

  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPress, FALLING);
}


void loop() {
  setBrightness(); // пытаемся выйти за пределы основного цикла
}


// Гарантируем, что buttonPress не срабатывает дважды
void buttonPress(){
  if(digitalRead(buttonPin) == 0){
    delay(10);
    if(digitalRead(buttonPin) == 0){

      setMode();
      buttonPressed = true;
      Serial.println(ledMode);
    }
  } 
}


//Увеличиваем режим светодиода, пока не будет достигнут максимальный уровень
void setMode(){
  if(ledMode == maxLEDMode){
    ledMode = 0;
  } else {
    ledMode++;
  }
}


//Устанавливаем цвета светодиодов в зависимости от ledMode
void setBrightness(){
  buttonPressed = false;

  if(ledMode == 0){
    mode0();
  }

  if(ledMode == 1){
    mode1();
  }

  if(ledMode == 2){
    mode2();
  }

  if(ledMode == 3){
    mode3();
  }

  if(ledMode == 4){
    mode4();
  }

  if(ledMode == 5){
    mode5();
  }
}


//Затухание
void mode0 (){
  fadeTo(0, 0, 0, 10);
}


// Исчезать до тусклого света
void mode1 (){
  int val = 255/5;
  fadeTo(val, val, val, 100);
}


// Переход к среднему свету
void mode2 (){
  int val = (255/3);
  fadeTo(val, val, val, 100);
}


// Переход к яркому свету
void mode3 (){
  fadeTo(255, 255, 255, 100);
}


// Бесконечный цикл по цветам RGB
void mode4(){
    fadeToWithCheck(255, 0, 0, 500);
    fadeToWithCheck(255, 255, 0, 500);
    fadeToWithCheck(0, 255, 0, 500);
    fadeToWithCheck(0, 255, 255, 500);
    fadeToWithCheck(0, 0, 255, 500);
    fadeToWithCheck(255, 0, 255, 500);
}


// Приостановка светодиода на цвете, унаследованном от Mode4
void mode5(){
  int rExisting = currentVals[0];
  int gExisting = currentVals[1];
  int bExisting = currentVals[2];
  fadeTo(rExisting, gExisting, bExisting, 1);
}


// Переход к заданному цвету с учетом переключателей режима 4
void fadeToWithCheck(int r, int g, int b, int interval){
  if(ledMode == 4){
    int rExisting = currentVals[0];
    int gExisting = currentVals[1];
    int bExisting = currentVals[2];

    for(int i = 0; i <= interval; i++){
      analogWriteInverted(rPin, map(i, 0, interval, rExisting, r));
      analogWriteInverted(gPin, map(i, 0, interval, gExisting, g));
      analogWriteInverted(bPin, map(i, 0, interval, bExisting, b));

      if(buttonPressed == true){
        Serial.println("stopping loop");
        currentVals[0] = map(i, 0, interval, rExisting, r);
        currentVals[1] = map(i, 0, interval, gExisting, g);
        currentVals[2] = map(i, 0, interval, bExisting, b);
        break;
      }
      delay(10);
    }

    if(buttonPressed == true){
      buttonPressed = false;
    } else {
      currentVals[0] = r;
      currentVals[1] = g;
      currentVals[2] = b;
    }
  }
}


// Переходим к заданному цвету
void fadeTo(int r, int g, int b, int interval){
  int rExisting = currentVals[0];
  int gExisting = currentVals[1];
  int bExisting = currentVals[2];

  for(int i = 0; i <= interval; i++){
    analogWriteInverted(rPin, map(i, 0, interval, rExisting, r));
    analogWriteInverted(gPin, map(i, 0, interval, gExisting, g));
    analogWriteInverted(bPin, map(i, 0, interval, bExisting, b));
    delay(10);
  }
  currentVals[0] = r;
  currentVals[1] = g;
  currentVals[2] = b;
}


// Инвертировать заданное значение из-за светодиода катода/анода
void analogWriteInverted( byte pin, byte value)
{
  analogWrite(pin, 255-value);
}

Будем признательны за любую помощь или совет! Большое спасибо, Роб

ОБНОВЛЕНИЕ — Рабочее решение

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

#include "TimerOne.h"

/*
* РЕЖИМЫ СВЕТОДИОДОВ
* 0 = выкл.
* 1 = тусклый
* 2 = середина
* 3 = яркий
* 4 = цветовой цикл
* 5 = выбор цвета
*/
volatile int ledMode = 0; // off
const int maxLEDMode = 5;
volatile bool buttonPressed = false;
unsigned long lastDebounceTime = -1;
unsigned long debounceDelay = 200;
int lastButtonState = HIGH;
int buttonState;
long secondTimer = 1000000;

int rPin = 6;
int gPin = 5;
int bPin = 3;
int buttonPin = 2;
int currentVals[] = {0, 0, 0};
int mode4Vals[] = {0, 0, 0};
int mode4Stage = 0;

long fadeInterval = 0;
long lastSeenInterval;


void setup() {
  Serial.begin(9600);
  pinMode(rPin, OUTPUT);
  pinMode(gPin, OUTPUT);
  pinMode(bPin, OUTPUT);
  pinMode(buttonPin, INPUT);

  Timer1.initialize(secondTimer / 1000);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPress, FALLING);

  turnOff();
}


void loop() {
  //setBrightness(); // try and move outside of main loop

}

void turnOff(){
  analogWriteInverted(rPin,0);
  analogWriteInverted(gPin,0);
  analogWriteInverted(bPin,0);

  currentVals[0] = 0;
  currentVals[1] = 0;
  currentVals[2] = 0;
}


// Ensure that buttonPress does not trigger twice
void buttonPress(){
  int reading = digitalRead(buttonPin);

  if (lastDebounceTime < 0 || (millis() - lastDebounceTime) > debounceDelay) {
    lastDebounceTime = millis();
    if(ledMode != 4){
        clearTimer();
    }
        setMode();
        buttonPressed = true;
        Timer1.attachInterrupt(incrementGlobalFadeInterval);
      }
    }


//Increment LED Mode until max level is reached
void setMode(){
  if(ledMode == maxLEDMode){
    ledMode = 0;
  } else {
    ledMode++;
  }
}


//Set LED colours depending on ledMode
void setBrightness(){
  buttonPressed = false;

  switch(ledMode){
    case 0: mode0(); break;
    case 1: mode1(); break;
    case 2: mode2(); break;
    case 3: mode3(); break;
    case 4: mode4(); break;
    case 5: mode5(); break;
    default: abort();
  }

}


//Fade to off
void mode0 (){
  fadeTo(0, 0, 0, 400);
}


// Fade to dim light
void mode1 (){
  int val = 255/5;
  fadeTo(val, val, val, 50);
}


// Fade to medium light
void mode2 (){
  int val = (255/3);
  fadeTo(val, val, val, 50);
}


// Fade to bright light
void mode3 (){
  fadeTo(255, 255, 255, 50);
}


// Endlessly cycle through RGB colours
void mode4(){
  switch(mode4Stage){
    case 0: fadeToWithCheck(255, 0, 0, 2000); break;
    case 1: fadeToWithCheck(255, 255, 0, 2000); break;
    case 2: fadeToWithCheck(0, 255, 0, 2000); break;
    case 3: fadeToWithCheck(0, 255, 255, 2000); break;
    case 4: fadeToWithCheck(0, 0, 255, 2000); break;
    case 5: fadeToWithCheck(255, 0, 255, 2000); break;
    default: abort();
  }
}


// Pause LED on colour inherited from Mode4
void mode5(){
  currentVals[0] = mode4Vals[0];
  currentVals[1] = mode4Vals[1];
  currentVals[2] = mode4Vals[2];

  int rExisting = currentVals[0];
  int gExisting = currentVals[1];
  int bExisting = currentVals[2];
  fadeTo(rExisting, gExisting, bExisting, 1);
}


// Fade to given colour, with awareness of mode4 toggles
void fadeToWithCheck(int r, int g, int b, int interval){
  if(ledMode == 4){
    int rExisting = currentVals[0];
    int gExisting = currentVals[1];
    int bExisting = currentVals[2];

    if(fadeInterval <= interval && lastSeenInterval != fadeInterval){
      lastSeenInterval = fadeInterval;
      analogWriteInverted(rPin, map(fadeInterval, 0, interval, rExisting, r));
      analogWriteInverted(gPin, map(fadeInterval, 0, interval, gExisting, g));
      analogWriteInverted(bPin, map(fadeInterval, 0, interval, bExisting, b));

      mode4Vals[0] = map(fadeInterval, 0, interval, rExisting, r);
      mode4Vals[1] = map(fadeInterval, 0, interval, gExisting, g);
      mode4Vals[2] = map(fadeInterval, 0, interval, bExisting, b);
    } else {
      currentVals[0] = map(fadeInterval, 0, interval, rExisting, r);
      currentVals[1] = map(fadeInterval, 0, interval, gExisting, g);
      currentVals[2] = map(fadeInterval, 0, interval, bExisting, b);
      fadeInterval = 0;

      if(mode4Stage == 5){
        mode4Stage = 0;
      } else {
        mode4Stage++;
      }
    }
  }
}


// Fade to given colour
void fadeTo(int r, int g, int b, int interval){
  int rExisting = currentVals[0];
  int gExisting = currentVals[1];
  int bExisting = currentVals[2];

  if(fadeInterval <= interval && fadeInterval != lastSeenInterval){
    lastSeenInterval = fadeInterval;
    analogWriteInverted(rPin, map(fadeInterval, 0, interval, rExisting, r));
    analogWriteInverted(gPin, map(fadeInterval, 0, interval, gExisting, g));
    analogWriteInverted(bPin, map(fadeInterval, 0, interval, bExisting, b));
  } else {
    clearTimer();
    currentVals[0] = r;
    currentVals[1] = g;
    currentVals[2] = b;
  }
}

void incrementGlobalFadeInterval(){
  fadeInterval++;
  setBrightness();
}

void clearTimer(){
  Timer1.detachInterrupt();
  fadeInterval = 0;
  lastSeenInterval = -1;
}

// Invert given value due to cathode/anode LED
void analogWriteInverted( byte pin, byte value)
{
  analogWrite(pin, 255-value);
}

, 👍2

Обсуждение

Попробуйте функцию switch в C++. Информацию найдете через поисковик., @MichaelT

Я предполагаю, что он будет работать так же, как переключатель в С#. Это значительно упорядочило бы мою функцию setBrightness. Хорошая мысль! Я не слишком понимаю, как это позволит избежать использования функции основного цикла?, @Rob Jeffrey

Еще одна проблема, которую я вижу, заключается в том, что вы используете задержку, а задержку нельзя использовать внутри прерывания., @Michel Keijzers

Спасибо, Мишель, это потому, что это не лучшая практика, или это может вызвать фундаментальные проблемы? Я поставил это на место, потому что, когда я нажимал кнопку, ISR срабатывал случайное количество раз, иногда один раз, иногда до 3 раз. Вы видели подобную проблему раньше? Извините, если это должен быть совсем другой пост., @Rob Jeffrey

плюс за хорошо написанный пост, @jsotola


2 ответа


Лучший ответ:

1

Не прерывать коротко

Внутри прерывания не используйте длинные паузы, то есть не задержку. Как отмечает Крисл ниже (см. комментарий), задержка даже не работает внутри прерывания, поскольку она основана на функции millis(), которая не обновляется в прерывании.

Причина в том, что прерывания должны быть как можно короче (поэтому, даже если вы будете использовать цикл for с micros() или другой способ удержания времени выполнения), потому что это может привести к тому, что новые прерывания не будут вызываться (во время прерывание, новые прерывания вызываться не будут, по крайней мере, с таким же или более низким приоритетом). Поэтому всегда делайте код в прерываниях очень коротким, особенно это нарушают задержки, а также операторы serial.print.

Я вижу, вы используете эту функцию, которая будет вызываться, если вы поместите все в обработчик прерываний:

// переходим к заданному цвету
void fadeTo(int r, int g, int b, int interval){
  int rExisting = currentVals[0];
  int gExisting = currentVals[1];
  int bExisting = currentVals[2];

  for(int i = 0; i <= interval; i++){
    analogWriteInverted(rPin, map(i, 0, interval, rExisting, r));
    analogWriteInverted(gPin, map(i, 0, interval, gExisting, g));
    analogWriteInverted(bPin, map(i, 0, interval, bExisting, b));
    delay(10);
  }
  currentVals[0] = r;
  currentVals[1] = g;
  currentVals[2] = b;
}

Устранение задержек

Однако удалить задержку немного сложно. Что вам нужно сделать, это (приблизительно):

  • Создайте глобальную переменную вместо переменной цикла (i, но дайте ей лучшее имя)
  • Вызов дополнительного прерывания на основе 10 мс (задержка).
  • В этом прерывании обработайте следующую итерацию (поэтому увеличьте глобальную переменную i (но новое имя)
  • Выполнить постобработку (например, если i == interval, выполнить последние три оператора функции (и, возможно, другие операторы из вызванных функций в исходном коде).

Несколько if в один оператор switch

Также вы можете использовать оператор switch.. Вместо

 if(ledMode == 0){
    mode0();
  }

  if(ledMode == 1){
    mode1();
  }

  if(ledMode == 2){
    mode2();
  }

  if(ledMode == 3){
    mode3();
  }

  if(ledMode == 4){
    mode4();
  }

  if(ledMode == 5){
    mode5();
  }
}

Использовать

switch (ledMode)
{
case 0: 
  mode0();
  break;

case 1:
  mode1();
  break;

..

case 5:
  mode5();
  break;

default:
   abort(); // Состояние ошибки
}

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

switch (ledMode)
{
case 0: mode0(); break;
case 1: mode1(); break;
..
case 5: mode5(); break;
default: abort();
}

Устранение дребезга кнопок

В своем примечании вы говорите, что после нажатия кнопки иногда функция вызывается несколько раз. Это происходит из-за дребезга кнопок. Когда вы нажимаете кнопку, в течение короткого времени контакт находится где-то между включенным и выключенным. В течение этого времени он может несколько раз переключаться между включением и выключением, поэтому вы получаете несколько вызовов, в основном очень коротких друг за другом. Чтобы предотвратить это, используйте функцию подавления дребезга кнопок (на сайте Arduino можно найти пример устранения дребезга кнопок). Это означает, что вы проверяете, стабильно ли определенное состояние в течение некоторого времени, и затем выполняете действие, а не немедленно.

,

Отличный ответ, Мишель, я уверен, что все это имеет для меня смысл. Я попробую и вернусь к вам со своими выводами., @Rob Jeffrey

Удачи, если это поможет вам, рассмотрите возможность голосования/принятия ответа., @Michel Keijzers

Я думал, что delay() вообще не работает внутри ISR, потому что он полагается на millis(), который, в свою очередь, полагается на прерывание для подсчета времени., @chrisl

@chrisl вы правы, в основном вы можете использовать milis (), но он не обновляется, поэтому использование задержки действительно не работает. Я адаптирую ответ. Спасибо!, @Michel Keijzers

Спасибо за помощь, Мишель! Не уверен, что мое решение именно то, что вы себе представляли, но оно определенно помогает. Я обновил свой вопрос рабочим кодом., @Rob Jeffrey


0

Удалите buttonPressed = false; из fadeToWithCheck. Как и в режиме 4 эта функция вызывается четыре раза, при нажатии кнопки пропускается только текущий вызов. Следующие вызовы не пропускаются, потому что ваша кнопка сброса нажата обратно в ложное состояние.

В этом нет необходимости, так как в начале setBrightness он уже сбрасывается обратно на false.

,