Methods for adjusting a LED strobe frequency and duty cycle

Hi, I'm working on a LED strobe meant to make water droplets appear to freeze in mid-air. It needs to run at about a 10% duty cycle with a frequency somewhere between 40 and 70Hz. (Getting the latter right is going to involve some trial and error, so I'm planning to include a pot for interactive adjustment.)

What I need help with is choosing the best coding method to use. I think the simplest is probably the best, but I would appreciate anyone's insight if I should pursue one of the other methods I've turned up. Right now, my goal is to get to a proof of concept with a single bright white LED for the strobe and a single pot for adjusting frequency. Future iterations may involve RGB LEDS cycling through colors and additional pots or sensors.

Here are the options I've dug up.

  • The simple approach: Bit-banging the LED on/off using delayMicroseconds(). But are there any issues with also having to read the pot and calculate the delays as part of the main loop?
  • An Instructable that I've been looking at for guidance uses a now ancient library that abstracts PWM register manipulation. It'a appealing because I can pretty easily modify the sketch used for that project. However, the PWM library was last updated in 2012, so I'm kinda wary of it.
  • I've read through a guide on PWM register manipulation. I think I could do this myself, though this would push the limit of my skills as I'm only an occasional Arduino user. On the other hand, I haven't seen many examples, so it may be harder than I think it would be.
  • I've seen a few references to using interrupts, but haven't yet delved into this option.

Hardware: Seeeduino 4.2 (ATmega 328P-MU) + random white LED from my components box.

Thanks!

