Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 12 of 12

Thread: Do you have a large LED matrix & Teensy 4 up and running?

  1. #1

    Do you have a large LED matrix & Teensy 4 up and running?

    I'm currently working on a versatile realtime rendering thing which is meant to generate procedural animations based on very few parameters. Stuff like spiral, caleidoscope, tunnel, lens, twist, rotation and so on, all based on multi layer procedural Perlin noise. Basically polar math + noise. It renders stuff like this:

    If you have a >2 kLED setup around I'd appreciate if you could quickly run this code and report back how many kPixel/s and how many fps you get (just check serial monitor).

    I get 80.000 pixel/s throughput on my Teensy 3.6, ESP32 is reported at 53 kpx/s, 110kpx/s at dual core operation. I'm really curious how much a Teensy 4 is capable of pushing out...and how that looks on a large panel / installation / setup.

    Just send me an email & I'll send you the link. Thanks for your support!
    Last edited by StefanPetrick; 03-13-2023 at 06:17 PM.

  2. #2
    Senior Member
    Join Date
    Jan 2014
    Hello Stefan,
    Love seeing your new work. Long time fan.
    I am just not sure what you are after with >2Kled?
    I am running Teensy 4.0 with 64x64 panels in many configurations
    including 3x3 panels.
    My circuitry is based on the SmartMatrix desig.
    Let me know if I can be of help.

  3. #3
    Hello Richard,

    Teensy 4 + 64x64 sounds great! If you have some free minutes it would be a great support if you could run this code on your SmartMatrix.

    I assume line 14, 15 and 71 need to be changed, maybe 44 & 291 as well.

    If you get it up & running I'd like to know the the pixel/s and fps count you get (check serial monitor). Also if you could share the changes needed for SmartMatrix it would be awesone.

    Thank you very much!

  4. #4
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Ayer Massachussetts
    Quote Originally Posted by StefanPetrick View Post
    Hello Richard,

    Teensy 4 + 64x64 sounds great! If you have some free minutes it would be a great support if you could run this code on your SmartMatrix.

    I assume line 14, 15 and 71 need to be changed, maybe 44 & 291 as well.

    If you get it up & running I'd like to know the the pixel/s and fps count you get (check serial monitor). Also if you could share the changes needed for SmartMatrix it would be awesone.

    Thank you very much!
    I don't use FastLED but does it support the HUB75 displays that are often used for the larger RGB displays? A quick glance at the FastLED source seemed to indicate it mostly supported serial protocols like WS2812B (neopixel) and APA102 (dotstar or Lumenati).

    FWIW, the shields used to be called SmartMatrix, but they got renamed to SmartLED (and the company building them is called

    While it should be fairly easy to change the code to use the HUB75 matrixes used by the SmartLED shield, it won't be just be changing 3 lines. I.e. write_pixel_to_framebuffer will need to be rewritten, along with the setup and show calls. I suspect you might also have to calculate the frame rate by hand.

    Perhaps somebody should add HUB75/SmartLED support to FastLED, or perhaps they did, and I missed it.

    FWIW, I have a few 32x64 and 64x64 HUB75 panels from Adafruit along with the SmartMatrix Teensy 4.x shield. I also bought the Teensy 3.5/3.6 shield, but I haven't used it. I haven't done any programming of the displays, other than just tweaking a few programs. My favorite display is the 2.0mm 64x64 pitch display:

  5. #5
    My code is entirely independent from FastLED, it could easiely run on any other LED protocol or library, also on a LCD- or OLED interface...

    All I need is a place to write the next frame into. Even the colordepth could be adjusted easiely, I convert 32 bit float values down to 0-255 brightness values in the very last step - it would be trivial to output 10 or 16 or any desired bits per color channel as well.

    There is only one place where I write data (line 291), it should be really simple to have this "render engine" (is this an appropriate term?) running on any visual 2d setup.

    edit: Even on only 8 bit color resolution the results are pretty satisfying.

    The minimal flicker is due to camera interference, IRL it looks uncompromised smooth and soft.
    Last edited by StefanPetrick; 03-17-2023 at 06:16 PM.

  6. #6
    This is the same algorithm outputing RGBs to Processing which draws colored rectangles based on the data the renderer provides.

    ...and here on low res 16x16 again...

    Last edited by StefanPetrick; 03-17-2023 at 09:36 PM.

  7. #7
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Ayer Massachussetts
    I had an hour or so available tonight, so took a quick look at the code, and in some places, I was able to replace the FastLED screen stuff with SmartLED.

    But unfortunately, you use a lot of FastLED support that may not map into SmartLED. I think you need somebody with a more detailed knowledge of both libraries to proceed further.

    In particular:

    • You include something called FLOAT.h that isn't in my library set, but it seems ok if I comment it out.
    • You use something from FastLED called inoise16. I have no idea what this is. But I suspect I can't just comment it out.
    • The adjust_gamma function will likely need to be completely rethought. I don't know if you can get the current values of pixel at the X/Y position.. For the moment, I had just commented out the code.
    • The report_performance function uses a function getFPS that would have to be recoded.

    FWIW, here is my initial stab at this. Note, I don't plan on spending any more time on this:

    // Polar basics demo for the 
    // FastLED Podcast #2
    // VO.1 preview version
    // by Stefan Petrick 2023
    // This code is licenced under a 
    // Creative Commons Attribution 
    // License CC BY-NC 3.0
    #if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
    #include <MatrixHardware_Teensy4_ShieldV5.h>		// SmartLED Shield for Teensy 4 (V5)
    #elif defined(ARDUINO_TEENSY35) || defined(ARDUINO_TEENSY36)
    #include <MatrixHardware_Teensy4_ShieldV4.h>		// SmartLED Shield for Teensy 4 (V5)
    #error "Include the correct header for the microprocessor"
    #include <SmartMatrix.h>
    /* SmartMatrix configuration and memory allocation */
    #define COLOR_DEPTH 24			// Choose the color depth used for storing pixels in the layers: 24 or 48
    					// (24 is good for most sketches - If the sketch uses type `rgb24` directly,
    					// COLOR_DEPTH must be 24)
    #define kMatrixWidth	64U		// Set to the width of your display, must be a multiple of 8
    #define kMatrixHeight	64U		// Set to the height of your display
    const uint8_t kRefreshDepth = 36;	// Tradeoff of color quality vs refresh rate, max brightness, and RAM usage.
    					// 36 is typically good, drop down to 24 if you need to.
    					// On Teensy, multiples of 3, up to 48: 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48.
    					// On ESP32: 24, 36, 48
    const uint8_t kDmaBufferRows = 4;       // known working: 2-4, use 2 to save RAM, more to keep from dropping frames
    					// and automatically lowering refresh rate.  (This isn't used on ESP32, leave as default)
    // Choose the configuration that matches your panels.  See more details in
    // MatrixCommonHub75.h and the docs:
    #if kMatrixHeight == 64
    const uint8_t kPanelType = SM_PANELTYPE_HUB75_64ROW_MOD32SCAN;
    #elif kMatrixHeight == 32
    const uint8_t kPanelType = SM_PANELTYPE_HUB75_32ROW_MOD16SCAN;
    #elif kMatrixHeight == 16
    const uint8_t kPanelType = SM_PANELTYPE_HUB75_16ROW_MOD8SCAN;
    #error "Update setting kPanelType"
    // see docs for options:
    const uint32_t kMatrixOptions		= (SM_HUB75_OPTIONS_NONE);
    const uint8_t  kBackgroundLayerOptions	= (SM_BACKGROUND_OPTIONS_NONE);
    const uint8_t  kScrollingLayerOptions	= (SM_SCROLLING_OPTIONS_NONE);
    // range 0-255
    const int defaultBrightness = 255;
    const rgb24 COLOR_BLACK = { 0, 0, 0 };
    //#include <FastLED.h>
    //#include <FLOAT.h>
    #define WIDTH  kMatrixWidth             // how many LEDs are in one row?
    #define HEIGHT kMatrixHeight            // how many rows?
    #define NUM_LEDS ((WIDTH) * (HEIGHT))
    float runtime;                          // elapse ms since startup
    float newdist, newangle;                // parameters for image reconstruction
    float z;                                // 3rd dimension for the 3d noise function
    float offset_x, offset_y;               // wanna shift the cartesians during runtime?
    float scale_x, scale_y;                 // cartesian scaling in 2 dimensions
    float dist, angle;                      // the actual polar coordinates
    int x, y;                               // the cartesian coordiantes
    int num_x = WIDTH;                      // horizontal pixel count
    int num_y = HEIGHT;                     // vertical pixel count
    // Background for setting the following 2 numbers: the FastLED inoise16() function returns
    // raw values ranging from 0-65535. In order to improve contrast we filter this output and
    // stretch the remains. In histogram (photography) terms this means setting a blackpoint and
    // a whitepoint. low_limit MUST be smaller than high_limit.
    uint16_t low_limit  = 30000;            // everything lower drawns in black
                                            // higher numer = more black & more contrast present
    uint16_t high_limit = 50000;            // everything higher gets maximum brightness & bleeds out
                                            // lower number = the result will be more bright & shiny
    float center_x = (num_x / 2) - 0.5;     // the reference point for polar coordinates
    float center_y = (num_y / 2) - 0.5;     // (can also be outside of the actual xy matrix)
    //float center_x = 20;                  // the reference point for polar coordinates
    //float center_y = 20;                
    //CRGB leds[WIDTH * HEIGHT];               // framebuffer
    float theta   [WIDTH] [HEIGHT];          // look-up table for all angles
    float distance[WIDTH] [HEIGHT];          // look-up table for all distances
    float vignette[WIDTH] [HEIGHT];
    float inverse_vignette[WIDTH] [HEIGHT];
    float spd;                            // can be used for animation speed manipulation during runtime
    float show1, show2, show3, show4, show5; // to save the rendered values of all animation layers
    float red, green, blue;                  // for the final RGB results after the colormapping
    float c, d, e, f;                                                   // factors for oscillators
    float linear_c, linear_d, linear_e, linear_f;                       // linear offsets
    float angle_c, angle_d, angle_e, angle_f;                           // angle offsets
    float noise_angle_c, noise_angle_d, noise_angle_e, noise_angle_f;   // angles based on linear noise travel
    float dir_c, dir_d, dir_e, dir_f;                                   // direction multiplicators
    void setup() {
      Serial.begin(115200);                 // check serial monitor for current fps count
      // Initialize Matrix
      // Teensy users: make sure to use the hardware SPI pins 11 & 13
      // for best performance
      //FastLED.addLeds<APA102, 11, 13, BGR, DATA_RATE_MHZ(12)>(leds, NUM_LEDS); 
      // FastLED.addLeds<NEOPIXEL, 13>(leds, NUM_LEDS);   
      render_polar_lookup_table();          // precalculate all polar coordinates 
                                            // to improve the framerate
      render_vignette_table(9.5);           // the number is the desired radius in pixel
                                            // WIDTH/2 generates a circle
    void loop() {
      // set speedratios for the offsets & oscillators
      spd = 0.05  ;
      c   = 0.013  ;
      d   = 0.017   ;
      e   = 0.2  ;
      f   = 0.007  ;
      calculate_oscillators();     // get linear offsets and oscillators going
      // ...and now let's generate a frame 
      for (x = 0; x < num_x; x++) {
        for (y = 0; y < num_y; y++) {
          // pick polar coordinates from look the up table 
          dist  = distance [x] [y];
          angle = theta    [y] [x];
          // Generation of one layer. Explore the parameters and what they do.
          scale_x  = 10000;                       // smaller value = zoom in, bigger structures, less detail
          scale_y  = 10000;                       // higher = zoom out, more pixelated, more detail
          z        = 0;                           // must be >= 0
          newangle = angle + angle_c;
          newdist  = dist;
          offset_x = 0;                        // must be >=0
          offset_y = 0;                        // must be >=0
          show1 = render_pixel();
          // Colormapping - Assign rendered values to colors 
          red   = show1;
          green = 0;
          blue  = 0;
          // Check the final results.
          // Discard faulty RGB values & write the valid results into the framebuffer.
      // Bring background layer to the front
      backgroundLayer.swapBuffers ();
      // check serial monitor for current performance data
      //EVERY_N_MILLIS(500) report_performance();
    //-----------------------------------------------------------------------------------end main loop --------------------
    void calculate_oscillators() {
      runtime = millis();                          // save elapsed ms since start up
      runtime = runtime * spd;                     // global anaimation speed
      linear_c = runtime * c;                      // some linear rising offsets 0 to max
      linear_d = runtime * d;
      linear_e = runtime * e;
      linear_f = runtime * f;
      angle_c = fmodf(linear_c, 2 * PI);           // some cyclic angle offsets  0 to 2*PI
      angle_d = fmodf(linear_d, 2 * PI);
      angle_e = fmodf(linear_e, 2 * PI);
      angle_f = fmodf(linear_f, 2 * PI);
      dir_c = sinf(angle_c);                       // some direction oscillators -1 to 1
      dir_d = sinf(angle_d);
      dir_e = sinf(angle_e);
      dir_f = sinf(angle_f);
      uint16_t noi;
      noi =  inoise16(10000 + linear_c * 100000);    // some noise controlled angular offsets
      noise_angle_c = map_float(noi, 0, 65535 , 0, 4*PI);
      noi =  inoise16(20000 + linear_d * 100000);
      noise_angle_d = map_float(noi, 0, 65535 , 0, 4*PI);
      noi =  inoise16(30000 + linear_e * 100000);
      noise_angle_e = map_float(noi, 0, 65535 , 0, 4*PI);
      noi =  inoise16(40000 + linear_f * 100000);
      noise_angle_f = map_float(noi, 0, 65535 , 0, 4*PI);
    // given a static polar origin we can precalculate 
    // all the (expensive) polar coordinates
    void render_polar_lookup_table() {
      for (int xx = 0; xx < num_x; xx++) {
        for (int yy = 0; yy < num_y; yy++) {
            float dx = xx - center_x;
            float dy = yy - center_y;
          distance[xx] [yy] = hypotf(dx, dy);
          theta[xx] [yy]    = atan2f(dy, dx);
    // calculate distance and angle of the point relative to
    // the polar origin defined by center_x & center_y
    void get_polar_values() {
      // calculate current cartesian distances (deltas) from polar origin point
      float dx = x - center_x;
      float dy = y - center_y;
      // calculate distance between current point & polar origin
      // (length of the origin vector, pythgorean theroem)
      // dist = sqrt((dx*dx)+(dy*dy));
      dist = hypotf(dx, dy);
      // calculate the angle
      // (where around the polar origin is the current point?)
      angle = atan2f(dy, dx);
      // done, that's all we need
    // convert polar coordinates back to cartesian
    // & render noise value there
    float render_pixel() {
      // convert polar coordinates back to cartesian ones
      float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
      float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
      // render noisevalue at this new cartesian point
      uint16_t raw_noise_field_value = inoise16(newx, newy, z);
      // a lot is happening here, namely
      // A) enhance histogram (improve contrast) by setting the black and white point
      // B) scale the result to a 0-255 range
      // it's the contrast boosting & the "colormapping" (technically brightness mapping)
      if (raw_noise_field_value < low_limit)  raw_noise_field_value =  low_limit;
      if (raw_noise_field_value > high_limit) raw_noise_field_value = high_limit;
      float scaled_noise_value = map_float(raw_noise_field_value, low_limit, high_limit, 0, 255);
      return scaled_noise_value;
      // done, we've just rendered one color value for one single pixel
    // float mapping maintaining 32 bit precision
    // we keep values with high resolution for potential later usage
    float map_float(float x, float in_min, float in_max, float out_min, float out_max) { 
      float result = (x-in_min) * (out_max-out_min) / (in_max-in_min) + out_min;
      if (result < out_min) result = out_min;
      if( result > out_max) result = out_max;
      return result; 
    // Avoid any possible color flicker by forcing the raw RGB values to be 0-255.
    // This enables to play freely with random equations for the colormapping
    // without causing flicker by accidentally missing the valid target range.
    void rgb_sanity_check() {
          // rescue data if possible: when negative return absolute value
          if (red < 0)     red = abs(red);
          if (green < 0) green = abs(green);
          if (blue < 0)   blue = abs(blue);
          // discard everything above the valid 0-255 range
          if (red   > 255)   red = 255;
          if (green > 255) green = 255;
          if (blue  > 255)  blue = 255;
    // check result after colormapping and store the newly rendered rgb data
    void write_pixel_to_framebuffer() {
          // the final color values shall not exceed 255 (to avoid flickering pixels caused by >255 = black...)
          // negative values * -1 
          // write the rendered pixel into the framebutter
          //leds[XY(x, y)] = finalcolor;
          backgroundLayer.drawPixel(x, y, {(uint8_t)red, (uint8_t)green, (uint8_t)blue});
    // find the right led index
    //uint16_t XY(uint8_t x, uint8_t y) {
    //  if (y & 1)                             // check last bit
    //    return (y + 1) * WIDTH - 1 - x;      // reverse every second line for a serpentine lled layout
    //  else
    //    return y * WIDTH + x;                // use this equation only for a line by line led layout
    //}                                        // remove the previous 3 lines of code in this case
    // make it look nicer - expand low brightness values and compress high brightness values,
    // basically we perform gamma curve bending for all 3 color chanels,
    // making more detail visible which otherwise tends to get lost in brightness
    void adjust_gamma() {
      //for (uint16_t i = 0; i < NUM_LEDS; i++)
      //  leds[i].r = dim8_video(leds[i].r);
      //  leds[i].g = dim8_video(leds[i].g);
      //  leds[i].b = dim8_video(leds[i].b);
    // precalculate a radial brightness mask
    void render_vignette_table(float filter_radius) {
      for (int xx = 0; xx < num_x; xx++) {
        for (int yy = 0; yy < num_y; yy++) {
          vignette[xx] [yy] = (filter_radius - distance[xx] [yy]) / filter_radius; 
          if (vignette[xx] [yy] < 0) vignette[xx] [yy] = 0;  
    // show current framerate and rendered pixels per second
    void report_performance() {
      //int fps = FastLED.getFPS();                 // frames per second
      //int kpps = (fps * HEIGHT * WIDTH) / 1000;   // kilopixel per second
      //Serial.print(kpps); Serial.print(" kpps ... ");
      //Serial.print(fps); Serial.print(" fps @ ");
      //Serial.print(WIDTH*HEIGHT); Serial.println(" LEDs ... ");

  8. #8
    Thanks for your time, Michael.

    Float.h is not essenential, I temporaty used it do use predifened numers, like FLT_MAX. I assumed that it comes with Arduino / Teensyduino, at least I never installed it but have it anyway.

    inoise16 is a 3d Simplex noise implementation that comes with FastLED. I am currently looking for something more performant to replace it with. Meanwhile this FastLED function can be used without using FastLED for anything else, it works not on the framebuffer, it just returns 16bit noisevalues.

    adjust_gamma is not essential, can be commented out for now, but I will rework it.

    The fps counting indeed uses a FastLED function - I'll replace it by a rewrite, too.

    I'm in contact with Louis (SM autor) and the SM community. I will come back here when I can provide a SM-version of this code, should be plug & play by then.

    Again, thanks for taking the time and pointing out the issues.

  9. #9
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Try changing #include <FLOAT.h> to #include <float.h>.

    The filename is all lowercase. Using uppercase probably works on Windows and most MacOS, but will fail with a case sensitive filesystem used most Linux systems or some MacOS.

  10. #10
    Senior Member
    Join Date
    Jan 2014
    I am not able to convert the code. I would be happy to test on larger matrices (192x192) should you desire.

    good luck,


  11. #11
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Ayer Massachussetts


    FWIW, there appears to be two examples in the SmartMatrix library that use FastLed with Smartmatrix (FastLed_Functions and FastLed_Panel_Plus_Apa102). They run on my display once I select the proper shield and display type. I haven't looked into what they are doing.

  12. #12
    @Richard: What kind of LEDs or interface do you use? Would be cool so see this running in large.

    Have you seen this one?

    Code here if you'd like to hypnotize yourself. :-)
    Last edited by StefanPetrick; 03-20-2023 at 10:58 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts