This is an initial sketch of a simple PI controller using the dsPIC30F4011.
I hope to add in quite a bit more detail and explanation to accompany this in the coming days, but I thought I’d post the current code for the time being since someone is currently waiting to use it in a project.
What I’ve done so far is very closely modeled on this excellent example on Wikipedia. However, I removed the derivative term since I’m just creating a PI controller.
//
// main.c - PI control with a dsPIC30F4011
// Written by Ted Burke
// Last updated 11-6-2013
//
// This example program shows a simple way to perform PI control
// on a system such as a DC voltage converter. The controller
// output takes the form of two complementary PWM channels (with
// 2us dead time between them). The PWM outputs are OC1 (pin 23)
// and OC2 (pin 18). I'm currently just driving an RC low pass
// filter (R = 220 ohm, C = 1000 uF) with PWM from pin 23 and
// feeding the filtered voltage back to an analog input (AN1).
// The setpoint is determined by the voltage on another analog
// input (AN0). Bascially, the PI controller adjusts the PWM
// duty cycle to make AN1 match AN0. The PWM duty cycle is
// clamped to the range 0.2 to 0.8.
//
// The PWM period is 50us, so the PWM frequency is 20kHz.
// The dead time is set to 2us which is in the right ballpark
// for IGBT devices, assuming some kind of push-pull system.
// The coefficients Kp and Ki are currently just a complete
// guess - these should be tuned for the actual system.
// dt is the sample time, which is 50us since the controller
// calculation is repeated at the start of each PWM cycle.
//
#include <xc.h>
#include <libpic30.h>
// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, i.e. 30 MIPS
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin
unsigned int read_analog_channel(int channel);
int main()
{
// Define time values
int Tpwm = 1500; // PWM period = 1500 * Tcy = 50us (i.e. 20kHz)
int dead_time = 60; // 2us deadtime is in the right ballpark for IGBTs
int Ton = Tpwm - dead_time - dead_time; // total on time during each cycle
double duty_cycle = 0.5;
double setpoint, measured_value, error, output;
double previous_error = 0, integral = 0 ;
double dt = 0.00005, Kp = 0.001, Ki = 0.001;
// Make all port D pins outputs
TRISD = 0;
// Configure AN0-AN8 as analog inputs
ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON
// Configure timer 2 (default timer for output compare)
PR2 = Tpwm; // 20kHz PWM frequency
T2CONbits.TON = 1; // Enable timer 2
// Initialise OC channel 1 & 2 start and stop times
OC1R = 0;
OC1RS = (int)(duty_cycle * Ton);
OC2R = OC1RS + dead_time;
OC2RS = PR2 - dead_time;
// Set output compare mode for continuous pulses
OC1CONbits.OCM = 0b101;
OC2CONbits.OCM = 0b101;
while(1)
{
while(_T2IF == 0); // wait for start of next cycle
_T2IF = 0;
setpoint = read_analog_channel(0);
measured_value = read_analog_channel(1);
error = setpoint - measured_value;
integral = integral + error*dt;
output = Kp*error + Ki*integral;
previous_error = error;
duty_cycle = output;
if (duty_cycle > 0.8) duty_cycle = 0.8;
if (duty_cycle < 0.2) duty_cycle = 0.2;
OC1RS = (int)(duty_cycle * Ton);
OC2R = OC1RS + dead_time;
}
return 0;
}
// This function reads a single sample from the specified
// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}