Do something for random time using millis

Hi,

I’m building a sound installation. Hardware part works fine but now I need to figure out the code. I want to understand the logic and at the moment I’m prototyping different kinds of concepts with blinking led sketch before applying those ideas to solenoids, electromagnets etc. For some time ago I started using millis instead of delay and that was a big change for me and opened a lot of opportunities. But now I hit a wall and just can’t get my head to understand how to even start testing this. I read some similar threads, but could not figure out the logic how to use random with this

If I use the blinking led as an example. For example:

  • Wait before doing anything (random time between 1000-5000 millis
  • Start blinking (at 100 millis interval, blink duration 100 millis)
  • Do this for random time (between 1000 - 5000 millis)
  • Start over

Just the basic code:

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
const int ledInterval = 100;
const int ledDuration = 100;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  currentMillis = millis();
  updateOnBoardLedState();
}

void updateOnBoardLedState() {

  if (ledState == LOW) {
    if (currentMillis - previousMillis >= ledInterval) {
      ledState = HIGH;
      previousMillis += ledInterval;
    }
  }
  else {
    if (currentMillis - previousMillis >= ledDuration) {
      ledState = LOW;
      previousMillis += ledDuration;
    }
    digitalWrite(ledPin, ledState);
  }

  Serial.print (currentMillis);
  Serial.print (" / ");
  Serial.print (previousMillis);
  Serial.print (" / ");
  Serial.println (ledState);
}

Hello averagea
At least the sketch needs two event controlled millis() based timers. One for start/stop blinking and one for blinking itself.
Have a nice day and enjoy coding in C++.

This would probably rather be for a number of cycles, same difference.

This is one place to start

See if that catches any brain cells ready to absorb new knowledge.

You can also google

 arduino finite state machine

and

arduino traffic lights

to get links to very many places describing the use and programming of "finite state machines" or FSMs.

It's a bit of a different way of looking at things but also the key to getting things going. No one gets very far at all with microprocessors without at least knowing these techniques if not embracing them wholsale.

I see the code you posted comes out of the "blink without delay" area, this is in fact a corner of the general idea of FSMs.

BTW - very good idea to play with proxies for the real hardware! A full up system will be easier to get working if you are confidat about the software.

For the same reason, it is advised that when you do start using the real stuff you test it in small steps, small sketches that test and exercise each thing you are attaching. Keep those handy so you can run them when you are in the thick of things to be able to confidently eliminate sources of confusion and error.

a7

I agree with alto777 about using a random number of blinks instead of a random total time, it will be easier to program

Here is an example how I would do it t958745.ino - Wokwi Arduino and ESP32 Simulator

@guix nice! No doubt you had that ready to go. :expressionless:

And thanks for going to the extra trouble withbthe wokwi. No excuse anymore to present untested code.

If the OP cares, replacing the if/else chain with a switch/case woukd make the expression of the algorithm more idiomatic.

switch/case is a natural for state machines.

But the functionality is identical.

a7

Reusing sketch from a previous similar question :slight_smile:

I modified @guix's wokwi.

Mostly to show that the loop() only has to call the FSM frequently, no one call takes any significant processing time, leaving your loop() running free like it was meant to. And to illustrate the use of switch/case.

Imagine if you wanted to do something else at (what appears to be) the same time - it would just be a matter of using the same technique, another FSM that woukd cooperate by not blocking.

You can have as manyof those as you need, and only when the combined needs of all of them exceed the time you actually have to get around to them will you have to think of a faster processor or different algorithms or whatever are all the things competing for the scarce resource which is time.

As it is in life. Time the only truly precious resource.

I'm supposed to be working on my taxes, that's why. :expressionless:

a7

Thank you all! Really what I needed and took a deep dive into state machines. I already started playing with switch/case and found that a lot more logical for my thinking. Next I'll try to really understand the examples you showed and get those working.

Eventually I'll be controlling electromagnets resonating strings instead of blinking leds. I tested those by manually setting the on/off speed and the sound is just right for my needs. Now I just need to make a code to play them as I want. :slight_smile: for that elapsed time instead of number might be easier (lower and higher notes require different amount of "blinks" for same duration) but let's see where my testing goes.

Hello averagea
Find below an example sketch to be tailered to your project needs.

/* BLOCK COMMENT
  ATTENTION: This Sketch contains elements of C++.
  https://www.learncpp.com/cpp-tutorial/
  Many thanks to LarryD
  https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
  https://forum.arduino.cc/t/do-something-for-random-time-using-millis/958745

  If I use the blinking led as an example. For example:

    Wait before doing anything (random time between 1000-5000 millis
    Start blinking (at 100 millis interval, blink duration 100 millis)
    Do this for random time (between 1000 - 5000 millis)
    Start over
*/
#define ProjectName "Do something for random time using millis"
// HARDWARE AND TIMER SETTINGS
// YOU MAY NEED TO CHANGE THESE CONSTANTS TO YOUR HARDWARE AND NEEDS
constexpr byte LedPins[] {9};     // portPin o---|220|---|LED|---GND

