T4 - Latency permanently increased by a single too long audio ISR handler.

Pio

Well-known member
Problem description:
I have noticed that if some of the components do exceed the max allowed execution time in the audio ISR (load goes over 100%) the overall latency is increased and stays increased. That is - it is enough to do it once and the latency stays at new level until the next reset.

To test it i created a custom component that injects a single shot delay into the ISR, the update function works like this:
- receive a new block
- if the delay is not 0, wait x ms.
- transmit the block
- set the delay to 0, making it one shot delay

Scope shots show the latency right after reset, with value ~6.8ms, correct for the 128 words block size.
DS1Z_QuickPrint14.png

After adding a one shot delay in the ISR the latency is increased depending on the injected delay value, yet it never goes back to the initial value
DS1Z_QuickPrint15.png
DS1Z_QuickPrint16.png

Test project:
C++:
#include <Arduino.h>
#include <Audio.h>
#include <AudioStream.h> 

class AudioEffectLatencyTest : public AudioStream
{
public:
    AudioEffectLatencyTest(void) : AudioStream(1, inputQueueArray){    };
    virtual void update(void)
    {
        audio_block_t *block;
        block = receiveWritable();
        if (!block) return;
        delay(delay_ms);
        transmit(block);
        release(block);
        delay_ms = 0;
    }
    void delay_set(uint32_t value)
    {
        __disable_irq();
        delay_ms = value;
        __enable_irq();
    }
    uint32_t delay_get() {return delay_ms;}
private:
    uint32_t delay_ms = 0;
    audio_block_t *inputQueueArray[1];
};

// GUItool: begin automatically generated code
AudioInputI2S            i2s_1;         //xy=598,332
AudioEffectLatencyTest     blockDelay;        // added custom component introducing a one shot delay into the audio ISR
AudioOutputI2S           i2s_2;         //xy=1230,344
AudioConnection          patchCord1(i2s_1, 0, blockDelay, 0);
AudioConnection          patchCord2(i2s_1, 1, i2s_2, 1);
AudioConnection          patchCord3(blockDelay, 0, i2s_2, 0);
AudioControlSGTL5000     codec;       //xy=647,561
// GUItool: end automatically generated code

bool codecOK = false;

void delayISR_set(uint32_t d);
void delay_NoInt_set(uint32_t d);

void setup()
{
    Serial.begin(115200);
    AudioMemory(20);
    codecOK = codec.enable();
    codec.inputSelect(AUDIO_INPUT_LINEIN);
    codec.volume(0.8f);
    codec.lineInLevel(10, 10);
    codec.adcHighPassFilterDisable();
    delay(3000);
    Serial.println("T.41 Latency test - one shot delay injected into the audio ISR");
    Serial.printf("Delay = %d ms\r\n", blockDelay.delay_get());
    Serial.println("Press 1 to set the delay inside the ISR to 0ms");
    Serial.println("Press 2 to set the delay inside the ISR to 5ms");
    Serial.println("Press 3 to set the delay inside the ISR to 10ms");
    Serial.println("Press 4 to set the delay inside the ISR to 20ms");
    Serial.println("Press 5 to set the delay within AudioNoInterrupts to 0ms");
    Serial.println("Press 6 to set the delay within AudioNoInterrupts to 5ms");
    Serial.println("Press 7 to set the delay within AudioNoInterrupts to 10ms");
    Serial.println("Press 8 to set the delay within AudioNoInterrupts to 20ms");   
    Serial.println("Press R to Reset the MCU");
}

void loop()
{
    while (Serial.available())
    {
        uint8_t data = Serial.read();
        
        switch(data)
        {
            case '1':
                delayISR_set(0);
                break;
            case '2':
                delayISR_set(5);
                break;
            case '3':
                delayISR_set(10);
                break;               
            case '4':
                delayISR_set(20);
                break;
            case '5':
                delay_NoInt_set(0);
                break;
            case '6':
                delay_NoInt_set(5);
                break;
            case '7':
                delay_NoInt_set(10);
                break;
            case '8':
                delay_NoInt_set(20);
                break;                                               
            case 'r':
            case 'R':
                SCB_AIRCR = 0x05FA0004; // MCU reset
                break;
            default:
                break;
        }
    }
}

void delayISR_set(uint32_t d)
{
    Serial.printf("Previous delay = %ld\r\n", blockDelay.delay_get());
    Serial.printf("Delay set to %ld ms\r\n", d);
    blockDelay.delay_set(d);
    Serial.printf("AudioMem usage max = %d\r\n", AudioMemoryUsageMax());
    AudioMemoryUsageMaxReset();
}

void delay_NoInt_set(uint32_t d)
{
    Serial.printf("NoInterrupts Delay set to %ld ms\r\n", d);
    AudioNoInterrupts();
    delay(d);
    AudioInterrupts();
}

Input signal is ~30Hz square wave.
I know this is not a normal use case and such situations should be avoided. In some cases there are large memory buffers placed in PSRAM requiring clearing - this takes too much time for one ISR. I was able to fix it by splitting the operation into chunks. I do have one component that is reporting the max CPU load of 45%, yet any attempt to enable it permanently increases the latency. Stays the same even after disabling the component,where it just re-transmits the input blocks directly to output.
 
I'll answer myself and maybe it will be useful for others: it's because of the two blocks queue used by the i2s output to populate the DMA buffer.
 
Back
Top