Using digitalWrite to create a square wave of a specific frequency.

Hi, I'm trying to use digitalWrite to create a square wave output from the digital pin of my Arduino Uno. Here is the code that I'm using:

int frequency = 100; //Set frequency in Hertz

double delayTime = 1000 / (frequency * 2);

void setup() 
{
pinMode(13, OUTPUT);  
pinMode(11, OUTPUT);
}

void loop() 
{
 digitalWrite(11, LOW);
 digitalWrite(13, HIGH);
 delay(delayTime);

 digitalWrite(13, LOW);
 digitalWrite(11, HIGH);
 delay(delayTime);
}

I checked with an oscilloscope and its able to output a square wave, however the frequency is slightly off. Here's a table of the frequencies that I'm inputing via the code and the frequencies that I'm measuring with the oscilloscope.

Input Output 10 10.0 20 20.0 30 31.3 40 41.7 50 50.0 60 62.5 70 71.4 80 83.3 90 100 100 100 110 125 120 125 130 167 140 167 150 167

Any advice would be greatly appreciated!

double delayTime = 1000 / (frequency * 2);Do the arithmetic for let's say frequency = 150.

Show your working

Consider, maybe, using delayMicroseconds() instead

1000 / (150 * 2) = 3.333 ms So the delay time is 3.333 ms for a pulse of 150Hz with a 50% duty cycle. Is that not right?

All instructions take some time. One C++ line of code can be several clock cycles as it could be made of 10s of lines of assembler code.

There are interrupts happening, example millis().

Google port manipution to shorten times.

Use Timer1 library to generate the frequency required.

.

#include <TimerOne.h>
//UNO only

void setup()
{
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);

Timer1.initialize(100);  // Frequency, 100us = 10khz
Timer1.pwm(9,512);       // 50% DC on pin 9

//Timer1.pwm(10,255);    // 25% DC on pin 10

// D.C. 
// 10KHz
// You can use 2 to 1023 
// 0 & 1 gives a constant LOW 
// 1024 gives a constant HIGH
// 2 gives ~125ns HIGH pulses
// 1023 gives ~125ns low pulses
// 512 gives 50us
}

void loop()
{
}

Instead of digitalWrite, try direct port manipulation, and delayMicroseconds.

unsigned long halfPeriod = 5000UL; // 5mS = 1/2 100 Hz 
// don't use a floating # for delay, delayMicroseconds
void setup()
{
pinMode(13, OUTPUT);  
digitalWrite (13, HIGH); // known starting level
pinMode(11, OUTPUT); 
digitalWrite (11, LOW); // known starting level
}

void loop(){
while (1){ // avoid  loop() jitter
  PINB = PINB | 0b00101000; // x-x-13-12-11-10-9-8 on Uno, 
                                             // toggle output by writing 1 to input register
  delayMicroseconds(halfPeriod);
  } 
}

Rizadon: 1000 / (150 * 2) = 3.333 ms So the delay time is 3.333 ms for a pulse of 150Hz with a 50% duty cycle. Is that not right?

well, it would be right, if delay accepted floating point parameters

larryd:
All instructions take some time.

This is not true. On Arduino UNO 1 machine code instruction takes from 1 to 4 CK. But the rest is important - one Arduino IDE (C++) command is translated into anything from one to thousands (or more?) machine code instructions.

If you want to generate exact square wave you must use timers (or assembly). If you don’t mind some glitches can occur you may try to generate it in software via C but it is very crude way.

EDIT: I have read “same time” instead “some time”. I am sorry for this.

Rizadon:
1000 / (150 * 2) = 3.333 ms
So the delay time is 3.333 ms for a pulse of 150Hz with a 50% duty cycle.
Is that not right?

Round to the nearest whole integer.

larryd:
All instructions take some time.
One C++ line of code can be several clock cycles as it could be made of 10s of lines of assembler code.

Strictly speaking this is true, but it’s irrelevant. A few cycles per period will not produce this much of a difference at frequencies this low.

Smajdalf: This is not true. On Arduino UNO 1 machine code instruction takes from 1 to 4 CK. But the rest is important - one Arduino IDE (C++) command is translated into anything from one to thousands (or more?) machine code instructions.

If you want to generate exact square wave you must use timers (or assembly). If you don't mind some glitches can occur you may try to generate it in software via C but it is very crude way.

"This is not true."

I did not realize this, which instruction(s) take no time to execute?

.

Instead of delay(), use delayMicroseconds(). So 3.333 = 3333, and the result will be a number that is a multiple of 4uS (0.004mS). 3333/2 = 1666.5 Can also make the code account for that - write it blink without delay style, have one half check for say 1664 and the other for 1668, will get you pretty close to 3333.

