1

How to generate 100khz square wave with 50% duty cycle using arduino uno The following code was tried but did not give appropriate output

void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delayMicroseconds(5); // wait for 5 micro seconds, 100khz frequency with  50%         
                           // duty cycle
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delayMicroseconds(5);              // wait for 5 micro seconds
}
2

2 Answers 2

4

The problem with your program is that it does not loop fast enough. Each call to delayMicroseconds() should take roughly the requested time to execute, but the CPU also needs time to execute the rest of your code, including the time needed to return from loop() and call it again. The period of the signal ends up being considerably longer than the desired 10 µs.

The simplest software-only approach would be to get rid of the delay using the technique described in the Blink Without Delay Arduino tutorial. With one twist: instead of

// save the last time you blinked the LED
previousMillis = currentMillis;

you would use

// save the last time you should have blinked the LED
previousMillis += interval;

This way the small timing errors do not add up: you get, on average the correct period. Oh, and obviously, you track time in microseconds, not milliseconds:

const uint16_t TOGGLE_TIME = 5;  // toggle pin every 5 us

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

void loop() {
    static uint16_t last_toggle;
    static uint8_t pin_state = LOW;

    if ((uint16_t) micros() - last_toggle >= TOGGLE_TIME) {
        pin_state = !pin_state;       // invert pin state
        digitalWrite(13, pin_state);
        last_toggle += TOGGLE_TIME;
    }
}

This may somehow work if you have a fast enough Arduino, maybe a Due or a Zero. If you have an AVR-based board (like the Uno and actually most Arduinos), you will not get anything faster than ≈ 50 kHz with this. This is because digitalWrite() is sooo slooow, it cannot cope with your 100 kHz. You can improve things by using direct port access[] instead of digitalWrite():

void loop() {
    static uint16_t last_toggle;

    if ((uint16_t) micros() - last_toggle >= TOGGLE_TIME) {
        PINB = _BV(PB5);             // invert pin state
        last_toggle += TOGGLE_TIME;
    }
}

Now you have roughly the requested frequency, but with some terrible jitter: testing on my Uno I could see each level being held for slightly less than 5 µs. Then, from time to time, one level is held for ≈ 9 µs. Things could still be made faster by wrapping the if inside an infinite loop, instead of relying on the Arduino core to repeatedly call loop():

void loop() {
    uint16_t last_toggle = 0;

    for (;;) {  // infinite loop
        if ((uint16_t) micros() - last_toggle >= TOGGLE_TIME) {
            PINB = _BV(PB5);             // invert pin state
            last_toggle += TOGGLE_TIME;
        }
    }
}

And now the signal looks still worse: with the program running faster, levels are held most of the time for only 3.5 µs, and then one out of every ≈ 4 levels is held for about 9 µs. The average frequency is probably right, but the jitter is absolutely awful, which is likely due to the 4 µs resolution of micros() noted by user Talk2.

The conclusion from these tests is: your only hope to get a somewhat clean signal at this frequency is to generate it in hardware, not in software. Choose one of the timers and have it generate the signal you want. I would avoid timer 0, as it is needed by the Arduino timing functions (delay(), millis()...). Here is how it can be done with timer 2:

void setup() {
    // The output will be on digital 3 = PD3 = OC2B.
    pinMode(3, OUTPUT);

    // Configure timer 2 for PWM @ 10 kHz on OC2B.
    TCCR2A = _BV(COM2B1)  // non-inverting PWM on OC2B
           | _BV(WGM20)   // mode 7: fast PWM, TOP = OCR2A
           | _BV(WGM21);  // ditto
    TCCR2B = _BV(WGM22)   // ditto
           | _BV(CS21);   // clock @ F_CPU / 8
    OCR2A  = 19;          // period = (19 + 1) * 8 CPU cycles
    OCR2B  = 9;           // HIGH for (9 + 1) * 8 CPU cycles
}

void loop() {
    // Nothing to do here, the signal is generated in hardware.
}

And now you have a perfectly clean 10 kHz square wave, while your program is free to do other stuff at the same time. Notice that the output is now on pin 3: the pin choice is limited when using the hardware-based approach. Notice also that you loose the PWM capability on pin 11, since it depends on timer 2.

0

First, be sure to use the "{}" in the menu to show your code in a code block

void setup() { 
// initialize digital pin 13 as an output. 
pinMode(13, OUTPUT); }

// the loop function runs over and over again forever 
void loop() { 
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)  
delayMicroseconds(5); // wait for a second 
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW 
delayMicroseconds(5); // wait for a second }

I think you could benefit from reading the Secrets of PWM

using a microsecond delay will only give you an approximate pwm signal.

The Arduino's programming language makes PWM easy to use; simply call analogWrite(pin, dutyCycle), where dutyCycle is a value from 0 to 255, and pin is one of the PWM pins (3, 5, 6, 9, 10, or 11). The analogWrite function provides a simple interface to the hardware PWM, but doesn't provide any control over frequency. (Note that despite the function name, the output is a digital signal, often referred to as a square wave.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.