Close

Does this project spark your interest?

Become a member to follow this project and don't miss any updates

0%
0%

DYPLED

a thin LED display in SIP module format, much better than TIL311s to visualise your computer buses

Similar projects worth following
The purpose of this SIP module is to display the value of a 16-bits parallel bus.
* SIP format for easy prototyping on a breadboard
* compatible with 3.3V and 5V systems
* selectable output formats: hexadecimal or decimal
* signed or unsigned numbers
* leading 0s can be hidden
* very high contrast display, easy dimming
* three push-buttons select the display modes (with colored feedback)
* user controls are also available on the SIP header: just tie the pin to GND or +3V
* low-cost parts, easy to repair, tweak, hack...
* low power (to be measured, less than 10mA)
* the whole design is documented here on hackaday.io ;-)

This is a module that #Hackaday TTLers and others CPU makers often need: they can spy on an address or a data bus without the hassles of a logic analyzer.

The PCB acts as a display panel with 5 7-segment digits, 3 push-buttons to select the modes, one adjustable resistor to set the brightness and a row of pins that connect to the circuit under examination. The interface is pretty simple:

Original sketch:Schematic with Eagle:

This is a sub-project of the #Discrete YASEP, which needs a dozen of displays that are

  • very thin
  • as cheap as possible
  • nice-looking
  • not power-hungry
  • easy to build/solder

I have looked at the different possibilities here, here, there, I've looked at the TIL311 integrated hexadecimal display and even started a project to create a modern, cheap clone (#PICTIL)

@al1 turned the #PICTIL into a very nice entry for the #The Square Inch Project but I need something different, not a faithful TIL311 drop-in replacement. Something that shows what we can actually do in 2015 ;-)

My requirements are fulfilled by the (niche) 4014 LED format. The comparison with the TIL311 proved promising so I made more experiments, resulting in a first prototype of 7 segments module.

I solved the decoding issue with a brute-force approach: a 1M×16 Flash chip. Some web-hunting found very cheap lots from brokers and it turned more flexible and cost-effective than other approaches (microcontrollers, CPLD...) thanks to the great pins-vs-price and combinatorial agility (and no need of VHDL or assembly language and proprietary probes to program it).

I found a little lot of very cheap AT49BV162A that have the very nice feature of being 5V compatible. It's an ideal match for this project: users don't need to worry about input voltages, it works with both 3.3V and 5V systems without adaptation. The other integrated circuits work with a wide voltage range. The module also operates with a power supply as low as 3V (it's actually limited by the LED's brightness). An integrated LDO limits the internal voltage to 3.2V.

Dimming is implemented with PWM with a semi-analog oscillator (using the cheap 74HC04). This also drives a divide-by-two counter (74HC74) for output multiplexing. This divider has complementary outputs that drive the appropriate MOSFET (which alternate in grounding their own phase rail).

The unused gates from the 74HC04 implement FlipFlops that select the display mode.

The sign bit is copied directly from the input's bit n°15, ANDed with the sign mode bit (trough a MUX2 in a 1G157 chip).

The last gotcha is the decimal mode which requires a fifth digit (ranging from 1 to 6). Compared to the previous hex-only design, the MOSFET are driven by the 74HC74 so this frees 2 data output lines, that turn into 4 lines when demuxed. A 74HC157 performs the additional decoding.


Logs:
1. Decoding the extra digit
2. Cost optimisation
3. Little enhancements
4. A dumb bistable
5. Adjustable PWM oscillator
6. Some updates
7. Preliminary layout
8. Stack-based programming
9. Tiny progress
10. Alternate uses
11. Small change
12. Other changes
13. New layout, new schematic
14. Dual-mode display
15. Autorouters are for the weak
16. Enhanced multiplexing
17. Another module
18. Generate the Flash's contents with a recursive algorithm
19. Bit shuffling: what goes where ?
20. Number conversion

