Rising edge input to output time difference

Hi,

I had a look on the forum and couldn't find exactly the answer I'm looking for.

I'm setting up a strobe to image a spinning object (70 Hz or 4200rpm). My motor generates a digital pulse each time it passes a particular location - so at max speed I get 70 rising edges per second.

I'd like to step this down to ~5Hz - this is the speed I need to acquire images and saves on strobe life-time and also hardware. What I'd like to do is detect the rising edge from my motor and output it from another pin on the arduino, and then ignore any subsequent rising edges for 200ms.

I can live with a 1-2 degree rotation lag between the input and the output. At 70 Hz, a 1 degree lag is ~40us. I'm going to work with an Arduino Uno Rev3. My question is - is it feasible to detect the input on a digital input put and then transmit the output within this 40us window? I've only used Arduino for a few very simple tasks so I'm not an expert and have no idea how quickly this can happen. Also a mechanical engineering so wouldn't have a great electronics or programming background either.

Separately, if it is feasible should I try and poll and empty loop but set up a rising edge interrupt to start my timer as the best option? Or run the main loop to poll the input pin and start my timer based on it going from low to high. So something like:

  • If input is low wait 20us
  • If input is high set output high and wait 10ms
  • Set output low
  • Wait 190ms

Thank you and kind regards,
Dave

If this was my project, I would use an interrupt on the pin and count the interrupts. Then in loop, check the interrupt count and when it got to the number I wanted, output a pulse on your desired pin and reset the interrupt count. Or reset the count first and then out put the pulse would probably work better.

Paul

Another way to go using interrupts is to use a lockout period in the ISR , so that it only executes at 5Hz no matter how frequently the interrupt is triggered.

void lockoutISR ()
{
  const int lockoutTime = 200;
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis(); 
  if (interruptTime - lastInterruptTime >= lockoutTime)
  {
    lastInterruptTime = interruptTime;
    //do what you want to do when interrupt triggers e.g digitalWrite(strobe_trigger, HIGH)
  }  
}

Is the input signal constant at 70 Hz?

If I was doing this project, I'd use the ESP32 PCNT API. I'd set the PCNT (Pulse CouNTer) to count pulses, which it does without using the CPU, and, after setting the PCNT to output a pulse with every 5 pulses counted, generate an interrupt that would allow the thing to be done. That way the CPU can do other things whiles the pulses are counted.

Quite easy for an UNO to handle, possibly even a simple pulse divider logic gate.
How is the strobe pulse supposed to look ... square wave?
If not, what's the strobe's duty cycle or pulse duration ... or does this need to be variable?
Of course the strobe's frequency will be variable and if you divide the input by 14, it will vary 0-5 Hz.

Most time lags result from the comfortable ISR handling and pin mapping of the Arduino firmware. And from inadequate statement sequences that miss to handle the reflected output ASAP.

If you did not already then try direct port manipulation instead of digitalRead/Write() and check the different time lag of the output signal.

Sorry, it's not clear to me from your OP: From the rising (falling?) edge of the rotation sensor how much time delay do you need before setting the output high?

Is the output signal of a fixed width and if so, how long?

The fastest way to respond to a rising edge is input polling. An interrupt is MUCH slower. For example, assuming input on pin 3 of PORT D, previously declared as INPUT, output immediately on pin 4, PORTD, previously declared as OUTPUT.

For stability, interrupts have to be off while polling, or the millis() tick could interrupt the wait loop.

   cli(); //turn off interrupts
   while ( (PIND&(1<<3)) == 0);  //wait for high on pin 3
   PORTD |= (1<<4); //set high on pin 4
   sei(); //turn back on millis()
   delay(10);  //wait 10 ms
   PORTD &= ~(1<<4); //pin 4 low again.
   delay(190); //wait 190 ms

I believe that the while loop takes 3 machine cycles (about 0.2 usec) maximum to respond, but of course, the Arduino can’t do anything else while it is waiting.

You can use an input capture which will capture the exact time stamp of the trigger edge and, using output compares, get a very accurate, hardware-determined placement of your output pulse.

/*
 * Sketch:  trig_pulse_delay.ino
 * Target:  Uno R3
 */

#define TRG_EDGE_RISING     (_BV(ICES1))
#define TRG_EDGE_FALLING    0           //                  ~ICES1 for OR

#define EDGE_CNT            14          //#                 number of trigger pulses between output
#define OP_DELAY            20000u      //#     N=E*500nS   delay from trigger edge to output active    (N=10000uS)
#define OP_WIDTH            2000u       //#     N=E*500nS   output pulse width                          (N=1000uS)

#define TRIG_WIDTH          250u        //uS                width of test trigger pulse
#define TRIG_PERIOD         14286u      //uS                period of test trigger pulse

const uint8_t pinOC1A = 9;
const uint8_t pinTrig = 8;

void setup() 
{
    pinMode( pinOC1A, OUTPUT );
    pinMode( pinTrig, INPUT_PULLUP );
    pinMode( 7, OUTPUT );   //used to generate a test trigger pulse (feed into pinTrig for test)
    
    TCCR1A = 0;
    TCCR1B = TRG_EDGE_RISING | _BV(CS11);        //rising edge trigger; prescaler=/8; gives 500nS/tick
    TIFR1 |= _BV(ICF1);
    TIMSK1 |= _BV(ICIE1);
    
}//setup

void loop() 
{
    //generate a test pulse train on pin 7
    //connect 7 to 8 for test purposes
    PORTD |= 0b10000000;
    delayMicroseconds( TRIG_WIDTH );
    PORTD &= 0b01111111;
    delayMicroseconds( TRIG_PERIOD - TRIG_WIDTH );

}//loop


ISR( TIMER1_CAPT_vect )
{
    static uint8_t
        edgeCount=EDGE_CNT;

    edgeCount--;
    if( edgeCount == 0 )
    {
        edgeCount = EDGE_CNT;

        //config OC1A to set pin on next OC
        TCCR1A = _BV(COM1A1) | _BV(COM1A0);
        //set the next OC to happen after the desired delay
        OCR1A = ICR1 + OP_DELAY;
        TIFR1 |= _BV(OCF1A);        //clear any pending OC flag
        TIMSK1 |= _BV(OCIE1A);      //enable the OC
        
    }//if
    
}//TIMER1_CAPT_vect

ISR( TIMER1_COMPA_vect )
{
    //config OC1A to clear pin on next OC
    TCCR1A = _BV(COM1A1);
    //set next OC to happen at the desired pulse width
    OCR1A = OCR1A + OP_WIDTH;
    TIMSK1 &= ~_BV(OCIE1A);         //disable further OCs until next IC enables again
                                    //no need to disconnect OC from pin
    
}//TIMER1_COMPA_vect

You can play with the defined constants EDGE_CNT, OP_DELAY and OP_WIDTH to set the number of pulses you wish to skip, the delay from the trigger edge to the output pulse going active and the width of the output pulse respectively.

For almost zero-delay you can use a logic AND gate controlled by the pulse source and Arduino. As long as the Arduino signals HIGH the pulse passes through the gate. After detection of a pulse keep the output low for the intended pause time.

My question is - is it feasible to detect the input on a digital input put and then transmit the output within this 40us window?

With the MCU clock at 16MHz, there is 640 clock cycles to do the job … more than enough time.
Here, in his example near the bottom, he got the overhead down to 1.118usec (18 clock cycles).