A strobe light with accurate frequencies

Hi all, I'm working on a strobe light (just one LED to begin with) and I would like to tune the frequency to precise values, which I thought would be a piece of cake with Arduino. However, I couldn't use a loop()/delay(period) method:

int T = 1;
void loop() {
  digitalWrite(12, HIGH);
  delayMicroseconds(500);
  digitalWrite(12, LOW);
  delay(T);
}

a max frequency of only 48 Hz can be reached with this method, still far from the 16 MHz of the Atmega. Even when my period T was less than 1 ms, which should yield f = 1/T = 1000 Hz.... moreover it was not very stable, the frequency would vary by 10 to 20%.

So I have searched and found this (among other interestting websites/blogs):

the problem is I found myself unable to understand what is going on in this source code. I really don't understand how the LED is turned on and off. Nothing else than changing the frequency seems to happen in the loop() function, so the function making the LED blink must be set in the setup() (or before? :o ), but I am really unable to see where. A detail: although it is not indicated, it seems like the guy is using the Timer1.h library. Here is the setup() function, where everything seems to happen (especially lines 9 to 14):

void setup() {
    pinMode(PIN_SYNC,OUTPUT);
    digitalWrite(PIN_SYNC,LOW); // show we arrived
 
    analogWrite(PIN_PWM10,0);           // turn off other PWM outputs
    analogWrite(PIN_PWM11,0);
 
    analogWrite(PIN_STROBE,1);          // let Arduino set up default Timer1 PWM
    TCCR1B = 0;                         // turn off Timer1 for strobe setup
    TCCR1A = 0x82;                      // clear OCR1A on match, Fast PWM, lower WGM1x = 14
    ICR1 = FlashPdCt;
    OCR1A = FlashLengthCt;
    TCNT1 = FlashLengthCt - 1;
    TCCR1B = 0x18 | TCCRxB_CS;          // upper WGM1x = 14, Prescale 1:64, start Timer1
 
    pinMode(PIN_KNOB_B,INPUT_PULLUP);
    pinMode(PIN_KNOB_A,INPUT_PULLUP);
 
    KnobState = digitalRead(PIN_KNOB_A);
    Button = PrevButton = ReadButtons(PIN_BUTTONS);
 
    attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
 
    Serial.begin(9600);
    fdevopen(&s_putc,0);                // set up serial output for printf()
 
    printf("Stroboscope Tachometer\r\nEd Nisley - KE4ZNU - December 2012\r\n");
 
    printf("Frequency: %d.%02d\nPulse duration: %d us\n",
           (int)FlashFreq,(int)(100.0 * (FlashFreq - trunc(FlashFreq))),
           (int)(1e6 * FlashLength));
 
    MillisThen = millis();
 
}

Some guidance, or links to other (easier) methods would be welcome.
Thanks :slight_smile:

1 Like

Tone library?

Hi Groove, thank you for your quick reply. I have thought of using a Tone library, but a Tone library will "generates a square wave of the specified frequency (and 50% duty cycle) on a pin". I would prefer to generate an impulse that is only a few percent of the duty cycle, in order to get a neat image.
For example a puls of 100 micros, even with a frequency of 20 Hz (T = 50 ms = 50,000 micros).
While using a tone method, maybe could the 50% pulse be reduced to a few percent?

briandu64:
Hi Groove, thank you for your quick reply. I have thought of using a Tone library, but a Tone library will "generates a square wave of the specified frequency (and 50% duty cycle) on a pin". I would prefer to generate an impulse that is only a few percent of the duty cycle, in order to get a neat image.
For example a puls of 100 micros, even with a frequency of 20 Hz (T = 50 ms = 50,000 micros).
While using a tone method, maybe could the 50% pulse be reduced to a few percent?

Something that might help you along is the PWM library, available here:- Arduino PWM Frequency Library
From the opening comments in the "PWM_lib_example":-

The library allows for a frequency range from 1Hz - 2MHz on 16 bit timers and 31Hz - 2 MHz on 8 bit timers.

(Second time I've posted that quote in 2 hours.)
And duty cycle can be set from 0 to 255, like with standard PWM. So at 20Hz, (using a 16-bit timer), the period will be 50mS, as you said. 1/255 x 50mS = 196uS. So, you can get pulses down to as short as 196uS. Would that be good enough? You'll have trouble doing better with any degree of accuracy, I'm afraid.

And of course, as you increase the frequency from there, you can have shorter and shorter pulses. At 40Hz, about 100uS, etc etc.

Try it this way:

byte T = 1;
void setup(){
pinMode (12, OUTPUT);
}
void loop() {
  while (1){
  PORTB = PORTB | 0b00010000; //digitalWrite(12, HIGH);
  delayMicroseconds(500);
  PORTB = PORTD & 0b11101111; //digitalWrite(12, LOW);
  delay(T);
  }
}

The while() skips the code that main() & void() does.
Direct port manipulation to set & clear the IO pin skips all the checking that digitalWrite does.
This should get you much closer to 1/.0015 S for period, so 666.67Hz.

Improvements after that can include changing to blink without delay.
For example, this code outputs a 20 KHz signal

/* sketch to output burst of 20KHz signal 
 run on regular Arduino
 */
byte pulseOut = 2;

unsigned long currentMicros;
unsigned long nextMicros;
unsigned long duration = 25UL; // flip every 50uS = 20KHz pulse
unsigned long elapsedMicros;

void setup(){

  pinMode (pulseOut, OUTPUT);
  digitalWrite(pulseOut, LOW);

}
void loop(){
    currentMicros = micros();
    elapsedMicros = currentMicros - nextMicros;
    if (elapsedMicros >= duration){
      nextMicros = nextMicros + duration;
        PIND = PIND | 0b00000100; // toggle D2 by writing to Input register
      }
    }

Thank you so much for these answers :slight_smile:
and it works :smiley:

One question: using the tone library, and this little setting outside, could it help reducing the pulse length to arbitrarily short periods? (see diagram attached)

schema principe limitation duty cycle.png

Crossroad, to understand your source code, it is necessary to get familiar with unsigned long, for example

unsigned long duration = 25UL

You are using UL even for the increment.
I have found this:

http://playground.arduino.cc/Code/TimingRollover

If I divide duration by a any factor (or multiply it), will the result still be a unsigned long?

hmmm no in fact it doesn't work:

if (elapsedMicros >= duration) {
    nextMicros = nextMicros + duration;
    PIND = PIND | 0b00000100; 
    digitalWrite(2, HIGH); // I stupidly added this line to the code, which I think is redundant, because of 
                               //the previous one
  }

But if I delete digitalWrite(2, HIGH);, nothing (seems to) happens, so I'm still searching

briandu64:
Crossroad, to understand your source code, it is necessary to get familiar with unsigned long, for example

unsigned long duration = 25UL

You are using UL even for the increment.
I have found this:

http://playground.arduino.cc/Code/TimingRollover

If I divide duration by a any factor (or multiply it), will the result still be a unsigned long?

Yes. But technically, an unsigned long is not required for interval variables. It is used to provide room for using larger variables than 25 later without overflowing the variable. If you were careful not to do that, you could use a byte and it would work safely.

But if I delete digitalWrite(2, HIGH);, nothing (seems to) happens, so I'm still searching

How are you determining that? The output should be toggling at 20 KHz rate, which you need an oscilliscope to see.
You can set duration to a much bigger number, slow down the frequency to something you can see,

CrossRoads:
How are you determining that? The output should be toggling at 20 KHz rate, which you need an oscilliscope to see.
You can set duration to a much bigger number, slow down the frequency to something you can see,

Just looking at it, where you use OR in this:-

PIND = PIND | 0b00000100;

Shouldn't it be XOR to 'toggle' the pin?

PIND = PIND ^ 0b00000100;

@oldsteve

PIND is an input register. PORTD is an output register.

From the data sheet

If PORTxn is written logic one when the pin is configured as an output pin, the port pin is driven
high (one). If PORTxn is written logic zero when the pin is configured as an output pin, the port
pin is driven low (zero).
11.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn.
Note that the SBI instruction can be used to toggle one single bit in a port.

Writing to a bit on PIND will toggle it, and XORing a bit on PORTD will toggle it as well.

My understanding is that writing to the input register takes fewer instructions than the XOR operation.

cattledog:
@oldsteve

PIND is an input register. PORTD is an output register.

From the data sheet

Writing to a bit on PIND will toggle it, and XORing a bit on PORTD will toggle it as well.

My understanding is that writing to the input register takes fewer instructions than the XOR operation.

Ah, right. I wasn't aware of that. Another idiosyncrasy of these chips that I'll have to get used to.
As you know, usually OR won't toggle a bit, only set it.
I'll have to try to remember this. (Didn't pick up on the PIND/PORTD bit, either, but was puzzled by the OR.)

Thanks for setting me straight.