|
Whoops, you need to delete this line:
field(SCAN, "Passive")
Mark
From: Tech-talk <tech-talk-bounces at aps.anl.gov> on behalf of Mark Rivers via Tech-talk <tech-talk at aps.anl.gov>
Sent: Friday, February 6, 2026 3:56 PM
To: EPICS Tech-Talk <tech-talk at aps.anl.gov>; Jesse Hopkins <jhopkins1 at illinoistech.edu>
Subject: Re: Help with continuous oscillation of a motor
Hi Jesse,
I think there is a simpler solution. Use the sseq record rather than the seq record. You then don't need the Check record at all.
The sseq record has WAIT fields that force the record to wait for one output link to complete before going to the next step. You can just set WAIT1 and WAIT2 to "Wait". Then it will first move one direction and then the other. By setting SCAN=0.1 second
it will then repeat the operation within 0.1 seconds. You may be able to just FLNK to itself which would have no delay.
Here is a database that I think will work, though I have not tested it.
record(sseq, "$(P)Toast:$(D)Move") {
field(DESC, "Toast motor")
field(SCAN, "Passive")
field(DOL1, "5")
field(WAIT1, "Wait")
field(LNK1, "$(MOTOR).VAL PP")
field(DOL2, "-5")
field(WAIT2, "Wait")
field(LNK2, "$(MOTOR).VAL PP")
field(SCAN, ".1 second")
}
Mark
From: Tech-talk <tech-talk-bounces at aps.anl.gov> on behalf of Jesse Hopkins via Tech-talk <tech-talk at aps.anl.gov>
Sent: Friday, February 6, 2026 3:44 PM
To: EPICS Tech-Talk <tech-talk at aps.anl.gov>
Subject: Help with continuous oscillation of a motor
Hi folks,
For certain experiments on our beamline we like to continuously oscillate the sample in the beam to spread out the x-ray dose and reduce radiation damage (we call this process “toasting”). For the past many years we’ve done this using control code specific
to a (Parker) motor controller dedicated to these experiments. This controller is reaching end of life, and I’m hoping to get a more general solution to this that only depends on logic in an IOC, so that whenever we have to update controllers in the future
we don’t have to rewrite this oscillation code.
I took a quick look through the archives of this list and some of the SynApps modules and didn’t see anything that would already do this for me, so I set out to make my own. My first attempt is to just use a combination of a calcout record and a seq record.
The seq record uses a selection mask to either send the positive or negative endpoint to the motor record to start a move. The calcout record checks whether the motor is at the positive or negative endpoint and then sets the mask value for the seq and tells
the seq to process. Then I set the motor FLNK to point to the calcout record PROC.
The problem that I’m running into is that the calcout seems to sometimes process a bit before the move is finished, or at least before the motor RBV has updated to the final position. You can start this process and it will run for a bit (5-10 cycles), and then
it will stop. When it stops, if you check the calcout .A record, which gets its value from the motor RBV, it will say something like -4.9784, while the motor RBV at the end of the move is something like -5.00380 for a move to -5 (note: I haven’t yet thought
about how to deal with deadband or step resolution in terms of determining which end the motor is at).
I’m wondering if there’s either another way to tell the calcout to process or if there’s a better way to do this that will create the desired effect. I should note that I don’t want to introduce any hardcoded delays anywhere because the point of this is a smooth
turnaround so that the beam doesn’t linger and preferentially damage spots of the sample at the end of the scans (also, unreliable).
Here are my test seq and calcout records (with fixed values for positions for this test, but in the future could point to aouts that the user sets for each limit, for example):
record(seq, "$(P)Toast:$(D)Move") {
field(DESC, "Toast motor")
field(SCAN, "Passive")
field(DOL0, "5")
field(LNK0, "$(MOTOR).VAL PP")
field(DOL1, "-5")
field(LNK1, "$(MOTOR).VAL PP")
field(SELM, "2")
field(SHFT, "0")
#field(SELN, "1")
field(SELL, "$(P)Toast:$(D)Check.OVAL")
}
record(calcout, "$(P)Toast:$(D)Check") {
field(DESC, "Check if toast motor moves")
field(INPA, "$(MOTOR).RBV")
#field(INPA, "0")
field(INPB, "5")
field(INPC, "-5")
field(CALC, "((A >= B) || (A <= C)) ? 1: 0")
field("OOPT", 3)
field("DOPT", 1)
field("OCAL", "(A >= B) ? 2 : 1")
field("OUT", "$(P)Toast:$(D)Move PP")
}
If this issue is just a fundamental limitation of the motor record processing I was thinking the next thing to do would be to write a sequence record, but I haven’t messed with SNL before and it seemed a lot easier to try these standard EPICS records first.
Any advice would be appreciated.
All the best.
- Jesse
----
Jesse Hopkins, PhD (he/him)
Director, BioCAT
Sector 18, Advanced Photon Source
Research Associate Professor, Illinois Tech
|