Timing and speed issues with pin change interrupt based routines - Arduino slow?

I have a project where I need very timing accurate processing of 12 hardware generated triggers (from external devices). My approach has been to use the pinchange interrupts in order to not miss any of these triggers.
The state of these triggers determines whether 1 of 4 possible digital outs is high or low. So basically 12 triggers control 4 the state of 4 outputs.

So the issue I am getting is that when the external triggers are firing at rates of about 500Hz (each at slightly different frequencies so interrupts are firing reasonably rapidly but at random time) I am getting very inconsistent timing for the 4 outputs. From my analysis so far it seems that the ISRs are causing the random delay of the output signal changes.

I have tried to make the ISRs as small as possible, I even reduced all of them to simply turning a flag on or off and then using the main loop to call functions based on the state of flags. This approach was actually worse then using the ISRs to call very short functions that involved direct port manipulation (i.e. an ISR would simply have some kind of single if..then condition and then write directly to a port instead of using digitalWrite).

So have I basically reached the limit of what a 16Mhz Arduino is capable of in terms of processing power? It seems to me this application is not too demanding,

I am going to try to re-write the code to work without interrupts and use polling instead (but read the entire pin data registers in one cycle to speed things up).

Any tips on how to approach a system like this??

You say that you only change flag from the ISR and after you perform the task in the main program. This is a good thechnique but are you sure the following ISR will not change the flag before the main code of the previous has been executed?

cantore:
You say that you only change flag from the ISR and after you perform the task in the main program. This is a good thechnique but are you sure the following ISR will not change the flag before the main code of the previous has been executed?

Good question. So in the code I need to execute functions only when a flag is set, if an ISR is called while in the middle of this function it will just set the same flag to ON as before and then the code will return to executing what it was doing before being interrupted. At the end of processing the function that was conditional on a specific flag it then sets that flag to OFF again.

Is there a better way to do this?

Hi,
Post your code, its possibly an error in the way you are managing variable access.

500*12 is 6000, so 18,000,000 divided by 6000 = 2,666 clock cycles available to process each interrupt. A pin change interrupt takes around 68 clock cycles to process depending on the ISR content.

One potential problem is that if all of your pins change at the same time it will take around 700 clock cycles to get through all the ISRs, thats about a 40 microsecond log jam of interrupts.

To eliminate variable access check out this post, its using pin change ints at a much lower frequency but outlines a variable access/protection strategy.

Duane B

rcarduino.blogspot.com

12 hardware generated triggers

High pulse? Toggle?

Other than what you've described, what is the application doing? Serial output? Managing an LCD?

It's pulses and the code is controlling analog function generators.

could you just have a tight loop that reads all 12 bits and does a table lookup or hash function to get the 4 output bits? Don't worry about changes - just translate 12 bits to 4.

bill2009:
could you just have a tight loop that reads all 12 bits and does a table lookup or hash function to get the 4 output bits? Don't worry about changes - just translate 12 bits to 4.

very interesting idea/approach!

Basically I have 4 analog functions I am controlling and each has a set of three interrupts associated with it. However each function can also operate in three different modes (controlled by a mode switch which can be polled very infrequently).

I was planning to at least try something simmilar to what you suggested and simply read the pin data registers in a solid block, quickly determine the new output state of all 4 output pins and then write to the output data register in a single write.

Using hash function or lookup table will still be useful and can be based on a formula of the three modes + 3 possible input states to determine output state for a single pin.

intellijel:
I am getting very inconsistent timing for the 4 outputs. From my analysis so far it seems that the ISRs are causing the random delay of the output signal changes.

Do you have figures?

I have some stuff about interrupts here:

It takes about 1.5 uS to enter an ISR (more for the built-in ones for external interrupts). But you are quoting detecting changes that happen every 2 mS, which is a lot longer time than that.

As I discuss on that page, interrupts are likely to have some jitter, simply because you can't interrupt in the middle of an instruction, so you have at least something like a 1 or 2 clock cycle variation. Also, interrupts can't occur if you are processing an interrupt, so if you are processing (say) a timer interrupt, your pin change interrupt could be delayed.

I would have to see your code to comment further, there might be efficiencies that can be added.

