Unexplainable variation occasionally breaking my critically accurate timer interrupt

Testkill
Posts: 4
Joined: Mon May 09, 2022 7:39 pm

Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby Testkill » Mon May 09, 2022 8:18 pm

Hello

I've been making a program in the past weeks to act as a machine of several WS2811/2812 controllers in one (argb). So that i can pwm controller motors or non digital led strips (as one single led for each led data signal)

I've got 95% of it figured out and working.
I'm reading argb data using the RMT as interrupts are too slow, to read the data. This works perfectly.

The issue i'm facing is sending the remainder of the data (that doesn't need to be processed by the esp, meant for another argb device (led or board).

The problem:

I'm sometimes about 1 in 20 packets getting a small extra delay. Something seems to be running, rarely, because it's almost always dead accurate, that shouldn't, I don't get why
Image
In this scope it is working correctly. The red line shows the maximum delay it occasionally has.

How data out the code works:

At first I tried counting pulses or reading/writing, on arduino microcontroller you can do nvic set priority but that doesn't seem to exist on arduino's esp32 framework, I couldn't get the interrupt to re trigger fast enough, even triggering once brings a little delay. So i've went another approach.

Image

Above image shows how i've done this. First of all, a single interrupt starts a timer.
This interrupt is very reliable, it always triggers at exactly the same position in the packet (this has been debugged through the scope), A little delay is noticable before it triggers but isn't important as the timer can be adjusted.
In other words, this is not the origin of the issue.

The timer is running on core 0 with nothing else running (at least written by me), so at the second the interrupt starts the timer there shouldn't be anything getting in the way of correctly triggering the timer alarm. But sometimes, something happens that adds a delay. This is the core of the issue.

The RMT code doesn't in any way interfere, i've debugged this on the scope, checking that the RMT recieve interrupt is triggered well after the entire packet is recieved.
on the above picture D2 is a debug lane that shows when the timer interrupt routine is on. Same way has been done for all other interrupt routines.

Pin writing is always done directly to the register.

Time sensitive stuff has been placed on IRAM

What I've already tried:

- Putting the interrupt on core 0, not detaching it but placing it in an IF statement so that it does nothing.

- Putting it core 0, (detach still same place) and attaching through the for(;;) loop on core 0 when a variable is set to 1 in recieve data.

- Same as above + disabling vtaskcore in the pin interrupt and re enabling in the timer interupt

Help would be greatly appreciated, i'm out of ideas how to fix this other than throwing it all away and using PSOC.

The code:

Code: Select all

#include "Arduino.h"
#include "string.h"

TaskHandle_t Task1;

portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

hw_timer_t *timer = NULL;
#define LR1 19
#define LG1 21
#define LB1 18

#define LR2 13
#define LG2 4
#define LB2 14

#define LR3 26
#define LG3 27
#define LB3 25

#define LR4 32
#define LG4 33
#define LB4 35

#define led_type_switch 35
#define data_in 22
#define data_out 23

const int freq = 5000;
const int res = 8;
static uint8_t order[3] = {0, 1, 2};

rmt_obj_t *rmt_recv = NULL;
static uint32_t leds[4][3];

void IRAM_ATTR registerwrite(uint8_t pin, uint8_t val)
{
    if (val)
    {
        GPIO.out_w1ts = ((uint32_t)1 << pin);
    }
    else
    {
        GPIO.out_w1tc = ((uint32_t)1 << pin);
    }
}

void parseRmt(rmt_data_t *items, size_t len, uint32_t leds[][3])
{
    rmt_data_t *it = NULL;
    it = &items[0];
    int led_val = 0;
    uint8_t select = 0;
    for (size_t i = 0; i < 96; i++)
    {
        it = &items[i];
        if (i % 8 == 0)
            led_val = 0;
        led_val += (it->duration0 > 6) << (7 - i % 8);

        if (i % 8 == 7)
        {
            leds[i / 24][select] = led_val;
            select++;
            if (select == 3)
            {
                select = 0;
            }
        }
    }
    for (int i = 0; i < 4; i++)
    {
        select = 3 * i;
        ledcWrite(select, leds[i][order[0]]);       // r out
        ledcWrite((select + 1), leds[i][order[1]]); // g out
        ledcWrite((select + 2), leds[i][order[2]]); // b out
    }
}

void IRAM_ATTR ISR()
{
    portENTER_CRITICAL(&timerMux);
    timerStart(timer);
    detachInterrupt(data_in);
    GPIO.status_w1tc = ((uint32_t)1 << data_in);
    portEXIT_CRITICAL(&timerMux);
}

void IRAM_ATTR onTimer()
{
    portENTER_CRITICAL_ISR(&timerMux);
    registerwrite(data_out, LOW);
    timerStop(timer);
    timerWrite(timer, 0);
    portEXIT_CRITICAL_ISR(&timerMux);
}

void IRAM_ATTR receive_data(uint32_t *data, size_t len, void *arg)
{
    portENTER_CRITICAL_ISR(&timerMux);
    registerwrite(data_out, HIGH);
    parseRmt((rmt_data_t *)data, len, leds);
    attachInterrupt(data_in, ISR, RISING);
    portEXIT_CRITICAL_ISR(&timerMux);
}

void TimerTask(void *parameter)
{
    timer = timerBegin(0, 8, true);
    timerAttachInterrupt(timer, &onTimer, true);
    timerStop(timer);
    timerAlarmWrite(timer, 1142, true);
    timerAlarmEnable(timer);
    for (;;)
    {
        vTaskDelay(10);
    }
}

void setup()
{
    // setup all channels identical
    for (int i = 0; i < 12; i++)
    {
        ledcSetup(i, freq, res);
    }
    // intitalize io pins like the switch and data out
    pinMode(led_type_switch, INPUT);
    pinMode(data_out, OUTPUT);

    // initialize pwm outputs
    ledcAttachPin(LR1, 0);
    ledcAttachPin(LG1, 1);
    ledcAttachPin(LB1, 2);

    ledcAttachPin(LR2, 3);
    ledcAttachPin(LG2, 4);
    ledcAttachPin(LB2, 5);

    ledcAttachPin(LR3, 6);
    ledcAttachPin(LG3, 7);
    ledcAttachPin(LB3, 8);

    ledcAttachPin(LR4, 9);
    ledcAttachPin(LG4, 10);
    ledcAttachPin(LB4, 11);

    // Initialize the channel to capture up to 128 items
    rmt_recv = rmtInit(data_in, false, RMT_MEM_128);

    // Setup RMT 100ns tick, treshold 50 ticks
    float realTick = rmtSetTick(rmt_recv, 100);
    rmtSetRxThreshold(rmt_recv, 50);

    Serial.printf("real tick set to: %fns\n", realTick);
    rmtRead(rmt_recv, receive_data, NULL);

    xTaskCreatePinnedToCore(
        TimerTask,
        "Task1",
        50000,
        NULL,
        2,
        &Task1,
        0);
}

void loop()
{
    if (digitalRead(led_type_switch) != laststate)
    {
        laststate = digitalRead(led_type_switch);
        if (laststate > 0)
        {
            order[0] = 0;
            order[1] = 1;
        }
        else
        {
            order[0] = 1;
            order[1] = 0;
        }
    }
}

Testkill
Posts: 4
Joined: Mon May 09, 2022 7:39 pm

Re: Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby Testkill » Sat May 14, 2022 4:55 pm

A tip would be extremely appreciated, I keep trying things I can't figure this out.

ESP_Sprite
Posts: 9764
Joined: Thu Nov 26, 2015 4:08 am

Re: Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby ESP_Sprite » Sun May 15, 2022 1:57 am

I'm wondering if the functions calling your ISR are in IRAM in Arduino... you could possibly use the ESP-IDF functions, I'm decently sure those are or can be configured to be in IRAM.

zyghom
Posts: 6
Joined: Tue Oct 19, 2021 11:13 pm

Re: Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby zyghom » Sun May 15, 2022 6:18 am

if I am not mistaken, you put portENTER_CRITICAL_ISR where it should be portENTER_CRITICAL and vice versa
I think portENTER_CRITICAL_ISR must be inside ISR, and portENTER_CRITICAL outside ISR

Testkill
Posts: 4
Joined: Mon May 09, 2022 7:39 pm

Re: Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby Testkill » Mon May 16, 2022 11:54 am

zyghom wrote:
Sun May 15, 2022 6:18 am
if I am not mistaken, you put portENTER_CRITICAL_ISR where it should be portENTER_CRITICAL and vice versa
I think portENTER_CRITICAL_ISR must be inside ISR, and portENTER_CRITICAL outside ISR
Good catch, you're right. Sadly it made no difference.

I also tried removing vtaskdelay on core0 and putting the wdt on 30 seconds & no panic, made no difference neither which is really wierd.

Testkill
Posts: 4
Joined: Mon May 09, 2022 7:39 pm

Re: Unexplainable variation occasionally breaking my critically accurate timer interrupt

Postby Testkill » Mon May 16, 2022 12:06 pm

I think I have found the real issue, it seems to be the interrupt starting the timer. I tried putting it on core0 again but that seems to add even more variation/delays No idea why, i checked the interrupt is correctly attached inbetween data packets.

Who is online

Users browsing this forum: No registered users and 70 guests