Help with attachInterrupt

Hello everyone. In this simplified sketch, I wish a simple routine (for you, not for me ;)) to get out of the 'while' loop (or another kind of loop). It would be appreciated the replacement of 'delay' to use that same routine in 'loop'.

const int open_LSW_interrupt_Pin = 20;
const int close_LSW_interrupt_Pin = 21;

void setup()
{
  ...
  //  function for creating external interrupts at pin20 on FALLING (HIGH to LOW)
  attachInterrupt(digitalPinToInterrupt(20), open_LSW_interrupt, FALLING);
  //  function for creating external interrupts at pin21 on FALLING (HIGH to LOW)
  attachInterrupt(digitalPinToInterrupt(21), close_LSW_interrupt, FALLING);

  CLOSE_VALVE();

  // When close_LSW signal goes to LOW then go to PAUSE_VALVE() function and follow setup. 
 while(true){  // NOT EXIT !!!
      Serial2.print(".");
      delay(1000);
  }  
   ...
}

void close_LSW_interrupt()  //ISR function excutes when close_LSW signal goes to LOW
{
  PAUSE_VALVE();
  Serial2.print("CLOSE LSW interrupt");
}

Thanks for your time and attention.

Why do you think that you need to use an interrupt to exit from a while loop ?

not sure you understand that a typical Arduino program is composed of a setup() invoked once at startup and loop() executed repeatedly. there's no need for a loop in setup()

but it's not clear what your program is attempting to do, besides the program having several undefined symbols (CLOSE_VALVE(), PAUSE_VAVE(), Serial2)

a "simple" program would configure two pins as INPUT_PULLUP and use them to control an pin as an OUTPUT to affect a "valve"; one pin opens and the other closes it

Hello Breixo

Don´t use while() function or hidden while() inside the delay() function.

Excuse me, I have been too concise.

I want to control a 4-way valve that has 2 limit switches, one clockwise 'OPEN' and one counterclockwise 'CLOSE'.

In the 'setup', I want to activate the closing of the valve until the limit switch opens (drops to LOW). It occurred to me to use a 'while' and 'delay' but it doesn't work, actually, my request is between [ ]:

//  In the 'setup'
-  CLOSE_VALVE(); // Active CLOSE of the valve 

// This is the mini routine that I haven't been able to implement:
[Here I have to go into an endless routine until the 'CLOSE' limit switch opens which activates the 'close_LSW_interrupt' interrupt]
// End

// In the 'loop':
- close_LSW_interrupt();  // This interrupt routine activates the PAUSE of the valve.

I'm embarrassed to include the sketch, I'm very rookie and very old. If it were essential, I would try to reduce it and embellish it.

Thanks again.

consider following which outlines what you've described
presume there are multiple outputs used to control the valve in either direction and both a clockwise and ccw limit switches can be connected to a single input

const byte PinSw    = A1;
const byte PinValve = LED_BUILTIN;

enum { CCW = HIGH, CW = LOW };
enum { Open = HIGH, Closed = LOW };

// -----------------------------------------------------------------------------
void
valveCcw (void)
{
    // set outputs as required to move valve CCW
}

void
valveCw (void)
{
    // set outputs as required to move valve CW
}

void
valveStop (void)
{
    // set outputs as required to stop valve
}

// -----------------------------------------------------------------------------
void
valveClose (void)
{
    valveCcw ();
    while (Open == digitalRead (PinSw))
        ;
    valveStop ();
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    valveClose();
}

// -----------------------------------------------------------------------------
void loop ()
{
    // monitor inputs request valve be open/closed
}

You don't need interrupts.

You can use them, but it needlessly complicates a simple task.

Tanks and Arduinos are the elements in many real projects, as well as being the subject matter for students learning to code.

Been done and done and done, you'll find many examlpes.

I'll try to find the thread most recently where this was done.

Meanwhile google

 arduino tank control hysteresis

and poke around for awhile.

a7

I'm sure I don't understand what a 4-way valve with two limit switches is, or does.

I'm sure I have no idea what all you need to do with this valve, or for what stimulus you might want to change the position of the valve which I don't see code for doing in anythnign you posted.

