Interrupt question

Hi,
I'm using a pin interrupt to trap a button press. Inside the ISR I would like to set a delay to counter switch bounce. I understand millis() won't work, can I use micros()..? If not, how else might I create a delay or at least get access to the Arduino's clock for timing purposes..?

Thanks! :slight_smile:

Inside the ISR I would like to set a delay

No, you wouldn't.

can I use micros()..?

No. You can't delay at all.

What you CAN do is use millis() (or micros()) to record when this interrupt occurred, and compare that to when the last interrupt occurred, and ignore this interrupt if it is too soon after the last interrupt.

I take it you are saying that it would be bad practice for me to try a delay in an interrupt routine ( which I understand ), but that micros() CAN be used, i.e. it will return a valid value..? The delay will be small just a few 10's of milliseconds. I do not want to have to use a delay and your solution is to be preferred. This begs a question: is it possible to interrupt an interrupt routine, or are other interrupts lost within an ISR...? It may happen that my ISR is called and almost immediately an interrupt event occurs again before the currently executing ISR has had a chance to complete...

Thanks.

I take it you are saying that it would be bad practice for me to try a delay in an interrupt routine

Yes.

but that micros() CAN be used, i.e. it will return a valid value..?

Yes. It's that millis() and micros() don't update in an ISR, because they depend on interrupts to update, and interrupts are disabled while your ISR is running.

is it possible to interrupt an interrupt routine

No, it is not.

It may happen that my ISR is called and almost immediately an interrupt event occurs again before the currently executing ISR has had a chance to complete.

There is a queue. It has a length of 1. One interrupt can be pending, for each interrupt type, while your ISR is executing. If you take too long, though, interrupts will be lost.

The attachInterrupt() description in the Arduino Reference makes mention of millis() not updating but not micros(), but it is implied that neither increment and they do return values within an ISR, ok.

So, just to recap, the trick is to immediately grab the value returned by millis() ( or micros() ) when the ISR is called, compare it to a previously stored ( volatile declared variable ) value, if the difference is less than the desired debounce time then drop out of ISR. If it is greater than the debounce time then store this value and execute ISR.

and they do return values within an ISR

They DO return values. It's just that if you have an ISR that takes 10 minutes, at the start and at the end, the values returned by millis() will be the same.

Depending on what you are doing the answer may be not to use an interrupt at all. Can you share with us why you need to use an interrupt to read the button press ?

The attachInterrupt() description in the Arduino Reference makes mention of millis() not updating but not micros()

"millis" will return a value, but not update because it uses an interrupt to count timer overflow. "micros" will return a value and update, because it simply reads a register.
However,

The delay will be small just a few 10's of milliseconds

in an interrupt is not territory you should be going towards.
Record the event, and move on - handle it at a more leisurely pace.

PaulS:

is it possible to interrupt an interrupt routine

No, it is not.

By calling 'sei' an interrupt can be re-entrant or interrupted. However there are concerns regarding an interrupt being entered more times than exited causing a stack overflow. Timing needs to be precise, interrupts firing at random times may be something to avoid with re-entrant code; whereas interrupts with a pre-determined or minimum time between manifestations will provide a stable time slice for a previous call to finish as the AVR has a deterministic execution.

Visit the link in my signature for a comprehensive atomic control solution.

In answer to UKHeliBob, I use a motor start/stop button to generate an interrupt. The ISR decides if the button press is to start or stop the stepper motor. If start, then the ISR attaches a small function to a Timer1 interrupt that simply toggles a pin for the frequency input to the stepper drive. if the motor is running, the ISR will detach the Timer1 interrupt. If there is a better way, I'm open to suggestions... :slight_smile:

Thanks for the explanation but it does not really explain why you need to use an interrupt rather than simply sensing the button press and taking the required action. Can you please post your whole program ? There may be a different way to do what you want but it will not necessarily be better.

Here is the code:

#include <stdio.h>
#include <TimerOne.h>  

// Each segment is defined as a single bit value of '1' in a byte. Bit positions are mapped to pin numbers,
// Bit 7 represents pin 11 running down to bit 0 representing pin 4.
const byte SegG = B10000000; // G = pin 11
const byte SegF = B01000000; // F = pin 10
const byte SegA = B00100000; // A = pin 09
const byte SegB = B00010000; // B = pin 08
const byte SegE = B00001000; // E = pin 07
const byte SegD = B00000100; // D = pin 06
const byte SegC = B00000010; // C = pin 05
const byte SegDP =B00000001; // DP = pin 04

