Archive for category Software
Anker LC40 flashlights can use either one lithium 18650 cell or an adapter holding three AAA cells. I now prefer 18650 cells, but they’re nigh onto 4 mm smaller than the flashlight ID and rattle around something awful.
I can fix that:
Three new entries appear in the cell dimension table of my OpenSCAD inter-series battery adapter program:
NAME = 0; ID = 0; // for non-cell cylinders OD = 1; LENGTH = 2; Cells = [ ["AAAA",8.3,42.5], ["AAA",10.5,44.5], ["AA",14.5,50.5], ["C",26.2,50], ["D",34.2,61.5], ["A23",10.3,28.5], ["CR123A",17.0,34.5], ["18650",18.8,65.2], ["3xAAA",21.2,56.0], ["AnkerLC40",23.0,55.0] // Flashlight tube loose-fit for 3xAAA adapter ];
I took the opportunity of adding OpenSCAD Customizer comments, which means this now works:
The model looks about the same as before, although with a few more sides just for pretty:
That was easy …
While that’s happening, you compare the DDS output to a reference frequency on an oscilloscope:
The top trace (and scope trigger) is the GPS-locked 10 MHz reference, the lower trace is the AD9850 DDS output (not through the MAX4165 buffer amp, because bandwidth). If the frequencies aren’t identical, the DDS trace will crawl left or right with respect to the reference: leftward if the DDS frequency is too high, rightward if it’s too low. If the DDS frequency is way off, then the waveform may scamper or run, with the distinct possibility of aliasing on digital scopes; you have been warned.
The joystick acts as a bidirectional switch, rather than an analog input, with the loop determining the step increment and timing. The ad-hoc axis orientation lets you (well, me) push the joystick against the waveform crawl, which gradually slows down and stops when the offset value makes the DDS output match the reference.
The OLED displays the current status:
The lurid red glow along the bottom is lens flare from the amber LED showing the relay is turned on. The slightly dimmer characters across the middle of the display show how the refresh interacts with the camera shutter at 1/30 s exposure.
N.B.: Normally, you know the DDS clock oscillator frequency with some accuracy. Dividing that value into 232 (for the AD9850) gives you the delta-phase count / frequency ratio that converts a desired DDS output frequency into the delta-phase value telling the DDS to make it happen.
In this case, I want the output frequency to be exactly 10.000000 MHz, so I’m adjusting the oscillator frequency (nominal 125 MHz + offset), calculating the corresponding count-to-Hz ratio, multiplying the ratio by 10.000000 MHz, stuffing the ensuing count into the DDS, and eyeballing what happens. When the oscillator frequency variable matches the actual oscillator frequency, then the actual output will 10.000000 MHz and the ratio will be correct.
Got it? Took me a while.
Although the intent is to tune for best frequency match and move on, you (well, I) can use this to accumulate a table of frequency offset vs. temperature pairs, from which a (presumably simple) formula can be conjured to render this step unnecessary.
The Arduino source code as a GitHub Gist:
The mailing tube arrived with contents intact, although the USPS inlet scanning didn’t work and the tube pretty much teleported across several states without leaving any tracking data behind. The recipient suggested several modifications to the caps:
Review of user experience of tube end:
The ribs on the endcap are very good at holding the cap on, so much so that I had to use a prying implement to remove it, which cracked the flange.
Would consider less depth on the cap, and possibly another layer on the flange.
Some continuous process improvement (a.k.a OpenSCAD hackage) produced a swoopy threaded cap with thumb-and-finger grips:
The finger grips are what’s left after stepping a sphere out of the cap while rotating it around the middle:
That worked out surprisingly well, with the deep end providing enough of a vertical-ish surface to push against.
The two hex holes fit a pin wrench, because the grips twist only one way: outward. The wrench eliminates the need for a flange, as you can now adjust the cap insertion before slathering packing tape over the ends. Man, I loves me some good late binding action!
A three-start thread seemed like overkill, but was quick & easy. The “thread form” consists of square rods sunk into the cap perimeter, with one edge sticking out:
They’re 1.05 times longer than the cap perimeter facets to make their ends overlap, although they’re not tapered like the ones in the broom handle dingus, because it didn’t (seem to) make any difference to the model’s manifoldhood.
Not needing any endcaps right now, I built one for show-n-tell:
The OpenSCAD source code as a GitHub Gist:
A strip of NXP (née Philips plus Freescale, including the part of Motorola that didn’t become ON) LM75A I²C temperature sensors arrived from beyond the horizon. To see if they worked, I soldered thin wires directly to the SO-8 pins, entombed it in Kapton tape to prevent spitzensparken, and jammed it under the foam insulation atop the AD9850 DDS module:
This turns out to be easier than screwing around with thermistors, because the chip reports the temperature directly in Celcius with ⅛ °C resolution. Classic LM75 chips from National (now absorbed by TI) had ½ °C resolution, but the datasheet shows the bits have an easily extensible format:
Huh. Fixed-point data, split neatly on a byte boundary. Who’d’a thunk it?
There’s a standard Arduino library using, naturally enough, floating point numbers, but I already have big fixed point numbers lying around and, with the I²C hardware up & running from the X axis DAC and OLED display, this was straightforward:
Wire.requestFrom(LM75_ADDR,2); Temp.fx_32.high = Wire.read(); Temp.fx_32.low = (uint32_t)Wire.read() << 24; PrintFixedPtRounded(Buffer,Temp,3); u8x8.drawString(0,ln,"DDS C "); u8x8.drawString(16-strlen(Buffer),ln,Buffer); printf(",%s",Buffer); ln += 1;
The next-to-last line squirts the temperature through the serial port to make those nice plots.
Casually ignoring all I²C bus error conditions will eventually lead to heartache and confusion. In particular, the Basement Laboratory temperature must never fall below 0 °C, because I just plunk the two’s-complement temperature data into an unsigned fixed point number.
Which produces the next-to-bottom line:
Alas, the u8x8 font doesn’t include a degree symbol.
Given sufficient motivation, I can now calibrate the DDS output against the GPS-locked 10 MHz standard to get a (most likely) linear equation for the oscillator frequency offset as a function of temperature. The DDS module includes a comparator to square up its sine wave, so an XOR phase detector or something based on filtering the output of an analog switch might be feasible.
Faced with a need to send documents rolled up in a tube, rather than folded flat, I sawed off a suitable length of cardboard tube from the heap, then discovered a distinct lack of end caps.
Well, once again, it’s 3D printing to the rescue:
The small ribs probably don’t actually do anything, but seemed like a nice touch.
They’re somewhat less boring from the bottom:
The fancy spider supports that big flat top and provides some crush resistance. The flat flange should collect the edge of the packing tape wrapped around the ends.
A firm shove installs them, so the size worked out perfectly:
Add a wrap of tape to each end, affix the USPS label, and they went out with the next day’s mail, PETG hair and all.
The OpenSCAD source code as a GitHub Gist:
Improving the crystal tester’s (nonexistent) grounding requires a band of copper tape around the inside of the proto board holder. Rather than cut the tape lengthwise to fit the holder, a new one will be just tall enough:
While I was at it, I deleted the washer recesses, because those didn’t work out well, and fiddled the screw holes to put the inserts in from the bottom:
Although the overhang inside the holes will be ugly, I’ll epoxy the inserts flush with the bottom and nobody will ever know.
The copper tape now makes a tidy ground strap:
With a gap in the front to eliminate the obvious loop:
The OpenSCAD source code as a GitHub Gist:
The OLED display has a noticeable delay between writing the first (double-size) line of text and the last line, which seemed odd:
The top trace in this scope shot goes high while the code begins the display update, which involves converting the variable to strings, the characters to bitmaps, then writing the data to the display:
The bottom trace shows I²C bus activity pretty much blots up all the time, with very little required for the computations in between the display writes for each text line.
Near the leading edge of the top trace, the code computes the new delta phase value and the X axis DAC output corresponding to that frequency:
TestCount.fx_64 = MultiplyFixedPt(ScanFreq,CtPerHz); // compute DDS delta phase TestCount.fx_32.low = 0; // truncate count to integer TestFreq.fx_64 = MultiplyFixedPt(TestCount,HzPerCt); // compute actual frequency Temp.fx_64 = (DAC_MAX * (ScanFreq.fx_64 - ScanFrom.fx_64)) / ScanWidth.fx_32.high; XAxisValue = Temp.fx_32.high; WriteDDS(TestCount.fx_32.high); // set DDS to new frequency XAxisDAC.setVoltage(XAxisValue,DAC_WR); // and set X axis to match
The burst in the top trace shows the five SPI writes to the DDS (one pulse per byte, with the hardware handling the serialization) and the bottom trace shows four I²C bus writes to the DAC:
A bit more detail shows writing each I²C byte to the DAC requires nine clock pulses (8 data, 1 ack):
The I²C bus ticks along at 400 kHz, with each byte requiring 33.4 µs (including the mandatory downtime around each burst), so the DAC update requires about 100 µs. The MCP4725 datasheet suggests a three byte “fast mode” write, but there’s not much point in doing so for my simple needs.
The display ticks along at the same pace with far more data.
In round numbers, the entire display update hits 6 text lines (1 double-height + 4 single-height) × 16 characters / line × 64 pixels / character = 6144 pixels.
The first scope shot shows the update requires something close to 90 ms, which allows for 2700 bytes = 90 ms / 33.4 µs, the equivalent of 21 k pixels. The SH1106 hardware includes an internal address counter, so there’s no need to transfer an address with each byte; I’m not sure where the factor-of-two overhead goes.
In order to get a faster update, there’s a definite need for lazy screen updates: no writes when there’s no change.
This probably doesn’t matter, because I can’t watch much faster, but it’s good to know the fancy fixed-point arithmetic isn’t the limiting factor.