Two PWM channels with 180 phase shift on Due

I had a problem with programming Due PWM channels and managed to write a code that seems to solve it, but I am not sure about its correctness. The problem is described below, and my questions are at the very end.

I need two PWM channels with a fix and rather high frequency 20 – 25 kHz. The duty factors should be variable and the phases of two channels should be shifted by half a period.

I programmed Leonardo to achieve this, but due to its frequency got a limited, around 8-bit, resolution. And I would like to have it better, because I want to use it in a programmable heating controller, where speed and precision matter.
I turned then to Due with more than 5 times higher frequency. The Due has a different system of PWM channels, more complicated and I did not find the Atmel datasheet very helpful.
Anyway, after looking for a solution in a number of places and after many experiments, I managed to write a code that seems to fit my needs.
The code is like this:

#include <Arduino.h>
 
void setupPWM();

void setup() {
  Serial.begin(115200);
  delay(10);
  setupPWM();
}

int i = 0;
void loop() {
  delay(5000);
  i++;
  uint32_t duty = i*200ul;
  PWM->PWM_CH_NUM[4].PWM_CDTY = duty;
  PWM->PWM_CH_NUM[5].PWM_CDTY = 4000ul - duty;
  Serial.println("duty set to " + String(i) + "/10 of max");
  if (i == 9) i = 0;
}

void setupPWM() {
  // Set up PWM on digital pins D9, D8 for channels 4, 5 respectively
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;  // enable PWM through peripheral clock enable register (p. 563)
  PIOC->PIO_ABSR |= PIO_ABSR_P21 | PIO_ABSR_P22;  // select port C pins 21 and 22 for PWM through its peripheral select register (p. 656)
  PIOC->PIO_PDR |= PIO_PDR_P21 | PIO_PDR_P22;  // PIO disable register enables peripheral control of the pins (p. 634)
  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);  // configure PWM clock A with no prescaler and no divider resulting in 84 MHz
  
  // Arduino Due pin 9
  PWM->PWM_CH_NUM[4].PWM_CMR |= PWM_CMR_CALG | PWM_CMR_CPRE_CLKA;
  PWM->PWM_CH_NUM[4].PWM_CPRD = 3999;
  PWM->PWM_CH_NUM[4].PWM_CDTY = 500;

  // Arduino Due pin 8
  PWM->PWM_CH_NUM[5].PWM_CMR |= PWM_CMR_CPOL | PWM_CMR_CALG | PWM_CMR_CPRE_CLKA;
  PWM->PWM_CH_NUM[5].PWM_CPRD = 3999;
  PWM->PWM_CH_NUM[5].PWM_CDTY = 3500;
  // Enable PWM channels
  PWM->PWM_ENA = PWM_ENA_CHID4 | PWM_ENA_CHID5;
}
`setupPWM()` function enables the PWM device, sets two pins for PWM output, and programs PWM clock A for the maximum frequency 84 MHz. I selected Due pins D9 & D8 and PWM channels 4 & 5. This is done by the code in the first 4 lines of the function. Then it configures the two selected channels, setting them to the center aligned waveform and to use the PWM clock A, sets the period and duty cycle. The only difference is that channel 5 has reversed polarity and duty cycles. This results in positive polarity pulses of the same length and shifted by half a period. At least it looks like that when I use the oscilloscope. The `loop()` function changes gradually the duty factor and seemingly the whole code works as expected. But I am not sure if it is true. So, I have questions and would appreciate help in answering them.

Is the code safe? I mean, may changing the duty factor in the way used in the loop() cause any harm either to Due or to a connected circuit? Should the update registers be used and how?

Are pulses in the two channels shifted exactly by half a period or is it an accident?

If the code needs to be corrected, in which way, then?

It won't harm the Due. We can't tell if it will affect the connected circuit, because you haven't told us what that is.

tbh, I am surprised that a heating circuit needs high precision.

Certain processes depend on temperature exponentially and I want to control it with a constant heating rate.

The connected circuit will be switching transistors and then the primary windings of the transformer, so they are probably safe, but I rather think of spikes that may produce some interference.

Ok, given such a general description, it would be better to be safe and use the update registers.

So the transformer is for isolation?

What do you do with the transformer output, rectify it back to variable DC?

Have you looked at "linear optocouplers"?

Tom.... :smiley: :+1: :coffee: :australia:

@bobcousins
Can you advice on how to use the update registers in this context?

I don't have specific advice, I would consult the datasheet. https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-11057-32-bit-Cortex-M3-Microcontroller-SAM3X-SAM3A_Datasheet.pdf Chapter 38

In principle it's just a question of enabling the mode and writing to the update registers instead of the "live" registers.

