74hc595 with four 7 Segment LEDs HDSP-5723

Posted on
  • Hi,

    can someone review my code? i think that there must be a more efficient way to get this done.

    console.log("start");
    
    //595 Belegung:
    /*
     1 = 2
     2 = 3
     3 = 4
     4 = 5
     5 = 6
     6 = 7
     7 = 8 = Decimal Point, not used
     8 = GND
     9 = Serial Out, not used
    10 = CLR Rest, Pin B7
    11 = SCK, SPI1 SCK, Pin B3
    12 = SS/RCK,Pin B6, 10KOhm to GND
    13 = Output Enable, GND
    14 = Datum/SER/SIN, SPI1 MOSI, Pin B5
    15 = 1
    16 = VCC 3,3V + 330Ohm
    */
    
    //SS Pin = B6
    //reset=B7
    
    //B10 1000 - 9999
    //B13  100 - 999
    //B14   10 - 99
    //B15    0 - 9
    
    var one    = 48;   // "00110000";
    var two    = 91;   // "01011001";
    var three  = 121;  // "01111001";
    var four   = 116;  // "01110100";
    var five   = 109;  // "01101101";
    var six    = 111;  // "01101111";
    var seven  = 56;   // "00111000";
    var eight  = 127;  // "01111111";
    var nine   = 125;  // "01111101";
    var zero   = 63;   // "00111111";
    
    var z = [zero,one,two,three,four,five,six,seven,­eight,nine];
    
    var outpins = [B10,B13,B14,B15]; // the four 7 Segment LED Blocks
    
    digitalWrite(outpins,15);        // LEDs off 15=1111
    digitalWrite([B7,B6],0);         // SS Pin und Reset to 0
    digitalWrite([B7],1);            // Reset to 1
    
    
    pinMode(B10, output);
    pinMode(B13, output);
    pinMode(B14, output);
    pinMode(B15, output);
    
    
    SPI1.setup({ mosi:B5, sck:B3, baud:4000000 }); //SPI Setup
    
    var out = 0; // current Segment
    var i   = 0; // Counter
    var i1=0, i2=0, i3=0, i4=0;
    
    
    //increase counter and split
    setInterval(function(){
    
      i++;
    
      if (i>9999)i=0;
    
      if (i>999){
        i1 = Math.floor(i/1000);
      } else {
        i1 = 0;
      }
    
      if (i>99){
        i2 = Math.floor(i/100) % 10;
      } else {
        i2 = 0;
      }
    
      if (i>9){
        i3 = Math.floor(i/10) % 10;
      } else {
        i3 = 0;
      }
    
      i4 = i % 10;
    
    
    },10);
    
    
    
    function loop4()
    {
      SPI1.send(z[i1],B6);
      digitalPulse(outpins[0],0,4);
      setTimeout(loop3,4);
    }
    
    function loop3()
    {
      SPI1.send(z[i2],B6);
      digitalPulse(outpins[1],0,4);
      setTimeout(loop2,4);
    }
    
    function loop2()
    {
      SPI1.send(z[i3],B6);
      digitalPulse(outpins[2],0,4);
      setTimeout(loop1,4);
    }
    
    function loop1()
    {
    
      SPI1.send(z[i4],B6);
      digitalPulse(outpins[3],0,4);
      setTimeout(loop4,4);
    }
    
    loop4();
    
    
    

    2 Attachments

  • Hi! Looking good! :)

    The easiest way for you to optimize is to cut down on white space. I think Espruino minimizes stuff slightly but if you don't optimize run-time you will optimize mental time and maybe even a few calories from having to scroll less ;)

    Btw @Gordon, does Espruino support the ECMAScript 6 standard of the 0b1111 way of describing the value 15? Likewise for the octals with 0o17?

  • NP...

    But before going there, are you sure you want to burn cycles for the multiplexing? I'd reather spend another 3 x 39 cts and send the data all at once to the daisy chained 74HC595.

    With 4 x 73HC595, create a string (which handles all your divs and mods), take the ascii vals of the chars and point adjusted into a table with your bits for segments (as you already do with z[])... something like this (assuming positive integer):

    // ...mapping from digit(a char)>ascii>segIdx>segVal>display
    // ----------------------------------------­-----------------
    //                        ascii seg  segment    s v  display 
    // digit char ascii      & 0x0F idx  val bin    dec     char
    // ----- ---- ---------- ------ ---  ---------- ---  -------
    // 0 --- "0"  0b00110000 0b0000   0  0b00111111  63        0
    // 1 --- "1"  0b00110001 0b0001   1  0b00110000  91        1
    // 2 ...
    // ...
    // 9 --- "9"  0b00111001 0b1001   9  0b01111101 125        9
    //       ":"  0b00111010 0b1010  10  0b00000000   0    space
    //       ";"  0b00111011 0b1011  11  0b01001111  79        E 
    //       "<"  0b00111100 0b1100  12  0b01000010  66        r
    var segs = new Uint8Array([63, 91,..., 125,0,79,66]); // 13 elts -- 13 seg combins
    var displayInt = function(n)  {
      SPI1.send((((n > 9999) 
            ?  ";<<:" // map ;, < and : to segments for E, r and space (blank)
            : ("::::" + n).substr(-4) // map 0 to 9 to segments for 0..9 and : to space
          ).split("")).map(function(char){ return segs[char.charCodeAt(0) && 15]; })
        , B6);
    };
    

    You can go even further including:

    1. general conversion error: parseInt() NaN shows Err
    2. Overflow: shows ErrO
    3. Underrun: shows ErrU
    4. negative numbers (The negative sign "-" is a bit a trouble maker because it is not an adjacent ascii value and takes a digit away. Use ... : (((n<0) ? ":::=" : "::::") + Math.abs(n)).substr(-4) ... and map "=" (ascii 48 + 14) to 01000000 segments value.
    5. allow decimals to display... ;-) (do an or | 1 for the segments at the 1's).

    If you do not like array.map(), you can use array.reduce() or a simple loop over the 4 characters and stick the values into a predefined Uint8Array (with the loop you can even omit String.split("")).

    Using a predefined UInt8Array, you can even still keep the multiplexing and do some 'efficient' display. Your display may dim and with changing in brightness, show digits in different brightness, and - worst - get stuck on a digit when Espruino does some other non-interuptable thing and cannot handle the mux timer timely. Do not use the setInterval(), but more so the setTimeout(), to indicate that your muxing is not 'really' to a beat. For example something like this:

    var segs = new Uint8Array([63, 91,...,125,0,79,66]); // 13 elts -- 13 seg combins
    var digs = new Uint8Array([0,0,0,0]); // 4 elts -- 4 * 7-segment displays
    var ddx = -1; // -- will be 0..3 for digit index for all the 4 digits
    var den = 0b10000; // -- will be 0b1000, 0b0100, 0b0010, 0b0001 for 
        // digit enble for all the 4 7-seg displays w/ open collector / reverse
        // output with current limitting resistor... 
        // you may anyway need a driver the output pin and a current limiting
       // resistor on the the anodes give all the allowed brightness of 7 x 20 mA
      // per segment (57xx series) - see NOTE
    var displayInt = function(n){ // convers n into digs[]
      ("::::" + n).substr(-4).forEach(function(c,i){ // assuming n integer 0..9999
        digs[i] = c.charCodeAt(0) & 15;
    }); };
    var muxDsp = function(){
      digitalWrite([B10,B13,B14,B15],den = (den >>= 1) ? den : 8)
      SPI1.send(digs[++ddx % 4],B6);
      setTimeout(muxDsp,10); // frame rate of 25/s
    };
    

    You notice that the conversion form integer to the bit/segment patterns values in digs[] happens only once at every invocation of displayInt() and independent on the actual multiplexed displaying by muxDsp. Each digit is displayed one after the other in self-re-invocating muxDsp(). Something that Espruino is really cool in: ...being event driven.
    Note though, that a new value is displayed 'cleanly' only after 4 muxDSP() completed after completion of displayInt().

    NOTE: Driving for the common cathode can be done with simple NPN transistor or ULN2003. Instead of current limiting resistors on the anodes, you can use PWMs to drive the pull downs at the common cathode and choose a duty cycle that does not make the power consumption exceed the display segments limit - watch the inversion... :). Connecting the nss_pin (2nd argument of) SPI.send() - in your case B6 - inverted to _OE, you can save yourself pin(s) for other purposes. Any clearing/resetting etc is not needed since always 8 bits are sent per digit and after sending 4 * 8 bits, all 'funny' stuff if flushed - shifted - out anyway.

    My preferred HW would have 4 * 74HC595, a pwm pin connected to _OE, HDSP-5723 common cathodes grounded, and no SPI nss_pin used. A related SW for invocation with, for example,dsp(7,SPI1);, looked then like this:

    var dsp = (function() {
      var ss = new Uint8Array([63, 91,..., 125,0,79,66]), ds = new Uint8Array(4);
      return function(n,spi) { var s = (":::" + n).substr(-4);
        spi.send(ds.reduce(function(ds,d,i){ds[i­] = s.charCodeAt(i)  && 15; return ds; },ds)); }; 
    })();
    

    ...next thing is to make a module out of it using the typical connect()-pattern:

    Having a module, it makes sense to have brightness as a connect and runtime option. Useful additional connect options to consider are: leading 0, no leading 0, leading or trailing negative sign, etc. Extending the display for strings to map as many alphanumeric character as reasonably readable on 7-segment display will make it a versatile and useful - and may be a bit bloated - module).

    PS: code only partially tested... ;)

  • nss_pin to OE won't do what you want. OE is active low, and nss_pin goes low, so that would only enable the outputs while you were putting data into them, instead of the other way around.

  • noticed that and updated to ...nss_pin inverted on _OE.. But thanks anyway.

  • Looks good!

    does Espruino support the ECMAScript 6 standard of the 0b1111 way of describing the value 15?

    Yes - so that could be a bit easier to read.

    Also...

    • pinMode(B10, output); isn't needed I think, but if it was, you'd need to define output as a string like pinMode(B10, 'output');. Version 1v81 and later of Espruino will actually produce an error for your code when it comes out, because the output variable isn't actually defined.
    • As @allObjects did with his code, you'll probably find that working with a 4 digit string simplifies things, even if you don't want to use map - which takes a bit of getting used to.
    • While less efficient than @allObjects' Uint8Array example, you could store all your digits directly in an object, which is a bit more readable and also allows you to handle stuff like spaces and alphanumeric characters if you wanted to: {0:0b00111111, 1:0b00110000, ' ':0, '-':..., 'b':...}. It means you'd then be able to display strings like "A -2" if you wanted.
    • If I understand right, what you're doing with digitalPulse is actually really good - because it's running in an IRQ, regardless of what's going on in Espruino it'll always keep the LEDs lit for the same amount of time. Worst case is Espruino gets busy and the LEDs flicker off for a bit.
    • I'd just wrap your 4 loop functions into 1, and I think using setInterval will be a bit neater:

      var digit = 0;
      var z = {0:0b00111111, 1:0b00110000, ' ':0, '-':..., 'b':...};
      var text = "1234";
      setInterval(function() {
      digit = (digit+1)&3; // digits 0..3
      SPI1.send(z[text[digit]],B6);
      digitalPulse(outpins[digit],0,4);
      }, 5); // maybe make setInterval slightly bigger than digitalPulse, just in case
      

    Hope that helps!

  • Hello friends,

    thank you for you suggestions.

    console.log("start");
    //595 Pinput:
    /*
     1 = 2
     2 = 3
     3 = 4
     4 = 5
     5 = 6
     6 = 7
     7 = 8 = Decimal Point/ DP
     8 = GND
     9 = carryover, to Next 595's SER/Pin 14
    
    10 = SCL/CLR Rest, not used, flushing 32bit each time.
    11 = Clock/SRCLK/SRCK, SPI1 SCK, Pin B3
    12 = RCK/RCLK/LATCH/, SPI SS,Pin B6 via 10KOhm to Ground = Data Out
    13 = Output Enable, GND
    14 = Data/SER/SIN, SPI1 MOSI, Pin B5
    15 = 1
    16 = VCC 3,3V 330Ohm
    */
    
    //SS Pin = B6
    
    var sport  = 0b00000001;
    var one    = 48;   // "00110000";
    var two    = 91;   // "01011001";
    var three  = 121;  // "01111001";
    var four   = 116;  // "01110100";
    var five   = 109;  // "01101101";
    var six    = 111;  // "01101111";
    var seven  = 56;   // "00111000";
    var eight  = 127;  // "01111111";
    var nine   = 125;  // "01111101";
    var zero   = 63;   // "00111111";
    
    var z = [zero,one,two,three,four,five,six,seven,­eight,nine];
    
    digitalWrite([B6,B7],0);         // SS Pin to 0
    digitalWrite([B7],1);
    
    
    SPI1.setup({ mosi:B5, sck:B3, baud:100000, order:'msb' }); //SPI Setup
    
    var out = 0; // current Segment
    var i   = 0; // Value to Display
    
    var i1=0, i2=0, i3=0, i4=0;
    
    
    setInterval(function(){
      i++;
      if (i>999)i=0;
    
      if (i>999){
        i1 = Math.floor(i/1000);
      } else {
        i1 = 0;
      }
    
      if (i>99){
        i2 = Math.floor(i/100) % 10;
      } else {
        i2 = 0;
      }
    
      if (i>9){
        i3 = Math.floor(i/10) % 10;
      } else {
        i3 = 0;
      }
    
      i4 = i % 10;
    
      //SPI1.write(buf_off,B6); //flush only needed when not writing to all four segements
      SPI1.write([ z[i4], z[i3],z[i2],z[i1] ],B6);
      //SPI1.write([ sport, sport, sport ], B6); //to debug a segement 
    
    },100);
    

    I have chained three additional 74HC595 s. The problem with that solution is, that i have no clue how to bring all this wires to a PCB breadboard.

    I think i will have to do it the multiplexing way to avoid all this new cables.


    2 Attachments

  • Wow, that's great! A lot of wires there!!

  • The 'all-you-can-eat wire-buffet' hints the use of a dedicated peripheral display that includes the logic to receive by, for example I2c, SPI, etc. and do the multiplexing. ...if the display is not the only thing you want to show case (educationally).

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

74hc595 with four 7 Segment LEDs HDSP-5723

Posted by Avatar for werty1st @werty1st

Actions