HARDWARE INTERRUPTS DURING delayMicroseconds() CALL

I'm doing continuous timed LED strobing for film scanning using Teensy 4.1. Depending on film speed, the exposure function will be called between 2 and 24 times per second. The exposure time is usually between 100us and 1ms. This is the function that is called each time an exposure is triggered:

Code:
void StartTimer()
{
  digitalWriteFast(PIN_SHUTTER_OUT, LOW);
  delayMicroseconds(gTimerInterval);
  digitalWriteFast(PIN_SHUTTER_OUT, HIGH);
}

I'd like to add two quadrature encoder inputs on pins D0 and D1 that will trigger external interrupt functions at up to 24Khz each. The interrupts will maintain the quadrature counts and call the StartTimer() function when the next film frame to be captured has reached the correct position. The interrupt function for each encoder channel looks like this:

Code:
void  GateInterruptA()
{
  if (digitalRead(TRANSPORT_PIN_TRIGGER_SELECT_A_IN) == HIGH) 
  {
    UpdateQuadratureCountA_HIGH();
      if (g_audioSample_Quad_A__High_Enabled)
        TriggerAudioSample();
  }
  else
  {
    UpdateQuadratureCountA_LOW();
      if (g_audioSample_Quad_A__Low_Enabled)
        TriggerAudioSample();
  }
    
  if((m_currentEncoderCount % m_encoderCountsPerFrame) == m_totalFramingOffset)
  {
    if(m_liveViewEnabled)
      StartTimer();
    else
    {
      if(m_programmedCaptureEnabled)
      {
        if((m_currentFrameCount<=m_programmedCaptureEndFrame)&&(m_currentFrameCount>=m_programmedCaptureStartFrame))
           StartTimer();
      }
    }
  }

In this scenario, the hardware interrupts will be called multiple times during each execution of the StartTimer() function. However, StartTimer() will always complete before it is called again. My question is:

Will the delayMicroseconds() call in StartTimer() lose accuracy because of the interrupts being called during its execution?
 
The T_4.1 call to delayMicroseconds() is based on monitoring the cycle counts - not any 'measured time period' that could be skewed by interrupts.

As long as the StartTimer() _isr is lower priority (higher number) it will be interrupted just fine.

Those other interrupts won't affect the 'time period' to be measured.

Though any interrupts of higher priority could delay return to StartTimer(), based on the time they take to execute, and exit for accurate execution of the digitalWriteFast() on the expiration of the delayMicroseconds().

Conversely, if the priority is higher than some, or all other, interrupts - they will be postponed across the delayMicroseconds().
 
Thank you DEFRAGSTER for your learned reply.

1. StartTimer() is not currently implemented as an ISR. It's just a global function that gets called by the encoder ISR's. Should I code it as an ISR?

2. The timely execution of the encoder ISR's is cricital to the vertical film framing position, or it will be erratic from frame to frame. So I guess I should optimize the encoder channel ISR's to be a fast as possible. Do you agree? If the encoder ISR's could be executed in less than one or two microseconds, all will be fine. This is the code for the encoder count updating that is called inside the ISR code posted above:

Code:
void  UpdateQuadratureCountA_HIGH()
{
  m_prevEncoderCount = m_currentEncoderCount;
  
    // check channel B to see which way encoder is turning
    if (digitalRead(TRANSPORT_PIN_TRIGGER_SELECT_B_IN) == HIGH)  
      m_currentEncoderCount += m_encoderPolarityFactor;         // CW
    else
      m_currentEncoderCount -= m_encoderPolarityFactor;         // CCW     
}

Is there a way to measure the execution time of the ISR's in clock cycles, or nanoseconds?

Thanks again.
 
Is there a way to measure the execution time of the ISR's in clock cycles, or nanoseconds?

The 2 most common ways are capturing the ARM cycle counter at begin and end where you later print the difference, and using digitalWriteFast() at the begin and end and then watch the pulse width with an oscilloscope or logic analyzer. Neither of these measure the time taken for the ARM processor to enter interrupt mode. But if the interrupt is a change at a pin, you can also monitor it with your scope and see the time between the hardware event and that first digitalWriteFast().
 
Thanks Paul.

I have TTL outputs of the individual RGB exposure channels on a DB9 in the rear termination panel. Maybe I can remap them for a test routine.

Teensy Rules! (I just wish I could get some).
 
Thank you DEFRAGSTER for your learned reply.

1. StartTimer() is not currently implemented as an ISR. It's just a global function that gets called by the encoder ISR's. Should I code it as an ISR?

2. The timely execution of the encoder ISR's is cricital to the vertical film framing position, or it will be erratic from frame to frame. So I guess I should optimize the encoder channel ISR's to be a fast as possible. Do you agree? If the encoder ISR's could be executed in less than one or two microseconds, all will be fine. This is the code for the encoder count updating that is called inside the ISR code posted above:

Code:
void  UpdateQuadratureCountA_HIGH()
{
  m_prevEncoderCount = m_currentEncoderCount;
  
    // check channel B to see which way encoder is turning
    if (digitalRead(TRANSPORT_PIN_TRIGGER_SELECT_B_IN) == HIGH)  
      m_currentEncoderCount += m_encoderPolarityFactor;         // CW
    else
      m_currentEncoderCount -= m_encoderPolarityFactor;         // CCW     
}

Is there a way to measure the execution time of the ISR's in clock cycles, or nanoseconds?

Thanks again.

1. If the code knows to run StartTimer() without an _isr at the right time - there is no value in making it an _isr.
2. Seems minimizing the time in _isr()'s would be the important part.
> use:: if (digitalReadFast(TRANSPORT_PIN_TRIGGER_SELECT_B_IN) == HIGH)

Posted this the other day: pjrc.com/threads/70821-Teensy-4-1-Interrupt-Problem
> measures in cycles away from loop() for an interrupt.
Shows usage of isrCnt = ARM_DWT_CYCCNT; to count cycles taking diff of uint32_t's.
 
Oh, Cool! I didn't know about digitalReadFast().

I just thought about something else. If I call StartTimer() from within the encoder ISR's, it will likely wait to return until the exposure is complete. This would prevent the ISR's from triggering properly as the StartTimer() function duration will overlap many executions of the ISR's.

What do you think about setting a global variable like "m_triggerExposure" to 1, and poll for it in the main code. Somthing like:

Code:
void WaitExposure()
{
while (m_scanningActive)
{
    if(m_triggerExposure)
       {
        StartTimer();
        m_triggerExposure = 0;
       }
    else
        delayMicroseconds(1);
 }
}

What do you think?
 
Oh, Cool! I didn't know about digitalReadFast().

I just thought about something else. If I call StartTimer() from within the encoder ISR's, it will likely wait to return until the exposure is complete. This would prevent the ISR's from triggering properly as the StartTimer() function duration will overlap many executions of the ISR's.

What do you think about setting a global variable like "m_triggerExposure" to 1, and poll for it in the main code. Somthing like:

...

What do you think?

Yikes - first answer didn't include looking at the code - yes - calling the StartTimer in the interrupt will delay that interrupt exit and prevent other same/lower priority interrupts from running for the duration.

T_4.1 'empty' loop() cycles over 10M/sec - and goes down from there as code is added.

If the loop() doesn't have spots where the polling would be delayed, then polling in the loop should work.

If not working well the _isr() could calculate the 'stop time', do the digitalWriteFast(PIN_SHUTTER_OUT, LOW);, and then loop could monitor for the 'stop time' with { ARM_DWT_CYCCNT or micros() } and then process as in the example code 'BlinkWithoutDelay.ino' [...\examples\02.Digital\BlinkWithoutDelay\BlinkWithoutDelay.ino]
 
Thanks again to DEFRAGSTER and PAUL. It's so kind of you to spend time with novice engineers and programmers.

In this application, the only other feature that would run in the background would be checking for ethernet commands in the master Loop() function, and that would not execute when StartTimer() is running. So, if I'm understanding you correctly, the StartTimer() routine will run without intervention of clock cycles except for the encoder ISR's. This whole exercise is being performed to offer my customers a cost-competitive hardware solution to my film scanning systems. My more powerful circuit boards include two Teensy 4.1's. One performs advanced transport functions, and the other does the LED lamphouse and exposure control. The Transport processor triggers the LED processor for exposures. It works great.

I'm a recovering Arduino user, It's so amazing that a $30 Teensy can be so powerful in real-world advanced embedded systems!



Well, I'm on to coding!
 
You might check the interrupt priority levels. I believe both Ethernet libraries use interrupts to be notified when a new packet has been received into the ring buffer. Incoming packets can be ARP, ICMP, Bonjour or other broadcast protocols which happen to be running on your LAN. The SysTick timer is always running to update millis() and other timing. If you use USB, it creates interrupts as USB transfers complete.

You probably want your timing sensitive interrupts at a higher priority, so they don’t get delayed by the other stuff.
 
PAUL:

Are the default interrupt priorities of the ethernet/Systick Timer/Etc documented, or do I need to parse the code? I use the native ethernet library for Teensy 4.1.
 
Back
Top