user80648: I recently wrote a simple script to...we'll say, "flash some LED's" and uploaded it to an attiny45. In short, the script cycles through 3 LED's and turns them on for a specified frequency, in a specified direction. With a button, I can cycle through different modes. There are 7 modes (default (LED's OFF), then 3 different speeds forwards and 3 different speeds backwards).
The script works fine-ish, however I've noticed that in order to read from the button input properly, the function that turns the LED's ON and OFF must be completed, meaning that for the longer cycles, the user may have to hold the button until the function completes. This is further complicated during the shorter cycles, where the input may detect multiple button-presses (despite adding a delay following the digital read.
NOTE: I could just make the delay longer, but this doesn't fix the issue during lower frequencies, when the function may take multiple seconds to complete.
I feel the best way to sort this (though I'd be open to other ideas) is to use the attachInterrupt() function; however, it doesn't work. The button no longer works and I'm not sure why.
// 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);
    }       
  }  
}

NOTE: callMode() and ledMode() are only split up into different functions because I know that the function passed to attachInterrupt() cannot contain any delays(). So each time the button is pressed, the mode variable should increment and then the switch-statement should cause the next mode to run. When we reach 7, let's reset to zero and enter the default/OFF mode.
I've been racking my head for a few days on this and not sure what the problem is. Perhaps I can only use the attachInterrupt() function to some other pin? Perhaps I'm not using that function correctly?
Thank you in advance for any and all help you can provide.
-Joe

user7508: Interrupts are overkill for this kind of ultra-slow problems, and they
can introduce some extra complexity. Here the best solution seems (to me
at least) to lie in the approach of the Blink Without Delay
Arduino tutorial. Obviously, it must be adapted. In that tutorial, the
system switches between only two states (LED off or on). Here the
cycling of the LEDs goes through six phases:

all LEDs are off
LED[0] is on
all LEDs are off
LED[1] is on
all LEDs are off
LED[2] is on

The basic algorithm is the same: when millis() tells us it is time to
move to the next phase, we do so and update the state of the LEDs.
Note that I numbered the phases in such a way that the last bit
(phase&1) tells us whether an LED is on, and in that case the
remaining bits (phase>>1) hold the index of the LED that is on.
Here is how to cycle the LEDs at a fixed speed:
if (millis() - last_change >= phase_time) {
    last_change += phase_time;

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

    /* Move to the next phase. */
    if (++phase >= 6) phase = 0;

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

On top of this you need a finite state machine that cycles
between the different modes (speeds and directions). In order to avoid
code repetition, I would split this mode into a “state” that can OFF,
FORWARD or BACKWARDS, and an index into an array of delay times (with 0,
1, 2 meaning fast, medium and slow, respectively).
Putting it all together (warning: not tested):
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);
    }
}

I did not write the button_pressed() function, which is meant to
debounce the button and detect the “just pressed” transition. It is left
as an exercise to the reader (or you can just use a debouncing
library).

80648: <p>I recently wrote a simple script to...we'll say, &quot;flash some LED's&quot; and uploaded it to an attiny45. In short, the script cycles through 3 LED's and turns them on for a specified frequency, in a specified direction. With a button, I can cycle through different modes. There are 7 modes (default (LED's OFF), then 3 different speeds forwards and 3 different speeds backwards).</p>
<p>The script works fine-ish, however I've noticed that in order to read from the button input properly, the function that turns the LED's ON and OFF must be completed, meaning that for the longer cycles, the user may have to hold the button until the function completes. This is further complicated during the shorter cycles, where the input may detect multiple button-presses (despite adding a delay following the digital read.</p>
<p>NOTE: I could just make the delay longer, but this doesn't fix the issue during lower frequencies, when the function may take multiple seconds to complete.</p>
<p>I feel the best way to sort this (though I'd be open to other ideas) is to use the attachInterrupt() function; however, it doesn't work. The button no longer works and I'm not sure why.</p>
<pre><code>// 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&lt;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 &gt;= 7) {
    mode = 0;
  }
}

