Improve Neopixel Peformance?

Posted on
  • I've got a 24 Led ring which I'm able to control successfully from Puck.js. The FPS, however, is a little 'meh'. I am using setInterval at an interval of 1 milli (just to see if that speeds it up any) and it looks like it's running around 20-25-ish FPS. Also, I don't have anything logging to the console (I noticed that slowed things down some).

    Is there any way to either increase the speed at which the pixels refresh over SPI or is there a better method than setInterval to provide a little more FPS to the leds?

    Thanks!

  • Actually, just ran a test, the framerate is dropping to 12fps when the code is executed - otherwise it runs at 33fps.

    
    //hacks to get TWEEN to work
    var window = {};
    window.setTimeout = setTimeout;
    
    var TWEEN = require("Tween");
    var neopixel = require("neopixel");
    
    //hacks to get TWEEN to work
    process.hrtime = null;
    TWEEN.now = Date.now;
    
    var lastLoop = new Date();
    
    function animate(time) {
      
        var thisLoop = new Date();
        var fps = 1000 / (thisLoop - lastLoop);
        console.log(fps);
        lastLoop = thisLoop;
      
        TWEEN.update(time);
    }
    setInterval(animate, 1);
    
    function run() {
      
      var leds = 24;
      var arr = new Uint8ClampedArray(leds*3);
      var n = 0;
    
      var coords = { x: 0, y: 0 }; // Start at (0, 0)
      var tween = new TWEEN.Tween(coords)
          .to({ x: 255, y: 2000 }, 1000)
        .easing(TWEEN.Easing.Quadratic.Out)
          .onUpdate(function() {
            var r = Math.round(coords.x);
            n = 0;
            for(var a = 0 ; a < arr.length ; a++) {
               arr[n++] = (Math.random() * 255) * 0.25; 
            }
            
            neopixel.write(D30, arr);
          })
          .start();
    
    }
    
  • Looks to me line 19 there is still a console.log.

    Why don't you push the frame rates into an array and dump that at the end or in a timeout that happens say once a minute?

  • Logging in Puck.js (when you're connected via Bluetooth) really slows things down (25ish FPS sounds about right) - so that would definitely be something to avoid doing often. When you're not connected it should be ok, but then there's not much point having them :)

    Neopixel writes should be pretty much as fast as they can get, however the actual JS code execution speed isn't huge, so that alone could be slowing things down for you.

    If you want to log speed I'd do:

    var frames = 0;
    var arr = new Uint8ClampedArray(leds*3);
    function animate() {
      neopixel.write(D30, arr);
      frames++;
    }
    setInterval(animate, 1);
    setInterval(function() {
      console.log(frames);
      frames=0;
    }, 1000);
    

    So then you're only doing a print every second, which shouldn't affect the speed.

  • Sounds interesting - any references I could take a look at to wrap my head around this concept?

  • This is a problem with espruino for animations...

    Remember the pingpong light project? (Which worked - there are 8 strings around my room of 10 lights each.) On esp8266's running espruino.

    Why only 10 per string? Js execution speed on the esp8266 controllers. Not that the esp8266 is the fastest microcontroller in town, of course

  • any references I could take a look at to wrap my head around this concept?

    In the code above? Rather than printing for every frame, you just increment a counter. Then every second you print the value of that counter. If 30 frames were rendered in that second then it should print 30.

    If you're asking about the JS execution speed then http://www.espruino.com/Performance might help though

  • While it's an old thread, starting with the code provided

    arr[n++] = (Math.random() * 255) * 0.25; 
    

    I tried to optimize, and see what's possible:

    const pin = NodeMCU.D5;
    const leds = 24;
    const arrl = 24*3;
    var arr = new Uint8ClampedArray(arrl);
    var animate = function(){
      for(var a = 0; a < arrl ; a++) {
        //0.01s
        //no calculation at all
        //0.06s
        //arr[a] = Math.random() * 64;
        //0.071s
        //arr[a] = Math.random() * 63.75;
        //0.075s
        //arr[a] = Math.random() * 255 * 0.25;
        //0.097s
        //arr[a] = Math.round(Math.random() * 63.75);
        //0.100s
        //arr[a] = Math.floor(Math.random() * 63.75);
      }
      require("neopixel").write(pin, arr);
    };
    

    The duration measured is included in the code sample, and it's obvious that the neopixel write itself is fast, there's no optimization needed. However filling every array index and calling Math functions makes things slow. To avoid this I did the following:

    var arr1 = new Uint8ClampedArray(leds);
    var arr2 = new Uint8ClampedArray(leds);
    var arr3 = new Uint8ClampedArray(leds);
    for(var a = 0; a < arrl ; a++) {
      arr1[a] = Math.random() * 64;
      arr2[a] = Math.random() * 64;
      arr3[a] = Math.random() * 64;
    }
    for (i=1; i<10; i++) {
        if (i===1) require("neopixel").write(pin, [].concat(arr1,arr2,arr3));
          else if (i===2) require("neopixel").write(pin, [].concat(arr1,arr1,arr1));
          else if (i===3) require("neopixel").write(pin, [].concat(arr1,arr3,arr2));
          else if (i===4) require("neopixel").write(pin, [].concat(arr3,arr2,arr1));
          else if (i===5) require("neopixel").write(pin, [].concat(arr1,arr3,arr1));
          else if (i===6) require("neopixel").write(pin, [].concat(arr3,arr2,arr3));
          else if (i===7) require("neopixel").write(pin, [].concat(arr3,arr2,arr3));
          else if (i===8) require("neopixel").write(pin, [].concat(arr3,arr3,arr2));
          else if (i===8) require("neopixel").write(pin, [].concat(arr3,arr2,arr3));
          else if (i===9) require("neopixel").write(pin, [].concat(arr3,arr3,arr2));
          else require("neopixel").write(pin, [].concat(arr3,arr3,arr3));
      }
    

    This code is 10x faster. However you should note that [].concat() returns a standard array with three (or four) UInt8Arrays inside. (concat and splice are not implemented for UInt8Arrays, so you can't use these functions). Running this code with standard arrays more than doubles the execution time. When doing rainbow effects or other smooth animations you would want to use UInt8Arrays as containers for RGB values, and standard arrays for these containers (if you have enough variables and RAM available).

  • ...line # 18 will never be executed...

    Since you have a complete filled loop, you can let go of the loop of 1..<10... just do the line. Furthermore, require is a lookup - fast but still a look up, therefore get it into a variable at the beginning w/ var np = require("neopixel");.

  • You can have a preallocated Uint8Array or the right size, and then use .set 3 times to set the contents at 3 specific indexes - that should be super fast (and will also allow for some rotation type effects if you want).

  • @allObjects: Line 18 doesn't matter as it was just used to get an idea of the execution time simulating some random merging of per-calculated values. I'll use the require(), that's good.

    @Gordon: I didn't get what function do you mean using ".set". I'd say setting RGB values at runtime is most likely too slow in most cases.

    Having this thread resurrected, I did a quick rainbow implementation.

    //rainbow effect
    
    const pin = NodeMCU.D5;
    pinMode(pin, "output");
    var neopixel = require("neopixel");
    
    //array with rgb values as Uint8ClampedArray
    var rgb = [];  //length = 255/step = max. number of leds
    const step = 3;  //integer n, use every n-th rainbow color, effect will be n times faster
    //const leds = 64; //the number of leds, used by the slower version only
    
    //
    //getRainbowColor(position = 0..255)
    //returns color of rainbow at the position as rgb in a Uint8ClampedArray
    //
    var getRainbowColor = function (position) {
    var rgb = new Uint8ClampedArray(3);
      if(position < 85) {
       rgb[0] = position * 3;
       rgb[1] = 255 - position * 3;
       rgb[2] = 0;
      } else if (position < 170) {
       position -= 85;
       rgb[0] = 255 - position * 3;
       rgb[1] = 0;
       rgb[2] = position * 3;
      } else {
       position -= 170;
       rgb[0] = 0;
       rgb[1] = position * 3;
       rgb[2] = 255 - position * 3;
      }
      return rgb;
    };
    
    //
    //prepare ()
    //
    //prefill the rgb array
    var prepare = function() {
      for (var i=0; i<255; i += step) {
        rgb.push(getRainbowColor(i));
      }
    };
    
    //
    //animate
    //
    var tmp;
    var animate = function() {
      //rgb.push(rgb.shift());                   //rotate the rgb array by one value
      rgb.unshift(rgb.pop());                    //rotate the rgb array by one value, a little bit faster
      neopixel.write(pin, rgb);                  //faster version
      //neopixel.write(pin, rgb.slice(1, leds)); //slower version, technically cleaner
    };
    
    
    var onInit = function() {
      prepare();
      setInterval(function() { animate();}, 10); //10ms, if using serial/bt/wifi this might too fast
    };
    
    onInit(); //don't save() this line
    

    If you have any suggestions on how to make the code faster please give me a hint. And if you want it to https://www.espruino.com/WS2811 feel free to copy it.

  • @maze1980, regarding line #18: lines 17 and 18 have the same condition... so #18 was never used.

  • I know, it's a copynpaste of the "else if.." line for about 10 times to do compare the speeds (mainloop with rgb color calculations vs mainloop with merging arrays).

  • I mean instead of [].concat(arr1,arr2,arr3) use http://www.espruino.com/Reference#l_Arra­yBufferView_set:

    var tmp = new Uint8ClampedArray(leds*3); // preallocate this
    tmp.set(arr1,0);
    tmp.set(arr2,leds);
    tmp.set(arr2,leds*2);
    
  • Thanks Gorden. It's a nice function and really fast. Just tried it with a pixel runner left/right.

    //runner (left/right)
    
    var pin = NodeMCU.D1;
    pinMode(pin, "output");
    var neopixel = require("neopixel");
    const leds = 64;
    
    const ledsx3 = 3*leds;
    //array with rgb values as Uint8ClampedArray
    const base = new Uint8ClampedArray(ledsx3);
    var show = new Uint8ClampedArray(ledsx3);
    var dot1 = new Uint8ClampedArray(3);
    dot1[0] = 128;
    dot1[1] = 128;
    dot1[2] = 128;
    var dot2 = new Uint8ClampedArray(3);
    dot2[0] = 128;
    dot2[1] = 0;
    dot2[2] = 0;
    var dot3 = new Uint8ClampedArray(3);
    dot3[0] = 0;
    dot3[1] = 0;
    dot3[2] = 128;
    
    //
    //prepare
    //
    var prepare = function() {
      for (var i=0; i<ledsx3; i++) {
        base[i] = 0;
      }
    };
    
    //
    //animate
    //
    var dir1 = 3;
    var pos1 = 0;
    var dir2 = -6;
    var pos2 = leds;
    var dly3 = 0;
    var pos3 = 3*Math.round(Math.random()*leds);
    var start, duration;
    var animate = function() {
      start = getTime();
      show.set(base);
      show.set(dot1, pos1);
      show.set(dot2, pos2);
      show.set(dot3, pos3);
      neopixel.write(pin, show);
      pos1 = pos1 + dir1;
      if (pos1 >= ledsx3) dir1 = -3;
      else if (pos1 <= 0) dir1 = 3;
      //duration of all code above: 0.006s
      pos2 = pos2 + dir2;
      if (pos2 >= ledsx3) dir2 = -6;
      else if (pos2 <= 0) dir2 = 6;
      //duration of all code above:  0.007s
      dly3++;
      if (dly3 === 7) {
        dly3 = 0;
        pos3 = 3*Math.round(Math.random()*leds);
      }
      duration = getTime()-start;
      //duration of all code above:  0.008s
    };
    
    
    var onInit = function() {
      setInterval(function() { animate(); }, 10);
    };
    
    onInit(); //don't save() this line
    

    Execute at your on risk, epilepsy warning.

  • what about replace base[i] = 0; with base.fill(0);in function prepare?

  • Actually the prepare function isn't used and needed, the array is initialized with 0 values. I should have removed the function. If any other value but zero was needed .fill would be faster and shorter than using a loop, indeed. Thanks for the info.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Improve Neopixel Peformance?

Posted by Avatar for user90787 @user90787

Actions