ESP32 Unexplainable occasional timing delay causing output jitters

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. It fully works, it's data out is just jittery.
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).

I feel like the arduino framework is limiting me but really don't want to start over.

[size=150]The problem:[/size]

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


In this scope it is working correctly. The red line shows the maximum delay it occasionally has.

[size=150]How data out the code works:[/size]

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.

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.[size=150] This is the core of the issue.[/size]

When the timer interrupt triggers a lane is put low (because i'm using an active low buffer, this sends the data in to data out. essentially cutting and forwarding whatever is left.

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(;:wink: 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

#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 (;:wink:
{
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;
}
}
}

That machine has "supervisor" interrupts operating in the background that you do not normally have control of. That is how it controls the WiFi etc. These ISRs take some time which in turn will cause jitter in your code.

What are you using the generate PWM, the LEDC API or the MCPWM API?

Yeah that's what i figured.
Any way I could give my own thing a higher priority? I already set the task itself to max priority.

Another solution I was thinking about is removing vtaskdelay and disabling the WDT so that it's stuck in my task. (Thus can't lose time doing anything else)
I already partly tried that with hiding vtaskdelay in a condition though that disables it in the critical moment.

See code?
ledc

Not sure how this is relevant though. These operations aren't done in the time critical moments.

The PWM generated by the MCPWM and the LEDC do NOT use CPU cycles or CPU interrupts unless programed to do so.

No.

Anyways, good luck.

What??? I really don't get the point of you even asking?

Here is a basket of fruits

Q: Are there lemons in your basket?

It's right in front of you, yes look

Q: No thanks, btw lemons are only sour when you bite in them

2 Likes

A tip would be greatly appreciated, I keep trying things but really can't find it.

I'm a bit surprised that they did not recommend to use machine learning, or Linear Regression (always capitalized) :wink:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.