• Not every thing that has a lot of lines and pins you want to drive with Espruino has a smart controller built in, such as 1-Wire, SPI, I2C,... Often there is computing power left on the 'central' MicroController - where the application runs - to take on the role - and work - of this smart controller. No need to add specialized hardware.

    There are multiple options to drive something with lots of pins and lines to drive with 'scrificing' just a few Espruino pins:

    The port expanders are great, they even are able to manage interrupts in different ways - and Espruino has even a simple module to talk to those (not supportiong interrupts though - yet). When it though comes to simple output (and even input), a shift register is good enough. And actually, Portexpanders fall under the category of specialized hardware, because - the mentioned MicroChip - includes a full blown I2C or SPI controller - like a satellite/moon controller of the application driving controller.

    To drive, for example a simple LED matrix, a few stringed 74HC595 can do the job - complemented with a little bit of smart software. RYO of such a construct can be tough, but at the same time it is a great teaching moment. It helps to understand and appreciate what's going on when having to pay for, for example, an easy to use display module, supported by an existing Espruino JavaScript software module.

    This whole conversation got triggered by this forum question from @user81779: Is it possible to connect this 8x8 RGB matrix to espruino boards?. The post has pictures of the kind of LED matrix @user81779 wants to use, which I add also to this post. The post also includes some suggestions how I would tackle the task.

    Pure RYO from end-2-end is fun, but time consuming, and sometimes it is more interesting to take two invented wheels and come up with the glue to get them work together nicely.

    For this conversation I have only one wheel and some parts of the shelf to use, such as Espruino Graphic Class and a few 74HC595. (I could also RYO the 595... but may be another time ;)...). The other wheel is only quasi of the shelf. It is of MY DIY Collector's shelf back from the outgoing 70'... see the picture... I built and explored it with an 8-bit Intel 8085 4Mhz German Engineered Dev Board with 256 (256!) bytes of SRAM for the program and 1KB (1024 Bytes) ROM Monitor. It had 9 inputs, 9 outputs, a hex keyboard, 6 7-segment displays to display code address and machine instruction code, and it did cost CHF/DM500 - about $280. Though very powerful and unseen, it was that cheap because the 'maker' - as we would say today - used a lot of brain - including time multiplexing for both the display AND the keyboard- in the monitor (operating system) to avoid having to buy and use expensive IO devices. Last but not least, the 6 7-segment display was completely multiplexed with just two 8-bit parallel gates at decoded IO addresses... but enough of nostalgia...

    I built this 3x5 LED matrix with 15 small red LEDs and could explore the multiplexing in the code.

    The plan is now to build first some logic and 'compose' some hardware to display on the LED matrix what is in a suitable display buffer.

    Next step will be to 'connect' the Espruino Graphics Library with this display buffer... E voila! We have what we wanted, and because we built it step by step by ourselves, we can adapt it to similar things.

    Adaption in reuse is mainly not in the big outline, but in the details and scaling (making it work for more than 8x8 matrix).

    For example, the display @user81779 wants to use, uses RGB LEDs, and what I can read from the available documentation, the data lines are not LED (Pixel) but Color bundled. Most displays store the information for one pixel (LED) in a piece of contiguous memory / bytes / bits... For example for a display with color depth of 3 (to support 16 colors), there is a 1 bit for each color Red, Green, and Blue - makes 3 bits - and they are stored next to each other. The display @user81779 mentions seems to do it differently: to lighten up a row or column of LEDs / Pixels, first all Red bits have to be sent, then the Green ones, and then the Blue ones... That's wha Im reading from the Raspberry PI code referenced in @user81779's post. Why I deduct so is because of line 24 which includes the complete heart shape... in 8x8 bits - 1 byte - and all others are constant (not even a buffer). You will understand when it comes to the wiring and the turning on and off of bits to lighten up pixels or to keep them dark.

    Even though this display goes that path, I want to start out with the usual approach of storing all information next to each other.

    To be continued...


    5 Attachments

    • M3x5FrontR.jpg
    • M3x5BackR.jpg
    • SN74HC595.png
    • M8x8BackR.jpg
    • M8x8FrontR.jpg
  • This post looks at the hard ware and wires it up.

    The antique, home made 'pricy' LED Matrix 3x5x1 w*h*c (c=color depth) has 3 Cols have common Anode, 5 rows have common Cathode. (Ignore The diodes DCR0...DCR4 in the row line.) [0..3,0..4] are the (red)
    LEDs w/ coordinate [0,0] in top left corner. The character graphic below shows the matrix:

    _________________________
    LED Matrix
    
    ColXBlueAnodes  C0BA         C1BA         C2BA
    Sr595nOutput    S2C          S2F          S3A
    PinNo           |2           |5           |15
                    |            |            |
       ColXGreenAnodes C0GA      |  C1GA      |  C2GA
       Sr595nOutput |  S2B       |  S2E       |  S2A
       PinNo        |  |1        |  |4        |  |7
                    |  |         |  |         |  |
          ColXRedAnodes|  C0RA   |  |  C1RA   |  |  C2RA
          Sr595nOutput |  S2A    |  |  S2D    |  |  S2B
          PinNo     |  |  |15    |  |  |3     |  |  |6
                    |  |  |      |  |  |      |  |  |
    *Blue & **Green *  ** |      *  ** |      *  ** |
    Anodes and Cathodes   |            |            |
    connected the same    |            |            |
    way as Red ones       |            |            |
                      .---+        .---+        .---+
                [0,0] V   |  [1,0] V   |  [2,0] V   |
                      -   |        -   |        -   |
    R0AC        DCR0  |   |        |   |        |   |
    S1A  ---------|<--+---(--------+---(--------+­---(--------
    15                    |            |            |
                      .---+        .---+        .---+
                [0,1] V   |  [1,1] V   |  [2,1] V   |
                      -   |        -   |        -   |
    R1AC        DCR1  |   |        |   |        |   |
    S1B  ---------|<--+---(--------+---(--------+­---(--------
    1                     |            |            |
                      .---+        .---+        .---+
                [0,2] V   |  [1,2] V   |  [2,2] V   |
                      -   |        -   |        -   |
    R1AC        DCR2  |   |        |   |        |   |
    S1C  ---------|<--+---(--------+---(--------+­---(--------
    3                     |            |            |
                      .---+        .---+        .---+
                [0,3] V   |  [1,3] V   |  [2,3] V   |
                      -   |        -   |        -   |
    R3AC        DCR3  |   |        |   |        |   |
    S1D  ---------|<--+---(--------+---(--------+­---(--------
    4                     |            |            |
                      .---+        .---+        .---+
                [0,4] V   |  [1,4] V   |  [2,4] V   |
                      -   |        -   |        -   |
    R4AC        DCR4  |   |        |   |        |   |
    S1E  ---------|<--+---(--------+---(--------+­---(--------
    5                     |            |            |
    

    --- EDIT --- NOTE that this 'passive' setup of the display matrix applies time multiplexing approach where a few lines only drive many LEDs, on (row or column of) LED(s) after the other in a fast, barely noticeable fashion (The Charliplexing needs even less lines because it takes diodes into account so same col/row driven with reverse voltage lights up (a) different LED(s)). Time multiplexing takes advantage of the fact that the human eye perceives a light as continuously on even though it is pulsed... the pulsing has just to be fast enough... the same concept that movies take advantage of: sufficient frames per second so the observer perceives transitions as smooth and flowing...

    The schema includes the option for having 3 LEDs - RGB - for each 'pixel'. Just check when you want to use RGB LEDs - 4 pins - that you observe the common cathode (or anode).

    Even though my matrix has only 1 color, the wiring-up with the 595 is for 3 colors in sequence BGR, R the LSB (only every 3rd output of cascaded 595 is connected to a R(ed) C(ol) of LEDs (when thinking of RGB LEDs w/ col lines LED bundled). The 595s are cascaded from left to right:

    • 1st 595 will drain (0V) the rows - one at the time
    • 2nd and 3rd will source (3.3V) the cols - all 3 cols at once

    For simplicity of the code, the cols and rows use their own 595(s). Horizontally, two more row RGB LEDs can be added (using the 15 lower outputs of the 16). Vertically, two more row RGB LEDs can be added (using all 8 ouputs.

    3 x M74HC595 8-bit shift register w/ latched three state output - pins and description below:

    _________________________
    74HC595 Shift-Register with Latching 3-state Output Registers
    
    - 14 SI    Serial In                        15 QA
    - 11 /SCK  Schift Clock                      1 QB 
    - 12 /RCK  Register Clock                    2 QC
    - 13 -G    Output Enable (PWM brightness)    3 QD
    -  9 QH'   H Stage for cascaded/next SI      4 QE
    - 10 -SCLR Shift Register Clear (CR Reset)   5 QF
    -  8 GND   Ground                            6 QG
    - 16 VCC   +2..+6V                           7 QH
    -    QA..H stringed/cascaded together to source/drain:
               - columns (16 bits w/ 16clocks) and then
               - rows (8 bits w/ 8clocks)
    

    Basic operation:

    • shift out columns of one row (padded/trailed to fill gap)
    • shift out rows with only on row low (padded/trailed)
    • /RCK latch (shows data)

    Attached picture shows the Espruino-Wifi wired up with 3 cascaded SN74HC595 and the LED matrix. Note that same pins of the 595 are all connected together - EXCEPT the SERial input (pin 14) of the first one which get's the SPI1 SDA line, and passes the bits on with it's output H' output (pin 9) to second 595's SERial input (pin 14)... and so does the second pass the bits on with it's H' output (pin 9) to third 595's SERial input (pin 14). In other words shift register serial data is daisy chained.

    Quite a fiddeling thing so many bread board wires... Luckily, I could keep the colors consistent, in other words, same color same 595 pin - except for the SERial data. All 595 outputs are brought into the front row creating the gap between the 595s for the outout A (pin 15), orange wire.
    blue is the daisy chaining SERial data, and white is the SERial clock. Yellow (Espruino-WiFi A0) is the NNS pin that is automatically pulled down by the SPIX.write() for writing and the pulling back up after the writing it serves the RCLK / raising flank to latch the data into the output registers. Green (A1) is holding the _OutputEnable down. It is able to PWN and can be used to PWM the output to protect LEDs from over-duty (when a LED driver stage is added to have more brighness / mA than the 595 can source and sink...

    To be continued...


    2 Attachments

    • wireingFrontal.jpg
    • wireingCloser.jpg
  • Now, let's hit some code that keeps the display showing what is in the display buffer. The initial approach is to have all pixel data in the buffer as well as the control data that does the multi-plex: picking the row. It leads to a bit less code, creates redundancy, uses memory... After all: it can be calculated while iterating through the data.

    The code contains documentation about its pieces.

    // ledMatrix9.js
    
    // code exploring led matrix display w/ Espruino board
    // Espruino-WiFi / Pico / Original / Puck
    
    /*
    Home made 'pricy' LED Matrix 3x5x1 w*h*c (c=color depth)
    Basic operation: 
    - shift out columns of one row (padded/trailed to fill gap)
    - shift out rows with only on row low (padded/trailed)
    - /RCK latch (shows data) - automatically w/ dnss
     */
    var dbuf = // display buffer, organized as rows.
    // Set LEDs @ [0,0], [1,1], [2,4] Red on (top left LED,
    // 2nd col of 2nd row and last/3rd col in last/5th row:
    //              col bits    row bit
    //   .......2   22111000   ...43210
    //   color  B   GRBGRBGR  drain
    [ [0b00000000,0b00000001,0b11111110] // - row 0
    , [0b00000000,0b00001000,0b11111101] // - row 1
    , [0b00000000,0b00000000,0b11111011] // - row 2
    , [0b00000000,0b00000000,0b11110111] // - row 3
    , [0b00000000,0b01000000,0b11101111] // - row 4
    ];
    
    
    var // connect definitions (Espruino -> 595 wiring): 
    //        SPI1 and other GPIOs          74HC595 (pin) 
      dspiDat=B5 // a) SDA ser data MOSI -> SER    (14)
    , dspiClk=B3 // b) SCK ser clock     -> SRCLK  (11)
    , dnss   =A0 // c) NSS to go w/ SPI  -> RCLK   (12)
    , dnoe   =A1 // d) 595 output enable -> _OE    (13)
    ;            // e) 595 _clear fix 1  -> _SRCLR (10)
    // A0 dnss is co-used for output register latch.
    //    Latch prevents blur while shifting.
    // A1 can do PWM when danger of over powering LEDs. 
    
    pinMode(dspiDat, "output"); dspiDat.set(); 
    pinMode(dspiClk, "output"); dspiClk.set();
    pinMode(dnss   , "output"); dnss.set();
    pinMode(dnoe   , "output"); dnoe.set();
    SPI1.setup({ mosi: dspiDat, sck: dspiClk });
    
    
    var rdx, iId // interval ID and interval time w/
      , iTm = 4; // row event every 4 millisec to yield
                 // 50 times/sec refresh, 20% duty cycle
    
    function r() { // run
      dnoe.reset();
      rdx = 4;
      iId = setInterval(function(){
          rdx = ++rdx % 5;
          SPI1.write(dbuf[rdx],dnss);      
      }, iTm);
    }
    
    function h() { // halt
      iId = clearInterval(iId);
      dnoe.set();
    }
    
    setTimeout(r,100);
    

    The function r() runs the example, function h() halts it.

    Attached is a picture of the running display showing what is in display buffer of example.

    Driving the 595 with higher voltage yields more brightness. To drive really large columns and rows, extra driver have to be used.

    What we have achieved so far, is driving a LED matrix through 74HC595 shift register with data from a display buffer. Next step will be connecting Espruino Graphic Class with this 'display driver'.

    To be continued...


    1 Attachment

    • runningR.jpg
  • Wow, nice! That's one hell of a write-up!

    You might find that the display is more averse to flicker if you do something like:

    function r() { // run
      dnoe.reset();
      setInterval(function() {
        var rdx = 5;
        while (rdx) SPI1.write(dbuf[--rdx],dnss);      
        // whatever SPI write needed to turn all columns off
      }, 10);
    }
    

    By scanning all in one go and then turning off, you don't get a slightly brighter row appearing if some other JS code takes a while to execute.

  • Thanks... the interesting part is still to come: hook it up with a display buffer drawn on and written to by the Graphics class.

    I see you point... but there is no pixel buffer... the interval around the SPI1 write - the write of just one row gets the brightness from the duration it is on... . it is really low level. may be you are talking about the fip... flip Graphics buffer into display buffer... yes there is no delay what so ever to plan. I'm interested too what this constant multiplexing 'costs'...will do sometime a bit later.

  • the write of just one row gets the brightness from the duration it is on

    Yes - in the devices I've used before like http://www.espruino.com/LPD6416 (or where I was driving an 8x8 display directly), they've been so bright that the time taken for the JS to execute has been long enough that it was about a perfect duration, even without setInterval. However I guess that could be different in your case.

  • @Gordon, the device you are talking about - http://www.espruino.com/LPD6416 - is equipped with a full set of shift registers each pixel has its own SR register, which means that refreshing is only needed when update is required. Updating then as quickly as possible and then idle it the way to go.

    In my case, I have only one SR register for each column - two 595 for max 5 columns of RGB LEDs. The rows - with one 595 for max 8 rows - is TIME MULTIPLEXED (not address multiplexed). Therefore, I have to constantly drive drive row after row and have to go through all rows about 50..60 times a second to make the eye to perceive that the LEDs are continuously on. With my particular setup of LEDs having Anode a the columns and Cathodes at the rows Therefore, the 2 595 for the columns source the LED with high (3.3 .. 5V), where as the single 595 for the rows sinks with low (0V) only the row that is currently loaded in the 2 595 for the columns (see 3rd byte in dbuf for each row... lines 19 thru 23 in code of post #3).

    Isn't that not called Charlieplexing? (but not using the fact that LEDs show only with current flowing in one direction - may be it is then just normal time multiplexing...).

    ...and Espruino IS the Multiplexer.

    You may have been misled by the fact that for the 3x5 matrix I'm using 3 8-bit shift registers... but even that should trigger, why 3 and not just 2 which are more than sufficient to non-multiplexed drive a 3x5 matrix. My 3 8-bit shift register can though drive a matrix of the size up to 5x8 of RGB LEDs (color depth 3). Even in my original use of the monochrome 3x8 matrix display I had to charlieplex: I used the single available 8-bit parallel port with 3 bits sourcing the columns and the remaining 5 bits draining the rows row by row over time, one row of LEDs at the time on.

  • LPD6416 - each pixel has its own SR register

    That's not the case at all - it's very similar to your display in that there's a shift register for rows. In its case there's some kind of counter for columns, but it's the same in that if you stop calling the update function, nothing is displayed.

    It's worth trying what I suggest though - it'll probably work quite well.

    Just FYI, there's a Charlieplex module for Espruino too: http://www.espruino.com/Charlieplex

  • Got it... used following code (r2() w/ 10ms vs. r() w/ iTm=4ms):

    function r2() {
      setInterval(function() {
        var rdx = 5;
        dnoe.reset(); // enable outpu
        while (rdx) SPI1.write(dbuf[--rdx],dnss);      
        dnoe.set();   // disable output
      }, 10);
    }
    
    function r() { // run
      dnoe.reset();
      rdx = 4;
      iId = setInterval(function(){
          rdx = ++rdx % 5;
          SPI1.write(dbuf[rdx],dnss);      
      }, iTm);
    }
    

    I

    It's a little bit dimmer, but it works... less duty cycle but the advantage of begin more consistent.

    Two shots attached show the different intensities and clip attached shows flipping between r2() and r().


    2 Attachments

  • Great! Yes, it's not perfect, but will be using a lot less CPU time. I guess you could try some other methods to brighten it up like scanning twice in the interval handler?

  • What - or 'Who' - do you call perfect? - I cannot expect more...

    Already w/ my version I ran the 595 w/ 5 Volts and it is quite noticeable. Doing so and scan twice makes for sure sense.

    @Gordon, what's your comment about the following piece of code? If the data buffer is built with just the column (sourcing) information per row and on scanning complemented with the 'calculated' row (draining) information, can then the Graphics class directly operate on the data buffer?

    // ledMatrix8.js - only col info in dbuf and gordon's scan
    
    // code exploring led matrix display w/ Espruino board
    // Espruino-WiFi / Pico / Original / Puck
    
    var dbuf = // display buffer, organized as rows.
    // Set LEDs @ [0,0], [1,1], [2,4] Red on (top left LED,
    // 2nd col of 2nd row and last/3rd col in last/5th row:
    //             col bits
    //  .......2   22111000
    //  color  B   GRBGRBGR
    [ 0b00000000,0b00000001 // - row 0
    , 0b00000000,0b00001000 // - row 1
    , 0b00000000,0b00000000 // - row 2
    , 0b00000000,0b00000000 // - row 3
    , 0b00000000,0b01000000 // - row 4
    ];
    
    var // connect definitions (Espruino -> 595 wiring): 
    //        SPI1 and other GPIOs          74HC595 (pin) 
      dspiDat=B5 // a) SDA ser data MOSI -> SER    (14)
    , dspiClk=B3 // b) SCK ser clock     -> SRCLK  (11)
    , dnss   =A0 // c) NSS to go w/ SPI  -> RCLK   (12)
    , dnoe   =A1 // d) 595 output enable -> _OE    (13)
    ;            // e) 595 _clear fix 1  -> _SRCLR (10)
    // A0 dnss is co-used for output register latch.
    //    Latch prevents blur while shifting.
    // A1 can do PWM when danger of over powering LEDs. 
    
    pinMode(dspiDat, "output"); dspiDat.set(); 
    pinMode(dspiClk, "output"); dspiClk.set();
    pinMode(dnss   , "output"); dnss.set();
    pinMode(dnoe   , "output"); dnoe.set();
    SPI1.setup({ mosi: dspiDat, sck: dspiClk });
    
    var iId       // interval ID and interval time w/
      , iTm = 10; // full scan event every 10 millisec
    
    function r() { // run / row drain bit (rBt) calculated
      iId = setInterval(function() {
          var rdx = 5, bdx = 0, rBt = 0xFE;
          dnoe.reset(); // enable output
          while (rdx--) {
            SPI1.write([dbuf[bdx++],dbuf[bdx++],rBt]­,dnss);
            rBt = (rBt<<1) | 0x01;
          }
          dnoe.set();   // disable output
        }, iTm);
    }
    
    function h() { // halt
      if (iId) iId = clearInterval(iId);
      dnoe.set();
    }
    
    setTimeout(r,100);
    

    I guess it depends on how the buffer is implemented for a 3x5x3 (w*h*colorDepth) Graphic:

    • a) with Bit, Nibble, Byte, Word, DoubleWord boundaries?
    • b) row bundled with above boundary?
    • c) pixel or color bundled?

    A pure bit stream is the most efficient in memory usage but not so efficient in manipulation (would require a lot of odd calculations and bits int nibble/byte/word/doubleWord shifting and and/or/xor-ing, nible/byte/word/doubleWord depending on color depth).

    Did just some 'explorative testing'... and valid bpp - bits per pixel - is a multiple of four... in other words, pixels are nibble bound... I have to define a 3x5x4 buffer... and I assume the number of bits per color (channel) are equal. Therefore, on streaming, the first bit - MSB - of every nibble is 0 and has to be dropped - either by recompose the output - or by skipping the shift register output pin...

    A 3x5x5 buffer takes up 8 bytes, and therefore, odd rows start with odd number of nibbles... oops... some nibble manipulation for rows 1 and 3..., and all rows need a nibble leading padding. --- And this in JavaScript? It gets a bit ugly: for every 3 nibbles consumed from dbuf, a nibble has to be inserted upfront to meet the 8-bit / byte boundary of the 8-bit shift register...

    I guess a .flip() has to kick in now before every(?) full scan, and the flip has to be compiled or in Thumb-assembler... Copying from one contiguous array buffer into another one should not be that difficult... never tried it, though.

  • ...DONE in JS: Buffer is controlled by Espruino built-in Graphics 'class'!!!

    The r() - in code looks weird... has lots of code! Looks almost as weird as the Graphics created ArrayBuffer (). For now r() is very 3x5x3 specific... generalization required...

    That's how the Graphics ArrayBuffer looks:

      This is how the Graphics created ArrayBuffer looks. 
      To support driving the LED matrix, two Uint16 views
      are created to do the shifting for odd rows and for
      prefixing the rows with a dummy nibble to meet the\
      SPI requirements of transmitting always 8 bits...
      upper-case BGR means bit is on (1), lower-case off (0).
        [-Byt0-][-Byt1-][-Byt2-][-Byt3-][-Byt4-]­[-Byt5-][-Byt6-][-Byt7-]
     R0 xbgrxbgR|   xbgr|      ||      ||      ||      ||      ||      |
        | 1   0||     2||      ||      ||      ||      ||      ||      |
        | 0   1|| 2    ||      ||      ||      ||      ||      ||      |
     R1 |      |xbgr   |xbgrxbgR|      ||      ||      ||      ||      |
        |      || 0    ||  2  1||      ||      ||      ||      ||      |
        |      ||     0||  1  2||      ||      ||      ||      ||      |
     R2 |      ||      ||      |xbgrxbgr|   xbgr|      ||      ||      |
        |      ||      ||      ||  1  0||     2||      ||      ||      |
        |      ||      ||      ||  0  1|| 2    ||      ||      ||      |
     R3 |      ||      ||      ||      |xbgr   |xbgrxbgr|      ||      |
        |      ||      ||      ||      || 0    || 2   1||      ||      |
        |      ||      ||      ||      ||     0|| 1   2||      ||      |
     R4 |      ||      ||      ||      ||      ||      |xbgrxbgr|   xbgR
        |      ||      ||      ||      ||      ||      ||  1  0||     2|
        |      ||      ||      ||      ||      ||      ||  0  1|| 2    |
        |  LSB || MSB  ||      ||      ||      ||      ||      |xxxx   |
        [=R0==even[0]==][--------------][=R3==ev­en[2]==][=R4==even[3]==]
                [-R1==odd[0]===][=R2===odd[1]==][-------­-------]
                  LSB     MSB 
      ('second number'lines - 10, 13, 16... are with option msb:true)
    

    Here the running code. It was cool to just talk to the cnvs (canvas) Graphics instance (directly in the console while display is running) with cnvs.setPixel(2,1,1); ....clear(...), ....drawLine(...), etc and see it reflected right away in the display...

    // ledMatrix6.js - adjusted to nibble bound pixel info
    //                 ...controlled with Graphics...!!!
    
    // code exploring led matrix display w/ Espruino board
    // Espruino-WiFi / Pico / Original / Puck
    
    // A canvas (display buffer) created by and understood
    // by Espruino Graphics 'class'. 3x5x(1R+1G_1B color)
    // consumes 8 bytes due to nibble bound pixels.
    var cnvs = Graphics.createArrayBuffer(3,5,4);
    // Set LEDs @ [0,0], [1,1], [2,4] Red on (top left LED,
    // 2nd col of 2nd row and last/3rd col in last/5th row:
    cnvs.setPixel(0,0,1);
    cnvs.setPixel(1,1,1);
    cnvs.setPixel(2,2,1);
    cnvs.setPixel(1,3,1);
    cnvs.setPixel(2,4,1);
    
    // Buffer Views on Graphics ArrayBuffer for scanning
    // (nibble manipulation):
    var dbfe = new Uint16Array(cnvs.buffer,0,4); // even bytes starting
    var dbfo = new Uint16Array(cnvs.buffer,1,3); // odd bytes starting
    
    var // connect definitions (Espruino -> 595 wiring): 
    //        SPI1 and other GPIOs          74HC595 (pin) 
      dspiDat=B5 // a) SDA ser data MOSI -> SER    (14)
    , dspiClk=B3 // b) SCK ser clock     -> SRCLK  (11)
    , dnss   =A0 // c) NSS to go w/ SPI  -> RCLK   (12)
    , dnoe   =A1 // d) 595 output enable -> _OE    (13)
    ;            // e) 595 _clear fix 1  -> _SRCLR (10)
    // A0 dnss is co-used for output register latch.
    //    Latch prevents blur while shifting.
    // A1 can do PWM when danger of over powering LEDs. 
    
    pinMode(dspiDat, "output"); dspiDat.set(); 
    pinMode(dspiClk, "output"); dspiClk.set();
    pinMode(dnss   , "output"); dnss.set();
    pinMode(dnoe   , "output"); dnoe.set();
    SPI1.setup({ mosi: dspiDat, sck: dspiClk });
    
    
    var iId       // interval ID and interval time w/
      , iTm = 10; // full scan event every 10 millisec
    
    function r() { // run / row drain bit (rBt) calculated
      iId = setInterval(function() {
          var rdxd=5, rdx=0, bdx=0, bex=0, box=0, rBt=0xFE, bs;
          dnoe.reset(); // enable output
          while (rdxd--) {
            bs = rdx % 4;
            if        (bs === 0) {
              v = dbfe[bex] & 0x0FFF;
              SPI1.write([v>>8, v & 0xFF, rBt],dnss);
              bex++;
            } else if (bs === 1) {
              v = dbfo[box] >> 4;
              SPI1.write([v>>8, v & 0xFF, rBt],dnss);
              box++;
            } else if (bs === 2) {
              v = dbfo[box] & 0x0FFF;
              SPI1.write([v>>8, v & 0xFF, rBt],dnss);
              box++;
              bex++;
            } else if (bs === 3) {
              v = dbfe[bex] >> 4;
              SPI1.write([v>>8, v & 0xFF, rBt],dnss);
              bex++;
            }
            rdx++;
            rBt = (rBt<<1) | 0x01;
          }
          dnoe.set();   // disable output
        }, iTm);
    }
    
    function h() { // halt
      if (iId) iId = clearInterval(iId);
      dnoe.set();
    }
    
    setTimeout(r,100);
    
  • Wow, nice! Would it simplify matters if you made the Graphics class 8 pixels wide/high even though you only used 3x5 of them?

  • Indeed - but it has not to be THAT inefficient: even number of columns makes life already easy, and with increased number of columns, memory efficiency for odd number of columns approaches 75% (starting out from 37.5%). The goal is to have a configurable piece of scan code...

    The greater issue is the targeted hardware, which - when you look at the pitched code example at their site - requires to send first all red bits of a row, then all greens, and then all blues... This *IS* 'The Blues' for programming. I do not know what their motivation was to wire up the hardware this way. Any color display controller I know of stores the information pixel-bundled and NOT color-bundled.

    To cater to that hardware wiring, I'm thinking of introducing compiled for the scan or add .flip() technique in a compiled function for fast bit fiddling (Pick all red bits from all row bits and put them together into a row write buffer, then all greens, and then all greens and finally all blues and then send the row write buffer... all this bit adding/shift/oring with reduced CPU cycles). First is better because transparent to the application, latter gives at least power to the application to run the bit fiddling only after multiple updates of the Graphics buffer that compose one or more logical (complete) updates (like a display transaction).

  • Wow, that's pretty frustrating. It might make life easier to use 4 bits per pixel, which then aligns a pixels nicely into 32 bits?

    var g = Graphics.createArrayBuffer(8,8,4,{msb:tr­ue}); // note 4 bits/pixel
    
    // extract just the red bits from 32 bits of 4bpp data = 8 pixels
    function convert_red(x) {
      //"compiled" - compiled code should work here
      return (x&1)|((x>>3)&2)|((x>>6)&4)|((x>>9)&8)|
             ((x>>12)&16)|((x>>15)&32)|((x>>18)&64)|(­(x>>21)&128);
    }
    var b = new Uint32Array(g.buffer);
    var eight_red_pixels = convert_red(b[row_number]);
    // etc
    

    ... and then maybe implement that in a flip function as you say so you're not having to do that ~800 times a second. For smaller displays like you're using I imagine non-compiled code would be absolutely fine.

    There's almost certainly a way to do the convert_red function more efficiently as well. If you haven't seen it already, http://graphics.stanford.edu/~seander/bi­thacks.html looks like it'd be right up your street :)

    I don't think that page contains the answer, but there must be a neat solution out there somewhere!

  • @Gordon, I assume it is shift right rather than shift left, is it?

    ...
    return (x&1)|((x>>3)&2)|((x>>6)&4)|((x>>9)&8)|
                  ((x>>12)&16)|((x>>15)&32)|((x>>18)&64)|(­(x>>21)&128);
    ...
    
  • Yes - sorry :) I've just changed it

  • ...this thread has a more recent, nice follow up that applies some of the 'implied' lessons learned:

    use a led matrix 5x7 without driver chip?

    Most prevalent is the use of Espruino Graphics' option to store bits vertically or cover (part of) columns in bytes vs part of rows (columns scattered over multiple buffer bytes)... It though all depends on how the LED matrix is built and has to be driven...

    Graphics.createArrayBuffer(<withInPixels­>,<heightIn8sOfPixels>,1,{vertical_byte:­true});

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

Driving LED matrix with shift registers such as 74HC595

Posted by Avatar for allObjects @allObjects

Actions