“I did not realize this, which instruction(s) take no time to execute?”
Only the hardware running in the background kind, like the set & forget PWM outputs, or the 8 bits being clocked in & out of shift registers during a serial transfer.

CrossRoads: "I did not realize this, which instruction(s) take no time to execute?" Only the hardware running in the background kind, like the set & forget PWM outputs, or the 8 bits being clocked in & out of shift registers during a serial transfer.

Yes, after that fact, it is in hardware and not part of the program flow unless changed.

.

CrossRoads: Instead of digitalWrite, try direct port manipulation, and delayMicroseconds.

unsigned long halfPeriod = 5000UL; // 5mS = 1/2 100 Hz 
// don't use a floating # for delay, delayMicroseconds
void setup()
{
pinMode(13, OUTPUT);  
digitalWrite (13, HIGH); // known starting level
pinMode(11, OUTPUT); 
digitalWrite (11, LOW); // known starting level
}

void loop(){ while (1){ // avoid  loop() jitter  PINB = PINB | 0b00101000; // x-x-13-12-11-10-9-8 on Uno,                                             // toggle output by writing 1 to input register  delayMicroseconds(halfPeriod);  } }

I tried using this but I'm still getting the same results as when I was using digitalWrite() with the delay()

Rizadon: I tried using this but I'm still getting the same results as when I was using digitalWrite() with the delay()

Again, you need to show us what you did.

larryd:

#include <TimerOne.h>

//UNO only

void setup()
{
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);

Timer1.initialize(100);  // Frequency, 100us = 10khz
Timer1.pwm(9,512);      // 50% DC on pin 9

//Timer1.pwm(10,255);    // 25% DC on pin 10

// D.C.
// 10KHz
// You can use 2 to 1023
// 0 & 1 gives a constant LOW
// 1024 gives a constant HIGH
// 2 gives ~125ns HIGH pulses
// 1023 gives ~125ns low pulses
// 512 gives 50us
}

void loop()
{
}

I tried this with the following code:

#include <TimerOne.h>
int frequency = 150; //Frequency in Hertz
unsigned long halfPeriod = 1000 / (frequency) * 1000;

void setup()
{
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);

Timer1.initialize(halfPeriod);  // Frequency, 100us = 10khz
Timer1.pwm(9,512);       // 50% DC on pin 9

//Timer1.pwm(10,255);    // 25% DC on pin 10

// D.C. 
// 10KHz
// You can use 2 to 1023 
// 0 & 1 gives a constant LOW 
// 1024 gives a constant HIGH
// 2 gives ~125ns HIGH pulses
// 1023 gives ~125ns low pulses
// 512 gives 50us
}

void loop()
{
}

And here are the results that I got:

Input Output
40 39.8
50 50
60 62.5
70 71.4
80 83.3
90 90.9
100 100
110 111
120 125
130 143
140 143
150 167

Also, is there a way to make the output of pin 10 be the inverse of pin 9’s output? Or should I use an inverter circuit instead?

int frequency = 150; //Frequency in Hertz
unsigned long halfPeriod = 1000 / (frequency) * 1000;

Do the arithmetic. Show your working.

"Also, is there a way to make the output of pin 10 be the inverse of pin 9's output? Or should I use an inverter circuit instead?" You could, but a simple transistor inverter circuit would work too.

.

Rizadon: Also, is there a way to make the output of pin 10 be the inverse of pin 9's output?

Doing it in s/w will cause a delay between having both pins in the desired state that is much longer than if it is done in h/w from pin 9's output as you will have to set the output of both pins. Depending on core you are using (AVR being the slowest), and the compiler version you are using, and the actual type of parameters being passed into digitalWrite(), that delay can be in order of microseconds like around 4-6 on a 16mHz AVR. (It can be MUCH longer if there are lots of interrupts being processed) The delay/latency would be much smaller on parts like the pic32 or the ESP8266 which are in the 10s of nanoseconds to update a pin ouput since they have much better implementation of digitalWrite() and better internal h/w registers inside the micro-controller.

DigitalWrite() is particularly painful on the AVR because of the combination of the semantics of the API the digitalWrite() function and the wimpy way the AVR i/o ports work and AVR instructions needed to work around it to ensure atomicity of the register update.

Now if you use Pauls AVR Teensy boards, he ships his own core that did a MUCH better job of coding digitalWrite(). Under certain circumstances you can get around 140ns on a 16mhz AVR. It requires using constants for the pin and value parameters.

But as larryd pointed out, a transistor is pretty simple and the latency between the update pin 9 and the transistor output will be measured in nano seconds and be very consistent regardless of what processor is driving the pin and regardless of what else is going on in the part like interrupts.

--- bill

Got it! I'll use the transistor inverter circuit to switch between low and high signals.

I'll continue looking at Timer1 to specify frequencies. Thanks for the help!