Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Using this Bash script to allow many different file names:
#!/bin/sh
export GDFONTPATH="/usr/share/fonts/truetype/"
base=${1%%.*}
echo Base name: ${base}
ofile=${base}.png
echo Output file: ${ofile}
gnuplot << EOF
#set term x11
set term png font "arialbd.ttf" 18 size 950,600
set output "${ofile}"
set title "Peltier Test - Loop Tuning"
set key noautotitles
unset mouse
set bmargin 4
set grid xtics ytics
set xlabel "Time - sec"
#set format x "%4.0f"
#set xrange [5000:7500]
#set xtics 0,5
set mxtics 2
set ytics nomirror autofreq
set ylabel "Various"
set format y "%5.1f"
set yrange [-2:2]
#set mytics 2
#set y2label "PWM"
#set format y2 "%3.0f"
#set y2range [0:255]
#set y2tics 32
#set rmargin 9
set datafile separator "\t"
#set label 1 "HP + LP" at 0.25,-14 font "arialbd,14"
plot \
"$1" using (\$8/1000):4 with lines lt 3 title "Error" ,\
"$1" using (\$8/1000):6 with lines lt 4 title "Drive"
# "$1" using 4 with lines lt 3 title "Error" ,\
# "$1" using 6 with lines lt 4 title "Drive"
# "$1" using (\$8/1000):1 with lines lt 3 title "Setpoint" ,\
# "$1" using (\$8/1000):2 with lines lt 4 title "Temp C"
EOF
There’s quite some other cruft in there, but the first part I must remember is right up at the top, where the magic incantation
base=${1%%.*}
chops off the file extension. Of course, that doesn’t work worth beans when the file name has several periods scattered through it.
The other part is at the bottom, where various alternate lines for the plot command must live after the last valid parameter line: the octothorpe comment header doesn’t work inside a command!
As I feared, P control can’t push the platform into the deadband all by itself at high temperatures, so I rewrote the loop the way it should have been all along:
PWM=1 beyond a limit well beyond the deadband, set integral=0 to avoid windup
Proportional + integral control inside that limit
Not worrying about relay chatter
Holding PWM=1 until the PI loop kicks in ensures that the P control won’t lose traction along the way, but full throttle must give way to PI control outside the deadband to avoid a massive overshoot. Relay chatter could be a problem around room temperature where the heating/cooling threshold falls within the deadband, but that won’t shouldn’t be a problem in this application.
Without much tuning, the results looked like this:
PI-Loop-Temps
Each temperature plateau lasts 3 minutes, the steps are 10 °C, starting at 30 °C and going upward to 50 °C, then downward to 0 °C, and upward to 20 °C. These are screenshots from OpenOffice Calc, so the resolution isn’t all that great.
Two internal variables show what’s going on:
PI-Loop-ErrDrive
The blue trace is the temperature error (actual – setpoint: negative = too cold = more heat needed), the purple trace is the signed PWM drive (-1.0 = full heat, +1.0 = full cool) summed from the P and I terms.
Overlaying all the plateaus with their starting edges aligned on the left, then zooming in on the interesting part, shows the detailed timing:
PI-Loop-ErrDrive-Overlay
These X axis units are in samples = calls to the PI function, which happened about every 100 ms, which is roughly what the main loop will require for the MOSFET measurements.
The Peltier module just barely reaches 0 °C with a 14 °C ambient: the drive exceeds +1.0 (output PWM = 255) as the temperature gradually stabilized at 0 °C with the module at full throttle; it’s dissipating 15 W to pump the temperature down. The heatsink reached 20 °C, with a simple foam hat surrounding the Peltier module and aluminum MOSFET mount. Any power dissipation from a MOSFET would add heat inside the insulation, but a bit more attention to detail should make 0 °C workable.
On the high end, it looks like the module might barely reach 60 °C.
Increasing the power supply voltage to increase the Peltier current would extend the temperature range, although a concerted stack probe didn’t produce anything like an 8 V 5A supply in the Basement Laboratory Parts Warehouse. If one turns up I’ll give it a go.
There’s a bit of overshoot that might get tuned away by fiddling with the P gain or squelching the integral windup beyond the deadband. The temperature changes will be the most time-consuming part of the MOSFET measurement routine no matter what, so it probably doesn’t make much difference: just stall 45 s to get past most of the transient overshoot, then sample the temperature until it enters the deadband if it hasn’t already gotten there. Reducing the initial overshoot wouldn’t improve the overall time by much, anyway, as it’d just increase the time to enter the deadband. Given that the initial change takes maybe 30 seconds at full throttle, what’s the point?
The PI loop Arduino source code, with some cruft left over from the last attempt, and some tweaks left to do:
#define T_LIMIT 3.0 // delta for full PWM=1 action
#define T_ACCEPT 1.5 // delta for good data (must be > deadband)
#define T_DEADBAND 1.0 // delta for integral-only control
#define T_PGAIN (1.0 / T_LIMIT) // proportional control gain: PWM/degree
#define T_IGAIN 0.001 // integral control gain: PWM/degree*sample
#define sign(x) ((x>0.0)-(x<0.0)) // adapted from old Utility.h library
//-- Temperature control
// returns true for temperature within deadband
int SetPeltier(float TNow, float TSet) {
float TErr, TErrMag;
int TSign;
float PelDrive;
int EnableHeat,OldEnableHeat;
static float Integral;
int TZone;
int PWM;
int PWMSigned;
TErr = TNow - TSet; // what is the temperature error
TErrMag = abs(TErr); // ... magnitude
TSign = sign(TErr); // ... direction
if (TErrMag >= T_LIMIT) // beyond outer limit
TZone = 3;
else if (TErrMag >= T_DEADBAND) // beyond deadband
TZone = 2;
else if (TErrMag >= T_DEADBAND/2) // within deadband
TZone = 1;
else // pretty close to spot on
TZone = 0;
switch (TZone) {
case 3: // beyond outer limit
PelDrive = TSign; // drive hard: -1 heat +1 cool
Integral = 0.0; // no integration this far out
break;
case 2: // beyond deadband
case 1: // within deadband
case 0: // inner deadband
PelDrive = T_PGAIN*TErr + T_IGAIN*Integral; // use PI control
Integral += TErr; // integrate the offset
break;
default: // huh? should not happen...
PelDrive = 0.0;
break;
}
EnableHeat = (PelDrive > 0.0) ? LOW : HIGH; // need cooling or heating?
OldEnableHeat = digitalRead(PIN_ENABLE_HEAT); // where is the relay now?
if (OldEnableHeat != EnableHeat) { // change from heating to cooling?
analogWrite(PIN_SET_IPELTIER,0); // disable PWM to flip relay
digitalWrite(PIN_ENABLE_HEAT,EnableHeat);
delay(15); // relay operation + bounce
}
PWM = constrain(((abs(PelDrive) * AO_PEL_SCALE) + AO_PEL_OFFSET),0.0,255.0);
analogWrite(PIN_SET_IPELTIER,PWM);
if (true) {
PWMSigned = (EnableHeat == HIGH) ? -PWM : PWM;
Serial.print(TSet,1);
Serial.print("\t");
Serial.print(TNow,1);
Serial.print("\t");
Serial.print(TZone,DEC);
Serial.print("\t");
Serial.print(TErr);
Serial.print("\t");
Serial.print(Integral,3);
Serial.print("\t");
Serial.print(PelDrive,3);
Serial.print("\t");
Serial.print(PWMSigned,DEC);
Serial.print("\t");
Serial.print(NowTime - StartTime);
Serial.println();
}
return (TZone <= 1);
Without much tuning at all the Peltier module holds the MOSFET-under-test block within the ±1 °C deadband for heating to 30 °C:
Bringup Test – Igain 0.01
And it’s pretty close for cooling to 10 °C:
Bringup Test – Cool – Igain 0.01
The PWM and Integral traces refer to the right-hand Y axis scale, the rest to the left. The total elapsed time is a bit under 3 minutes, but it’s measured in samples because I’m not going to bother with a formal timebase for this thing.
The Basement Laboratory is around 14 °C right now, so cooling isn’t all that much of a problem.
The code doesn’t really run a PI loop: it switches from P control outside the deadband to I control inside, preloading the integral accumulator to maintain the PWM value at the inbound switchover. That sidesteps the whole integral windup problem, which seems like a Good Idea, but a quick test at 50 °C says the P control may not have enough moxie to reach the deadband and the I control seems overenthusiastic.
More fiddling is definitely in order.
So far, the machinery looks like this:
rDS Tester – Peltier Tests
The aluminum block toward the rear holds the MOSFET and the thermistor atop the Peltier module, all stuck on the black CPU cooler with the fan on the bottom. The various heatsinks are obviously scrounged from the heap and are much too large; some fine tuning is in order now that the temperature’s nailed down.
On the other end of the Peltier driver’s PWM pulse, the MOSFET turns on with a surprisingly lengthy time constant:
Peltier Drain – Turn-On
The upper trace shows the drain voltage drops to nearly 0 V as the transistor turns on, then rises to about 600 mV. The IRLZ14 spec says RDS is 200 mΩ max, so that’s in line with the actual 3.3 A through the Peltier module, and puts the dissipation at 2.2 W.
The lower trace is the gate drive, showing a small Miller effect. The Channel 2 scale readout is off by a factor of 10, as I forgot to tell the scope that I was using a 10x probe. It’s really 5 V/div, not 500 mV/div.
The cursors put the time constant at 1.3 µs. If the inductance is the 5.4 µH indicated by the turn-off resonance, then the total circuit resistance is nearly 4 Ω… which is obviously not the case, given the 5 V supply voltage and the 3.3 A current.
Looking at the Peltier module’s power supply at the board terminal reveals the true cause:
Peltier Supply Transient
The scale is 1 V/div with 0 V at the bottom of the screen , so the switching supply produces 5.2 V with no load and 4.6 V at about 3 A. It’s rated at 5 V and 3.7 A, so the Peltier current is right up near its limit.
The glitch when the MOSFET turns off shows that the supply can’t absorb much transient power in either direction, which is typical of switching supplies. In this day & age, there’s no bulk capacitance to smooth out line-frequency ripples from a full-bridge rectifier.
The total circuit resistance is about 1.8 Ω, figuring the Peltier module at 1.5 Ω, the MOSFET at 0.2 Ω, and everything else at 0.1 Ω. That says the actual current is around 2.6 A, although the fancy Tek Hall Effect probe I mooched from Eks puts it at almost exactly 3 A; I’d tend to trust the Tek probe’s opinion more than my sum of small numbers. With 4.6 V and 3 A, the total resistance is spot on 1.5 Ω. The Peltier module’s resistance is temperature sensitive, so a few tenths of an ohm variation isn’t entirely unexpected.
So that says the L/R time constant is (5.4 µH / 1.5 Ω) = 3.6 µs, which makes more sense: it’s entirely masked by the power supply transient.
A touch of bulk capacitance may be in order. To supply 3 A for 5 µs with 0.5 V droop:
C = IΔT/ΔV = 3•5x10-6/0.5 = 30 µF
Well, that’s not so bad after all… I’m sure I have a high-voltage cap along those lines.
There’s a reason the MOSFET tester has connectors for three separate supplies: I expected nasty transients from a high-current PWM load. One supply for the Peltier, another for the MOSFET-under-test’s drain, and a triple-output supply for the Arduino and analog circuitry.
The headset / phone switch in my ancient HelloDirect phone headset became increasingly intermittent and finally stopped switching at all, so I tore the thing apart. It has two snap latches on each side in addition to the single screw in the bottom:
HelloDirect headset interface – top interior
The 4PDT switch just to this side of the volume drum can’t be taken off the board without unsoldering all 12 terminals and two case anchors, so I just eased some DeOxit Red into the openings and vigorously exercised it. That seems to have done the trick.
I cleaned out a bunch of fuzz and a spider husk while the hood was up…
After measuring & fiddling around with all those capacitors, the rest of the board went together fairly easily:
GPS-HT Wouxun interface – brassboard
It’s difficult to test from the Basement Laboratory, although the tones and audio levels sound about right.
The next step: conjure up a box. That shape has nothing to recommend it, so I’m doodling an extrusion-like shell with endcaps that should work better and look nicer… but that’s behind some other stuff that must happen first.