Page 1 of 1

Cache/load interupt call to make first execution fast

Posted: Wed Apr 10, 2024 2:08 pm
by macros
I have a project where I need to react very fast to the call of an pulse counter interrupt.

However I noticed that the first activation after reset comes with a huge delay. Subsequent calls execute within 4µs, but the first one takes 55µs.

I guess this is due to code loaded into RAM from flash.

I already have the IRAM_ATTR for the interrupt handler.
I also tried preloading parts of the call. If I call my interrupt function by hand, delay decreases to 40µS for the first call.
If I add another pulse counter and let it trigger the interrupt earlier it reduces the time to 28µS.
This is likely because I have to bail out of the interrupt before walking into an if which would segfault and remaining code still has to be cached.
Likely this can be improved a bit by trying to walk trough more of the real code.

Are there better methods or compilation flags to ensure the first execution is also fast?

Oszilloscope picture for a first call with a call to the interrupt function from normal code to cache a bit:
Image

Oszilloscope picture for a subseqeuent call:
Image
--------------

Detailed and additional information.

Within the interrupt only some global and local variables are accessed and an if with 4 conditions is evaluated.
Additionally there are these function calls

Code: Select all

pcnt_get_event_status(pcnt_unit, &current_event.status);
digitalWrite(pin,state); // Called only by real first interupt execution
I use this function to initialize the pcnt (based largely on the example code):

Code: Select all

/* Initialize PCNT functions:
 *  - configure and initialize PCNT
 *  - set up the input filter
 *  - set up the counter events to watch
 */
void pcnt_init(pcnt_unit_t unit, int pin, int ctrl_pin, int16_t treshold0, int16_t treshold1)
{
    /* Prepare configuration for the PCNT unit */
    pcnt_config_t pcnt_config = {
        // Set PCNT input signal and control GPIOs
        .pulse_gpio_num = pin,
        .ctrl_gpio_num = ctrl_pin,
        // What to do when control input is low or high?
        .lctrl_mode = PCNT_MODE_REVERSE, // Reverse counting direction if low
        .hctrl_mode = PCNT_MODE_KEEP,    // Keep the primary counter mode if high
                                         // What to do on the positive / negative edge of pulse input?
        .pos_mode = PCNT_COUNT_INC,      // Count up on the positive edge
        .neg_mode = PCNT_COUNT_DIS,      // Keep the counter value on the negative edge
        // Set the maximum and minimum limit values to watch
        .counter_h_lim = PCNT_H_LIM_VAL,
        .counter_l_lim = PCNT_L_LIM_VAL,
        .unit = (pcnt_unit_t)unit,
        .channel = PCNT_CHANNEL_0,
    };
    /* Initialize PCNT unit */
    pcnt_unit_config(&pcnt_config);

    /* Configure and enable the input filter */
    pcnt_set_filter_value(unit, 10);
    pcnt_filter_enable(unit);

    /* Set threshold 0 and 1 values and enable events to watch */
    if (treshold0 != PCNT_NOTRESHOLD)
    {
        pcnt_set_event_value(unit, PCNT_EVT_THRES_0, treshold0);
        pcnt_event_enable(unit, PCNT_EVT_THRES_0);
    }
    if (treshold1 != PCNT_NOTRESHOLD)
    {
        pcnt_set_event_value(unit, PCNT_EVT_THRES_1, treshold1);
        pcnt_event_enable(unit, PCNT_EVT_THRES_1);
    }
    /* Enable events on zero, maximum and minimum limit values */
    // pcnt_event_enable(unit, PCNT_EVT_ZERO);
    // pcnt_event_enable(unit, PCNT_EVT_H_LIM);
    // pcnt_event_enable(unit, PCNT_EVT_L_LIM);

    /* Initialize PCNT's counter */
    pcnt_counter_pause(unit);
    pcnt_counter_clear(unit);

    /* Install interrupt service and add isr callback handler */
    if (isr_installed == false) {
        // We set the ESP_INTR_FLAG_IRAM so interrupt has to have IRAM_ATTR
        pcnt_isr_service_install(ESP_INTR_FLAG_IRAM); 
        isr_installed = true;
    }
    pcnt_isr_handler_add(unit, trigger_interrupt_handler, (void *)unit);

    /* Everything is set up, now go to counting */
    pcnt_counter_resume(unit);
}
If somebody is interested to investigate this with a full example code,
I can try to create a minimal working example on Friday.

Re: Cache/load interupt call to make first execution fast

Posted: Thu Apr 11, 2024 12:58 am
by boarchuz
You're better off ensuring everything your callback needs is in IRAM. Warming up the cache seems very fragile, at best.

That means ensuring pcnt_get_event_status and digitalWrite, as well as any functions they depend on, are in IRAM too. This might be tricky to alter in an Arduino environment.

Re: Cache/load interupt call to make first execution fast

Posted: Fri Apr 12, 2024 8:03 am
by macros
I am open to switching the code fully to ESP-IDF functions too if that would solve that problem.
I use only a few Arduino functions for convenience.
How would I ensure that pcnt_get_event_status and digitalWrite are in IRAM in that case?

Right now I am using PlatformIO to build my project if that matters.

The project does not use many libraries and calls so code size is low the caching works incredibly well right now. I have tested it many times. However an solution which works reliably even for larger projects (e.g. If I add a webserver) would be great.