I had a recent project where I wanted to generate VGA video signals, and even one clock cycle added too much jitter. In that case putting the processor to sleep helped get consistent results.

I think you need to quantify your expectations. How much jitter can you tolerate, and how much are you measuring?

Thanks for all the suggestions so far.

based on the advice I opted for a non-isr based solution in order to have more consistent timing. All reads and writes are done directly via port manipulation and I have tried to reduce/minimise the logic used.

When I created a test system that limited the operations to one mode (for all channels) I was able to get speeds that were actually quite acceptable. Once I added back all the necessary conditional logic and modes the timing got too delayed again.

I have attached my code with the hopes someone can see some clever ways to squeeze more speed out of it but I am beginning to lose hope. I think I may have to create an all digital logic system based on discrete flipflops and logic ICs in order to get the tight timing I need.

#define FASTADC 1

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif


/*
//bits used

//PINC  
#define EOA1 0
#define EOA2 1
#define EOA3 2
#define EOA4 3

//PINB
#define EOC1 2
#define EOC2 3
#define EOC3 4
#define EOC4 5

//PIND
#define TRIG1 0
#define TRIG2 1
#define TRIG3 2
#define TRIG4 3

//PORTD 
#define GATE1 4
#define GATE2 5
#define GATE3 6
#define GATE4 7

*/


//DIGITAL INPUT PINS
#define EOA1pin 14
#define EOA2pin 15
#define EOA3pin 16
#define EOA4pin 17

byte EOApin[] = {EOA1pin, EOA2pin, EOA3pin, EOA4pin};

#define EOC1pin 10
#define EOC2pin 11
#define EOC3pin 12
#define EOC4pin 13

byte EOCpin[] = {EOC1pin, EOC2pin, EOC3pin, EOC4pin};

#define TRIG1pin 0
#define TRIG2pin 1
#define TRIG3pin 2
#define TRIG4pin 3

byte TRIGpin[] = {TRIG1pin, TRIG2pin, TRIG3pin, TRIG4pin};

#define QUADSWApin 8
#define QUADSWBpin 9

//ANALOG INPUT PINS
#define MODESW1pin 4
#define MODESW2pin 5
#define MODESW3pin 6
#define MODESW4pin 7

byte MODESWpin[] = {MODESW1pin, MODESW2pin, MODESW3pin, MODESW4pin};

//DIGITAL OUTPUT PINS
#define GATE1pin 4
#define GATE2pin 5
#define GATE3pin 6
#define GATE4pin 7

byte GATEpin[] = {GATE1pin, GATE2pin, GATE3pin, GATE4pin};

#define AR    0
#define AD    1
#define LOOP  2


#define OFF 0
#define ON  1

byte mode[4] = {AR,AR,AR,AR};
byte prevMode[4] = {AR,AR,AR,AR};
byte quadMode[2] = {OFF, OFF};


#define SWITCHPOLLTIME 500

byte count = 0;

void setup() {
  
  #if FASTADC
  // set prescale to 16
  sbi(ADCSRA,ADPS2) ;
  cbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;
  #endif

  
  for (int i = 0; i<4; i++) {
    pinMode(EOApin[i], INPUT);
   // digitalWrite(EOApin[i], LOW);
    pinMode(EOCpin[i], INPUT);
   // digitalWrite(EOCpin[i], LOW);
    pinMode(TRIGpin[i], INPUT);
    //digitalWrite(TRIGpin[i], LOW);
    pinMode(GATEpin[i], OUTPUT);
  }
  
  
  pinMode(QUADSWApin, INPUT);
  pinMode(QUADSWBpin, INPUT);
  digitalWrite(QUADSWApin, HIGH);
  digitalWrite(QUADSWBpin, HIGH); 
}


unsigned long time = 0;
unsigned long prevTime = 0;

