Control your output voltage - owntech-foundation/Tutorials GitHub Wiki
Objectives
The goal of this tutorial is to use the TWIST as a buck converter in closed-loop. We will implement a PI controller that tracks the reference voltage. We will start from the previous tutorial.
To better represent what will be done, the image below shows the control of one single leg. It is possible to see that the output low-side voltage measurement is used by the power converter to drive its PWM value.
Simplified representation of a power converter showing its power switches, filter and control loop. This control will be implemented in this tutorial.
In the TWIST converter the legs are interleaved, meaning they are controlled in parallel. You can find the TWIST current implementation here.
Required hardware
- TWIST
v_1_1_2
or later - STLinkV3
- PC 64-bits (windows or linux)
- DC power supply (48 V, 2 A)
- Resistive load
- Oscilloscope or multimeter
Required software
- Git
- Visual Studio Code with PlatformIO (see Blinky tutorial)
- Visual Studio Code with Source Control extension (see OwnPlot tutorial)
- OwnPlot (see SerialPlot tutorial)
Create the project
- We will create the project by copying the
buck-2
tutorial in a new branch, that will be calledbuck_3
. - In the side menu, click on the Source Control button, Which will open a new menu . You can see that the only changes on your code are located at the main.cpp file. We are going to commit these changes as to keep track of our progress.
- Click on the
+
button on the right side of themain.cpp
file to "stage" the changes. You can add a comment to explain what these changes are useful for. It is good practice to describe your changes when you commit them in git. Click on the+ Commit
button.
Staged changes for the
buck-2
tutorial
- Click on the
buck-2
branch on the bottom left of the screen and create a new branch called "buck-3".
Enter the name of your new branch
buck-3
- In the bottom menu, check that you are now in the
buck-3
branch.
Check the bottom of your screen to verify in which branch you are
Step-by-step implementation
- Configure the libraries and Define the variables
In src/main.cpp
, in the section OWNTECH DRIVERS
, include the following library. It will activate the code for PID control which will be used for the voltage mode.
//-------------OWNTECH DRIVERS-------------------
# include "opalib_control_pid.h"
Go to the owntech.ini
file under the main.cpp
file and decomment line 18. As shown in the image below. It will automatically download the pid library that will be used in this tutorial.
In src/main.cpp
, in the section USER VARIABLE DECLARATIONS
, add the voltage reference, a counter for the step applying a step on the reference and the PID parameteres. Keep all the previous variables.
//--------------USER VARIABLES DECLARATIONS----------------------
static float32_t voltage_reference = 10; //voltage reference (app task)
static int cpt_step = 0; //counter for voltage reference (app task)
//static float32_t kp = 0.000215;
//static float32_t ki = 2.86;
//static float32_t kd = 0.0;
- Configure the hardware peripherals
In src/main.cpp
, in the setup_hardware()
function, do not modify anything.
- Configure the software scheduling
In src/main.cpp
, in the function setup_software()
, initialize the pid controller, the data acquisition and all the scheduling functions. The pid control will now be available through a series of function calls.
void setup_software()
{
// opalib_control_init_interleaved_pid(kp, ki, kd, control_task_period);
application_task_number = scheduling.defineAsynchronousTask(loop_application_task);
communication_task_number = scheduling.defineAsynchronousTask(loop_communication_task);
scheduling.defineUninterruptibleSynchronousTask(&loop_control_task, control_task_period);
scheduling.startAsynchronousTask(application_task_number);
scheduling.startAsynchronousTask(communication_task_number);
scheduling.startUninterruptibleSynchronousTask();
}
- Loop communication task
In the communication task you will change the commands to enable the closed-loop buck mode. To do so you will create a new mode called POWERMODE
.
Find the enum serial_interface_menu_mode
. Add the following code to declare a new mode called BUCKMODE.
enum serial_interface_menu_mode //LIST OF POSSIBLE MODES FOR THE OWNTECH CONVERTER
{
IDLEMODE =0,
SERIALMODE,
POWERMODE,
BUCKMODE,
};
In src/main.cpp
, in the function loop_communication_task()
, add the following code. This code listens to the Serial port. Depending on the character it has received, it switches between IDLEMODE, POWERMODE and BUCKMODE.
void loop_communication_task()
{
received_serial_char = console_getchar();
switch (received_serial_char) {
case 'h':
//----------SERIAL INTERFACE MENU-----------------------
printk(" ________________________________________\n");
printk("| ------- MENU --------- |\n");
printk("| press i : idle mode |\n");
printk("| press s : serial mode |\n");
printk("| press p : power mode |\n");
printk("| press u : duty cycle UP |\n");
printk("| press d : duty cycle DOWN |\n");
printk("| press b : closed-loop buck mode |\n");
printk("|________________________________________|\n\n");
//------------------------------------------------------
break;
case 'i':
printk("idle mode\n");
mode = IDLEMODE;
break;
case 's':
printk("serial mode\n");
mode = SERIALMODE;
break;
case 'p':
printk("power mode\n");
mode = POWERMODE;
pwm_enable = true;
break;
case 'u':
printk("duty cycle UP: %f\n", duty_cycle);
duty_cycle = duty_cycle + duty_cycle_step;
if(duty_cycle>1.0) duty_cycle = 1.0;
break;
case 'd':
printk("duty cycle DOWN: %f\n", duty_cycle);
duty_cycle = duty_cycle - duty_cycle_step;
if(duty_cycle<0.0) duty_cycle = 0.0;
break;
case 'b':
printk("closed-loop buck mode\n");
mode = BUCKMODE;
break;
default:
break;
}
}
- Define the application task
In src/main.cpp
, in the function loop_application_task()
, add the following code. The IDLEMODE
stops the power flow, the transmission of data and turns the LED1 off. The SERIALMODE
only turns on the LED1. The POWERMODE
turns the LED1 is ON and prints the values of duty_cycle
on the Serial Monitor. In BUCKMODE, the value of the voltage_reference
will change periodically creating a square wave. Now the data is constantly printed to the serial port.
void loop_application_task()
{
if(mode==IDLEMODE) {
hwConfig.setLedOff();
}else if(mode==SERIALMODE) {
hwConfig.setLedOn();
}else if(mode==POWERMODE) {
hwConfig.setLedOn();
}else if(mode==BUCKMODE) {
hwConfig.setLedOn();
if(cpt_step==10) voltage_reference = 15;
if(cpt_step==20) {
voltage_reference = 10;
cpt_step=0;
}
cpt_step ++;
}
printk("%f:", duty_cycle);
printk("%f:", Vhigh_value);
printk("%f:", V1_low_value);
printk("%f:", V2_low_value);
printk("%f:", ihigh_value);
printk("%f:", i1_low_value);
printk("%f:", i2_low_value);
printk("%f\n", voltage_reference);
scheduling.suspendCurrentTaskMs(100);
}
- Loop control task
In src/main.cpp
, in the function loop_control_task()
, add the following code. In BUCKMODE, the PID tracks the voltage_reference
by modifying the duty_cycle
.
void loop_control_task()
{
meas_data = dataAcquisition.getLatest(V_HIGH);
if(meas_data!=NO_VALUE) Vhigh_value = meas_data;
meas_data = dataAcquisition.getLatest(V1_LOW);
if(meas_data!=NO_VALUE) V1_low_value = meas_data;
meas_data = dataAcquisition.getLatest(V2_LOW);
if(meas_data!=NO_VALUE) V2_low_value= meas_data;
meas_data = dataAcquisition.getLatest(I_HIGH);
if(meas_data!=NO_VALUE) ihigh_value = meas_data;
meas_data = dataAcquisition.getLatest(I1_LOW);
if(meas_data!=NO_VALUE) i1_low_value = meas_data;
meas_data = dataAcquisition.getLatest(I2_LOW);
if(meas_data!=NO_VALUE) i2_low_value = meas_data;
if(mode==IDLEMODE || mode==SERIALMODE) {
pwm_enable = false;
power.stopAll();
}else if(mode==POWERMODE || mode==BUCKMODE) {
if(!pwm_enable) {
pwm_enable = true;
power.startAll();
}
if(mode==BUCKMODE) {
// this function compares the asked voltage to the output voltage in order to compute the duty cycle
duty_cycle = opalib_control_interleaved_pid_calculation(voltage_reference, V1_low_value);
}
//Sends the PWM to the switches
power.setAllDutyCycle(duty_cycle);
}
}
- Connect hardware
Now we will connect your TWIST board to the power supply and to the PC via the STLink.
- Connect the pins Vhigh and GND of the TWIST to the DC power supply (set its current limitation at 1 A).
- Connect the pins VLow1, Vlow2 and GND of the TWIST to a resistive load.
- Connect the micro-JTAG connector of the SPIN to the PC thanks to the STLinkV3.
- Switch ON the DC power supply. Choose a voltage between 0 and 48 V.
-
Build and Upload (+ ).
-
In the bottom toolbar, click on the Serial Monitor icon . Select it and press the
h
key. Press thep
button, you should see some data show on the terminal:
Expected outputs
- Press
i
to switch to IDLEMODE: stops the power and turns the LED1 OFF. - Press
p
to switch to POWERMODE: starts the power on open-loop and turns the LED1 ON. - Press
u
to increase theduty_cycle
. - Press
d
to decrease theduty_cycle
. - Press
b
to switch to BUCKMODE: tracks the reference voltage in closed loop and turns the LED1 ON.
OwnPlot Visualization
- First kill the Serial Monitor by clicking on the trash button on the right hand side of the window as shown in the image below.
- Launch OwnPlot. In Port tab, choose the STLinkV3. Click on the
Closed
button to open the port.
:warning: The Port might have a different name depending on your operating system.
- In OwnPlot, Settings tab, set the
# of channels
to 8.
- In OwnPlot, in the Chart tab, change the same of the datasets as shown below:
- In OwnPlot, in the Send tab, add the commands below by typing the command name and its associated character.
- When you click on the
Boost
buttion the output should track the voltage reference that switches between 10 V and 15 V.
- In OwnPlot, click on the
Idle
button corresponding to thei
command to switch to IDLEMODE and turn the LED1 OFF.
That’s it!
Contributors
- 2021.11.04: Romain Delpoux, Loic Queval, Adrien Prévost
- 2021.11.07: Luiz Villa, Antoine Boche
- 2022.01.24: Luiz Villa, Loic Queval
- 2022.02.01: Luiz Villa
- 2022.02.22: Luiz Villa
- 2022.03.13: Luiz Villa
- 2023.01.17: Luiz Villa
- 2023.07.10: Luiz Villa
- 2023.10.14: Mathilde Longuet