update gui and triggering: speed sync works!

This commit is contained in:
2018-11-22 12:32:43 +01:00
parent 2e534f306e
commit e832b40124
7 changed files with 183 additions and 113 deletions

View File

@@ -38,7 +38,8 @@ class MotionBase:
def setup_sync(self, crdId=1, prgId=2, plcId=2, mode=0, **kwargs):
'''setup the timing synchronization for the motion program
mode=0 : no sync at all
mode=1 : first sync test
mode=1 : synchronize start
mode=2 : synchronize start and adapt motion speed
this function generates the code blocks:
self.sync_wait and self.sync_run
sync_wait can be put in the program to force a timing sync
@@ -51,52 +52,14 @@ class MotionBase:
pass
self.sync_run = '&{crdId}b{prgId}r'''.format(prgId=prgId, crdId=crdId)
elif mode == 1:
# changes the Timebase of coord system
# modifies the timebase to start/stop a running program
# this can also be used to adjust the execution speed
# the plc for now
# - waits untis <flag> is false
# - waits raising edge of <flag> and the sets DesTimeBase=ServoPeriod
# flag ='PowerBrick[0].GpioData[0].0.1==1'
flag = 'Gate3[1].Chan[0].UserFlag==0'
prg = '''close all buffers
disable plc {plcId}
open plc {plcId}
L0=Sys.PhaseCount
send 1"sync0 %d:%d\\n",Sys.PhaseCount,Sys.PhaseCount-L0
while({flag}){{}}
send 1"sync1 %d:%d\\n",Sys.PhaseCount,Sys.PhaseCount-L0
L1=Sys.PhaseCount
while(1)
{{
if({flag})
{{
PowerBrick[0].GpioData[0].16.1=1
Coord[{crdId}].DesTimeBase=Sys.ServoPeriod
send 1"sync2 %d:%d\\n",Sys.PhaseCount,Sys.PhaseCount-L1
break
}}
}}
disable plc {plcId}
close
'''.format(plcId=plcId, crdId=crdId, flag=flag)
comm=self.comm
if comm is not None:
gpascii=comm.gpascii
gpascii.send_block(prg)
self.sync_prg = 'Coord[{crdId}].DesTimeBase=0'.format(prgId=prgId, plcId=plcId, crdId=crdId)
self.sync_run = 'enable plc {plcId};&{crdId}b{prgId}r'''.format(prgId=prgId, plcId=plcId, crdId=crdId)
elif mode == 2:
elif mode in (1,2):
# code block to insert in the program
# - waits untis <flag> is false
# - waits raising edge of <flag> and the sets DesTimeBase=ServoPeriod
# flag ='PowerBrick[0].GpioData[0].0.1==1'
flag0 = 'Gate3[1].Chan[0].UserFlag==0'
flag1 = 'Gate3[1].Chan[1].UserFlag==0'
flag0='Gate3[1].Chan[0].UserFlag==0'
flag1='Gate3[1].Chan[1].UserFlag==0'
prg = '''
//L0=Sys.PhaseCount
//send 1"sync0 %d:%d\\n",Sys.PhaseCount,Sys.PhaseCount-L0
@@ -109,6 +72,76 @@ close
'''.format(plcId=plcId, crdId=crdId, flag0=flag0, flag1=flag1)
self.sync_prg = prg
self.sync_run = '&{crdId}b{prgId}r'''.format(prgId=prgId, plcId=plcId, crdId=crdId)
if mode==2:
notFlag1 = 'Gate3[1].Chan[1].UserFlag!=0'
try:
prop=kwargs['prop'] #proportional value to adapt speed
except KeyError:
prop = 1E-4
try:
maxJitter = kwargs['maxJitter'] # proportional value to adapt speed
except KeyError:
maxJitter=100
prg='''close all buffers
disable plc {plcId}
open plc {plcId}
Coord[1].DesTimeBase=Sys.ServoPeriod //reset to 100% timebase
Coord[1].Q[0]=0 //set CryPos counter to 0
while({flag0}){{}} //wait Jungfrau start Trigger
while({flag1}){{}} //ESx detector trigger
L0=0 //CryPos counter
//L1 ServoCount event 1
//L2 jitter: neg:motion lags trigger,pos:motion leads trigger
while(L0>=0) //as long we are not at the last point
{{
L0=Coord[1].Q[0] //CryPos counter
while({flag1} && L0==Coord[1].Q[0]){{}} //wait for event
L1=Sys.ServoCount //a event happened
L2=1000
//send 1"event\\n"
if(L0==Coord[1].Q[0])
{{//it was a trigger
//send 1"trigger %d %d\\n",L0,L1
while(Sys.ServoCount<L1+{maxJitter}) //wait half a trigger period for event
{{
if(L0!=Coord[1].Q[0])
{{//event CryPos counter
L2=L1-Sys.ServoCount //jitter: neg:motion lags trigger
break
}}
}}
}}
else
{{//it was a CryPos counter
//send 1"CryPos%d %d\\n",L0,L1
while(Sys.ServoCount<L1+{maxJitter}) //wait half a trigger period for event
{{
if({notFlag1})
{{//event trigger
L2=Sys.ServoCount-L1 //jitter: pos:motion leads trigger
break
}}
}}
}}
if(L2!=1000)
{{
L3=Sys.ServoPeriod*(1-{prop}*L2)
//send 1"jitter %d timebase: %f\\n",L2,L3
Coord[1].DesTimeBase=L3
}}
}}
disable plc {plcId}
close
enable plc {plcId}
'''.format(plcId=plcId, crdId=crdId, flag0=flag0, flag1=flag1, notFlag1=notFlag1, prop=prop, maxJitter=maxJitter)
comm=self.comm
if comm is not None:
gpascii=comm.gpascii
gpascii.send_block(prg)
def run(self):
'runs the code sync_run which has been generated with setup_sync()'

View File

@@ -91,12 +91,12 @@ class MPLCanvasErr(FigureCanvas):
def OnBtnPress(self, event):
"""on button press we will see if the mouse is over us and store some data"""
print(dir(event.guiEvent))
#print(dir(event.guiEvent))
return
def OnBtnRelease(self, event):
"""on release we reset the press data"""
print(dir(event.guiEvent))
#print(dir(event.guiEvent))
#self.OnMouse(event)
return

View File

@@ -100,12 +100,12 @@ class MPLCanvasVelo(FigureCanvas):
def OnBtnPress(self, event):
"""on button press we will see if the mouse is over us and store some data"""
print(dir(event.guiEvent))
#print(dir(event.guiEvent))
return
def OnBtnRelease(self, event):
"""on release we reset the press data"""
print(dir(event.guiEvent))
#print(dir(event.guiEvent))
#self.OnMouse(event)
return

View File

@@ -46,34 +46,20 @@ class MPLCanvasImg(FigureCanvas):
self.fig=fig
self.ax=ax
def InitChild(self,pts,rec):
def InitChild(self,doc):
fig=self.fig
ax=self.ax
pts=doc.fh['pts']
rec=doc.fh['rec']
idxTrigger=doc.idxTrigger
#res=rot.ActPos,x.ActPos,y.ActPos,rot.DesPos,x.DesPos,y.DesPos
#idx 0 1 2 3 4 5
#idx=np.ndarray(shape=len(pts),dtype=np.int32)
#for i in range(len(pts)):
# l=rec[:,(3,2)]-pts[i,:]
# l2=l[:,0]**2+l[:,1]**2
# idx[i]=np.argmin(l2)
idx=[]
for i in range(len(pts)):
l=rec[:,(3,2)]-pts[i,:]
l2=l[:,0]**2+l[:,1]**2
idx.extend(np.where(l2<1)[0].tolist())
#print idx
recPts=rec[idx,:]
#f2 = plt.figure()
#a2 = f2.add_axes([0.075,0.075,0.85,0.85])
#a2.plot(rec[:,1],'r-',label='ptsDot') #,picker=5 default value
#a2.plot(rec[:,0],'g-',label='ptsDot') #,picker=5 default value
#a2.plot(rec[:,3],'r--',label='ptsDot') #,picker=5 default value
#a2.plot(rec[:,2],'g--',label='ptsDot') #,picker=5 default value
#plt.show()
# idx=[]
# for i in range(len(pts)):
# l=rec[:,(3,2)]-pts[i,:]
# l2=l[:,0]**2+l[:,1]**2
# idx.extend(np.where(l2<1)[0].tolist())
# recPts=rec[idx,:]
hl=[]
hl+=ax.plot(pts[:,0],pts[:,1],'r.',label='ptsDot') #,picker=5 default value
@@ -86,7 +72,13 @@ class MPLCanvasImg(FigureCanvas):
hl+=ax.plot(rec[:, 3], rec[:, 2], 'b-',label='recDesPos')
hl+=ax.plot(rec[:,1],rec[:,0],'g-',label='recActPos')
hl+=ax.plot(recPts[:,1],recPts[:,0],'g.',label='recDot')
#hl+=ax.plot(recPts[:,1],recPts[:,0],'g.',label='recDot')
hl+=ax.plot(rec[-1:,1],rec[-1:,0],'r.--',label='recNxt')
#hh=ax.plot(rec[idxTrigger,1],rec[idxTrigger,0],'rx',label='trig')
#hh[0].get_markeredgewidth();hh[0].get_markersize()
hl+=ax.plot(rec[idxTrigger,1],rec[idxTrigger,0],'rx',label='trig',markeredgewidth=1.,markersize=6.)
#hl+=ax.plot(recPts[:,1],recPts[:,0],'g.',label='recDot')
ax.xaxis.set_label_text('x-pos um')
ax.yaxis.set_label_text('y-pos um')
ax.axis('equal')
@@ -149,7 +141,7 @@ class MPLCanvasImg(FigureCanvas):
def OnBtnPress(self, event):
"""on button press we will see if the mouse is over us and store some data"""
print(dir(event.guiEvent))
#print(dir(event.guiEvent))
return
if event.inaxes == self.colBar.ax:
#if event.guiEvent.LeftDClick()==True:
@@ -247,7 +239,7 @@ class MAxyPlotFrame(wx.Frame):
#wxAxCtrl.SetCallback(MAxyPlotFrame.OnSetView,wxAxCtrl)
#sizer.Add(wxAxCtrl.sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, border=5)
canvas.InitChild(doc.fh['pts'],doc.fh['rec'])
canvas.InitChild(doc)
#self.Fit()
self.Centre()
@@ -295,6 +287,7 @@ class MAxyPlotFrame(wx.Frame):
hl=canvas.hl
hl[2].set_data(rec[:idx+1, 3], rec[:idx+1, 2])
hl[3].set_data(rec[:idx+1, 1], rec[:idx+1, 0])
hl[4].set_data(rec[idx:idx+10, 1], rec[idx:idx+10, 0])
#ax.draw_artist(hl[2])
x=ax.get_xlim();x=(x[1]-x[0])/2;
y=ax.get_ylim();y=(y[1]-y[0])/2;