void loop() {
  
 time = millis(); 
 
// this timer condition calls a function that checks the state of 6 mode switches. It is called periodically since a slow response is ok and checking it eats up processing time
//The counter inside is set so that every time it is called it only checks one of the 4 switches,
if ((time-prevTime) > 50) {
  count = (count+1) %4;
  modeSW_update(count);
  prevTime = time;
  quadSW_update();
} 


if (quadMode[0] == ON) {
  //if bitRead(PIND,0) bitClear(PORTD, 4);  //TRIG1 trigger to start GATE1
  if bitRead(PINB,3) bitSet(PORTD, 4);     //EOC2 trigger to start GATE1
  if bitRead(PINC, 0) bitSet(PORTD, 5);    //EOA1 trigger start GATE2
  if bitRead(PINC, 1) bitClear(PORTD, 4);    //EOA2 trigger stop GATE1
  if bitRead(PINB,2) bitClear(PORTD, 5);     //EOC1 trigger to stop GATE2
  
 // if bitRead(PINB,3) bitClear(PORTD, 4);     //EOC2 trigger to start GATE1
} else {
  
  if (mode[0] == LOOP) { if bitRead(PINB,2) bitSet(PORTD, 4); }
  if (mode[1] == LOOP) { if bitRead(PINB,3) bitSet(PORTD, 5); }
  
  if (mode[0] == AR) { 
  if bitRead(PIND,0) bitSet(PORTD, 4); else bitClear(PORTD, 4);
  } else { 
    if (!bitRead(PORTD,4) && bitRead(PIND, 0) ) bitSet(PORTD,4);
    if bitRead(PINC, 0) bitClear(PORTD, 4); 
  }

if (mode[1] == AR) { 
  if bitRead(PIND,1) bitSet(PORTD, 5); else bitClear(PORTD, 5); 
  } else { 
    if (!bitRead(PORTD,5) && bitRead(PIND, 1) ) bitSet(PORTD,5);
    if bitRead(PINC, 1) bitClear(PORTD, 5); 
  }
  
}

if (quadMode[1] == ON) {
  //if bitRead(PIND,3) bitSet(PORTD, 6);     //TRIG1 trigger to start GATE1
  if bitRead(PINB,5) bitSet(PORTD, 6);     //EOC2 trigger to start GATE1
  if bitRead(PINC, 2) bitSet(PORTD, 7);    //EOA1 trigger start GATE2
  if bitRead(PINC, 3) bitClear(PORTD, 6);    //EOA2 trigger stop GATE1
  if bitRead(PINB,4) bitClear(PORTD, 7);     //EOC1 trigger to stop GATE2
  
  
} else {

  
  if (mode[2] == LOOP) { if bitRead(PINB,4) bitSet(PORTD, 6); }
  if (mode[3] == LOOP) { if bitRead(PINB,5) bitSet(PORTD, 7); }
  
  
    
  if (mode[2] == AR) { 
    if bitRead(PIND,2) bitSet(PORTD, 6); else bitClear(PORTD, 6); 
    } else { 
        if (!bitRead(PORTD,6) && bitRead(PIND, 2) ) bitSet(PORTD,6);
      if bitRead(PINC, 2) bitClear(PORTD, 6); 
      
    }
    
  if (mode[3] == AR) { 
    if bitRead(PIND,3) bitSet(PORTD, 7); else bitClear(PORTD, 7); 
    } else { 
      if (!bitRead(PORTD,7) && bitRead(PIND, 3) ) bitSet(PORTD,7);
      if bitRead(PINC, 3) bitClear(PORTD, 7); 
    }
    
  }
}


void modeSW_update(byte cnt) {
int val;  
     val = analogRead(MODESWpin[cnt]);
     
     if (val < 10) mode[cnt] = AD;
     else if ( (val >= 10) && (val < 400) ) { mode[cnt] = AR; }
     else //if (val >= 400) mode[cnt] = LOOP;
       mode[cnt] = LOOP;
}


void quadSW_update() {
    quadMode[0] = !bitRead(PINB,0);
    quadMode[1] = !bitRead(PINB,1);
}

For reading multiple inputs I think you will find interrupts are the way to go.

So far it is all rather vague. You haven't posted your attempt to use interrupts, so we can't comment on the method you used.

... the issue I am getting is that when the external triggers are firing at rates of about 500Hz ...

A 16 MHz processor should be able to handle incoming data at 0.5 KHz.

I was able to get speeds that were actually quite acceptable. Once I added back all the necessary conditional logic and modes the timing got too delayed again.

Again, can you give figures please? What speeds? What is "too delayed"?

Maybe sketch the sort of input pulses you are expecting. Are you saying you have 4 x 500 Hz pulses, where they may fire in close proximity? How close? What is an acceptable detection rate?

The analogRead takes around 104 uS, so whether or not you do it infrequently, whenever you do that there is a window of 104 uS when you won't detect anything, especially in a polling loop. It's possible to do analog reads asynchronously.

Again, can you give figures please? What speeds? What is "too delayed"?

I don't have a logic analyser and since the delays are random it is hard to measure on my scope but it is about 80-100 uS of delay.
Anything more than about 10uS is really noticeable.

Maybe sketch the sort of input pulses you are expecting. Are you saying you have 4 x 500 Hz pulses, where they may fire in close proximity? How close? What is an acceptable detection rate?

there are actually 12 input pulses that may fire at rates of up to 500Hz asychronously. It is possible at any time to have up to 8 fire at exactly the same time (or very close). The delay in reading these would be one of the main contributors to timing issues on the generated gates and processing them with less than 10uS error would be ideal.
Also using interrupts meant that these delays introduced were always random quantities. At least with the polling the jitter was more consistent in behavior.

The analogRead takes around 104 uS, so whether or not you do it infrequently, whenever you do that there is a window of 104 uS when you won't detect anything, especially in a polling loop. It's possible to do analog reads asynchronously.

I didn't see how I could get around this unless it was being polled and everything else was interrupt driven. How do you read it asynchronously?

You haven't posted your attempt to use interrupts, so we can't comment on the method you used.

Here is my code with ISRs.

#include "pins_arduino.h"

volatile uint8_t *port_to_pcmask[] = {
  &PCMSK0,
  &PCMSK1,
  &PCMSK2
};

static int PCintMode[24];

typedef void (*voidFuncPtr)(void);

volatile static voidFuncPtr PCintFunc[24] = {
  NULL };

volatile static uint8_t PCintLast[3];

/*
 * attach an interrupt to a specific pin using pin change interrupts.
 */
 void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  uint8_t slot;
  volatile uint8_t *pcmask;

  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  }
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }

// -- Fix by Baziki. In the original sources it was a little bug, which cause analog ports to work incorrectly.
  if (port == 1) {
     slot = port * 8 + (pin - 14);
  }
  else {
     slot = port * 8 + (pin % 8);
  }
// --Fix end
  PCintMode[slot] = mode;
  PCintFunc[slot] = userFunc;
  // set the mask
  *pcmask |= bit;
  // enable the interrupt
  PCICR |= 0x01 << port;
}

void PCdetachInterrupt(uint8_t pin) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *pcmask;

  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  }
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }

  // disable the mask.
  *pcmask &= ~bit;
  // if that's the last one, disable the interrupt.
  if (*pcmask == 0) {
    PCICR &= ~(0x01 << port);
  }
}

// common code for isr handler. "port" is the PCINT number.
// there isn't really a good way to back-map ports and masks to pins.
static void PCint(uint8_t port) {
  uint8_t bit;
  uint8_t curr;
  uint8_t mask;
  uint8_t pin;

  // get the pin states for the indicated port.
  curr = *portInputRegister(port+2);
  mask = curr ^ PCintLast[port];
  PCintLast[port] = curr;
  // mask is pins that have changed. screen out non pcint pins.
  if ((mask &= *port_to_pcmask[port]) == 0) {
    return;
  }
  // mask is pcint pins that have changed.
  for (uint8_t i=0; i < 8; i++) {
    bit = 0x01 << i;
    if (bit & mask) {
      pin = port * 8 + i;
      // Trigger interrupt if mode is CHANGE, or if mode is RISING and
      // the bit is currently high, or if mode is FALLING and bit is low.
      if ((PCintMode[pin] == CHANGE
          || ((PCintMode[pin] == RISING) && (curr & bit))
          || ((PCintMode[pin] == FALLING) && !(curr & bit)))
          && (PCintFunc[pin] != NULL)) {
        PCintFunc[pin]();
      }
    }
  }
}


SIGNAL(PCINT0_vect) {
  PCint(0);
}
SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) {
  PCint(2);
}

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


//DIGITAL INPUT PINS
#define EOA1pin 14
#define EOA2pin 15
#define EOA3pin 16
#define EOA4pin 17

byte EOApin[] = {EOA1pin, EOA2pin, EOA3pin, EOA4pin};

