Archive for category Electronics Workbench
This is a staged recreation based on actual events; pay no attention to the Colpitts oscillators growing in the background.
Attaching goesinta and goesouta cables to the HP 8591 spectrum analyzer & tracking generator showed it worked just fine:
The reference level is -40 dBm, not the usual 0 dBm, due to the loss in those resistive pads. Unsurprisingly, the parallel resonance valley looks pretty ragged at -120 dBm = 1 nW = 7 µV.
Remove the jumper to put the capacitor in series:
The marker delta resolution surely isn’t 1 Hz, but 750 Hz should get us in the right ballpark.
Substituting a 72 Ω resistor, found by binary search rather than twiddling a pot:
Which gives us all the measurements:
- Fs = 3.57824 MHz
- Fc = Fs + 750 Hz = 3.57899 MHz
- Rm = 72 Ω
- C0 = 3.83 pF
- Cpar = 3.70 pF
Turn the crank and the crystal motional parameters pop out:
- Lm = 117 mH
- Cm = 17 fF
- Rm = 72 Ω
- Q = 36 k
Looks like a pretty good crystal to me!
A recent QEX article (Jan/Feb 2017, Crystal Measurement Parameters Simplified, Chuck Adams K7QO) suggested a simplified version of the K8IQY crystal parameter test fixture would work just as well for low-frequency quartz resonators:
The resistive pads eliminate the fussy toroids and their frequency dependence.
Tossing a handful of parts on a small proto board:
I found two absurdly long hunks of RG-174 coax with BNC connectors, so that’s how it connects to the outside world; sacrificing a short SMA jumper would reduce the clutter, but that’s in the nature of fine tuning. At the frequencies this fixture will see, coax properties don’t matter.
I can’t think of a better way to mount those AT26 cans than by soldering the wire leads directly to a pin header; pushing them under spring clips seems fraught with peril, not to mention excessive stray capacitance.
Measure the actual in-circuit capacitance for the 33 pF cap (shown as 39 pF in the schematic, it’s not critical), which worked out to 34.6 pF. That’s the external series capacitance Cx.
The overall procedure, slightly modified from the original:
- Measure C0 with resonator in capacitance fixture
- Solder resonator to pins
- Remove jumper to put capacitor Cx in series
- Find series-resonant peak = Fc
- Install jumper to short Cx
- Find series-resonant peak = Fs < Fc
- Remember the peak amplitude
- Unsolder crystal
- Install suitable trimpot = Rm in socket
- Adjust trimpot to produce same output amplitude
Crunch the numbers to get the crystal’s motional parameters:
Rm = trimpot resistance
Lm = 1 / [4 π2 (Fs + Fc) (Fs - Fc) (C0 + Cx)]
Cm = 1 / [(2 π Fs)2 Lm]
Q = [2 π Fs Lm] / Rm
Then you’re done!
Crystals (or resonators) in AT26 packages have vanishingly small capacitances, so I conjured a little fixture for my AADE L/C Meter IIB (*) that holds them securely under little fingers snipped from an EMI shield:
The finger on the right sits atop a snippet of rectangular brass tube so it need not bend so far.
The base is a snippet of double-sided PCB with copper tape soldered around the edges. I drilled the holes slightly oversize and soldered copper tape there, giving the top foil a direct connection to the terminals. The raggedy slot looks like it came from a hacksaw; no false advertising there.
The meter reports 6.5 pF of stray capacitance and nulls it to zero as usual. Without the fixture, it shows 2.5 pF.
With the crystal in that position, the meter measures Cpar, the parasitic capacitance from both terminals to the can, which should be (roughly) twice the capacitance from either terminal to the can.
Two more clips measure C0, the plate-to-plate capacitance:
The meter drive is about 200 mV at 700 kHz, far away from resonance. Assuming the resonator’s effective series resistance is 25 kΩ (tuning forks aren’t crystals!), it’s dissipating 1.5 µW (and less as the ESR goes up). That may be slightly hot for some resonators, but it’s surely survivable.
Some preliminary data on five 32.768 kHz crystals shows Cpar = 0.4 pF and C0 = 0.9 pF. I don’t trust those numbers very much, but they’re reproducible within 0.1-ish pF.
(*) Almost All Digital Electronics and its website vanished after the owner died; the meter continues to work fine. The cheap knockoffs flooding eBay and Amazon may get you close to the goal.
Thinking of a 60 kHz crystal filter front end for the WWVB receiver brought a little bag of 32.768 kHz crystals to the surface; I figured I could use them as crash test dummies while a bag of 60 kHz crystals travels around the planet. Come to find out they don’t behave quite like crystals and a bit of investigation shows the little cans contain tuning fork resonators, not crystal slabs.
I had to see that, so I grabbed the base of one in a pin vise:
I don’t know the part number for those resonators, but it’s something like AT26, where the “26” means a cylindrical can 2 mm OD and 6 mm long, more or less.
Notching the can at the chuck with a triangular file, then wiggling the can with needle-nose pliers, eventually broke it off:
The other side:
A look through the microscope show they’re transparent, with laser trim scars on the ends:
The “holes” are unplated quartz areas, clear as the finest glass.
Not what I was expecting to see, at all!
You’ll recall the LED atop the 21HB5A tube failed, shortly after replacing the bottom LED and rewiring the ersatz plate lead, which led me to rebuild the whole thing with SK6812 RGBW LEDs. So I printed all the plastic parts again, because the duodecar tube socket’s pin circle can fit into a hard drive platter’s unmodified 25 mm hole, then drilled another platter to suit:
The hole under the drill fits the 3.5 mm stereo socket for the ersatz plate lead, so it’s bigger than before.
I’ve switched from Arduino Pro Minis with a separate USB converter to Arduino Nanos with an on-board CH340 USB chip, because the fake FTDI chips on the converters are a continuing aggravation:
Adding those wire slots to the sockets definitely helps tidy things up; the wires no longer need a crude cable tie anchoring them to the socket mounting screws.
I wanted to drive the LEDs from the A7 pin, rather than the A3 pin I’d been using on the Pro Minis, to keep the wires closer together, but it turns out that A6 and A7 can’t become digital output pins. So I used A5, although I may come to regret the backward incompatibility.
In any event, the 21HB5A tube looks spiffy with its new LEDs in full effect:
I dialed the white LED PWM down to 32, making the colors somewhat pastel, rather than washed-out.
The Arduino source code as a GitHub Gist:
The failed WS2812 pixel remains defunct:
Attach scope probes to its data input and output pins (with the fixture face-down on the bench):
The output no longer comes from the Land of Digital Signals:
I immediately thought the broken bits occupied the first 24 bit times, when the WS2812 controller should be absorbing those bits from the incoming stream. The vertical cursors show the failed bits occupy 54 µs = 40-ish bit times at 800 kHz (or you can count them), so it’s worse than a simple logic failure.
A closer look:
At least for those bits, neither output transistor works well at all. On the other paw, the output shouldn’t even be enabled for the first 24 bits, so there’s that to consider.
Lo and behold, it also fails the Josh Sharpie Test:
You may recall it passed the leak test shortly before I assembled the test array a month ago. Evidently, just few days of operation suffices to wreck the seal, let air / moisture into the package, and kill the controller. Not a problem you’d find during a production-line test (assuming there is such a thing), but it should certain appear during the initial design & production qualification test phase (another assumption).
Weirdly, a day after taking that photo, the controller began working perfectly again and the LEDs look just like they should: there is no explaining that!
With the OLED wired up to the Raspberry Pi, the LUMA.OLED driver makes it surprisingly easy to slap text on the screen, at least after some obligatory fumbling around:
import textwrap from luma.oled.device import sh1106 from luma.core.serial import spi from luma.core.render import canvas from PIL import ImageFont … snippage … font1 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',14) font2 = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',11) wrapper = textwrap.TextWrapper(width=128//font2.getsize('n')) StatLine = 0 DataLine = 17 # allow for weird ascenders and accent marks LineSpace = 16 Contrast = 255 # OLED brightness setting serial = spi(device=0,port=0) device = sh1106(serial) device.contrast(Contrast)
The Python Imaging Library below the LUMA driver supports Truetype fonts that look much better than the default fonts. For these tiny displays, DejaVu Sans comes close enough to being a single-stroke (“stick”) font and, being proportional, packs more text into a fixed horizontal space.
textwrap library chops a string into lines of a specified length, which works great with a fixed-width font and not so well with a proportional font. I set the line length based on the width of a mid-size lowercase letter and hope for the best. In round numbers, each 128 pixel line can hold 20-ish characters of the size-11 (which might be the height in pixels) font.
It also understands hyphens and similar line-ending punctuation:
Felix Mendelssohn- Bartholdy - Piano Concerto No.01 in
It turns out whatever library routine blits characters into the bitmap has an off-by-one error that overwrites the leftmost column with the pixel columns that should be just off-screen on the right; it may also overwrite the topmost row with the bottommost row+1. I poked around a bit, couldn’t find the actual code amid the layers of inherited classes and methods and suchlike, and gave up: each line starts in pixel column 1, not 0. With
textwrap generally leaving the rightmost character in each line blank, the picket-fence error (almost) always overwrites the first column with dark pixels.
Display coordinates start at (0,0) in the upper left corner, but apparently the character origin corresponds to the box around an uppercase letter, with ascenders and diacritical marks extending (at least) one pixel above that. The blue area in these displays starts at (0,16), but having the ascenders poke into the yellow section is really, really conspicuous, so
DataLine Finagles the text down by one pixel. The value of
Linespace avoids collisions between descenders and ascenders in successive lines that you (well, I) wouldn’t expect with a spacing equal to the font height.
The display has a variable brightness setting, called “contrast” by the datasheet and driver, that determines the overall LED current (perhaps according to an exponential relationship, because an α appears in the tables). I tweak the value in
Contrast based on where the streamer lives, with 1 being perfectly suitable for a dark room and 255 for ordinary lighting.
The LUMA package includes a scrolling terminal emulator. With maybe four lines, tops, on that display (in a reasonable font, anyhow), what’s the point?
Instead, I homebrewed a panel with manual positioning:
def ShowStatus(L1=None,L2=None,L3='None'): with canvas(device) as screen: screen.text((1,StatLine),Media[CurrentKC][0:11], font=font1,fill='white') screen.text((127-(4*font1.getsize('M') + 2),StatLine),'Mute' if Muted else ' ', font=font1,fill='white') screen.text((1,DataLine),L1, font=font2,fill='white') screen.text((1,DataLine + 1*LineSpace),L2, font=font2,fill='white') screen.text((1,DataLine + 2*LineSpace),L3, font=font2,fill='white')
Yeah, those are global variables in the first line; feel free to object-orient it as you like.
The LUMA driver hands you a blank screen inside the
with … as …: context, whereupon you may draw as you see fit and the driver squirts the bitmap to the display at the end of the context. There’s apparently a way to set up a permanent canvas and update it at will, but this works well enough for now.
That means you (well, I) must mange those three lines by hand:
ShowStatus('Startup in ' + Location, 'Mixer: ' + MixerChannel + ' = ' + MixerVol, 'Contrast: ' + str(Contrast))
Chopping the track info string into lines goes like this:
if TrackName: info = wrapper.wrap(TrackName) ShowStatus(info, info if len(info) > 1 else '', info if len(info) > 2 else '') else: ShowStatus('No track info','','')
Something along the way ruins Unicode characters from the track info, converting them into unrelated (and generally accented) characters. They work fine when shipped through the logging interface, so it may be due to a font incompatibility or, more likely, my not bothering to work around Python 2’s string vs. byte stream weirdness. Using Python 3 would be a Good Idea, but I’m unsure all the various & sundry libraries are compatible and unwilling to find out using programming as an experimental science.
The Python source code as a GitHub Gist: