• It's a while I had something done. @Mrbbp inspires / challenges me with some Graphical Art Installations... I finally got the hardware - a 4 in 1 board with 4 8x8 LED Matrixes controlled with Maxim MAX7219. To make it work smoothly, I take advantage of the way the Espruino Graphics object uses the storage and the Espruino's compiled inline-C feature. - Checkout the attached .mp4 clip...

    to be continued...


    2 Attachments

  • ...continuation:

    I know that Espruino Graphics Object has already nice features to draw texts, lines and shapes, move them around and display on various devices. Speed is tough most of the time critical...

    For a smooth scrolling text with no clipping issues is kind of easy when taking just one line - 8 bit height - and cofiguring vertical byte storage layout in the buffer. But that is not enough... the displaying hardware has to support a similar topography... Unfortunately, the MAX7219 and the wiring of the board are not very supporting of my lean approach.

    I use the MAX8219 module from Epruino modules library, butI feed only part of the Graphics buffer. Since that requires quite some amount and not trivial bit manipulating I resorted to inline Compiled C to fill the display buffer that I move as a view port over the actual graphics buffer. This way I can have any length of text - limited by the memory - and have to transform only the part that is displayed.

    To make it work with simple data structures and logic, I force the buffers and argument passing array to be flat arrays. The Uint32Array needs 6+ elements at construction time to get flat. E.toString(...) is the 'sledge hammer of choice' to get the buffers flat.

    I did some timing:

    • 3 ms for the transformation of 256 dots from vertical storage pattern into horizontal one.
    • 31+ ms for shipping it out onto the display (the module... and I'm sure something can be done to make that better... may be that I will tackle later).

    I made a thought exercise with a TM1640 - see playing with ArrayBuffer and led matrix... - and am almost settled with the conclusion that it is a it is a better device thane the MAX7219 (may be I will think different after having an opportunity to actually play with it).

    The inline C is a first shot... that probably could get some more attention. But so far I'm happy with it and glad this C-option is available.
    Here is the code:

    // scrollText8MAX7219.js - (c) allObjects - 2020.0606
    // HORIZONTAL scrolling text w/ string of Maxim MAX7219
    // and 8x8 LED matrices. Requires Espruino > 2v05: 2v05.155
    //
    // Approach: Display buffer is a view port moving over
    // much larger graphics buffer of vertical (byte) storage
    // aligmnent structure for optimized horizontal scrolling.
    // Since (most) displays are implemented for vertical (line)
    // scrolling and expect hardware/wire wise to be fed that
    // way. For performance reason, inline compiled C is used
    // to extract from vertical storage oriented (static)
    // graphics buffer into horizontal storage oriented (dynamic) 
    // display buffer just what is needed for displaying current
    // frame. Overall result is a shift/extract and rotate kind
    // of data tranformtion.
    var dTex = "         Espruino - IoT as easy as 123!         " // default
      , dFnt = ((require("Font6x8").add(Graphics))?"":"­Font6x8")
    //, dFnt = ""   // ""=default built in Font4x6, set dTxY = 1
      , dTxX = 0    // draw string X coord (horizontal from left)
      , dTxY = 0    // draw string Y coord (vertical from top)
      , scrs =    4 // 8x8 screens (number of 8x8 matrixes)
      , gWid =  320 // graphics width in pixels horizontal
      , grph        // graphics obj for drawing text, etc
      , gBuf        // grph's graphics buffer(vert byte)
      , dBeg        // display begin (port begin)
      , dEnd        // display end (port end + 1)
      , dSPI = SPI2 //
      , dDIn = B15  // MAX7219 / display data in Pin
      , dClk = B13  // MAX7219 / display clock Pin
      , dLCS = B14  // MAX7219 / display load / CS Pin
      , dDev        // MAX7219 display device
      , dBuf        // display buffer to feed MAX7219s...
      , dLen        // display buffer length (scrs x 8)
      , max7219Mod = require("MAX7219") // ...(horiz. byte)
      , sIId         // scroll intervall Id
      , t10 , t11
      , t20 , t21
      ;
    var extr = function( // extract from grph into disp buf
            src // grph buffer is source, vertical byte
          , dst // disp buffer is destination, horiz. byte
          , beg // begin in src (grph buffer)
          , cnt // count of bytes to extract (8,16,24,...)
          ) { // returns 0 for success, >0 for error
      var args = new Uint32Array(6) // from 6 up its flat
        , addr = E.getAddressOf(args,1);
      args[0] = 1; // return 0=success; >0=error
      args[1] = E.getAddressOf(src,1);
      args[2] = E.getAddressOf(dst,1);
      args[3] = beg;
      args[4] = cnt;
      if (    addr        // check for flat arrays and flat
           && args[1]     // strings to avoid disaster (all
           && args[2] ) { // in-bounds check not impl'd yet)
        return cdC.extr(addr); // extract in compiled C
      } else {
        return 2; // addr, src, dst not flat arrays/strs
      }
      return args[0];
    };
    var cdC = E.compiledC(`
    // void extr(int)
    int extr(unsigned int *args) { // etract from src into dst
      char          dbg = 0;
      char         *src = (char*)*(args+1); // src ptr
      char         *dst = (char*)*(args+2); // dst ptr
      unsigned int  sbg = *(args+3);        // src begin
      unsigned int  cnt = *(args+4);        // ext count
      int           ret = 4;   // return
      char         rowV = 1;   // row val for src bit extract
      char        *srcR;       // src ptr runner src..src+cnt-1
      unsigned int srcC;       // src col counter cnt..1 (down)
      char         colV = 1;   // col val for bit dst add
      char         dstV = 0;   // dst byte val ( |.. )
      char        *dstR = dst; // dst ptr runner dst..dst+cnt-1
      char           xV;       // src byte col extracted value
      while (rowV != 0) {   // all src rows
        srcR = src + sbg;     // set src runner to 1st src byte
        srcC = cnt;           // set col count runner to count
        while (srcC > 0) {    // all src cols 
          xV =   (*(srcR))      // get vertical src byte...
               & rowV;          // ...by row value
          if (xV > 0) {         // if row bit in src byte set,
            dstV |= colV;         // set col bit in dst byte
          }                     // col bit set complete
          colV <<= 1;           // shift col val (div by 2)
          if (colV == 0) {      // if dst byte complete,
            *(dstR) = dstV;       // set dst byte
            dstR++;               // advance dst
            colV = 1;             // ini col val for dst bit add
            dstV = 0;             // ini dst byte value
          }                     // dst byte completed
          srcR++;               // src col (for src row) 'completed'
          srcC--;               // src col (for src row) 'completed'
        }                     // all src cols  (for src row) 'compld'
        rowV <<= 1;           // shift row val (div by 2)
      }                     // all src rows completed
      ret = 0;              // no error (JS function to invoke extr()
      return ret;           // has to make sure it WILL be error free,  
    }                     // such all in bounds and flat storages
    `);
    function ini() { // initialize
      dLen = scrs*8;
      dBuf = new Uint8Array(dLen);
      dBuf.fill(0);
      dBuf = E.toString(dBuf);
      dSPI.setup({mosi:dDIn,sck:dClk});
      dDev = max7219Mod.connect(dSPI, dLCS, scrs);
      grph = Graphics.createArrayBuffer(
                 gWid,8,1,{vertical_byte:true});
      grph.buffer = (gBuf=E.toString(grph.buffer));
      grph.clear();
    }
    function tex(t) {
      t = "" + ((t) ? t : dTex);
      if (dFnt) grph["set"+dFnt]();
      dEnd = Math.max(grph
        .clear()
        .drawString(t,dTxX,dTxY)
        .stringWidth(t)-(scrs*8),0);
      dEnd = (dEnd>0) ? dEnd : 0;
      dBeg = -1;
    }
    function s() { // scroll a step
      dBeg = (++dBeg<dEnd) ? dBeg : 0;
      t10 = getTime();
      var err = extr(gBuf,dBuf,dBeg,dLen);
      t11 = Math.round((getTime() - t10) * 1000);
      if (err) {
        console.log("error on extr():",err);
      } else {
        t20 = getTime();
        dDev.raw(dBuf);
        t21 = Math.round((getTime() - t20) * 1000);
      }
      setTimeout(s,0);
    }
    function onInit() {
      ini();
      tex();
      s();
      // sIId = setInterval(s,40);
    }
    setTimeout(onInit,999);
    

    Work leading up to this implementation and more detail discussion you can find in these other threads:

  • From initial version, I looked into different fonts... The implementation with single line of 8x8 modules, font size is limited to fonts with height of max. 8 pixel. Below a clip and some shots with Font6x8.

    PS: It's quite fun and difficult at the same time to capture a 'clean' frame with complete 'Espruino'. Lag of shutter (due to focus? light adjustment/aperture). and (time) of exposure create all kinds of funny looks as attached below. In some shots you can even see the scanning of the optical sensor: some dots have different colors: nothing, yellow, red,... Of course, I could have stopped the scroll... (...but would that not be cheating?)


    2 Attachments

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

Scrolling text w/ Maxim MAX7219 and stringed 8x8 LED Matrixes

Posted by Avatar for allObjects @allObjects

Actions