#define EOC1pin 10
#define EOC2pin 11
#define EOC3pin 12
#define EOC4pin 13

byte EOCpin[] = {EOC1pin, EOC2pin, EOC3pin, EOC4pin};

#define TRIG1pin 0
#define TRIG2pin 1
#define TRIG3pin 2
#define TRIG4pin 3

byte TRIGpin[] = {TRIG1pin, TRIG2pin, TRIG3pin, TRIG4pin};


#define QUADSWApin 8
#define QUADSWBpin 9



//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ swapped pins 2 and 4 to correct mistake!
//ANALOG INPUT PINS
#define MODESW1pin 4
#define MODESW2pin 5
#define MODESW3pin 6
#define MODESW4pin 7

byte MODESWpin[] = {MODESW1pin, MODESW2pin, MODESW3pin, MODESW4pin};

//DIGITAL OUTPUT PINS
#define GATE1pin 4
#define GATE2pin 5
#define GATE3pin 6
#define GATE4pin 7

byte GATEpin[] = {GATE1pin, GATE2pin, GATE3pin, GATE4pin};


#define AR    0
#define AD    1
#define LOOP  2


#define OFF 0
#define ON  1

volatile byte state = 0;

byte mode[4] = {AR,AR,AR,AR};
byte prevMode[4] = {AR,AR,AR,AR};

byte quadMode[2] = {OFF, OFF};

volatile byte EOA_flag[4] = {OFF, OFF, OFF, OFF};
volatile byte EOC_flag[4] = {OFF, OFF, OFF, OFF};
volatile byte TRIG_flag[4] = {OFF, OFF, OFF, OFF};


//**************************************************************************************************************************************************************************************************************************************
void setup() {
  
  
  for (int i = 0; i<4; i++) {
    pinMode(EOApin[i], INPUT);
    digitalWrite(EOApin[i], LOW);
    pinMode(EOCpin[i], INPUT);
    digitalWrite(EOCpin[i], LOW);
    pinMode(TRIGpin[i], INPUT);
    digitalWrite(TRIGpin[i], LOW);
    pinMode(GATEpin[i], OUTPUT);
  }
  
  
  pinMode(QUADSWApin, INPUT);
  pinMode(QUADSWBpin, INPUT);
  digitalWrite(QUADSWApin, HIGH);
  digitalWrite(QUADSWBpin, HIGH);
 
 
 
  PCattachInterrupt(EOApin[0],EOA0,RISING);
  PCattachInterrupt(EOApin[1],EOA1,RISING);
  PCattachInterrupt(EOApin[2],EOA2,RISING);
  PCattachInterrupt(EOApin[3],EOA3,RISING);
  
 
  PCattachInterrupt(EOCpin[0],EOC0,RISING);
  PCattachInterrupt(EOCpin[1],EOC1,RISING);
  PCattachInterrupt(EOCpin[2],EOC2,RISING);
  PCattachInterrupt(EOCpin[3],EOC3,RISING);
  
  
  PCattachInterrupt(TRIGpin[0],TRIG0,CHANGE);
  PCattachInterrupt(TRIGpin[1],TRIG1,CHANGE);
  PCattachInterrupt(TRIGpin[2],TRIG2,CHANGE);
  PCattachInterrupt(TRIGpin[3],TRIG3,CHANGE); 
  
  
}


void loop() {
  
  
 modeSW_update();
 quadSW_update();
 
 
 checkEOC();
 checkTRIG();
 checkEOA();

  
}


void modeSW_update() {
int val = 0;
int newmode = 0;

   for (int i = 0; i<4; i++) {
     val = analogRead(MODESWpin[i]);
     
     if (val < 10) newmode = AD;
     if ( (val >= 10) && (val < 400) ) { newmode = AR; }
     if (val >= 400) newmode = LOOP;
     
     if (newmode != mode[i]) {
       prevMode[i] = mode[i];
       mode[i] = newmode;
       digitalWrite(GATEpin[i], LOW);  //reset envelope.
       eoc(i); //call EOC to get loop happening.
     }
     
  }
  
  
  //check if mode changed and perform action like zero envelope??
}


