The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Oddities

Who’d’a thunk it?

  • Raspberry Pi Interrupts vs. Rotary Encoder

    Raspberry Pi Interrupts vs. Rotary Encoder

    Thinking about using a rotary encoder to focus a Raspberry Pi lens led to a testbed:

    RPi knob encoder test setup
    RPi knob encoder test setup

    There’s not much to it, because the RPi can enable pullup resistors on its digital inputs, whereupon the encoder switches its code bits to common. The third oscilloscope probe to the rear syncs on a trigger output from my knob driver.

    I started with the Encoder library from PyPi, but the setup code doesn’t enable the pullup resistors and the interrupt (well, it’s a callback) handler discards the previous encoder state before using it, so the thing can’t work. I kept the overall structure, gutted the code, and rebuilt it around a state table. The code appears at the bottom, but you won’t need it.

    Here’s the problem, all in one image:

    Knob Encoder - ABT - fast - overview
    Knob Encoder – ABT – fast – overview

    The top two traces are the A and B encoder bits. The bottom trace is the trigger output from the interrupt handler, which goes high at the start of the handler and low at the end, with a negative blip in the middle when it detects a “no motion” situation: the encoder output hasn’t changed from the last time it was invoked.

    Over on the left, where the knob is turning relatively slowly, the first two edges have an interrupt apiece. A detailed view shows them in action (the bottom half enlarge the non-shaded part of the top half):

    Knob Encoder - ABT - fast - first IRQs
    Knob Encoder – ABT – fast – first IRQs

    Notice that each interrupt occurs about 5 ms after the edge causing it!

    When the edges occur less than 5 ms apart, the driver can’t keep up. The next four edges produce only three interrupts:

    Knob Encoder - ABT - fast - 4 edges 3 IRQ
    Knob Encoder – ABT – fast – 4 edges 3 IRQ

    A closer look at the three interrupts shows all of them produced the “no motion” pulse, because they all sampled the same (incorrect) input bits:

    Knob Encoder - ABT - fast - 4 edges 3 IRQ - detail
    Knob Encoder – ABT – fast – 4 edges 3 IRQ – detail

    In fact, no matter how many edges occur, you only get three interrupts:

    Knob Encoder - ABT - fast - 9 edges 3 IRQ
    Knob Encoder – ABT – fast – 9 edges 3 IRQ

    The groups of interrupts never occur less than 5 ms apart, no matter how many edges they’ve missed. Casual searching suggests the Linux Completely Fair Scheduler has a minimum timeslice / thread runtime around 5 ms, so the encoder may be running at the fastest possible response for a non-real-time Raspberry Pi kernel, at least with a Python handler.

    If. I. Turn. The. Knob. Slowly. Then. It. Works. Fine. But. That. Is. Not. Practical. For. My. Purposes.

    Nor anybody else’s purposes, really, which leads me to think very few people have ever tried lashing a rotary encoder to a Raspberry Pi.

    So, OK, I’ll go with Nearer and Farther focusing buttons.

    The same casual searching suggested tweaking the Python thread’s priority / niceness could lock it to a different CPU core and, obviously, writing the knob handler in C / C++ / any other language would improve the situation, but IMO the result doesn’t justify the effort.

    It’s worth noting that writing “portable code” involves more than just getting it to run on a different system with different hardware. Rotary encoder handlers are trivial on an Arduino or, as in this case, even an ARM-based Teensy, but “the same logic” doesn’t deliver the same results on an RPi.

    My attempt at a Python encoder driver + simple test program as a GitHub Gist:

    # Rotary encoder test driver
    # Ed Nisley – KE4ZNU
    # Adapted from https://github.com/mivallion/Encoder
    # State table from https://github.com/PaulStoffregen/Encoder
    import RPi.GPIO as GPIO
    class Encoder(object):
    def __init__(self, A, B, T=None, Delay=None):
    GPIO.setmode(GPIO.BCM)
    self.T = T
    if T is not None:
    GPIO.setup(T, GPIO.OUT)
    GPIO.output(T,0)
    GPIO.setup(A, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(B, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    self.delay = Delay
    self.A = A
    self.B = B
    self.pos = 0
    self.state = (GPIO.input(B) << 1) | GPIO.input(A)
    self.edges = (0,1,-1,2,-1,0,-2,1,1,-2,0,-1,2,-1,1,0)
    if self.delay is not None:
    GPIO.add_event_detect(A, GPIO.BOTH, callback=self.__update,
    bouncetime=self.delay)
    GPIO.add_event_detect(B, GPIO.BOTH, callback=self.__update,
    bouncetime=self.delay)
    else:
    GPIO.add_event_detect(A, GPIO.BOTH, callback=self.__update)
    GPIO.add_event_detect(B, GPIO.BOTH, callback=self.__update)
    def __update(self, channel):
    if self.T is not None:
    GPIO.output(self.T,1) # flag entry
    state = (self.state & 0b0011) \
    | (GPIO.input(self.B) << 3) \
    | (GPIO.input(self.A) << 2)
    gflag = '' if self.edges[state] else ' – glitch'
    if (self.T is not None) and not self.edges[state]: # flag no-motion glitch
    GPIO.output(self.T,0)
    GPIO.output(self.T,1)
    self.pos += self.edges[state]
    self.state = state >> 2
    # print(' {} – state: {:04b} pos: {}{}'.format(channel,state,self.pos,gflag))
    if self.T is not None:
    GPIO.output(self.T,0) # flag exit
    def read(self):
    return self.pos
    def read_reset(self):
    rv = self.pos
    self.pos = 0
    return rv
    def write(self,pos):
    self.pos = pos
    if __name__ == "__main__":
    import encoder
    import time
    from gpiozero import Button
    btn = Button(26)
    enc = encoder.Encoder(20, 21,T=16)
    prev = enc.read()
    while not btn.is_held :
    now = enc.read()
    if now != prev:
    print('{:+4d}'.format(now))
    prev = now
    view raw encoder.py hosted with ❤ by GitHub
  • Helicopter Hovering

    Helicopter Hovering

    Spotted on a ride on New Hackensack Road around what’s grandly known as the Hudson Valley Regional Airport:

    Helicopter Hovering Practice 1
    Helicopter Hovering Practice 1

    Indeed, it was.

    I watched a private airshow for a few minutes:

    Helicopter Hovering Practice 2
    Helicopter Hovering Practice 2

    The same helicopter thumped over our house, about two miles from the runway as the chopper flies, while I was getting ready for the ride, and it was hovering as I reached the airport. I think the pilot was practicing, because the chopper made very precise movements across the airport, translated front / back / left / right, and hovered motionless for minutes at a time despite wind gusts.

    Looks like enjoyably intense concentration!

  • Praying Mantis: Ootheca Construction

    Praying Mantis: Ootheca Construction

    After not seeing any Praying Mantis activity in the Butterfly Bush for a few days, I discovered our armored hunter in the nearby decorative grass:

    Praying Mantis - building ootheca
    Praying Mantis – building ootheca

    The appendages at the tip of her abdomen were spread to the sides and her whole body moved in small circles, although I couldn’t get a good view of the proceedings. Building an ootheca apparently requires concerted effort, as she was still hard at work when dusk fell.

  • Pixel 3a Screen Protector FTW!

    Pixel 3a Screen Protector FTW!

    Despite carrying a glass-fronted gadget in my pocket for most of the past two decades, this is the first time I’ve done this:

    Pixel 3a screen protector - as broken
    Pixel 3a screen protector – as broken

    Turns out you can’t trust a rolling seat on a slightly unlevel surface, as shifting your weight can let the thing roll out from under you with no warning. If you’re taking a picture at the same time, the phone reaches the impact point before your hand: even a nice case with bumpers all around won’t be quite enough protection.

    I was tempted to leave it un-fixed as a constant reminder to not do that again, but the broken glass was rough to the touch and interfered with Android’s swipe-upward gestures.

    Fortunately, the tempered-glass screen protector absorbed the energy without damage to the actual screen:

    Pixel 3a screen protector - sidelit
    Pixel 3a screen protector – sidelit

    A thin plastic layer holds the protector’s fragments together; I hadn’t known it was a two-layer structure.

    Being that type of guy, I had a spare protector in a desk drawer and managed to apply it without trapping any bubbles or fuzz underneath.

  • Tour Easy: PTT Switch Cleaning

    Tour Easy: PTT Switch Cleaning

    The switch I installed on Mary’s bike a year ago was intended for indoor use only and, without any trace of weather sealing, recently became intermittent. No surprise, as it’s happened before, but, by regarding my vast assortment of little switches as consumables, we get a low-profile / tactile / E-Z push PTT button without forming a deep emotional attachment.

    Anyhow, you can see the unsealed square perimeter of the switch actuator:

    Tour Easy - PTT button
    Tour Easy – PTT button

    The light-gray button sits on a post molded into the actuator. Pry the actuator out and the switch dome shows crud worn off the cross-shaped plunger:

    Tour Easy - PTT button - dome plate
    Tour Easy – PTT button – dome plate

    The underside of the dome has a weird golden discoloration that surely wasn’t original:

    Tour Easy - PTT button - dome plate discoloration
    Tour Easy – PTT button – dome plate discoloration

    I have no idea how a liquid (?) could have gotten in there and done that without leaving other traces along the way. The contact bump on the discolored leg had some crud built up around it which responded well to a small screwdriver.

    Contrary to what the symmetrical four-legged dome might suggest, only one leg rests on a contact in a corner:

    Tour Easy - PTT button - contacts
    Tour Easy – PTT button – contacts

    So, yes, a bit of dirt / corrosion / mystery juice in a single spot could render the whole thing intermittent.

    I removed the obvious crud from the obvious spots, wiped everything down with some Caig DeoxIT, reassembled in reverse order, and it seems to be all good again. Of course, these things only fail on the road, so it’ll take a few rides to verify the fix.

  • Mystery Microscope Objective Illuminator

    Mystery Microscope Objective Illuminator

    Rummaging through the Big Box o’ Optics in search of something else produced this doodad:

    Microscope objective illuminator - overview
    Microscope objective illuminator – overview

    It carries no brand name or identifier, suggesting it was shop-made for a very specific and completely unknown purpose. The 5× objective also came from the BBo’O, but wasn’t related in any way other than fitting the threads, so the original purpose probably didn’t include it.

    The little bulb fit into a cute and obviously heat-stressed socket:

    Microscope objective illuminator - bulb detail
    Microscope objective illuminator – bulb detail

    The filament was, of course, broken, so I dismantled the socket and conjured a quick-n-dirty white LED that appears blue under the warm-white bench lighting:

    Microscope objective illuminator - white LED
    Microscope objective illuminator – white LED

    The socket fits into the housing on the left, which screws onto a fitting I would have sworn was glued / frozen in place. Eventually, I found a slotted grub screw hidden under a glob of dirt:

    Microscope objective illuminator - lock screw
    Microscope objective illuminator – lock screw

    Releasing the screw let the fitting slide right out:

    Microscope objective illuminator - lamp reflector
    Microscope objective illuminator – lamp reflector

    The glass reflector sits at 45° to direct the light coaxially down into the objective (or whatever optics it was originally intended for), with the other end of the widget having a clear view straight through. I cleaned the usual collection of fuzz & dirt off the glass, then centered and aligned the reflection with the objective.

    Unfortunately, the objective lens lacks antireflection coatings:

    Microscope objective illuminator - stray light
    Microscope objective illuminator – stray light

    The LED tube is off to the right at 2 o’clock, with the bar across the reflector coming from stray light bouncing back from the far wall of the interior. The brilliant dot in the middle comes from light reflected off the various surfaces inside the objective.

    An unimpeachable source tells me microscope objectives are designed to form a real image 180 mm up inside the ‘scope tube with the lens at the design height above the object. I have the luxury of being able to ignore all that, so I perched a lensless Raspberry Pi V1 camera on a short brass tube and affixed it to a three-axis positioner:

    Microscope objective illuminator - RPi camera lashup
    Microscope objective illuminator – RPi camera lashup

    A closer look at the lashup reveals the utter crudity:

    Microscope objective illuminator - RPi camera lashup - detail
    Microscope objective illuminator – RPi camera lashup – detail

    It’s better than I expected:

    Microscope objective illuminator - RPi V1 camera image - unprocessed
    Microscope objective illuminator – RPi V1 camera image – unprocessed

    What you’re seeing is the real image formed by the objective lens directly on the RPi V1 camera’s sensor: in effect, the objective replaces the itsy-bitsy camera lens. It’s a screen capture from VLC using V4L2 loopback trickery.

    Those are 0.1 inch squares printed on the paper, so the view is about 150×110 mil. Positioning the camera further from the objective would reduce both the view (increase the magnification) and the amount of light, so this may be about as good as it get.

    The image started out with low contrast from all the stray light, but can be coerced into usability:

    Microscope objective illuminator - RPi V1 camera image - auto-level adjust
    Microscope objective illuminator – RPi V1 camera image – auto-level adjust

    The weird violet-to-greenish color shading apparently comes from the lens shading correction matrix baked into the RPi image capture pipeline and can, with some difficulty, be fixed if you have a mind to do so.

    All this is likely not worth the effort given the results of just perching a Pixel 3a atop the stereo zoom microscope:

    Pixel 3a on stereo zoom microscope
    Pixel 3a on stereo zoom microscope

    But I just had to try it out.

  • Multimeter Current-sense Resistor

    Multimeter Current-sense Resistor

    Replacing the battery in an old Craftsman (!) multimeter brought its 10 A current-sense resistor into the light:

    Multimeter current resistor - nipped copper wire
    Multimeter current resistor – nipped copper wire

    Unlike the contemporary AN8008/9 meters, it looks like an ordinary copper wire trimmed to the proper resistance by nipping it with a cutter.

    It measures something under 10 mΩ, so I’m sure they adjusted the resistance by applying a known current and watching the meter reading while crunching the wire until the proper value appears.

    I may have actually used the 10 A range, but I’d be hard pressed to say when or why, so the resistor is at least as good as it needs to be!