So tell me more, and more about the real problem this code will eventually be used to solve.

Also Do you have a link to the valve you are using?

TIA

a7

Hi, alto777,
I'm sorry I wasn't able to explain the help requested. Let's see if I can get it now.

The hardware (my speciality) of the project is already finished and is about replacing a very old (30 years old) radiant floor heating controller.
The mechanical part is basically a 4-way mixing valve activated by a geared motor that rotates 90º left and right (it takes 120" to complete 90º in either direction) mixing the water from the boiler with the return water .
To avoid breaking the coupling between the valve and the gearmotor there are two limit switches. A drive pump completes the mechanical part.

The electronic part is basically an Arduino that measures the outside temperature, calculates the temperature that the water mixture should have, compares it with the one being driven and, depending on the temperature difference, sends the left turn signal. (CLOSE) or to the right (OPEN) or stops the gear motor that controls the valve, in movements that can last between 2" and 120".

My request on how to use 'attachInterrupt' is because the execution times of the open, close and stop maneuvers are very long and I am trying to replace the 'delay' with 'millis()', hence my interest in a routine within the setup capable of sending an initial CLOSE signal (which can last from 0 to 120") and activate the closing interruption to stop the maneuver without the 'delay(120000)'. Although it is true that it could implement a control of the state of the switch, I had assumed that it would be, more or less, the same to stop any opening or closing maneuver within a 'millis()' loop.

In other words, how could you use 'attachInterrupt' to in simple OPEN_VALVE() and CLOSE_VALVE() routines, handled by 'millis()' so that when either interrupt is triggered, it jumps to PAUSE_VALVE() and start another 'millis()' count?.

I think I'm messing it up, if I haven't been able to explain what help I need, just don't reply, I'll understand and I'll continue to thank you for your time and attention.

Hello Breixo

Design a timer using the millis() function to have a with a start/stop function.

Have a nice day and enjoy coding in C++.

No. Just needlessly complicating it. :wink:

Managing a process that involves time and digital inputs and outputs is straightforward, and can usually be done without interrupts.

@paulpaulson and @gcjr have shed some light on this, basically you are being led to employ the IPO model.

Input. Gather all relevant data. Switch states and time

Process. Using the switch states and time since develop the desired motor control signals and update timer(s)

Output. Set the motor going or not going, left or right, whatever.

Rinse and repeat.

The loop() runs very rapidly, these steps take, to a first approximation zero time, so some ppl would add a delay to make the whole thing run at, say, 20 times a second (dealy(50)).

Which will neatly debounce the limit switches as a side effect.

With thanks and a tip o' the hat to @paulpaulson.

a7

1 Like

don't understand what this is trying to say
don't understand why both interrupt and millis()

assume there's no need for separate inputs from both limits switch, the valve needs to be stopped in both cases. An interrupt could be used to invoke the stop function on the falling edge from the limit switches. but the limit switches could monitor the limit switches and just quickly stop the motor as well.

don't understand the need for anything timed with millis(). presumably the temperature is monitored in loop() and some function called to start turning the valve in one direction or the other.

a flag might be used to track the position of the valve to determine whether it needs to be energized. Stopping the motor would also need to set/toggle the flag

So, you are trying to modulate the valve between full open and full closed
depending on temperature differential but stop the valve at end limits.
Is that correct?

Hello Breixo

consider

#define usl unsigned long
enum Action {Start, Stopp};
constexpr int InputPins[] {A0, A1};
constexpr int OutputPin {9};
constexpr usl OpenValveDelay {5000};
struct TIMEREVENT
{
  int startPin;
  int stoppPin;
  int outputPin;
  usl stamp;
  usl duration;
  void makeTimer (int startPin_, int stoppPin_, int outputPin_)
  {
    startPin = startPin_;
    stoppPin = stoppPin_;
    outputPin = outputPin_;
    pinMode (startPin, INPUT_PULLUP);
    pinMode (stoppPin, INPUT_PULLUP);
    pinMode (outputPin, OUTPUT);
  }
  void startTimer (usl currentMillis_, usl duration_)
  {
    digitalWrite(outputPin, HIGH);
    stamp = currentMillis_;
    duration = duration_;
  }
  void stoppTimer()
  {
    digitalWrite(outputPin, LOW);
  }
  void runTimer (usl currentMillis_)
  {
    if (currentMillis_ - stamp >= duration && digitalRead(outputPin) == HIGH) stoppTimer();
  }
} openValve;
void setup()
{
  openValve.makeTimer(InputPins[Start], InputPins[Stopp], OutputPin);
}
void loop()
{
  usl currentMillis = millis();
  if (digitalRead(openValve.startPin) ? LOW : HIGH == HIGH) openValve.startTimer(currentMillis, OpenValveDelay);
  if (digitalRead(openValve.stoppPin) ? LOW : HIGH == HIGH) openValve.stoppTimer();
  openValve.runTimer(currentMillis);
}

Have a nice day and enjoy coding in C++.

//  thermostat controlled valve

const byte PinTemp        = A0;
const byte PinLimitOpened = A1;
const byte PinLimitClosed = A2;

const byte  PinMotorA = 12;
const byte  PinMotorB = 11;

int tempThresh = 70;
int TempHyst   = 1;

enum { Closed, Opened };
byte valvePos;

byte butLst;

// -----------------------------------------------------------------------------
void valveClose (void)
{
    Serial.println (__func__);
    digitalWrite (PinMotorA, HIGH);
    digitalWrite (PinMotorB, LOW);
}

void valveOpen (void)
{
    Serial.println (__func__);
    digitalWrite (PinMotorA, LOW);
    digitalWrite (PinMotorB, HIGH);
}

void valveStop (void)
{
    Serial.println (__func__);
    digitalWrite (PinMotorA, LOW);
    digitalWrite (PinMotorB, LOW);
}

// -----------------------------------------------------------------------------
void loop ()
{
    int temp = analogRead (A0);

    if (Closed == valvePos)  {
        if (LOW == digitalRead (PinLimitOpened))  {
            valveStop ();
            valvePos = Opened;
        }
        else if ((tempThresh - TempHyst) > temp)
            valveOpen ();
    }
    else if (Opened == valvePos)  {
        if (LOW == digitalRead (PinLimitClosed))  {
            valveStop ();
            valvePos = Closed;
        }
        else if ((tempThresh + TempHyst) < temp)
            valveClose ();
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    pinMode (PinMotorA, OUTPUT);
    pinMode (PinMotorB, OUTPUT);

    pinMode (PinLimitOpened, INPUT_PULLUP);
    pinMode (PinLimitClosed, INPUT_PULLUP);
}

I wrote this simulator simulation:

motorCW(), motorCCW() and motorStop() do the obivous.

I fed back from the simulation some limit switches you will find on digital inputs 6 and 7.

So you have control of the motor, and can read the limit switches.

I wrote a deomonstration primitive sweeper in the blank space, that is where you have full loop speed and 99.9 percent of the processor left over for the real functionality you want to dream up.

// https://wokwi.com/projects/358461378751133697
// https://forum.arduino.cc/t/help-with-attachinterrupt/1097996

# define leftSwitch   6
# define rightSwitch  7

void setup()
{
  mySetup(115200);
  setupSimulation();
  xprintf("\nHello World.\n\n"); 

// your setup stuff here

  pinMode(leftSwitch, INPUT_PULLUP);
  pinMode(rightSwitch, INPUT_PULLUP);
  
// primitive: start motor moving

  motorCW();
}

void loop()
{
// service the simulated motor/limit switches  
  loopSimulation();

// your loop stuff here, non-blocking please!

// primitive: sweep by limits, for awhile

  unsigned long now = millis();

  unsigned char leftLimit = digitalRead(leftSwitch);
  unsigned char rightLimit = digitalRead(rightSwitch);

  if (leftLimit) motorCW();
  if (rightLimit) motorCCW();

  if (now > 15000) {
    motorStop();
    xprintf("That's All,Folks!\n");
    
    while (1); // die here
  }
}

// here be dragons...
// simulate motor and limit switches
# include <Servo.h>

# define servoPin A0
# define leftLED  A1
# define rightLED A2

Servo brocolli;
unsigned long now;

void setupSimulation()
{
  brocolli.attach(servoPin);

  brocolli.write(0);
  delay(500);

  brocolli.write(180);
  delay(500);

  brocolli.write(0);
  delay(500);

  brocolli.write(90);

  pinMode(leftLED, OUTPUT);
  pinMode(rightLED, OUTPUT);
}

void loopSimulation()
{
  now = millis();

  limitLEDs();
  motor();
}

// primitive sweep by limit swtches
void testSimulation()
{
  motorCCW();
  while (digitalRead(leftLED) == LOW) {
    motor();
    limitLEDs();
  }

  motorCW();
  while (digitalRead(rightLED) == LOW) {
    motor();
    limitLEDs();
  }
}

void limitLEDs()
{
  int position = brocolli.read();

  digitalWrite(leftLED, position > 170 ? HIGH : LOW);
  digitalWrite(rightLED, position < 10 ? HIGH : LOW);
}

static int motorDirection;

void motorCW()
{
  motorDirection = -1;
}

void motorStop()
{
  motorDirection = 0;
}

void motorCCW()
{
  motorDirection = 1;
}

void motor()
{
  static unsigned long lastMotorPoke;
  static int motorPosition = 90;

  now = millis();

  if (!motorDirection) return;

  if (now - lastMotorPoke < 50) return;
  lastMotorPoke = now;

  motorPosition += motorDirection << 2;
  motorPosition = constrain(motorPosition, 0, 180);

  brocolli.write(motorPosition);
}

// 

// programmer misses printf...

void xprintf(const char *format, ...)
{
  char buffer[256];
  va_list args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end(args);
  Serial.print(buffer);
}

// programmer forgets day of week, version

void mySetup(unsigned long bandRate)
{
  Serial.begin(bandRate);
  
  char s[] = __FILE__;
  byte b = sizeof(s);
  while ( (b > 0) && (s[b] != 47)) b--;
  char *u = s + b + 1;
 
  xprintf("\nHEllo WOrld!\n");
  xprintf("%s  %s\n\n", __DATE__, u);

  xprintf("!\n");
}

Here find the simulated simulator.

You can marvel at the simulated valve motor and limit switches, or just ignore it and focus on the loop() function.

The only caveat is that the loop() must not be blocked, and must always call loopSimulation() so the mechanism supporting the simulated valve motor and limit switches is able to advance.

In the real code, you would need to call motor() at loop speed, the function that respects motor direction and makes motor move or not. It is designed to always be called, and is what gives you the "hands off" auotmaticity you seek.

Remains: an error or warning if the motor is hard driven past the limit switches - in real life, this might be implemented by a timer what says it's been a long time, should have reached the limit by now...

a7

OK, sry. Coding under the influence.

This loop might better show that at the high level, one only needs to set the motor in motion and monitor the limit switches to stop (and maybe reverse) it when it hits one.

Maybe not. Still trying to make a simple loop.

void loop()
{
  static unsigned char once;
  if (!once) {       // once only : set the motor going CW
    motorCW();
    once = 1;
  }

// service the simulated motor/limit switches  
  loopSimulation();

// your loop stuff here, non-blocking please!

// primitive: stop the motor when it reaches the limit switch
// reverse and go the other way, stop and do nothing more

  unsigned char leftLimit = digitalRead(leftSwitch);
  unsigned char rightLimit = digitalRead(rightSwitch);

  if (rightLimit) {
    motorStop();
    motorCCW();
  }

  if (leftLimit)
    motorStop();

// rest of loop is running full speed.
}

I'll be with the Umbrella Academy who are meeting later not at the beach, maybe before they get too far I can get some help from them.

I'm sure this could be better hidden with language features I am still not really using. Well.

a7

With a little help from my friends, the simulated simulation is done.

See the plain and literal loop(). It goes to some trouble and verbosity to clearly separate the IPO phases. Obvsly not clever.

Most of the junk that supports the demo is below the fold.

// https://wokwi.com/projects/358508660395976705
// https://forum.arduino.cc/t/help-with-attachinterrupt/1097996

# define leftSwitch   6
# define rightSwitch  7

# define sensorPin      A3

void setup()
{
  pinMode(leftSwitch, INPUT_PULLUP);
  pinMode(rightSwitch, INPUT_PULLUP);

// set up the simulation environment
  setupSimulation();
}

// motor state constants
enum {STOPPED, CW, CCW};

# define COLD 128
# define HOT  896

void loop()
{
// system state variable
  static unsigned char motorState = CW;

// Input
  unsigned char leftLimit = digitalRead(leftSwitch);
  unsigned char rightLimit = digitalRead(rightSwitch);

  unsigned int theTemperature = analogRead(sensorPin);

// Process
  if (theTemperature < COLD)
    motorState = CW;

  if (theTemperature > HOT)
    motorState = CCW;

  if (motorState == CCW && leftLimit) motorState = STOPPED;
  if (motorState == CW && rightLimit) motorState = STOPPED;

// Output
  if (motorState == STOPPED) motorStop();
  else if (motorState == CW) motorCW();
  else if (motorState == CCW) motorCCW();

// give the simulation loop a shot
  loopSimulation();
}

// here be dragons... run the simulated valve and limit switches
// simulate motor and limit switches
# include <Servo.h>

# define servoPin A0
# define leftLED  A1
# define rightLED A2

Servo brocolli;

static unsigned long now;

void setupSimulation()
{
  Serial.begin(115200);
  Serial.println("\nHEllo WOrld!\n");

  brocolli.attach(servoPin);

  brocolli.write(90);

  pinMode(leftLED, OUTPUT);
  pinMode(rightLED, OUTPUT);
}

void loopSimulation()
{
  now = millis();
  limitLEDs();
  motor();
}

// primitive sweep by limit swtches
void testSimulation()
{
  motorCCW();
  while (digitalRead(leftLED) == LOW) {
    motor();
    limitLEDs();
  }

  motorCW();
  while (digitalRead(rightLED) == LOW) {
    motor();
    limitLEDs();
  }
}

void limitLEDs()
{
  int position = brocolli.read();

  digitalWrite(leftLED, position > 170 ? HIGH : LOW);
  digitalWrite(rightLED, position < 10 ? HIGH : LOW);
}

static int motorDirection;

void motorCW()
{
  motorDirection = -1;
}

void motorStop()
{
  motorDirection = 0;
}

void motorCCW()
{
  motorDirection = 1;
}

void motor()
{
  static unsigned long lastMotorPoke;
  static int motorPosition = 90;

  if (!motorDirection) return;

  if (now - lastMotorPoke < 75) return;
  lastMotorPoke = now;

  motorPosition += motorDirection << 2;
  motorPosition = constrain(motorPosition, 0, 180);

  brocolli.write(motorPosition);
}

My idea of fun. I have no life.

Play with it here. The slide fader is proxy for the measured temperature.

a7

Hi alto777, thank you very much!.

Absolutely impressed. Until yesterday I was stuck with 'interrupts', I was looking at a couple of pages about the IPO model and it seemed so simple and great, I don't understand how it didn't occur to me. :wink:.
What has been absolutely total is the example in Wokwi. I was not aware of this simulation tool.

Many thanks to you and your friends for your time and attention.

That's what we here for, glad it landed.

I am compelled to point out the ease with which a well-mannered sketch can handle two things at once; here we see it handling your thing, whilst simultaneously taking care of the proxy simulated elements that allow for convincing testing of that. And an entertaining display of all that in action.

Like I say, my idea of fun.

You can appreciate I hope how insertion of any tasks that do not play by the rules would take this whole thing down.

As it is, the loop runs at 1000 Hz, measured by placing a toggle pin in the loop at the top, viz:

	digitalWrite(9, !digitalRead(9));  // loop frequency/2 heartbeat

which actually seems kind of slow, so I will be looking at what is taking so much processing time. One thing it isn't are calls to delay().

Also, my goal was to create a blank space where anyone's solution could be tested. If I have time between now and getting out to the beach, I may give my friend's ideas a shot.

a7