diff --git a/Readme.md b/Readme.md index 6d6a948..0e7aa9a 100644 --- a/Readme.md +++ b/Readme.md @@ -1139,7 +1139,7 @@ zamofing_t@ganymede:~/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python$ cp -arL ~/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python/*.py \ - ~/Documents/prj/SwissFEL/PBTools/pbtools/ + ~/Documents/prj/SwissFEL/PBTools/pbtools/ \ /sf/bernina/config/swissmx/zamofing_t/ cp -arL ~/Documents/prj/SwissFEL/epics_ioc_modules/ESB_MX/python/PBMotionAnalyzer/*.py \ /sf/bernina/config/swissmx/zamofing_t/PBMotionAnalyzer/ diff --git a/matlab/SCRATCH.m b/matlab/SCRATCH.m index 84c0d41..a883955 100644 --- a/matlab/SCRATCH.m +++ b/matlab/SCRATCH.m @@ -163,12 +163,59 @@ open('stage_closed_loop.slx') controlSystemDesigner('bode',sys); controlSystemDesigner(1,sys); % <<<<<<<<< This opens a transferfiûnction that can be edited - +1 num=[8.32795069e-11, 1.04317228e-08, 6.68431323e-05, 3.31861324e-03, 7.32824533e+00]; den=[5.26156641e-18, 1.12897840e-14, 7.67853031e-12, 1.03201301e-08, 2.05154780e-06, 1.34279894e-03, 7.19229912e-02, 1.00000000e+00]; mot2=tf(num,den); controlSystemDesigner('bode',mot2); - - +end + + +m1=10; d1=10; k1=0; +m2=.3; d2=0.15; k2=100; +m3=1.2; d3=.04; k3=10; + +%k2 determines resonance frequency k2 higher -> resfrq higher +%d2 determines the damping d2=0 no damping d2=10 strong damping +%m2 determines how much energy is in the resonance + +%m1 is big compared to the other masses +%d1 is the speed dependant friction +%k1 can be set to 0, because these is no position for no force +%-> but this means for 1/s^2 the system is not observable any more + +ks=k1+k2+k3; +ds=d1+d2+d3; + +A=[ 0 1 0 0 0 0 ; + -ks/m1 -ds/m1 k2/m1 d2/m1 k3/m1 d3/m1 ; + 0 0 0 1 0 0 ; + k2/m2 d2/m2 -k2/m2 -d2/m2 0 0 ; + 0 0 0 0 0 1 ; + k3/m1 d3/m1 0 0 -k3/m3 -d3/m3 ]; +B=[ 0 1 0 0 0 0]'; +C=[ 1 0 0 0 0 0]; +D=[0]; + +ss1=ss(A,B,C,D); +bodeplot(ss1,{.1,1000}); +tf(ss1) + + +%simplified no resonance +A=[ 0 1 ; + -k1/m1 -d1/m1 ]; +B=[ 0 1 ]'; +C=[ 1 0 ]; +D=[0]; +ss1=ss(A,B,C,D); +bodeplot(ss1,{.1,1000}); +tf(ss1) +chkCtrlObsv(ss1,'') +%but with k1=0 or d1=0 the system is neither controllable nor observable +%-> the +p = eig(A); +disp(p); + +K = place(A,B,[-5 -10]); -end \ No newline at end of file diff --git a/python/MXMotion.py b/python/MXMotion.py index b720633..249e08d 100644 --- a/python/MXMotion.py +++ b/python/MXMotion.py @@ -73,11 +73,13 @@ class MotionBase: self.sync_prg = prg self.sync_run = '&{crdId}b{prgId}r'''.format(prgId=prgId, plcId=plcId, crdId=crdId) if mode==2: + #frequence jitter 50Hz Swissgrid: + #https://www.swissgrid.ch/de/home/operation/grid-data/current-data.html# notFlag1 = 'Gate3[1].Chan[1].UserFlag!=0' try: prop=kwargs['prop'] #proportional value to adapt speed except KeyError: - prop = 1E-4 + prop = .5E-4 try: maxJitter = kwargs['maxJitter'] # proportional value to adapt speed except KeyError: @@ -86,58 +88,67 @@ class MotionBase: 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 +Coord[1].Q[0]=0 //set syncCnt 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 +{syncCnt}=0 //sync counter, sent when motion is on a crystal +//{evnt} current PhaseCount event +//{jitter} jitter: neg:motion lags trigger,pos:motion leads trigger +//{syncEnvt} Prev syncEnvt PhaseCount +{sync2sync}=0 // last syncEnvt-syncEnvt PhaseCount +{dtb}=Sys.ServoPeriod //DesTimeBase +//{tmp} temp variable -while(L0>=0) //as long we are not at the last point +while({syncCnt}>=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 + {syncCnt}=Coord[1].Q[0] //sync counter + while({flag1} && {syncCnt}==Coord[1].Q[0]){{}} //wait for event + {evnt}=Sys.PhaseCount //a event happened + {jitter}=1000 //send 1"event\\n" - if(L0==Coord[1].Q[0]) + if({syncCnt}==Coord[1].Q[0]) {{//it was a trigger - //send 1"trigger %d %d\\n",L0,L1 - while(Sys.ServoCount0) {{ - L3=Sys.ServoPeriod*(1-{prop}*L2) - //send 1"jitter %d timebase: %f\\n",L2,L3 - Coord[1].DesTimeBase=L3 + {dtb}=Sys.ServoPeriod*(1-{prop}*{jitter}) + //{dtb}={dtb}*(1-{jitter}/{sync2sync}*.5) + //send 1"syncCnt %d jitter %d sync2sync %d timebase: %f\\n",{syncCnt},{jitter},{sync2sync},{dtb} + Coord[1].DesTimeBase={dtb} }} }} disable plc {plcId} close enable plc {plcId} -'''.format(plcId=plcId, crdId=crdId, flag0=flag0, flag1=flag1, notFlag1=notFlag1, prop=prop, maxJitter=maxJitter) +'''.format(plcId=plcId, crdId=crdId, flag0=flag0, flag1=flag1, notFlag1=notFlag1, prop=prop, maxJitter=maxJitter, + syncCnt='L0', evnt='L1', jitter='L2', syncEnvt='L3', sync2sync='L4', dtb='L5', tmp='L6') comm=self.comm if comm is not None: gpascii=comm.gpascii diff --git a/python/MXTuningDoc/fastStageTune.odt b/python/MXTuningDoc/fastStageTune.odt index 9a338da..f056f8d 100644 Binary files a/python/MXTuningDoc/fastStageTune.odt and b/python/MXTuningDoc/fastStageTune.odt differ diff --git a/python/helicalscan.py b/python/helicalscan.py index 8cce73e..5ec77ec 100755 --- a/python/helicalscan.py +++ b/python/helicalscan.py @@ -354,7 +354,7 @@ class HelicalScanGui(): def update_anim(self,frm): rec=self.helScn.rec - (cx, cz, w, fy)=rec[int(frm),:4];cx=-cx;cz=-cz + (cx, cz, w, fy)=rec[int(frm),:4];cx=-cx;cz=-cz #change sign because axes have neg direction w*=d2r/1000 # scale from deg to rad if self.manip: param = self.helScn.param @@ -781,7 +781,6 @@ a #1->I ''' pbParam=param.copy() - pbParam[:,(0,2)]=-pbParam[:,(0,2)] #change sign of x_0,x_1,z_0,z_1, because axes have neg direction sh=pbParam.shape s = ('z', 'y', 'x', 'r', 'phi') a = np.ones(sh[1], dtype=np.uint8).reshape(1, -1) @@ -790,7 +789,7 @@ a subsParam=dict(map(lambda k, i, v: (k + '_' + str(i), v), s * sh[0], c, pbParam.reshape(-1))) subsParam['d2r']=d2r/1000. subsParam['r2d']=1000./d2r - subsParam['cmt']='//' + subsParam['cmt']='' #'//' subs={'qCX':'L4', 'qCZ':'L5', 'qW':'L3', 'qFY':'L1', @@ -1003,6 +1002,7 @@ close elif mode==1: #### pvt motion #y=2.3 6.2 dx=0, dz=0 w=0..3600000 # 10 rev cnt= kwargs.get('cnt', 1) #move path multiple times + sync_frq=kwargs.get('sync_frq',10) # synchronization mark all n points cntVert = kwargs.get('cntVert', 12) cntHor = kwargs.get('cntHor', 4) hRng = kwargs.get('hRng', (-.2,.2)) @@ -1010,6 +1010,7 @@ close yRng = kwargs.get('yRng', self.param[:,1]) pt2pt_time = kwargs.get('pt2pt_time', 100) smt = kwargs.get('smt', 1) # SegMoveTime, default = 1ms -> velocity calc not yet 100% correct (smt=0 not 100% working) + numPt=cntVert*cntHor pt=np.zeros((numPt,4)) if cntHor>1: @@ -1049,7 +1050,8 @@ close prg.append(' Coord[1].SegMoveTime=%d'%smt) #to calculate every 1 ms the inverse kinematics prg.append(' pvt%g abs'%pt2pt_time) #100ms to next position for idx in range(1,pv.shape[0]): - #prg.append(' P2000=%d'%idx) + if sync_frq is not None and idx%sync_frq==0: + prg.append('Coord[1].Q[0]=%d'%(idx)) prg.append(' X%g:%g Z%g:%g B%g:%g Y%g:%g' % tuple(pv[idx, (0,4,1,5,2,6,3,7)])) #prg.append('Y%g:%g' % tuple(pv[idx, (5, 7)])) #prg.append('B%g:%g' %(idx*1000,0)) @@ -1065,9 +1067,9 @@ close 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') - - prg.append(' P1000=1') prg.append('close') #prg.append('&1\nb%dr\n'%prgId) @@ -1100,7 +1102,7 @@ close if __name__=='__main__': def run_test(args): test=args.test - args.host=None + #args.host=None if args.host is None: comm=gather=None @@ -1123,10 +1125,19 @@ if __name__=='__main__': HelicalScanTests.calcParamSim(hs) hs.setup_motion(mode=1,cntHor=5,cntVert=15,hRng=(-50,50),wRng=(0,120000),smt=0,pt2pt_time=200) + hs.setup_coord_trf() + hs.setup_sync(mode=0) # None: no sync at all mode=1: sync on timing UserFlag + hs.setup_gather() + hs.run() + print('wait until gather finished:') + fn = '/tmp/helicalscan' + hs.gather_upload(fn + '.npz') + hs.load_rec(fn + '.npz') + for manip in (False,True): hsg=HelicalScanGui(hs);hsg.interactive_cx_cz_w_fy(manip=manip) hsg=HelicalScanGui(hs);hsg.interactive_dx_dz_w_y(manip=manip) - #hsg=HelicalScanGui(hs);hsg.interactive_anim(manip=manip) + hsg=HelicalScanGui(hs);hsg.interactive_anim(manip=manip) return elif test==3: @@ -1153,9 +1164,9 @@ if __name__=='__main__': #cpx X0 Z0 B0 Y258 #cpx X0 Z0 B120000 Y258 - #hs.calcParam(x = ((-1154.4, 216.3, -250.7), ( -1330.2, 340.9, -230.4)), - # y = (1405.7,1019.2), - # z = ((-1309.6, -1010.9, -2410.3),( -1219.4, -918.8, -2510.4))) + hs.calcParam(x = ((-1154.4, 216.3, -250.7), ( -1330.2, 340.9, -230.4)), + y = (1405.7,1019.2), + z = ((-1309.6, -1010.9, -2410.3),( -1219.4, -918.8, -2510.4))) ### use simulation motors ### @@ -1171,7 +1182,7 @@ if __name__=='__main__': eval(s) hs.setup_coord_trf() - hs.setup_sync(mode=2) # None: no sync at all mode=1: sync on timing UserFlag + hs.setup_sync(mode=0) # None: no sync at all mode=1: sync on timing UserFlag hs.setup_gather() #hs.gen_prog(mode=-1) diff --git a/python/shapepath.py b/python/shapepath.py index 9d30cf3..8ede8a0 100755 --- a/python/shapepath.py +++ b/python/shapepath.py @@ -224,20 +224,10 @@ class ShapePath(MotionBase): prg.append('dwell 100') prg.append('Gather.Enable=0') elif mode==1: #### pvt motion - try: - pt2pt_time=kwargs['pt2pt_time'] #how many ms to move to next point (pt2pt_time) - except KeyError: - print('missing pt2pt_time, use default=100ms') - pt2pt_time=100. + pt2pt_time=kwargs.get('pt2pt_time', 100) self.meta['pt2pt_time']=pt2pt_time - try: - 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 + cnt=kwargs.get('cnt', 1) # move path multiple times + sync_frq=kwargs.get('sync_frq', 10) # synchronization mark all n points try: pt=self.ptsCorr except AttributeError: @@ -639,9 +629,9 @@ if __name__=='__main__': #sp.setup_gather(acq_per=1) #Gather.MaxLines=116508 580pts 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() #no sync at all #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.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) @@ -650,8 +640,8 @@ if __name__=='__main__': #sp.gen_grid_points(w=2,h=20,pitch=50,rnd=0);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=10,acq_per=1) #sp.gen_rand_points(n=500, scale=1000);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=10,acq_per=1) - #sp.gen_grid_points(w=30,h=30,pitch=50,rnd=0.2);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=40) - sp.gen_rand_points(n=400, scale=1000);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=10) + sp.gen_grid_points(w=30,h=30,pitch=50,rnd=0.2);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=40) + #sp.gen_rand_points(n=400, scale=1000);sp.sort_points(xy);sp.setup_motion(fnPrg=fn+'.prg',mode=1,pt2pt_time=10) #>>>setup gather and sync<<<