44#! DBDEND
55
66
7-
7+ record(transform, "$(P)$(Q):setMonitor") {
8+ field(INPA, "$(P)$(Q):x.SET CP")
9+ field(INPB, "$(P)$(Q):y.SET CP")
10+ field(INPC, "$(P)$(Q):z.SET CP")
11+ field(INPD, "$(P)$(Q):ax.SET CP")
12+ field(INPE, "$(P)$(Q):ay.SET CP")
13+ field(INPF, "$(P)$(Q):az.SET CP")
14+ field(CLCG, "a||b||c||d||e||f")
15+ field(OUTG, "$(P)$(T).SET CA")
16+ }
17+
18+ # We already have a mechanism for stopping the table when any soft motor's
19+ # STOP field is 1. Reimplement existing table:stop PV to drive that mechanism
820record(bo, "$(P)$(Q):stop") {
921 field(DTYP, "Soft Channel")
10- field(OUT, "$(P)$(Q):stop1.A PP MS")
22+ field(DOL, "1")
23+ field(OUT, "$(P)$(Q):x.STOP PP MS")
1124}
1225
1326record(bo, "$(P)$(Q):update") {
@@ -35,7 +48,6 @@ record(calc, "$(P)$(Q):geomIsSRI") {
3548 field(INPA, "$(P)$(T).GEOM CP MS")
3649}
3750
38-
3951record(table, "$(P)$(T)") {
4052 field(DESC, "Optical Table")
4153 field(LX, "510")
@@ -92,6 +104,64 @@ record(table, "$(P)$(T)") {
92104 field(FLNK, "$(P)$(Q):ifValuesWritten")
93105}
94106
107+ # We're going to monitor the STOP fields of the soft motors, so we know when
108+ # the user told a motor to stop. We're also going to write to all of those
109+ # STOP fields when we detect that the user wants the table to stop. We need
110+ # to distinguish our writes from the user's writes.
111+ record(bo, "$(P)$(Q):weSaidStop") {
112+ #field(TPRO, "1")
113+ field(ZNAM, "No")
114+ field(ONAM, "Yes")
115+ }
116+ record(transform, "$(P)$(Q):monitorStop") {
117+ #field(TPRO, "1")
118+ field(SDIS, "$(P)$(Q):weSaidStop")
119+ field(DISV, "1")
120+ # listen to our soft motors' .STOP fields
121+ field(INPA, "$(P)$(Q):x.STOP CP MS")
122+ field(INPB, "$(P)$(Q):y.STOP CP MS")
123+ field(INPC, "$(P)$(Q):z.STOP CP MS")
124+ field(INPD, "$(P)$(Q):ax.STOP CP MS")
125+ field(INPE, "$(P)$(Q):ay.STOP CP MS")
126+ field(INPF, "$(P)$(Q):az.STOP CP MS")
127+ # I==any soft motor was told to stop
128+ field(CLCI, "a||b||c||d||e||f")
129+ field(OUTI, "$(P)$(Q):weSaidStop PP MS")
130+ # don't write to the .STOP field that triggered us to process
131+ field(CMTJ, "bitmask for doStop")
132+ field(CLCJ, "!a+!b*2+!c*4+!d*8+!e*16+!f*32+64+128")
133+ field(OUTK, "$(P)$(Q):doStop.PROC PP")
134+ }
135+ record(seq, "$(P)$(Q):doStop") {
136+ #field(TPRO, "1")
137+ field(SDIS, "$(P)$(Q):weSaidStop")
138+ field(DISV, "0")
139+ field(SELL, "$(P)$(Q):monitorStop.J")
140+ field(SELM, "Mask")
141+ field(DOL1, "1")
142+ field(LNK1, "$(P)$(Q):x.STOP PP")
143+ field(DOL2, "1")
144+ field(LNK2, "$(P)$(Q):y.STOP PP")
145+ field(DOL3, "1")
146+ field(LNK3, "$(P)$(Q):z.STOP PP")
147+ field(DOL4, "1")
148+ field(LNK4, "$(P)$(Q):ax.STOP PP")
149+ field(DOL5, "1")
150+ field(LNK5, "$(P)$(Q):ay.STOP PP")
151+ field(DOL6, "1")
152+ field(LNK6, "$(P)$(Q):az.STOP PP")
153+ field(DOL7, "1")
154+ field(LNK7, "$(P)$(Q):stop1.A PP")
155+ # We need to wait for all the postings resulting from puts to stop_*
156+ # fields to be received by monitorStop's CP links before enabling
157+ # that record to respond to new postings of those fields. Writing
158+ # with a CA link should ensure that, but delaying by 0.1 s provides
159+ # additional protection.
160+ field(DOL8, "0")
161+ field(DLY8, ".1")
162+ field(LNK8, "$(P)$(Q):weSaidStop CA")
163+ }
164+
95165record(transform, "$(P)$(Q):stop1") {
96166 field(DESC, "Table-stop distribution")
97167 field(CLCB, "a")
@@ -371,19 +441,6 @@ record(fanout, "$(P)$(Q)fpInit1") {
371441 field(LNK5, "$(P)$(Q)fpSelect.PROC PP")
372442}
373443
374- record(motor, "$(P)$(Q):x") {
375- field(DTYP, "Soft Channel")
376- field(OUT, "$(P)$(T).X PP")
377- field(RDBL, "$(P)$(T).EX")
378- field(MRES, ".00001")
379- field(RRES, "1")
380- field(URIP, "Yes")
381- field(PREC, "$(PREC=3)")
382- field(RTRY, "0")
383- field(DINP, "$(P)$(Q):done.VAL")
384- }
385-
386-
387444record(sseq, "$(P)$(Q):writeMotors") {
388445 field(DO1, "1")
389446 field(LNK1, "$(P)$(Q):startMove.DO1 PP")
@@ -418,8 +475,27 @@ record(dfanout, "$(P)$(Q):isSoftFan") {
418475 field(OUTF, "$(P)$(Q):az.LOCK")
419476}
420477
478+ record(motor, "$(P)$(Q):x") {
479+ # alias is for spec, to make it easier to manage configuration of soft motors
480+ alias("$(P)$(Q):m1")
481+ field(DTYP, "Soft Channel")
482+ # ignore the SET field, we're going to implement it in the database, rather
483+ # than in individual motor records
484+ field(IGSET, "1")
485+ field(OUT, "$(P)$(T).X PP")
486+ field(RDBL, "$(P)$(T).EX")
487+ field(MRES, ".00001")
488+ field(RRES, "1")
489+ field(URIP, "Yes")
490+ field(PREC, "$(PREC=3)")
491+ field(RTRY, "0")
492+ field(DINP, "$(P)$(Q):done.VAL")
493+ }
494+
421495record(motor, "$(P)$(Q):y") {
496+ alias("$(P)$(Q):m2")
422497 field(DTYP, "Soft Channel")
498+ field(IGSET, "1")
423499 field(OUT, "$(P)$(T).Y PP")
424500 field(RDBL, "$(P)$(T).EY")
425501 field(MRES, ".00001")
@@ -432,7 +508,9 @@ record(motor, "$(P)$(Q):y") {
432508}
433509
434510record(motor, "$(P)$(Q):z") {
511+ alias("$(P)$(Q):m3")
435512 field(DTYP, "Soft Channel")
513+ field(IGSET, "1")
436514 field(OUT, "$(P)$(T).Z PP")
437515 field(RDBL, "$(P)$(T).EZ")
438516 field(MRES, ".00001")
@@ -445,7 +523,9 @@ record(motor, "$(P)$(Q):z") {
445523}
446524
447525record(motor, "$(P)$(Q):ax") {
526+ alias("$(P)$(Q):m4")
448527 field(DTYP, "Soft Channel")
528+ field(IGSET, "1")
449529 field(OUT, "$(P)$(T).AX PP")
450530 field(RDBL, "$(P)$(T).EAX")
451531 field(MRES, ".00001")
@@ -458,7 +538,9 @@ record(motor, "$(P)$(Q):ax") {
458538}
459539
460540record(motor, "$(P)$(Q):ay") {
541+ alias("$(P)$(Q):m5")
461542 field(DTYP, "Soft Channel")
543+ field(IGSET, "1")
462544 field(OUT, "$(P)$(T).AY PP")
463545 field(RDBL, "$(P)$(T).EAY")
464546 field(MRES, ".00001")
@@ -472,7 +554,9 @@ record(motor, "$(P)$(Q):ay") {
472554
473555
474556record(motor, "$(P)$(Q):az") {
557+ alias("$(P)$(Q):m6")
475558 field(DTYP, "Soft Channel")
559+ field(IGSET, "1")
476560 field(OUT, "$(P)$(T).AZ PP")
477561 field(RDBL, "$(P)$(T).EAZ")
478562 field(MRES, ".00001")
@@ -612,6 +696,31 @@ record(calcout, "$(P)$(Q):done") {
612696 field(OOPT, "Transition To Non-zero")
613697}
614698
699+ # Strategy: We need to find out when all motors have finished executing a move
700+ # that we started. To do this without a race condition, we set a gate PV to
701+ # "open", write to motors, and then have the last motor written to set the gate
702+ # PV to "closed" when it finishes. But we don't know which motor will be the
703+ # last motor written to, because the table record can work with fewer than six
704+ # motors, and we don't know which motors don't actually exist.
705+ # $(P)$(Q):lastMotor figures this out at init time.
706+
707+ # Evidently, $(P)$(Q):lastMotor's PINI==RUNNING field isn't getting
708+ # $(P)$(Q):closeGate.INPA initialized. This seems bulletproof.
709+ record(scalcout, "$(P)$(Q):initLastMotor") {
710+ #field(TPRO, "1")
711+ field(SCAN, "10 second")
712+ # Note that this must be a CA link so it can read a link field
713+ field(INAA, "$(P)$(Q):closeGate.INPA CA")
714+ field(CALC, "aa==''")
715+ field(OOPT, "When Zero")
716+ field(DOPT, "Use OCAL")
717+ field(OCAL, "'Passive'")
718+ field(OUT, "$(P)$(Q):initLastMotor.SCAN CA")
719+ field(FLNK, "$(P)$(Q):lastMotor")
720+ }
721+
722+ # Find the last motor that will be written to. (Ignore motors that
723+ # don't actually exist.) Write that motor's .DMOV PV
615724record(scalcout, "$(P)$(Q):lastMotor") {
616725 field(CALC, "(f?ff:e?ee:d?dd:c?cc:b?bb:aa)[0,' ']+' CP'")
617726 field(INPA, "$(P)$(Q):dmov.INAV CA")
0 commit comments