// Individual segment definitions are 'OR'ed to make up the digit definitions. These are then negated so that a '1' means off and
// '0' means on. This is so that when a '0' is written to a pin output, i.e. a particular segment needs to be turned on,
// the pin acts like a sink and draws current to drive the individual LED. This is particular to the display used, which uses a common anode
// where LED current needs to be sunk.
const byte Digit0 = ~( SegA | SegB | SegC | SegD | SegE | SegF);
const byte Digit1 = ~( SegB | SegC);
const byte Digit2 = ~( SegA | SegB | SegD | SegE | SegG );
const byte Digit3 = ~( SegA | SegB | SegC | SegD | SegG );
const byte Digit4 = ~( SegB | SegC | SegF | SegG );
const byte Digit5 = ~( SegA | SegC | SegD | SegF | SegG );
const byte Digit6 = ~( SegA | SegC | SegD | SegE | SegF | SegG );
const byte Digit7 = ~( SegA | SegB | SegC );
const byte Digit8 = ~( SegA | SegB | SegC | SegD | SegE | SegF | SegG );
const byte Digit9 = ~( SegA | SegB | SegC | SegD | SegF | SegG );

byte Digits[]={Digit0,Digit1,Digit2,Digit3,Digit4,Digit5,Digit6,Digit7,Digit8,Digit9};


// Definitions for interrupt pins
#define BUTT_STEP 3 // Stepper start/stop switch interrupt pin
#define ESTOP 2 // Emergency stop interrupt. e.g. safety door opened.

// Definitions for Analog port pins
#define ENC_A 14      // Encoder input A
#define ENC_B 15      // Encoder input B
#define BUTT_LED 16   // Power for button LED 
#define SWCH_DIR 17   // Switch input for direction
#define DIR 18        // Output for direction input to microstepping drive 
#define PUL 19        // Output for frequency input to microstepping drive
#define ENC_PORT PINC // Analog port


void setup()
{
  
//  Serial.begin(115200);
//  Serial.println("Start");
  
  
  // Set display pins for sinking current.
  for( byte n=4;n<=11;n++)
  {
    pinMode(n, OUTPUT);   
    digitalWrite(n,HIGH);
  }
  
  // Setup stepper start/stop button
  pinMode(BUTT_STEP, INPUT);
  digitalWrite(BUTT_STEP, HIGH);
  attachInterrupt(1, StartStopPressed, FALLING);

  // Setup emergency stop interrupt
  pinMode(ESTOP, INPUT);
  digitalWrite(ESTOP, HIGH);
  attachInterrupt(0, EmergencyStop, FALLING);
  
  // Setup encoder pins as inputs
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);

  //Setup button LED power pin
  pinMode(BUTT_LED, OUTPUT);
  digitalWrite(BUTT_LED, LOW);
  
  //Setup direction switch pin
  pinMode(SWCH_DIR, INPUT);
  digitalWrite(SWCH_DIR, HIGH);

  //Setup direction output pin
  pinMode(DIR, OUTPUT);
  digitalWrite(DIR, LOW);

  //Setup frequency output pin
  pinMode(PUL, OUTPUT);
  digitalWrite(PUL, LOW);

  
  //Display initial splash screen on startup
  Splash();
  
  PrintDigit(0);
}
 
 

int Digit = 0;
volatile boolean MotorRun = false;

// The code in the loop() funtion constantly polls a rotary encoder and displays a user selected
// number on a 7 segment display until an interrupt occurs on the motor start/stop pin.
void loop()
{
  
// The following is code to read a rotary encoder ( slighty modified  to suit ) and is borrowed from here:
// http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
 static int enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
 static int tmpdata;
 static int counter = 0;      //this variable will be changed by encoder input
 static uint8_t old_AB = 0;


  if(!MotorRun) // Allow user to change speed only when motor is stopped.
  {
    old_AB <<= 2;                   //remember previous state
    old_AB |= ( ENC_PORT & 0x03 );  //add current state
    tmpdata = enc_states[( old_AB & 0x0f )];

    if( tmpdata )
      counter += tmpdata ;
// End borrowed code....

   if( counter == 4  ) // Counter has rotated one 'notch' clockwise
   {
     counter=0;
     if( Digit <9 ) 
     {
       Digit++;
       PrintDigit(Digit); 
     }
   } 

   if( counter == -4 ) // Counter has rotated one 'notch' anti-clockwise
   {
     counter=0;
     if( Digit >0 )
     {
       Digit--;
       PrintDigit(Digit);
     }
   }
  }
  
}
 

