Ardunio as an accurate low duty cycle 1000kHz square vawe synthetizer

Hello, dear Ardunio community!

As someone without enough experience on the field, I am looking for your advice.

Question:
I would like to use an ATMega328 IC to generate a square wave with an adjustable frequency with the following propertities:
Frequency: in the range of 1Hz-1000kHz with with 0.1% frequency stability, and with the possibility of setting any given frequency with at least (about) 0.1% or 0.1Hz accuracy. (if impossible, 1% accuracity is OK.)
Duty cycle: about 5-10% with reasonable stability, preferably adjustable in the range of 1-50%.
Is the ATMega328 capable of producing this signal by itself? If yes, is there a programming libary I could use? If not, could you recommend a cheap (< 3$) frequency generator IC with this capability that I could use with the ATMega?

Background:
I want to build a simple and cheap stroboscope. The general plan is controlling 3-5 power LEDs with the square vawe described above through transistors/MOSFETs/etc. I am considering several approaches. The simplest is using two 555s, one in astable mode with adjustable frequency, driving the other in monostable mode for adjusting the duty cycle without altering the frequency.

I like this for it's simplicity, but the frequency remains unknown. The second plan is using a barebone ATMega328 (with either a 16MHz or 20MHz crystal) producing the square vawe, controlled with a knob and 2-4 buttons, and displaying the frequency on an SPI-connected 7-segment display. I, however, have very basic experience with Ardunio, so the programming part should be simple. Third plan is using the two-555 circuit, and counting the frequency with the ardunio, but I'd prefer digital controll if I use an MCU anyway.

Sure, try this code to start, then expand to have different times for high & low levels.

byte pin8 = 8;
byte pin9 = 9;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
unsigned long halfPeriod = 5000UL; // microseconds, x 2 = 10mS = 100 Hz

void setup() {
  pinMode (pin8, OUTPUT);
  digitalWrite (pin8,  HIGH);
  pinMode (pin9, OUTPUT);
  digitalWrite (pin9, LOW);
  currentMicros = micros();
  previousMicros = currentMicros;
}

void loop() {
  while (1) {
    currentMicros = micros();
    elapsedMicros = currentMicros - previousMicros;
    if (elapsedMicros >= halfPeriod) {
      PINB = PINB | 0b00000011;
      previousMicros = previousMicros + halfPeriod;
    }
  }
}

You won't get 0.1% accuracy with the arduino standard ceramic resonator clock reference.

2%? And a poor temperature coefficient.

Fit a crystal.

Allan

CrossRoads:
Sure, try this code to start, then expand to have different times for high & low levels.

Thanks. So, apparently simple bruteforce on/off switching would do the trick. How high would the frequency limit possibly be? I guess the comparing/shifting/etc. program parts take a bit of time, and the discrete timing also affects the accuracy.

allanhurst:
You won't get 0.1% accuracy with the arduino standard ceramic resonator clock reference.

2%? And a poor temperature coefficient.

Fit a crystal.

2% is probably OK, I am just ambicious a bit. :stuck_out_tongue: I will use an external crystal if that's what you mean.

Replace the resonator on the arduino board with a crystal - it's not very hard.

Allan

~15.2hz to 1Mhz using TimerOne library

Note: 1=1Mhz, 2=500Khz, etc.

#include <TimerOne.h>

//   UNO only !!!!!!!!!!!!!!!!!!!!!!

void setup() 
{
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  
  unsigned int num = 1;
  //65535  gives 15.259hz  slowest frequency
  //16666  gives 60hz
  //10000  gives 100hz
  //1000   gives 1Khz
  //100    gives 10Khz
  //50     gives 20Khz
  //10     gives 100Khz
  //1      gives ~1Mhz     fastest frequency
  //numbers in between are proportional
  //set your frequency
  Timer1.initialize(num);
  //set your duty cycle, values 0-1023 are valid
  Timer1.pwm(9,512);  //50%
  //Timer1.pwm(10,255); //25%
}

void loop() 
{
}

The code shown in the several previous posts will not meet the OP's most basic requirements. Edit: On second look, CrossRoads code will function up to a point but you'll never get 1000kHz out of it.

A strobe needs a continuously adjustable frequency source. You cannot generate this using the timers of the ATMega328 or any of the Arduino processors as you are limited to powers of two.

An easy solution is to employ a DDS (direct digital synthesis) signal generator and use the Arduino for the user interface. The AD9833 is good up to 10mhz and uses a simple serial interface. They can be had for under $10.

eBay listing for AD9833

AD9833 Datasheet

If you Google "AD9850 Arduino VFO" you can find several reference projects using the older AD9850 DDS chip that is good to 40Mhz. They've been used in many homebrew ham radio projects. The AD9833 is an updated version that supports sine, triangle and square wave outputs, the AD9850 was sine only.

Also, beware of these things floating around eBay and elsewhere for cheap.

Screen Shot 2017-06-09 at 5.51.36 PM.png

They say they are AVR DDS generators, and yes, they have an ATmega16 under the LCD but what they don't tell you is they are only good to about 5khz. They function by making a crude DAC by tying a 8 bit output port together with a R2R ladder and dumping the result into an LM358. Works as a proof of concept but it has serious limitations due to processor speed and opamp bandwidth.

Just a quick update.
I cheked CrossRoad's code with a scope, and works relativly accurately in the 0-1kH range, and somewhat usabe up to 2-30kHz. I think I'll go with it, 1000kHz is too ambicious and I probably won't need it anyway.