Well, the minute you start talking about duty cycle it seems you already made your mind on using PWM. I would use a different approach (although someone will come and say I'm using PWM anyways).


In your loop() check if an LED event is due (turn LED ON or OFF). If it is, do that and compute the next event time. Just mark the LED as ON or OFF so you can tell what its state is (yes, this is a Finite State Machine)

After this, check your pot (I would use an encoder), and adjust the interval accordingly for the next loop cycle.

At 70Hz, there will be plenty of time left on each iteration for a lot more to do.

And make a note to yourself: avoid using delay() inside loop()

2 Likes

Thanks @mancera1979! Is the method you describe what others have meant when they talk about interrupts, or is that something different?

You'll probably want to figure-out the minimum on-time (for effective perceived brightness) and keep that fixed, and then just vary the off-time.

1 Like

Here's a simple duty cycle test pgm for Nano that times in microseconds and works from keyboard. Set serial monitor line ending to Newline.

/*
Duty cycle test pgm for Nano, in serial monitor:
enter p then pulse time in microseconds or 
      c then cycle time in microseconds or both like,
      p10000,c100000
*/
uint32_t timer,
         cycleTime = 1000000, // default 1 second
         pulseLen = 10000;   // default 10 ms
const byte pulseOut = LED_BUILTIN;

void setup()
{
  Serial.begin(115200); // set line ending to Newline
  pinMode(pulseOut, OUTPUT);
  timer = micros();
}

void loop()
{
  byte inChar;
    
  if (Serial.available() > 0)
  {
    inChar = Serial.read();
    if (inChar == 'p')
    {
      pulseLen = Serial.parseInt();
      Serial.print("Pulse length = ");
      Serial.println(pulseLen);
    }
    else if (inChar == 'c') {
      cycleTime = Serial.parseInt();
      Serial.print("Cycle time = ");
      Serial.println(cycleTime);
    }
    else if(inChar == '\n'){} // skips 1 second delay
  }

  if (micros() - timer > cycleTime)
     timer += cycleTime;

  digitalWrite(pulseOut,micros() - timer < pulseLen);   
}
1 Like

Nope. It is closer to what @JCA34F proposed.

There are basically 3 elements to take care inside your loop:

-whether it is time to turn on the LED (preset time interval has gone by since the last time it was turned on), do it and record the current time

-else, whether it is time to turn off the LED (preset ON time has gone by since it was turned on )

-adjust the interval/frequency. Using @JCA34F example you would tether the fixture to a computer. Or, you can work with your pot, or a couple of buttons …and a display

1 Like

If you want to see the effect then you need tio take human eyes into consideration. We have persistence of vision for example where 24 frames per second or faster blend together, frames over 40 millis apart won't seem frozen and PWM is frames 1 or 2 millis apart.

8 or fewer flashes per second can work but 4 or less will be better for a freeze in motion.

Cameras have faster eyes, there are solved catch a balloon popping old projects for Arduino out there and the drop of water on water effects.

See about using a led car headlight "bulb" to get it real bright!

1 Like

And if the LED draws more than about 20mA, you will need a transistor between the Seeduino and LED.

1 Like

Hello chassy

Welcome to the worldbest Arduino forum ever.

Consider this example using two timers.
You might to do fine tune the timing needs to your project requirements.

//https://forum.arduino.cc/t/methods-for-adjusting-a-led-strobe-frequency-and-duty-cycle/1178842
//https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
#define ProjectName "Methods for adjusting a LED strobe frequency and duty cycle"
#define NotesOnRelease "1"
// -- some useful text replacements used
#define equ ==
#define analog(x,y) analogWrite(x,y)
// make names
enum TimerEvents {Idle, Expired};
enum MinMax {Min, Max};
// make variables
constexpr uint8_t PotPin {A8};
constexpr uint8_t FlashLed {9};
constexpr uint32_t FlashTime[] {1000, 2000};
// make structures
struct TIMER
{
  uint32_t now;
  uint32_t trigger;
  uint8_t control;
  uint8_t expired ( uint32_t currentMillis)
  {
    uint8_t timerState = currentMillis - now >= trigger and control;
    if (timerState == Expired ) now = currentMillis;
    return (timerState);
  }
};
TIMER blinkMe {0, 1000, HIGH};
TIMER flashLed {0, 10, HIGH};
// make application
void setup()
{
  Serial.begin(115200);
  Serial.print("Source: "), Serial.println(__FILE__);
  Serial.print(ProjectName), Serial.print(" - "), Serial.println(NotesOnRelease);
  pinMode (FlashLed, OUTPUT);
  Serial.println(" =-> and off we go\n");
}
void loop()
{
  uint32_t currentMillis = millis();
  if (blinkMe.expired(currentMillis) equ Expired)
  {
    blinkMe.trigger = ((uint32_t) map(analogRead(PotPin), 0, 1023, FlashTime[Min], FlashTime[Max]));
    analog(FlashLed,255);
    flashLed.now = currentMillis;
  }
  if (flashLed.expired(currentMillis) equ Expired)
  {
     analog(FlashLed,0);
  }
}

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

1 Like

I'm a great believer in the KISS approach.
Your pot is unlikely to change rapidly. If it does basically its faulty.
bit banging at less than 100Hz - even with a very short duty cycle - isnt going to challenge the arduino.
Nor is reading the ADC to read the pot value.

However you could alternatively just use a 555 timer IC and a few components. (I've done this, it works and runs from a PP3 battery)

To get bright short pulses from an LED you can use a high intensity LED and discharge a capacitor through it.

3 Likes

Yup, the flicker fusion threshold starts around 60Hz, and I'm deliberately not going under 40Hz because the strobing that can affect epileptics runs from about 6-30Hz.

You have 3 variables:

  1. Pulse frequency. Should be equal to the water drop frequency. Otherwise you can have aliasing. Syncing the events may prove tricky. It's part of the fun. In your case, my guess is that the frequency will be lower than 100Hz, which is well within the Arduino capability. I go as high as 240Hz to freeze fan blades @ 13440RPM. Still have to try with a mototool. The vision persistence has nothing to do here.

  2. Pulse duration. This depends on the water drop speed. I don't expect it to be high, so you can get away with a long pulse, such as 5-10ms. (Always lower than the interval between pulses, of course). My pulse generator (I use to measure fan speed) uses 100 microseconds, but I have to darken the room. If I go higher than that the fan blades appear blurry because they rotate very fast.

  3. Pulse intensity. The light intensity you need is contingent on the surrounding light. This is a trial-and-error process. And remember, if you use a high-current LED your Arduino output pin should control a transistor, not a LED directly. Although the voltage regulator in a Seeeduino can provide up to 500mA it is recommended to use a separate power supply. I use a 12V LED powered off a lab power supply.

My recommendation is to use the lowest possible combination of pulse duration and pulse intensity to prevent dazzling. You can house the LED in a short opaque tube (such as a straw wrapped with masking tape) so you are not looking at it continuously.

After having fun with the water drops you will move on to fans and motors. Have fun!

1 Like

24 FPS is 41 2/3 ms per frame, the flashes combine into motion.

You want drops to appear still, that means short, bright flashes maybe 500 ms or longer (seconds) apart, too slow to trigger seizures.

No, that’s not how stroboscopes work (or fail.)
Consider ye olde neon bulb at 60Hz that was used to check lap turntable speeds.

That's how eyes work. Dim a led at 60 Hz with short ON times. Does it appear to blink? Say 1666 us ON and 15000 us OFF?

The appearance of the turntable strobe depends on synchronization with shiny flat spots along one of a few bands of flat spots... should work the same leds, no?

compiled and tested, my eyes see a dimmed led.

const unsigned long wait[ 2 ] = { 15000, 1666 };
unsigned long startUs;
byte index = 1;

void setup() {
  pinMode( 13, OUTPUT );
  digitalWrite( 13, index );
  startUs = micros();
}

void loop() {
  if ( micros() - startUs >= wait[ index ] )
  {
    startUs += wait[ index ];
    index = !index;
    digitalWrite( 13, index );
  }
}

More properly wait[] should be waitUs[] to match startUs.

@westfw and @GoForSmoke - Deeper reading if you're interested:

No. but it will still have a proper stroboscopic effect on moving objects (like water drops.)

That's also why video (30fps or faster) of spinning things can make them appear to be turning backward.

2 Likes

And if I blink led13 with PWM and wave it fast in low light, I get a trail of dots. If i want solid trails, maybe a digital pot would be a better choice than pulsing the led.

Exactly what I'm making, sans dye!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.