void quadSW_update() {
  if (!digitalRead(QUADSWApin)) quadMode[0] = ON;
    else quadMode[0] = OFF;
  if (!digitalRead(QUADSWBpin)) quadMode[1] = ON;
    else quadMode[1] = OFF;
}

code continued...

/**************************************************************************
*
*
/**************************************************************************/
void eoa(int pin) {
  switch (mode[pin]) {
    case AR:
    break;
    case AD:
      bitClear(PORTD, GATEpin[pin]);
    break;
    case LOOP:
      bitClear(PORTD, GATEpin[pin]);
    break;
  }
}



/**************************************************************************
*
*
/**************************************************************************/
void eoc(int pin) {
   if (mode[pin] == LOOP) {
      bitSet(PORTD, GATEpin[pin]);
  }();
}


/**************************************************************************
*
*
/**************************************************************************/
void trig(int pin) {
    state = bitRead(PIND,pin);           
  switch (mode[pin]) {
    case AR:
      if (state) 
            bitSet(PORTD, GATEpin[pin]);
        else 
          bitClear(PORTD, GATEpin[pin]);
    break;
    case AD:
       if (state) {
             bitSet(PORTD, GATEpin[pin]);
       }    
    break;
    case LOOP:
      if (state) { 
         bitSet(PORTD, GATEpin[pin]);
        }
        
    break;
  }
}



/**************************************************************************
*
*
/**************************************************************************/
void EOA0() {
    EOA_flag[0] = ON;
}

void EOA1() {
  EOA_flag[1] = ON;
}

void EOA2() {
  EOA_flag[2] = ON;
}

void EOA3() {
  EOA_flag[3] = ON;
}


/**************************************************************************
*
*
/**************************************************************************/
void EOC0() {
  EOC_flag[0] = ON;
}

void EOC1() {
 EOC_flag[1] = ON;
}

void EOC2() {
  EOC_flag[2] = ON;
}

void EOC3() {
  EOC_flag[3] = ON;
}

/**************************************************************************
*
*
/**************************************************************************/
void TRIG0() {
  TRIG_flag[0] = ON;
}

void TRIG1() {
  if (quadMode[0] == OFF) {
    TRIG_flag[1] = ON;}
}

void TRIG2() {
  TRIG_flag[2] = ON;
}

void TRIG3() {
  if (quadMode[1] == OFF) {
    TRIG_flag[3] = ON;
  }
}



/**************************************************************************
*
*
/**************************************************************************/
void checkEOA() {
  
  if (EOA_flag[0] == ON) {
    EOA_flag[0] = OFF;
    if (quadMode[0] == ON) digitalWrite(GATEpin[1], HIGH);
      else eoa(0);
   }


  if (EOA_flag[1] == ON) {
    EOA_flag[1] = OFF;
    if (quadMode[0] == ON) digitalWrite(GATEpin[0], LOW);
      else  eoa(1);
  }


  if (EOA_flag[2] == ON) {
    EOA_flag[2] = OFF;
    if (quadMode[1] == ON) digitalWrite(GATEpin[3], HIGH);
      else eoa(2);
  }


  if (EOA_flag[3] == ON) {
    EOA_flag[3] = OFF;
    if (quadMode[1] == ON) digitalWrite(GATEpin[2], LOW);
      else eoa(3); 
  }
}




/**************************************************************************
*
*
/**************************************************************************/
void checkEOC() {
 
  if (EOC_flag[0] == ON) {
     EOC_flag[0] = OFF;
    if (quadMode[0] == ON) {
      digitalWrite(GATEpin[0], HIGH);
      digitalWrite(GATEpin[1], LOW);
    } else eoc(0);  
  }


  if (EOC_flag[1] == ON) {
     EOC_flag[1] = OFF;
     if (quadMode[0] == OFF) eoc(1);  
  }

  if (EOC_flag[2] == ON) {
     EOC_flag[2] = OFF;
    if (quadMode[1] == ON) {
      digitalWrite(GATEpin[2], HIGH);
      digitalWrite(GATEpin[3], LOW);
    } else eoc(2); 
}

  if (EOC_flag[3] == ON) {
     EOC_flag[3] = OFF;
     if (quadMode[0] == OFF) eoc(3); 
  }

}




