hpwit/artnetESP32

readFrame() blocks code exexution

Closed this issue · 22 comments

I'm using your library to do something that it wasn't designed for. I'm drawing 2D images in the air one line at a time.
Core 0 receives the artnet packages and core 1 draws them line after line without a pause. My problem is this. When I call readFrame() it gives me the next frame if it's ready and I can resume drawing the lines. If the next frame isn't ready, for example if there was some packet loss and incomplete frames, readFrame() blocks the execution until I receive the next full frame or it timeouts after a second. I can't draw new lines during this time.

I tried fixing the problem by running readFrame() on core 0 but I got even more frame loss. I also tried running readFrame() in parallel on core 1 but that also increased packet loss. Maybe core 1 is too busy with drawing the leds.

Could you suggest a solution? I tried looking into your source code but could only partially understand how it worked. Is it possible to have all artnet related stuff on core 0 and when I call something perhaps called readFrameWithoutPause() on core 1 that would do a quick memcpy if there is a new full frame in the buffer and if there is no new frame it would do nothing and just let me continue drawing the previous frame line by line.

Here is a video of what I'm doing with your amazing library and a link to the source code. Thanks for all your great work!

https://www.youtube.com/watch?v=1GYIpmODAFo
https://github.com/yliniemi/artnet_whip

hpwit commented

Hello
happy to see that you're using my library.
Great staff by the way.
I think you are missing the library a little bit because you don't need to have a loop for the fastled.show look at the code

https://github.com/hpwit/artnetESP32/blob/master/examples/exampleArtnet/exampleArtnet.ino

In that configuration the fastled.show is automatically called once you've received a frame. you do not have to worry about it.
artnet.setFrameCallback(&displayfunction); //set the function that will be called back a frame has been received artnet.setLedsBuffer((uint8_t*)leds); //set the buffer to put the frame once a frame has been received
let me know if it's not clear

Thanks for a quick reply!

displayFunction() is run once when a new frame is received. If I put FastLED.show() there it would only run it once and just show one line out of 32.
My frame is 32 x 144 pixels and I draw it one line at a time. That's why I have FastLED.show() inside a loop on core 1. I have to cycle through all the lines in a frame. Sometimes multiple times if a new frame hasn't arrived yet in the buffer. Kind of like in the old days when a CRT monitor drew the frame one line at a time.

hpwit commented

Oh oki I see !!
Then I would add a scroll animation to yours so you only send each time only one line not all the 'screen' as you don"t need it. in defining in the fastled you do not need to define 32*144 with is too much and you lose time there.

may is see how your mapping look like from your tool ?

I think I can change the code to to wait only for a specific line before saying that you can has the fastled.show
it would be something like Artnet.readUniverse(x)
what do you think ?

I use Jinx to stream the animations to the staff. My led strip is 144 pixels long so I made the artnet universes the same size. It's easier to set up Jinx when my led strip and universe are the same lenght.

What tool do you suggest I use? Jinx is the only one I've tried. I attached a screenshot of Jinx interface. My matrix is 32 x 144. 32 universes with universe zise of 144.

I would prefer to get a whole frame. Then I can cycle trough the universes/lines constantly in the backrgound on core 1.

Did I understand you correcly? You could change the library to call displayFunction() after it receives each universe.
I see two problems with this. I could end up calling FastLED.show() too fast. displayFunction() is run on core 0 while FastLED.show() is run on core 1. I might end up calling it again before the previous call has finished. Also I want the staff to continue drawing the current frame line by line over and over even if I don't get any more artnet packages. I don't want the staff to freeze if the network has some issues.

I create an array for fastled with this line CRGB ledStrip[LED_HEIGHT];

CRGB ledsArtnet[LED_HEIGHT * LED_WIDTH]; is just for the ArtnetESP32. I use it as a frame buffer. On line 57 in my source code I use memcopy to copy a certain line from the frame buffer before FastLED.show()

Screenshot (3)

hpwit commented

ok i See what you do
why do you need line 53 ? why do you need a new readFrame when you have readframe in a loop?
what you need to do is to use two buffers for your leds (one you display /on you write in) but you'll have to deal with the sync ...
let me look at that a bit in detail I am sur I can come up with something to resolve your issue.

Thanks for looking into this.

Line 53 is needed. I actually never call readFrame(NULL). It was used when I tested running it on an infinite loop on core 0 but now that funtion is never called.

Best solution would be this but it would probably require changing too much:
There could be three buffers being filled one frame at a time all running on core 0. When I call readFrame() it returns the pointer to the last full frame buffer and marks the previous buffer as available for writing. If there is no new frame, it doesn't have to do anything. This would be best for my case since I don't actually have more than one strip in an array for FastLED. But tripple buffering would increase memory consumption for everybody else by 33% because FastLED requires it's own array at compile time.

hpwit commented

Hello
I can use my library to drive the leds which have a functionality to do show(leds) if you are willing to ‘leave’ fastled I will try to give you a new code by tomorrow.
regards
Yves

That's so kind of you. I'm willing to leave fastled. It's just a tool for me.

Thanks for all your help. This was a super nice experience. You are the first developer/maintainer I have ever messaged.

hpwit commented

I have a question. I was doing some math and if you send from jinx at 25fps that means one full frame every 40ms.
To display 144 pixels it will take 4,32ms hence for 32 lines 138ms or more than 3 times the time for on frame�.
how do you want to deal with that ?

You're correct. It takes me longer to display one frame than it takes Jinx to push out a new one. I will lose some frames but I'm okay with that. I'm actually ok with getting just a few frames per second via artnet.