shuffle.c

Generates DYPLED.bin

5.09 kB 08/20/2016 at 20:50

x-c

DYPLED.bin

Flash contents, 2M bytes, not tested

2.00 MB 08/20/2016 at 20:49

octet-stream

shuffle4.html

# run at your own risks # Proof-of-concept code for the recursive generation algorithm.

5.43 kB 08/20/2016 at 02:18

HyperText Markup Language (HTML)

atmel_AT49BV162,163A.pdf

1M×16 Flash, 3.3V with 5V-tolerant inputs

530.88 kB 01/12/2016 at 20:14

Preview Adobe Portable Document Format

View all 4 files

  • 1 × AT49BV162A Memory ICs / FLASH Memory
  • 1 × 74HC04 SOIC14 hex CMOS inverter
  • 1 × 74HC74 SOIC14 dual Flip-Flop with complementary outputs
  • 36 × white LED segments 4014 thin format, warm white
  • 4 × SMT LED, various colors 0603 format
  • 3 × push-button membranes ultra-thin, like the rest
  • 1 × trimmer / adjustable resistor 100K SMT, 100K Murata RVG4F03A-104WM-TG
  • 7 × obligatory 100nF ceramic capacitors 0603 format
  • 1 × Dual Schottky diode BAS70-04 SOT23
  • 1 × 24-pins headers, 2.54mm pitch optional pins to solder