View File

@@ -148,21 +148,22 @@ class MAMainFrame(wx.Frame):
l=rec[idx:idx+rng,(3,2)]-pts[i,:]
l2=l[:,0]**2+l[:,1]**2
ofs=l2.argmin()
print(l2[ofs])
#print(l2[ofs])
idx+=ofs
idxInPos.append(idx)
idx+=rng/2
doc.idxInPos=idxInPos=np.array(idxInPos)
#jitter=idxInPos-idxTrigger[:idxInPos.shape[0]]
self.SetStatusText('scaling of DesTimeBase: %f'%(float(idxInPos[-1])/idxTrigger[idxInPos.shape[0]-1]))
#self.PlotJitter()
self.wxTimeCtrl.slider.SetRange(0,lenRec-1)
ts=fh['meta'].item()['timebase']
page=idxInPos[1]-idxInPos[0]
self.wxTimeCtrl.slider.SetPageSize(page)
self.wxTimeCtrl.slider.SetPageSize(idxInPos[1]-idxInPos[0])
self.wxPosCtrl.slider.SetRange(0,lenPts-1)
self.wxPosCtrl.slider.SetPageSize(10)
self.wxTrigCtrl.slider.SetRange(0,idxTrigger.shape[0]-1)
self.wxTrigCtrl.slider.SetPageSize(10)
def CloseFile(self):
#http://docs.wxwidgets.org/2.8/wx_windowdeletionoverview.html#windowdeletionoverview
@@ -173,6 +174,26 @@ class MAMainFrame(wx.Frame):
except AttributeError as e:
pass
def PlotJitter(self):
doc=self.doc
fh=doc.fh
idxInPos=doc.idxInPos
idxTrigger=doc.idxTrigger
#display the jitter of inPos->trigger
ts=fh['meta'].item()['timebase']
jitter=idxTrigger[:idxInPos.shape[0]]-idxInPos
import matplotlib.pyplot as plt # used for the colormaps
plt.figure('jitter inPos -> trigger: scaling of DesTimeBase: %f'%(float(idxInPos[-1])/idxTrigger[idxInPos.shape[0]-1]))
plt.plot(jitter*ts)
plt.ylabel('jitter in ms')
plt.ylabel('point idx')
plt.show()
def DispJitter(self,idxInPos,idxTrigger):
doc=self.doc
ts=doc.fh['meta'].item()['timebase']
self.SetStatusText("Jitter: %.2gms scaling: %.6g"%((idxTrigger-idxInPos)*ts,float(idxInPos)/idxTrigger))
def OnOpen(self, event):
dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), '','numpy files (*.npz;*.npy)|*.npz;*.npy|all (*.*)|*.*', wx.OPEN|wx.FD_CHANGE_DIR)
if dlg.ShowModal() == wx.ID_OK:
@@ -287,40 +308,43 @@ rec
pass
@staticmethod
def OnSetTime(usrData, value, msg):
def OnSetTime(usrData, idxRec, msg):
'called when the time slider has been changed'
#print('OnSetTime', usrData, value, msg)
view=usrData.slider.Parent
doc=view.doc
idx=np.argmin(abs(doc.idxInPos-value))
view.wxPosCtrl.SetValue(idx)
idx=np.argmin(abs(doc.idxTrigger-value))
view.wxTrigCtrl.SetValue(idx)
doc.Update(view,0,value)
idxInPos=np.argmin(abs(doc.idxInPos-idxRec))
view.wxPosCtrl.SetValue(idxInPos)
idxTrigger=np.argmin(abs(doc.idxTrigger-idxRec))
view.wxTrigCtrl.SetValue(idxTrigger)
view.DispJitter(doc.idxInPos[idxInPos],doc.idxTrigger[idxTrigger])
doc.Update(view,0,idxRec)
@staticmethod
def OnSetIdxInPos(usrData, value, msg):
def OnSetIdxInPos(usrData, idxInPos, msg):
'called when the idxInPos slider has been changed'
#print('OnSetPosIdx', usrData, value, msg)
view = usrData.slider.Parent
doc = view.doc
value=doc.idxInPos[value]
view.wxTimeCtrl.SetValue(value)
idx=np.argmin(abs(doc.idxTrigger-value))
view.wxTrigCtrl.SetValue(idx)
doc.Update(view, 0, value)
idxRec=doc.idxInPos[idxInPos]
view.wxTimeCtrl.SetValue(idxRec)
idxTrigger=np.argmin(abs(doc.idxTrigger-idxRec))
view.wxTrigCtrl.SetValue(idxTrigger)
view.DispJitter(doc.idxInPos[idxInPos],doc.idxTrigger[idxTrigger])
doc.Update(view, 0, idxRec)
@staticmethod
def OnSetIdxTrigger(usrData, value, msg):
def OnSetIdxTrigger(usrData, idxTrigger, msg):
'called when the idxTrigger slider has been changed'
#print('OnSetIdxTrigger', usrData, value, msg)
view = usrData.slider.Parent
doc = view.doc
value=doc.idxTrigger[value]
view.wxTimeCtrl.SetValue(value)
idx=np.argmin(abs(doc.idxInPos-value))
view.wxPosCtrl.SetValue(idx)
doc.Update(view, 0, value)
idxRec=doc.idxTrigger[idxTrigger]
view.wxTimeCtrl.SetValue(idxRec)
idxInPos=np.argmin(abs(doc.idxInPos-idxRec))
view.wxPosCtrl.SetValue(idxInPos)
view.DispJitter(doc.idxInPos[idxInPos],doc.idxTrigger[idxTrigger])
doc.Update(view, 0, idxRec)
if __name__ == '__main__':

