Question that probably has a really obvious answer but I'm just too dumb to find it

Hi! So I am a total noob and am just starting out with Arduino and coding in general. I've recently bought a Neopixel LED strip and came to the conclusion that I want to build something with it. I'm thinking a control panel with buttons on it, with each button controlling a different routine that the strip loops through. If I press the first button the LED-s loop through routine nr.1, but as soon as I press another button, the strip should change to the respective routine. I'm thinking to use interrupts in my code but I can't figure out a way to make it so that after the code is done with the interrupt it goes straight to the start of the main loop. Is that even possible? I've watched countless YT videos on the topic, but still no answer.

Just about anything is possible.

Start small and go one step at a time. Learn to read buttons, program timed actions without using delay(), etc. Interrupts are for experts, so avoid those until you have all of the other things down cold. Plenty of tutorials on line, and in the Arduino IDE.

1 Like

So, you are saying that I can make my project work without interrupts?

Yes

And, could you maybe explain to me how that would work?

Get some basics working and well understood, before worrying about such off the wall questions. Don't forget to use code tags when posting and asking about code.

Scan your switches every ≈ 20ms.

When you detect a change in switch state, act on that new switch state.

Example:

//TIMER examples

#include <Servo.h>
Servo myservo;

#define enabled                  true
#define disabled                 false

#define PUSHED                   LOW
#define notPUSHED                HIGH

const byte heartbeatLED        = 13;
const byte startSwitch         = 2;     //+5V---[50k INPUT_PULLUP]---[switch]---GND
const byte servoPin            = 3;

bool servoFlag                 = disabled;

byte lastStartSwitchStatus;

//**********************************
//timing stuff
unsigned long servoMillis;
unsigned long heartbeatMillis;
unsigned long switchMillis;


//***************************************************************************
void setup()
{
  pinMode(startSwitch, INPUT_PULLUP);

  pinMode(heartbeatLED, OUTPUT);

  myservo.attach(servoPin);
  myservo.write(0);

} //END of setup()


//***************************************************************************
void loop()
{
  //*********************** 1/2 second TIMER
  //time to toggle the heartbeatLED ?
  if (millis() - heartbeatMillis >= 500)
  {
    //restart the TIMER
    heartbeatMillis = millis();

    //toggle the LED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //*********************** 20ms TIMER
  //time to check the switches and sensors ?
  if (millis() - switchMillis >= 20)
  {
    //restart the TIMER
    switchMillis = millis();

    checkSwitches();
  }

  //***********************  2 second TIMER
  //if we are timing, has the 2 seconds servo TIMER expired ?
  if (servoFlag == enabled && millis() - servoMillis >= 2000)
  {
    servoFlag = disabled;

    //move the servo to 0'
    myservo.write(0);

  }


  //*********************************
  //Other non blocking code goes here
  //*********************************

} //END of loop()


//***************************************************************************
//the following switches and sensors will be tested every 20ms
void checkSwitches()
{
  byte switchStatus;

  //****************************************
  //start switch
  switchStatus = digitalRead(startSwitch);

  //**********************
  //was there a change in switch state ?
  if (lastStartSwitchStatus != switchStatus)
  {
    //update to the new state
    lastStartSwitchStatus = switchStatus;

    //*************
    if (switchStatus == PUSHED)
    {
      //move the servo to 180'
      myservo.write(180);

      //restart the servo TIMER
      servoMillis = millis();

      //enable the servo TIMER
      servoFlag = enabled;

    }

  } //END of  if (lastStartSwitchStatus != switchStatus)


  //****************************************
  //other switch and sensor code
  //****************************************

} //END of checkSwitches()


//***************************************************************************

1 Like

How to write Timers and Delays in Arduino has a millisDelay class that keeps track of if the timer is running or stopped. That would remove some of the flags and simplify the if statements.
Multi-tasking in Arduino covers structuring your code at tasks to use millis timers
multitaskingDiagramSmall

1 Like

It's really a Catch-22. It takes experience to use interrupts correctly and reliably. But, you don't get to be experienced until you try it, bugger it up, and get burnt a few times.

2 Likes

I think it is possible (not to say advisable) with 'setjmp/longjmp'. Those functions are supposed to allow you to jump back to a point earlier in the execution of the sketch, such as to the end of setup(). However, it doesn't seem to work properly with the Arduino.

I get all sorts of problems jumping back to the end of setup(). Even a short switch closure is causing MANY interrupts and, for some reason, the loop() function is producing bizarre output. maybe someone else can figure out why it isn't working.

#include <setjmp.h>

jmp_buf BackToTheBeginning;

const byte InterruptPin = 2;
volatile byte InterruptCount = 0;

void setup()
{
  Serial.begin(115200);
  delay(200);
  Serial.println("Start of setup().");

  pinMode(InterruptPin, INPUT_PULLUP);

  // Return to here after an interrupt
  setjmp(BackToTheBeginning);

  attachInterrupt(digitalPinToInterrupt(InterruptPin), myISR, FALLING);
  Serial.println("End of setup().");
}

void loop()
{
  Serial.println("Start of loop().");

  // Send a number every half a second
  for (int i = 0; i < 10; i++)
  {
    Serial.print(InterruptCount);
    Serial.print(' ');
    Serial.println(i);
    delay(500);
  }

  Serial.println("End of loop().");
}

void myISR()
{
  detachInterrupt(digitalPinToInterrupt(InterruptPin));

  InterruptCount++;

  longjmp(BackToTheBeginning, 1);
}

This looks very promising, I will look into it. Another thing that came to my mind was to use the goto function, but apparently it doesn't work between different functions. (example below)

void loop()
    {
      ....
      REACH:
      ......
    }

    void function()
    {
    goto REACH ;
    }

Any idea why that is?

This is the code that I have at the moment. It's just to test if my idea works, so it's not the final product.

#include <FastLED.h>

#define NUM_LEDS 48
#define DATA_PIN 4
CRGB leds[NUM_LEDS];

volatile int button = 1;

void setup() {
  FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);
  Serial.begin(9600);
  pinMode(2,INPUT);
  pinMode(3,INPUT);
  
  attachInterrupt(0,change1,RISING);
  attachInterrupt(1,change2,RISING);

  
  Serial.println("end setup");
}