View all 21 components

  • Not a failure but not a success either

    Yann Guidon / YGDES3 days ago 0 comments

    I received the latest PCB batch with the first DYPLED boards, and the issues are explained in this page.

    I don't think I can make the modules work completely as expected, mainly because of the unmasked vias that will create shorts with the thermal pads of the 4014 LEDS.

    At least I can validate other aspects of the module, such as the push-buttons, the PWM, solderability, etc.

  • File generation in C

    Yann Guidon / YGDES08/20/2016 at 20:59 0 comments

    It took me 3h only (and I was not rushing) to translate the JS code into C and get a decent binary file. I've just uploaded the source and the generated file in the main page.

    C has its own gotchas but the many debug features I used in JS have been very useful in C so the port was a breeze. Compilation is easy:

    gcc -Wall -o shuffle shuffle.c
    Execution is pretty fast too:
    $ /usr/bin/time ./shuffle > DYPLED.bin
    0.85user 0.01system 0:00.88elapsed 99%CPU
    (0avgtext+0avgdata 1392maxresident)k
    0inputs+4096outputs (0major+111minor)pagefaults 0swaps
    And to check the output, add any argument on the command line.


    The source code is very flexible, allowing me to adjust and adapt the data, for any change during the first tests and for the next generation of displays derived from it.

  • Number conversion

    Yann Guidon / YGDES08/19/2016 at 02:06 0 comments

    The last log (Bit shuffling: what goes where ?) explains how the address bits are shuffled. Once the logical value and all the modes/options are obtained, these informations are compiled to generate a 32-bits word that is output to the file, which will program the Flash.

    There are two conversion functions : hexadecimal and decimal. They are very similar and in fact can be merged into one, since the base parameter can work very well with this system (I just realise that in ASCII systems, it's better to have separate conversion routines, but here it's pointless). So let's just forget about function pointers...

    Given the base parameter, it's easy to decompose the number into digits. There is only one corner case to deal with : what to display when the input is zero. Of course it should display ___0 (and not a void screen, or else people wonder if the circuit works at all) but there are 2 ways to achieve this :

    • initialise the display to ___0 and use a while(>0){} loop (which is not entered in the only case where it is zeo)
    • or initialise to nothing (____) and use a do..while (repeat...until) so the remainder of zero is written at least once.

    The 2nd choice is better because the init value is all-cleared and the corner case happens only once, the do-while loop is lighter.

    So we have the following code:

    DigitLUT=[ "0", "1", "2", "3", "4", "5", "6",
     "7", "8", "9", "A", "B", "C", "D", "E", "F"];
    
    // The previously described recursive function
    function recurse( index, logic, sign, zero_ext, base) {
      msg+=" "+logic;
      if (index>0){  //0) {
        var bit=BinLUT[index--];
        recurse(index, logic, sign, zero_ext, base);
    
        if (bit < 0) {
          // special code for the modes
          switch(bit) {
            case -1: sign=1;      break;
            case -2: zero_ext=42; break;
            case -3: base=16;     break;
          }
        }
        else
          logic|= 1 << bit;
        recurse(index, logic, sign, zero_ext, base);
      }
      else {
        // This is a "leaf" call,
        // where conversion takes place:
        var i=0; // index of the digit;
        var d;   // the digit
        var m="\n";
    
        do {
          d=(logic % base)|0;
          logic= (logic/base)|0;
          m=DigitLUT[d]+m;
        } while (logic > 0);
        msg+=" - "+m;
      }
    }

    This code seems to work pretty well (once you solve rounding/FP issues with JavaScript by ORing 0)

     ------ -1
     0 0 0 - 0
     16384 - 16384
     32768 32768 - 32768
     49152 - 49152
     ------ -3
     0 0 0 0 0 - 0
     16384 - 4000
     32768 32768 - 8000
     49152 - C000

    This code is directly inspired by traditional conversion functions from binary to ASCII. However our case differs substantially from an ASCII terminal because each new iteration writes to a different digit, corresponding to different codes.

    In JavaScript or other similar languages, this is where multi-dimensional arrays become useful: there are 5 arrays (one for each digit) with 16 sub-arrays (for all the values). The counter i starts to be useful...

    Well, being a rebel, I prefer to use a single array with 5×16 entries and increment i by 16.

    var i=0; // index of the digit;
    var d;   // the digit
    var m="\n";
    
    do {
      d=(logic % base)|0;
      logic= (logic/base)|0;
      m=DigitLUT[d+i]+m;
      i+=16;
    } while (logic > 0);

    Then, it's "only a matter" of generating the rght bit pattern for each number.

    Yes, the time has come to work on this...

    The data pins are connected to the respective segments:

    D   1    2
     0    F2
     1    F1
     2  F3   A3
     3  G3   C3
     4  C1   G1
     5  B1   F0
     6  D1   E1
     7  C0   D0
     8  A2   B2
     9  A1   F1
    10  E3   D3
    11  F2   B3
    12  E2   G2
    13  G0   E0
    14  D2   C2
    15  B0   A0
    
    Conversely, and more interesting, the segments are connected to the following data pins (+16 means connected to phase F2)
    DigitPins=[
    // A      B       C     D      E       F     G
     [ 15+16, 15   ,  7   ,  7+16, 13+16,  5+16, 13    ],
     [  9   ,  5   ,  4   ,  6   ,  6+16,  9+16,  4+16 ],
     [  8   ,  8+16, 14+16, 14   , 12   , 11   , 12+16 ],
     [  2+16, 11+16,  3+16, 10+16, 10   ,  2   ,  3],
    ];
    

    Let's combine these pin numbers with the list...

    Read more »

  • Bit shuffling: what goes where ?

    Yann Guidon / YGDES08/18/2016 at 22:46 0 comments

    ln the last episode (Generate the Flash's contents with a recursive algorithm), I defined the general algorithm to generate the Flash's contents. It accepts arbitrary permutations. Now, the question is to generate the proper permutation vector.

    From what I have coded, it is obvious that the algorithm must be taken from the hardware point of view to give the logic address. In other words, we take the list of Flash pins (in order) then lookup what it corresponds to.

    At least, I did something right: bit 0 of the Flash address selects the high/low word so there is no need to recompute the logic value twice :-) The computation cost is halved again and each "leaf" of the recursive algorithm will output 4 bytes.

    Now on to the other address bits:

    Flash    Function
    addr
     bit
    
     0   High/low word
     1   D09       
     2   D08
     3   D07
     4   D06
     5   D05
     6   D04
     7   D03
     8   D12
     9   D01
    10   D13
    11   D00
    12   D14
    13   D15
    14   SIGNED/UNSIGNED
    15   0-EXT
    16   HEX/DEC
    17   D10
    18   D11
    19   D02
    

    Now if the mode bits (SIGN, 0ext and Hex/dec) were in the LSB, that would save even more computations (good to know for next time) but.... K.C. Lee will complain again that I over-optimise (not that he's wrong buuuut...). In the end, routing has had the last word.

    Anyway the code becomes a bit more interesting because there are those 3 modes to manage, in the middle of the permutation vector. This gives me the idea to select the conversion function not with a "if" at the leaf level, but by passing a function pointer, just like the "logic" address parameter. Some may cringe but this saves some coding efforts, it is more elegant to me :-)

    Once again, this is not about writing the fastest algorithm but to make the leanest conversion functions. Reducing the number of (iteratively redundant) IF statements is a high priority because it makes the code more modular. Propagating attributes in the recursive calling stack keeps the system conceptually simple and safe (no kludge), despite the increased stack occupation (which takes a toll on CPU+memory).

    So where are we now ?

    • Hex/Dec is managed by propagating a function pointer to the conversion function. Start recursing with the HEX code, then when index 16 is found, replace the function pointer with DEC code.
    • Signed/Unsigned... let's keep it simple, one IF won't kill me because it will just select a little line. Another propagated parameter is added to the growing list of arguments...
    • 0-extension : that's another matter which affects both the HEX and DEC code, which creates 2 IF in two different functions (potential bug alert). The idea is simple too : recursively propagate a "default" value of the display, which can be null or 0000. When the leaf functions will add digits, they (unconditionally) will clear the default value and overwrite the digit.

    The last part tells us that writing a digit will be done by a single function, called by both HEX and DEC conversion functions. Modularity for the win :-)

    Now, on to the next puzzle !

    I want to reduce the number of IFs in the leaf functions and the recursive code. But the modes will require more code inside the recursive functions. Each call would require to go through a switch-case... How can this be kept to the bare minimum ?

    I have come up to this system:

       // permutation of the input bits
       var BinLUT=[
         0,   // High/low word, unused
         9, 
         8,
         7,
         6,
         5,
         4,
         3,
        12,
         1,
        13,
         0,
        14,
        15,
        -1, // SIGNED/UNSIGNED
        -2, // 0-EXT
        -3, // HEX/DEC
        10,
        11,
         2 ];
    The modes are coded by negative numbers so if the number is not negative, the normal (fast) code is executed, otherwise the switch-case is examined. The code becomes "table-driven", the behaviour gets defined by an array of numbers, less by the code itself. This also means that there are less places to check if the pins change.

    The new recursive function becomes:

       function recurse( index, logic) {
         msg+=" "+logic;
         if (index>0) {
           var bit=BinLUT[index--];
           var mask=1 << bit;
    
    ...
    Read more »

  • Generate the Flash's contents with a recursive algorithm

    Yann Guidon / YGDES08/18/2016 at 02:32 4 comments

    As I just sent the PCB to fab, I have a couple of week to wait...

    But the design is not over ! The Flash chip must be programmed with a lookup table contained in a file, which must be generated from the pin assignations, and this is not as straight-forward as it seems !

    Now let's consider this : the Flash is pretty large, a few megabytes. We can work "in memory" and scan an array in RAM then dump it to a file. But this will take a long time, and cause a lot of cache misses. Yes, I'm an over-optimiser but if you consider that I might implement the algorithm with bash, for extra geek points, a better and leaner approach seems desirable.

    I have chosen a "streaming" algorithm that doesn't hold the whole contents in RAM, but writes it to a file as soon as each byte or word is computed.

    It's not that complicated, the algorithm might work like this:

    • Loop 2Mi times (as many as words)
    • For each word:
      • compute the logical index from the physical index (the counter)
      • compute the value corresponding to the logical index
      • translate the value into an output code
      • write the code to the output

    That's very nice but in practice, it's obviously inefficient because a lot of values will be computed over and over but they never change...

    Quite a few things can be precomputed, such as the digit->output code conversion. However, because we are totally reshuffling the address bits, addresses/indices must be recomputed at every cycle.

    This computation is actually a remapping of the bits so it's not so arbitrary. If I flip the bit #n, then the bit #m will be flipped in the logical index. This opens an opportunity to save a lot of lookups...

    A traditional bit-shuffling routine would loop over all the input bits (let's say 21 because that's how many address lines we have), then for each bit, lookup what is the position of the corresponding logical bit. Since there are 21×2²¹ lookups, that's a long computation overall.

    I have found how to cut this cost in half with a little, neat recursive trick. It does not use a loop counter, but a bit index counter. Starting at index 21, for example, the procedure function calls itself twice with the decremented index. So the procedure is called with index 21, but each time with a different parameter. As long as the index is not 0, the procedure calls itself twice, leading to 2²¹ calls, as expected.

    Here is a first JavaScript example of a recursive counter:

    <html>
     <head>
      <script>
       function start() {
        var pre=document.getElementById("out");
        var msg="Starting:\n"
    
        // permutation of the input bits
        var BinLUT= [ 4, 2, 5, 3, 6, 0, 1 ];
    
        function recurse( index) {
          msg+=" "+index;
          bit=BinLUT[index];
          if (index>0) {
            index--;
            recurse(index);
            recurse(index);
          }
          else
            msg+=" *\n";
        }
        recurse(6);
    
        pre.innerHTML=msg+"end!"
       }
      </script>
     </head>
    <body onload="start()">
     <pre id="out">
      empty
     </pre>
    </body>
    </html>
    
    The output shows that one half of the upper bits are not evaluated, in average:
    Starting:
     6 5 4 3 2 1 0 *
     0 *
     1 0 *
     0 *
     2 1 0 *
     0 *
     1 0 *
     0 *
     3 2 1 0 *
     0 *
     1 0 *
     0 *
     2 1 0 *
     0 *
     1 0 *
     0 *
     4 3 2 1 0 *
     0 *
     1 0 *
     0 *
     2 1 0 *
     0 *
     1 0 *
     0 *
     3 2 1 0 *
    .....

    Now the magic is that the first call forwards its initial parameter (the logical index) but, before the second call, the index is updated with the right bit set to 1. This leads to only 2²⁰ lookups.In the following example, the code performs a bit reversal:
    <html>
     <head>
      <script>
       var msg="Starting:\n"
    
       // permutation of the input bits
       var BinLUT=[ 3, 2, 1, 0 ];
    
       function recurse( index, logic) {
         msg+=" "+logic;
         if (index>0) {
           var bit=BinLUT[index--];
           recurse(index, logic);
           recurse(index, logic|(1<<bit) );
         }
         else
           msg+="\n";
       }
    
       function start() {
        var...
    Read more »

  • Another module

    Yann Guidon / YGDES08/16/2016 at 01:20 0 comments

    I still haven't sent the PCB to manufacture, I'm waiting for another project to share the cost. Meanwhile I know I have to develop the software that generates the Flash's contents. I'm also considering another multiplexing method and I'll have to prototype it for the #Discrete YASEP displays. So I made this quick simple PCB:

    The size is exactly 8×0.1" and the digits can be assembled side by side to create large numbers.

    Since it's a more generic circuit than #DYPLED and it can be used in more projects, I consider offering it as well :-)

    It's pretty easy to use and can be driven by a microcontroller, for example, if the Flash/ROM method does not interest you.


    Update 20160817:

    This minimodule has been forked to #4014 LED minimodule !

  • Enhanced multiplexing

    Yann Guidon / YGDES08/08/2016 at 21:06 0 comments

    It's too late but the following idea struck me at the last moment of routing.

    There is actually no need of two common rails for the display. One rail that is switched from 0 to +Vcc is enough. This would simplify the routing, though in this case, the difference is not important enough to justify rerouting anything. I'll leave it like this but I write this as a "reminder for later".

    For now, the system works like this : each Flash pin goes to a resistor then two LEDs, each tied to one of the common rails. The LEDs are wired in the same direction so one of the rails must be set to 0V to turn the corresponding LED on.

    This is actually overkill if we realise that the LEDs could be connected in reverse and in parallel, with the single common rail swinging for 0V (to turn one LED on) to +VCC (this turns the other LED on). The only change is to reverse the value of the FLASH memory for one half of the output values.

    This becomes more complex since the 5th digit's decoder would require a total redesign as well...


    20160822:

    This did not work as expected. But all is well that ends well !

    Damned protection diodes...

  • Autorouters are for the weak

    Yann Guidon / YGDES08/06/2016 at 22:43 12 comments

    I finally completed the place/route work for the module! It took a long time (it started many months ago...) and even though all the parts fit, I wasn't sure the tracks would. Yet there is still a little room under the contrast adjustable resistor. If you're impressed, well, I'm even more impressed that I completed it and respected all the constraints!

    Is there an autorouter out there that could do such a crazy job ?

  • Dual-mode display

    Yann Guidon / YGDES07/20/2016 at 01:07 0 comments

    The native working mode of the module is "Hexadecimal / Decimal" depending on one bistable button.

    For an application such as a simple DDS (like my very own #Quick & Dirty Frequency Generator ) the input value is provided in binary by hex/binary selectors. In this case the mode button can select beween raw divisor value and expected frequency, just by programming the Flash EPROM correctly :-)

  • New layout, new schematic

    Yann Guidon / YGDES04/21/2016 at 09:39 0 comments

    I can't believe I did it... It's probably the most tortuous routing I've ever done. You can see by the number of vias, how much efforts I put into avoiding parts on the visible side.

    The right side is still not routed and I must triple-check that the logic on the left is correct. I'm so relieved I removed the SC70 MUX and a couple of other parts...

    The new schematic includes all the previously mentioned changes.

View all 22 project logs

Enjoy this project?

Share      

Discussions

Yann Guidon / YGDES wrote 08/20/2016 at 02:52 point

I just realised that "negative hexadecimal" is awkward...

Without the limitation of the 5th digit, I would implement an octal mode :-D

  Are you sure? yes | no

Yann Guidon / YGDES wrote 08/18/2016 at 02:26 point

I sent a first batch to fab ! 0.6mm thick, white mask, might be delivered in 2 weeks...

  Are you sure? yes | no

esot.eric wrote 08/17/2016 at 02:38 point

Hey, props on the blog-writeup! And looks like this is coming along nicely.

  Are you sure? yes | no

Yann Guidon / YGDES wrote 08/17/2016 at 02:43 point

You're welcome !

A surprise happened and... #4014 LED minimodule ! a sub-fork appeared :-D

  Are you sure? yes | no

Yann Guidon / YGDES wrote 07/04/2016 at 00:56 point

July 2016: I still haven't received the pull-down resistor networks. They are not essential but damnit... Why is it so hard to find 4×1M resistors in 1206 package ?
Ironicly, I have found a cut tape of 4×1M resistors but in a smaller, even-harder-to-solder package. Am I cursed ?

  Are you sure? yes | no

Yann Guidon / YGDES wrote 07/20/2016 at 00:52 point

I finally received a reel of 1206 resistor networks ! At last !

And now, what about completing the PCB layout ?...

  Are you sure? yes | no