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!