Hi guys,
I am using the F28335 controlCARD I would like to implement a closed loop control for a simple buck converter. In open loop, I successfully generate the necessary PWM signals and I managed to get some ADC readings, which directly brings me to my question:
How and where is the ADC reading optimally implemented in a closed loop control?
I generate my PWM signals with the ePWM module (UP/DOWN count) and enter the ISR for the ePWM module twice in every switching period (i.e. when the counter hits PRD and 0). Within the ePWM ISR, I perform the ADC reading AND the PWM generation.
Going through the examples in controlSUITE, the ADC reading uses its own ISR, which is triggered by the ePWM module. This aspect confuses me since it is different from my approach.
Could you maybe elaborate on how a proper implementation of the ADC is done (does the ADC reading have to have its own ISR or is it just as fine to perform the ADC reading within the ePWM ISR?)
Thank you very much.
Below my current implementation for the F28335 DSP:
#include "DSP28x_Project.h" // Device Headerfile and Examples Include File #include <math.h> // Prototype statements for functions found within this file. void InitEPwm2(void); void InitEPwm6(void); void InitializeADC(void); __interrupt void epwm2_isr(void); // User defined long freq; double ADCgain; double PRD; #define RESULTS_BUFFER_SIZE 256 //buffer for storing conversion results Uint16 AdcaResults[RESULTS_BUFFER_SIZE]; Uint16 resultsIndex; volatile Uint16 bufferFull; void main(void) { // Step 1. Initialize System Control: // PLL, WatchDog, enable Peripheral Clocks InitSysCtrl(); // Set the switching PRD [kHz] in freq freq = 16; // Calculate the running phase: 2^32/(2*fsw)*50; -> 2^32/(16000*2)*50 = 6710886 PRD = 150000/(2*freq); ADCgain = PRD/4096; // Define ADCCLK clock frequency ( less than or equal to 25 MHz ) // Assuming InitSysCtrl() has set SYSCLKOUT to 150 MHz EALLOW; SysCtrlRegs.HISPCP.all = 3; EDIS; // Step 2. Initialize GPIO: // This example function is found in the F2837xS_Gpio.c file and // illustrates how to set the GPIO to its default state. InitGpio(); // For this case just init GPIO pins for ePWM1, ePWM2, ePWM3 // These functions are in the F2837xS_EPwm.c file InitEPwm2Gpio(); InitEPwm6Gpio(); InitAdc(); // For this example, init the ADC // Step 3. Clear all interrupts and initialize PIE vector table: // Disable CPU interrupts DINT; // Initialize the PIE control registers to their default state. // The default state is all PIE interrupts disabled and flags // are cleared. // This function is found in the F2837xS_PieCtrl.c file. InitPieCtrl(); // Disable CPU interrupts and clear all CPU interrupt flags: IER = 0x0000; IFR = 0x0000; // Initialize the PIE vector table with pointers to the shell Interrupt // Service Routines (ISR). // This will populate the entire table, even if the interrupt // is not used in this example. This is useful for debug purposes. // The shell ISR routines are found in F2837xS_DefaultIsr.c. // This function is found in F2837xS_PieVect.c. InitPieVectTable(); // Interrupts that are used in this example are re-mapped to // ISR functions found within this file. EALLOW; // This is needed to write to EALLOW protected registers PieVectTable.EPWM2_INT = &epwm2_isr; //function for ePWM2 interrupt EDIS; // This is needed to disable write to EALLOW protected registers // Step 4. Initialize the Device Peripherals: // For this example, only initialize the ePWM and ADC EALLOW; SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; EDIS; InitEPwm2(); InitEPwm6(); InitializeADC(); //Initialize results buffer for(resultsIndex = 0; resultsIndex < RESULTS_BUFFER_SIZE; resultsIndex++) { AdcaResults[resultsIndex] = 0; } resultsIndex = 0; bufferFull = 0; EALLOW; SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC =1; EDIS; // Enable CPU INT3 which is connected to EPWM1-3 INT: IER |= M_INT3; // Enable EPWM INTn in the PIE: Group 3 interrupt 1-3 PieCtrlRegs.PIEIER3.bit.INTx2 = 1; // Enable global Interrupts and higher priority real-time debug events: EINT; // Enable Global interrupt INTM ERTM; // Enable Global realtime interrupt DBGM // Step 6. IDLE loop. Just sit and loop forever (optional): //take conversions indefinitely in loop do { //wait while ePWM causes ADC conversions, which then cause interrupts, //which fill the results buffer, eventually setting the bufferFull //flag while(!bufferFull); bufferFull = 0; //clear the buffer full flag }while(1); } /* Interrupt service routine reading the output voltage and generating the PWM */ __interrupt void epwm2_isr(void) { AdcaResults[resultsIndex++] = AdcRegs.ADCRESULT0 >>4; if(RESULTS_BUFFER_SIZE <= resultsIndex) { resultsIndex = 0; bufferFull = 1; } // Calculates the duty cycles EPwm6Regs.CMPA.half.CMPA=AdcaResults[resultsIndex]*ADCgain; EPwm2Regs.CMPA.half.CMPA=0.5*PRD; // Clear INT flag for this timer EPwm2Regs.ETCLR.bit.INT = 1; // Acknowledge this interrupt to receive more interrupts from group 3 PieCtrlRegs.PIEACK.all = PIEACK_GROUP3; } void InitEPwm2() { EPwm2Regs.TBPRD = PRD; // Set timer period EPwm2Regs.TBPHS.half.TBPHS = 0x0000; // Phase is 0 EPwm2Regs.TBCTR = 0x0000; // Clear counter // Setup TBCLK EPwm2Regs.ETSEL.bit.SOCAEN = 1; //enable SOCA EPwm2Regs.ETSEL.bit.SOCASEL = 4; // Select SOC on up-count EPwm2Regs.ETPS.bit.SOCAPRD = 1; // Generate pulse on 1st event EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // Count up EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE; // Disable phase loading EPwm2Regs.TBCTL.bit.PRDLD = TB_SHADOW; EPwm2Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW; EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; EPwm2Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; // Clock ratio to SYSCLKOUT EPwm2Regs.TBCTL.bit.CLKDIV = TB_DIV1; // Slow just to observe on the // scope // Set actions EPwm2Regs.AQCTLA.bit.CAU = AQ_CLEAR; // Set PWM2A on Zero EPwm2Regs.AQCTLA.bit.CAD = AQ_SET; EPwm2Regs.AQCTLB.bit.CAU = AQ_SET; // Set PWM2A on Zero EPwm2Regs.AQCTLB.bit.CAD = AQ_CLEAR; // Active Low complementary PWMs - setup the deadband EPwm2Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm2Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; // EPwm2Regs.DBCTL.bit.IN_MODE = DBA_ALL; EPwm2Regs.DBRED = 100; EPwm2Regs.DBFED = 100; // Interrupt where we will modify the deadband EPwm2Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO; // Select INT on Zero event EPwm2Regs.ETSEL.bit.INTEN = 1; // Enable INT EPwm2Regs.ETPS.bit.INTPRD = ET_1ST; // Generate INT on 1st event } void InitEPwm6() { EPwm6Regs.TBPRD = PRD; // Set timer period EPwm6Regs.TBPHS.half.TBPHS = 0x0000; // Phase is 0 EPwm6Regs.TBCTR = 0x0000; // Clear counter // Setup TBCLK EPwm6Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // Count up EPwm6Regs.TBCTL.bit.PHSEN = TB_ENABLE; // Disable phase loading EPwm6Regs.TBCTL.bit.PRDLD = TB_SHADOW; EPwm6Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; EPwm6Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; EPwm6Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW; EPwm6Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; EPwm6Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; EPwm6Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; // Clock ratio to SYSCLKOUT EPwm6Regs.TBCTL.bit.CLKDIV = TB_DIV1; // Slow just to observe on the // scope // Set actions EPwm6Regs.AQCTLA.bit.CAU = AQ_CLEAR; // Set PWM2A on Zero EPwm6Regs.AQCTLA.bit.CAD = AQ_SET; EPwm6Regs.AQCTLB.bit.CAU = AQ_SET; // Set PWM2A on Zero EPwm6Regs.AQCTLB.bit.CAD = AQ_CLEAR; // Active Low complementary PWMs - setup the deadband EPwm6Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm6Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; // EPwm6Regs.DBCTL.bit.IN_MODE = DBA_ALL; EPwm6Regs.DBRED = 100; EPwm6Regs.DBFED = 100; } void InitializeADC() { // Configure ADC AdcRegs.ADCMAXCONV.all = 0x0001; // Setup 2 conv's on SEQ1 AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x0; // Setup ADCINA3 as 1st SEQ1 conv. AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x2; // Setup ADCINA2 as 2nd SEQ1 conv. AdcRegs.ADCTRL2.bit.EPWM_SOCA_SEQ1 = 1;// Enable SOCA from ePWM to start SEQ1 AdcRegs.ADCTRL2.bit.INT_ENA_SEQ1 = 1; // Enable SEQ1 interrupt (every EOS) }