Phase shifted PWM outputs - shift changing unexpectedly?

I have been using the code below to create two offset square waves. It works - with one caveat I can't seem to overcome:

The requested phase shift is 90 degrees. However, running the loop below, it will occasionally give me a shift of 270 degrees instead.

I've set the outputs into toggle mode - and if I missed a toggle, then I can see it would cause these symptoms - but I can't for the moment work out why this might be occuring. Any ideas...?

int ttlA = 9;
int ttlB = 10;

void setup() {
  Serial.begin(9600);
  pinMode(ttlA, OUTPUT);  // Arduino Pin  9 = OCR1A
  pinMode(ttlB, OUTPUT);  // Arduino Pin 10 = OCR1B

  // Set both outputs in toggle mode
  TCCR1A = 0x00;
  bitSet(TCCR1A, COM1A0);
  bitSet(TCCR1A, COM1B0);

  TCCR1B = 0x00;
  bitSet(TCCR1B, WGM12);
  bitSet(TCCR1B, WGM13);

  OCR1A = 0;  // First output is the base, it always toggles at 0
}

// prescaler of 1 will get us 8MHz - 122Hz
// At 16Mhz, toggling every tick (clocks per toggle=1), we toggle at 16MHz, which gives us 8Mhz frequency
// ICR1 is a 16 bit register, so a maximum value of 2^16=65536
// if clocks per toggle is 65536, then we get 122Hz

#define CLK 16000000UL  // Clock speed is 16MHz on Arduino Uno

int setWaveforms(unsigned long freq, int shift) {
  //Stop the timer whilst we change things and re-zero the timer registers
  TCCR1B = 0x00;

  unsigned long clocks_per_toggle = (CLK / freq) / 2;  // /2 becuase it takes 2 toggles to make a full wave
  ICR1 = clocks_per_toggle;
  unsigned long offset_clocks = (clocks_per_toggle * shift) / 180UL;  // Do mult first to save precision
 
  OCR1A = 0;              //counts until TCNT = O then toggles
  OCR1B = offset_clocks;  //counts until TCNT = offset_clocks then toggles

  // Turn on timer
  TCNT1H = 0x00;
  TCNT1L = 0x00;
  bitSet(TCCR1B, WGM12);
  bitSet(TCCR1B, WGM13);
  bitSet(TCCR1B, CS10);
}

void loop() {
	setWaveforms(1000, 90);
    delay(4000);
	setWaveforms(1500, 90);
    delay(4000);
	setWaveforms(2000, 90);
    delay(4000);
	setWaveforms(2500, 90);
    delay(4000);
}

As a workaround, I'd set both GPIO's to a defined state in setup() as well as setWaveforms. This way you know what the starting position is.

Good idea - I added:

digitalWrite(ttlA, 0);
  digitalWrite(ttlB, 0);

to both setup and in setWaveforms (just before reenabling timer1)
However, on a scope I can still see the pase shift is sometimes 90 degrees, and sometimes 270....

Setting control bits one by one will result in possibly unwanted timer states. Better set the bits in a temporary variable and only write the final value to a timer register.

1 Like

Good idea - I had stopped the timer when editing values to try to avoid that, but have now explicitly used things like:

  byte temp=0x00;
  bitSet(temp, WGM12);
  bitSet(temp, WGM13);
  bitSet(temp, CS10);
  TCCR1B=temp;

However, whilst a bit neater, this doesn't seem to have changed the occasional phase shifts I am seeing

Yeah, it's puzzling. I couldn't explain the phase shift from the non-atomic bit operations, so I'm not too surprised that this modification hasn't been effective.

Sorry to be of little help!

This repo came to my rescue:

It looks very similar to what I had already, with the key difference that it works reliably!

I'll do some digging to try to work out why that is, but for now, all is good :slight_smile:

It sounds like you may be using a Nano, Uno or 2560 in CTC mode (toggle). If so, it will occasionally flip-phase 180 degrees when trying to adjust the phase-shift angle during run-time.

You can get around this using the Forced Output Compare registers to clean things up during the phase-change. The idea is, stop Timer, reset wavegens to LOW and re-start timer.

There is an example project at Runtime Micro that illustrates how to make run-time changes and achieve (no phase-flip) results. The down-side is your O-Scope won't like the Stop-Start action of code during phase-angle changes. The good news is the phase-flip is gone.

hth

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.