// Display start up animation
void Splash()
{

const byte x = 60;

  for( int n=0;n<=1;n++)
  {
  PrintBits(SegA);
  delay(x);
  PrintBits(SegA | SegB );
  delay(x);
  PrintBits(SegA | SegB | SegC );
  delay(x);
  PrintBits(SegA | SegB | SegC | SegD );
  delay(x);
  PrintBits(SegA | SegB | SegC | SegD | SegE );
  delay(x);  
  PrintBits(SegA | SegB | SegC | SegD | SegE | SegF);
  delay(x);

  PrintBits(SegB | SegC | SegD | SegE | SegF);
  delay(x);
  PrintBits(SegC | SegD | SegE | SegF);
  delay(x);
  PrintBits(SegD | SegE | SegF);
  delay(x);
  PrintBits(SegE | SegF);
  delay(x);
  PrintBits(SegF);
  delay(x);  
  }  
  
  PrintBits(B00000000);
  delay(50);
}


//Display an arbitrary pattern
void PrintBits( byte Bits )
{ 
  Bits=~Bits;
  for( int n=7;n>=0;n--)
  {
   digitalWrite(n+4,bitRead(Bits,n));
  }
}


// Display a digit
void PrintDigit( byte digit )
{
  for( int n=7;n>=0;n--)
  {
   digitalWrite(n+4,bitRead(Digits[digit],n));
  }
}


// Interrupt routine called when stop/start button is pressed
void StartStopPressed()
{

   // Insert debounce handler here...... //
  
  if ( !MotorRun )
  {
    MotorRun = true;
    digitalWrite(BUTT_LED, HIGH);             // Turn on button LED
    digitalWrite(DIR,digitalRead(SWCH_DIR));  // Set motor direction to selected direction
    Timer1.initialize(2200/(Digit+1));        // Set Timer for 1/2 desired frequency. Here it is set for around 900Hz on display speed setting '0'
    Timer1.attachInterrupt(Pulse);            // Start motor
  }
  else
  {
    Timer1.detachInterrupt();     // Stop motor
    digitalWrite(PUL, LOW); 
    digitalWrite(BUTT_LED, LOW);  // Turn off button LED  
    MotorRun = false;    
  }
}  


//Timer1 interrupt routine. Each call toggles the designated frequency output pin. Minimum practical half cycle for stepper motor = around 10uS.
void Pulse()
{
   PORTC = PINC ^ B00100000 | B00001011;  // Toggle frequency output pin using XOR preserving the pullup status of the rotary encoder and direction input pins.
}


void EmergencyStop()
{
    Timer1.detachInterrupt();     // Stop motor
    digitalWrite(PUL, LOW);
    digitalWrite(BUTT_LED, LOW);  // Turn off button LED
    MotorRun = false;
}

A brief explanation of the program: The user selects a stepper speed from 0-9 using a rotary encoder and selects direction with a toggle switch. The main loop is dedicated to polling the encoder and updating the single digit seven segment display. I wanted to use interrupts for the button because I wanted to ensure that polling the encoder was a fast as it could be so that user input wasn't missed. I haven't tried a debounce routine alongside the polling routine for this reason. Maybe it would work fine, I just had it in my head that using interrupts would be tidier... :wink:

I wanted to use interrupts for the button because I wanted to ensure that polling the encoder was a fast as it could be so that user input wasn't missed.

You've got this backwards. The encoder should be triggering interrupts. The switch should be polled. An encoder signal will be much shorter than a fat-fingered-human-pressing-a-switch signal.

I agree that interrupts should have been used for the encoder, unfortunately I only have 2 interrupts pins on the Arduino (Seeduino). That would mean polling whilst trying to maintain a potential 50KHz signal to the stepper driver. It seemed like too big an overhead to allow polling and debouncing code to run alongside. In addition there would be a second pin to monitor which would act as the emergency stop...

In addition there would be a second pin to monitor which would act as the emergency stop...

You're not putting emergency stop under software control, are you? :astonished:

Interrupt triggered, stop motor.. more as a safety precaution rather than an emergency stop.... I take it you're suggesting that's a bad move....?