Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Category: Software
General-purpose computers doing something specific
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);
It appears there are at least two different 10 W aluminum resistor sizes: the one used by Dale and the one used by everybody else. It’s either that or the EAGLE HS10 symbol is wrong…
Using those dimensions, here’s a part that more closely fits the resistors in my heap. EAGLE 6 uses an XML file format, so you can stuff some ASCII text into the appropriate sections of your custom.lbr file (or whatever).
The EAGLE package, which remains HS10 as in the resistor-power library, should produce something that looks like this:
EAGLE 10 W Resistor package
The XML code includes top-keepout rectangles under the body footprint:
It seems that a much older version of Eagle allowed device names along the lines of ELECTRET MIC that contained blanks and worked perfectly at the time. Since then, the rules changed to prohibit blanks, but the EAGLE 5.x series evidently allowed those names to exist as long as they weren’t used in the schematic or touched in the library editor. In 6.x, however, you can’t even load the library without triggering an error message.
Because 6.x won’t load the library, you can’t use the library editor to remove the blank.
Because the most recent version of 5.x kvetches about the blank, you can’t use the library editor to remove the blank.
Having only two offending device names, I figured I could use a hex editor to jam a hyphen in place of the blanks and be done with it. Come to find out that EAGLE (wisely) wraps a checksum around the binary library file to detect such changes and prevent the files from loading. I think that’s an excellent idea, even if it was inconvenient in this situation.
Fortunately, 6.x both complains about the problem and offers up a “text editor” window with the complete XML source code for the library that it converted from the 5.x binary format.
So:
Copy-and-paste the text into an editor that supports highlighted XML editing
A Circuit Cellar reader recommended the KEMET Spice calculator that lets you explore the Z / ESR / capacitance / inductance of their various capacitors:
Despite the fact that nobody bothers to crack your web passwords, as it’s easier for them to crack the entire server and scoop out everyone’s personally sensitive bits like so much caviar, all websites remind / require you to pick strong passwords. So, when I registered myself on a high-value website, I did what I always do: ask my password-generation program for a dollop of entropy.
It came up with something along the lines of:
Gmaz78fb'd]
You can see where this is going, right?
Pressing Submit (which always makes me whisper Inshallah with a bad accent) produced:
The mumble.com website is temporarily unavailable. Please try again later.
A friend asked me to scrub and rebuild an ancient IBM Thinkpad 760XD (there were good reasons for this task that aren’t relevant here), which led to a blast from the past:
Windows 98 Welcome
After Windows settled down from its obligatory reboots, installing the exceedingly complex MWave DSP drivers from three diskettes (!) produced this classic result:
Windows 98 – BSOD
Ordinarily, I’d suggest installing some flavor of Linux, but the 760XD’s BIOS can’t boot from either CD or USB, so you’d be forced to sneak the install files onto the hard drive, hand-craft a suitable boot diskette (!), and then perpetrate some serious fiddling around. That made even less sense than (re-)installing Windows 98.
However, given that exposing a fresh Windows 98 installation to the 2012 Internet would resemble tossing a duckling into a brush chipper, we agreed that this laptop’s next experience should be at an upcoming e-waste recycling event.
The next morning confronted me with this delightful reminder that nobody knows how to handle boot-time errors, not even on a 2011 PC:
Lenovo – USB Keyboard not found
The keyboard cable had gotten dislodged when the USB hub fell from its perch along the back edge of the desk. It’s fine now…