added hardware board software and PC control guide
This commit is contained in:
51
PC_control.md
Normal file
51
PC_control.md
Normal 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
233
Software/TMC2130.py
Normal 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
27
Software/config.py
Normal 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
6
Software/led.py
Normal 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)
|
||||
@@ -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
18
Software/sTimer.py
Normal 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()
|
||||
Reference in New Issue
Block a user