void loop() {
  switch(button){
    case 1:
      acc1();
      break;

    case 2:
      acc2();
      break;
  }

}

void change1(){
  button = 1;
  Serial.println("BUTT1");
}

void change2(){
  button = 2;
  Serial.println("BUTT2");
}

void acc1(){
  Serial.println("acc1");
  // Turn the LED on, then pause
  leds[0] = CRGB::Red;
  FastLED.show();
  delay(500);
  // Now turn the LED off, then pause
  leds[0] = CRGB::Black;
  FastLED.show();
  delay(500);
  
}

void acc2(){
  Serial.println("acc2");
  // Move a single white led 
   for(int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed = whiteLed + 1) {
      // Turn our current led on to white, then show the leds
      leds[whiteLed] = CRGB::White;

      // Show the leds (only one of which is set to white, from above)
      FastLED.show();

      // Wait a little bit
      delay(100);

      // Turn our current led back to black for the next loop around
      leds[whiteLed] = CRGB::Black;
  }
}

I am happy to announce that it's working as I want it to, except for one part. When I press another button to change the routine the leds go through, the code waits for the current routine to end and only after that it changes to the next one. I don't know if I explained my problem properly so if you guys have any questions you are more than welcome to ask :)).

@johnwasser leaving the propriety and other arguments about using setjmp/longjmp aside, it is unfair to criticize it until you have employed it correctly.

google or consult your favorite reference, there’s a bit more to it than you’ve coded.

I have tried it on the the Arduino, just because I was surprised to find it available, and had used it elsewhere. It is a mechanism that def has its place.

a7

I've also tried to implement it in my code but it kept doing weird stuff.

I can make a simple setjmp/longjmp example work fine here.

Arduino UNO and Nano, Mac OSX, IDE 1.8.7 which may make no difference.

I cannot get a more ambitious and complete example, which I hoped to provide as a disservice to y'all to function on either board.

Same behaviour on both: starts off fine, then goes a bit cray-cray. Imma find out if anyone has done anything real with setjmp/longjmp; I allow for the possibility that I have an unrelated error in my code.

I haven't decided it is worth using more sleep over… for now, not in my bag o' Arduino tricks.

a7

1 Like

Labels are local to the function. You can't reference a label in another function just like you can't reference a local variable in another function.

If I have employed it incorrectly, please let me know how to correct my mistake.

