This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

LAUNCHXL-F28069M: Help with Tuning using PID algorithm for a Project

Part Number: LAUNCHXL-F28069M
Other Parts Discussed in Thread: C2000WARE

Tool/software:

Hello, I need help with my senior design project. My senior Design Project goal is to create a PID load controller to provide power to a commercial building using a Battery Energy Storage System to discharge power to the building when it exceeds what the Utility grid can provide. My goal with the F28069M microcontroller is to use an algorithm to be able to tune the real power demand (column C) at a setpoint towards the average, which is 93.62KW. I looked towards serval codes that the DCL libary provides and I am trying to find a code that so I can be able to tune the values accuratley for a fast system response as well as to produce a PWM signal with a Duty cycle that corresponds to the tuned values. The code that I looked towards is the code provided below:

/* Example_F28069_PID.c
*
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
* ALL RIGHTS RESERVED
*
*/

// header files
#include "F2806x_Device.h"
#include "F2806x_Examples.h"
#include "F2806x_GlobalPrototypes.h"
#include "DCLF32.h"

// function prototypes
extern interrupt void control_Isr(void);

// global variables
long IdleLoopCount = 0;
long IsrCount = 0;
float rk = 0.25f;
float yk;
float lk;
float uk;
DCL_PID pid1 = PID_DEFAULTS;
float Duty;
float upperlim = 0.95f;
float lowerlim = 0.05f;
unsigned int clampactive;


