100 kHz interleaved signal generation?

I am generating a 100 kHz interleaved signal (image attached) with 80 % duty cycle using the following code for Arduino UNO. However, when I measure the signal frequency with oscilloscope, I get 48 kHz only. If I try to reduce the delayMicroseconds() the maximum achievable frequency is 61 kHz.

Why this is happening? Either with UNO I cannot get more than 61 kHz or the oscilloscope measurements are incorrect... I could figure out the reason. Please help me with this issue.

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

digitalWrite(11, HIGH);
digitalWrite(13, HIGH);
}

void loop()
{
digitalWrite(13, LOW);
delayMicroseconds(2);
digitalWrite(13, HIGH);
delayMicroseconds(3);
digitalWrite(11, LOW);
delayMicroseconds(2);
digitalWrite(11, HIGH);
delayMicroseconds(3);
}

signal.png

Here's the reference page for delayMicroseconds()

This function works very accurately in the range 3 microseconds and up. We cannot assure that delayMicroseconds will perform precisely for smaller delay-times.

To get 100kHz, your loop would have to cycle in 10us.
How long does a single digitalWrite take?

OP's picture:
signal.png

Assuming an ATMega328P, you might be able to do it totally in hardware using "Phase Correct PWM Mode" on Timer 1 or Timer 2.

gfvalvo:
Assuming an ATMega328P, you might be able to do it totally in hardware using "Phase Correct PWM Mode" on Timer 1 or Timer 2.

Nope... in phase-correct PWM mode the max. frequency possible is 62 kHz!

AWOL:
To get 100kHz, your loop would have to cycle in 10us.
How long does a single digitalWrite take?

digitalWrite takes 3 us on UNO... thats what the problem is. Any solution?

dangtepau:
Nope... in phase-correct PWM mode the max. frequency possible is 62 kHz!

Not when you use Timer 1 in Mode 10. That allows you to set TOP via the ICR1 register.

See Section 16.9.4

dangtepau:
Nope... in phase-correct PWM mode the max. frequency possible is 62 kHz!

Only if you need full resolution on the duty cycle. If you're OK with less, that's what CTC mode is for.

gfvalvo:
Not when you use Timer 1 in Mode 10. That allows you to set TOP via the ICR1 register.

Yes, you are absolutely correct. With some examples I have managed to get one of the required signals with the following code. How I can get the other phase-shifted signal?

void setup()
{
// Set PB1 to be an output (Pin9 Arduino UNO)
DDRB |= (1 << PB1);

// Clear Timer/Counter Control Registers
TCCR1A = 0;
TCCR1B = 0;

// Set inverting mode
TCCR1A |= (1 << COM1A1);
TCCR1A |= (1 << COM1A0);

// Set PWM Phase Correct, Mode 10
TCCR1A |= (1 << WGM11);
TCCR1B |= (1 << WGM13);

// Set prescaler to 1 and starts PWM
TCCR1B |= (1 << CS10);
TCCR1B |= (0 << CS11);

// Set PWM frequency = 100kHz, duty-cycle = 20%
ICR1 = (F_CPU / (1*200000)) - 1;
OCR1A = ICR1 / (100 / 20);
}

void loop()
{
// main loop code
}

You have to use inverted-PWM on one channel.

#include "Arduino.h"

void setDutyCycle(uint16_t d);

const uint32_t pwmFreq = 100000;
const uint16_t topCount = F_CPU / (2 * pwmFreq); // Sect 16.9.4, don't subtract 1

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting");

  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);

  TCCR1B = (1 << WGM13);      // Stop counter, waveform generator Mode 10
  TCCR1A = (1 << WGM11);      // waveform generator Mode 10
  TCCR1A |= (1 << COM1A1);    // non-inverting PWM on OC1A
  TCCR1A |= (1 << COM1B1) | (1 << COM1B0);    // inverting PWM on OC1B
  ICR1 = topCount;                    // TOP - 100 KHz PWM
  setDutyCycle(topCount / 2);
  TCNT1 = 0;                        // Reset counter
  TCCR1B |= (1 << CS10);          // Start counter, bypass prescaler
}

void loop() {
  static uint16_t dutyCycle = topCount / 2;
  bool cycleChanged = false;
  while (Serial.available()) {
    char c = Serial.read();
    switch (c) {
      case '+':
        if (dutyCycle < topCount) {
          dutyCycle++;
          cycleChanged = true;
        }
        break;

      case '-':
        if (dutyCycle > 0) {
          dutyCycle--;
          cycleChanged = true;
        }
        break;

      default:
        break;
    }
  }
  if (cycleChanged) {
    setDutyCycle(dutyCycle);
    Serial.print("Duty Cycle = ");
    Serial.println(dutyCycle);
  }
}

void setDutyCycle(uint16_t d) {
  OCR1A = d;
  OCR1B = topCount - d;
}

gfvalvo:
You have to use inverted-PWM on one channel.

I could not get any waveform on UNO pin 9 and 10 with your code.

Don't know what to tell you. Those scope shots are from an Uno R3.

ok... got 80% duty cycle, 100kHz with the following modifications. Thanks a lot.

...
  TCCR1B = (1 << WGM13);      // Stop counter, waveform generator Mode 10
  TCCR1A = (1 << WGM11);      // waveform generator Mode 10
  TCCR1A |= (1 << COM1A1);    // non-inverting PWM on OC1A
  TCCR1A |= (1 << COM1A0);
  TCCR1A |= (1 << COM1B1);
  TCCR1A |= (0 << COM1B0);    // inverting PWM on OC1B
  ICR1 = topCount;                    // TOP - 100 KHz PWM
  setDutyCycle(topCount / 4.8);
  TCNT1 = 0;                        // Reset counter
  TCCR1B |= (1 << CS10);          // Start counter, bypass prescaler
}

void loop() {
  static uint16_t dutyCycle = topCount / 4.8;
...

The comments in your code are incorrect. With the register setting you posted, OC1A is inverting and OC1B is non-inverting.

Did you trying changing the duty cycle with the code I posted? Just enter '+' or '-' at the Serial Monitor (and maybe 'enter key' too) to change it up or down. See the code in the loop() function.

Hi Everyone.

Very interesting and use full code from --gfvalvo--

Please explain where in the void loop() does the output pins change state?

Thanks.

mikedb:
Please explain where in the void loop() does the output pins change state?

They don't. All the work is done by hardware Timer 1. See Section 16 of the ATMega328P Datasheet.

Thanks.

This info is very handy. :slight_smile: :slight_smile: :slight_smile:

gfvalvo:
The comments in your code are incorrect. With the register setting you posted, OC1A is inverting and OC1B is non-inverting.

Did you trying changing the duty cycle with the code I posted? Just enter '+' or '-' at the Serial Monitor (and maybe 'enter key' too) to change it up or down. See the code in the loop() function.

I corrected the comments... just changed the code but forgot to update the comments.

I haven't changed the duty cycle with '+' or '-' at the serial monitor. I use it for driving MOSFETs with MC33152 for a DC-DC converter and need to keep 80% duty cycle for time being.

Thanks again gfvalvo for your help.