View File

@@ -184,9 +184,14 @@ class ShapePath(MotionBase):
1. generates program <prgId> and saves to fnPrg
the type of generated program is defined by <mode>
2. runs the program on the deltatau
mode=-1 jog a 10mm square
mode=0 linear motion
mode=1 pvt motion
mode=2 spline motion
kwargs:
acq_per : acquire period: acquire data all acq_per servo loops (default=1)
pt2pt_time : time to move from one point to the next point
sync_frq : synchronization mark all n points
'''
prg=['close all buffers','open prog %d'%(prgId)]
comm=self.comm
@@ -229,6 +234,10 @@ class ShapePath(MotionBase):
cnt=kwargs['cnt'] #move path multiple times
except KeyError:
cnt=1
try:
sync_frq=kwargs['sync_frq'] #synchronization mark all n points
except KeyError:
sync_frq=10
try:
pt=self.ptsCorr
except AttributeError:
@@ -259,7 +268,10 @@ class ShapePath(MotionBase):
prg.append('N100:')
prg.append(' pvt%g abs'%pt2pt_time) #100ms to next position
for idx in range(1,pv.shape[0]):
if sync_frq is not None and idx%sync_frq==0:
prg.append('Coord[1].Q[0]=%d'%(idx))
prg.append('X%g:%g Y%g:%g'%tuple(pv[idx,(0,2,1,3)]))
prg.append('Coord[1].Q[0]=%d' % (idx))
prg.append('X%g Y%g' % tuple(pv[-1, (0,1)]))
if cnt>1:
prg.append('dwell 10')
@@ -273,6 +285,8 @@ class ShapePath(MotionBase):
prg.append('}')
else:
prg.append('dwell 1000')
if sync_frq is not None:
prg.append('Coord[1].Q[0]=-1')
prg.append('Gather.Enable=0')
elif mode==2: #### spline motion
try:
@@ -437,28 +451,30 @@ class ShapePath(MotionBase):
idxTrigger = np.where(np.diff(idxTrigger) == 1)[0] + 1
idxInPos = [] # first point at idx 0
try: # find approximate distance of 2 points
rng = idxTrigger[2] - idxTrigger[1]
rng = idxTrigger[2]-idxTrigger[1]
except IndexError:
rng = int(lenRec / lenPts)
idx = rng / 2
rng = int(lenRec/lenPts)
idx = rng/2
for i in range(lenPts):
l = rec[idx:idx + rng, (3, 2)] - pts[i, :]
l2 = l[:, 0] ** 2 + l[:, 1] ** 2
ofs = l2.argmin()
print(l2[ofs])
idx += ofs
l=rec[idx:idx+rng,(3,2)]-pts[i,:]
l2=l[:,0]**2+l[:,1]**2
ofs=l2.argmin()
#print(l2[ofs])
idx+=ofs
idxInPos.append(idx)
idx += rng / 2
idx+= rng/2
idxInPos = np.array(idxInPos)
jitter=idxInPos-idxTrigger[:idxInPos.shape[0]]
jitter=idxTrigger[:idxInPos.shape[0]]-idxInPos
fig = plt.figure('trigger jitter')
ax = fig.add_subplot(1, 1, 1)
hl = []
hl += ax.plot(jitter*ts, 'b-',label='jitter')
ax.xaxis.set_label_text('position idx')
ax.yaxis.set_label_text('jitter (ms)')
ax.yaxis.set_label_text('jitter motion (ms)')
print('scaling of DesTimeBase: %f'%(float(idxInPos[-1])/idxTrigger[idxInPos.shape[0]-1]))
plt.show()
@@ -623,8 +639,8 @@ if __name__=='__main__':
sp.setup_gather(acq_per=2)
#setup_sync(self, crdId=1, prgId=2, plcId=2, mode=0, **kwargs):
#sp.setup_sync() #no sync at all
#sp.setup_sync(mode=1) #sync with timing system (PLC)
sp.setup_sync(mode=2) #sync with timing system (PROG)
#sp.setup_sync(mode=1) #sync with timing system (PROG)
sp.setup_sync(mode=2) #sync with timing system and PLC to sync speed (PROG)
#sp.gen_grid_points(w=2,h=2,pitch=50,rnd=.2);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=10,acq_per=1)
#sp.gen_swissmx_points(width=1000,ofs=(-500,0));sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=40,acq_per=1)
@@ -647,7 +663,7 @@ if __name__=='__main__':
sp.run()
trigger(0.5)
sp.gather_upload(fnRec=fn+'.npz')
sp.plot_gather()
sp.plot_gather(mode=8)
# cfg={"points": [[100,523],[635,632],[756,213]],"sequencer":[sort_points,move_trj+')']}