Cache/load interupt call to make first execution fast
Posted: Wed Apr 10, 2024 2:08 pm
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:
Oszilloscope picture for a subseqeuent call:
--------------
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
I use this function to initialize the pcnt (based largely on the example code):
If somebody is interested to investigate this with a full example code,
I can try to create a minimal working example on Friday.
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:
Oszilloscope picture for a subseqeuent call:
--------------
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, ¤t_event.status);
digitalWrite(pin,state); // Called only by real first interupt execution
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);
}
I can try to create a minimal working example on Friday.