Improve perf of adc interrupt + retune

This commit is contained in:
cnlohr
2023-04-25 01:45:27 -04:00
parent db6911617a
commit 81c4a0a77e

View File

@@ -48,11 +48,11 @@ int PWM_MAXIMUM_DUTY = 48; //This is changed based on vdd.
//
// Honestly, this is MUCH more sophisticated than it needs to be! I was using
// a P-only loop for quite some time without any issues.
#define ERROR_P_TERM 3
//#define ERROR_D_TERM -2 // Derivative term not used.
#define I_SAT_MAX 4096 // I sat disabled.
#define I_SAT_MIN -2048 // I sat disabled.
#define ERROR_I_TERM -6
#define ERROR_P_TERM 2 // Actually 2^2
#define ERROR_D_TERM 2 // Actually 2^2
#define I_SAT_MAX (4096) // SAT * 2^(-ADC_IIR+ERROR_I_TERM) = Max impact to PWM
#define I_SAT_MIN (-2048)
#define ERROR_I_TERM -6 // Actually 2^-6
// We filter our ADC inputs because they are kind of noisy.
//
@@ -102,150 +102,142 @@ void ADC1_IRQHandler(void)
void ADC1_IRQHandler(void)
{
// If you want to see how long this functon takes to run, you can use a scope and
// then monitor pin D6 if you uncomment this and the bottom copy.
// GPIOD->BSHR = 1<<6;
// If you want to see how long this functon takes to run, you can use a
// scope and then monitor pin D6 if you uncomment this and the bottom copy.
GPIOD->BSHR = 1<<6;
uint32_t statr = ADC1->STATR;
// Acknowledge pending interrupts.
// This will always be ADC_JEOC, so we don't need to check.
ADC1->STATR = 0;
if( statr & ADC_JEOC )
{
// Acknowledge the interrupt.
// Acknowledge the interrupt.
// It is crucial that our ADC sample is ALWAYS ALIGNED to the PWM, that way
// any ripple and craziness that happens from the chopping of the flyback
// is completely filtered out because of where we are sampling.
// It is crucial that our ADC sample is ALWAYS ALIGNED to the PWM, that
// way any ripple and craziness that happens from the chopping of the
// flyback is completely filtered out because of where we are sampling.
// This performs a low-pass filter on our data, ADC1->RDATAR the sample
// rate, always. As a side note, the value of the IIR (but now it's
// 2^VDD_IIR bigger)
int adcraw = ADC1->RDATAR;
int newadc = adcraw + (lastadc - (lastadc>>ADC_IIR));
lastadc = newadc;
// This performs a low-pass filter on our data, ADC1->RDATAR
// the sample rate, always. As a side note, the value of the IIR
// (but now it's 2^VDD_IIR bigger)
int adcraw = ADC1->RDATAR;
int newadc = adcraw + (lastadc - (lastadc>>ADC_IIR));
lastadc = newadc;
int err = feedback_vdd - lastadc;
int err = feedback_vdd - lastadc;
static int integral;
static int lasterr;
int derivative = (err - lasterr);
lasterr = err;
integral += err;
static int integral;
// We asymmetrically allow the integral to saturate, to help prevent long-
// term oscillations.
integral = ( integral > ((I_SAT_MAX)<<ADC_IIR) ) ? ((I_SAT_MAX)<<ADC_IIR) : integral;
integral = ( integral < ((I_SAT_MIN)<<ADC_IIR) ) ? ((I_SAT_MIN)<<ADC_IIR) : integral;
// D term is not used.
//static int lasterr;
//int derivative = (err - lasterr);
//lasterr = err;
integral += err;
// This is the heart of the PID loop.
// General note about shifting: Be sure to combine your shifts.
// If you shift right, then left, you will lose bits of precision.
// These shifts turn into single left and right immediate shifts.
int plant =
(err << ( (-ADC_IIR) + (ERROR_P_TERM) )) +
(integral >> ( ADC_IIR - (ERROR_I_TERM) )) +
(derivative << ( (-ADC_IIR) + (ERROR_D_TERM) ) );
plant = ( plant > PWM_MAXIMUM_DUTY ) ? PWM_MAXIMUM_DUTY : plant;
plant = ( plant < 0 ) ? 0 : plant;
TIM1->CH2CVR = plant;
// We asymmetrically allow the integral to saturate, to help prevent long-
// term oscillations.
integral = ( integral > ((I_SAT_MAX)<<ADC_IIR) ) ? ((I_SAT_MAX)<<ADC_IIR) : integral;
integral = ( integral < ((I_SAT_MIN)<<ADC_IIR) ) ? ((I_SAT_MIN)<<ADC_IIR) : integral;
// Use injection channel data to read vref. This is needed because we
// measure all values WRT to VDD and GND. So we need to measure the vref
// a lot to make sure we know what value we are are targeting Ballparks
// (for unfiltered numbers) (Just as a note)
// 0xF0 / 240 for 5V input << lastrefvdd
// 0x175 / 373 for 3.3v input << lastrefvdd
// This is the heart of the PID loop.
// General note about shifting: Be sure to combine your shifts.
// If you shift right, then left, you will lose bits of precision.
int plant =
(err << ( (-ADC_IIR) + (ERROR_P_TERM) )) +
(integral >> ( ADC_IIR - (ERROR_I_TERM) )) +
0;//(derivative >> ( (ADC_IIR) - (ERROR_D_TERM) ) );
plant = ( plant > PWM_MAXIMUM_DUTY ) ? PWM_MAXIMUM_DUTY : plant;
plant = ( plant < 0 ) ? 0 : plant;
TIM1->CH2CVR = plant;
// Do an IIR low-pass filter on VDD. See IIR discussion above.
uint32_t vdd = lastrefvdd = ADC1->IDATAR1 + (lastrefvdd - (lastrefvdd>>VDD_IIR));
{
// Use injection channel data to read vref. This is needed because we
// measure all values WRT to VDD and GND. So we need to measure the
// vref a lot to make sure we know what value we are are targeting
// Ballparks (for unfiltered numbers) (Just as a note)
// 0xF0 / 240 for 5V input << lastrefvdd
// 0x175 / 373 for 3.3v input << lastrefvdd
#ifndef ENABLE_TUNING
// Do an IIR low-pass filter on VDD. See IIR discussion above.
uint32_t vdd = lastrefvdd = ADC1->IDATAR1 + (lastrefvdd - (lastrefvdd>>VDD_IIR));
// If we aren't enabling tuning, we can update max on time here. We want to
// limit on-time based on DC voltage on the flyback so that we can get
// close to (But not get into) saturation of the flyback transformer's core
//
// We can compute expected values, but experimenting is better.
// Transformer inductance is ~6uH.
// Our peak current is ~500mA
// The average voltage is ~4V
//
// 4V / .000006H = 0.5A / 666666A/s = 750nS but turns out this was
// pessemistic.
//
// Experimentation showed that the core of the transformer saturates in
// about 1uS at 5V and 1.4uS at 3.3v. More specifically the relationhip
// between our maximum on-time and vref-measured-by-vdd works out to about:
//
// max_on_time_slices = lastrefvdd / 4.44.
//
// There's a neat trick where you can divide by weird decimal divisors by
// adding and subtracing terms. We perform this trick here and below
//
// 1÷(1÷41÷641÷1281÷1024) is about equal to dividing by 4.43290
// It can be simplified it for our purposes as: 1÷(1÷41÷641÷128)
//
// You can arbitrarily add and subtract terms to get as closed to your
// desired target value as possbile.
//
// When we divide a value by powers-of-two, it becomes a bit shift.
//
// The bit shift and IIR adjustments can be made so that the compiler can
// optimize out the addition there.
//
// The following code actually
PWM_MAXIMUM_DUTY =
(lastrefvdd>>(2+VDD_IIR))
- (lastrefvdd>>(6+VDD_IIR))
- (lastrefvdd>>(7+VDD_IIR));
#endif
#ifndef ENABLE_TUNING
// target_feedback is in volts. 0..200 maps to the device voltage.
// lastrefvdd = 0xF0 for 5V input.
// lastrefvdd = 0x175 for 3.3v input.
//
// feedback_vdd = 408 for ~192V @ 5
// feedback_vdd = 680 for ~192V @ 3.3
//
// 408 = 192 * 240 / x = (192*240)/408 = 112.941176471
// 680 = 192 * 373 / x = (373*680)/192 = 105.317647059
//
// More tests showed this value across units is around 117.
//
// X This becomes our denominator.
// feedback_vdd = (current vdd measurement * target voltage) / 117
//
// Further testing identified that the denominator is almost exactly 117.
// We can perform a divison by 117 very quickly by
//
// feedback = numerator/128 + numerator/2048 + numerator/4096
//
// See note above about the constant division trick.
//
// Side-note:
//
// This is unintuitively slow becuase is because it uses a multiply. The
// CH32V003 does not natively have a multiply instruction, so this actually
// calls out to __mulsi3 in libgcc.a. As a note, it's time complexity is
// determined by the size of the right-hand value of multiply, which you
// should try to make the smaller value.
// If we aren't enabling tuning, we can update max on time here. We
// want to limit on-time based on DC voltage on the flyback so that
// we can get close to (But not get into) saturation of the flyback
// transformer's core.
//
// We can compute expected values, but experimenting is better.
// Transformer inductance is ~6uH.
// Our peak current is ~500mA
// The average voltage is ~4V
//
// 4V / .000006H = 0.5A / 666666A/s = 750nS but turns out this was
// pessemistic.
//
// Experimentation showed that the core of the transformer saturates
// in about 1uS at 5V and 1.4uS at 3.3v. More specifically the
// relationhip between our maximum on-time and vref-measured-by-vdd
// works out to about:
//
// max_on_time_slices = lastrefvdd / 4.44.
//
// There's a neat trick where you can divide by weird decimal divisors
// by adding and subtracing terms. We perform this trick here and below
//
// 1÷(1÷41÷641÷1281÷1024) is about equal to dividing by 4.43290
// It can be simplified it for our purposes as: 1÷(1÷41÷641÷128)
//
// You can arbitrarily add and subtract terms to get as closed to your
// desired target value as possbile.
//
// When we divide a value by powers-of-two, it becomes a bit shift.
//
// The bit shift and IIR adjustments can be made so that the compiler
// can optimize out the addition there.
//
// The following code actually
PWM_MAXIMUM_DUTY =
(lastrefvdd>>(2+VDD_IIR))
- (lastrefvdd>>(6+VDD_IIR))
- (lastrefvdd>>(7+VDD_IIR));
#endif
// target_feedback is in volts. 0..200 maps to the device voltage.
// lastrefvdd = 0xF0 for 5V input.
// lastrefvdd = 0x175 for 3.3v input.
//
// feedback_vdd = 408 for ~192V @ 5
// feedback_vdd = 680 for ~192V @ 3.3
//
// 408 = 192 * 240 / x = (192*240)/408 = 112.941176471
// 680 = 192 * 373 / x = (373*680)/192 = 105.317647059
//
// More tests showed this value across units is around 117.
//
// X This becomes our denominator.
// feedback_vdd = (current vdd measurement * target voltage) / 117
//
// Further testing identified that the denominator is almost
// exactly 117. We can perform a divison by 117 very quickly by
//
// feedback = numerator/128 + numerator/2048 + numerator/4096
//
// See note above about the constant division trick.
//
// Side-note:
// This is unintuitively slow becuase is because it uses a multiply.
// The CH32V003 does not natively have a multiply instruction, so this
// actually calls out to __mulsi3 in libgcc.a. As a note, it's time
// complexity is determined by the size of the right-hand value of
// multiply, which you should try to make the smaller value.
uint32_t numerator = (vdd * target_feedback);
feedback_vdd =
(numerator>>(7+VDD_IIR-ADC_IIR)) +
(numerator>>(11+VDD_IIR-ADC_IIR));
}
}
uint32_t numerator = (vdd * target_feedback);
feedback_vdd =
(numerator>>(7+VDD_IIR-ADC_IIR)) +
(numerator>>(11+VDD_IIR-ADC_IIR));
// Pet the watchdog. If we got here, things should be OK.
WatchdogPet();
// GPIOD->BSHR = (1<<(16+6));
GPIOD->BSHR = (1<<(16+6));
}
static void SetupTimer1()