The current staff has the 144 leds in two parts run in parallel so it takes me half as long but it's still like 80 milliseconds which is 2 artnet frames long. So in the best case scenario I would have to skip half the frames. The source code I gave you isn't the one actually running in the staff but the only thing that is different is breaking the 144 pixel universe in half for both halves of the staff.

This is the look I'm going for
https://www.youtube.com/watch?v=NkIDpA9yk94

In the currently available products you have to save the images to the rom of the staff and then cycle through them with an internal timer or a remote control. I want to stream the images to the staff. It doesn't matter if it looks like a slide show instead of an animation. It's still way faster than current products that show the same frame for several seconds. So even getting just one frame per second would be a huge improvement.

How I'd like the software to deal with this?

One way would be tripple buffering where I get the pointer to the next full frame when I call readFrameWithoutPause(). If the buffers are full and more artnet frames are coming they could just be dropped.

Another way would be to have the current double buffering you already have in the library and then just doing a memcpy of the last full frame when I call readFrameWithoutPause() and if there is no new frame readFrameWithoutPause() doesn't do anything and just lets the thread continue running. And just like in the first solution if the buffers are full and more artnet frames are coming they could just be dropped.

hpwit commented

Ok. If you are ok with that it should be possible.

hpwit commented

hello try this
https://gist.github.com/hpwit/24603e10840d50598994ad74da5fffdd
normally it should work as such
display ing in loop on frame while downloading another one.
once the frame has been displayed either a new frame is ready => displaying the new frame other display the same frame.
once a frame is downloaded, the process waits for the other one to be displayed
let me know

Thank you for looking into this during the weekend.

Unfortunately flashing the new code didn't resolve the issue but created a totally garbled image. Here is video I took with the led strip on the floor. In the first video esp32 is receiving packages from Jinx and in the second I turned Jinx off. The third video is what it looked like before.

https://www.youtube.com/watch?v=QwgMeXTseJo
https://www.youtube.com/watch?v=BYs7KGc3aRs
https://www.youtube.com/watch?v=mpgBHCXpNqA

I have the new 2 kHz pwm version of ws2812b. The ancient one had 400 kHz pwm and they did a silent revision. The led strip is connected directly to the esp32. I know ws2812b wants a 5 volt signal but I don't have a logic level converter,

hpwit commented

Humm it looks like I have made a huge mistake lol
let me look into that
I will come back to you asap
i will try to get some leds to test it too
in your code you're doing 144 x 75 pixels right ?

I'm doing 32 x 144 pixels. I got too many incomplete frames with 75 x 144 so I changed it to 32. The source code is in flux because I'm testing new things all the time.

I see that your new driver uses pointers unlike FastLED. That's cool especially for my project since I don't have to do memcpy.

I didn't know I could do artnet.setLedsBuffer(pointerToBuffer) during runtime. I thought I could only set it once.

What does artnet.readFrame() do? I tried following the code and it calls read3() and that has two places it might loop in. What is it doing in those loops and are they necessary?

hpwit commented

Hello
indeed I have implemented the showPixels(ponter) to be able to show any ledsstrips without making copy. if you read the readme you'll see that I can use this to actually do scrolling without moving any bits.

I didn't know I could do artnet.setLedsBuffer(pointerToBuffer) during runtime. I thought I could only set it once.

Actually Indeed but never thought of using it before using your code :)
could you retry this
https://gist.github.com/hpwit/24603e10840d50598994ad74da5fffdd
i have made small modifications
let me know otherwise I would need to pass the full object in parameter

Thanks for a new version. This time it shows an image after like a minute. There is massive frame loss.

nb frames read: 1 nb of incomplete frames:4810 lost:481000.00 %

hpwit commented

ok i have put a two second elapse time before starting to display
can you

  • comment line 53 and retry
  • then change 0- by 1 line 314 and 1 by 0 line 293
    let me know

I can't comment line 53. It's { in my code.

I changed which cores things run on like you suggested and now I only lose 90% of the frames instead of 99.9%.

nb frames read: 79 nb of incomplete frames:762 lost:964.56 %

But that's actually good enough for now. At least the the led whip doesn't freeze like it used to in my original source code.

hpwit commented

sorry it was line 55
vTaskDelay(1)
let me know

I played around with the source code all day and this was solution was indeed the best it could do. This is how much frame loss I got.
nb frames read: 4715 nb of incomplete frames:33973 lost:720.53 %

Since it takes me 138 ms to show all the lines the minimum theoretical loss would be 250% so 720% isn't too far from it.

With my original source code I got this much frame loss.
nb frames read: 954 nb of incomplete frames:1092 lost:114.47 %

The problem with this one was the jitter in the animation when calling readFrame() blocks execution and I have to wait for it to fishish before I can call FastLED.show() again.

I'm perfectly ok with this solution at least for now. 720% frame loss means 3 fps on average and that's quite good for my use case. I'll build a few of these pov whips and an 8 strip pov umbrella. I can come back to this problem later on.
Do you think it's possible to get artnetESP32 running fully on core 0 some day?

I also noticed that your I2SClocklessLedDriver gives more stable results than FastLED. I'm going to start using it. FastLED would sometimes shift the strip by a few pixels every once in a while. I2SClocklessLedDriver does that much less frequently.

How do you want to be credited in my github repository? I licenced my code under agpl 3.0. Should I include some text with the licence and your name in the beginning of artnet_whip.ino

Thank you for all your help :)

hpwit commented

Hello
Good to hear that !!!
I think I can improve the code by doing something inside the fastled show. I need to think about it a little bit.

I think I can have everything from arnet running on core 0. I will look at that.

I have worked a lot on this new version to be more stable and to have more functionalities at a driver level.

First thank you for asking me how I want to be credited !!!! Just mentioned me in your readme and the link to my libraries

You're welcome. and post some video of your staff