Interrupts interfering with PWM Generation

adi1893

Member
Hi,

I am using a Teensy 4.1 for a project that requires a PI loop which decides the duty cycle of one PWM output. The PI loop has to be run at 25kHz while the frequency of the PWM output it controls will be at 10kHz. There will be another PWM output which is at a constant duty cycle at 250Hz

I am using three instances of IntervalTimers, one at 25kHz for the PI, one at 10kHz for the first PWM and one at 250Hz for the second PWM.

The issue I am facing is that even after reducing the Analog Read times by using the ADC library (even with Conversion and Sampling time set to FAST_SPEED) the PI interrupt takes around 8 microseconds to execute (around 4 microseconds for one Analog Input, using two at the moment but this may increase to 3 or 4 later in the same interrupt). If at the time the PWM output state has to be switched and the PI loop is executed, the PWM output state gets switched anywhere from 0 to 8 (sometimes 16) microseconds later than required and thus does not create a stable PWM waveform.

If there is a better way of writing this or optimizing/using other libraries or control methods or just a better approach to this I would be very happy for your help. If there is a better way of reading the analog inputs that may contribute as well. Code for both IntervalTimer Interrupts is mentioned below.

Below is the code for PWM generation of 10kHz. The dcTmrATime and dcTmrBTime values are the On and Off values off the PWM cycle (100 microseconds for 10kHz cycle)

Code:
// DC Bus PWM Interrupt Routine
void dcInt() {
  if (dcTmrA == HIGH){
    dcTmrA = LOW;  
    dcOutState = HIGH;    
    dcTmr.update(dcTmrBTime);
  }
  else {
    dcTmrA = HIGH;
    dcOutState = LOW;
    dcTmr.update(dcTmrATime);
  }
  digitalWriteFast(dcBusPin,dcOutState);
}

Below is the code for the PID calculations
A0 has a potentiometer connected for manipulating the Setpoint
A1 has a Current Transformer which is the Feedback of the PI loop

Code:
// PID 1 Interrupt Routine
void pidInt(){    
  //Get Analog Data and Scale
  anaVal = (double)map(analogRead(A0),0.0,1023.0,0.0,2000.0);   //0 to 2000 mA    - Setpoint
  ctV = (double)map(analogRead(A1),0.0,1023.0,0.0,3300.0);        //0 to 3300 mV    - Feedback (2500mV is 0 mA)
  dcCur = (double)map(ctV,2500.0, 3300.0, 0,8192.0);                  //2500 to 3300 mV is 0 to 8192 mA    - Convert CT Voltage to Current
  //PI Calculations
  //Error
  dcCurError = dcCurSP - dcCur;
  //Proportional
  dcCurProp = dcCurError * dcCurKp;
  //Integrator
  dcCurIntegrator = dcCurIntegrator + 0.5 * dcCurKi * dcCurSampleTime/1000000 * (dcCurError + dcCurPrevError);    //Sample Time Variable in microseconds
  //Anti-wind-up via integrator clamping
  if(dcCurIntegrator >= dcCurLimitIntMax) dcCurIntegrator = dcCurLimitIntMax;
  if(dcCurIntegrator <= dcCurLimitIntMin) dcCurIntegrator = dcCurLimitIntMin;
  //Summation of P and I
  dcCurOutput = dcCurProp + dcCurIntegrator;
  //Output Limits
  if(dcCurOutput >= dcCurLimitMax) dcCurOutput = dcCurLimitMax;
  if(dcCurOutput <= dcCurLimitMin) dcCurOutput = dcCurLimitMin;
  //values to carry forward
  dcCurPrevError = dcCurError;
  //Output to PWM time
  dcTmrATime = (int)dcCurOutput;    //0 to 100% output of PI control is same as 0 to 100 microseconds of PWM for 10kHz
  dcTmrBTime = 100 - dcTmrATime;
}
 
Last edited:
I didn't read your sketches well enough to completely understand all aspects of your application, so my suggestion is more general in nature, & hopefully not completely silly !! Would it be possible to run a single IntervalTimer at 50KHz, and use counting variables to only process the PI every other interrupt, and the PWM every 5th interrupt ?? Of course, proper precautions would need to be taken (volatile ?) since these counting variables are changing within an interrupt context.

Mark J Culross
KD5RXT
 
The PI loop has to be run at 25kHz while the frequency of the PWM output it controls will be at 10kHz
Why? I would want to avoid this very situation and ensure the same number of loops per output transition (ie 20 or 30kHz rather than 25kHz for the loop perhaps?). Sometimes you do this because the input is sampled more often than the output - arrange for the input sample rate to be an integer multiple of the output sample rate if you can.

One tip: if you can use phase-correct PWM you can update the output at twice the PWM frequency, reducing output latency.
 
Why? I would want to avoid this very situation and ensure the same number of loops per output transition (ie 20 or 30kHz rather than 25kHz for the loop perhaps?). Sometimes you do this because the input is sampled more often than the output - arrange for the input sample rate to be an integer multiple of the output sample rate if you can.

One tip: if you can use phase-correct PWM you can update the output at twice the PWM frequency, reducing output latency.

Hi MarkT,

This does make more sense. I will have the PID calculations and PWM duty cycle updating in the same loop at 20 or 30kHz.

I was previously switching the output for the PWM high/low manually through code and just found out that I can use the Teensy_PWM library for generating the PWM and just update the duty cycle at the end of the PID loop. This has taken care of the issue and the PWM is very stable now.

Thank you for the inputs!
 
Back
Top