This commit is contained in:
sandst_a committed 2026-10-23 10:26:45 +02:00
1 parent 1166eb808c
commit d7049fb8b2
10 files changed
+136 -47

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
ioc_name: MTEST04-MTN-HEXAPOD
ioc_host: c6025a-08
ioc_port: 50001
ioc_user: root
epics_version: 7.0.10
os: deb12
cpu_architecture: x86_64
+20
View File
@@ -0,0 +1,20 @@
# Hexapod cpp_logic simulation IOC example.
# Run with the hexapod project as the IOC working directory.
# Build first: make logic ECMC_ROOT=<ecmc source> ECMCCFG_ROOT=<ecmccfg source>
# build/libhexapod_logic.so
# cfg/libhexapod_logic.so_cpp_logic.subs
#
# Axes 1..6: X, Y, Z, Rx, Ry, Rz (internal coordinate trajectories).
# Axes 7..12: L1..L6 (external IK trajectories and ideal leg feedback).
# No EtherCAT master or physical hardware is required.
# The IOC launcher supplies iocInit; ecmccfg finalizes at that point.
#
# Useful panels after startup:
# caqtdm -macro "IOC=HEXAPOD" ecmcCppLogicOverview.ui
# caqtdm -macro "IOC=HEXAPOD,CPP_ID=0" ecmcCppLogic.ui
epicsEnvSet("IOC", "${IOC=HEXAPD}")
require ecmccfg v11.2.0_RC1 "ECMC_VER=v11.2.0_RC1,MASTER_ID=-1,EC_RATE=1000"
# Load all twelve virtual axes, then the C++ transform and its status PVs.
${SCRIPTEXEC} ./cfg/load.cmd
+24 -40
View File
@@ -1,43 +1,27 @@
CXX ?= c++
CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -pedantic
ECMC_ROOT ?= ../ecmc
ECMCCFG_ROOT ?= ../ecmccfg
PYTHON ?= python3
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
SHARED := -dynamiclib
EXT := dylib
else
SHARED := -shared
EXT := so
include /ioc/tools/sls_ioc.makefile
# The offline build also creates a build/ directory. Always run the IOC
# build recipe so the source Makefile can check its own dependencies.
.PHONY: default build clean
IOC_STARTUP_FILE := $(notdir $(firstword $(wildcard *_startup.script)))
IOC_PARAMS_FILE := $(notdir $(firstword $(wildcard *_parameters.yaml)))
IOC_NAME ?= $(patsubst %_startup.script,%,$(IOC_STARTUP_FILE))
ifeq ($(IOC_NAME),)
IOC_NAME := $(patsubst %_parameters.yaml,%,$(IOC_PARAMS_FILE))
endif
.PHONY: all test logic pvs clean
all: build/libhexapod.$(EXT) build/test_kinematics
build:
mkdir -p build
build/libhexapod.$(EXT): src/c_api.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -fPIC $(SHARED) -o $@ src/c_api.cpp
build/test_kinematics: tests/test_kinematics.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -Isrc -o $@ tests/test_kinematics.cpp
test: all
./build/test_kinematics
$(PYTHON) python/viewer.py --self-test
logic: build/libhexapod_logic.$(EXT) pvs
build/libhexapod_logic.$(EXT): src/logic.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -fPIC $(SHARED) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ src/logic.cpp
CPP_LOGIC_ROOT ?= $(abspath ../..)
PYTHON ?= python3
GENERATE_QT ?= 1
IOC_QT_PANEL := qt/$(IOC_NAME)_cpp_logic.ui
IOC_QTGEN := $(CPP_LOGIC_ROOT)/utils/cpp_logic_ioc_qtgen.py
IOC_SUBS_FILE := cfg/libmain.so_cpp_logic.subs
.PHONY: pvs ui
pvs:
$(PYTHON) $(ECMCCFG_ROOT)/examples/PSI/plugins/cpp_logic/utils/ecmcCppLogicSourceSubstGen.py --source src/logic.cpp --logic-name hexapod --output cfg/libhexapod_logic.so_cpp_logic.subs
clean:
rm -rf build
$(MAKE) -C src stage
.PHONY: test-logic
test-logic: build/test_logic
./build/test_logic
build/test_logic: tests/test_logic.cpp src/logic.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ tests/test_logic.cpp
.PHONY: bench
bench: build/kinematics_bench
./build/kinematics_bench
build/kinematics_bench: bench/kinematics_bench.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -Isrc -o $@ bench/kinematics_bench.cpp
ui: pvs
if [ "$(GENERATE_QT)" = "1" ]; then $(PYTHON) $(IOC_QTGEN) --input $(IOC_SUBS_FILE) --output $(IOC_QT_PANEL) --pv-prefix "$(IOC_NAME):" --title "$(IOC_NAME) cpp_logic PVs"; fi
+12 -2
View File
@@ -303,7 +303,17 @@ This produces `build/libhexapod_logic.so` and the generated EPICS substitutions.
On macOS the build produces `.dylib` files for development tests; these cannot be
loaded in a Linux IOC.
From an existing ecmccfg IOC startup, with this project as its working directory,
`HEXAPD_startup.script` is the simulation IOC startup, following the ecmccfg
`cpp_logic_motion` example. It requires `ecmccfg strucpp` with `ECMC_VER=strucpp`,
selects a 1 kHz cycle without an EtherCAT master, and loads all twelve virtual
axes and the C++ logic through `cfg/load.cmd`. Build the library first using
headers matching that ecmc installation, then run `HEXAPD_startup.script` with your
normal PSI IOC launcher and this project as its working directory. The launcher
supplies `iocInit`; ecmccfg finalizes the configuration and enters runtime during
initialization. The default IOC name is `HEXAPD` unless the launcher sets `IOC`.
Axes remain disabled at startup and motor records are not loaded.
To integrate into an existing ecmccfg IOC startup, with this project as its working directory,
include the following **after ecmccfg initialization and before runtime startup**:
```text
@@ -346,7 +356,7 @@ prefix, including the trailing colon:
```sh
.venv/bin/pip install -r python/requirements-ioc.txt
.venv/bin/python python/viewer.py --prefix HEX:
.venv/bin/python python/viewer.py --prefix HEXAPD:
```
Use the prefix configured by `loadCppLogic.cmd` (`DB_PREFIX`, default `${IOC}:`).
+1 -1
View File
@@ -20,7 +20,7 @@ encoder:
numerator: 1
denominator: 1
bits: 32
position: ec0.s-1.ONE
# External feedback is supplied by C++ FK; no EtherCAT position link.
trajectory:
source: 0
+1 -1
View File
@@ -21,7 +21,7 @@ encoder:
numerator: 1
denominator: 1
bits: 32
position: ec0.s-1.ONE
# External feedback is supplied by the C++ ideal plant; no EtherCAT position link.
trajectory:
source: 1
+1 -1
View File
@@ -3,4 +3,4 @@
# cfg/axes.cmd loads the two shared axis YAML templates via loadYamlAxis.cmd.
# Axis IDs 1..12 must be free. No physical EtherCAT hardware is linked.
${SCRIPTEXEC} ./cfg/axes.cmd
${SCRIPTEXEC} ${ecmccfg_DIR}loadCppLogic.cmd, "DIR=./build/,FILE=libhexapod_logic.so,ASYN_PORT=HEX,REPORT=1,MACROS='COORD_FIRST=1,LEG_FIRST=7,SIMULATION=1'"
${SCRIPTEXEC} ${ecmccfg_DIR}loadCppLogic.cmd, "ASYN_PORT=HEX,REPORT=1,MACROS='COORD_FIRST=1,LEG_FIRST=7,SIMULATION=1'"
+43
View File
@@ -0,0 +1,43 @@
CXX ?= c++
CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -pedantic
ECMC_ROOT ?= ../ecmc
ECMCCFG_ROOT ?= ../ecmccfg
PYTHON ?= python3
UNAME := $(shell uname -s)
ifeq ($(UNAME),Darwin)
SHARED := -dynamiclib
EXT := dylib
else
SHARED := -shared
EXT := so
endif
.PHONY: all test logic pvs clean
all: build/libhexapod.$(EXT) build/test_kinematics
build:
mkdir -p build
build/libhexapod.$(EXT): src/c_api.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -fPIC $(SHARED) -o $@ src/c_api.cpp
build/test_kinematics: tests/test_kinematics.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -Isrc -o $@ tests/test_kinematics.cpp
test: all
./build/test_kinematics
$(PYTHON) python/viewer.py --self-test
logic: build/libhexapod_logic.$(EXT) pvs
build/libhexapod_logic.$(EXT): src/logic.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -fPIC $(SHARED) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ src/logic.cpp
pvs:
$(PYTHON) $(ECMCCFG_ROOT)/examples/PSI/plugins/cpp_logic/utils/ecmcCppLogicSourceSubstGen.py --source src/logic.cpp --logic-name hexapod --output cfg/libhexapod_logic.so_cpp_logic.subs
clean:
rm -rf build
.PHONY: test-logic
test-logic: build/test_logic
./build/test_logic
build/test_logic: tests/test_logic.cpp src/logic.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ tests/test_logic.cpp
.PHONY: bench
bench: build/kinematics_bench
./build/kinematics_bench
build/kinematics_bench: bench/kinematics_bench.cpp src/kinematics.hpp | build
$(CXX) $(CXXFLAGS) -Isrc -o $@ bench/kinematics_bench.cpp
+24
View File
@@ -0,0 +1,24 @@
include /ioc/tools/driver.makefile
MODULE = main
BUILDCLASSES = Linux
ARCH_FILTER = deb10% deb12%
EXCLUDE_VERSIONS+=3 7.0.5 7.0.6 7.0.7
USR_CXXFLAGS += -std=c++17
ecmc_VERSION = v11.2.0_RC1
DIRS+=.
SOURCES += $(foreach d,${DIRS}, $(wildcard $d/*.c) $(wildcard $d/*.cpp))
HEADERS += $(foreach d,${DIRS}, $(wildcard $d/*.h))
HEADERS += $(foreach d,${DIRS}, $(wildcard $d/*.hpp))
CPP_LOGIC_ROOT ?= $(abspath ../../..)
PYTHON ?= python3.9
SUBSGEN := $(CPP_LOGIC_ROOT)/utils/ecmcCppLogicSourceSubstGen.py
STAGE_SUBS ?= ../cfg/lib$(MODULE).so_cpp_logic.subs
STAGE_DIR := $(dir $(STAGE_SUBS))
LOCAL_MODULE := $(notdir $(abspath ..))
.PHONY: stage
stage:
mkdir -p $(STAGE_DIR); \
$(PYTHON) $(SUBSGEN) --source main.cpp --logic-name "$(LOCAL_MODULE)" --output $(STAGE_SUBS)
+3 -2
View File
@@ -37,8 +37,9 @@ struct HexapodLogic : ecmcCpp::LogicBase {
model=hexapod::Model(g); geometry_home=model.inverse({});
if(!model.withinStroke(geometry_home)) throw std::runtime_error("Home is outside leg stroke");
for(int i=0;i<6;++i) {
ecmc.input("ax"+std::to_string(coordFirst+i)+".traj.setpos",pose_set[i]);
ecmc.input("ax"+std::to_string(legFirst+i)+".enc.actpos",leg_actual[i]);
// C++ item bindings use asyn names, rather than PLC object paths.
ecmc.input("ax"+std::to_string(coordFirst+i)+".setpos",pose_set[i]);
ecmc.input("ax"+std::to_string(legFirst+i)+".actpos",leg_actual[i]);
}
epics.readOnlyArray("hex.pose_set",pose_set)
.readOnlyArray("hex.pose_actual",pose_actual)