EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

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 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
<== Date ==> <== Thread ==>

Subject: Re: Questions about creating a Model 3 Driver for stepper motor axis
From: Torsten Bögershausen via Tech-talk <tech-talk at aps.anl.gov>
To: Mark Rivers <rivers at cars.uchicago.edu>, Paul-Elie GUILLOT <peguillot at irelec-alcen.com>, "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>
Date: Thu, 18 Jan 2024 15:26:31 +0100
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
ANJ, 25 Jan 2024 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·