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.

The New Hotness

  • Arducam Motorized Focus Camera: Rotary Encoder and Equation

    Mashing rotary encoder reading together with the focus-distance-to-DAC equation produces well-behaved camera focusing.

    First, set up another test range:

    Arducam Motorized Focus Camera - desktop test range
    Arducam Motorized Focus Camera – desktop test range

    Run the test code:

    # Simpleminded focusing test for
    #  Arducam Motorized Focus Camera
    # Gets events through evdev from rotary encoder knob
    
    # Ed Nisley - KE4ZNU
    # 2020-10-20
    
    import sys
    import math
    import evdev
    import smbus
    
    # useful functions
    
    def DAC_from_distance(dist):
        return math.trunc(256*(10.8 + 2180/dist))
    
    # write DAC word to camera I2C bus device
    #  and ignore the omnipresent error return
    
    def write_lens_DAC(bus,addr,val):
        done = False
        while not done:
            try:
                bus.write_word_data(addr,val >> 8,val & 0xff)
            except OSError as e:
                if e.errno == 121:
    #                print('OS remote error ignored')
                    done = True
            except:
                print(sys.exc_info()[0],sys.exc_info()[1])
            else:
                print('Write with no error!')
                done = True
    
    # set up focus distances
    
    closest = 50            # mm
    farthest = 500
    nominal = 100           # default focus distance
    
    foci = [n for n in range(closest,nominal,5)] \
         + [n for n in range(nominal,250,10)]  \
         + [n for n in range(250,1501,25)]
    
    # compute DAC equivalents for each distance
    
    foci_DAC = list(map(DAC_from_distance,foci))
    
    focus_index = foci.index(nominal)
    
    # set up the I2C bus
    
    f = smbus.SMBus(0)
    lens = 0x0c
    
    # set up the encoder device handler
    # requires rotary-encoder dtoverlay aimed at pins 20 & 21
    
    d = evdev.InputDevice('/dev/input/by-path/platform-rotary@14-event')
    print('Rotary encoder device: {}'.format(d.name))
    
    # set initial focus
    
    write_lens_DAC(f,lens,foci_DAC[focus_index])
    
    # fetch I2C events and update the focus forever
    
    for e in d.read_loop():
    #    print('Event: {}'.format(e))
    
        if e.type == evdev.ecodes.EV_REL:
    #        print('Rel: {}'.format(e.value))
    
            if (e.value > 0 and focus_index < len(foci) - 1) or (e.value < 0 and focus_index > 0):
                focus_index += e.value
    
            dist = foci[focus_index]
            dac = foci_DAC[focus_index]
    
            print('Distance: {:4d} mm DAC: {:5d} {:04x} i: {:3d}'.format(dist,dac,dac,focus_index))
    
            write_lens_DAC(f,lens,dac)
    

    Because the knob produces increments of ±1, the code accumulates them into an index for the foci & foci_DAC lists, then sends the corresponding entry from the latter to the lens on every rotary encoder event.

    And then It Just Works!

    The camera powers up with the lens focused at infinity (or slightly beyond), but setting it to 100 mm seems more useful:

    Arducam Motorized Focus Camera - 100 mm
    Arducam Motorized Focus Camera – 100 mm

    Turning the knob counterclockwise runs the focus inward to 50 mm:

    Arducam Motorized Focus Camera - 50 mm
    Arducam Motorized Focus Camera – 50 mm

    Turning it clockwise cranks it outward to 1500 mm:

    Arducam Motorized Focus Camera - 1500 mm
    Arducam Motorized Focus Camera – 1500 mm

    The mug is about 300 mm away, so the depth of field extends from there to infinity (and beyond).

    It needs more work, but now it has excellent upside potential!