And a proramming question related to that code: I understand that PINB = PINB | 0b00000011; sets pin 8 and 9 to HIGH, but what sets them low again? | is bitvise OR, so once you set the last two bit to 1 nothig seems to set them back. What happens?

PINB &= 0b11111100; will clear the two bits you set with the OR.

avr_fred:
PINB &= 0b11111100; will clear the two bits you set with the OR.

Thx, I know the solution, but I don't understand why CrossRoad's code works. (Because it does...)

but I don't understand why CrossRoad's code works. (Because it does...)

PINx is an input register containing the state of the pins and is normally read.

There is a special instruction, defined in the data sheet, that when you write a 1 to a bit in the PINx register, it toggles that pin.

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.

cattledog:
PINx is an input register containing the state of the pins and is normally read.

There is a special instruction, defined in the data sheet, that when you write a 1 to a bit in the PINx register, it toggles that pin.

Ah, I see, thanks. I thought PINx simply sets which pins should be high/low.

Upon further reading I found the instruction tone(pin, frequency), which causes the given pin to output a square wave with the given frequency (31Hz-65535Hz) and 50% duty cycle. Do you know a libary that has a function like this but with the possibility to reach 10% or 90% duty cycle?

Do you know a library that has a function like this but with the possibility to reach 10% or 90% duty cycle?

You were pointed at TimerOne.h in reply #5. Did you try it?

Try this for changing the duty cycle, compiles, but might need a little tweaking still.
I've run similar code at 20 KHz, bring the Period down a scotch to basically allow for the while(1) code time.

byte pin8 = 8;
byte pin9 = 9;
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedMicros;
unsigned long Period = 10000UL; // overall period
// pick the high time, calculate the low time from what's left
unsigned long highPeriod = 5000UL; // microseconds, x 2 = 10mS = 100 Hz
// probably best if times are increments of 4uS to match up with micros()
unsigned long lowPeriod; // microseconds,  set to = Period - highPeriod in setup()

byte highTime;

void setup() {
pinMode (pin8, OUTPUT);
digitalWrite (pin8, HIGH);
pinMode (pin9, OUTPUT);
digitalWrite (pin9, LOW);
currentMicros = micros();
previousMicros = currentMicros;
lowPeriod = Period - highPeriod;
}

void loop() {
while (1) {
currentMicros = micros();
elapsedMicros = currentMicros - previousMicros;
if (highTime == 1 && elapsedMicros >= highPeriod) {
PINB = PINB | 0b00000011;
previousMicros = previousMicros + lowPeriod;
highTime = 0;
}
if (highTime == 0 && elapsedMicros >= lowPeriod) {
PINB = PINB | 0b00000011;
previousMicros = previousMicros + highPeriod;
highTime = 1;
}

}

}

cattledog:
You were pointed at TimerOne.h in reply #5. Did you try it?

I could try it only now, last time I miswired something in a hurry, and it didn't work at all :-[ now that I have time, I see timerOne is pretty much what I was looking for. it's much more stable than the software solution.

The only drawback is what avr_fred mentioned: the period must be an integer, so the higher the frequency, the lower the accuracy. But I think I can live with this, the error is below 1% under 10kHz.

Bonus: this is the waveform at the highest possible frequency (1MHz) at 12,8% duty cycle. Pretty and stable 8)

larryd:
~15.2hz to 1Mhz using TimerOne library

Note: 1=1Mhz, 2=500Khz, etc.

#include <TimerOne.h>

//  UNO only !!!!!!!!!!!!!!!!!!!!!!

void setup()
{
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
 
  unsigned int num = 1;
  //65535  gives 15.259hz  slowest frequency
  //16666  gives 60hz
  //10000  gives 100hz
  //1000  gives 1Khz
  //100    gives 10Khz
  //50    gives 20Khz
  //10    gives 100Khz
  //1      gives ~1Mhz    fastest frequency
  //numbers in between are proportional
  //set your frequency
  Timer1.initialize(num);
  //set your duty cycle, values 0-1023 are valid
  Timer1.pwm(9,512);  //50%
  //Timer1.pwm(10,255); //25%
}

void loop()
{
}

By my math, Timer1 should be capable of going down to 0.2 Hz on a 16 MHz AVR. The argument for initialize is the period of the PWM signal (in microseconds), not the TOP value. It calculates the correct TOP and prescaler settings based on that,

Back to the beginning.

OP :

Background:
I want to build a simple and cheap stroboscope.

The traditional practice is to use a xenon discharge lamp as in a photographic flashgun as the light source - it's MUCH brighter than a few modern LEDs, good as they are.

1000Hz should be plenty - not many things rotate at > 60,000 rpm

Perhaps a pot-controlled 555 with a frequency counter, which an arduino could do well.

Allan

allanhurst:
The traditional practice is to use a xenon discharge lamp as in a photographic flashgun as the light source - it's MUCH brighter than a few modern LEDs, good as they are.

1000Hz should be plenty - not many things rotate at > 60,000 rpm

Perhaps a pot-controlled 555 with a frequency counter, which an arduino could do well.

That's my plan B if the LEDs won't work well. ;D The thing is, the cheapest xenon car bulbs I could buy costs about 15-20$, while the five power LEDs I ordered from eBay were 1$. And LEDs seems to be easier circuit-wise at first glance.

I agree with you frequency-vise, though one of my plan is examining violin strings and other vibrations in the audio frequency, hence the push for 10kHz.