Created
November 13, 2024 14:20
-
-
Save FilipDominec/322046a23f5fa9449ba1a185c0d44a00 to your computer and use it in GitHub Desktop.
Control software for the SEM/CL setup at FZU in the A44 lab - updated for rp2daq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vpos_panchro = 4230 | |
vpos_spectro = 1180 | |
vpos_monochro = 110 | |
calib1_nm = 27.1 | |
calib1_stepper = 0 | |
calib2_nm = 653.3 | |
calib2_stepper = 100000 | |
min_limit_nm = 100 | |
max_limit_nm = 1000 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
#-*- coding: utf-8 -*- | |
""" | |
Update after Win11 transition of the "black" computer in the A/44 room. 2024-11-11 | |
Module dependencies: tkinter, serial, struct | |
""" | |
# TODO keyboard shortcuts; | |
# TODO saving recent few | |
# big optional TODO PS/2 keyboard sniffer | |
# TODO TEST NEW FEATURES 2021-01-08 | |
import sys, os, time, serial, struct | |
import tkinter as tk | |
from tkinter import messagebox | |
## == error handling with a graphical message box == | |
def myerr(exc_type, exc_value, tb): | |
import traceback | |
message = '\r'.join(traceback.format_exception(exc_type, exc_value, tb)) | |
print(message) | |
messagebox.showerror(title=exc_value, message=message) | |
sys.excepthook = myerr | |
## == settings loading == {{{ | |
def load_settings_from_file(infile='a44_config.txt'): | |
#infile = os.path.realpath(__file__) | |
#print("DEBUG: infile = ", infile) | |
settings = {} | |
with open(infile) as f: | |
for l in f.readlines(): | |
l = l.split('#')[0] # ignore comments | |
k,v = [s.strip() for s in l.split('=', 1)] # split at first '=' sign | |
settings[k] = v | |
return settings | |
settings = load_settings_from_file()# }}} | |
## == Serial port & stepper init == {{{ | |
import rp2daq | |
rp = rp2daq.Rp2daq() | |
zero_pos = [0,0] | |
zero_pos[0] = rp.stepper_init(0, dir_gpio=12, step_gpio=13, endswitch_gpio=19, inertia=400)["initial_nanopos"] | |
zero_pos[1] = rp.stepper_init(1, dir_gpio=10, step_gpio=11, endswitch_gpio=18, inertia=400)["initial_nanopos"] | |
NANOSTEP_PER_MICROSTEP = 256 | |
VERT_MINIMUM_POS = -10000 | |
MONO_MINIMUM_POS = -1000000 | |
MONO_MOTOR_ID = 0 | |
VERT_MOTOR_ID = 1 | |
## == Hardware-communication routines =={{{ | |
def vmotor(choice): | |
print('VMOTOR', choice) | |
settings = load_settings_from_file() | |
## send the message to the hardware | |
if choice == VERT_MINIMUM_POS: | |
target_micropos = -10000 | |
else: | |
target_micropos = int(settings['vpos_'+choice]) | |
rp.stepper_move(VERT_MOTOR_ID, | |
to=zero_pos[VERT_MOTOR_ID] + 2*target_micropos*NANOSTEP_PER_MICROSTEP, | |
speed=64, | |
reset_nanopos_at_endswitch=1) | |
## visual GUI feedback (todo indicate that the movement was really finished) | |
for k,v in button_dict.items(): v.config(relief=tk.SUNKEN if k == choice else tk.RAISED) | |
for k,v in frame_dict.items(): v.config(bg='green2' if k == choice else BGCOLOR) | |
lbl_wavelength.config(bg='green2' if choice=='monochro' else BGCOLOR) | |
#ent_wavelength.config(state=tk.NORMAL if choice=='monochro' else tk.DISABLED) (why not move monochromator any time...) | |
#btn_wavelength.config(state=tk.NORMAL if choice=='monochro' else tk.DISABLED) | |
def mmotor(target_micropos): | |
rp.stepper_move(MONO_MOTOR_ID, | |
to=zero_pos[MONO_MOTOR_ID] + 2*target_micropos*NANOSTEP_PER_MICROSTEP, | |
speed=64*2, | |
reset_nanopos_at_endswitch=1) | |
def mmotor_by_wl(target_wavelength_nm): | |
## (re)load the monochromator calib | |
settings = load_settings_from_file() | |
nm1, nm2 = float(settings['calib1_nm']), float(settings['calib2_nm']), | |
s1, s2 = float(settings['calib1_stepper']), float(settings['calib2_stepper']) | |
target_micropos = int((target_wavelength_nm-nm1)/(nm2-nm1)*(s2-s1)+s1) | |
#print("DEBUG: target_micropos = ", target_micropos) | |
mmotor(target_micropos) | |
def mmotor_by_entry(event): | |
try: | |
target_wavelength_nm = float(ent_wavelength.get()) | |
ent_wavelength.config(bg='#ffffff') | |
except: | |
ent_wavelength.config(bg='#ff8800') | |
print("DEBUG: target_wavelength_nm = ", target_wavelength_nm) | |
print( settings['min_limit_nm'] + settings['max_limit_nm']) | |
target_wavelength_nm = min(max(target_wavelength_nm, float(settings['min_limit_nm'])), float(settings['max_limit_nm'])) | |
txt_wavelength.set('{:6.0f}'.format(target_wavelength_nm)) | |
mmotor_by_wl(target_wavelength_nm) | |
# }}} | |
## == GUI init == | |
root = tk.Tk() | |
root.geometry("300x330+300+300") | |
root.wm_title("CL control") | |
#root.pack_propagate(0) # don't shrink | |
btnkwarg = {'padx':100, 'pady':20} | |
frm_panchro = tk.Frame(root, height=32, width=32); frm_panchro.pack(padx=5, pady=5, side=tk.TOP) | |
btn_panchro = tk.Button(frm_panchro, text='Panchromatic imaging', command=lambda:vmotor('panchro'), **btnkwarg) # fill=tk.X, ipady=10, | |
btn_panchro.pack(padx=5, pady=5, side=tk.TOP) | |
BGCOLOR = btn_panchro.cget('bg') | |
frm_spectro = tk.Frame(root, height=32, width=32); frm_spectro.pack(padx=5, pady=5, side=tk.TOP) | |
btn_spectro = tk.Button(frm_spectro, text='Spectrometer', command=lambda:vmotor('spectro'), **btnkwarg) | |
btn_spectro.pack(padx=5, pady=5, side=tk.TOP) | |
frm_monochro = tk.Frame(root, height=32, width=32); frm_monochro.pack(padx=5, pady=5, side=tk.TOP) | |
btn_monochro = tk.Button(frm_monochro, text='Monochromator imaging', command=lambda:vmotor('monochro'), **btnkwarg) | |
btn_monochro.pack(padx=5, pady=5, side=tk.TOP) | |
lbl_wavelength = tk.Label(text='Wavelength (nm)', master=frm_monochro ); lbl_wavelength.pack(padx=5, pady=5, side=tk.LEFT) | |
txt_wavelength = tk.StringVar() | |
ent_wavelength = tk.Entry(frm_monochro, text='nm', textvariable=txt_wavelength, width=10); | |
ent_wavelength.pack(padx=5, pady=5, side=tk.LEFT) | |
ent_wavelength.bind('<Return>', mmotor_by_entry) | |
btn_wavelength = tk.Button(frm_monochro, text='set!', command=lambda:mmotor_by_entry('mouse')); btn_wavelength.pack(padx=5, pady=5, side=tk.LEFT) | |
frm_endstop = tk.Frame(root, height=22); frm_endstop.pack(padx=5, pady=5, side=tk.TOP) | |
btn_mgotovend = tk.Button(frm_endstop, text='Vertical zero', command=lambda:[vmotor(VERT_MINIMUM_POS)], bg='#eebbbb', padx=10, pady=2) # fill=tk.X, ipady=10, | |
btn_mgotovend.pack(padx=5, pady=5, side=tk.LEFT) | |
btn_mgotomend = tk.Button(frm_endstop, text='Monochromator zero', command=lambda:[mmotor(MONO_MINIMUM_POS)], bg='#eebbbb', padx=10, pady=2) # fill=tk.X, ipady=10, | |
btn_mgotomend.pack(padx=5, pady=5, side=tk.LEFT) | |
frame_dict = {'panchro':frm_panchro, 'spectro':frm_spectro, 'monochro':frm_monochro} | |
button_dict = {'panchro':btn_panchro, 'spectro':btn_spectro, 'monochro':btn_monochro} | |
## == responsive GUI loop and termination == | |
tk.mainloop() | |
## TODO try py2exe https://stackoverflow.com/questions/13894083/py2exe-window-disappears-immediately-after-running |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is how the GUI looks like.