/* main */
main()
{
/* initialise system */
InitSysCtrl(); // [F2806x_SysCtrl.c]
DINT; // disable interrupts
IER = 0x0000;
IFR = 0x0000;
InitPieCtrl(); // initialise PIE control registers [F2806x_PieCtrl.c]
InitPieVectTable(); // initialise PIE vector table [F2806x_PieVect.c]
EALLOW;
PieVectTable.ADCINT1 = &control_Isr; // [F28069_PID_cisr.c]
EDIS;

/* configure ePWM1 */
EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
EDIS;
InitEPwm(); // [F2806x_EPwm.c]
EPwm1Regs.TBCTL.bit.CTRMODE = 3; // freeze TB counter
EPwm1Regs.TBCTL.bit.PRDLD = 1; // immediate load
EPwm1Regs.TBCTL.bit.PHSEN = 0; // disable phase loading
EPwm1Regs.TBCTL.bit.SYNCOSEL = 3; // disable SYNCOUT signal
EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0; // TBCLK = SYSCLKOUT
EPwm1Regs.TBCTL.bit.CLKDIV = 0; // clock divider = /1
EPwm1Regs.TBCTL.bit.FREE_SOFT = 2; // free run on emulation suspend
EPwm1Regs.TBPRD = 0x2328; // set period for ePWM1 (0x2328 = 10kHz)
EPwm1Regs.TBPHS.all = 0; // time-base Phase Register
EPwm1Regs.TBCTR = 0; // time-base Counter Register
EPwm1Regs.ETSEL.bit.SOCAEN = 1; // enable SOC on A group
EPwm1Regs.ETSEL.bit.SOCASEL = 1; // select SOC from zero match
EPwm1Regs.ETPS.bit.SOCAPRD = 1; // generate pulse on 1st event
EPwm1Regs.CMPCTL.bit.SHDWAMODE = 0; // enable shadow mode
EPwm1Regs.CMPCTL.bit.LOADAMODE = 2; // reload on CTR=zero
EPwm1Regs.CMPA.half.CMPA = 0x0080; // set compare A value
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET; // HIGH on CMPA up match
EPwm1Regs.AQCTLA.bit.ZRO = AQ_CLEAR; // LOW on zero match
EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
EDIS;

/* configure ADC */
InitAdc(); // [F2806x_Adc.c]
EALLOW;
AdcRegs.ADCCTL1.bit.INTPULSEPOS = 0; // early interrupt generation
AdcRegs.INTSEL1N2.bit.INT1E = 1; // enabled ADCINT1
AdcRegs.INTSEL1N2.bit.INT1CONT = 0; // disable ADCINT1 continuous mode
AdcRegs.INTSEL1N2.bit.INT1SEL = 1; // setup EOC1 to trigger ADCINT1
AdcRegs.INTSEL1N2.bit.INT2E = 0; // enable ADCINT2
AdcRegs.INTSEL1N2.bit.INT2CONT = 0; // disable ADCINT1 continuous mode
AdcRegs.INTSEL1N2.bit.INT2SEL = 0; // setup EOC1 to trigger ADCINT2
AdcRegs.ADCSOC0CTL.bit.CHSEL = 0; // set SOC0 channel select to ADCINA0
AdcRegs.ADCSOC1CTL.bit.CHSEL = 8; // set SOC1 channel select to ADCINB0
AdcRegs.ADCSOC0CTL.bit.TRIGSEL = 5; // set SOC0 start trigger on EPWM1A, due to round-robin SOC0 converts first then SOC1
AdcRegs.ADCSOC1CTL.bit.TRIGSEL = 5; // set SOC1 start trigger on EPWM1A, due to round-robin SOC0 converts first then SOC1
AdcRegs.ADCSOC0CTL.bit.ACQPS = 6; // set SOC0 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
AdcRegs.ADCSOC1CTL.bit.ACQPS = 6; // set SOC1 S/H Window to 7 ADC Clock Cycles, (6 ACQPS plus 1)
EDIS;

/* configure GPIO */
InitGpio(); // [F2806x_Gpio.c]
EALLOW;
GpioCtrlRegs.GPBMUX1.bit.GPIO34 = 0; // GPIO34 = I/O pin
GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1; // GPIO34 = output
GpioDataRegs.GPBSET.bit.GPIO34 = 1; // GPIO34 = 1
GpioCtrlRegs.GPBMUX1.bit.GPIO39 = 0; // GPIO39 = I/O pin
GpioCtrlRegs.GPBDIR.bit.GPIO39 = 1; // GPIO39 = output
GpioDataRegs.GPBCLEAR.bit.GPIO39 = 1; // GPIO39 = 0
EDIS;

/* initialise controller variables */
pid1.Kp = 9.0f;
pid1.Ki = 0.015f;
pid1.Kd = 0.35f;
pid1.Kr = 1.0f;
pid1.c1 = 188.0296600613396f;
pid1.c2 = 0.880296600613396f;
pid1.d2 = 0.0f;
pid1.d3 = 0.0f;
pid1.i10 = 0.0f;
pid1.i14 = 1.0f;
pid1.Umax = 1.0f;
pid1.Umin = -1.0f;

rk = 0.25f; // initial value for control reference
lk = 1.0f; // control loop not saturated

/* enable interrupts */
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; // enable PIE INT 1.1 (ADCINT1) - [adcisr]
IER |= M_INT1; // enable core interrupt 1 (ADC) - [control_isr]
SetDBGIER(0x0001); // enable real-time debug interupts
EINT; // enable global interrupt mask

EALLOW;
EPwm1Regs.TBCTL.bit.CTRMODE = 0; // PWM1 timer: count up and start
EDIS;

/* idle loop */
while(1)
{
IdleLoopCount++; // increment loop counter
asm(" NOP");
} // while

} // main


/* control ISR: triggered by ADC EOC */
interrupt void control_Isr(void)
{
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;

// read ADC channel
yk = ((float) AdcResult.ADCRESULT0 - 2048.0f) / 2047.0f;

// run PID controller
uk = DCL_runPID_C4(&pid1, rk, yk, lk);

// external clamp for anti-windup reset
clampactive = DCL_runClamp_C1(&uk, upperlim, lowerlim);
lk = (clampactive == 0U) ? 1.0f : 0.0f;

// write u(k) to PWM
Duty = (uk / 2.0f + 0.5f) * (float) EPwm1Regs.TBPRD;
EPwm1Regs.CMPA.half.CMPA = (Uint16) Duty;

IsrCount++;
}


