Добавление прерывания в ATtiny45 с помощью Arduino

Недавно я написал простой сценарий для...мы скажем: "прошейте несколько светодиодов" и загрузим его в attiny45. Короче говоря, скрипт циклически переключается через 3 светодиода и включает их на заданную частоту в заданном направлении. С помощью кнопки я могу переключаться между различными режимами. Существует 7 режимов (по умолчанию (светодиод выключен), затем 3 разные скорости вперед и 3 разные скорости назад).

Сценарий работает нормально, однако я заметил, что для правильного считывания с ввода кнопки функция, которая включает и выключает индикатор, должна быть завершена, что означает, что для более длительных циклов пользователю, возможно, придется удерживать кнопку до завершения функции. Это еще более усложняется во время более коротких циклов, когда вход может обнаруживать несколько нажатий кнопок (несмотря на добавление задержки после цифрового считывания.

ПРИМЕЧАНИЕ: Я мог бы просто увеличить задержку, но это не устраняет проблему при более низких частотах, когда выполнение функции может занять несколько секунд.

Я чувствую, что лучший способ разобраться в этом (хотя я был бы открыт для других идей) - использовать функцию attachInterrupt (); однако это не работает. Кнопка больше не работает, и я не знаю, почему.

// Pin Variables
int led1 = 2;
int led2 = 3;
int led3 = 4;
int leds[] = {led1, led2, led3};
int modePin = 0; // (physical pin 5 on the IC)

// Mode Speeds and setting
int fastTime = 100;
int mediumTime = 300;
int slowTime = 1000;
int offTime = 100;
int mode = 0;

void setup() {
  for (int i=0; i<3; i++){
    pinMode(leds[i], OUTPUT);
    digitalWrite(leds[i], LOW);
  }
  attachInterrupt(modePin, callMode, RISING);
}

void loop() {
  // If we get here, reset and go back to default (all LED's OFF)
  if (mode >= 7) {
    mode = 0;
  }
}

void callMode(){
  ++mode;
  //TIME: 1000, 300, 100 (milliseconds)
   switch (mode) {
     case 0:
       // Default, OFF
       for (int i=0; i<3; i++){
         digitalWrite(leds[i], LOW);
       }
       break;  
     case 1:
       // Forward, Fast
       ledMode(fastTime, offTime, true);
       break;
     case 2:
       // Forward, Medium
       ledMode(mediumTime, offTime, true);
       break;
     case 3:
       // Forward, Slow
       ledMode(slowTime, offTime, true);
       break;
     case 4:
       // Backwards, Fast
       ledMode(fastTime, offTime, false);
       break;
     case 5:
       // Backwards, Medium
       ledMode(mediumTime, offTime, false);
       break;
     case 6:
       // Backwards, Slow
       ledMode(slowTime, offTime, false);
       break;
     case 7:
       // Return to Zero
       for (int i=0; i<3; i++){
          digitalWrite(leds[i], LOW);
       }
       mode = 0;
       break;  
   }    
}

void ledMode(int onTime, int offTime, bool forward){
  if (forward) {
    for (int i = 0; i <= 2; i++) {
      digitalWrite(leds[i], HIGH);
      delay(onTime);                       
      digitalWrite(leds[i], LOW);
      delay(offTime);
    }       
  }  
  else {
    for (int i = 2; i >= 0; i--) {
      digitalWrite(leds[i], HIGH);
      delay(onTime);                       
      digitalWrite(leds[i], LOW);
      delay(offTime);
    }       
  }  
}

ПРИМЕЧАНИЕ: callMode() и ledMode() разделены только на разные функции, потому что я знаю, что функция, переданная в attachInterrupt (), не может содержать никаких delay(). Таким образом, каждый раз при нажатии кнопки переменная режима должна увеличиваться, а затем оператор switch должен вызывать запуск следующего режима. Когда мы достигнем 7, давайте сбросим значение до нуля и войдем в режим по умолчанию/ВЫКЛ.

Я уже несколько дней ломаю голову над этим и не знаю, в чем проблема. Возможно, я могу использовать функцию attachInterrupt() только для какого-либо другого пина? Возможно, я неправильно использую эту функцию?

Заранее благодарю вас за любую помощь, которую вы можете оказать.

-Джо

, 👍1

Обсуждение

1. Повторите "_ Я знаю, что функция, переданная в attachInterrupt (), не может содержать никаких задержек()_”: вы не должны вызывать " delay()` из контекста прерывания. Независимо от того, вызывается ли "delay ()" непосредственно обработчиком прерываний или косвенно через другую функцию, не имеет значения. 2. Вы пробовали [Мигать без задержки](https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay) подходить?, @Edgar Bonet

используйте " callMode ()", чтобы установить флаг, например NextSeq = true; ... в " ledMode () проверьте наличие флага ... если (в следующем квартале)`, @jsotola

@EdgarBonet Я думаю, что это было бы выполнимо с одним светодиодом, но не тогда, когда я проезжаю через 3 из них. Я пытаюсь принять его сейчас, но это определенно оказывается сложным, мягко говоря, @JoeBro391


1 ответ


3

Прерывания являются излишними для такого рода сверхмедленных задач, и они могут привести к некоторой дополнительной сложности. Здесь лучшее решение, как мне кажется (по крайней мере, мне), заключается в приближении Мигания Без промедления Учебник по Arduino. Очевидно, что он должен быть адаптирован. В этом руководстве система переключается только между двумя состояниями (индикатор выключен или включен). Здесь циклическое включение светодиодов проходит шесть фаз:

  1. все светодиоды выключены
  2. Индикатор[0] горит
  3. все светодиоды выключены
  4. Светодиод[1] горит
  5. все светодиоды выключены
  6. Светодиод[2] горит

Основной алгоритм тот же: когда millis() сообщает нам, что пришло время перейти к следующему этапу, мы делаем это и обновляем состояние светодиодов.

Обратите внимание, что я пронумеровал фазы таким образом, чтобы последний бит (фаза&1) сообщал нам, включен ли светодиод, и в этом случае оставшиеся биты (фаза>>>1>>) содержат индекс включенного светодиода.

Вот как переключать светодиоды с фиксированной скоростью:

if (millis() - last_change >= phase_time) {
    last_change += phase_time;

    /* Если горит светодиод, выключите его. */
    if (phase & 1)
        digitalWrite(leds[phase >> 1], LOW);

    /* Переходите к следующему этапу. */
    if (++phase >= 6) phase = 0;

    /* При необходимости включите светодиод. */
    if (phase & 1)
        digitalWrite(leds[phase >> 1], HIGH);
}

Кроме того, вам нужен конечный автомат, который переключается между различными режимами (скоростями и направлениями). Чтобы избежать повторения кода, я бы разделил этот режим на “состояние”, которое может отключиться, ВПЕРЕД или НАЗАД, и индекс в массиве времен задержки (с 0, 1, 2, что означает быстрый, средний и медленный, соответственно).

Собирая все это вместе (предупреждение: не проверено):

const uint8_t leds[] = { 2, 3, 4 };
const uint32_t times[] = { 100, 300, 1000 };

void setup() {
    for (int i = 0; i < 3; i++)
        pinMode(leds[i], OUTPUT);
}

enum { OFF, FORWARD, BACKWARDS } state = OFF;  // global state
uint8_t time_idx;      // index in times[] (0 = fast)
int8_t phase;          // 0 = all off, 1 = led[0] on, 2 = all off...
uint32_t last_change;  // last time the phase changed

void loop() {

    /* Change mode on button press. */
    if (button_pressed()) {
        if (state == OFF) {
            state = FORWARD;
            time_idx = 0;  // start fast
        } else {
            time_idx++;    // slow down
            if (time_idx >= 3) {  // change direction or stop
                time_idx = 0;
                if (state == FORWARD)
                    state = BACKWARDS;
                else              // state is BACKWARDS
                    state = OFF;
            }
        }
    }

    /* Cycle the LEDs. */
    if (state != OFF && millis() - last_change >= times[time_idx]) {
        last_change += times[time_idx];

        /* If an LED is on, turn it off. */
        if (phase & 1)
            digitalWrite(leds[phase >> 1], LOW);

        /* Move to the next phase. */
        if (state == FORWARD) {
            if (++phase >= 6) phase = 0;
        } else {  // BACKWARDS
            if (--phase < 0) phase = 5;
        }

        /* Turn on the LED if needed. */
        if (phase & 1)
            digitalWrite(leds[phase >> 1], HIGH);
    }
}

Я не писал функцию button_pressed (), которая предназначена для отключения кнопки и обнаружения перехода “только что нажата". Это оставлено в качестве упражнения для читателя (или вы можете просто воспользоваться библиотекойдля разбора).

,

Большое вам спасибо за ваши отзывы! Просто чтобы завершить этот цикл, я собирался реализовать это в своем проекте, но потом у меня появилась другая идея. Я вернулся к более старой версии прошивки до прерывания и после нажатия кнопки просто добавил 4-секундную задержку. Это требует, чтобы пользователь нажимал и удерживал кнопку до тех пор, пока не будет обнаружено нажатие (никогда не превышающее длину текущего цикла), но для этой цели работает довольно хорошо. Во всех случаях пользователь нажимает до тех пор, пока индикатор не остановится, а затем отпускает. Работает как заклинание. Поговорим о простом решении., @JoeBro391