// CONSTANT DEFINITION
enum {One};
// VARIABLE DECLARATION AND DEFINITION
unsigned long currentTime;
struct TIMER {              // has the following members
  unsigned long duration;   // memory for interval time
  unsigned long stamp;      // memory for actual time
  bool onOff;               // control for start/stop
};
enum {Random, Flash};
struct RANDOMFLASH {
  byte name_;
  TIMER wait;
} randomFlashs [] {
  {Random, 1000, 0, true},
  {Flash, 100, 0, true},
};
// -------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Serial.println(F("."));
  Serial.print(F("File   : ")), Serial.println(__FILE__);
  Serial.print(F("Date   : ")), Serial.println(__DATE__);
  Serial.print(F("Project: ")), Serial.println(ProjectName);
  pinMode (LED_BUILTIN, OUTPUT);  // used as heartbeat indicator
  pinMode(LedPins[One], OUTPUT);
}
void loop () {
  currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  for (auto &randomFlash : randomFlashs) {
    if (currentTime - randomFlash.wait.stamp >= randomFlash.wait.duration && randomFlash.wait.onOff) {
      randomFlash.wait.stamp = currentTime;
      switch (randomFlash.name_) {
        case Random:
          randomFlash.wait.duration = random(1000, 5000);
          randomFlashs[Flash].wait.onOff = ! randomFlashs[Flash].wait.onOff;
          digitalWrite (LedPins[One], randomFlashs[Flash].wait.onOff);
          break;
        case Flash:
          digitalWrite (LedPins[One], !digitalRead(LedPins[One]));
      }
    }
  }
}

Have a nice day and enjoy coding in C++.

Thank you once again everyone!

What a neat short code @paulpaulson definitely many new things for me to learn from.

I started playing around with your sketch @alto777 and @guix. Finally understood the concept behind. Really nice and simple use of switch/case and ++state

I managed to get this short code to do what I want (sort of..) with led and then I proceeded to try actually control electromagnets and play sound by resonating strings. It works. Basic idea:

  • Wait random time
  • Choose electromagnet oscillation speed randomly out of 3 options (these are just examples, need to be defined based on the string features eventually)
  • Oscillate electromagnet random time
  • Start over

For some reason if I use millis (for example "pulse length" 4ms) electromagnet goes on/off a lot slower than using micros (4000 μs). Thats the reason behind dividing and multiplying by 1000..

Any comments on how to make the code better/shorter? I'm using Uno with Adafruit Motorshield v2.

#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *myMotor = AFMS.getMotor(4);

byte motorState = LOW;

void setup()
{
  Serial.begin(9600);
  AFMS.begin();
  Serial.println( "Ready to play" );
  randomSeed(analogRead(A0));
}

void loop()
{
  string1();
  //more strings here
}

void string1()
{
  uint32_t now = micros();
  static uint32_t past = now;
  static uint32_t timer = 0;
  static uint32_t pulse = 0; 
  static uint32_t cycles = 0; //on-off-cycles for electromagnet
  static uint32_t pulseOption[] = {3000, 4000, 8000}; //pulse length options
  static uint8_t state = 0;

  switch (state) {
    case 0 :
      timer = (1000 * random(2000, 5001));
      pulse =  pulseOption[random(0, 3)];;
      ++state;
      Serial.print( "Waiting " );
      Serial.print( timer / 1000 );
      Serial.println( " ms..." );
      break;

    case 1 :
      if (now - past >= timer ) {
        timer = pulse;
        uint32_t rnd = (1000 * random(1000, 5001)); // Oscillation length
        cycles = rnd / ( pulse * 2 ); // Number of cycles = oscillation length  / (pulse length * 2)
        ++state;

        Serial.print( "Oscillating: " );
        Serial.print( rnd / 1000 );
        Serial.print( " ms / Pulse length: " );
        Serial.print( timer / 1000 );
        Serial.print( " ms / Cycles: " );
        Serial.println( cycles );
      }
      break;

    case 2 :
      if (now - past >= timer ) {
        past = now;
        myMotor->run(FORWARD);
        if (motorState == LOW) {
          motorState = HIGH;
          myMotor->setSpeed(255);
        } else {
          motorState = LOW;
          myMotor->setSpeed(0);
        }

        if (motorState == false && --cycles == 0 ) {
          state = 0;
          Serial.println( "Done, return to state 0" );
        }
      }
      break;

    default :
      Serial.println("Something wrong!");
      for (; ; );   // hang up this program, something is very wrong.
      break;
  }

Next step is to add more strings and electromagnets ( string2(); string3(); ) and get these to play independently. After that I'll start prototyping using potentiometers to affect the now randomised lengths. For more strings I'll start just by duplicating the code above but could this be eventually made using arrays?

Edit: Adding more string and magnets actually was just as easy as adding duplicates of string1() with appropriate names. I really like these millis (micros). But it feels a bit weird to just add a lot of the same code again and again. Maybe arrays could be a way to make this shorter?

Any time you find yourself saying "for some reason" it is advised to make a determination of the cause.

Using delay(4) should produce identical results to using delayMicroseconds(4000);

Using millis(4) should produce identical results to using micrso(4000).

Assuming you do all the 1000 factors. Here if you bungled that the difference would be huge and undeniably caused by the switch. By "a lot slower" pretty sure you didn't mean 1000 times longer.

Identical to within a small number of microseconds, which shouldn't make a diff in the observed behaviour in this case.

If you get curious, try to make a tiny program that might confirm that the difference is making the difference, cough!, that is to say that you are addressing a real problem not some spurious side effect of… dunno.

In any case, don't forget that this came up: it might be something we have all overlooked and come back at the very worst possible time.

a7