/* end of file */

I know this code is able to produce the PWM signal; however, I want a code that is able to provide kp, ki,kd values that are accurate and can correspond with the System. I know this question is very bland, but I am just really stuck and need some assistance with the controller cause I don't have as much experience with the controller or control systems. Attached below is the excel file with the real power demand column highlighted with the values I am trying to tune.

final data.xlsx

  • You can take a look at the Chapter 11 Tuning Regulators of the InstaSPIN-FOC and InstaSPIN-MOTION User's Guide for motor control as a reference.

    https://www.ti.com/lit/spruhj1 

    And also you may find more articles about PID on some of engineering website as below that can be implemented to calculated the gains here.

    https://realpars.com/pid-tuning/

  • Thanks for the videos they Really helped with my understanding in the code I am trying to make the code be able to display the rise time, settiling time, as well as the overshoot values that will be uptained while tuning my controller. I have been trying to work on it for a while, adding on to the previous code I tried using AI for assitance but I just keep running into a wall tyring to fix it attached below is the the code that I am struggling with adding on towards. 

    Example_F28069_PID.c
    *
    * Step-response metrics added:
    * - Rise time (10–90%)
    * - Percent overshoot
    * - Settling time (±5% by default with hold time)
    *
    * Console printing done in main loop when metrics are ready.
    */

    #include "F2806x_Device.h"
    #include "F2806x_Examples.h"
    #include "F2806x_GlobalPrototypes.h"
    #include "DCLF32.h"
    #include <math.h> // fabsf, fmaxf
    #include <stdio.h> // printf (enable semihosting / I/O redirection in CCS)

    /* function prototypes */
    extern interrupt void control_Isr(void);

    /* global variables */
    long IdleLoopCount = 0;
    long IsrCount = 0;
    float rk = 0.25f;
    float yk;
    float lk;
    float uk;
    DCL_PID pid1 = PID_DEFAULTS;
    float Duty;
    float upperlim = 0.95f;
    float lowerlim = 0.05f;
    unsigned int clampactive;

    /* --- METRICS: configuration/state --- */
    volatile float Ts = 0.0001f; // ISR sample time (10 kHz)
    volatile float tol = 0.05f; // ±5% settling band
    volatile unsigned long settle_hold_samples = 200U; // must remain in band this many samples

    volatile float last_rk = 0.25f;
    volatile unsigned int measuring = 0U;
    volatile unsigned long k_now = 0UL; // sample index since step
    volatile unsigned long k_t10 = 0UL; // time index at 10% level
    volatile unsigned long k_t90 = 0UL; // time index at 90% level
    volatile unsigned long settle_count = 0UL;

    volatile float y_start = 0.0f;
    volatile float y_final = 0.0f;
    volatile float amp = 0.0f;
    volatile float y10 = 0.0f;
    volatile float y90 = 0.0f;
    volatile float y_max = -1e30f;
    volatile float y_min = 1e30f;

    volatile unsigned int have_t10 = 0U;
    volatile unsigned int have_t90 = 0U;
    volatile unsigned int settled = 0U;

    volatile float rise_time_s = 0.0f;
    volatile float settling_time_s = 0.0f;
    volatile float overshoot_pct = 0.0f;

    /* print trigger (set in ISR, printed in main) */
    volatile unsigned int print_metrics = 0U;

    /* main */
    main()
    {
    /* initialise system */
    InitSysCtrl();
    DINT;
    IER = 0x0000;
    IFR = 0x0000;
    InitPieCtrl();
    InitPieVectTable();
    EALLOW;
    PieVectTable.ADCINT1 = &control_Isr;
    EDIS;

    /* configure ePWM1 */
    EALLOW;
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
    EDIS;
    InitEPwm();
    EPwm1Regs.TBCTL.bit.CTRMODE = 3;
    EPwm1Regs.TBCTL.bit.PRDLD = 1;
    EPwm1Regs.TBCTL.bit.PHSEN = 0;
    EPwm1Regs.TBCTL.bit.SYNCOSEL = 3;
    EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0;
    EPwm1Regs.TBCTL.bit.CLKDIV = 0;
    EPwm1Regs.TBCTL.bit.FREE_SOFT = 2;
    EPwm1Regs.TBPRD = 0x2328; // 10 kHz
    EPwm1Regs.TBPHS.all = 0;
    EPwm1Regs.TBCTR = 0;
    EPwm1Regs.ETSEL.bit.SOCAEN = 1;
    EPwm1Regs.ETSEL.bit.SOCASEL = 1;
    EPwm1Regs.ETPS.bit.SOCAPRD = 1;
    EPwm1Regs.CMPCTL.bit.SHDWAMODE = 0;
    EPwm1Regs.CMPCTL.bit.LOADAMODE = 2;
    EPwm1Regs.CMPA.half.CMPA = 0x0080;
    EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;
    EPwm1Regs.AQCTLA.bit.ZRO = AQ_CLEAR;
    EALLOW;
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
    EDIS;

    /* configure ADC */
    InitAdc();
    EALLOW;
    AdcRegs.ADCCTL1.bit.INTPULSEPOS = 0;
    AdcRegs.INTSEL1N2.bit.INT1E = 1;
    AdcRegs.INTSEL1N2.bit.INT1CONT = 0;
    AdcRegs.INTSEL1N2.bit.INT1SEL = 1;
    AdcRegs.INTSEL1N2.bit.INT2E = 0;
    AdcRegs.INTSEL1N2.bit.INT2CONT = 0;
    AdcRegs.INTSEL1N2.bit.INT2SEL = 0;
    AdcRegs.ADCSOC0CTL.bit.CHSEL = 0;
    AdcRegs.ADCSOC1CTL.bit.CHSEL = 8;
    AdcRegs.ADCSOC0CTL.bit.TRIGSEL = 5;
    AdcRegs.ADCSOC1CTL.bit.TRIGSEL = 5;
    AdcRegs.ADCSOC0CTL.bit.ACQPS = 6;
    AdcRegs.ADCSOC1CTL.bit.ACQPS = 6;
    EDIS;

    /* configure GPIO */
    InitGpio();
    EALLOW;
    GpioCtrlRegs.GPBMUX1.bit.GPIO34 = 0;
    GpioCtrlRegs.GPBDIR.bit.GPIO34 = 1;
    GpioDataRegs.GPBSET.bit.GPIO34 = 1;
    GpioCtrlRegs.GPBMUX1.bit.GPIO39 = 0;
    GpioCtrlRegs.GPBDIR.bit.GPIO39 = 1;
    GpioDataRegs.GPBCLEAR.bit.GPIO39 = 1;
    EDIS;

    /* initialise controller variables */
    pid1.Kp = 9.0f;
    pid1.Ki = 0.015f;
    pid1.Kd = 0.35f;
    pid1.Kr = 1.0f;
    pid1.c1 = 188.0296600613396f;
    pid1.c2 = 0.880296600613396f;
    pid1.d2 = 0.0f;
    pid1.d3 = 0.0f;
    pid1.i10 = 0.0f;
    pid1.i14 = 1.0f;
    pid1.Umax = 1.0f;
    pid1.Umin = -1.0f;

    rk = 0.25f; // initial reference
    lk = 1.0f; // loop not saturated

    /* enable interrupts */
    PieCtrlRegs.PIEIER1.bit.INTx1 = 1;
    IER |= M_INT1;
    SetDBGIER(0x0001);
    EINT;

    EALLOW;
    EPwm1Regs.TBCTL.bit.CTRMODE = 0; // start PWM
    EDIS;

    /* Example: trigger a single step after startup delay (optional) */
    // for (volatile uint32_t i = 0; i < 1000000; i++) { asm(" NOP"); }
    // rk = 0.6f; // uncomment to cause a step automatically

    /* idle loop */
    while(1)
    {
    IdleLoopCount++;

    /* Print metrics once per step test (set in ISR) */
    if (print_metrics) {
    print_metrics = 0U;

    printf("\n--- Step Response Metrics ---\n");
    printf("Rise Time : %.6f s\n", rise_time_s);
    printf("Overshoot : %.2f %%\n", overshoot_pct);
    printf("Settling Time : %.6f s\n", settling_time_s);
    printf("-----------------------------\n");
    }

    asm(" NOP");
    }
    }

    /* control ISR: triggered by ADC EOC */
    interrupt void control_Isr(void)
    {
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
    AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;

    /* read output (ADC channel) */
    yk = ((float) AdcResult.ADCRESULT0 - 2048.0f) / 2047.0f;

    /* Detect reference step */
    if (fabsf(rk - last_rk) > 1e-6f) {
    measuring = 1U;
    k_now = 0UL;
    k_t10 = 0UL;
    k_t90 = 0UL;
    have_t10 = 0U;
    have_t90 = 0U;
    settled = 0U;
    settle_count = 0UL;
    overshoot_pct= 0.0f;

    y_start = yk;
    y_final = rk;
    amp = y_final - y_start;

    y10 = y_start + 0.1f * amp;
    y90 = y_start + 0.9f * amp;

    y_max = -1e30f;
    y_min = 1e30f;

    last_rk = rk;
    }

    /* run PID */
    uk = DCL_runPID_C4(&pid1, rk, yk, lk);

    /* anti-windup clamp */
    clampactive = DCL_runClamp_C1(&uk, upperlim, lowerlim);
    lk = (clampactive == 0U) ? 1.0f : 0.0f;

    /* write control to PWM */
    Duty = (uk / 2.0f + 0.5f) * (float) EPwm1Regs.TBPRD;
    EPwm1Regs.CMPA.half.CMPA = (Uint16) Duty;

    /* Metrics tracking */
    if (measuring) {
    k_now++;

    /* extrema for overshoot perspective (optional) */
    if (yk > y_max) y_max = yk;
    if (yk < y_min) y_min = yk;

    /* 10% and 90% crossings (works for up/down steps) */
    if (!have_t10) {
    if ((amp >= 0.0f && yk >= y10) || (amp < 0.0f && yk <= y10)) {
    k_t10 = k_now;
    have_t10 = 1U;
    }
    }
    if (!have_t90) {
    if ((amp >= 0.0f && yk >= y90) || (amp < 0.0f && yk <= y90)) {
    k_t90 = k_now;
    have_t90 = 1U;
    if (have_t10) {
    rise_time_s = ((float)(k_t90 - k_t10)) * Ts;
    }
    }
    }

    /* percent overshoot relative to final value */
    if (fabsf(amp) > 1e-12f) {
    if (amp >= 0.0f && yk > y_final) {
    float os = (yk - y_final) / fabsf(amp) * 100.0f;
    if (os > overshoot_pct) overshoot_pct = os;
    } else if (amp < 0.0f && yk < y_final) {
    float os = (y_final - yk) / fabsf(amp) * 100.0f;
    if (os > overshoot_pct) overshoot_pct = os;
    }
    }

    /* settling time: must stay within band for 'settle_hold_samples' */
    if (!settled && fabsf(yk - y_final) <= tol * fabsf(amp)) {
    settle_count++;
    if (settle_count >= settle_hold_samples) {
    settling_time_s = ((float)(k_now - settle_hold_samples + 1UL)) * Ts;
    settled = 1U;
    print_metrics = 1U; // request print in main loop
    // measuring = 0U; // optionally stop tracking after settle
    }
    } else if (fabsf(yk - y_final) > tol * fabsf(amp)) {
    settle_count = 0UL;
    }
    }

    IsrCount++;
    }

    /* end of file */

  • You can refer to the application note as mentioned above. If you want to implement the DCL code, you can take a look at the guide under the folder in MCSDK. Sorry, we can't help to test your own code.

    C:\ti\c2000\C2000Ware_6_00_00_00\libraries\control\DCL\c28\docs