Edit; I suggest to follow "Method 2" described on page 988.

Transformer transforms low current (<~ 1 A) into high current needed to heat the heater. Isolation is a side effect. AC heats as well as DC.

Well, I saw that method but it is listed under synchronous channels, which they are not. I tried synchronous channels but couldn't get the shifted phase.

Ok, I guess you are the expert then.

I wouldn't ask if I was one.

What is the Leonardo code for what you did?

The code setting PWM channels on Leonardo is:

void setupPWM(void) {
  p = Ntop;
  p = p/ocr1A;

  // 1 Hz: prescaler 1/256 (4), TOP 31249
  // 25 kHz: prescaler 1 (1), TOP 319
  DDRB |= bit(PORTB5);  // activate PB5 as output - timer/counter 1A (OC1A) - Arduino Leonardo pin D9
  DDRC |= bit(PORTC6);  // activate PC6 as output - timer/counter 3A (OC3A) - Arduino Leonardo pin D5
  // timer/counter #1A control registers: WGM 10, COM 2, prescaler 4 (1/256)
  TCCR1A = 0;
  TCCR1B = 0;
  ICR1 = Ntop;
  TCCR1A = bit(COM1A1) | bit(WGM11);
  TCCR1B = bit(WGM13) | bit(CS12);
  OCR1A = ocr1A;
  // timer/counter #3A control registers: WGM 10, COM 3, prescaler 4 (1/256)
  TCCR3A = 0;
  TCCR3B = 0;
  ICR3 = Ntop;
  TCCR3A = bit(COM3A1) | bit(COM3A0) | bit(WGM31);
  TCCR3B = bit(WGM33) | bit(CS32);
  OCR3A = ocr3A;
}

void dutyFactor(double dF) {
  dF = min(100, dF);
  dF = max(0, dF);
  dF = 1.0 - dF/100.0;
  ocr1A = dF*Ntop;
  ocr3A = Ntop - ocr1A;
  OCR1A = ocr1A;
  OCR3A = ocr3A;
  p = (100.0 - ocr1A*100.0/Ntop);
}

void setFast(void) {  // 25 kHz
  Ntop = 319;
  ocr1A = Ntop;
  ocr3A = Ntop - ocr1A;
  // timer/counter #1A control registers: WGM 10, COM 2, prescaler 1 (1/1)
  TCCR1A = 0;
  TCCR1B = 0;
  ICR1 = Ntop;
  TCCR1A = bit(COM1A1) | bit(WGM11);
  TCCR1B = bit(WGM13) | bit(CS10);
  OCR1A = ocr1A;
  // timer/counter #3A control registers: WGM 10, COM 3, prescaler 1 (1/1)
  TCCR3A = 0;
  TCCR3B = 0;
  ICR3 = Ntop;
  TCCR3A = bit(COM3A1) | bit(COM3A0) | bit(WGM31);
  TCCR3B = bit(WGM33) | bit(CS30);
  OCR3A = ocr3A;
}

It is not the whole program. setupPWM() sets the clock and prescaler to 1 Hz PWM frequency (for testing purposes). setFast() changes that to 25 kHz at the expense of resolution, though. dutyFactor(double) changes only the duty factor of both channels. Note that duty factors of both channels are complimentary, they sum up to the whole period. This is because the waveform is center aligned and polarity of one channel is reversed.

Ah, then they are complimentary signals, not phase-shifted.

Is it like this: (85% on channel 1, 15% on channel 2, pulses centered on each other?)

How did you keep the Leonardo's two timers aligned, i.e. TCNT1 == TCNT3?

What is the circuit? Are these two signals controlling the two terminals of the transformer?

So why not just use a MOSFET to PWM the heater directly?

What will the PWM input voltage be to the transformer?
What voltage and current is needed directly for the heater?
What power rating is the heater?

Low current input to high current output means high voltage input to low voltage output.

Tom.... :smiley: :+1: :coffee: :australia:

I see you understand transformers. But I don't understand your questions. How do they relate to my problem?

Hi,
Why not use a MOSFET and do away with the complications of a driver circuit between the controller and the transformer primary.

If you want some isolation, use an opto coupler in the MOSFET gate circuit.

Tom.... :smiley: :+1: :coffee: :australia:
PS. I seem to be suggesting optos a lot lately... weird!!

180 phase shift and reversed complimetary means the same. And I needed exactly 180 phase shift fixed.
They are probably aligned by some internal mechanism. I selected the center aligned waveform and this probably fixes it. This mode is called "Phase Correct PWM Mode" in the manual.

What manual?

Tom.... :smiley: :+1: :coffee: :australia:
PS. I'm off to bed, 2:00am here... I need my beauty sleep... :smile: