Posts Tagged Improvements

Vacuum Tube Lights: Duodecar Rebuild

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:

Duodecar disk drilling

Duodecar disk drilling

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:

21HB5A base - interior

21HB5A base – interior

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:

21HB5A with RBGBW LEDs - cyan violet phase

21HB5A with RBGBW LEDs – cyan violet phase

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:


, , ,

Leave a comment

Raspberry Pi Streaming Radio Player: OLED Display

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:

RPi OLED Display - Plenitude

RPi OLED Display – Plenitude

Connect the hardware, install the driver, then the setup goes like this:

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')[0])

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)

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.

The 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((127-(4*font1.getsize('M')[0] + 2),StatLine),'Mute' if Muted else ' ',

    screen.text((1,DataLine + 1*LineSpace),L2,
    screen.text((1,DataLine + 2*LineSpace),L3,

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)
             info[1] if len(info) > 1 else '',
             info[2] if len(info) > 2 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:



Raspberry Pi: OLED Display

These cute displays have barely enough dots for the job:

RPi OLED Display - Classical

RPi OLED Display – Classical

That’s a 0.96 inch = 24.4 mm OLED display, measured diagonally, with a breathtaking 8192 = 128×64 dots. It’s a binary display: on or off pixels, nothing in between. This is not a color display: what you see is what it does, with a two-pixel void between the yellow and blue sections.

The void is a physical separation that does not affect the display addressing: the yellow section has 16 rows, the blue section has 48. It’s your responsibility to keep things where they belong; a character descender from the yellow section will appear in the blue section.

They’re three bucks each, shipped halfway around the planet: search eBay / Amazon for oled 128x64 yellow. The all-blue and all-white versions do not have the two-pixel void. I have some white 1.3 inch versions on the way for those applications requiring 35% more visibility.

The SPI interface uses all seven wires, peeled from a premade 100 mm 40-pin cable with female pin connectors:

RPi OLED Display - Wiring

RPi OLED Display – Wiring

Other OLED versions have a four-wire I2C interface. The boards have option jumpers on the back, but the pin header along the edge will have 7 holes for SPI or 4 holes for I2C .

Caveat emptor for online buyers: the item picture(s) may not match the title or the description text. The low-end sellers carrying beach balls, cookware, MOSFETs, cheap consumer electronics, and OLEDs do not understand the tech on a small board that’s Just Another SKU among thousands.

For cables, search eBay or Amazon for ribbon dupont "female to female" 10cm. Amazon has sets of male-female, male-male, and female-female jumpers for ten bucks in various lengths. The insulation seems rather stiff and I may be forced to build better cables with fine wire inside PET braid.

The SPI interface soaks up a tidy block of pins on the RPi’s big header:

RPi OLED Display - RPi connector detail

RPi OLED Display – RPi connector detail

The LUMA-OLED Python driver doc gives a useful summary of those connections, herewith extracted for future reference:

  • 17 VCC – 3.3 V works for sure, 5 V might not
  • 18 DC – Data/Command
  • 19 D1 (“dee one”) – Data to display = MOSI
  • 20 GND
  • 21 not used, that’s the pin in the midst of the block
  • 22 RST – Reset
  • 23 D0 (“dee zero”) – clock to display = SCLK
  • 24 CS – Chip Select = CE0 (“cee ee zero”)

Pin 1 is in front on the left end of that picture, closest to the MicroSD card slot, and proceeds 1-2, 3-4, and so forth along the length of the connector: odds toward the CPU, evens toward the PCB edge.

The LUMA-OLD maintainter must have OLED boards with a slightly different SPI pinout than mine: VCC and GND are interchanged. Caveat emptor!

Obviously, it’s desperately in need of a cute little case, which is in the nature of fine tuning.




SK2812 RGBW LED: Test Fixture

[Edit: The SK2812 in the title and elsewhere should be SK6812. If I change the title, then all the other links break. So it goes.]

An envelope of RGBW LEDs, allegedly with SK6812 controllers, arrived from halfway around the planet:

SK2812RGBW LEDs - as received

SK2812RGBW LEDs – as received

The yellow phosphor sauce poured atop the blue LED on the left that makes it glow white leaves the upper loop of two wire bonds sticking out, but I can’t fault ’em for that. The overall build quality looks better than the ill-fated WS2812 LEDs, although it’s hard to tell by looking.

I conjured a test stand from the vasty digital deep by tweaking the WS2812 mount:

SK6812 LED Array Test Fixture - Slic3r preview

SK6812 LED Array Test Fixture – Slic3r preview

Wiring up a 5×5 panel went as before:

SK2812RGBW test fixture - rear

SK2812RGBW test fixture – rear

The array test code adds another pixel channel and runs another raised sine wave with another random period, accomplished without much hackage.

With the warm-white LED at full throttle (MaxPWM = 255), the panel tends toward the pallid end of HSV space:

SK2812RGBW test fixture - front - W PWM255

SK2812RGBW test fixture – front – W PWM255

Dialing the white MaxPWM back to 32 crisps things a bit:

SK2812RGBW test fixture - front - W PWM32

SK2812RGBW test fixture – front – W PWM32

Of course, the RGBW data stream isn’t compatible with the RGB data stream, so vacuum tubes with SK6812 chips require a slightly different driver and I can’t mix the two chips on a single tube.

The Arduino source code as a GitHub Gist:

, ,


ShopVac Hose Barb Adapter

A small ShopVac arrived with a ribbed hose carrying an absurdly long wand, so I conjured a barbed adapter with a much shorter tapered snout for the machine tools:

Vacuum hose fittings - hose barb to nozzle

Vacuum hose fittings – hose barb to nozzle

Trimming the hose end at one of the ribs makes a tidy fit:

Vacuum hose fittings - ribbed hose barb

Vacuum hose fittings – ribbed hose barb

Now I need not trip over the vacuum hose between the bandsaw bench and the sander bench…

The OpenSCAD code as a GitHub Gist:


Leave a comment

Streaming Radio Advertisements: Carpet Bombing

After a protracted silence in a Radionomy stream, the Raspberry Pi player offered this log:

2017-03-05 11:17:07,890 INFO: Starting mplayer on Plenitude -> /home/pi/Playlists/Radio-PLENITUDE.m3u
2017-03-05 11:17:13,651 INFO: Track name: []
2017-03-05 11:44:02,296 INFO: Track name: [David Wahler - Whispers from Eternity]
2017-03-05 11:46:36,995 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 11:47:07,117 INFO: Track name: []
2017-03-05 11:49:07,080 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 11:49:10,079 INFO: Track name: [Jef Mounet & Danièle Mounet - L'ancre musicale Natures d'Eau]
2017-03-05 12:02:02,271 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:02:32,424 INFO: Track name: []
2017-03-05 12:04:32,243 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:05:01,925 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:07:02,276 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:07:31,968 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:09:32,262 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:10:02,192 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:12:02,311 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:12:32,184 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:14:32,085 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:15:02,217 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:17:02,057 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:17:32,445 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:19:32,083 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 12:19:35,171 INFO: Track name: [Jean-Marc Staehle - Bercé par tant de beauté]
2017-03-05 12:23:42,410 INFO: Track name: [Francesco - Sur le chemin]
2017-03-05 12:29:50,265 INFO: Track name: [Michel Pépé - Pacifica]
2017-03-05 12:35:07,493 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:35:37,377 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:37:37,478 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 12:37:41,476 INFO: Track name: [Music And Wellness (Musique Et Bien Etre) - Absolute Winner]
2017-03-05 12:46:36,742 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 12:47:06,668 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 12:49:06,538 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 12:49:10,270 INFO: Track name: [Patrick Vuillaume &Nicole Bally - Pearls of Light (Instrumental by Nicole Bally)]
2017-03-05 12:53:45,357 INFO: Track name: [Trine Opsahl - Sister moon]
2017-03-05 12:54:58,596 INFO: Track name: [Peter Kater - Rebirth]
2017-03-05 13:04:52,726 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 13:05:22,665 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 13:07:21,561 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 13:07:25,808 INFO: Track name: [Deuter - Flowing]
2017-03-05 13:12:55,970 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 13:13:25,859 INFO: Track name: []
2017-03-05 13:15:26,449 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 13:15:33,022 INFO: Track name: [Radio PLENITUDE - Jingle Intro Publicité]
2017-03-05 13:15:59,437 INFO: Track name: [Targetspot - TargetSpot]
2017-03-05 13:17:59,559 INFO: Track name: [Radio PLENITUDE - Jingle Extro Publicité]
2017-03-05 13:18:06,133 INFO: Track name: [O - Part I]

The Jingle lines introduce a short interlude of chimes separating music from advertisements. The Intro chimes play for 30 seconds and the Extro chimes play for three to five seconds. Some stations have similar interludes, others do not; apparently the station gets to choose the format.

The [Targetspot - TargetSpot] lines mark two minutes of TargetSpot insertion: either advertisements (if you’re in their target market) or generic musical interludes similar to the station’s genre (if you’re out-of-market). The ads and music often lack volume-matching with the streaming music, rarely have lower volume, and the ads are incomprehensible to my ears. The musical interludes seem to be randomly chosen from a small set of candidate tracks that, along with the chimes, become annoyingly familiar in short order.

The [] lines (yes, an empty string) mark two minutes of Public Service Announcements, advertisements, or generic musical interludes. I’m uncertain how they differ from the [Targetspot - TargetSpot] insertions.

At a minimum, Radionomy inserts two minutes of TargetSpot / PSAs after every 12 to 15 minutes of music. Adding in the Jingle markers, ads occupy just under 20% of the total “airtime” for this station.

However, bizarre events like the 17 nonstop minutes of jingles and ads inserted just after noon occur with inexplicable frequency. I’ve noticed half an hour of similar back-to-back-to-back ads on other stations, so it’s not a rare event.

To quote the TargetSpot website:

TargetSpot serves ads in real time to each listener’s personalized stream, creating a one-to-one relationship between the advertiser and the listener. The result is a dramatic increase in message relevancy and campaign effectiveness

Those keyword markers turn out to be incredibly convenient. Just sayin’…

, ,

1 Comment

Mini-Lathe Carriage Stop: Spring Counterbore

While pondering the tailstock ways, I realized the spring on the LMS Adjustable Carriage Stop just needed a counterbore to make it work right:

LMS Carriage Stop - spring counterbore

LMS Carriage Stop – spring counterbore

The OEM spring now sits slightly compressed with the screw tip flush at the far end of the block:

LMS Carriage Stop - reassembled

LMS Carriage Stop – reassembled

That OEM screw head knurling leaves a bit to be desired, doesn’t it?

Actually boring the hole would be a remarkably tedious process for little gain. Instead, I lined up the block in the drill press using a ¼ inch drill (the OEM hole isn’t hard metric!) in the unthreaded section, enlarged it with progressively larger drills up to an O (0.316 inch = 8 mm), then finished with a P (0.323 in = 8.2 mm).

As it turned out, my guesstimated relaxed spring length was a bit off, so I turned a brass bushing to shorten the hole by 2 mm:

LMS Carriage Stop - screw bushing

LMS Carriage Stop – screw bushing

If I don’t mention it, nobody will ever know!

The original doodle, with close-enough sizes:

LMS Carriage Stop - spring counterbore doodle

LMS Carriage Stop – spring counterbore doodle