Repository navigation
kinematics: modules as functions of a parameter block, callable outside realtime - #4702
Open
grandixximo wants to merge 19 commits into
Open
grandixximo wants to merge 19 commits into
grandixximo wants to merge 19 commits into
Conversation
A module's haldata is a struct of pin handles, and a handle is a pointer to the value cell hal_get_real() reads, so a second copy of the module outside realtime can point its haldata at cells carrying the values the RT instance reads and its forward and inverse work unmodified, on live values, at any pose. Per module: export nonrt_attach(), which asks a caller-supplied resolver for each input pin by name, runs the same coordinate parse setup runs and returns the forward and inverse; the maths is untouched and no header grows per module. The cells bound are pins of the caller's own component, connected to the signal the RT pin reads or to one made for the purpose, since a reference into another component's pin has that component's lifetime. Name lookup lives in the loader, userspace code linked against liblinuxcnchal; walking the HAL name space from an RT object is what the HAL isolation work removes. Input pins only, or the two copies write each other's state. Verified against the shared-memory snapshot design this replaces: identical caps from kinslimits, and a pin set by setp is seen without a motion thread running.
A module reads its geometry from pins it created and keeps its type and iteration scratch in statics, so it can only answer for the machine as it is now, from inside realtime; a planner, a load-time check or a preview had to carry a second copy of the maths. Add the form in which the caller supplies everything: a kins_params block with the type, the joint map, the tool and the geometry, a kins_scratch for what an iterative method carries between calls, and a kins_ops table with the forward, inverse, frames and Jacobian of one type as functions of the two. A module declares its geometry as a table of named entries; in RT the shared code makes one pin per entry under the names configs already use and copies pins in and outputs out around every call, so the maths never touches a pin. kins_single.c supplies the classic entry points for a module with one type, switchkins.c for several, dispatching a type registered with switchkinsRegisterOps() through the block and the older registrations as before, so modules convert one at a time. Every converted module exports kinsDescribe() for callers outside RT; one that does not reports itself RT-only. trivkins, 5axiskins and userkfuncs convert here. The non-RT loader moves onto kinsDescribe() and nonrt_attach() goes; jacobian.cc asks the module for its Jacobian. An iterating ops forward restarts from the pose it last solved and otherwise from the caller's estimate, keeping a result only when the solve succeeded, and the gui forward of a pure type is seeded the same way. kinslimits reports the same caps for 5axiskins to the digit.
…meter block The trt maths becomes two ops tables over one geometry table, TRT_PARAMS, with the joint map read from the block instead of the JX statics and the tool length from p->tool.tran.z, which the shared code fills from the tool-offset entry. The two modules register the tables with switchkinsRegisterOps() in the order sparm decides, and the identity and userk types come from the shared ops. The joint assignment print and the required-letter check that trtKinematicsSetup() did are now the shared code's, so the setup function goes with the haldata. maxkins keeps its fixed joint order and becomes a kins_single.c module: the table declares pivot-length as the HAL_IO pin it was and conventional-directions as before, and the three functions read the block. Pin names and defaults are unchanged throughout.
…s, matrixkins, userkins: move onto the parameter block Each becomes a kins_module over one ops table and links kins_single.c. The maths is unchanged: where it read a pin it reads the block, what it kept between calls it keeps in the scratch, so rosekins counts its turns per caller and reports them as declared outputs, and tripodkins keeps its HAL_IO pins. kinematicsHome() goes from corexykins and rotatekins; nothing called it. The two deltas share their maths with a python module through a header whose geometry was statics; it is a struct the caller passes now, python API unchanged. matrixkins's nine coefficients were HAL parameters and are pins of the same names. In tree, matrixkins and userkins link kins_single.c; out of tree, userkins gets the same entry points from the new kins_single_core module, which the HAL file loads ahead of it, as switchkins_core serves a switchkins component.
Each arm declares its dimensions as a table, reads them from the block and registers one ops table for its own type, with the identity and userk types from the shared ops. pumakins keeps its flange frame and declares the half turn through the ops table's native rotation, where switchkinsRegisterFrames() carried it before. The setup functions and haldata go; pin names and defaults are unchanged.
…arameter block Each component declares its geometry as a table, writes its types as ops over the block, and supplies switchkinsSetup() like the C modules do; EXTRA_SETUP() runs it through switchkinsRunSetup() and initialises, so the components link switchkins_setup.o too and export kinsDescribe() with the rest. The trsrn TCP type registers its frames and Jacobian in its ops table and the TOOL type the identity frames, as they were registered before. The inverses go on reading the rotary angles from their joint argument, as they always have. Pin names and defaults are unchanged.
The three that build a geometry from many pins build it from the block on each call: genser its link description, genhex its base and platform points, pentakins its base points and effector circles. What they reported through output pins, iteration counts, strut corrections, the hexapod's fwd-kins-fail and gui pose, goes through the scratch, so each caller keeps its own. genhex and pentakins declare their forward as iterating and the switchkins core seeds it as before. genhexkins's six gui pose pins were inputs written by the module and are outputs now; pentakins's HAL parameters become pins of the same names. genserfuncs.c loses its haldata and globals, and ugenserkins builds a block and calls the ops.
The out-of-tree template declares its geometry as a table, writes its example type as ops over the block and supplies switchkinsSetup() like the in-tree modules. EXTRA_SETUP() runs it through switchkinsRunSetupWith() and kinsDescribe() replays it through switchkinsDescribeWith(), both exported by switchkins_core, so the template still builds with halcompile alone and includes no source. The kparms it built was never zeroed, which the grown struct would have turned into a crash; switchkinsRunSetupWith() zeroes it.
…he ops table A kins_ops table now says what its type IS the same way it says the maths: .primary marks the module's working transform, and switchkinsRegisterOps() bridges it into the declared flags exactly like .identity. G43.4 resolves the switch target from the flag, so the number of the working kinematics stops being a guess. Like identity, primary may be declared for at most one kinstype; two answers fail the module load. Every in-tree working transform declares it: fiveaxis, the two trt tables, scara, puma, genser, genhex, three21, millturn, tdr and the trsrn tcp tables. The trsrn tool kinematics stays undeclared: it is a reporting mode, not the working transform. A third flag, KINSTYPE_MACHINE, names the machine frame type: the machine with the orientation left out, XYZ the pivot in machine coordinates and the rotary letters the rotary joints. On a machine whose slides line up with its frame that is the identity type, so a module that declares none has its identity type stand in, and every in-tree module is of that kind. A module whose carriage does not line up, a slanted slide or a composite Y on a mill-turn, declares its machine frame type separately and keeps identity for a type whose joints really are the axes, since the shortcuts taken on the identity flag (the Jacobian, the tool offset change, the user-space loader's KINEMATICS_IDENTITY) would be wrong on a transform. G13.1 and G49 cancel to the machine frame type; one at most, as for the others.
The conventions chapter gains a section on the two blocks, the table, the ops table and what the shared code does with them in and outside realtime, and the Writing a Module list gains "no state". The frames and Jacobian sections point at the ops table where they pointed at the register calls. The switchkins chapter's Code Notes describe switchkinsRegisterOps(), the table in kparms, switchkinsRunSetup() and kinsDescribe(), keep the older registration as the older form, and the outline is a module written the new way.
…lazily The loader zeroed the joints before a module's inverse and before an iterating forward. Motion hands a module the joints the machine is at and some read them, a nutating head its rotary angles, the hexapod its starting pose, so zeros put the loader on a different branch from realtime for xyzacb_trsrn. The caller's joints are the seed now, an iterating forward keeps the caller's pose, and the Jacobian runs its inverse from what the last inverse found. A halcompile component references hal_export_funct() and the rest of what its rtapi_app_main() needs, which only the realtime HAL library provides, so dlopen with RTLD_NOW refused every component; nothing here calls that main, so bind lazily. The module is looked up in the directory it is installed in, with the HAL library within reach. A module built out of tree takes the shared kinematics code from switchkins_core or kins_single_core, as it does in realtime. The loader finds which in the module's undefined symbols and puts that core in scope before the module, since the module takes the identity ops from it too, data that binds as the module loads, lazily or not. A module that does not load at all is one only realtime can run.
tests/kins-params loads each module, evaluates it once in realtime through the classic entry points (paritycheck.c publishes the forward, the inverse and the Jacobian on pins) and once through the non-realtime loader from python (check.py: kinsDescribe(), the block filled from the module's pins, the same ops), and requires the same success and the same numbers to rounding. 34 runs over the 24 modules, every switchable type that has geometry of its own, the parallel machines from a pose their forward can be seeded with. Checked by mutation: the loader not refreshing the geometry fails 10 comparisons in the first module with a table; the loader seeding the inverse with zeros instead of the caller's joints fails the three translation joints of xyzacb_trsrn, which reads its rotary angles from that array. check.py holds the loader's libraries local, the way a GUI holds them under the interpreter it loads, so whatever a module needs in scope has to come from the loader: the out of tree switchkins module does not load without its core put in scope first.
A module whose maths needs the tool length read it from a pin the config had to net from motion.tooloffset.z: a copy of controller state motion already holds, one cycle late, and a missing net gave wrong joints with no error. The block carries the tool, so motion hands it over: kinematicsSetTool() is called whenever the offset changes, referenced weakly so an older module still loads. kins_single.c and switchkins.c export it for every module on the block; the pin is read only until motion has sent anything, and a pin left disagreeing is reported once, since it is a config setting a tool length where the tool table should. A pin nobody set is not a disagreement. The non-RT loader takes the tool from the caller through kinematicsUserSetTool() where one is given, a planner knowing what a segment runs under better than the machine does. tests/kins-tool-offset runs xyzac-trt-kins with nothing on its tool pin and checks that G43 reaches the joints, that netting the pin changes nothing and that G49 takes the length out; tests/kins-params checks the caller's tool on the loader.
The trt modules, genhexkins and pentakins already have motion hand them the offset in effect; these two still read a pin the config had to net from motion.tooloffset.z, and a missing net gave wrong joints with no error. Flag the entry as the tool, drop the nets from the sims, and have the kins-switch test check that G43.4 reaches the joints with nothing netted: in the tilted pose the length moves them by its tilt term, the same length along the tool instead of along Z.
The head carries the tool and nothing turns the work, so the work frame is the machine frame and the tool frame is Rz(C) Ry(-B), the tilt left handed about y as the module's note 10 says. Its third column is the tool axis the forward already uses, from the tip towards the holder, so no native rotation is needed. Without them G53.1, G53.3, G53.6 and G68.3 refused on the bridgemill sim while every other five-axis module took them. tests/kins-frames takes the module and gains two checks: the type the module models must supply frames of its own, since a switchable module's identity type passed on its neighbour's answers; and the W joint runs the tool out along the reported axis reversed, which ties the frame to the forward. Reversing the tilt, reversing the head's turn and dropping the frames each fail.
…realtime The loader gains the two frames, the tool frame inverse and a survey of which joints turn the work, on kins_util.c code the library already carries; only the glue between the block form and the joint-only frame functions is new. The survey lets a caller hold the table still without a config entry naming it. A second survey tells the two orienting rotaries apart, the primary whose axis is fixed in the machine from the secondary whose axis it carries, by turning each a little and reading the axis of the resulting rotation; the sign of the secondary is how a caller names a pose rather than counting them. It answers only for exactly two such rotaries, so a robot wrist gets nothing. tests/kins-params checks the pair every module reports. kinematicsUserInitString() takes [KINS] KINEMATICS as the HAL file hands it to loadrt.
The loader read the RT instance's pins through pins of its own, linked to the signal each RT pin was on, and where a pin had no signal it made one and linked the RT pin to it. That leaves the module's pins connected to signals the config never made, so a net of one of them after the module was first evaluated fails on a pin that is already connected. Read each pin by name with hal_get_p() instead, at every refresh: nothing is made in HAL, a pin netted later reads the signal from then on, and the loader no longer has to be initialised before the caller's hal_ready().
The note on kinematicsSwitchable() said the in-tree single-type modules use KINS_NOT_SWITCHABLE. They are on the parameter block now and get these entry points from kins_single.c. The macro stays for a module written without the block.
kinematics.h carried three things: the entry points motion, task and the interpreter call on a module, the helpers a module is written with, and the prototypes of particular modules that share their maths between two loadable modules. A consumer that wanted the flag values or the switch entry point read all of it. kinematics.h now declares what a module presents to its callers: the kinematics type, the flags, forward, inverse, home, the tool and work frames, the tool frame inverse, the Jacobian, the switch, the type flags and the tool offset. kins_module.h, exported alongside it, takes the module side: the joint map and identity helpers, the tool frame library, the generic Jacobian, the parameter block and the shared code that runs an ops table. What trtfuncs.c and userkfuncs.c share with the modules that load them is in trtfuncs.h and userkfuncs.h next to them, in tree only. SWITCHKINS_MAX_TYPES is defined in kinematics.h itself, where motion and NML read it, and kins_module.h aliases KINS_MAX_TYPES to it. Nothing declared moves or changes; only which header declares it.
grandixximo
force-pushed
the
kins-params
branch
from
October 19, 2026 00:01
9fd7379 to
10d8fa8
Compare
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is item 4 of the multiaxis roadmap in #4374, the next lane after #4595 (switchkins core) and #4600 (tilted work plane).
Today a kinematics module reads its geometry from pins it created and keeps its type and any iteration state in statics. It can only answer for the machine as it stands, from inside realtime. Anything else that needs the same maths, a planner, a load-time limit check or a preview, has to carry a second copy of it.
With this PR the caller supplies everything:
kins_params: the type, the joint map, the tool and the geometry.kins_scratch: what an iterative method carries between calls, one per caller.kins_ops: the forward, inverse, frames and Jacobian of one type, as functions of those two.A module declares its geometry once as a table of named entries. In realtime, the shared code makes one pin per entry, under the names configs already use, and copies pins in and outputs out around every call, so the maths never touches a pin. Outside realtime, the loader (
kinematics_user.c) fills the same block by reading the module's pins by name withhal_get_p(), and calls the same ops. Nothing is created in HAL. The loader opens the module's core library before the module, so an out-of-tree module answers in a GUI process as well as in task.What changes
kins_single.c, through akins_single_coremodule, the same way switchkins: one dispatch for kinematics modules, halcompile components and out-of-tree modules #4595 does it withswitchkins_core. Switchkins modules register ops tables withswitchkinsRegisterOps(). The older registrations still work, so out-of-tree modules keep building and loading.kinematicsSetTool()whenever the offset changes, so a config no longer has to netmotion.tooloffset.zinto the module's tool pin. A missing net used to give wrong joints with no error. The pin is still read until motion has sent anything. A pin that disagrees with motion is reported once. 5axiskins and maxkins take the length this way too, and the sims drop their nets.kinematics.hkeeps the interface motion, task and the interpreter call. The module side (joint map, identity helpers, tool frame library, Jacobian helpers, parameter block) moves tokins_module.h, which is exported beside it. Nothing declared changes, only which header declares it.kinslimitsis installed. It is the command-line front end of the loader.Visible to configs and out-of-tree modules
kinematics.hfor the identity helpers orkparmsneedskins_module.has well. A single-type module written the old way, withKINS_NOT_SWITCHABLE, builds and loads as before.Tests
tests/kins-paramsloads each module and evaluates it once in realtime through the classic entry points, and once through the loader from Python, holding the libraries local the way a GUI does. It requires the same success and the same numbers to rounding. That is 34 runs over the 24 modules, with every switchable type that has geometry of its own. I checked it by mutation: a loader that does not refresh the geometry fails 10 comparisons on the first module with a table, and a loader that seeds the inverse with zeros fails three joints of xyzacb_trsrn.tests/kins-tool-offsetruns xyzac-trt-kins with nothing on its tool pin. It checks that G43 reaches the joints, that netting the pin changes nothing, and that G49 takes the length out.tests/kins-frames,tests/kins-switchandtests/tool-frame(the realtime checker from tests: tool-frame checks the helpers as a realtime component #4673) gain checks for the 5axiskins frames, for the tool length with nothing netted, and for the joints that turn the work.The full suite passes on this branch alone and on the multiaxis stack it opens, apart from
tests/blendmath, which fails on master with GCC 14 and is fixed by #4676.