void callMode(){
  ++mode;
  //TIME: 1000, 300, 100 (milliseconds)
   switch (mode) {
     case 0:
       // Default, OFF
       for (int i=0; i&lt;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&lt;3; i++){
          digitalWrite(leds[i], LOW);
       }
       mode = 0;
       break;  
   }    
}

void ledMode(int onTime, int offTime, bool forward){
  if (forward) {
    for (int i = 0; i &lt;= 2; i++) {
      digitalWrite(leds[i], HIGH);
      delay(onTime);                       
      digitalWrite(leds[i], LOW);
      delay(offTime);
    }       
  }  
  else {
    for (int i = 2; i &gt;= 0; i--) {
      digitalWrite(leds[i], HIGH);
      delay(onTime);                       
      digitalWrite(leds[i], LOW);
      delay(offTime);
    }       
  }  
}
</code></pre>
<p>NOTE: callMode() and ledMode() are only split up into different functions because I know that the function passed to attachInterrupt() cannot contain any delays(). So each time the button is pressed, the mode variable should increment and then the switch-statement should cause the next mode to run. When we reach 7, let's reset to zero and enter the default/OFF mode.</p>
<p>I've been racking my head for a few days on this and not sure what the problem is. Perhaps I can only use the attachInterrupt() function to some other pin? Perhaps I'm not using that function correctly?</p>
<p>Thank you in advance for any and all help you can provide.</p>
<p>-Joe</p>
 7508: <p>Interrupts are overkill for this kind of ultra-slow problems, and they
can introduce some extra complexity. Here the best solution seems (to me
at least) to lie in the approach of the <a href="https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay" rel="nofollow noreferrer">Blink Without Delay</a>
Arduino tutorial. Obviously, it must be adapted. In that tutorial, the
system switches between only two states (LED off or on). Here the
cycling of the LEDs goes through six phases:</p>
<ol start="0">
<li>all LEDs are off</li>
<li>LED[0] is on</li>
<li>all LEDs are off</li>
<li>LED[1] is on</li>
<li>all LEDs are off</li>
<li>LED[2] is on</li>
</ol>
<p>The basic algorithm is the same: when <code>millis()</code> tells us it is time to
move to the next phase, we do so and update the state of the LEDs.</p>
<p>Note that I numbered the phases in such a way that the last bit
(<code>phase&amp;1</code>) tells us whether an LED is on, and in that case the
remaining bits (<code>phase&gt;&gt;1</code>) hold the index of the LED that is on.</p>
<p>Here is how to cycle the LEDs at a fixed speed:</p>
<pre class="lang-cpp prettyprint-override"><code>if (millis() - last_change &gt;= phase_time) {
    last_change += phase_time;

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

    /* Move to the next phase. */
    if (++phase &gt;= 6) phase = 0;

    /* Turn on the LED if needed. */
    if (phase &amp; 1)
        digitalWrite(leds[phase &gt;&gt; 1], HIGH);
}
</code></pre>
<p>On top of this you need a <a href="https://majenko.co.uk/blog/finite-state-machine" rel="nofollow noreferrer">finite state machine</a> that cycles
between the different modes (speeds and directions). In order to avoid
code repetition, I would split this mode into a “state” that can OFF,
FORWARD or BACKWARDS, and an index into an array of delay times (with 0,
1, 2 meaning fast, medium and slow, respectively).</p>
<p>Putting it all together (warning: not tested):</p>
<pre class="lang-cpp prettyprint-override"><code>const uint8_t leds[] = { 2, 3, 4 };
const uint32_t times[] = { 100, 300, 1000 };

void setup() {
    for (int i = 0; i &lt; 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 &gt;= 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 &amp;&amp; millis() - last_change &gt;= times[time_idx]) {
        last_change += times[time_idx];

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

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

        /* Turn on the LED if needed. */
        if (phase &amp; 1)
            digitalWrite(leds[phase &gt;&gt; 1], HIGH);
    }
}
</code></pre>
<p>I did not write the <code>button_pressed()</code> function, which is meant to
debounce the button and detect the “just pressed” transition. It is left
as an exercise to the reader (or you can just use a <a href="https://github.com/thomasfredericks/Bounce2" rel="nofollow noreferrer">debouncing
library</a>).</p>
