added hardware board software and PC control guide

This commit is contained in:
2026-03-02 19:21:00 +01:00
parent 6775b5abec
commit c9d16c4a9b
6 changed files with 416 additions and 29 deletions

51
PC_control.md Normal file
View File

@@ -0,0 +1,51 @@
# PC control protocol
## Reporting
Board periodically sends info about lens in below format:
```
Focus mode: MF Focus limit: NO Focus: 16388 Focal length: 27.999964 Aperture: 4884
```
### Focus mode:
Values:
* MF - manual focus
* AF - autofocus (manual focus ring is disabled in this mode)
### Focus limit:
Values:
* NO - focus element is not in any limit position
* NEAR - focus element is in far limit position
* FAR - focus element is in near limit position
* UNKNOWN - another unknown value, it shows after far, so I suspect parking position
### Focus:
* Raw data from lens indicating focus position in steps
### Focal length:
* Computed from raw data
### Aperture:
* Raw data from lens
## Control
Messages which control the lens from PC, they are *not* case sensitive
### Focus mode:
For autofocus: `af`<br>
For manual focus: `mf`
### Aperture:
You can send either absolute value corresponding to what lens returns or relative value.<br>
For absolute: `aperture 5678`<br>
For relative: `aperture_rel 100` or `-100` depending if you want to open or close it
### Focus:
You can send either absolute value corresponding to what lens returns or relative value.<br>
For absolute: `focus 18000`<br>
For relative: `focus_rel 100` or `-100` depending in which direction you want to focus
### Zoom:
Zoom is a bit different in that it is controlled externally with stepper motor so the commands are a bit different. You can zoom on specific focal length or by count of steps. There are not set software limits to where you can zoom, so make sure, that you have some kind of protection setup either mechanical or in stepper controller setup with for example top current limit.<br>
For zoom at focal length: `zoom 100 50` where first number is focal length and second one is number of steps per iteration, smaller number = better resolution.<br>
For steps mode: `zoom 0 200` where first number is direction: 0 retract, 1 extend, and second is number of steps.<br>
Zoom speed can be set in file on the board, when connected to PC and with open Thonny IDE, you can select interpreter in bottom right corner to MicroPython (RP2040) and after that in left panel open config.py and edit `ZOOM_SPEED`<br>
If lens zooms backwards, flip stepper motor connector. *MAKE SURE, THAT BOARD IS POWERED OFF*

233
Software/TMC2130.py Normal file
View File

@@ -0,0 +1,233 @@
from machine import Pin, PWM, SPI
import time
import utime
from config import *
from led import *
from sTimer import sTimer_Start, sTimer_Stop
import sys
import _thread
sLock = _thread.allocate_lock()
EN = Pin(13, Pin.OUT)
DIR = Pin(14, Pin.OUT)
STEP = Pin(15, Pin.OUT)
ENDSTOP = Pin(8, Pin.IN, Pin.PULL_UP)
DC_EN = Pin(11, Pin.OUT)
DC_IN = Pin(10, Pin.OUT)
DC_OUT = Pin(12, Pin.IN)
SPI_MODE = Pin(20, Pin.OUT)
spi_cs = Pin(17, Pin.OUT)
spi = SPI(0, baudrate=100000, polarity=1, phase=1, bits=8, firstbit=SPI.MSB, sck=Pin(18, Pin.OUT), mosi=Pin(19, Pin.OUT), miso=Pin(16, Pin.IN))
DC_EN(0)
DC_IN(0)
spi_cs(1)
EN(1) #0 = enabled
DIR(0)
STEP(0)
MM_PER_STEP = SCREW_PITCH / (MOTOR_STEPS * MICROSTEPPING)
FREQ_PER_STEP_INCREASE = (((MOTOR_STEPS * MICROSTEPPING) / SCREW_PITCH) * ACCELERATION) / 60
REG_GCONF = 0x00
REG_GSTAT = 0x01
REG_IHOLD_IRUN = 0x10
REG_CHOPCONF = 0x6C
REG_COOLCONF = 0x6D
REG_DCCTRL = 0x6E
REG_DRVSTATUS = 0x6F
step_cntr = 0
target_steps = 0
target_frequency = 0
current_frequency = 0
def step():
global STEP, step_cntr, current_frequency, target_frequency, target_steps, FREQ_PER_STEP_INCREASE
STEP.value(not STEP.value())
STEP.value(not STEP.value())
LED_ERR.value(not LED_ERR.value())
step_cntr += 1
if(current_frequency < target_frequency):
current_frequency += FREQ_PER_STEP_INCREASE
if(target_steps >= step_cntr):
pass
def rotate_motor(steps, feedrate):
global current_frequency, step_cntr, target_steps, target_frequency
step_cntr = 0
target_steps = steps
target_frequency = ((feedrate / SCREW_PITCH) * MOTOR_STEPS * MICROSTEPPING) / 60 #steps/s = rpm * steps_per_rotation / 60
print(target_frequency)
print(FREQ_PER_STEP_INCREASE)
current_frequency = FREQ_PER_STEP_INCREASE
def timer_callback(counter):
if (counter % 1) == 0:
step()
def get_stats():
global STEP, step_cntr, current_frequency, target_frequency, target_steps, FREQ_PER_STEP_INCREASE
print(step_cntr, current_frequency, target_frequency, FREQ_PER_STEP_INCREASE)
def rotate_motor_test():
rotate_motor(32*200, 5)
sTimer_Start()
time.sleep(2)
sTimer_Stop()
def prepare_spi_msg(addr, d0, d1, d2, d3, rw):
msg = bytearray()
if rw == 'r':
msg.append(addr)
else:
msg.append(0x80 + addr)
msg.append(d0)
msg.append(d1)
msg.append(d2)
msg.append(d3)
return msg
def send_spi_msg(msg):
spi_cs(0)
rxdata = bytearray(5)
spi.write_readinto(msg, rxdata)
#print(rxdata.hex())
spi_cs(1)
time.sleep(0.01)
def TMC2130_Rotate(dir, steps, speed): #speed in rpm
#1rmp = 200steps/min=3.333steps/s~150ms@50%duty
sLock.acquire()
wait = (((1 / ((MOTOR_STEPS * MICROSTEPPING) / 60)) / 2) / speed) * 1000 * 1000
freq = 1 / ((wait / 1000000) * 2)
if AXIS_REVERSE:
if dir == 0:
dir = 1
else:
dir = 0
DIR(dir)
LED_DIR(1)
STEP = PWM(Pin(15), freq=int(freq), duty_u16=32768)
time.sleep((1 / freq) * steps)
STEP.deinit()
LED_DIR(0)
sLock.release()
homing = False
def TMC2130_Home():
global homing
dir = HOME_DIR
STEP = Pin(15, Pin.OUT)
steps_mm = (MOTOR_STEPS * MICROSTEPPING) / SCREW_PITCH
rpm = HOME_SPEED / SCREW_PITCH
wait = (((1 / ((MOTOR_STEPS * MICROSTEPPING) / 60)) / 2) / rpm) * 1000 * 1000
freq = 1 / ((wait / 1000000) * 2)
if AXIS_REVERSE:
if dir == 0:
dir = 1
else:
dir = 0
DIR(dir)
LED_DIR(dir)
sys.stdout.write('homing\r\n')
homing = True
STEP = PWM(Pin(15), freq=int(freq), duty_u16=32768)
while homing:
pass
STEP.deinit()
if HOME_DEBOUNCE == True:
sys.stdout.write('debouncing\r\n')
if dir == 0:
dir = 1
else:
dir = 0
DIR(dir)
LED_DIR(dir)
steps = steps_mm * abs(HOME_DEBOUNCE_DISTANCE)
TMC2130_Rotate(dir, steps, HOME_DEBOUNCE_SPEED)
rpm = HOME_DEBOUNCE_SPEED / SCREW_PITCH
wait = (((1 / ((MOTOR_STEPS * MICROSTEPPING) / 60)) / 2) / rpm) * 1000 * 1000
freq = 1 / ((wait / 1000000) * 2)
if dir == 0:
dir = 1
else:
dir = 0
DIR(dir)
LED_DIR(dir)
homing = True
STEP = PWM(Pin(15), freq=int(freq), duty_u16=32768)
while homing:
pass
STEP.deinit()
def TMC2130_Disable():
EN.value(1)
def TMC2130_Enable():
EN.value(0)
def endstop_callback(endstop):
global homing
homing = False
def TMC2130_Init():
SPI_MODE(1)
# setup coolstep
microstep = 0
if MICROSTEPPING == 128:
microstep = 1
elif MICROSTEPPING == 64:
microstep = 2
elif MICROSTEPPING == 32:
microstep = 3
elif MICROSTEPPING == 16:
microstep = 4
elif MICROSTEPPING == 8:
microstep = 5
elif MICROSTEPPING == 4:
microstep = 6
elif MICROSTEPPING == 2:
microstep = 7
elif MICROSTEPPING == 1:
microstep = 8
else:
microstep = 0
msg = prepare_spi_msg(REG_CHOPCONF, 0x00+microstep, 0x01, 0x00, 0xC3, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(REG_IHOLD_IRUN, 0x00, 0x06, I_RUN, I_HOLD, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(0x11, 0x00, 0x00, 0x00, 0x0A, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(REG_GCONF, 0x00, 0x00, 0x00, 0x04, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(0x13, 0x00, 0x00, 0x01, 0xF4, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(0x70, 0x00, 0x04, 0x01, 0xC8, 'w')
send_spi_msg(msg)
msg = prepare_spi_msg(REG_COOLCONF, 0x00, 0x00, 0x03, 0x01, 'w')
send_spi_msg(msg)
TMC2130_Enable()

27
Software/config.py Normal file
View File

@@ -0,0 +1,27 @@
MOTOR_STEPS = 200
MICROSTEPPING = 16
I_HOLD = 10 #hold current (1-31)
I_RUN = 10 #run current (1-31)
SCREW_PITCH = 5 #in mm/rotation
AXIS_LENGTH = 1000 #mm
AXIS_ID = 'X' #id of axis (X,Y,U,V)
AXIS_REVERSE = False
HOME_DIR = 0
HOME_SPEED = 600 #in mm/s
HOME_DEBOUNCE = True
HOME_DEBOUNCE_DISTANCE = 5 #in mm
HOME_DEBOUNCE_SPEED = 200
ACCELERATION = 1
FOCUS_MIN = 12000
FOCUS_MAX = 23000
AP_MIN = 4884
AP_MAX = 6132
FOCAL_LEN_MIN = 28
FOCAL_LEN_MAX = 200
ZOOM_SPEED = 30

6
Software/led.py Normal file
View File

@@ -0,0 +1,6 @@
from machine import Pin
LED_ERR = Pin(25, Pin.OUT)
LED_DIR = Pin(3, Pin.OUT)
LED_MSG = Pin(6, Pin.OUT)
LED_OK = Pin(7, Pin.OUT)

View File

@@ -4,11 +4,20 @@ import time
import math
import sys
import select
from led import *
from config import *
import _thread
from TMC2130 import TMC2130_Rotate, TMC2130_Init, rotate_motor_test
lens_cs = machine.Pin(7, machine.Pin.IN)
body_cs = machine.Pin(8, machine.Pin.OUT)
vd_lens = machine.Pin(9, machine.Pin.OUT)
tx_pin = machine.Pin(16, machine.Pin.OUT)
lens_cs = machine.Pin(4, machine.Pin.IN)
body_cs = machine.Pin(5, machine.Pin.OUT)
vd_lens = machine.Pin(2, machine.Pin.OUT)
tx_pin = machine.Pin(0, machine.Pin.OUT)
LED_OK.value(1)
poll_obj = ''
@@ -27,13 +36,9 @@ focus_pos = 0 #focus position to be set manually
af_in_progress = False
focus_mode = "MF" #AF/MF
focus_min = 12000
focus_max = 23000
ap_min = 4884
ap_max = 6132
focus_pos = 0
change_focus = False
relative_focus = False
utime.sleep(1)
vd_lens.value(1)
@@ -56,6 +61,18 @@ time.sleep_ms(20)
rx_ack = False
def Zoom(focal_distance, steps_per_iter):
dir = 0
reverse = False
if focal_distance > focal_length:
dir = 1
while focal_distance > focal_length:
TMC2130_Rotate(dir, steps_per_iter, ZOOM_SPEED)
else:
while focal_distance < focal_length:
TMC2130_Rotate(dir, steps_per_iter, ZOOM_SPEED)
def Comms_Init():
global poll_obj
@@ -63,24 +80,49 @@ def Comms_Init():
poll_obj.register(sys.stdin, select.POLLIN)
def Comms_Read():
global poll_obj, ap, focus_mode, change_focus, focus_pos
global poll_obj, ap, focus_mode, change_focus, focus_pos, relative_focus
poll_results = poll_obj.poll(1)
if poll_results:
rw = sys.stdin.readline().strip()
rw = rw.upper()
if "APERTURE" in rw:
data = rw.split(' ')
ap = int(data[1])
try:
data = rw.split(' ')
if "REL" in rw:
ap = aperture_raw + int(data[1])
else:
ap = int(data[1])
except:
print("APERTURE SET ERROR")
if "FOCUS" in rw:
data = rw.split(' ')
focus_pos = int(data[1])
change_focus = True
try:
data = rw.split(' ')
focus_pos = int(data[1])
change_focus = True
if "REL" in rw:
relative_focus = True
except:
print("FOCUS SET ERROR")
if "AF" in rw:
focus_mode = "AF"
if "MF" in rw:
focus_mode = "MF"
if "ZOOM" in rw:
try:
data = rw.split(' ')
if "STEPS" in rw:
if int(data[1]) < 2:
if int(data[1]) >= 0:
if int(data[2]) > 0:
_thread.start_new_thread(TMC2130_Rotate, (int(data[1]), int(data[2]), ZOOM_SPEED))
else:
if int(data[1]) > FOCAL_LEN_MIN:
if int(data[1]) < FOCAL_LEN_MAX:
if int(data[2]) > 0:
_thread.start_new_thread(Zoom, (int(data[1]), int(data[2])))
except:
print("ZOOM SET ERROR")
def focal_length_conv(raw):
return (math.exp((raw+92063.3)/8082.9)+406.979)/3256.82
@@ -93,10 +135,10 @@ def aperture_count(value, leftMin=2.8, leftMax=16, rightMin=4884, rightMax=6132)
return rightMin + (valueScaled * rightSpan)
def aperture(value):
if value < ap_min:
value = ap_min
if value > ap_max:
value = ap_max
if value < AP_MIN:
value = AP_MIN
if value > AP_MAX:
value = AP_MAX
return value
def rx_end(pin):
@@ -144,7 +186,8 @@ def print_rx():
focus_limit = "UNKNOWN"
if msg[5] == 5:
aperture_raw = msg[6] + (msg[7] << 8)
print("Focus limit:", focus_limit, "Focus:", focus, "Focal length:", focal_length, "Aperture:", aperture_raw)
print("Focus mode:", focus_mode, "Focus limit:", focus_limit, "Focus:", focus, "Focal length:", focal_length, "Aperture:", aperture_raw)
LED_MSG.toggle()
else:
if b == 0xf0:
@@ -215,12 +258,12 @@ def send_af():
time.sleep_ms(6)
def move_focus(position):
global ap, ap_closing, af_in_progress, focus_mode, focus_pos, focus_min, focus_max
global ap, ap_closing, af_in_progress, focus_mode, focus_pos, FOCUS_MIN, FOCUS_MAX
if position < focus_min:
position = focus_min
if position > focus_max:
position = focus_max
if position < FOCUS_MIN:
position = FOCUS_MIN
if position > FOCUS_MAX:
position = FOCUS_MAX
vd_pulse()
time.sleep_ms(10)
@@ -256,10 +299,14 @@ def shutdown():
time.sleep_ms(6)
def normal_loop():
global ap, ap_closing, af_in_progress, focus_mode, focus_pos, change_focus
global ap, ap_closing, af_in_progress, focus_mode, focus_pos, change_focus, relative_focus
if change_focus:
move_focus(focus_pos)
if relative_focus:
move_focus(focus+focus_pos)
relative_focus = False
else:
move_focus(focus_pos)
change_focus = False
else:
vd_pulse()
@@ -274,14 +321,16 @@ def normal_loop():
send_focus(focus_mode, af_in_progress)
time.sleep_ms(6)
print_rx()
TMC2130_Init()
Comms_Init()
lens_cs.irq(handler=rx_end, trigger=machine.Pin.IRQ_FALLING)
vd_pulse()
body_cs.value(1)
time.sleep_ms(4)
uart_bodytx = machine.UART(0, baudrate=750000, tx=machine.Pin(16), rx=machine.Pin(17)) # UART1, GP0 (TX) and GP1 (RX)
uart_bodytx = machine.UART(0, baudrate=750000) # UART1, GP0 (TX) and GP1 (RX) #, tx=machine.Pin(16), rx=machine.Pin(17)
body_cs.value(0)
time.sleep_ms(15)
vd_pulse()
@@ -313,3 +362,6 @@ while True:
normal_loop()
Comms_Read()

18
Software/sTimer.py Normal file
View File

@@ -0,0 +1,18 @@
from machine import Timer
import TMC2130
FREQUENCY = 100 #kHz
counter = 0
sTim = Timer()
def sTim_callback(t):
global counter
counter += 1
TMC2130.timer_callback(counter)
def sTimer_Start():
sTim.init(freq=FREQUENCY*1000, mode=Timer.PERIODIC, callback=sTim_callback)
def sTimer_Stop():
sTim.deinit()