Hi,
For the ‘Wait’ to work, you’ll also need to use CA link attributes on LNK1 and LNK2 .
One reason for a controller based solution would be if you need a different acceleration profile from those supported for normal moves (for example Sin profile, instead of a standard S-curve or trapezoid).
But if a standard profile works, it’s much easier via database logic.
Cheers,
Matt
From: Tech-talk <tech-talk-bounces at aps.anl.gov>
On Behalf Of Mark Rivers via Tech-talk
Sent: Friday, February 6, 2026 5:01 PM
To: EPICS Tech-Talk <tech-talk at aps.anl.gov>; Jesse Hopkins <jhopkins1 at illinoistech.edu>
Subject: [EXTERNAL] Re: Help with continuous oscillation of a motor
Also, this is a better link to the documentation: https: //epics-modules. github. io/calc/sseqRecord. html
Mark From: Mark Rivers <rivers@ cars. uchicago. edu>
Sent: Friday, February 6, 2026 3: 59 PM To: EPICS Tech-Talk <tech-talk@ aps. anl. gov>;
Also, this is a better link to the documentation:
Whoops, you need to delete this line:
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(LNK1, "$(MOTOR).VAL PP")
field(LNK2, "$(MOTOR).VAL PP")
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(LNK0, "$(MOTOR).VAL PP")
field(LNK1, "$(MOTOR).VAL PP")
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(CALC, "((A >= B) || (A <= C)) ? 1: 0")
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
|