From ac5ab3855e626f5ea01086e58bc3991191b8ac95 Mon Sep 17 00:00:00 2001 From: smathis Date: Fri, 23 Jan 2026 11:16:19 +0100 Subject: [PATCH] Updated turboPmac version and added constructor for standard axis --- README.md | 7 +-- src/seleneGuide.dbd | 1 + src/seleneGuideController.cpp | 93 +++++++++++++++++++++++++++++++++++ turboPmac | 2 +- 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7b4eeed..5037791 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Two additional PVs are provided in `db/seleneGuide.db`: ### Usage in IOC shell seleneGuide exports the following IOC shell functions: -- `seleneGuideController`: Create a new controller object. This object is essentially a `turboPmacController` object from the standard [Turbo PMAC driver](https://git.psi.ch/sinq-epics-modules/turboPmac), but it supports the additional motor PVs for absolute position and normalization. This means that it can be used with "normal" `turboPmacAxis` as well. +- `seleneGuideController`: Create a new controller object. This object is essentially a `turboPmacController` object from the standard [Turbo PMAC driver](https://git.psi.ch/sinq-epics-modules/turboPmac), but it supports the additional motor PVs for absolute position and normalization. This means that it can be used to control a "standard" TurboPMAC axis as well. To ensure compatibility between controller and axis, it is recommended to use the wrapper `seleneStandardAxis` instead of the `turboPmacAxis` provided by the basic TurboPMAC driver (because otherwise the versions of the `seleneGuide` and the `turboPmac` drivers are not independent from each other). - `seleneOffsetAxis`: Create a new offset axis object with the specified x- and z-offset from an arbitrary origin in mm. - `seleneVirtualAxes`: Create the virtual lift and the angle axes. @@ -78,8 +78,9 @@ seleneOffsetAxis("$(NAME)",5, 5933.99477, 0.0); seleneOffsetAxis("$(NAME)",6, 7463.87259, 0.0); # These are two "normal" PMAC axes which work the same way they would with a turboPmacController. -turboPmacAxis("$(NAME)",7); -turboPmacAxis("$(NAME)",8); +# Using the wrapper seleneStandardAxis ensures compatibility to the seleneGuideController. +seleneStandardAxis("$(NAME)",7); +seleneStandardAxis("$(NAME)",8); # This function call creates the lift and the angle axes. # The arguments on position 2 to 7 are the axis indices of the offset axes diff --git a/src/seleneGuide.dbd b/src/seleneGuide.dbd index 7243063..df47ce2 100644 --- a/src/seleneGuide.dbd +++ b/src/seleneGuide.dbd @@ -1,3 +1,4 @@ registrar(seleneVirtualAxesRegister) registrar(seleneOffsetAxisRegister) +registrar(seleneStandardAxisRegister) registrar(seleneGuideControllerRegister) \ No newline at end of file diff --git a/src/seleneGuideController.cpp b/src/seleneGuideController.cpp index d040ca2..95bd365 100644 --- a/src/seleneGuideController.cpp +++ b/src/seleneGuideController.cpp @@ -161,4 +161,97 @@ static void seleneGuideControllerRegister(void) { } epicsExportRegistrar(seleneGuideControllerRegister); +/* +C wrapper for a "standard" TurboPMAC axis constructor. This function is +identical to "turboPmacCreateAxis" in the standard TurboPMAC driver. However, it +is recommended to use this constructor to make sure the version of the axis +matches that of the controller. The controller is read from the portName. +*/ +asynStatus seleneStandardCreateAxis(const char *portName, int axis) { + + /* + findAsynPortDriver is a asyn library FFI function which uses the C ABI. + Therefore it returns a void pointer instead of e.g. a pointer to a + superclass of the controller such as asynPortDriver. Type-safe upcasting + via dynamic_cast is therefore not possible directly. However, we do know + that the void pointer is either a pointer to asynPortDriver (if a driver + with the specified name exists) or a nullptr. Therefore, we first do a + nullptr check, then a cast to asynPortDriver and lastly a (typesafe) + dynamic_upcast to Controller + https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c + */ + void *ptr = findAsynPortDriver(portName); + if (ptr == nullptr) { + /* + We can't use asynPrint here since this macro would require us + to get an asynUser from a pointer to an asynPortDriver. + However, the given pointer is a nullptr and therefore doesn't + have an asynUser! printf is an EPICS alternative which + works w/o that, but doesn't offer the comfort provided + by the asynTrace-facility + */ + errlogPrintf("Controller \"%s\" => %s, line %d\nPort not found.", + portName, __PRETTY_FUNCTION__, __LINE__); + return asynError; + } + // Unsafe cast of the pointer to an asynPortDriver + asynPortDriver *apd = (asynPortDriver *)(ptr); + + // Safe downcast + seleneGuideController *pC = dynamic_cast(apd); + if (pC == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d\nController " + "is not a seleneGuideController.", + portName, __PRETTY_FUNCTION__, __LINE__); + return asynError; + } + + // Prevent manipulation of the controller from other threads while we + // create the new axis. + pC->lock(); + + /* + We create a new instance of the axis, using the "new" keyword to + allocate it on the heap while avoiding RAII. + https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp + https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp + + The created object is registered in EPICS in its constructor and can + safely be "leaked" here. + */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wunused-variable" + turboPmacAxis *pAxis = new turboPmacAxis(pC, axis); + + // Allow manipulation of the controller again + pC->unlock(); + return asynSuccess; +} + +/* +Same procedure as for the CreateController function, but for the axis +itself. +*/ +static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)", + iocshArgString}; +static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt}; +static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, + &CreateAxisArg1}; +static const iocshFuncDef configSeleneStandardCreateAxis = { + "seleneStandardAxis", 2, CreateAxisArgs, + "Create an instance of a standard turboPmac axis for the selene " + "controller. The first argument is the controller this axis should be " + "attached to, the second argument is the axis number."}; +static void configSeleneStandardCreateAxisCallFunc(const iocshArgBuf *args) { + seleneStandardCreateAxis(args[0].sval, args[1].ival); +} + +// This function is made known to EPICS in turboPmac.dbd and is called by +// EPICS in order to register both functions in the IOC shell +static void seleneStandardAxisRegister(void) { + iocshRegister(&configSeleneStandardCreateAxis, + configSeleneStandardCreateAxisCallFunc); +} +epicsExportRegistrar(seleneStandardAxisRegister); + } // extern "C" diff --git a/turboPmac b/turboPmac index 62ccf04..66db8ce 160000 --- a/turboPmac +++ b/turboPmac @@ -1 +1 @@ -Subproject commit 62ccf046fda111d62723615913d73f5bc3dd5810 +Subproject commit 66db8ce408e72178532581899777c0a44e436bbb