LC: multiple interrupts on one pin

Status
Not open for further replies.

anku

Member
Hello,
I have a soft-off/emergency switch that, when triggered, should pause a blocking routine (multistepper) immediately. That said, I might miss some basic understanding about interrupts, but here's what I did:

In setup():
Code:
attachInterrupt(SafeOffPin, offRising, RISING);
attachInterrupt(SafeOffPin, offHigh, HIGH);
attachInterrupt(SafeOffPin, offFalling, FALLING);
then
Code:
void offRising(){
   Serial.println("offRising");
}
void offHigh(){
   Serial.println("offHigh");
}
void offFalling(){
   Serial.println("offFalling");
}

When I do that, only offFalling() is executed. But it would be nice if there was some way to wrap some definite start and end around the pause, basically to display a message and release some enable pins on motor drivers. Any ideas?

best, Philip
 
What I would do, is extend multistepper to support a global volatile flag, that is initialized to zero in the user init() function, and set in the interrupt function.

Necessary changes to MultiStepper.cpp include replacing the last two functions with
Code:
external volatile unsigned char  emergency_stopped;

// Returns true if any motor is still running to the target position.
boolean MultiStepper::run()
{
    uint8_t i;
    boolean ret = false;
    for (i = 0; i < _num_steppers; i++)
    {
	if ( _steppers[i]->distanceToGo() != 0)
	{
	    if (!emergency_stopped)
	    {
		_steppers[i]->runSpeed();
	    }
	    ret = true;
	}
    }
    return ret;
}

// Blocks until all steppers reach their target position and are stopped
void    MultiStepper::runSpeedToPosition()
{ 
    while (run() && !emergency_stopped)
	;
}

// Blocks until all steppers reach their target position and are stopped
void    MultiStepper::runSpeedToPosition()
{ 
    while (!emergency_stopped && run())
	;
}
and probably two functions in AccelStepper.cpp ,
Code:
external volatile unsigned char  emergency_stopped;

// Implements steps according to the current step interval
// You must call this at least once per step
// returns true if a step occurred
boolean AccelStepper::runSpeed()
{
    // Do nothing if emergency stopped
    if (emergency_stopped)
	return false;

    // Dont do anything unless we actually have a step interval
    if (!_stepInterval)
	return false;

    unsigned long time = micros();   
    if (time - _lastStepTime >= _stepInterval)
    {
	if (_direction == DIRECTION_CW)
	{
	    // Clockwise
	    _currentPos += 1;
	}
	else
	{
	    // Anticlockwise  
	    _currentPos -= 1;
	}
	step(_currentPos);

	_lastStepTime = time; // Caution: does not account for costs in step()

	return true;
    }
    else
    {
	return false;
    }
}
and
Code:
// Blocks until the target position is reached and stopped
void AccelStepper::runToPosition()
{
    while (!emergency_stopped && run())
	;
}

In your own code, you declare the flag,
Code:
volatile unsigned char  emergency_stopped;
clear it in your init() function using
Code:
emergency_stopped = 0;
before initializing the steppers; you can also do that whenever the emergency stop is canceled (so it acts more like an immediate "pause" than stop),
and in your interrupt function that gets called whenever the emergency stop is enabled, add
Code:
emergency_stopped = 1;

It only uses one additional byte of RAM, and a tiny bit of Flash/ROM.

The idea is that the flag acts like a big red "DON'T DO IT!" to the multistepper library. It is marked extern in the MultiStepper files, because it is not declared there (it is declared in your own code); and it is also marked volatile to tell the compiler that its value can change at any time, so must not be cached. "Caching" in the compiler context means that if it sees it used in a while loop condition, means that even if it sees that the loop body does not change the value, it cannot just check it once before the loop; it must check it every loop iteration.

Normally, such flags are made internal to the class. Here, I deliberately made it part of your own code, accessed "externally" by the modifications in the multistepper library, as it is a kinda "global" feature.

It would not be difficult to think about this for a bit, then submit a patch to the maintainer (Mike McCauley at AirSpayce), to implement it more properly. Probably, the AccelStepper class should have a public volatile flag, say stopped , that the above functions access; cleared in the constructor, and set in the user emergency stop function.

However, I do not use AccelStepper/MultiStepper myself, and haven't even bothered to check if the above code works: I'm too lazy, sorry.
 
only offFalling() is executed
That's because attachinterrupt can't set multiple tests for the state of one pin. Each call to attachInterrupt for a particular pin replaces whatever was previously set. Your last call was to test for FALLING which replaces what was there before and only a FALLING state on the pin will trigger an interrupt.
You can, however, use CHANGE which will cause an interrupt on a FALLING or RISING state. The interrupt routine can then examine the state of the pin to determine whether it was a rising or falling state which caused the change. The interrupt routine can also keep track of whether the pin is LOW or HIGH.

Pete
 
Using CHANGE is probably the best way.

You could also connect the same signal to multiple pins and use attachInterrupt on each pin. However, if you use HIGH and your function returns while the signal is still high, you can expect that interrupt to run again immediately, very likely preventing any others from running.

If this feature is needed in emergency situations where lives may be at risk, you probably should have a physical switch which is certain to disconnect power even if the software has crashed with interrupts disabled.
 
Wow, thank you so much for your thorough answers!
@Nominal Animal: Your answer goes a little beyond the scope of my little problem, but I'll definitively keep it in mind for later polishing of the project-- I want to simply be able to disable the stepper drivers at any time. When I do it while the steppers are in motion, they'll loose their position and that is where your approach would make an excellent solution.

I was not aware that there is only one interrupt per pin, but the use of CHANGE might just cover everything needed. And I'll make shure not to have the fate of mankind be dependent solely on my code, I swear =)

So, here's what works for me:
somewhere early:
Code:
#define SafeOffPin 10       // Int
volatile bool safeOffState; // Store the SafeOff Btn state

In setup()
Code:
pinMode(SafeOffPin, INPUT_PULLUP);
attachInterrupt(SafeOffPin, offChange, CHANGE);

somewhere else:
Code:
void offChange(){
   if(debug) Serial.println("offChange");

   if(!digitalRead(SafeOffPin)){                // [Machine ON] (knob right)
      if(safeOffState){                         // Was [OFF] before
         if(debug) Serial.println("Machine ON");
         digitalWrite(ALL_EN_PIN, LOW);         // Enable stepper drivers
         mcp.digitalWrite(powerLED, HIGH);
         mcp.digitalWrite(InOutLED, HIGH);
         updateLcd(0,true);                     // draw all items
         safeOffState = false;
      }
   }
   else{                                        // [Machine OFF] (knob left)
      if(!safeOffState){                        // Was [ON] before
         if(debug) Serial.println("Machine SoftOFF");
         digitalWrite(ALL_EN_PIN, HIGH);        // Disable stepper drivers
         mcp.digitalWrite(powerLED, LOW);
         mcp.digitalWrite(InOutLED, LOW);
         updateLcd(24, true);                   // Msg: "save to cut power now"
         safeOffState = true;
      }
   }   
}
 
Status
Not open for further replies.
Back
Top