Closed Loop with 2 PWM Signals 180 Degree Apart

I am trying to make a signal closed loop for an interleaved boost converter. A few weeks ago, with the help of the community, I got the PWM signals sorted out.

Now I am trying to make the system closed loop. Output pins are 9 and 10, output voltage from the converter is on A0, and input voltage to the converter is on pin A1, frequency is 32kHz.

I have the PID closed loop (PID_v1.h) working with a simple boost converter on pin 6 but can’t work out how to get the outputs on pin 9 and 10. On my scope the duty cycles are not responding to anything and show 0%.

I have included the code I have tried, and a picture of the duty cycle required at 60%.

#include "TimerHelpers.h"
 
#include <PID_v1.h>

int pwmPin1 = 9; 
int pwmPin2 = 10;
double analogPin0 = A0;
double analogPin1 = A1;
double Out = 0;
double Involtage = 0;

double Setpoint, Input, Output;
double Kp=0.4, Ki=0, Kd=0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup() {
  pinMode(pwmPin1, OUTPUT);
  pinMode(pwmPin2, OUTPUT);

  TCCR1A = 0;        // reset timer 1
  TCCR1B = 0;
  
  Timer1::setMode (8, Timer1::PRESCALE_1, Timer1::CLEAR_A_ON_COMPARE | Timer1::SET_B_ON_COMPARE);
  ICR1 = 250; // 250 -> 32kHz w PRESCALE_1 Sets frequency with prescaler as f=F_CPU/PRESCALE/ICR/2
  
  TCNT1 = 0;         // reset counter
  OCR1A =  ICR1 / 4;     // compare A register value
  OCR1B =  ICR1 - OCR1A;   // compliment and invert
  
  Serial.begin(115200);
  analogWrite(pwmPin1, 120);

  //initialize the variables we're linked to
  Input = analogRead(analogPin0);
  Setpoint = 200; // 175= 10v 220=12V  

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0,220);
}

void loop() {

  Out = analogRead(analogPin0);// read the output voltage pin
  Involtage = analogRead(analogPin1);// read the input voltage pin
    
  myPID.Compute();
  analogWrite(pwmPin1, Output);
  analogWrite(pwmPin2, Output);
  
  OCR1A = ICR1 * 1UL * A0/1023; // duty cycle
  OCR1B = ICR1 - OCR1A; // complement for inversion

 Serial.print ("input volts    "); 
 Serial.print ((Involtage * (5.0/1023)) * (12.3));// 12.3 for resistors
 Serial.print("  ");
 Serial.print (Involtage);
 
 Serial.print("    output volts   ");
 Serial.print ((Out * (5.0/1023)) * (12.9));// 12.9 for resistors
 Serial.print("  ");
 Serial.println (Out); 
}

[60% duty cycle.pdf|attachment](upload://nCyz8QwHeD6pwAwobonh7BxaEld.pdf) (26.7 KB)

I think your analogWrite and your timer manipulations are interfering with one another. Once you've got the WGM turned on at the pins in setup, all you need to do is set your OCR1A and OCR1B registers to get the duty cycle. Leave analogWrite out of it.

1 Like

Why are you doing these?

Since you are controlling the pwms with the low-level registers, you should avoid the higher level functions and calculate OCR1A and OCR1B directly.

1 Like

When I had the feedback working on pin 6 I used:

myPID.Compute();
analogWrite(pwmPin, Output);

So I thought I needed those to write to the two PWM pins.

I have just tried the code without,

analogWrite(pwmPin1, Output);
analogWrite(pwmPin2, Output);

and the result is still the same. No effect on the output PWM signals.

OK, what do you intend these lines to do?

Isn't A0 a constant?

void setup() {
  Serial.begin(115200);
  Serial.println(A0);
  Serial.println(250*1UL*A0/1023);
}
void loop() {
}

You are correct A0 is a constant and shouldn’t be there.
To be honest I tried to stitch together the code from 3 files to produce this file, so I am not entirely sure why I have added the line which refers to A0.

I think you want to vary the duty cycle, and those lines are key, but it is unclear if you want to vary it based on the PID's Output or an analogRead(A0).

I remember this thread:

and

The duty cycle needs to respond from the circuit output voltage on pin A0. This will adjust the duty cycle to keep it at the setpoint,

 Input = analogRead(analogPin0);
  Setpoint = 200; // 175= 10v 220=12V  

So the information flow is something like this?

A0 --> analogRead(A0) --> Input -->PID --> Output --> OCR1A -> PWM

I think you might need to rework this line:

... to use Output somehow.

Yes, the reading from A0 will automatically adjust the duty cycle based on the setpoint with a PID controller. The duty cycle will be on timer 1 which will control pins 9 and 10.

Then that line should use Output instead of A0? Right?

I’d set the PID output limit to match the ICR1 range and then use Output directly with something like these completely untested snippets:

void Setup(){
  ...
  myPid.SetOutputLimits(0,ICR1);
  ...  
}

void loop(){

 ...
  if(myPID.Compute(){
    OCR1A = Output; // duty cycle
    OCR1B = ICR1 - OCR1A; // complement for inversion
     ...

  }
 ...
}

I think that here they implemented it, an interleaved boost converter controlled with an Arduino Nano. It is shown at the bottom:

I don't know if the source code is available.

Here's a more complete version, tested in the Wokwi simulation setup above:

#include "TimerHelpers.h" // https://www.gammon.com.au/forum/?id=11504
// for https://forum.arduino.cc/t/two-pwm-signals-on-pins-9-and-10-with-input-on-a1-signals-need-to-be-180-degrees-apart/1280072/4?u=davex

#include "TimerHelpers.h"

#include <PID_v1.h>

int pwmPin1 = 9;
int pwmPin2 = 10;
double analogPin0 = A0;
double analogPin1 = A1;
double Out = 0;
double Involtage = 0;

double Setpoint, Input, Output;
double Kp = 0.4, Ki = 0, Kd = 0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup() {
  pinMode(pwmPin1, OUTPUT);
  pinMode(pwmPin2, OUTPUT);

  TCCR1A = 0;        // reset timer 1
  TCCR1B = 0;

  Timer1::setMode (8, Timer1::PRESCALE_1, Timer1::CLEAR_A_ON_COMPARE | Timer1::SET_B_ON_COMPARE);
  ICR1 = 250; // 250 -> 32kHz w PRESCALE_1 Sets frequency with prescaler as f=F_CPU/PRESCALE/ICR/2

  TCNT1 = 0;         // reset counter
  OCR1A =  ICR1 / 4;     // compare A register value
  OCR1B =  ICR1 - OCR1A;   // compliment and invert

  Serial.begin(115200);
  analogWrite(pwmPin1, 120);

  //initialize the variables we're linked to
  Input = analogRead(analogPin0);
  Setpoint = 200; // 175= 10v 220=12V

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0, ICR1);
}

void loop() {

  Out = analogRead(analogPin0);// read the output voltage pin
  Involtage = analogRead(analogPin1);// read the input voltage pin
  Input = Out;
  if (myPID.Compute()) {

    OCR1A = Output; // duty cycle
    OCR1B = ICR1 - OCR1A; // complement for inversion
    if (0) {
      Serial.print ("input volts    ");
      Serial.print ((Involtage * (5.0 / 1023)) * (12.3)); // 12.3 for resistors
      Serial.print("  ");
      Serial.print (Involtage);

      Serial.print("    output volts   ");
      Serial.print ((Out * (5.0 / 1023)) * (12.9)); // 12.9 for resistors
      Serial.print("  ");
      Serial.println (Out);
    }
  }
  report();
}

void report() {
  static uint32_t last = 0;
  if (millis() - last >= 500) {
    last += 500;
    Serial.print("Input:"); Serial.print(Input);
    Serial.print(" Setpoint:"); Serial.print(Setpoint);
    Serial.print(" Output:"); Serial.print(Output);
    Serial.println();
  }
}



void reportTimer1(void) {
  Serial.print("TCCR1A:0b");
  Serial.println(TCCR1A, BIN);
  Serial.print("TCCR1B:0b");
  Serial.println(TCCR1B, BIN);
  Serial.print("ICR1:");
  Serial.println(ICR1);
  Serial.print("OCR1A:");
  Serial.println(OCR1A);
  Serial.print("OCR1B:");
  Serial.println(OCR1B);
  Serial.print("Duty:");
  Serial.println(OCR1A * 100.0 / ICR1, 3);
  Serial.print("Freq:");
  Serial.print(F_CPU / 256.0 / ICR1 / 2, 4);
  Serial.println("Hz\n");
}

Your code above didn't change the Input based on A0, and didn't make use of Output, so the PID would do nothing.

I tried the code in Post 14. The duty cycles to the two MOSFET’s don’t change at all as I change the input voltage, and I cannot set the output to a constant value with the Setpoint command.

I know the PID function works as I had a simple boost converter working with changing duty cycles to match a changing input voltage while keeping the output voltage constant.

Seems to work for me. I upped the prescaler to mer1::PRESCALE_8 to see the PWM better in the slower Wokwi-simulated scope in:

I just ran the Wokwi to have a look. The duty cycles look they are responding but the output should remain constant, at a given setpoint, with a changing input. The output from the simulation changes with a changing input.

Maybe we're confusing Input and Output. A PID's Input is the Output of the Plant/Process/System, and the Output of the PID is the Input of the Plant/Process/System.

Wokwi doesn't have a part for whatever the Plant/Process/System is that the Arduino would hook up to, so per:

I was using a manually-controlled Potentiometer to simulate the signal from the external Plant/Process/System that is fed into A0. which gets processed through the PID's kP, Ki, Kd into OCR1A/ICR1 duty cycle outputs on the two pins.

I haven't a clue what, for example, a 10% duty cycle on the MOSFETs input fed into the Plant/Process/System would produce as the Plant/Process/System output that the arduino could read on A0.

The only way a PID Output is constant is if the error between the PID Input and PID Setpoint is constant.

Yes, you are correct, the PID input is the output from the DC-DC converter. The PID output directly controls the two duty cycles to the switches in the DC-DC converter.

I am sending varying voltage signals to the converter, which then, the PID should keep the output from the converter at a constant voltage as set by the setpoint command.

The PID output is not constant as it needs to change the duty cycles to keep the circuit output constant with a variable input voltage to the circuit.

I have a simple boost converter responding correctly to this PID code. The output voltage of the circuit is stable at 12.5v with an input of 2v to 12v. On the scope I can see the duty cycle change as I change the input voltage to hold the output voltage constant.

#include <PID_v1.h>

int pwmPin = 6;
int ledPin = 13;
int analogPin = 0;
int val = 0;

unsigned long previousMillis = 0;        // will store last time
const long interval = 500;           // interval at which to delay

double Setpoint, Input, Output;
double Kp=.2, Ki=.4, Kd=0;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup() {
  pinMode(pwmPin,OUTPUT);
  pinMode(ledPin, OUTPUT); // onboard LED
  
  TCCR0B = (TCCR0B & 0b11111000) | 0x01; // 62KHz @see http://playground.arduino.cc/Main/TimerPWMCheatsheet
  analogWrite(pwmPin, 120);
  Serial.begin(9600);

  //initialize the variables we're linked to
  Input = analogRead(analogPin);
  Setpoint = 200; // 200 = 12v 

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(0,220);
}
void loop() {
    Input = analogRead(analogPin);    // read the input pin
    myPID.Compute();
    analogWrite(pwmPin, Output);


  // Blink the status LED
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    digitalWrite(ledPin, !digitalRead(ledPin));
    Serial.println(Input);
    Serial.println(Output);
    Serial.println("");
    
  }
}