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
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.
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;
}
}
}