/**************************************************************************
*
*
/**************************************************************************/
void checkTRIG() {
  
   if (EOC_flag[0] == ON) {
     EOC_flag[0] = OFF;
     trig(0);
  }

   if (EOC_flag[1] == ON) {
     EOC_flag[1] = OFF;
     if (quadMode[0] == OFF) {
      trig(1);}
    }

   if (EOC_flag[2] == ON) {
     EOC_flag[2] = OFF;
     trig(2);
  }

  if (EOC_flag[3] == ON) {
     EOC_flag[3] = OFF;
     if (quadMode[1] == OFF) {
      trig(3);}
    }

}

intellijel:

The analogRead takes around 104 uS, so whether or not you do it infrequently, whenever you do that there is a window of 104 uS when you won't detect anything, especially in a polling loop. It's possible to do analog reads asynchronously.

I didn't see how I could get around this unless it was being polled and everything else was interrupt driven. How do you read it asynchronously?

To answer this first:

Scroll down to: "Read the Analog-to-Digital converter asynchronously".

To save me having to try to comprehend all this stuff:

void EOC0() {
  EOC_flag[0] = ON;
}

void EOC1() {
 EOC_flag[1] = ON;
}

Can you just tell me what sort of thing is supposed to happen when you detect a transition? Do you need to detect both ways? (high->low and low->high)?

Basically, are you just turning on a bit somewhere else (like, another port)?

there are actually 12 input pulses that may fire at rates of up to 500Hz asychronously

What processor is this? You are using 12 input pins here, and you are doing what with the remaining ones?

That pin-change stuff looks wordy to me, I think we can do that in less cycles.

Can you just tell me what sort of thing is supposed to happen when you detect a transition? Do you need to detect both ways? (high->low and low->high)?

For 8 of the pins (the EOC and EOA) I simply need to detect a lo to high transition. Usually the result is that one of the Gate pins (the only 4 outputs) is turned on or off. It is not as simple as looking things up in a table since there are conditional results based on the mode your are in for that channel and whether or not the gate output is already high.
This logic has been reduced to the basics in my non-isr version.

The trig inputs I use a detect change instead of just lo-hi since in some modes I need to know how long the pins are kept high (in this mode "AR mode" the gate output pin simply follows the trig pin. So while trig pin is hi, gate pin should be hi).

For processor I am using an Atmega168-20U (smd version).

Thanks for the tip on the asynchronous ADC read! I am going to implement that and try to improve some of my ISR routines using some of the speedups I used in the non-isr version.

Here is an example of reading up to 8 pins in a pin-change interrupt. By getting down to basics we keep overheads low:

/*
ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect
*/

volatile byte oldPinD;

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here

 byte d = PIND;

 // exclude pins that haven't changed
 byte changed = (d ^ oldPinD) & d;

if (changed & _BV (0))  // D0
  {
  PORTB |= _BV (0);   // toggle D8
  PORTB &= ~_BV (0);
  }

if (changed & _BV (1))  // D1
  {
  PORTB |= _BV (1);   // toggle D9
  PORTB &= ~_BV (1);
  }

if (changed & _BV (2))  // D2
  {
  PORTB |= _BV (2);   // toggle D10
  PORTB &= ~_BV (2);
  }
 
 // and so on
 
 oldPinD = d;
 }  // end of PCINT2_vect

void setup ()
  { 
  // pin change interrupt
  PCMSK2 |= 0xFF;  // all 8 pins
  PCIFR  |= _BV (PCIF2);   // clear any outstanding interrupts
  PCICR  |= _BV (PCIE2);   // enable pin change interrupts for D0 to D7
  
  // inputs
  for (byte i = 0; i <= 7; i++)
    digitalWrite (i, HIGH);  // pull-ups

  // outputs
  for (byte i = 8; i <= 11; i++)
    pinMode (i, OUTPUT);
  }

void loop () {}

Results (grounding D0 and D1 at the same time):

You can see it took just over 2 uS for the first reaction, and only 375 nS for the second one (of course they were both on the same interrupt).

I think the worst-case in your case would be if they were close together but not close enough to be serviced by the same interrupt.

So in your scheme the ISR are performing the functions (not setting flags). I guess this assumes the functions are as efficient as possible.

I will give it go!

Whichever way you do it, if whatever-it-is-you-do when you find the transition takes too long, that is going to impact the results.