Experimental Physics and Industrial Control System
Hej again,
some more comments inside.
On 2024-01-18 01:47, Mark Rivers via Tech-talk wrote:
Hi Paul-Elie,
* I see that when creating an axis controller using the class
asynMotorController, it creates a lot of parameters then linked to a
motor record by their names (the string passed to the createParam()
function), is that correct?
Yes, that is correct.
* How does each axis has its own set of parameters while connected to
the same asynMotorController, is it because the asynMotorAxis class
creates an AsynUser which connects to the controller?
By doing this, it creates a separate set of parameters for the axis
with the pasynUser_?
No, that not quite correct. Asyn address devices using a string “port
name”, and an integer “address”. There is a single parameter table for
the controller. That is 2-D table [numParameters, numAxes]. So there
is an entry for the velocity of each axis, etc.
* I heavily rely on the record programming to carry out advanced
functions, such as backlash compensation and closed loop motion on
the encoder.
Do you have experience in those matters? How will the system reach a
specific encoder position? Will it use a linear term between encoder
ticks and motor steps?
But if the axis is not linear enough, there will be issues in
reaching the position?
If I understand correctly, usually motors use the FRAC and RTRY
mechanism to reach a encoder position with a certain number of retry?
In practice, how do most people operate with an encoder? Do they
use the motor in open loop and monitor the encoder, or do they set
UEIP at 1 and use FRAC motion?
Retries are most commonly used with stepper motors with encoders, or
some other readback mechanism. For servos we generally rely on the
controller to close the loop. The motor record is then not involved in
doing any feedback operations.
* My motors are able to do microstepping (decomposing a full step in
64 microsteps), should I implement this in the MRES field of the
record, as not being 200 full steps but 12800 microsteps?
I think so because I intend to give the motor positions in
microsteps. Is there a low limit on the MRES value?
Yes, that is correct.
We use the scaling function inside TwinCAT. Everything is commissioned
inside TwinCAT – number of (micro) steps,
how much the motor advances in mm/per revolution.
What you then see on the wire (between TwinCAT and the EPICS IOC)
is always in mm.
The commissioning needs to set the “In target position monitor windows”
(or so).
And then there is a time/timeout,
how long the motor (or encoder) needs to stay inside that window.
The commissioning of those 2 parameters is dependent on your real
hardware, how exact do you want to reach a position vs how exact
should the position be reached. The smaller the window, the longer
it may take for the position to stabilize. And that needs some
experience (and we let the motion control engineers do it)
However, the size of the window should correspond to the RBDB
field.
We read out that window size, and put it into this record:
record(ai,"$(PREFIX)$(MOTOR_NAME)-CfgRDBD-RB")
(Later it is forwarded into the “.RDBD” field of the motorRecord)
* On the system, we will be using absolute encoders, in that regard
the encoder ratio is always 1 to transfer to encoder counts, correct?
From my understanding, encoder ratio is mainly to interface with
incremental encoders converting to raw A/B ticks in full encoder tick.
* Because from what I gathered the motorEncoderPosition_ must be
linked to the Field REP and is the hardware counter of the encoder.
I think encoder ratio is for stepper motors, and is the ratio between
motor steps and encoder counts.
* Is there a filter of some sorts on the encoder counts field or
should I implement one directly on my Beckhoff controller and then
use the delayAfterMove parameter to allow the encoder counts to
stabilize?
Delay after move was mainly designed for servo controllers which may
decide to report that a move is complete before the final settling is
complete.
We do the filtering inside TwinCAT.
This functionality is already implemented in the NC blocks.
Is there a special reason, that you want to do this on our own, or is it
a lack
of understanding, which code that has already been written.
And is ready to use (inside TwinCAT) ?
* As I have absolute encoder on all axis, for an axis the parameter
motorhasEncoder_ is always at 1 as well as the motorStatusHomed_ .
However, to use and encoder, the record needs to have the record
field UEIP at 1.
Is it defined by the user, or should initialize UEIP at 1, in my
template defined in “motor.substitutions.irelec” adding the field UEIP
to my .db file (which is derived from basic_asyn_motor.db with some
field added)?
You can define it in your database or in autosave.
* From what I understand, the motor record is defined by the
motorRecord.cc and motorRecord.dbd and Model 3 drivers mainly
interface with the devMotorAsyn.c, is it correct?
Yes, that is correct.
A change on a field of the record will be processed by motorRecord.cc
and use the different functions I have redefined for my Driver (move,
poll, etc…), is it correct?
Yes, that is correct.
Does the devMotorAsyn.c defines the support entry table required to
process the record commands? (with the function start_trans, build_trans
and end_trans)
Yes, that is correct.
Looking through the code, I understood that for Model 2 Driver and
Support, one needs to defined the support entry table to process the
record commands? (ex: MCB4B_build_trans function)
Correct.
In Model 3, it seems the asynMotorController directly handles the
modification of its parameters by calling the needed function of an
axis. How does the record interface with the asynMotorController parameters?
I can’t find where devMotorAsyn.c writes to the asynMotorController
parameters.
It is done on the build_trans function.
https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L480 <https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L480>
It constructs a message and queues a request to write to the driver in
this line:
https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L634 <https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L634>
When the driver is ready the callback function is called
https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L654 <https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L654>
It then writes integer and floating point parameters in the lines
starting here:
https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L695 <https://github.com/epics-modules/motor/blob/f8a3239f0678f8f3d75fd5cf2f9c717dc3db9426/motorApp/MotorSrc/devMotorAsyn.c#L695>
Those values write to asynMotorController, which then calls the axis
functions.
Bottom line of this topic is: Can I use the record advanced functions
such as backlash compensation even with a Model 3 Driver?
Yes.
* The record always feed steps command to the controller if I
understood the code correctly.
Yes.
Hm.
Putting some light here:
We have 2 ½ coordinate systems:
The “dial” coordinates: That is what the mechanics define.
Example: 0.0 is the “middle of the beam”, you can move between -20.0 and
+20.0
That is the DVAL field
When the beam, for one reason or the other, the user can set an offset,
and/or change the direction. (The OFF and DIR fields)
That are the user coordinates, the VAL field.
Then we have the 3rd coordinate system, that are steps.
So for a controller (a stepper card), the workflow goes like this:
User wites to the VAL field, the motorRecord calculates the DVAL:
DVAL := (VAL – OFF) * direction
From there, we go into steps, somewhat simplified
steps = DVAL / fabs(MRES)
The steps are send into the driver, in our case, the model 3 driver.
Since the driver talks mm over the wire toward the controller,
we need to convert back from steps into mm again, reverting the
conversion.
<https://github.com/EuropeanSpallationSource/m-epics-ethercatmc/blob/master/ethercatmcApp/src/ethercatmcAxis.cpp#L618>
The similar logic does exist in other drivers, like XPS
<https://github.com/epics-motor/motorNewport/blob/d84cfe2bf5247475cebd8ce609c219e854c72742/newportApp/src/XPSAxis.cpp#L267>
The possible advantage is, that you can define a database, where units
are mm, but the controller uses µm.
We decided that we always use the same units in the motion controller as
in the EPICS database.
In other words, you can set MRES to a “dummy” value, like 1 µm.
For our purposes, I couldn’t fully make of my mind, if this conversion
forth-and-back is a good thing, or not.
So the ESS motor version has a flag, and when set,
the motorRecord skips the conversion:
<https://github.com/EuropeanSpallationSource/motor/blob/ess-master/motorApp/MotorSrc/motorDevSup.c#L46>
However as I don’t have a home routine (due to absolute encoders), the
open loop (step count) is reset at each hardware controller power reset.
Thus, the user should never do move_absolute in openloop. To disable it,
I would like the UEIP to be always at 1? Or something should be done to
set the open loop position using setposition based on the absolute
encoder at the start of the controller?
It could be the home procedure, just sending a setposition() to the
stepper based on the encoder position? Or does the home position require
motion?
Your home function does not need to do anything, it can just report the
absolute encoder position.
* I noticed there is no process of error catching on the motor and
error resetting, is that correct?
Should I always reset an axis in Error on my Beckhoff hardware
controller or should I include command on my EPICS driver to do so,
thus adding record for those specific actions?
Because for example, if my motor reaches a hardware limit switch
while in motion it will put itself in error state, which then needs
an error reset using the MC_Reset.
You can handle that in your driver.
Yes, as I wrote otherwise, that is addressed in the driver.
Mark
Sorry for the longish stuff, to make it even longer, here comes a log
from a write to the
VAL field:
# This is a log from the ESS version, which does have printouts
# inside the motorRecord.cc itself
# Motor is at 100, user wants to move to 101
# caput IOC:m1 101
# The user writes to the VAL field, EPICS calls the special function,
# twice. Once before the value is updated, once after
2024/01/18 08:13:28.292 [motorRecord.cc:3199 IOC:m1 01] special
fieldIdx=VAL value=100 after=0
2024/01/18 08:13:28.292 [motorRecord.cc:3199 IOC:m1 01] special
fieldIdx=VAL value=101 after=1
# After that, process() is called in motorRecord.cc
2024/01/18 08:13:28.292 [motorRecord.cc:1572 IOC:m1 01]
process:---------------------- begin reason=0 mip=0x0('')
2024/01/18 08:13:28.292 [motorRecord.cc:2724 IOC:m1 01] do_work: begin
udf=0 stat=0 nsta=0 spmg=GO commandedDval=100.000000 dval=100.000000
last.dval=100.000000 drbv=100.000000 mip=0x0('')
2024/01/18 08:13:28.292 [motorRecord.cc:2238 IOC:m1 01]
doDVALchangedOrNOTdoneMoving: begin commandedDval=100.000000
dval=101.000000 last.dval=100.000000 drbv=100.000000 mip=0x0('')
2024/01/18 08:13:28.292 [motorRecord.cc:2095 IOC:m1 01] doRetryOrDone
dval=101.000000 rdbd=0.100000 spdb=0.100000 bdst=0.000000 stat=0 rcnt=0
pref_dir=1 relpos=1.000000 relbpos=1.000000 drbv=100.000000
# Now the motorRecord has figured out, that a move() is needed
2024/01/18 08:13:28.292 [motorRecord.cc:2108 IOC:m1 01] mipSetBit
MOVE(Mo) old='' new=MOVE(Mo)
2024/01/18 08:13:28.292 [motorRecord.cc:1086 IOC:m1 01]
doMoveDialPosition(2127) mode=Position position=101.000000 frac=1.000000
use_rel=0 val=101.000000 diff=1.000000
2024/01/18 08:13:28.292 [motorRecord.cc:2026 IOC:m1 01]
process:---------------------- end
# The move() had been transfered into the driver, as Mark has described
# The values are transferred into the TwinCAT system over the wire
2024/01/18 08:13:28.293 [ethercatmcController.cpp:815]
out=Main.M1.bExecute=0;Main.M1.nCommand=3;Main.M1.nCmdData=0;Main.M1.fPosition=101.000000;Main.M1.fAcceleration=20.000000;Main.M1.fDeceleration=20.000000;Main.M1.fVelocity=20.000000;Main.M1.bExecute=1
in=OK;OK;OK;OK;OK;OK;OK;OK status=asynSuccess (0)
#
# The poller calls the motorRecord (there is nothing new)
2024/01/18 08:13:28.293 [motorRecord.cc:1572 IOC:m1 01]
process:---------------------- begin reason=1 mip=0x20(MOVE(Mo))
2024/01/18 08:13:28.293 [motorRecord.cc:1614 IOC:m1 01] msta.Bits.RA_DONE=0
2024/01/18 08:13:28.294 [motorRecord.cc:2026 IOC:m1 01]
process:---------------------- end
# The poller inside EPICS has something new: The motor is moving (at
least busy in TwinCAT speach)
2024/01/18 08:13:28.294 [ethercatmcAxis.cpp:1295] poll(1) mvnNRdy=1
Ver=1 bBusy=1 bExe=1 bEnabled=1 atTarget=0 wf=3 ENC=0 fPos=0
fActPosition=100 time=0.000590
2024/01/18 08:13:28.294 [ethercatmcAxis.cpp:957] poll(1)
callParamCallbacksUpdateError Error=0 old=0 ErrID=0x0 old=0x0 Warn=0
nCmd=3 old=0 txt=NULL
# The callback goes into the motorRecord. No position changed, bit
"motor moving"
2024/01/18 08:13:28.294 [motorRecord.cc:1572 IOC:m1 01]
process:---------------------- begin reason=1 mip=0x20(MOVE(Mo))
2024/01/18 08:13:28.294 [motorRecord.cc:1594 IOC:m1 01]
msta.Bits.RA_MOVING=1
2024/01/18 08:13:28.294 [motorRecord.cc:2026 IOC:m1 01]
process:---------------------- end
# Moving has finished
2024/01/18 08:13:29.107 [ethercatmcAxis.cpp:1295] poll(1) mvnNRdy=0
Ver=1 bBusy=0 bExe=1 bEnabled=1 atTarget=0 wf=0 ENC=33 fPos=0
fActPosition=101 time=0.001890
2024/01/18 08:13:29.107 [ethercatmcAxis.cpp:957] poll(1)
callParamCallbacksUpdateError Error=0 old=0 ErrID=0x0 old=0x0 Warn=0
nCmd=0 old=3 txt=NULL
# motorRecord gets a callback
2024/01/18 08:13:29.107 [motorRecord.cc:1572 IOC:m1 01]
process:---------------------- begin reason=1 mip=0x20(MOVE(Mo))
2024/01/18 08:13:29.107 [motorRecord.cc:1594 IOC:m1 01]
msta.Bits.RA_MOVING=0
2024/01/18 08:13:29.107 [motorRecord.cc:1614 IOC:m1 01] msta.Bits.RA_DONE=1
2024/01/18 08:13:29.107 [motorRecord.cc:1779 IOC:m1 01] motor is stopped
dval=101.000000 drbv=101.000000 pp=0 udf=0 stat=0 stop=0 pmr->spmg=GO
mip=0x20(MOVE(Mo)) msta=0x4923
2024/01/18 08:13:29.107 [motorRecord.cc:1782 IOC:m1 01] setDmov old=0 new=1
2024/01/18 08:13:29.107 [motorRecord.cc:1913 IOC:m1 01] (stopped)
dmov==TRUE; no DLY; pp=0 udf=0 stat=0 sevr=0 mip=0x20(MOVE(Mo))
2024/01/18 08:13:29.107 [motorRecord.cc:1328 IOC:m1 01] maybeRetry:
close enough commandedDval=101.000000 last.dval=100.000000
dval=101.000000 drbv=101.000000 rdbd=0.100000 diff=0.000000 rcnt=0
pmr->rtry=1 mip=0x20(MOVE(Mo))
2024/01/18 08:13:29.107 [motorRecord.cc:1376 IOC:m1 01] mipClrBit 'Jf Jr
J1 Hf Hr Mo Ry Lp Mb St Dr Da jS J2 Ex' old=MOVE(Mo) new=''
2024/01/18 08:13:29.107 [motorRecord.cc:2724 IOC:m1 01] do_work: begin
udf=0 stat=0 nsta=0 spmg=GO commandedDval=101.000000 dval=101.000000
last.dval=100.000000 drbv=101.000000 mip=0x0('')
2024/01/18 08:13:29.107 [motorRecord.cc:2238 IOC:m1 01]
doDVALchangedOrNOTdoneMoving: begin commandedDval=101.000000
dval=101.000000 last.dval=100.000000 drbv=101.000000 mip=0x0('')
2024/01/18 08:13:29.107 [motorRecord.cc:2290 IOC:m1 01] too_small
dval=101.000000 spdb=0.100000 mres=1.000000 drbv=101.000000
# Under the time while moving, nobody has written a new value to the
.VAL field.
# We are done
2024/01/18 08:13:29.108 [motorRecord.cc:2026 IOC:m1 01]
process:---------------------- end
*From:* Tech-talk <tech-talk-bounces at aps.anl.gov> *On Behalf Of
*Paul-Elie GUILLOT via Tech-talk
*Sent:* Wednesday, January 17, 2024 4:11 AM
*To:* tech-talk at aps.anl.gov
*Subject:* Questions about creating a Model 3 Driver for stepper motor axis
Hello to everyone at tech-talk,
I am currently working on implementing an EPICS driver for controlling
axis which are stepper motors with absolute encoders.
My hardware controller is a Beckhoff PLC I am programming to interface
with. I am planning to use mm as EGU.
I have read multiple documentation and I have started to implement a
driver based on the Model 3 drivers, I have heavily inspired myself from
the MCB4B Driver for ACS and the Phytron driver.
I am starting to get a better understanding on how it works but I still
need clarifications on some points. Would you be so kind to help me on
those matters?
First of all,
* I see that when creating an axis controller using the class
asynMotorController, it creates a lot of parameters then linked to a
motor record by their names (the string passed to the createParam()
function), is that correct?
* How does each axis has its own set of parameters while connected to
the same asynMotorController, is it because the asynMotorAxis class
creates an AsynUser which connects to the controller?
By doing this, it creates a separate set of parameters for the axis
with the pasynUser_?
* I heavily rely on the record programming to carry out advanced
functions, such as backlash compensation and closed loop motion on
the encoder.
Do you have experience in those matters? How will the system reach a
specific encoder position? Will it use a linear term between encoder
ticks and motor steps?
But if the axis is not linear enough, there will be issues in
reaching the position?
If I understand correctly, usually motors use the FRAC and RTRY
mechanism to reach a encoder position with a certain number of retry?
In practice, how do most people operate with an encoder? Do they
use the motor in open loop and monitor the encoder, or do they set
UEIP at 1 and use FRAC motion?
* My motors are able to do microstepping (decomposing a full step in
64 microsteps), should I implement this in the MRES field of the
record, as not being 200 full steps but 12800 microsteps?
I think so because I intend to give the motor positions in
microsteps. Is there a low limit on the MRES value?
* On the system, we will be using absolute encoders, in that regard
the encoder ratio is always 1 to transfer to encoder counts, correct?
From my understanding, encoder ratio is mainly to interface with
incremental encoders converting to raw A/B ticks in full encoder tick.
Because from what I gathered the motorEncoderPosition_ must be linked to
the Field REP and is the hardware counter of the encoder.
* Is there a filter of some sorts on the encoder counts field or
should I implement one directly on my Beckhoff controller and then
use the delayAfterMove parameter to allow the encoder counts to
stabilize?
* As I have absolute encoder on all axis, for an axis the parameter
motorhasEncoder_ is always at 1 as well as the motorStatusHomed_ .
However, to use and encoder, the record needs to have the record
field UEIP at 1.
Is it defined by the user, or should initialize UEIP at 1, in my
template defined in “motor.substitutions.irelec” adding the field UEIP
to my .db file (which is derived from basic_asyn_motor.db with some
field added)?
* From what I understand, the motor record is defined by the
motorRecord.cc and motorRecord.dbd and Model 3 drivers mainly
interface with the devMotorAsyn.c, is it correct?
A change on a field of the record will be processed by
motorRecord.cc and use the different functions I have redefined for
my Driver (move, poll, etc…), is it correct?
Does the devMotorAsyn.c defines the support entry table required to
process the record commands? (with the function start_trans,
build_trans and end_trans)
Looking through the code, I understood that for Model 2 Driver and
Support, one needs to defined the support entry table to process the
record commands? (ex: MCB4B_build_trans function)
In Model 3, it seems the asynMotorController directly handles the
modification of its parameters by calling the needed function of an
axis. How does the record interface with the asynMotorController parameters?
I can’t find where devMotorAsyn.c writes to the asynMotorController
parameters.
Bottom line of this topic is: Can I use the record advanced functions
such as backlash compensation even with a Model 3 Driver?
* The record always feed steps command to the controller if I
understood the code correctly.
However as I don’t have a home routine (due to absolute encoders),
the open loop (step count) is reset at each hardware controller
power reset.
Thus, the user should never do move_absolute in openloop. To disable
it, I would like the UEIP to be always at 1? Or something should be
done to set the open loop position using setposition based on the
absolute encoder at the start of the controller?
It could be the home procedure, just sending a setposition() to the
stepper based on the encoder position? Or does the home position require
motion?
* I noticed there is no process of error catching on the motor and
error resetting, is that correct?
Should I always reset an axis in Error on my Beckhoff hardware
controller or should I include command on my EPICS driver to do so,
thus adding record for those specific actions?
Because for example, if my motor reaches a hardware limit switch
while in motion it will put itself in error state, which then needs
an error reset using the MC_Reset.
I need it’s a lot of questions, I hope it is clear enough.
In any case, thank you for your time and help.
Paul-Elie GUILLOT
Technology Manager in Automation and Robotics
Téléphone: +33 (0)4 76 44 9893
20 rue du Tour de l'Eau
F-38400 St Martin d’Hères Cedex
Phone : +33 (0)4 76 44 12 96
irelec-alcen.com <http://www.irelec-alcen.com/en>
*IRELEC after sales services – please send your request to
support at irelec-alcen.zendesk.com <mailto:support at irelec-alcen.zendesk.com> *
Any disclosure, use, dissemination or reproduction (either whole or
partial) of this message or the information contained herein is strictly
prohibited without prior consent of Irelec.
- Replies:
- RE: Questions about creating a Model 3 Driver for stepper motor axis Paul-Elie GUILLOT via Tech-talk
- References:
- Questions about creating a Model 3 Driver for stepper motor axis Paul-Elie GUILLOT via Tech-talk
- RE: Questions about creating a Model 3 Driver for stepper motor axis Mark Rivers via Tech-talk
- Navigate by Date:
- Prev:
Re: Generic EPICS IOCs Ralph Lange via Tech-talk
- Next:
Re: External: Re: Generic EPICS IOCs Niko Kivel via Tech-talk
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
<2024>
- Navigate by Thread:
- Prev:
RE: Questions about creating a Model 3 Driver for stepper motor axis Mark Rivers via Tech-talk
- Next:
RE: Questions about creating a Model 3 Driver for stepper motor axis Paul-Elie GUILLOT via Tech-talk
- Index:
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
<2024>