LED Panel with 64x32 Leds controlled by Arduino board

Posted on
  • About a year ago, Gordon added some functions to support Led panel with 64x32 color pixel.
    At that time, I got it running on an ESP32.
    Some days ago this came back to my fingers, and I tried to get it running with one of the first Espruino boards.
    Big surprise, these older boards are much faster than the ESP32. To make it more visible, some lines are witten and the result is what you see in attached images showing a P5 board.


    5 Attachments

    • DSC_0110.JPG
    • DSC_0115.JPG
    • DSC_0126.JPG
    • DSC_0139.JPG
    • DSC_0141.JPG
  • Software with some demos, including some special commands for high speed.

    require("Font8x16").add(Graphics);
    require("Font6x8").add(Graphics);
    
    function Led64x32(){
      var me = this;
      var Pen;  //pin Enable
      var sfnc,dfnc;  // bound functions to shift data and set address
      var ledBuf;//graphics.buffer for LED
      var arr; //array of drawing functions
      var boardDur; //refresh time for panel in mS
      var tmr; //interval timer for refresh 
      me.init = function(board){
        switch(board){
          case "ESP32":
            initPins(D2,D16,D4,D17,D15,D27, D5,D18,D19,D21, D26,D22,D25);
            boarDur = 20;
            break;
          case "Espruino":
            initPins(B3,B5,B4,B6,C11,C10,B7,C9,B8,C8­,C6,B9,C12);
            boardDur = 10;
            break;
          default: console.log(board + " not supported (yet?)");
        }
        return initGraphic();
      };
      me.stop = function(){if(tmr) clearInterval(tmr);};
      function initPins(R1,R2,B1,B2,G1,G2,A,B,C,D,Latch­,Clock,Enable){
        Pen = Enable;
        sfnc = shiftOut.bind(null,[R2,G2,B2,undefined,R­1,G1,B1],{clk:Clock});
        dfnc = digitalWrite.bind(null,[Enable,Latch,Lat­ch,D,C,B,A,Enable]);
      }
      function initGraphic(){
        gr = Graphics.createArrayBuffer(64,32,4,{inte­rleavex:true});
        ledBuf = gr.buffer;
        createFuncArr();
        return gr;
      }
      function createFuncArr(){
        arr = [];
        arr.push({callback:Pen.reset.bind(Pen)})­;
        for(i = 0; i < 16; i++){
          arr.push(new Uint8Array(ledBuf,i*64,64));
          arr.push({callback:dfnc.bind(null,33|i<<­1)});
        }
        arr.push({callback:Pen.set.bind(Pen)});
      }
      me.start = function(){
        tmr = setInterval(function(){
          sfnc(arr);
        },boardDur);
      };
      me.frame = function(c){
        me.clear();
        if(!c) c = 7;
        grf.setColor(c);
        grf.drawRect(0,0,63,31);
        grf.drawRect(1,1,62,30);
      };
      me.clear = function(c){
        clearInterval();
        grf.setBgColor(0);
        grf.clear();
      };
    }
    var lf = new Led64x32();
    var grf = lf.init("Espruino");
    //Some demos
    function color(col){
      clearInterval();
      if(!col) col = 0;
      grf.setBgColor(col);
      grf.clear();
      lf.start();
    }
    function graph(){
      clearInterval();
      var barh = 5;
      grf.setBgColor(6);
      grf.clear();
      grf.setColor(2);
      grf.fillRect(5,12,50,25);
      grf.setColor(4);
      grf.fillRect(10,14,40,20);
      grf.fillRect(10,0,40,0);
      grf.setColor(7);
      grf.drawRect(0,31,5,5);
      lf.start();
      setInterval(function(){
        barh++; if(barh>24) barh = 1;
        grf.setColor(0);
        grf.fillRect(1,30,4,6);
        grf.setColor(1);
        grf.fillRect(1,30,4,30 - barh);
      },200);
    }
    function xmas(){
      var pnt = 0;
      var px = [[10,10],[25,20],[30,15],[40,14]];
      function flicker(){
        grf.setPixel(px[pnt][0],px[pnt][1],2);
        pnt++;if(pnt>3)pnt = 0;
        grf.setPixel(px[pnt][0],px[pnt][1],7);
      }
      lf.frame(7);
      grf.setColor(2);
      grf.fillPoly([1,1,1,31,30,16,1,1]);
      grf.fillPoly([15,3,15,28,40,16,15,3]);
      grf.fillPoly([28,6,28,25,50,16,28,6]);
      grf.setColor(7);
      grf.drawLine(6,2,8,2);
      grf.drawLine(6,30,8,30);
      lf.start();
      setInterval(function(){flicker();},300);­
    }
    function txt(t,c){
      if(!t) t = "Open";
      if(!c) c = 2;
      var cbg = 7;
      lf.frame(cbg);
      grf.setColor(cbg);
      grf.drawEllipse(2,2,62,30);
      grf.setFont8x16();
      grf.setColor(c);
      grf.drawString(t,36 - grf.stringWidth(t) / 2,7);
      lf.start();
      setInterval(function(){
        cbg--;if(cbg<1) cbg=7;
        grf.setColor(cbg);grf.drawEllipse(2,2,62­,30);
        grf.setColor(8 - cbg);
        grf.drawString(t,36 - grf.stringWidth(t) / 2,7);
      },500);
    }
    function magic(){
      var col = 1;radius = 15;
      clearInterval();
      grf.setBgColor(0);
      grf.clear();
      lf.start();
      setInterval(function(){
        col++;
        if(col>7) col = 1;
        radius--;
        if(radius < 3){
          radius = 15;
          grf.setColor(0);
          grf.fillCircle(30,15,15);
        }
        grf.setColor(col);
        grf.fillCircle(30,15,radius);
      },100);
    }
    function mondrinator(){
      var rndCol = function(){ return Math.random() * 6;};
      var rndBorder = function(){ return 1 + Math.random() * 7;};
      var rndRect = function(){ return Math.random() * 4;};
      clearInterval();
      lf.start();
      function showNext(){
        var right,left,top,bottom,rectId;
        grf.setBgColor(7);grf.clear();
        grf.setColor(rndCol());left = rndBorder();grf.drawLine(left,0,left,31)­;
        grf.setColor(rndCol());right = rndBorder();grf.drawLine(63-right,0,63-r­ight,31);
        grf.setColor(rndCol());top = rndBorder();grf.drawLine(0,top,63,top);
        grf.setColor(rndCol());bottom = rndBorder();grf.drawLine(0,31-bottom,63,­31-bottom);
        rectId = parseInt(rndRect());grf.setColor(rndCol(­));
        switch(rectId){
          case 0: grf.fillRect(0,0,left - 1,top - 1);break;
          case 1: grf.fillRect(64-right,0,63,top - 1);break;
          case 2: grf.fillRect(0,32 - bottom,left-1,31);break;
          case 3: grf.fillRect(64-right,31,63,32 - bottom);break;
        }
        setTimeout(function(){showNext();},2000)­;
      }
      setTimeout(function(){showNext();},2000)­;
    }
    function pie(){
      var i,last;
      lf.clear();
      last = 0;
      centerX = 45; centerY = 16;
      var data = [[20,1,"Apple"],[30,2,"Beef"],[40,4,"Bee­r"],[10,6,"Pills"]];
      function drawPie(pnt){
        grf.setColor(7);
        grf.drawLine(0,0,0,31);
        grf.drawLine(1,0,1,31);
        var i,dt = data[pnt];
        var polyData = [centerX,centerY];
        var s = Math.PI * last / 100;
        var x = centerX + Math.round(Math.cos(s) * 15);
        var y = centerY + Math.round(Math.sin(s) * 15);
        polyData.push(x); polyData.push(y);
        for(i = 0; i < 10; i++){
          last += dt[0] / 10;
          s = Math.PI * (last + dt[0] / 10 * i) / 100;
          x = centerX + Math.round(Math.cos(s) * 15);
          y = centerY + Math.round(Math.sin(s) * 15);
          polyData.push(x);polyData.push(y);
        }
        last += dt[0];
        grf.setColor(dt[1]);
        grf.fillPoly(polyData,true);
      }
      function drawText(pnt){
        var dt = data[pnt];
        grf.setFont6x8();
        grf.setColor(dt[1]);
        grf.drawString(dt[2],4,pnt * 8);
      }
      for(i = 0; i < data.length;i++){
        drawPie(i);
        drawText(i);
      }
      lf.start();
    }
    
  • Nice! Thank you!

  • @JumJum... checked out your circle... if you rotate your edge coordinates by half of the slices' angle, you can avoid the 'spikes' - single dots - at the 4 winds (N S E W or Top, Bottom, Left, Right).

  • @JumJum ... or use this function to draw circle:

    function circle(xm, ym, r) {
          var x = -r,
            y = 0,
            err = 2 - 2 * r;
          do {
            g.setPixel(xm - x, ym + y);
            g.setPixel(xm - y, ym - x);
            g.setPixel(xm + x, ym - y);
            g.setPixel(xm + y, ym + x);
            r = err;
            if (r <= y) err += ++y * 2 + 1;
            if (r > x || err > y) err += ++x * 2 + 1;
          } while (x < 0);
    }
    

    The 'spikes' come from the optimized algorithm that can handle ellipses and circles incl. fill (4 auf einen Streich).


    1 Attachment

    • Bildschirmfoto 2019-12-05 um 17.56.24.jpg
  • No matter with or without spikes - great work - I like it!

  • After sneaking and testing, got this:

    function ellipse(int xm, int ym, int a, int b)
    {
       var dx = 0;
       var dy = b;
       var a2 = a*a
       var b2 = b*b;
       var err = b2-(2*b-1)*a2
       var e2; /* Fehler im 1. Schritt */
    
       do {
           g.setPixel(xm+dx, ym+dy); 
           g.setPixel(xm-dx, ym+dy); 
           g.setPixel(xm-dx, ym-dy); 
           g.setPixel(xm+dx, ym-dy); 
    
           e2 = 2*err;
           if (e2 <  (2*dx+1)*b2) { dx++; err += (2*dx+1)*b2; }
           if (e2 > -(2*dy-1)*a2) { dy--; err -= (2*dy-1)*a2; }
       } while (dy >= 0);
    
       while (dx++ < a) { 
           g.setPixel(xm+dx, ym); 
           g.setPixel(xm-dx, ym);
       }
    }
    

    @Gordon, would it be worth the effort (original source )?


    1 Attachment

    • Bildschirmfoto 2019-12-05 um 19.11.40.jpg
  • Yes, totally - I actually just posted in another thread but I'm definitely up for pulling this in

  • ...though circles, ellipses: Could be a performance killer? I'd prefer the segmentation as alternative. On the other hand, I expect it to be better than trigo functions - is it?

  • I think in this case the ellipse function is faster than polygon fill (at least than the one we'll have to implement soon)

  • Got a P5 64x64 panel with fm6126 chip and a ESP32 .

    Is is now running Espruino with @JumJum code after some small changes.

    /* jshint esversion: 6 */
    var P_A = D19,
        P_B = D23,
        P_C = D18,
        P_D = D5,
        P_E = D15,
        P_R1 = D13,
        P_R2 = D27,
        P_G1 = D21,
        P_G2 = D17,
        P_B1 = D12,
        P_B2 = D4,
        P_LAT = D22,
        P_OE = D2,
        P_CLK = D14,
        MaxLed = 256,
        X = 64,
        Y = 64;
    
    var BLACK = 0,
        RED = 1,
        BLUE = 2,
        MAGENTA = 3,
        GREEN = 4,
        YELLOW = 5,
        TUTQUOISE = 6,
        // ....
        WHITE = 15;
    
    function Led64x64() {
        var me = this;
        var Pen; //pin Enable
        var sfnc, dfnc; // bound functions to shift data and set address
        var ledBuf; //graphics.buffer for LED
        var arr; //array of drawing functions
        var boardDur; //refresh time for panel in mS
        var tmr; //interval timer for refresh
    
        me.init = function() {
            initFM6126();
            initPins(P_R1, P_R2, P_B1, P_B2, P_G1, P_G2,
                P_A, P_B, P_C, P_D, P_E,
                P_LAT, P_CLK, P_OE);
            boarDur = 20;
            return initGraphic();
        };
    
        me.stop = function() { if (tmr) clearInterval(tmr); };
    
        me.start = function() {
            tmr = setInterval(function() {
                sfnc(arr);
            }, boardDur);
        };
    
        me.clear = function(c) {
            clearInterval();
            grf.setBgColor(0);
            grf.clear();
        };
    
        function initFM6126() {
            var C12 = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
            var C13 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0];
            digitalWrite([P_OE, P_LAT, P_CLK], 0b100);
            // Send Data to control register 11
            for (var l = 0; l < MaxLed; l++) {
                y = l % 16;
                if (C12[y]) {
                    digitalWrite([P_R1, P_G1, P_B1, P_R2, P_B2, P_G2], 0b111111);
                } else {
                    digitalWrite([P_R1, P_G1, P_B1, P_R2, P_B2, P_G2], 0);
                }
                digitalWrite(P_LAT, (l > MaxLed - 12) ? 1 : 0);
                digitalWrite(P_CLK, 1);
                digitalWrite(P_CLK, 0);
            }
            digitalWrite(P_LAT, 0);
            digitalWrite(P_CLK, 0);
            // Send Data to control register 12
            for (l = 0; l < MaxLed; l++) {
                y = l % 16;
                if (C13[y]) {
                    digitalWrite([P_R1, P_G1, P_B1, P_R2, P_B2, P_G2], 0b111111);
                } else {
                    digitalWrite([P_R1, P_G1, P_B1, P_R2, P_B2, P_G2], 0);
                }
                digitalWrite(P_LAT, (l > MaxLed - 13) ? 1 : 0);
                digitalWrite(P_CLK, 1);
                digitalWrite(P_CLK, 0);
            }
            digitalWrite(P_LAT, 0);
            digitalWrite(P_CLK, 0);
        }
    
        function initPins(R1, R2, B1, B2, G1, G2, A, B, C, D, E, Latch, Clock, Enable) {
            Pen = Enable;
            sfnc = shiftOut.bind(null, [R2, G2, B2, undefined, R1, G1, B1], { clk: Clock });
            dfnc = digitalWrite.bind(null, [Enable, Latch, Latch, E, D, C, B, A, Enable]);
        }
    
        function initGraphic() {
            gr = Graphics.createArrayBuffer(X, Y, 4, { interleavex: true });
            ledBuf = gr.buffer;
            createFuncArr();
            return gr;
        }
    
        function createFuncArr() {
            arr = [];
            arr.push({ callback: Pen.reset.bind(Pen) });
            for (i = 0; i < 32; i++) {
                arr.push(new Uint8Array(ledBuf, i * X, Y));
                arr.push({ callback: dfnc.bind(null, 65 | i << 1) });
            }
            arr.push({ callback: Pen.set.bind(Pen) });
        }
    }
    
    test = function() {
        setInterval(function() {
            grf.setColor(Math.random() * 10);
            grf.fillPoly([Math.random() * 63, Math.random() * 63,
                Math.random() * 63, Math.random() * 63,
                Math.random() * 63, Math.random() * 63
            ], true);
            grf.setColor(0xffff);
            grf.setFont("6x8", 2).drawString(times, 0, 0, true);
            times++;
        }, 1E3);
        lf.start();
    };
    
    var lf = new Led64x64();
    var grf = lf.init();
    var times = 0;
    
    setTimeout(test, 1E3);
    

    If you watch the video you will immediately figure that refresh is too slow to have a stable picture.

    Any idea how to improve speed?

    Could i2s be the kicker?

    Have to try this code with a original Espruino board, hopefully it is faster.

    https://youtu.be/ontmWG33XVA

  • Original Espruino Boards are faster than ESP32, at least for functions like this.
    But I'm pretty sure, based on my experience with 32x64, 64x64 even Espruino boards will still be too slow.
    Watching prices for 64x64 boards, they are really cheap, P2.5 for less than 13€ shipping included

    I2S would be an option, if parallel mode is supported. Have in mond you have to set 11(5+6) data and 3 control ports for a 64x64 Matrix

    Special driver like the one for Neopixel could be an option.
    If I would be Gordon, I would not like this. Support for several boards could end in a nightmare.

  • Just tested a P4 64x64 with a Espruino Wifi board - it work's, without flicker!

    Unbelievable how fast those STM32 chips are, compare with the ESP32.

  • I think it's because on STM32 you have each pin memory mapped to an address, which means functions like shiftOut can really be optimised.

    ESP32 shiftOut could definitely be improved a lot, and it may be that if the IO pins are memory mapped too you could just implement the relevant GetPinAddress functions and get a massive speed boost

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

LED Panel with 64x32 Leds controlled by Arduino board

Posted by Avatar for JumJum @JumJum

Actions