Getting an LED to blink 10 random numbers

Posted on
  • So I'm trying to design a circuit to blink out 10 randomly generated numbers in a row on the Pico onboard LED, with a break between each number. Being that I know very little about java, it's been a bit of a struggle. I've been using combinations of setTimeout and setInterval, but I can't seem to figure out how to blink the numbers one after the other.

    I've come to the point where I'm using an array to generate concurrent timers as per below. Both g and rando are Uint8ClampedArray(10). h is declared globally but doesn't work any better locally. I've started with 4 to keep it simple.

       for (h=0;h<4;h++) {
         console.log(h);
           g[h] = setTimeout('console.log(rando[h]);', rando[h]*400);
       }
    

    outputs the following:

    new Uint8ClampedArray([16, 17, 1, 9, 8, 7, 15, 18, 19, 1])
    0
    1
    2
    3 //output of console.log(h) during the loop
    8 //output of what should be the first 4 numbers of the rando array.
    8
    8
    8
    

    WTF am I doing wrong and/or what am I missing to get this thing to blink out 10 random numbers with a space in between?

    Any help would be appreciated

  • Ahh - I think you're hitting a few issues here...

    One is that things like setTimeout schedule some work to happen and then return immediately - so your for loop will be scheduling 4 timers at 16*400, 17*400, 1*400 and 9*400ms from the point of execution (rather than after each other) - so rando[2] will execute before all the others because it's 1*400ms (I'm not sure that's quite what you want?).

    The other one is that because all those Timeouts are executing after the loop has finished, h is set to the value it'll be at the end of the loop (which is 4) for all of the timeouts.

    For that issue, you can call setTimeout from a function:

    function doTimer(my_h) {
      return setTimeout(function() {
        console.log(rando[my_h]);
      }, rando[my_h]*400)
    }
    
       for (h=0;h<4;h++) {
         console.log(h);
           g[h] = doTimer(h);
       }
    

    Since there's a new function call for each timeout, h will be unique for each function to will dump the right value.

    You can also pass data in to setTimeout, so for instance you could do:

    for (h=0;h<4;h++) {
         console.log(h);
           g[h] = setTimeout(function (arg) {
             console.log(arg);
           }, rando[h]*400, rando[h]);
       }
    

    Both should work.

    For what you're want to do with the blinking, it might be worth trying something a bit different. You could have a single function that you call that actually alters the array it works from.

    So for instance the code below runs blinker - which looks at the first item in the array - if it's nonzero it blinks the led, decrements it, and calls itself again after a short delay. If it's zero it takes it off the array, waits for a longer period, then calls again.

    Finally if the array is empty it stops - there's no more work to do...

    var rando = [16, 17, 1, 9, 8, 7, 15, 18, 19, 1];
    
    function blinker() {
      if (!rando.length) return; // nothing to do
      if (rando[0]>0) {
        rando[0]--;
        digitalPulse(LED1,1,100);
        setTimeout(blinker, 200);
      } else { // first element is 0 - make a gap between sets of pulses    
        rando.shift(); // take the first item off
        setTimeout(blinker, 1000); // wait
      }
    }
    
    blinker();
    
  • Thanks so much for your help. No joke I have probably logged 10 hours into trying to solve this problem on my own, learning the hard way that setIntevals with the same name execute concurrently from a loop instead of sequentially and that one clearInterval blows the whole thing up. Definitely not an efficient use of time but I learned a lot.

    One thing with your last example though is that I want to retain the data in memory. If I do a for counter with that code, would I still accomplish the overall goal? (alternatively I could always transfer the data into a new array and then pull/destroy the data from new that array, but that seems inefficient?

  • I want to retain the data in memory. I could always transfer the data into a new array and then pull/destroy the data from new that array, but that seems inefficient?

    It depends how big your array is going to be. If it's only a few items long like your example, it's really not going to make any real difference. It'll only use the data for a short period of time, and then it's gone.

    If you do want to copy the array, all you need to do is newArray=rando.slice()

    That works out well for you as well, because the Uint8Array you were using can't have shift used on it (because it's a typed array). slice will conveniently convert it to a normal array for you.

    If I do a for counter with that code, would I still accomplish the overall goal?

    I'm not sure I know exactly what you mean, but a for loop will execute all at once like in your initial code, which isn't what you want - you want something that executes after each pulse, so it can decide what to do next at that point.

    I've just reworked the code above so it doesn't mess with the array:

    var rando = [16, 17, 1, 9, 8, 7, 15, 18, 19, 1];
    
    function doFlashes(rando) {
      var idx = 0; // index in rando
      var cnt = 0; // number of blinks
      
      function blinker() {
        if (idx>=rando.length) return; // nothing to do
        if (cnt < rando[idx]) {
          cnt++;
          digitalPulse(LED1,1,100);
          setTimeout(blinker, 200);
        } else {
          idx++;
          cnt = 0;
          setTimeout(blinker, 1000);
        }
      }
      
      blinker();
    }
    
    doFlashes(rando);
    

    It's basically the same, but instead of comparing everything against 0 and empty arrays it's using idx and cnt to look up the data in rando.

    Realistically, that's probably a lot more understandable anyway!

  • Right. So the problem with my execution was it was just smashing through it all at once and thus not giving it the time to properly execute. That's what I didn't fully grasp in your first explanation.

    If I understand correctly, by using if/else within a function using the timers, it will execute in a time appropriate fashion with proper controls, instead of firing everything at once and then having it wait until later to find all the variables have changed as part of the initial for execution..

    Ironically your last code is similar to something else I tried which used setInterval and a clearInterval inside of a setTimeout, but as the loop executed, the intervals conflicted with each other due to their method of execution.

    Again, thanks so much for your help!

  • Yes, you're totally right.

    I think it's the bit of JavaScript that takes the most effort getting your head around. While you can use JS like any other language, where delays are involved you're much better off using callback functions as above - which is pretty alien if you're used to just writing procedural code that executes linearly.

    The real bonus is that Espruino can keep doing other things inbetween running the code above. If you implemented it all as delays in something like C, the device wouldn't be able to handle much else while the LEDs were flashing.

  • @AdaMan82, with the code you just mastered, you may very much enjoy lines 235..257 of the code in this conversation. The last 3 paragraphs of the text include the description.

    If you want to run, study and observe yourself the section I'm talking about, upload the snippet below. After uploading, you enter in the console lg(b,n,"Tn") - with b = 0 or 1 and n = 0..9, for example: lg(0,5,"T5"), or lg(1,3,"T3").

    It does not matter how many time and in what frequency you call lg(), the (message) string is logged in the console right away, the red/error LED1 - or green/ok LED2 - blinks (the message code as) Tn in Morse code as words - correctly timed - until all done. Since all timings are implemented w/ setTimeout(...), the code is not (noticeably) blocking any other code, even though (JS) execution is single threaded... This ***event drivenness *** of code execution is the beauty of Espruino vs. the ugliness of forced sequencing with blocking delays (by killing CPU cycles w/ NOPs) in infinite main loop driven code execution, such as in, for example, Arduino coding model (Application events, such as timeouts, can be emulated with checking for pre-calculated time replacing the delays, but the main loop is always going on, hence infinite... and consuming power... see Arduino and RTC using the 1Hz 'beat' of the RTC as a time base for implementing a time and event management).

    Setting logging = false, you will see just the blinking.

    Instead of using a queue (append to string), sequencing could also be solved using Promises ('time recursion').

    var logging  = true;   // log activities to console (when an 'output' is connected)
    
    var cds=""; // c-oded (breaks, led colors G/R, Ts and) d-igit-s - 'queue'
    var cl=null; // ok/error green/red c-oded l-ed
    var bts = {"L":300,"S":80,"P":120,"B":450}; // long/short-on/off b-link t-ime-s in [ms]
    var bds = ["LLLLL", "SLLLL", "SSLLL", "SSSLL", "SSSSL", "SSSSS", "LSSSS", "LLSSS", "LLLSS", "LLLLS"]; // Long/Short coding of m-essage c-ode (mc), 0..9
    
    function lg(ok,mc,m) { // l-o-g ok/err m-essage / LED blink m-essage c-ode
      if (logging) console.log(m);
      var l = cds.length; // = 0 if all blinking incl. Pause was done (queue empty)
      cds += "BB"+((ok) ? "G" : "R")+"LB"+bds[mc]; // 'feeding' blinking queue
      if (l === 0) bl(); // start blink/work queue on 1st entries/was empty
    }
    
    function bl() { // 'bl-ink' Long/Short/Break+Pause 'recursively'
      var c = cds.charAt(0); // c-har defining next LED/time
      if (c=="G") { cl = LED2; cds = cds.substr(1); c = cds.charAt(0); }
      else if (c=="R") { cl = LED1; cds = cds.substr(1); c = cds.charAt(0); }
      if (c!="B") cl.set(); // turn LED on when not Break
      setTimeout(function(){ // for Long/Short/Break 
        cl.reset(); // turn  LED off
        setTimeout(function(){ // for Pause
          cds = cds.substr(1); // remove just completed L/S/B+P time
          if (cds.length > 0) setTimeout(bl,1); // blink until done
        },bts.P); // Pause between Long and Short (and Break)
      },bts[c]); // Long / Short (on), Break (stay off)
    }
    
  • Nice! Thanks again! Yeah, see all my life I've coded with linear execution in mind, so it took a while to wrap my head around it. The first thing I went for was delay when I couldnt make it work which is obviously wrong.

    Ultimately the big picture is to design something that lights up 10 random lights on a strip of WS2812B LEDs, of a pre-determined length, the length of which is determined by pressing a button to increment the length of the strip, a button to decrease the length of the strip, and a button to randomize 10 lights within the boundaries of the strip and illuminated them for 3 minutes.

    The above piece was me just trying to work with the built in LED for now, and then using the output on the LEDs once I get it all figured out.

  • Ic... almost...

    What I do not understand is the button to increment/decrement the length of the strip... because the strip has at runtime obviously at all times the same length, does it?

    Or do you you think with the button to move the then lit LEDs back and forth on the strip?

    Try to understand the big pic here (I also noticed the related conversation titled [v issues],(WS2812B issues), where you do your first steps w/ WS2812B.

    WS2812B strips have some timing sensitivity... therefore, you will always have to prep all values in an array and feed it to the write..., in other words - when I understand you correct - there will be, for example, 5 0-triplets which will go out first and keep in the end the last 5 LEDs dark, then 10 mixed triplets for the 10 lit LEDs in various colors, and then, finally another 3 0-triplets to keep the first 3 LEDs of a strip of total 18 LEDs in the dark. Does that sound about right?

    Can you make a char-graphic of various sets of states the strip would show in? ...for example

      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17 - 18 strip
                  0   1   2   3   4   5   6   7   8   9                     - 10 lit
    Data column wise filled into an array, totally 54 values from 0..255:
      0   0   0 023 204 024 102 004 093 000 167 053 069   0   0   0   0   0 - R
      0   0   0 124 004 147 000 012 255 132 015 142 148   0   0   0   0   0 - G
      0   0   0 227 137 065 102 000 204 000 164 034 209   0   0   0   0   0 - B
    

    Is that about what you have in mind?

  • No way more simple than that. See graphic below.
    Imagine the black bars being full slots in a holder, and the white bars being empty slots, that could be filled.

    [+] increases the length of the strip flashing the next light in the strip to show where the "max" is at for the random (green dot in this case).

    [-] decreases the length of the strip as per above.

    [rnd] lights up 10 random "full" slots (cyan dots as an example) for 3 minutes. If you press the button again, it lights up a new random 10 lights for 3 minutes.


    1 Attachment

    • project.png
  • I guess i get it.

    The three cols of dots are stringed together, one after the other?

  • That's right. It is one whole strip. zig zagging through the assembly.

    Ultimately 3 functions. One to increase the number (ideally illuminating the light its at), one to reduce the number (illuminating the spot its at) and one to generate a random 10 numbers up to and including where its at and leave them on for 3 mins.

    Its pretty straightforward (I think)

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

Getting an LED to blink 10 random numbers

Posted by Avatar for AdaMan82 @AdaMan82

Actions