Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Although the economic argument for producing custom cookie cutters may not be persuasive, the fact that you (well, I) can produce custom widgets certainly is. Most of the things I build and repair don’t require great mechanical strength or finicky dimensional precision, so a DIY 3D printer is exactly the right hammer for the job.
The Peltier assembly looked like this while I was epoxying everything together with JB Weld:
Peltier module – epoxy curing
The aluminum-case resistor held the heatsink at 105 °F to encourage the epoxy to cure in a finite amount of time.
The 40 mm square block is a squared-up piece of 1/2 inch aluminum plate (manual CNC on the Sherline, nothing fancy) with a pair of 6-32 tapped holes for the screws that will hold TO-220 transistors or the yet-to-be-built TO-92 adapter. The CPU heatsink got a pair of symmetric holes for the posts holding it to the acrylic base, but other than that it’s perfectly stock.
MOSFET thermal block – drilling
Then epoxy the thermistor brick to the middle of the block between the two screws, stick on some obligatory Kapton tape to prevent embarrassing short circuits, and add a foam collar around the Peltier module to insulate the block from the heatsink:
MOSFET thermal block
A square foam shako covers everything, held down with a random chunk o’ weighty stuff, to insulate the whole affair from the world at large.
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.
My old BOB Yak trailer mounts to the bike axle with stainless steel grenade pins, which works fine. After all these years, alas, the rubber straps securing the pins to the frame have rotted away. The original straps are nicely molded affairs:
BOB Yak – original pin strap
I snipped a large O-ring, deployed four small cable ties, and this ought to last for another decade:
BOB Yak – new pin strap
The strap in the first picture hadn’t quite broken, but the rubber was cracked and ready to snap. So I made a preemptive strike…
This one came out surprisingly well, apart from the total faceplant with that resistor. With any luck, it’ll measure MOSFET on-state drain resistance over temperature for an upcoming Circuit Cellar column; it’s a honkin’ big Arduino shield, of course.
I think I can epoxy the resistor kinda-sorta in the right spot without having to drill through the PCB into the traces. Maybe nobody will notice?
The traces came out fairly well, although I had to do both the top and bottom toner transfer step twice to get good adhesion. Sometimes it works, sometimes it doesn’t, and I can’t pin down any meaningful differences in the process.
And it really does have four distinct ground planes. The upper right carries 8 A PWM Peltier current, the lower right has 3 A drain current, the rectangle in the middle is the analog op-amp circuitry tied to the Analog common, and surrounding that is the usual Arduino bouncy digital ground stuff. The fact that Analog common merges with digital ground on the Arduino PCB is just the way it is…