http://www.cplusplus.com/reference/csetjmp/setjmp/
http://www.cplusplus.com/reference/csetjmp/longjmp/
I don't see anything in this documentation that indicates that I have not "employed it correctly". I don't need to know if the setjmp() is returning the first time (returns 0) or because of longjmp() (returns non-zero) so I don't check the return value.

@johnwasser well I never argue with success. Since it works for you, I’ll leave it at that.

Oh, wait, it doesn’t work for you:wink:

But your unusual, like I never seen it done, use of the mechanism may be part of the trouble. The question is can a single setjmp created object be longjmp’d at multiple times.

As much googling as I am going to do on your account suggests that it is probably OK. But it is trivial to re-execute the setjmp for each single longjmp it is expected to accommodate. Why not cut out even the tiniest probability that you inadvertently raise up the circumstances in which a second (or Nth) longjmp goes bad?

I find more ink on how to not use setjmp/longjmp than on whether it works on the UNO or any of the small Arduino boards. And plenty of admonishments to just not do.

I have used it in on processors from the smallest to the largest, not many times, but it does have a place. It vexes me that I cannot roll up a simple example that works. When

  Serial.print(count);

where count is a sixteen bit integer prints 7 digit numbers, you know something has gone terribly wrong.

As I said before, if I can make a little more progress I may bother the heavies with the matter. My Arduino logic looks fine, and runs well as a standalone C program, the big difference and I suspect deal breaker is in the interrupt mechanism, which I had to simulate on the desktop version.

Sadly, my idea of fun. :expressionless:

a7

Done. I also added more output and more delays but I still get bizarre output from the 'for' loop. It is still printing values above 65535 for a SIGNED int. My guess is that setjmp is not saving enough registers.

Start of setup().
Attaching interrupt
setjmp returned 10
Attaching interrupt
Interrupt attached
End of setup().
Start of loop().
1 0
1 1
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
End of loop().
Start of loop().
1 0
1 1
1 2
1 3
1 4
1 5
1 6
1 7
1 8
setjmp returned 10
Attaching interrupt
setjmp returned 10
Attaching interrupt
Interrupt attached
End of setup().
3 65537
3 65538
3 65539
3 65540

The current sketch:

#include <setjmp.h>

jmp_buf BackToTheBeginning;

const byte InterruptPin = 2;
volatile byte InterruptCount = 0;

void setup()
{
  Serial.begin(115200);
  delay(200);
  Serial.println("Start of setup().");

  pinMode(InterruptPin, INPUT_PULLUP);

  // Return to here after an interrupt
  int r;
  while ((r = setjmp(BackToTheBeginning)) != 0)
  {
    Serial.print("setjmp returned ");
    Serial.println(r);
    delay(1000);
  }

  Serial.println("Attaching interrupt");
  delay(1000);
  attachInterrupt(digitalPinToInterrupt(InterruptPin), myISR, FALLING);
  Serial.println("Interrupt attached");
  delay(1000);

  Serial.println("End of setup().");
  delay(1000);
}

void loop()
{
  Serial.println("Start of loop().");
  delay(1000);

  // Send a number every half a second
  for (int i = 0; i < 10; i++)
  {
    Serial.print(InterruptCount);
    Serial.print(' ');
    Serial.println(i);
    delay(500);
  }

  Serial.println("End of loop().");
  delay(1000);
}

void myISR()
{
  detachInterrupt(digitalPinToInterrupt(InterruptPin));

  InterruptCount++;

  longjmp(BackToTheBeginning, 10);
}

@johnwasser Nice work. That's an MRE for the big boys.

But setjmp/longjmp work perfectly; the finger is now firmly pointed at the interrupt mechanism messing it up.

Oops! Spoke too soon. I do get better, nearly what I expected behaviour when I don't use interrupts, but now have some truly bizarre output, not seen in the "desktop" version, that is inexpressibly beyond inexplicable.

So sry, I was really hoping that investigation of faking the interrupt would demonstrate that your original use of setjmp/longjmp would otherwise be OK.

The interrupt thing points up a problem with the implementation of setjmp/longjmp, which may or may not be quite all that is wrong with it.

Given the general and understandable distaste for use of the "non-local goto", I dunno if it is worth more of anyone's time; I will probably still look for anyone who has successfully done something with it on these little machines…

And figure out why things can get printed out of order, yikes.

a7