Compare commits
7 Commits
94ee690796
...
2cda7be4d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cda7be4d4 | |||
| d84f84b5ee | |||
| 5b94d5c85a | |||
| dc0831eb71 | |||
| c9c7e43a70 | |||
| d292360a8d | |||
| 5c8847d334 |
@ -18,7 +18,9 @@ set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/output" CACHE PATH "" FORCE)
|
|||||||
set(CMAKE_BUILD_RPATH "\$ORIGIN:\$ORIGIN/../lib")
|
set(CMAKE_BUILD_RPATH "\$ORIGIN:\$ORIGIN/../lib")
|
||||||
set(CMAKE_INSTALL_RPATH "\$ORIGIN:\$ORIGIN/../lib")
|
set(CMAKE_INSTALL_RPATH "\$ORIGIN:\$ORIGIN/../lib")
|
||||||
|
|
||||||
# Use RUNPATH (new dtags) generally preferable
|
# Use transitive RPATH so CLion can run build-tree test executables without
|
||||||
|
# manually setting LD_LIBRARY_PATH for indirect third-party dependencies.
|
||||||
|
add_link_options(-Wl,--disable-new-dtags)
|
||||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)
|
set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)
|
||||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||||
|
|
||||||
|
|||||||
103
README.md
103
README.md
@ -1,23 +1,25 @@
|
|||||||
# CMVR-ES
|
# CMVR-ES
|
||||||
|
|
||||||
## Overview
|
## 简介
|
||||||
|
|
||||||
## Installation
|
CMVR-ES 工程。
|
||||||
|
|
||||||
### 1. Git submodules install
|
## 安装
|
||||||
|
|
||||||
|
### 1. 拉取 Git 子模块
|
||||||
|
|
||||||
```
|
```
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Dependency install
|
### 2. 安装系统依赖
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# basic
|
# 基础工具
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt install -y build-essential cmake git pkg-config patchelf libboost-all-dev libssl-dev
|
sudo apt install -y build-essential cmake git pkg-config patchelf libboost-all-dev libssl-dev
|
||||||
|
|
||||||
# opencv
|
# OpenCV
|
||||||
sudo apt install -y \
|
sudo apt install -y \
|
||||||
libjpeg-dev libpng-dev libtiff-dev \
|
libjpeg-dev libpng-dev libtiff-dev \
|
||||||
libavcodec-dev libavformat-dev libswscale-dev \
|
libavcodec-dev libavformat-dev libswscale-dev \
|
||||||
@ -39,7 +41,7 @@ sudo apt-get install libassimp-dev
|
|||||||
# visp
|
# visp
|
||||||
sudo apt-get install -y libx11-dev liblapack-dev libzbar-dev libpthread-stubs0-dev libdc1394-dev nlohmann-json3-dev
|
sudo apt-get install -y libx11-dev liblapack-dev libzbar-dev libpthread-stubs0-dev libdc1394-dev nlohmann-json3-dev
|
||||||
|
|
||||||
# realsense
|
# RealSense
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
libusb-1.0-0-dev libudev-dev \
|
libusb-1.0-0-dev libudev-dev \
|
||||||
libglu1-mesa-dev
|
libglu1-mesa-dev
|
||||||
@ -48,4 +50,91 @@ sudo apt-get install -y \
|
|||||||
sudo apt install gnuplot-qt
|
sudo apt install gnuplot-qt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3. 配置 IgH EtherCAT
|
||||||
|
|
||||||
|
工程已内置 IgH EtherCAT 1.7.0 的 userspace 文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency/x86/third_party/ethercat/v1.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
先设置本机路径:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export CMVR_ES_ROOT=/path/to/cmvr-es
|
||||||
|
export IGH_ETHERCAT_ROOT=$CMVR_ES_ROOT/dependency/x86/third_party/ethercat/v1.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
安装当前内核的 header:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y linux-headers-$(uname -r)
|
||||||
|
```
|
||||||
|
|
||||||
|
如果内置目录里已经有当前内核版本的 EtherCAT 内核模块,直接安装到系统:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo mkdir -p /lib/modules/$(uname -r)/ethercat
|
||||||
|
sudo cp -r $IGH_ETHERCAT_ROOT/lib/modules/$(uname -r)/ethercat/* /lib/modules/$(uname -r)/ethercat/
|
||||||
|
sudo depmod
|
||||||
|
```
|
||||||
|
|
||||||
|
内置内核模块只适用于相同内核版本。如果 `$IGH_ETHERCAT_ROOT/lib/modules/$(uname -r)` 不存在,说明这台机器的内核版本不匹配,需要在这台机器上重新编译安装 IgH EtherCAT:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential autoconf automake libtool pkg-config git \
|
||||||
|
linux-headers-$(uname -r)
|
||||||
|
|
||||||
|
cd /tmp
|
||||||
|
git clone --branch stable-1.7 --depth 1 https://gitlab.com/etherlab.org/ethercat.git ethercat-stable-1.7
|
||||||
|
cd ethercat-stable-1.7
|
||||||
|
|
||||||
|
./bootstrap
|
||||||
|
./configure \
|
||||||
|
--prefix=$IGH_ETHERCAT_ROOT \
|
||||||
|
--libdir=$IGH_ETHERCAT_ROOT/lib \
|
||||||
|
--includedir=$IGH_ETHERCAT_ROOT/include \
|
||||||
|
--sysconfdir=$IGH_ETHERCAT_ROOT/etc \
|
||||||
|
--with-systemdsystemunitdir=$IGH_ETHERCAT_ROOT/lib/systemd/system \
|
||||||
|
--enable-generic \
|
||||||
|
--with-linux-dir=/lib/modules/$(uname -r)/build
|
||||||
|
|
||||||
|
make -j$(nproc) all modules
|
||||||
|
make install
|
||||||
|
sudo make modules_install
|
||||||
|
sudo depmod
|
||||||
|
```
|
||||||
|
|
||||||
|
启动 EtherCAT。`eno1` 换成实际连接 EtherCAT 从站的网卡:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会写入内置 IgH 配置文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$IGH_ETHERCAT_ROOT/etc/ethercat.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会把该网卡的 MAC 写到 `MASTER0_DEVICE`,使用 `DEVICE_MODULES="generic"`,把网卡从 NetworkManager 断开,并通过 `ethercatctl -c` 启动 IgH master。
|
||||||
|
|
||||||
|
查看状态:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
script/ethercat/status_ethercat.sh
|
||||||
|
|
||||||
|
$IGH_ETHERCAT_ROOT/bin/ethercat master
|
||||||
|
$IGH_ETHERCAT_ROOT/bin/ethercat slaves
|
||||||
|
$IGH_ETHERCAT_ROOT/bin/ethercat pdos
|
||||||
|
```
|
||||||
|
|
||||||
|
停止 EtherCAT:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1 --restore-network
|
||||||
|
```
|
||||||
|
|||||||
65
cmvr-es/config/devices/motor/ethercat_motors.pb.txt
Normal file
65
cmvr-es/config/devices/motor/ethercat_motors.pb.txt
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
motor {
|
||||||
|
id: "ethercat_motors"
|
||||||
|
|
||||||
|
motor_groups {
|
||||||
|
id: "right_arm_ethercat"
|
||||||
|
bus_type: MOTOR_BUS_ETHERCAT
|
||||||
|
vendor: MOTOR_VENDOR_EYOU
|
||||||
|
protocol: MOTOR_PROTOCOL_ETHERCAT_CIA402
|
||||||
|
|
||||||
|
ethercat {
|
||||||
|
master_index: 0
|
||||||
|
cycle_us: 1000
|
||||||
|
slave_op_timeout_ms: 12000
|
||||||
|
slave_state_poll_period_ms: 10
|
||||||
|
|
||||||
|
cia402 {
|
||||||
|
profile_position_trigger_delay_ms: 2
|
||||||
|
state_transition_timeout_ms: 1200
|
||||||
|
velocity_stop_timeout_ms: 2000
|
||||||
|
status_poll_period_ms: 10
|
||||||
|
stopped_velocity_tolerance_rad_s: 0.001
|
||||||
|
}
|
||||||
|
|
||||||
|
dc {
|
||||||
|
enable: true
|
||||||
|
reference_motor_id: 1
|
||||||
|
sync0_cycle_us: 1000
|
||||||
|
sync0_shift_us: 0
|
||||||
|
sync_reference_clock_period: 1
|
||||||
|
assign_activate: 768
|
||||||
|
sync_monitor_period_ms: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
slaves { motor_id: 1 alias: 0 position: 0 }
|
||||||
|
slaves { motor_id: 2 alias: 0 position: 1 }
|
||||||
|
slaves { motor_id: 3 alias: 0 position: 2 }
|
||||||
|
slaves { motor_id: 4 alias: 0 position: 3 }
|
||||||
|
slaves { motor_id: 5 alias: 0 position: 4 }
|
||||||
|
slaves { motor_id: 6 alias: 0 position: 5 }
|
||||||
|
slaves { motor_id: 7 alias: 0 position: 6 }
|
||||||
|
}
|
||||||
|
|
||||||
|
joint_limits {
|
||||||
|
enable: true
|
||||||
|
source: JOINT_LIMIT_SOURCE_CUSTOM
|
||||||
|
joints { joint_name: "R_SHOULDER_P" q_lb: -3.14 q_ub: 3.14 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_SHOULDER_R" q_lb: -0.78 q_ub: 1.57 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_SHOULDER_Y" q_lb: -3.14 q_ub: 3.14 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_ELBOW_R" q_lb: 0 q_ub: 2.05 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_WRIST_P" q_lb: -3.14 q_ub: 3.14 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_WRIST_Y" q_lb: -0.78 q_ub: 0.78 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_WRIST_R" q_lb: -0.57 q_ub: 1.57 qd: 5.0 qdd: 10.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
motors {
|
||||||
|
motors { id: 1 joint_name: "R_SHOULDER_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 2 joint_name: "R_SHOULDER_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 3 joint_name: "R_SHOULDER_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 4 joint_name: "R_ELBOW_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 5 joint_name: "R_WRIST_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 6 joint_name: "R_WRIST_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 7 joint_name: "R_WRIST_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
motor {
|
||||||
|
id: "ethercat_motors"
|
||||||
|
|
||||||
|
motor_groups {
|
||||||
|
id: "right_arm_ethercat"
|
||||||
|
bus_type: MOTOR_BUS_ETHERCAT
|
||||||
|
vendor: MOTOR_VENDOR_EYOU
|
||||||
|
protocol: MOTOR_PROTOCOL_ETHERCAT_CIA402
|
||||||
|
|
||||||
|
ethercat {
|
||||||
|
master_index: 0
|
||||||
|
cycle_us: 1000
|
||||||
|
slave_op_timeout_ms: 15000
|
||||||
|
slave_state_poll_period_ms: 10
|
||||||
|
|
||||||
|
cia402 {
|
||||||
|
profile_position_trigger_delay_ms: 2
|
||||||
|
state_transition_timeout_ms: 1200
|
||||||
|
velocity_stop_timeout_ms: 2000
|
||||||
|
status_poll_period_ms: 10
|
||||||
|
stopped_velocity_tolerance_rad_s: 0.001
|
||||||
|
}
|
||||||
|
|
||||||
|
dc {
|
||||||
|
enable: false
|
||||||
|
reference_motor_id: 1
|
||||||
|
sync0_cycle_us: 1000
|
||||||
|
sync0_shift_us: 0
|
||||||
|
sync_reference_clock_period: 1
|
||||||
|
assign_activate: 768
|
||||||
|
sync_monitor_period_ms: 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
slaves { motor_id: 1 alias: 0 position: 0 }
|
||||||
|
slaves { motor_id: 2 alias: 0 position: 1 }
|
||||||
|
slaves { motor_id: 3 alias: 0 position: 2 }
|
||||||
|
slaves { motor_id: 4 alias: 0 position: 3 }
|
||||||
|
}
|
||||||
|
|
||||||
|
joint_limits {
|
||||||
|
enable: true
|
||||||
|
source: JOINT_LIMIT_SOURCE_CUSTOM
|
||||||
|
joints { joint_name: "R_SHOULDER_P" q_lb: -3.14 q_ub: 3.14 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_SHOULDER_R" q_lb: -0.78 q_ub: 1.57 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_SHOULDER_Y" q_lb: -3.14 q_ub: 3.14 qd: 5.0 qdd: 10.0 }
|
||||||
|
joints { joint_name: "R_ELBOW_R" q_lb: 0 q_ub: 2.05 qd: 5.0 qdd: 10.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
motors {
|
||||||
|
motors { id: 1 joint_name: "R_SHOULDER_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 2 joint_name: "R_SHOULDER_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 3 joint_name: "R_SHOULDER_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
motors { id: 4 joint_name: "R_ELBOW_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,6 @@ motor {
|
|||||||
bus_type: MOTOR_BUS_MUJOCO
|
bus_type: MOTOR_BUS_MUJOCO
|
||||||
vendor: MOTOR_VENDOR_MUJOCO
|
vendor: MOTOR_VENDOR_MUJOCO
|
||||||
protocol: MOTOR_PROTOCOL_MUJOCO
|
protocol: MOTOR_PROTOCOL_MUJOCO
|
||||||
tool_frame: "R_FINGER_TIP"
|
|
||||||
mujoco {
|
mujoco {
|
||||||
world_id: "mujoco_world"
|
world_id: "mujoco_world"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ motor {
|
|||||||
bus_type: MOTOR_BUS_CAN
|
bus_type: MOTOR_BUS_CAN
|
||||||
vendor: MOTOR_VENDOR_TI5
|
vendor: MOTOR_VENDOR_TI5
|
||||||
protocol: MOTOR_PROTOCOL_CANOPEN
|
protocol: MOTOR_PROTOCOL_CANOPEN
|
||||||
tool_frame: "L_FINGER_TIP"
|
|
||||||
can {
|
can {
|
||||||
channel_id: 0
|
channel_id: 0
|
||||||
}
|
}
|
||||||
@ -16,13 +15,13 @@ motor {
|
|||||||
urdf_path: "model/xiaoyan_description/dual_arm.urdf"
|
urdf_path: "model/xiaoyan_description/dual_arm.urdf"
|
||||||
}
|
}
|
||||||
motors {
|
motors {
|
||||||
motors { id: 23 joint_name: "L_SHOULDER_P" }
|
motors { id: 23 joint_name: "L_SHOULDER_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 24 joint_name: "L_SHOULDER_R" }
|
motors { id: 24 joint_name: "L_SHOULDER_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 25 joint_name: "L_SHOULDER_Y" }
|
motors { id: 25 joint_name: "L_SHOULDER_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 26 joint_name: "L_ELBOW_R" }
|
motors { id: 26 joint_name: "L_ELBOW_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 27 joint_name: "L_WRIST_P" }
|
motors { id: 27 joint_name: "L_WRIST_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 28 joint_name: "L_WRIST_Y" }
|
motors { id: 28 joint_name: "L_WRIST_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 29 joint_name: "L_WRIST_R" }
|
motors { id: 29 joint_name: "L_WRIST_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +30,6 @@ motor {
|
|||||||
bus_type: MOTOR_BUS_CAN
|
bus_type: MOTOR_BUS_CAN
|
||||||
vendor: MOTOR_VENDOR_TI5
|
vendor: MOTOR_VENDOR_TI5
|
||||||
protocol: MOTOR_PROTOCOL_CANOPEN
|
protocol: MOTOR_PROTOCOL_CANOPEN
|
||||||
tool_frame: "R_FINGER_TIP"
|
|
||||||
can {
|
can {
|
||||||
channel_id: 1
|
channel_id: 1
|
||||||
}
|
}
|
||||||
@ -47,13 +45,13 @@ motor {
|
|||||||
joints { joint_name: "R_WRIST_R" q_lb: -0.57 q_ub: 1.57 qd: 5.0 qdd: 10.0 }
|
joints { joint_name: "R_WRIST_R" q_lb: -0.57 q_ub: 1.57 qd: 5.0 qdd: 10.0 }
|
||||||
}
|
}
|
||||||
motors {
|
motors {
|
||||||
motors { id: 16 joint_name: "R_SHOULDER_P" }
|
motors { id: 16 joint_name: "R_SHOULDER_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 17 joint_name: "R_SHOULDER_R" }
|
motors { id: 17 joint_name: "R_SHOULDER_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 18 joint_name: "R_SHOULDER_Y" }
|
motors { id: 18 joint_name: "R_SHOULDER_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 19 joint_name: "R_ELBOW_R" }
|
motors { id: 19 joint_name: "R_ELBOW_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 20 joint_name: "R_WRIST_P" }
|
motors { id: 20 joint_name: "R_WRIST_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 21 joint_name: "R_WRIST_Y" }
|
motors { id: 21 joint_name: "R_WRIST_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 22 joint_name: "R_WRIST_R" }
|
motors { id: 22 joint_name: "R_WRIST_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,9 +71,9 @@ motor {
|
|||||||
joints { joint_name: "HEAD_R" q_lb: -3.14 q_ub: 3.14 qd: 3.0 }
|
joints { joint_name: "HEAD_R" q_lb: -3.14 q_ub: 3.14 qd: 3.0 }
|
||||||
}
|
}
|
||||||
motors {
|
motors {
|
||||||
motors { id: 32 joint_name: "HEAD_Y" }
|
motors { id: 32 joint_name: "HEAD_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 30 joint_name: "HEAD_P" }
|
motors { id: 30 joint_name: "HEAD_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 31 joint_name: "HEAD_R" }
|
motors { id: 31 joint_name: "HEAD_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +92,8 @@ motor {
|
|||||||
joints { joint_name: "WAIST_P" q_lb: -3.14 q_ub: 3.14 qd: 3.0 }
|
joints { joint_name: "WAIST_P" q_lb: -3.14 q_ub: 3.14 qd: 3.0 }
|
||||||
}
|
}
|
||||||
motors {
|
motors {
|
||||||
motors { id: 4 joint_name: "WAIST_Y" }
|
motors { id: 4 joint_name: "WAIST_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 15 joint_name: "WAIST_P" }
|
motors { id: 15 joint_name: "WAIST_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ device_manager {
|
|||||||
name: "cmvr_es"
|
name: "cmvr_es"
|
||||||
version: "0.1"
|
version: "0.1"
|
||||||
description: "cmvr edge system version 0.1"
|
description: "cmvr edge system version 0.1"
|
||||||
|
init_all_motors_when_no_active_joints: true
|
||||||
|
|
||||||
devices {
|
devices {
|
||||||
id: "mujoco_world"
|
id: "mujoco_world"
|
||||||
@ -74,6 +75,13 @@ device_manager {
|
|||||||
enable: false
|
enable: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devices {
|
||||||
|
id: "ethercat_motors"
|
||||||
|
type: DEVICE_TYPE_MOTOR_SYSTEM
|
||||||
|
config_file: "devices/motor/ethercat_motors.pb.txt"
|
||||||
|
enable: false
|
||||||
|
}
|
||||||
|
|
||||||
devices {
|
devices {
|
||||||
id: "right_arm"
|
id: "right_arm"
|
||||||
type: DEVICE_TYPE_ROBOT_ARM
|
type: DEVICE_TYPE_ROBOT_ARM
|
||||||
|
|||||||
@ -73,7 +73,7 @@ public:
|
|||||||
bool isConnected() const override { return motor_manager_ != nullptr; }
|
bool isConnected() const override { return motor_manager_ != nullptr; }
|
||||||
Result powerOn() override { return torqueOn(); }
|
Result powerOn() override { return torqueOn(); }
|
||||||
Result powerOff() override { return torqueOff(); }
|
Result powerOff() override { return torqueOff(); }
|
||||||
Result brakeRelease() override { return torqueOn(); }
|
Result brakeRelease() override;
|
||||||
Result shutdown() override;
|
Result shutdown() override;
|
||||||
Result clearFault() override { return Result::success(); }
|
Result clearFault() override { return Result::success(); }
|
||||||
Result unlockProtectiveStop() override { return Result::success(); }
|
Result unlockProtectiveStop() override { return Result::success(); }
|
||||||
|
|||||||
@ -196,7 +196,10 @@ Result MotorRobotArm::torqueOn()
|
|||||||
if (!motor) {
|
if (!motor) {
|
||||||
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
||||||
}
|
}
|
||||||
motor->brake();
|
if (!motor->torqueOn()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to torque on motor for joint: " + joint_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
emergency_stopped_ = false;
|
emergency_stopped_ = false;
|
||||||
return Result::success();
|
return Result::success();
|
||||||
@ -209,7 +212,26 @@ Result MotorRobotArm::torqueOff()
|
|||||||
if (!motor) {
|
if (!motor) {
|
||||||
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
||||||
}
|
}
|
||||||
motor->torqueOff();
|
if (!motor->torqueOff() || !motor->brakeRelease()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to torque off motor for joint: " + joint_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result MotorRobotArm::brakeRelease()
|
||||||
|
{
|
||||||
|
for (const auto& joint_name : joint_names_) {
|
||||||
|
auto motor = getMotor_(joint_name);
|
||||||
|
if (!motor) {
|
||||||
|
return Result::failure(ArmErrorCode::RobotNotReady,
|
||||||
|
"motor not found for joint: " + joint_name);
|
||||||
|
}
|
||||||
|
if (!motor->brakeRelease()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to release brake for joint: " + joint_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Result::success();
|
return Result::success();
|
||||||
}
|
}
|
||||||
@ -238,7 +260,10 @@ Result MotorRobotArm::emergencyStop()
|
|||||||
if (!motor) {
|
if (!motor) {
|
||||||
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
return Result::failure(ArmErrorCode::RobotNotReady, "motor not found for joint: " + joint_name);
|
||||||
}
|
}
|
||||||
motor->brake();
|
if (!motor->quickStop()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to quick stop motor for joint: " + joint_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
emergency_stopped_ = true;
|
emergency_stopped_ = true;
|
||||||
return Result::success();
|
return Result::success();
|
||||||
@ -298,7 +323,11 @@ Result MotorRobotArm::moveJ(const JointPositionCommand& target, const MotionOpti
|
|||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < motors.size(); ++i) {
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
const double qd = i < sample.velocity.size() ? sample.velocity[i] : 0.0;
|
const double qd = i < sample.velocity.size() ? sample.velocity[i] : 0.0;
|
||||||
motors[i]->setTarget(sample.position[i], qd);
|
if (!motors[i]->commandCyclicPosition(sample.position[i], qd)) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to command cyclic position for joint: " +
|
||||||
|
motors[i]->jointName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (k + 1 < samples.size()) {
|
if (k + 1 < samples.size()) {
|
||||||
const double next_t = samples[k + 1].t > 0.0 ? samples[k + 1].t
|
const double next_t = samples[k + 1].t > 0.0 ? samples[k + 1].t
|
||||||
@ -331,7 +360,11 @@ Result MotorRobotArm::speedJ(const JointVelocityCommand& velocity,
|
|||||||
if (motor->getMode() != msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY) {
|
if (motor->getMode() != msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY) {
|
||||||
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY);
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY);
|
||||||
}
|
}
|
||||||
motor->setTarget(velocity.velocity[i] * speed_scaling_);
|
if (!motor->commandCyclicVelocity(velocity.velocity[i] * speed_scaling_)) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to command cyclic velocity for joint: " +
|
||||||
|
joint_names_[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,7 +490,11 @@ Result MotorRobotArm::servoJ(const JointPositionCommand& target)
|
|||||||
if (motor->getMode() != msgs::RUN_MODE_CYCLIC_SYNC_POSITION) {
|
if (motor->getMode() != msgs::RUN_MODE_CYCLIC_SYNC_POSITION) {
|
||||||
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
}
|
}
|
||||||
motor->setTarget(target.position[i], 0.0);
|
if (!motor->commandCyclicPosition(target.position[i], 0.0)) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandFailed,
|
||||||
|
"failed to command cyclic position for joint: " +
|
||||||
|
joint_names_[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Result::success();
|
return Result::success();
|
||||||
}
|
}
|
||||||
@ -740,7 +777,9 @@ bool MotorRobotArm::executeMoveLTrajectory_(const CartesianJointTrajectory& traj
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (std::size_t j = 0; j < motors.size(); ++j) {
|
for (std::size_t j = 0; j < motors.size(); ++j) {
|
||||||
motors[j]->setTarget(position[j], velocity[j]);
|
if (!motors[j]->commandCyclicPosition(position[j], velocity[j])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next_deadline += std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
next_deadline += std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||||
std::chrono::duration<double>(dt_segment));
|
std::chrono::duration<double>(dt_segment));
|
||||||
|
|||||||
@ -30,7 +30,7 @@ namespace cmvr {
|
|||||||
return BASE_ID + sdo_frame_.node_id();
|
return BASE_ID + sdo_frame_.node_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetFrameData(msgs::CommandSpecifier cs, msgs::ObIndex index,msgs::ObSubIndex sub_index, uint32_t data) {
|
void SetFrameData(msgs::CommandSpecifier cs, uint32_t index, uint32_t sub_index, uint32_t data) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
sdo_frame_.set_cs(cs);
|
sdo_frame_.set_cs(cs);
|
||||||
sdo_frame_.set_index(index);
|
sdo_frame_.set_index(index);
|
||||||
|
|||||||
@ -49,10 +49,10 @@ namespace cmvr {
|
|||||||
auto command = static_cast<msgs::CommandSpecifier>(bytes[0]);
|
auto command = static_cast<msgs::CommandSpecifier>(bytes[0]);
|
||||||
|
|
||||||
// 解析 index(字节1和字节2,低字节优先)
|
// 解析 index(字节1和字节2,低字节优先)
|
||||||
auto index = static_cast<msgs::ObIndex>(bytes[1] + (bytes[2] << 8));
|
const uint32_t index = bytes[1] + (bytes[2] << 8);
|
||||||
|
|
||||||
// 解析 subindex(字节3)
|
// 解析 subindex(字节3)
|
||||||
auto subindex = static_cast<msgs::ObSubIndex>(bytes[3]);
|
const uint32_t subindex = bytes[3];
|
||||||
|
|
||||||
// 根据 command 解析 data(字节4~7)
|
// 根据 command 解析 data(字节4~7)
|
||||||
uint32_t data = 0;
|
uint32_t data = 0;
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
add_library(motor_core INTERFACE)
|
add_library(motor_core INTERFACE)
|
||||||
|
|
||||||
target_include_directories(motor_core INTERFACE ${CMAKE_SOURCE_DIR}/cmvr-es/devices)
|
target_include_directories(motor_core
|
||||||
|
INTERFACE
|
||||||
|
${CMAKE_SOURCE_DIR}/cmvr-es
|
||||||
|
${CMAKE_SOURCE_DIR}/cmvr-es/devices
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(motor_core
|
target_link_libraries(motor_core
|
||||||
INTERFACE
|
INTERFACE
|
||||||
@ -12,4 +16,5 @@ add_library(cmvr_es::device::motor_core ALIAS motor_core)
|
|||||||
add_subdirectory(drivers/ti5_canopen)
|
add_subdirectory(drivers/ti5_canopen)
|
||||||
add_subdirectory(drivers/mujoco)
|
add_subdirectory(drivers/mujoco)
|
||||||
add_subdirectory(bus_runtime)
|
add_subdirectory(bus_runtime)
|
||||||
|
add_subdirectory(drivers/ethercat_motor)
|
||||||
add_subdirectory(manager)
|
add_subdirectory(manager)
|
||||||
|
|||||||
@ -66,13 +66,22 @@ namespace cmvr::device{
|
|||||||
return protocol_->getMode(node_id_);
|
return protocol_->getMode(node_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void torqueOff() {
|
virtual bool torqueOn() {
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
if (!protocol_) {
|
if (!protocol_) {
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
protocol_->torqueOff(node_id_);
|
return protocol_->torqueOn(node_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool torqueOff() {
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!protocol_) {
|
||||||
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return protocol_->torqueOff(node_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void setLimitQ(double ub, double lb) {
|
virtual void setLimitQ(double ub, double lb) {
|
||||||
@ -101,43 +110,69 @@ namespace cmvr::device{
|
|||||||
}
|
}
|
||||||
// virtual void setLimitTau(double tau) = 0;
|
// virtual void setLimitTau(double tau) = 0;
|
||||||
// virtual void setLimitCurrent(double tau) = 0;
|
// virtual void setLimitCurrent(double tau) = 0;
|
||||||
virtual void brake() {
|
virtual bool brakeRelease() {
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
if (!protocol_) {
|
if (!protocol_) {
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
protocol_->brake(node_id_);
|
return protocol_->brakeRelease(node_id_);
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param q unit : rad
|
|
||||||
*/
|
|
||||||
virtual void setQ(double q) {
|
|
||||||
std::scoped_lock lock(mtx_);
|
|
||||||
if (!protocol_) {
|
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protocol_->setQ(node_id_, q);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void setTarget(double q,double qd) {
|
virtual bool quickStop() {
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
if (!protocol_) {
|
if (!protocol_) {
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
protocol_->setTarget(node_id_,q, qd);
|
return protocol_->quickStop(node_id_);
|
||||||
|
}
|
||||||
|
virtual bool commandProfilePosition(double target_q,
|
||||||
|
double max_qd = 0.0,
|
||||||
|
double max_qdd = 0.0) {
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!protocol_) {
|
||||||
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return protocol_->commandProfilePosition(node_id_, target_q, max_qd, max_qdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void setTarget(double qd) {
|
virtual bool commandProfileVelocity(double target_qd, double max_qdd = 0.0) {
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
if (!protocol_) {
|
if (!protocol_) {
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
protocol_->setTarget(node_id_, qd);
|
return protocol_->commandProfileVelocity(node_id_, target_qd, max_qdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool commandCyclicPosition(double target_q,
|
||||||
|
double target_qd = 0.0) {
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!protocol_) {
|
||||||
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return protocol_->commandCyclicPosition(node_id_, target_q, target_qd);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool commandCyclicVelocity(double target_qd) {
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!protocol_) {
|
||||||
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return protocol_->commandCyclicVelocity(node_id_, target_qd);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool commandCyclicTorque(double target_tau) {
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!protocol_) {
|
||||||
|
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return protocol_->commandCyclicTorque(node_id_, target_tau);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool calibrateZeroQ() {
|
virtual bool calibrateZeroQ() {
|
||||||
@ -156,16 +191,6 @@ namespace cmvr::device{
|
|||||||
}
|
}
|
||||||
return protocol_->reachedTargetQ(node_id_);
|
return protocol_->reachedTargetQ(node_id_);
|
||||||
}
|
}
|
||||||
// rad /s
|
|
||||||
virtual void setQd(double qd) {
|
|
||||||
std::scoped_lock lock(mtx_);
|
|
||||||
if (!protocol_) {
|
|
||||||
CMVR_LOG(ERROR) << "Protocol not set for motor";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return protocol_->setQd(node_id_,qd);
|
|
||||||
}
|
|
||||||
// virtual void setQdd(double qdd) = 0; // rad /s^2
|
|
||||||
// virtual void setTau(double tau) = 0; // N m
|
// virtual void setTau(double tau) = 0; // N m
|
||||||
// virtual void clear_err() = 0;
|
// virtual void clear_err() = 0;
|
||||||
// virtual void getStatus() = 0;
|
// virtual void getStatus() = 0;
|
||||||
|
|||||||
@ -4,7 +4,18 @@ add_library(motor_bus_runtime SHARED
|
|||||||
ethercat/src/ethercat_motor_bus_runtime.cpp
|
ethercat/src/ethercat_motor_bus_runtime.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(motor_bus_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
set(IGH_ETHERCAT_ROOT
|
||||||
|
${CMAKE_SOURCE_DIR}/dependency/x86/third_party/ethercat/v1.7.0
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(motor_bus_runtime
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
PRIVATE
|
||||||
|
${IGH_ETHERCAT_ROOT}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_directories(motor_bus_runtime PRIVATE ${IGH_ETHERCAT_ROOT}/lib)
|
||||||
|
|
||||||
target_link_libraries(motor_bus_runtime
|
target_link_libraries(motor_bus_runtime
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@ -12,9 +23,28 @@ target_link_libraries(motor_bus_runtime
|
|||||||
cmvr_es::device::motor_core
|
cmvr_es::device::motor_core
|
||||||
cmvr_es::mujoco_world
|
cmvr_es::mujoco_world
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
ethercat
|
||||||
cmvr_es::device::canbus
|
cmvr_es::device::canbus
|
||||||
glog
|
glog
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(cmvr_es::device::motor_bus_runtime ALIAS motor_bus_runtime)
|
add_library(cmvr_es::device::motor_bus_runtime ALIAS motor_bus_runtime)
|
||||||
install(TARGETS motor_bus_runtime LIBRARY DESTINATION lib)
|
install(TARGETS motor_bus_runtime LIBRARY DESTINATION lib)
|
||||||
|
|
||||||
|
add_executable(ethercat_motor_bus_runtime_real_test
|
||||||
|
ethercat/src/ethercat_motor_bus_runtime_real_test.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ethercat_motor_bus_runtime_real_test
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/cmvr-es
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ethercat_motor_bus_runtime_real_test
|
||||||
|
PRIVATE
|
||||||
|
cmvr_es::device::motor_bus_runtime
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
pthread
|
||||||
|
glog
|
||||||
|
)
|
||||||
|
|||||||
@ -1,10 +1,23 @@
|
|||||||
#ifndef CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
|
#ifndef CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
|
||||||
#define CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
|
#define CMVR_ES_ETHERCAT_MOTOR_BUS_RUNTIME_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "../../abstract_motor_bus_runtime.h"
|
#include "../../abstract_motor_bus_runtime.h"
|
||||||
|
#include "motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h"
|
||||||
|
|
||||||
|
typedef struct ec_domain ec_domain_t;
|
||||||
|
typedef struct ec_master ec_master_t;
|
||||||
|
typedef struct ec_slave_config ec_slave_config_t;
|
||||||
|
|
||||||
namespace cmvr::device {
|
namespace cmvr::device {
|
||||||
|
|
||||||
@ -17,12 +30,136 @@ public:
|
|||||||
|
|
||||||
const std::string& id() const { return id_; }
|
const std::string& id() const { return id_; }
|
||||||
const config::EtherCATConfig& config() const { return config_; }
|
const config::EtherCATConfig& config() const { return config_; }
|
||||||
|
void setPdoMapping(EthercatPdoMapping mapping);
|
||||||
|
const EthercatPdoMapping& pdoMapping() const { return pdo_mapping_; }
|
||||||
const config::EthercatSlaveConfig* slaveForMotor(int motor_id) const;
|
const config::EthercatSlaveConfig* slaveForMotor(int motor_id) const;
|
||||||
|
bool hasMotor(int motor_id) const;
|
||||||
|
|
||||||
|
bool hasPdoEntry(int motor_id, std::uint16_t index, std::uint8_t subindex) const;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool writePdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value)
|
||||||
|
{
|
||||||
|
return writePdoRaw_(motor_id, index, subindex, valueBitLength_<T>(),
|
||||||
|
toRawValue_(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool readPdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T& value) const
|
||||||
|
{
|
||||||
|
std::uint64_t raw = 0;
|
||||||
|
if (!readPdoRaw_(motor_id, index, subindex, valueBitLength_<T>(), raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = fromRawValue_<T>(raw);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool writeSdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T value)
|
||||||
|
{
|
||||||
|
return writeSdoRaw_(motor_id, index, subindex, valueBitLength_<T>(),
|
||||||
|
toRawValue_(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool readSdo(int motor_id, std::uint16_t index, std::uint8_t subindex, T& value)
|
||||||
|
{
|
||||||
|
std::uint64_t raw = 0;
|
||||||
|
if (!readSdoRaw_(motor_id, index, subindex, valueBitLength_<T>(), raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = fromRawValue_<T>(raw);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct PdoEntryRuntime {
|
||||||
|
EthercatPdoEntryConfig cfg;
|
||||||
|
unsigned int offset{0};
|
||||||
|
bool rx{false};
|
||||||
|
std::uint64_t value{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SlaveRuntime {
|
||||||
|
config::EthercatSlaveConfig cfg;
|
||||||
|
ec_slave_config_t* slave_config{nullptr};
|
||||||
|
std::unordered_map<std::uint32_t, PdoEntryRuntime> pdo_entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool configureSlave_(SlaveRuntime& slave);
|
||||||
|
bool configureDc_();
|
||||||
|
bool waitSlavesOperational_();
|
||||||
|
void cyclicLoop_();
|
||||||
|
void readFeedbackLocked_();
|
||||||
|
void writeCommandsLocked_();
|
||||||
|
void releaseMaster_();
|
||||||
|
bool hasValidPdoMapping_() const;
|
||||||
|
bool writePdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex,
|
||||||
|
std::uint8_t bit_len, std::uint64_t value);
|
||||||
|
bool readPdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex,
|
||||||
|
std::uint8_t bit_len, std::uint64_t& value) const;
|
||||||
|
bool writeSdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex,
|
||||||
|
std::uint8_t bit_len, std::uint64_t value);
|
||||||
|
bool readSdoRaw_(int motor_id, std::uint16_t index, std::uint8_t subindex,
|
||||||
|
std::uint8_t bit_len, std::uint64_t& value);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr std::uint8_t valueBitLength_()
|
||||||
|
{
|
||||||
|
using ValueType = std::remove_cv_t<T>;
|
||||||
|
static_assert(std::is_integral_v<ValueType>, "EtherCAT object values must be integral");
|
||||||
|
static_assert(!std::is_same_v<ValueType, bool>, "bool is not a valid EtherCAT object value");
|
||||||
|
static_assert(sizeof(ValueType) == 1 || sizeof(ValueType) == 2 || sizeof(ValueType) == 4,
|
||||||
|
"only 8/16/32-bit EtherCAT object values are supported");
|
||||||
|
return static_cast<std::uint8_t>(sizeof(ValueType) * 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static std::uint64_t toRawValue_(T value)
|
||||||
|
{
|
||||||
|
using ValueType = std::remove_cv_t<T>;
|
||||||
|
using UnsignedType = std::make_unsigned_t<ValueType>;
|
||||||
|
return static_cast<std::uint64_t>(static_cast<UnsignedType>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static T fromRawValue_(std::uint64_t raw)
|
||||||
|
{
|
||||||
|
using ValueType = std::remove_cv_t<T>;
|
||||||
|
using UnsignedType = std::make_unsigned_t<ValueType>;
|
||||||
|
const auto unsigned_value = static_cast<UnsignedType>(raw);
|
||||||
|
ValueType value{};
|
||||||
|
std::memcpy(&value, &unsigned_value, sizeof(ValueType));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::uint32_t pdoEntryKey_(std::uint16_t index, std::uint8_t subindex);
|
||||||
|
static std::string hexIndex_(std::uint32_t index);
|
||||||
|
static std::uint64_t maskValue_(std::uint64_t value, std::uint8_t bit_len);
|
||||||
|
static bool isSupportedBitLength_(std::uint8_t bit_len);
|
||||||
|
static std::uint64_t readEntryValue_(const std::uint8_t* domain_data,
|
||||||
|
const PdoEntryRuntime& entry);
|
||||||
|
static void writeEntryValue_(std::uint8_t* domain_data,
|
||||||
|
const PdoEntryRuntime& entry);
|
||||||
|
static std::uint64_t steadyTimeNs_();
|
||||||
|
static std::uint64_t timePointNs_(std::chrono::steady_clock::time_point time_point);
|
||||||
|
static std::uint32_t usToNs_(std::uint32_t value_us);
|
||||||
|
static std::int32_t usToNs_(std::int32_t value_us);
|
||||||
|
|
||||||
std::string id_;
|
std::string id_;
|
||||||
config::EtherCATConfig config_;
|
config::EtherCATConfig config_;
|
||||||
std::unordered_map<int, const config::EthercatSlaveConfig*> slaves_by_motor_id_;
|
EthercatPdoMapping pdo_mapping_;
|
||||||
|
std::unordered_map<int, SlaveRuntime> slaves_by_motor_id_;
|
||||||
|
|
||||||
|
ec_master_t* master_{nullptr};
|
||||||
|
ec_domain_t* domain_{nullptr};
|
||||||
|
std::uint8_t* domain_data_{nullptr};
|
||||||
|
|
||||||
|
mutable std::mutex data_mutex_;
|
||||||
|
std::thread cyclic_thread_;
|
||||||
|
std::atomic<bool> running_{false};
|
||||||
|
bool initialized_{false};
|
||||||
bool started_{false};
|
bool started_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef CMVR_ES_ETHERCAT_PDO_MAPPING_H
|
||||||
|
#define CMVR_ES_ETHERCAT_PDO_MAPPING_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
struct EthercatPdoEntryConfig {
|
||||||
|
std::uint16_t index{0};
|
||||||
|
std::uint8_t subindex{0};
|
||||||
|
std::uint8_t bit_len{0};
|
||||||
|
std::string name;
|
||||||
|
bool padding{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EthercatPdoConfig {
|
||||||
|
std::uint16_t index{0};
|
||||||
|
std::uint8_t sync_manager{0};
|
||||||
|
bool rx{false};
|
||||||
|
std::vector<EthercatPdoEntryConfig> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EthercatPdoMapping {
|
||||||
|
std::uint32_t vendor_id{0};
|
||||||
|
std::uint32_t product_code{0};
|
||||||
|
std::string name;
|
||||||
|
std::vector<EthercatPdoConfig> rx_pdos;
|
||||||
|
std::vector<EthercatPdoConfig> tx_pdos;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_ETHERCAT_PDO_MAPPING_H
|
||||||
@ -1,7 +1,18 @@
|
|||||||
#include "motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
#include "motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "common/base/logging/logger.h"
|
#include "common/base/logging/logger.h"
|
||||||
|
|
||||||
|
#include <ecrt.h>
|
||||||
|
|
||||||
namespace cmvr::device {
|
namespace cmvr::device {
|
||||||
|
|
||||||
bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg)
|
bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg)
|
||||||
@ -21,14 +32,34 @@ bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
config_ = group_cfg.ethercat();
|
config_ = group_cfg.ethercat();
|
||||||
if (config_.master_id().empty()) {
|
|
||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] master_id is empty: " << id_;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (config_.cycle_us() <= 0) {
|
if (config_.cycle_us() <= 0) {
|
||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] cycle_us must be positive: " << id_;
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] cycle_us must be positive: " << id_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!config_.has_slave_op_timeout_ms()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing slave_op_timeout_ms: " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (config_.slave_op_timeout_ms() == 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] slave_op_timeout_ms must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!config_.has_slave_state_poll_period_ms()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing slave_state_poll_period_ms: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (config_.slave_state_poll_period_ms() == 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] slave_state_poll_period_ms must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!hasValidPdoMapping_()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] PDO mapping is not configured: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
slaves_by_motor_id_.clear();
|
slaves_by_motor_id_.clear();
|
||||||
for (const auto& slave : config_.slaves()) {
|
for (const auto& slave : config_.slaves()) {
|
||||||
@ -36,34 +67,106 @@ bool EthercatMotorBusRuntime::init(const config::MotorGroupConfig& group_cfg)
|
|||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] invalid motor_id in slave config: " << id_;
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] invalid motor_id in slave config: " << id_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (slave.slave_index() < 0) {
|
|
||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] invalid slave_index for motor "
|
|
||||||
<< slave.motor_id() << " in group: " << id_;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (slaves_by_motor_id_.count(slave.motor_id()) > 0) {
|
if (slaves_by_motor_id_.count(slave.motor_id()) > 0) {
|
||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] duplicate slave motor_id: "
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] duplicate slave motor_id: "
|
||||||
<< slave.motor_id() << " in group: " << id_;
|
<< slave.motor_id() << " in group: " << id_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
slaves_by_motor_id_[slave.motor_id()] = &slave;
|
SlaveRuntime runtime;
|
||||||
|
runtime.cfg = slave;
|
||||||
|
slaves_by_motor_id_.emplace(slave.motor_id(), std::move(runtime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (slaves_by_motor_id_.empty()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] no EtherCAT slaves configured: " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
master_ = ecrt_request_master(config_.master_index());
|
||||||
|
if (!master_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to request EtherCAT master "
|
||||||
|
<< config_.master_index() << ": " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
domain_ = ecrt_master_create_domain(master_);
|
||||||
|
if (!domain_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to create EtherCAT domain: " << id_;
|
||||||
|
releaseMaster_();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
(void)motor_id;
|
||||||
|
if (!configureSlave_(slave)) {
|
||||||
|
releaseMaster_();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configureDc_()) {
|
||||||
|
releaseMaster_();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::setPdoMapping(EthercatPdoMapping mapping)
|
||||||
|
{
|
||||||
|
pdo_mapping_ = std::move(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
bool EthercatMotorBusRuntime::start()
|
bool EthercatMotorBusRuntime::start()
|
||||||
{
|
{
|
||||||
if (started_) {
|
if (started_) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] EtherCAT master is not implemented yet: " << id_;
|
if (!initialized_ || !master_ || !domain_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized: " << id_;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ecrt_master_activate(master_) != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to activate EtherCAT master: " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
domain_data_ = ecrt_domain_data(domain_);
|
||||||
|
if (!domain_data_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to get EtherCAT domain data: " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
writeCommandsLocked_();
|
||||||
|
}
|
||||||
|
|
||||||
|
running_.store(true);
|
||||||
|
cyclic_thread_ = std::thread(&EthercatMotorBusRuntime::cyclicLoop_, this);
|
||||||
|
started_ = true;
|
||||||
|
|
||||||
|
if (!waitSlavesOperational_()) {
|
||||||
|
stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMVR_LOG(INFO) << "[EthercatMotorBusRuntime] started EtherCAT runtime: " << id_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void EthercatMotorBusRuntime::stop()
|
void EthercatMotorBusRuntime::stop()
|
||||||
{
|
{
|
||||||
|
running_.store(false);
|
||||||
|
if (cyclic_thread_.joinable()) {
|
||||||
|
cyclic_thread_.join();
|
||||||
|
}
|
||||||
started_ = false;
|
started_ = false;
|
||||||
|
initialized_ = false;
|
||||||
|
domain_data_ = nullptr;
|
||||||
|
releaseMaster_();
|
||||||
}
|
}
|
||||||
|
|
||||||
const config::EthercatSlaveConfig* EthercatMotorBusRuntime::slaveForMotor(const int motor_id) const
|
const config::EthercatSlaveConfig* EthercatMotorBusRuntime::slaveForMotor(const int motor_id) const
|
||||||
@ -72,7 +175,780 @@ const config::EthercatSlaveConfig* EthercatMotorBusRuntime::slaveForMotor(const
|
|||||||
if (it == slaves_by_motor_id_.end()) {
|
if (it == slaves_by_motor_id_.end()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return it->second;
|
return &it->second.cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::hasMotor(const int motor_id) const
|
||||||
|
{
|
||||||
|
return slaves_by_motor_id_.count(motor_id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::hasPdoEntry(const int motor_id,
|
||||||
|
const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
const auto slave_it = slaves_by_motor_id_.find(motor_id);
|
||||||
|
if (slave_it == slaves_by_motor_id_.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return slave_it->second.pdo_entries.count(pdoEntryKey_(index, subindex)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::configureSlave_(SlaveRuntime& slave)
|
||||||
|
{
|
||||||
|
slave.slave_config = ecrt_master_slave_config(master_,
|
||||||
|
static_cast<std::uint16_t>(slave.cfg.alias()),
|
||||||
|
static_cast<std::uint16_t>(slave.cfg.position()),
|
||||||
|
pdo_mapping_.vendor_id,
|
||||||
|
pdo_mapping_.product_code);
|
||||||
|
if (!slave.slave_config) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to get slave config: group=" << id_
|
||||||
|
<< ", motor_id=" << slave.cfg.motor_id()
|
||||||
|
<< ", alias=" << slave.cfg.alias()
|
||||||
|
<< ", position=" << slave.cfg.position()
|
||||||
|
<< ", vendor=" << hexIndex_(pdo_mapping_.vendor_id)
|
||||||
|
<< ", product=" << hexIndex_(pdo_mapping_.product_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SyncBuild {
|
||||||
|
bool rx{false};
|
||||||
|
std::vector<std::uint16_t> pdo_indices;
|
||||||
|
std::vector<std::vector<ec_pdo_entry_info_t>> entry_storage;
|
||||||
|
std::vector<ec_pdo_info_t> pdo_infos;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto add_pdo_to_sync_build = [](std::map<std::uint8_t, SyncBuild>& builds,
|
||||||
|
const EthercatPdoConfig& pdo) {
|
||||||
|
auto& build = builds[pdo.sync_manager];
|
||||||
|
build.rx = pdo.rx;
|
||||||
|
build.pdo_indices.push_back(pdo.index);
|
||||||
|
auto& entries = build.entry_storage.emplace_back();
|
||||||
|
entries.reserve(pdo.entries.size());
|
||||||
|
for (const auto& entry : pdo.entries) {
|
||||||
|
entries.push_back({entry.index, entry.subindex, entry.bit_len});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::uint8_t, SyncBuild> sync_builds;
|
||||||
|
for (const auto& pdo : pdo_mapping_.rx_pdos) {
|
||||||
|
add_pdo_to_sync_build(sync_builds, pdo);
|
||||||
|
}
|
||||||
|
for (const auto& pdo : pdo_mapping_.tx_pdos) {
|
||||||
|
add_pdo_to_sync_build(sync_builds, pdo);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [sync_manager, build] : sync_builds) {
|
||||||
|
(void)sync_manager;
|
||||||
|
build.pdo_infos.reserve(build.entry_storage.size());
|
||||||
|
for (std::size_t i = 0; i < build.entry_storage.size(); ++i) {
|
||||||
|
auto& entries = build.entry_storage[i];
|
||||||
|
build.pdo_infos.push_back({
|
||||||
|
build.pdo_indices[i],
|
||||||
|
static_cast<unsigned int>(entries.size()),
|
||||||
|
entries.data(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ec_sync_info_t> sync_infos;
|
||||||
|
sync_infos.push_back({0, EC_DIR_OUTPUT, 0, nullptr, EC_WD_DISABLE});
|
||||||
|
sync_infos.push_back({1, EC_DIR_INPUT, 0, nullptr, EC_WD_DISABLE});
|
||||||
|
for (auto& [sync_manager, build] : sync_builds) {
|
||||||
|
const auto direction = build.rx ? EC_DIR_OUTPUT : EC_DIR_INPUT;
|
||||||
|
const auto watchdog = build.rx ? EC_WD_ENABLE : EC_WD_DISABLE;
|
||||||
|
sync_infos.push_back({
|
||||||
|
sync_manager,
|
||||||
|
direction,
|
||||||
|
static_cast<unsigned int>(build.pdo_infos.size()),
|
||||||
|
build.pdo_infos.data(),
|
||||||
|
watchdog,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sync_infos.push_back({0xff});
|
||||||
|
|
||||||
|
if (ecrt_slave_config_pdos(slave.slave_config, EC_END, sync_infos.data()) != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to configure PDOs: group=" << id_
|
||||||
|
<< ", motor_id=" << slave.cfg.motor_id()
|
||||||
|
<< ", position=" << slave.cfg.position();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto register_entry = [&](const EthercatPdoEntryConfig& entry, const bool rx) -> bool {
|
||||||
|
if (entry.padding) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!isSupportedBitLength_(entry.bit_len)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported PDO entry bit length: "
|
||||||
|
<< static_cast<int>(entry.bit_len)
|
||||||
|
<< ", entry=" << hexIndex_(entry.index)
|
||||||
|
<< ":" << static_cast<int>(entry.subindex)
|
||||||
|
<< ", motor_id=" << slave.cfg.motor_id()
|
||||||
|
<< ", group=" << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = pdoEntryKey_(entry.index, entry.subindex);
|
||||||
|
if (slave.pdo_entries.count(key) > 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] duplicate PDO entry: "
|
||||||
|
<< hexIndex_(entry.index)
|
||||||
|
<< ":" << static_cast<int>(entry.subindex)
|
||||||
|
<< ", motor_id=" << slave.cfg.motor_id()
|
||||||
|
<< ", group=" << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int result = ecrt_slave_config_reg_pdo_entry(
|
||||||
|
slave.slave_config, entry.index, entry.subindex, domain_, nullptr);
|
||||||
|
if (result < 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to register PDO entry "
|
||||||
|
<< hexIndex_(entry.index) << ":" << static_cast<int>(entry.subindex)
|
||||||
|
<< ", motor_id=" << slave.cfg.motor_id()
|
||||||
|
<< ", group=" << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PdoEntryRuntime runtime;
|
||||||
|
runtime.cfg = entry;
|
||||||
|
runtime.offset = static_cast<unsigned int>(result);
|
||||||
|
runtime.rx = rx;
|
||||||
|
slave.pdo_entries.emplace(key, std::move(runtime));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& pdo : pdo_mapping_.rx_pdos) {
|
||||||
|
for (const auto& entry : pdo.entries) {
|
||||||
|
if (!register_entry(entry, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& pdo : pdo_mapping_.tx_pdos) {
|
||||||
|
for (const auto& entry : pdo.entries) {
|
||||||
|
if (!register_entry(entry, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::configureDc_()
|
||||||
|
{
|
||||||
|
if (!config_.has_dc()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing explicit DC config: "
|
||||||
|
<< id_ << ". Add dc { enable: false } or a complete enabled DC config.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& dc = config_.dc();
|
||||||
|
if (!dc.has_enable()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC enable: " << id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.enable()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dc.has_reference_motor_id()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC reference_motor_id: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.has_sync0_cycle_us()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC sync0_cycle_us: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.has_sync0_shift_us()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC sync0_shift_us: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.has_sync_reference_clock_period()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC sync_reference_clock_period: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.has_assign_activate()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC assign_activate: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!dc.has_sync_monitor_period_ms()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing DC sync_monitor_period_ms: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dc.reference_motor_id() <= 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC reference_motor_id must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dc.sync0_cycle_us() == 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC sync0_cycle_us must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dc.sync_reference_clock_period() == 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC sync_reference_clock_period must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dc.assign_activate() == 0 || dc.assign_activate() > 0xFFFFU) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC assign_activate must be in [1, 0xFFFF]: "
|
||||||
|
<< id_ << ", value=" << hexIndex_(dc.assign_activate());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dc.sync_monitor_period_ms() == 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC sync_monitor_period_ms must be positive: "
|
||||||
|
<< id_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto reference_it = slaves_by_motor_id_.find(dc.reference_motor_id());
|
||||||
|
if (reference_it == slaves_by_motor_id_.end() || !reference_it->second.slave_config) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] DC reference motor is not configured: "
|
||||||
|
<< id_ << ", reference_motor_id=" << dc.reference_motor_id();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int select_result = ecrt_master_select_reference_clock(
|
||||||
|
master_, reference_it->second.slave_config);
|
||||||
|
if (select_result != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to select DC reference clock: "
|
||||||
|
<< id_ << ", reference_motor_id=" << dc.reference_motor_id()
|
||||||
|
<< ", result=" << select_result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto assign_activate = static_cast<std::uint16_t>(dc.assign_activate());
|
||||||
|
const auto sync0_cycle_ns = usToNs_(dc.sync0_cycle_us());
|
||||||
|
const auto sync0_shift_ns = usToNs_(dc.sync0_shift_us());
|
||||||
|
for (auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
const int result = ecrt_slave_config_dc(slave.slave_config,
|
||||||
|
assign_activate,
|
||||||
|
sync0_cycle_ns,
|
||||||
|
sync0_shift_ns,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
if (result != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to configure DC: "
|
||||||
|
<< id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", position=" << slave.cfg.position()
|
||||||
|
<< ", result=" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CMVR_LOG(INFO) << "[EthercatMotorBusRuntime] configured DC: "
|
||||||
|
<< id_
|
||||||
|
<< ", reference_motor_id=" << dc.reference_motor_id()
|
||||||
|
<< ", sync0_cycle_ns=" << sync0_cycle_ns
|
||||||
|
<< ", sync0_shift_ns=" << sync0_shift_ns
|
||||||
|
<< ", sync_reference_clock_period=" << dc.sync_reference_clock_period()
|
||||||
|
<< ", assign_activate=" << hexIndex_(assign_activate)
|
||||||
|
<< ", sync_monitor_period_ms=" << dc.sync_monitor_period_ms();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::waitSlavesOperational_()
|
||||||
|
{
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(config_.slave_op_timeout_ms());
|
||||||
|
const auto poll_period =
|
||||||
|
std::chrono::milliseconds(config_.slave_state_poll_period_ms());
|
||||||
|
|
||||||
|
ec_domain_state_t last_domain_state{};
|
||||||
|
int last_domain_result = 0;
|
||||||
|
do {
|
||||||
|
bool all_slaves_operational = true;
|
||||||
|
for (const auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
(void)motor_id;
|
||||||
|
ec_slave_config_state_t state{};
|
||||||
|
const int result = ecrt_slave_config_state(slave.slave_config, &state);
|
||||||
|
if (result != 0 ||
|
||||||
|
!state.online ||
|
||||||
|
!state.operational ||
|
||||||
|
state.al_state != EC_AL_STATE_OP) {
|
||||||
|
all_slaves_operational = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_domain_result = ecrt_domain_state(domain_, &last_domain_state);
|
||||||
|
const bool domain_complete =
|
||||||
|
last_domain_result == 0 &&
|
||||||
|
last_domain_state.wc_state == EC_WC_COMPLETE;
|
||||||
|
|
||||||
|
if (all_slaves_operational && domain_complete) {
|
||||||
|
CMVR_LOG(INFO) << "[EthercatMotorBusRuntime] all EtherCAT slaves operational: "
|
||||||
|
<< id_
|
||||||
|
<< ", working_counter=" << last_domain_state.working_counter;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(poll_period);
|
||||||
|
} while (std::chrono::steady_clock::now() < deadline);
|
||||||
|
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] timeout waiting for EtherCAT slaves OP: "
|
||||||
|
<< id_
|
||||||
|
<< ", timeout_ms=" << config_.slave_op_timeout_ms()
|
||||||
|
<< ", domain_result=" << last_domain_result
|
||||||
|
<< ", domain_wc_state=" << static_cast<int>(last_domain_state.wc_state)
|
||||||
|
<< ", working_counter=" << last_domain_state.working_counter;
|
||||||
|
|
||||||
|
for (const auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
ec_slave_config_state_t state{};
|
||||||
|
const int result = ecrt_slave_config_state(slave.slave_config, &state);
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] slave state: "
|
||||||
|
<< id_
|
||||||
|
<< ", motor_id=" << motor_id
|
||||||
|
<< ", position=" << slave.cfg.position()
|
||||||
|
<< ", result=" << result
|
||||||
|
<< ", online=" << state.online
|
||||||
|
<< ", operational=" << state.operational
|
||||||
|
<< ", al_state=" << static_cast<int>(state.al_state);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::cyclicLoop_()
|
||||||
|
{
|
||||||
|
const auto period = std::chrono::microseconds(config_.cycle_us());
|
||||||
|
const bool dc_enabled = config_.dc().enable();
|
||||||
|
const auto dc_sync_period = dc_enabled ? config_.dc().sync_reference_clock_period() : 0U;
|
||||||
|
const auto dc_monitor_period =
|
||||||
|
dc_enabled ? std::chrono::milliseconds(config_.dc().sync_monitor_period_ms())
|
||||||
|
: std::chrono::milliseconds(0);
|
||||||
|
std::uint32_t dc_sync_counter = 0;
|
||||||
|
bool dc_monitor_queued = false;
|
||||||
|
auto cycle_time = std::chrono::steady_clock::now();
|
||||||
|
auto next_dc_monitor_time = cycle_time + dc_monitor_period;
|
||||||
|
while (running_.load()) {
|
||||||
|
if (dc_enabled) {
|
||||||
|
ecrt_master_application_time(master_, timePointNs_(cycle_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
ecrt_master_receive(master_);
|
||||||
|
ecrt_domain_process(domain_);
|
||||||
|
if (dc_enabled && dc_monitor_queued) {
|
||||||
|
const std::uint32_t dc_sync_diff_ns =
|
||||||
|
ecrt_master_sync_monitor_process(master_);
|
||||||
|
if (dc_sync_diff_ns == static_cast<std::uint32_t>(-1)) {
|
||||||
|
CMVR_LOG(WARNING) << "[EthercatMotorBusRuntime] DC sync monitor failed: "
|
||||||
|
<< id_;
|
||||||
|
} else {
|
||||||
|
CMVR_LOG(INFO) << "[EthercatMotorBusRuntime] dc_sync_diff_ns="
|
||||||
|
<< dc_sync_diff_ns
|
||||||
|
<< ", group=" << id_;
|
||||||
|
}
|
||||||
|
dc_monitor_queued = false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
readFeedbackLocked_();
|
||||||
|
writeCommandsLocked_();
|
||||||
|
}
|
||||||
|
ecrt_domain_queue(domain_);
|
||||||
|
if (dc_enabled) {
|
||||||
|
++dc_sync_counter;
|
||||||
|
if (dc_sync_counter >= dc_sync_period) {
|
||||||
|
dc_sync_counter = 0;
|
||||||
|
ecrt_master_sync_reference_clock(master_);
|
||||||
|
}
|
||||||
|
ecrt_master_sync_slave_clocks(master_);
|
||||||
|
|
||||||
|
if (cycle_time >= next_dc_monitor_time) {
|
||||||
|
const int monitor_result = ecrt_master_sync_monitor_queue(master_);
|
||||||
|
if (monitor_result == 0) {
|
||||||
|
dc_monitor_queued = true;
|
||||||
|
} else {
|
||||||
|
CMVR_LOG(WARNING) << "[EthercatMotorBusRuntime] failed to queue DC sync monitor: "
|
||||||
|
<< id_ << ", result=" << monitor_result;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
next_dc_monitor_time += dc_monitor_period;
|
||||||
|
} while (cycle_time >= next_dc_monitor_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ecrt_master_send(master_);
|
||||||
|
|
||||||
|
cycle_time += period;
|
||||||
|
std::this_thread::sleep_until(cycle_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::readFeedbackLocked_()
|
||||||
|
{
|
||||||
|
if (!domain_data_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
(void)motor_id;
|
||||||
|
for (auto& [key, entry] : slave.pdo_entries) {
|
||||||
|
(void)key;
|
||||||
|
if (!entry.rx) {
|
||||||
|
entry.value = readEntryValue_(domain_data_, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::writeCommandsLocked_()
|
||||||
|
{
|
||||||
|
if (!domain_data_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
(void)motor_id;
|
||||||
|
for (const auto& [key, entry] : slave.pdo_entries) {
|
||||||
|
(void)key;
|
||||||
|
if (entry.rx) {
|
||||||
|
writeEntryValue_(domain_data_, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::releaseMaster_()
|
||||||
|
{
|
||||||
|
if (master_) {
|
||||||
|
ecrt_release_master(master_);
|
||||||
|
}
|
||||||
|
master_ = nullptr;
|
||||||
|
domain_ = nullptr;
|
||||||
|
domain_data_ = nullptr;
|
||||||
|
for (auto& [motor_id, slave] : slaves_by_motor_id_) {
|
||||||
|
(void)motor_id;
|
||||||
|
slave.slave_config = nullptr;
|
||||||
|
slave.pdo_entries.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::hasValidPdoMapping_() const
|
||||||
|
{
|
||||||
|
return pdo_mapping_.vendor_id != 0 &&
|
||||||
|
pdo_mapping_.product_code != 0 &&
|
||||||
|
!pdo_mapping_.rx_pdos.empty() &&
|
||||||
|
!pdo_mapping_.tx_pdos.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::writePdoRaw_(const int motor_id,
|
||||||
|
const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex,
|
||||||
|
const std::uint8_t bit_len,
|
||||||
|
const std::uint64_t value)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
auto slave_it = slaves_by_motor_id_.find(motor_id);
|
||||||
|
if (slave_it == slaves_by_motor_id_.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto entry_it = slave_it->second.pdo_entries.find(pdoEntryKey_(index, subindex));
|
||||||
|
if (entry_it == slave_it->second.pdo_entries.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& entry = entry_it->second;
|
||||||
|
if (!entry.rx || entry.cfg.bit_len != bit_len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry.value = maskValue_(value, entry.cfg.bit_len);
|
||||||
|
if (domain_data_) {
|
||||||
|
writeEntryValue_(domain_data_, entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::readPdoRaw_(const int motor_id,
|
||||||
|
const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex,
|
||||||
|
const std::uint8_t bit_len,
|
||||||
|
std::uint64_t& value) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||||
|
const auto slave_it = slaves_by_motor_id_.find(motor_id);
|
||||||
|
if (slave_it == slaves_by_motor_id_.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto entry_it = slave_it->second.pdo_entries.find(pdoEntryKey_(index, subindex));
|
||||||
|
if (entry_it == slave_it->second.pdo_entries.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& entry = entry_it->second;
|
||||||
|
if (entry.rx || entry.cfg.bit_len != bit_len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = maskValue_(entry.value, entry.cfg.bit_len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::writeSdoRaw_(const int motor_id,
|
||||||
|
const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex,
|
||||||
|
const std::uint8_t bit_len,
|
||||||
|
const std::uint64_t value)
|
||||||
|
{
|
||||||
|
if (!isSupportedBitLength_(bit_len)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported SDO write bit length: "
|
||||||
|
<< static_cast<int>(bit_len)
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex)
|
||||||
|
<< ", motor_id=" << motor_id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slave_it = slaves_by_motor_id_.find(motor_id);
|
||||||
|
if (slave_it == slaves_by_motor_id_.end()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing motor for SDO write: "
|
||||||
|
<< motor_id << ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& slave = slave_it->second;
|
||||||
|
if (!slave.slave_config || !master_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized for SDO write: "
|
||||||
|
<< id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto raw_value = maskValue_(value, bit_len);
|
||||||
|
if (!started_) {
|
||||||
|
int result = -1;
|
||||||
|
switch (bit_len) {
|
||||||
|
case 8:
|
||||||
|
result = ecrt_slave_config_sdo8(slave.slave_config, index, subindex,
|
||||||
|
static_cast<std::uint8_t>(raw_value));
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
result = ecrt_slave_config_sdo16(slave.slave_config, index, subindex,
|
||||||
|
static_cast<std::uint16_t>(raw_value));
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
result = ecrt_slave_config_sdo32(slave.slave_config, index, subindex,
|
||||||
|
static_cast<std::uint32_t>(raw_value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to configure startup SDO: "
|
||||||
|
<< "group=" << id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex)
|
||||||
|
<< ", bit_len=" << static_cast<int>(bit_len)
|
||||||
|
<< ", value=" << raw_value
|
||||||
|
<< ", result=" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 4> data{};
|
||||||
|
switch (bit_len) {
|
||||||
|
case 8:
|
||||||
|
EC_WRITE_U8(data.data(), static_cast<std::uint8_t>(raw_value));
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
EC_WRITE_U16(data.data(), static_cast<std::uint16_t>(raw_value));
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
EC_WRITE_U32(data.data(), static_cast<std::uint32_t>(raw_value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto data_size = static_cast<std::size_t>(bit_len / 8);
|
||||||
|
std::uint32_t abort_code = 0;
|
||||||
|
const int result = ecrt_master_sdo_download(
|
||||||
|
master_,
|
||||||
|
static_cast<std::uint16_t>(slave.cfg.position()),
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
data.data(),
|
||||||
|
data_size,
|
||||||
|
&abort_code);
|
||||||
|
if (result != 0) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to write SDO: "
|
||||||
|
<< "group=" << id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", slave_position=" << slave.cfg.position()
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex)
|
||||||
|
<< ", bit_len=" << static_cast<int>(bit_len)
|
||||||
|
<< ", value=" << raw_value
|
||||||
|
<< ", result=" << result
|
||||||
|
<< ", abort_code=" << hexIndex_(abort_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::readSdoRaw_(const int motor_id,
|
||||||
|
const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex,
|
||||||
|
const std::uint8_t bit_len,
|
||||||
|
std::uint64_t& value)
|
||||||
|
{
|
||||||
|
if (!isSupportedBitLength_(bit_len)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] unsupported SDO read bit length: "
|
||||||
|
<< static_cast<int>(bit_len)
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex)
|
||||||
|
<< ", motor_id=" << motor_id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slave_it = slaves_by_motor_id_.find(motor_id);
|
||||||
|
if (slave_it == slaves_by_motor_id_.end()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] missing motor for SDO read: "
|
||||||
|
<< motor_id << ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& slave = slave_it->second;
|
||||||
|
if (!master_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] runtime is not initialized for SDO read: "
|
||||||
|
<< id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 4> data{};
|
||||||
|
const auto data_size = static_cast<std::size_t>(bit_len / 8);
|
||||||
|
std::size_t result_size = 0;
|
||||||
|
std::uint32_t abort_code = 0;
|
||||||
|
const int result = ecrt_master_sdo_upload(
|
||||||
|
master_,
|
||||||
|
static_cast<std::uint16_t>(slave.cfg.position()),
|
||||||
|
index,
|
||||||
|
subindex,
|
||||||
|
data.data(),
|
||||||
|
data_size,
|
||||||
|
&result_size,
|
||||||
|
&abort_code);
|
||||||
|
if (result != 0 || result_size != data_size) {
|
||||||
|
CMVR_LOG(ERROR) << "[EthercatMotorBusRuntime] failed to read SDO: "
|
||||||
|
<< "group=" << id_ << ", motor_id=" << motor_id
|
||||||
|
<< ", slave_position=" << slave.cfg.position()
|
||||||
|
<< ", object=" << hexIndex_(index)
|
||||||
|
<< ":" << static_cast<int>(subindex)
|
||||||
|
<< ", bit_len=" << static_cast<int>(bit_len)
|
||||||
|
<< ", result=" << result
|
||||||
|
<< ", result_size=" << result_size
|
||||||
|
<< ", abort_code=" << hexIndex_(abort_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bit_len) {
|
||||||
|
case 8:
|
||||||
|
value = EC_READ_U8(data.data());
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
value = EC_READ_U16(data.data());
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
value = EC_READ_U32(data.data());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = maskValue_(value, bit_len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t EthercatMotorBusRuntime::pdoEntryKey_(const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex)
|
||||||
|
{
|
||||||
|
return (static_cast<std::uint32_t>(index) << 8U) | subindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EthercatMotorBusRuntime::hexIndex_(const std::uint32_t index)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "0x" << std::hex << std::uppercase << index;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t EthercatMotorBusRuntime::maskValue_(const std::uint64_t value,
|
||||||
|
const std::uint8_t bit_len)
|
||||||
|
{
|
||||||
|
switch (bit_len) {
|
||||||
|
case 8:
|
||||||
|
return value & 0xFFU;
|
||||||
|
case 16:
|
||||||
|
return value & 0xFFFFU;
|
||||||
|
case 32:
|
||||||
|
return value & 0xFFFFFFFFULL;
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthercatMotorBusRuntime::isSupportedBitLength_(const std::uint8_t bit_len)
|
||||||
|
{
|
||||||
|
return bit_len == 8 || bit_len == 16 || bit_len == 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t EthercatMotorBusRuntime::readEntryValue_(const std::uint8_t* domain_data,
|
||||||
|
const PdoEntryRuntime& entry)
|
||||||
|
{
|
||||||
|
switch (entry.cfg.bit_len) {
|
||||||
|
case 8:
|
||||||
|
return EC_READ_U8(domain_data + entry.offset);
|
||||||
|
case 16:
|
||||||
|
return EC_READ_U16(domain_data + entry.offset);
|
||||||
|
case 32:
|
||||||
|
return EC_READ_U32(domain_data + entry.offset);
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthercatMotorBusRuntime::writeEntryValue_(std::uint8_t* domain_data,
|
||||||
|
const PdoEntryRuntime& entry)
|
||||||
|
{
|
||||||
|
switch (entry.cfg.bit_len) {
|
||||||
|
case 8:
|
||||||
|
EC_WRITE_U8(domain_data + entry.offset, static_cast<std::uint8_t>(entry.value));
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
EC_WRITE_U16(domain_data + entry.offset, static_cast<std::uint16_t>(entry.value));
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
EC_WRITE_U32(domain_data + entry.offset, static_cast<std::uint32_t>(entry.value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t EthercatMotorBusRuntime::steadyTimeNs_()
|
||||||
|
{
|
||||||
|
return timePointNs_(std::chrono::steady_clock::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t EthercatMotorBusRuntime::timePointNs_(
|
||||||
|
const std::chrono::steady_clock::time_point time_point)
|
||||||
|
{
|
||||||
|
const auto time_since_epoch = time_point.time_since_epoch();
|
||||||
|
return static_cast<std::uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>(time_since_epoch).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t EthercatMotorBusRuntime::usToNs_(const std::uint32_t value_us)
|
||||||
|
{
|
||||||
|
return value_us * 1000U;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t EthercatMotorBusRuntime::usToNs_(const std::int32_t value_us)
|
||||||
|
{
|
||||||
|
return value_us * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cmvr::device
|
} // namespace cmvr::device
|
||||||
|
|||||||
@ -0,0 +1,102 @@
|
|||||||
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
config::MotorGroupConfig createSingleSlaveGroup()
|
||||||
|
{
|
||||||
|
config::MotorGroupConfig group;
|
||||||
|
group.set_id("ethercat_real_test");
|
||||||
|
group.set_bus_type(config::MOTOR_BUS_ETHERCAT);
|
||||||
|
group.set_vendor(config::MOTOR_VENDOR_EYOU);
|
||||||
|
group.set_protocol(config::MOTOR_PROTOCOL_ETHERCAT_CIA402);
|
||||||
|
|
||||||
|
auto* ethercat = group.mutable_ethercat();
|
||||||
|
ethercat->set_master_index(0);
|
||||||
|
ethercat->set_cycle_us(1000);
|
||||||
|
ethercat->set_slave_op_timeout_ms(12000);
|
||||||
|
ethercat->set_slave_state_poll_period_ms(10);
|
||||||
|
|
||||||
|
auto* dc = ethercat->mutable_dc();
|
||||||
|
dc->set_enable(true);
|
||||||
|
dc->set_reference_motor_id(1);
|
||||||
|
dc->set_sync0_cycle_us(1000);
|
||||||
|
dc->set_sync0_shift_us(0);
|
||||||
|
dc->set_sync_reference_clock_period(1);
|
||||||
|
dc->set_assign_activate(768);
|
||||||
|
dc->set_sync_monitor_period_ms(1000);
|
||||||
|
|
||||||
|
auto* slave = ethercat->add_slaves();
|
||||||
|
slave->set_motor_id(1);
|
||||||
|
slave->set_alias(0);
|
||||||
|
slave->set_position(0);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(EthercatMotorBusRuntimeRealTest, InitStartAndReadStatusword)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
EthercatMotorBusRuntime runtime;
|
||||||
|
runtime.setPdoMapping(createEyouCia402PdoMapping());
|
||||||
|
|
||||||
|
ASSERT_TRUE(runtime.init(createSingleSlaveGroup()));
|
||||||
|
EXPECT_EQ(runtime.busType(), config::MOTOR_BUS_ETHERCAT);
|
||||||
|
EXPECT_TRUE(runtime.hasMotor(1));
|
||||||
|
EXPECT_NE(runtime.slaveForMotor(1), nullptr);
|
||||||
|
EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_CONTROL_WORD_6040, 0x00));
|
||||||
|
EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_STATUS_WORD_6041, 0x00));
|
||||||
|
EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_OPERATION_MODE_6060, 0x00));
|
||||||
|
EXPECT_TRUE(runtime.hasPdoEntry(1, msgs::CIA402_MODE_DISPLAY_6061, 0x00));
|
||||||
|
|
||||||
|
const bool started = runtime.start();
|
||||||
|
EXPECT_TRUE(started);
|
||||||
|
if (!started) {
|
||||||
|
runtime.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(runtime.writePdo<std::uint16_t>(1, msgs::CIA402_CONTROL_WORD_6040, 0x00, 0x0000));
|
||||||
|
EXPECT_TRUE(runtime.writePdo<std::int8_t>(1, msgs::CIA402_OPERATION_MODE_6060, 0x00, 0));
|
||||||
|
|
||||||
|
const int settle_ms = 1000;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(settle_ms));
|
||||||
|
|
||||||
|
std::uint16_t statusword = 0;
|
||||||
|
EXPECT_TRUE(runtime.readPdo<std::uint16_t>(1, msgs::CIA402_STATUS_WORD_6041, 0x00, statusword));
|
||||||
|
|
||||||
|
std::int8_t mode_display = 0;
|
||||||
|
EXPECT_TRUE(runtime.readPdo<std::int8_t>(1, msgs::CIA402_MODE_DISPLAY_6061, 0x00, mode_display));
|
||||||
|
|
||||||
|
std::cout << "CIA402 statusword: 0x" << std::hex << statusword
|
||||||
|
<< ", mode display: " << std::dec << static_cast<int>(mode_display)
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
const int hold_ms = 10000;
|
||||||
|
if (hold_ms > 0) {
|
||||||
|
std::cout << "Holding EtherCAT runtime for " << hold_ms
|
||||||
|
<< " ms. Check slave state in another terminal." << std::endl;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(hold_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
49
cmvr-es/devices/motor/drivers/ethercat_motor/CMakeLists.txt
Normal file
49
cmvr-es/devices/motor/drivers/ethercat_motor/CMakeLists.txt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
add_library(ethercat_motor_driver SHARED
|
||||||
|
src/cia402/cia402_protocol.cpp
|
||||||
|
src/vendor/eyou/eyou_motor.cpp
|
||||||
|
src/vendor/eyou/eyou_motor_adapter.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ethercat_motor_driver
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ethercat_motor_driver
|
||||||
|
PUBLIC
|
||||||
|
cmvr_es::device::motor_core
|
||||||
|
cmvr_es::device::motor_bus_runtime
|
||||||
|
PRIVATE
|
||||||
|
cmvr_es::proto
|
||||||
|
glog
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(cmvr_es::device::ethercat_motor_driver ALIAS ethercat_motor_driver)
|
||||||
|
install(TARGETS ethercat_motor_driver LIBRARY DESTINATION lib)
|
||||||
|
|
||||||
|
add_executable(eyou_motor_real_test
|
||||||
|
src/vendor/eyou/eyou_motor_real_test.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(eyou_motor_real_test
|
||||||
|
PRIVATE
|
||||||
|
cmvr_es::device::ethercat_motor_driver
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
pthread
|
||||||
|
glog
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(eyou_motor_device_manager_real_test
|
||||||
|
src/vendor/eyou/eyou_motor_device_manager_real_test.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(eyou_motor_device_manager_real_test
|
||||||
|
PRIVATE
|
||||||
|
cmvr_es::device_manager
|
||||||
|
cmvr_es::device::motor_manager
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
pthread
|
||||||
|
glog
|
||||||
|
)
|
||||||
@ -0,0 +1,161 @@
|
|||||||
|
#ifndef CMVR_ES_CIA402_OBJECTS_H
|
||||||
|
#define CMVR_ES_CIA402_OBJECTS_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cmvr::device::cia402 {
|
||||||
|
|
||||||
|
union Controlword {
|
||||||
|
std::uint16_t value;
|
||||||
|
struct {
|
||||||
|
std::uint16_t switch_on : 1;
|
||||||
|
std::uint16_t enable_voltage : 1;
|
||||||
|
std::uint16_t quick_stop : 1;
|
||||||
|
std::uint16_t enable_operation : 1;
|
||||||
|
std::uint16_t new_set_point : 1;
|
||||||
|
std::uint16_t change_set_immediately : 1;
|
||||||
|
std::uint16_t relative : 1;
|
||||||
|
std::uint16_t fault_reset : 1;
|
||||||
|
std::uint16_t halt : 1;
|
||||||
|
std::uint16_t reserved : 2;
|
||||||
|
std::uint16_t manufacturer_specific : 5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
union Statusword {
|
||||||
|
std::uint16_t value;
|
||||||
|
struct {
|
||||||
|
std::uint16_t ready_to_switch_on : 1;
|
||||||
|
std::uint16_t switched_on : 1;
|
||||||
|
std::uint16_t operation_enabled : 1;
|
||||||
|
std::uint16_t fault : 1;
|
||||||
|
std::uint16_t voltage_enabled : 1;
|
||||||
|
std::uint16_t quick_stop : 1;
|
||||||
|
std::uint16_t switch_on_disabled : 1;
|
||||||
|
std::uint16_t warning : 1;
|
||||||
|
std::uint16_t manufacturer_specific_8 : 1;
|
||||||
|
std::uint16_t remote : 1;
|
||||||
|
std::uint16_t target_reached : 1;
|
||||||
|
std::uint16_t internal_limit_active : 1;
|
||||||
|
std::uint16_t operation_mode_specific : 2;
|
||||||
|
std::uint16_t manufacturer_specific : 2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Controlword) == sizeof(std::uint16_t));
|
||||||
|
static_assert(sizeof(Statusword) == sizeof(std::uint16_t));
|
||||||
|
|
||||||
|
enum class DeviceState {
|
||||||
|
SwitchOnDisabled,
|
||||||
|
ReadyToSwitchOn,
|
||||||
|
SwitchedOn,
|
||||||
|
OperationEnabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct StateRule {
|
||||||
|
std::uint16_t relevant_bits;
|
||||||
|
std::uint16_t expected_bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline StateRule stateRule(const DeviceState state)
|
||||||
|
{
|
||||||
|
// CiA402 device states are matched by selected 0x6041 statusword bits.
|
||||||
|
switch (state) {
|
||||||
|
case DeviceState::SwitchOnDisabled:
|
||||||
|
return {0x004F, 0x0040};
|
||||||
|
case DeviceState::ReadyToSwitchOn:
|
||||||
|
return {0x006F, 0x0021};
|
||||||
|
case DeviceState::SwitchedOn:
|
||||||
|
return {0x006F, 0x0023};
|
||||||
|
case DeviceState::OperationEnabled:
|
||||||
|
return {0x006F, 0x0027};
|
||||||
|
}
|
||||||
|
return {0x006F, 0x0000};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
inline Controlword controlword(const std::uint16_t value)
|
||||||
|
{
|
||||||
|
Controlword cw{};
|
||||||
|
cw.value = value;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Statusword statusword(const std::uint16_t value)
|
||||||
|
{
|
||||||
|
Statusword sw{};
|
||||||
|
sw.value = value;
|
||||||
|
return sw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword shutdownControlword()
|
||||||
|
{
|
||||||
|
Controlword cw{};
|
||||||
|
cw.quick_stop = 1;
|
||||||
|
cw.enable_voltage = 1;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword switchOnControlword()
|
||||||
|
{
|
||||||
|
auto cw = shutdownControlword();
|
||||||
|
cw.switch_on = 1;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword enableOperationControlword()
|
||||||
|
{
|
||||||
|
auto cw = switchOnControlword();
|
||||||
|
cw.enable_operation = 1;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword quickStopControlword()
|
||||||
|
{
|
||||||
|
auto cw = enableOperationControlword();
|
||||||
|
cw.quick_stop = 0;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword faultResetControlword()
|
||||||
|
{
|
||||||
|
Controlword cw{};
|
||||||
|
cw.fault_reset = 1;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Controlword profilePositionControlword(const bool new_set_point)
|
||||||
|
{
|
||||||
|
auto cw = enableOperationControlword();
|
||||||
|
cw.change_set_immediately = 1;
|
||||||
|
cw.new_set_point = new_set_point ? 1 : 0;
|
||||||
|
return cw;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool hasState(const Statusword status, const DeviceState state)
|
||||||
|
{
|
||||||
|
const auto rule = detail::stateRule(state);
|
||||||
|
return (status.value & rule.relevant_bits) == rule.expected_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isSwitchOnDisabled(const Statusword status)
|
||||||
|
{
|
||||||
|
return hasState(status, DeviceState::SwitchOnDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isOperationEnabled(const Statusword status)
|
||||||
|
{
|
||||||
|
return hasState(status, DeviceState::OperationEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool targetReached(const Statusword status)
|
||||||
|
{
|
||||||
|
return status.target_reached != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device::cia402
|
||||||
|
|
||||||
|
#endif // CMVR_ES_CIA402_OBJECTS_H
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
#ifndef CMVR_ES_CIA402_PROTOCOL_H
|
||||||
|
#define CMVR_ES_CIA402_PROTOCOL_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "cmvr/config/motor_config/motor_config.pb.h"
|
||||||
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_objects.h"
|
||||||
|
#include "devices/motor/motor_protocol_interface.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
class Cia402Protocol final : public MotorProtocolInterface {
|
||||||
|
public:
|
||||||
|
explicit Cia402Protocol(std::shared_ptr<EthercatMotorBusRuntime> bus_runtime,
|
||||||
|
const config::Cia402ProtocolConfig& config);
|
||||||
|
~Cia402Protocol() override = default;
|
||||||
|
|
||||||
|
bool initNode(std::uint8_t node_id) override;
|
||||||
|
|
||||||
|
void setMode(std::uint8_t node_id, msgs::RunMode mode) override;
|
||||||
|
msgs::RunMode getMode(std::uint8_t node_id) override;
|
||||||
|
void setLimitQdd(std::uint8_t node_id, double u_qdd, double l_qdd) override;
|
||||||
|
void setLimitQd(std::uint8_t node_id, double qd) override;
|
||||||
|
void setLimitQ(std::uint8_t node_id, double ub, double lb) override;
|
||||||
|
bool calibrateZeroQ(std::uint8_t node_id) override;
|
||||||
|
bool reachedTargetQ(std::uint8_t node_id) override;
|
||||||
|
bool commandProfilePosition(std::uint8_t node_id,
|
||||||
|
double target_q,
|
||||||
|
double max_qd,
|
||||||
|
double max_qdd) override;
|
||||||
|
bool commandProfileVelocity(std::uint8_t node_id,
|
||||||
|
double target_qd,
|
||||||
|
double max_qdd) override;
|
||||||
|
bool commandCyclicPosition(std::uint8_t node_id,
|
||||||
|
double target_q,
|
||||||
|
double target_qd) override;
|
||||||
|
bool commandCyclicVelocity(std::uint8_t node_id,
|
||||||
|
double target_qd) override;
|
||||||
|
bool commandCyclicTorque(std::uint8_t node_id, double target_tau) override;
|
||||||
|
void setMotorConversion(std::uint8_t node_id,
|
||||||
|
double encoder_counts_per_rev,
|
||||||
|
double gear_ratio) override;
|
||||||
|
bool torqueOn(std::uint8_t node_id) override;
|
||||||
|
bool torqueOff(std::uint8_t node_id) override;
|
||||||
|
bool brakeRelease(std::uint8_t node_id) override;
|
||||||
|
bool quickStop(std::uint8_t node_id) override;
|
||||||
|
|
||||||
|
double getQ(std::uint8_t node_id) override;
|
||||||
|
double getQd(std::uint8_t node_id) override;
|
||||||
|
bool syncTargetToActualPosition(std::uint8_t node_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct NodeState {
|
||||||
|
msgs::RunMode mode{msgs::RUN_MODE_CYCLIC_SYNC_POSITION};
|
||||||
|
cia402::Controlword controlword{};
|
||||||
|
std::int32_t target_position{0};
|
||||||
|
std::int32_t target_velocity{0};
|
||||||
|
std::int16_t target_torque{0};
|
||||||
|
std::int32_t profile_velocity{0};
|
||||||
|
std::int32_t profile_acceleration{0};
|
||||||
|
std::int32_t profile_deceleration{0};
|
||||||
|
double limit_q_lb{0.0};
|
||||||
|
double limit_q_ub{0.0};
|
||||||
|
double limit_qd{0.0};
|
||||||
|
double limit_qdd{0.0};
|
||||||
|
double encoder_counts_per_rev{0.0};
|
||||||
|
double gear_ratio{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::int8_t toCia402Mode_(msgs::RunMode mode);
|
||||||
|
static msgs::RunMode fromCia402Mode_(std::int8_t mode);
|
||||||
|
static cia402::Controlword nextControlword_(cia402::Statusword statusword);
|
||||||
|
static bool isOperationEnabled_(cia402::Statusword statusword);
|
||||||
|
static bool targetReached_(cia402::Statusword statusword);
|
||||||
|
|
||||||
|
std::int32_t radToCounts_(double angle_rad, const NodeState& state) const;
|
||||||
|
double countsToRad_(std::int32_t counts, const NodeState& state) const;
|
||||||
|
std::int32_t radPerSecToCounts_(double velocity_rad_s, const NodeState& state) const;
|
||||||
|
std::int32_t radPerSec2ToCounts_(double acceleration_rad_s2, const NodeState& state) const;
|
||||||
|
double countsToRadPerSec_(std::int32_t velocity_counts_s, const NodeState& state) const;
|
||||||
|
NodeState& nodeState_(std::uint8_t node_id);
|
||||||
|
const NodeState* findNodeState_(std::uint8_t node_id) const;
|
||||||
|
bool hasValidConversion_(std::uint8_t node_id, const NodeState& state) const;
|
||||||
|
bool validateNodePdos_(std::uint8_t node_id) const;
|
||||||
|
bool readStatusword_(std::uint8_t node_id, std::uint16_t& statusword) const;
|
||||||
|
bool readActualPosition_(std::uint8_t node_id, std::int32_t& actual_position) const;
|
||||||
|
bool readActualVelocity_(std::uint8_t node_id, std::int32_t& actual_velocity) const;
|
||||||
|
bool readModeDisplay_(std::uint8_t node_id, std::int8_t& mode_display) const;
|
||||||
|
bool writeControlword_(std::uint8_t node_id, cia402::Controlword controlword);
|
||||||
|
bool writeControlwordAndWait_(std::uint8_t node_id,
|
||||||
|
cia402::Controlword controlword,
|
||||||
|
cia402::DeviceState target_state,
|
||||||
|
const char* state_name);
|
||||||
|
bool waitStatus_(std::uint8_t node_id,
|
||||||
|
cia402::DeviceState target_state,
|
||||||
|
const char* state_name) const;
|
||||||
|
bool waitVelocityNearZero_(std::uint8_t node_id, const char* action_name) const;
|
||||||
|
bool writePositionLimitsToDictionary_(std::uint8_t node_id, const NodeState& state) const;
|
||||||
|
bool writeVelocityLimitToDictionary_(std::uint8_t node_id, const NodeState& state) const;
|
||||||
|
bool writeAccelerationLimitsToDictionary_(std::uint8_t node_id, const NodeState& state) const;
|
||||||
|
bool prepareSafeTargetsForMode_(std::uint8_t node_id, msgs::RunMode mode, NodeState& state);
|
||||||
|
void writeTargetsForMode_(std::uint8_t node_id, msgs::RunMode mode, const NodeState& state) const;
|
||||||
|
void writeProfilePositionTarget_(std::uint8_t node_id, NodeState& state);
|
||||||
|
void writeNode_(std::uint8_t node_id, NodeState& state);
|
||||||
|
|
||||||
|
std::shared_ptr<EthercatMotorBusRuntime> bus_runtime_;
|
||||||
|
config::Cia402ProtocolConfig config_;
|
||||||
|
std::unordered_map<std::uint8_t, NodeState> nodes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_CIA402_PROTOCOL_H
|
||||||
81
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h
vendored
Normal file
81
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#ifndef CMVR_ES_EYOU_CIA402_PDO_MAPPING_H
|
||||||
|
#define CMVR_ES_EYOU_CIA402_PDO_MAPPING_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "cmvr/msgs/canopen.pb.h"
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_pdo_mapping.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
namespace eyou_cia402_pdo_mapping_detail {
|
||||||
|
|
||||||
|
inline constexpr std::uint32_t VENDOR_ID = 0x00001097;
|
||||||
|
inline constexpr std::uint32_t PRODUCT_CODE = 0x00002406;
|
||||||
|
|
||||||
|
inline EthercatPdoEntryConfig entry(const std::uint16_t index,
|
||||||
|
const std::uint8_t subindex,
|
||||||
|
const std::uint8_t bit_len,
|
||||||
|
std::string name)
|
||||||
|
{
|
||||||
|
EthercatPdoEntryConfig cfg;
|
||||||
|
cfg.index = index;
|
||||||
|
cfg.subindex = subindex;
|
||||||
|
cfg.bit_len = bit_len;
|
||||||
|
cfg.name = std::move(name);
|
||||||
|
cfg.padding = index == 0 || bit_len == 0;
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace eyou_cia402_pdo_mapping_detail
|
||||||
|
|
||||||
|
inline EthercatPdoMapping createEyouCia402PdoMapping()
|
||||||
|
{
|
||||||
|
using namespace eyou_cia402_pdo_mapping_detail;
|
||||||
|
|
||||||
|
EthercatPdoConfig rx_pdo;
|
||||||
|
rx_pdo.index = msgs::CANOPEN_RPDO2_MAP_1601;
|
||||||
|
rx_pdo.sync_manager = 2;
|
||||||
|
rx_pdo.rx = true;
|
||||||
|
rx_pdo.entries = {
|
||||||
|
entry(msgs::CIA402_CONTROL_WORD_6040, 0x00, 16, "Control Word"),
|
||||||
|
entry(msgs::CIA402_TARGET_POSITION_607A, 0x00, 32, "Target Position"),
|
||||||
|
entry(msgs::CIA402_TARGET_VELOCITY_60FF, 0x00, 32, "Target Velocity"),
|
||||||
|
entry(msgs::CIA402_TARGET_TORQUE_6071, 0x00, 16, "Target Torque"),
|
||||||
|
entry(msgs::CIA402_PROFILE_ACCELERATION_6083, 0x00, 32, "Profile Acceleration"),
|
||||||
|
entry(msgs::CIA402_PROFILE_DECELERATION_6084, 0x00, 32, "Profile Deceleration"),
|
||||||
|
entry(msgs::CIA402_PROFILE_VELOCITY_6081, 0x00, 32, "Profile Velocity"),
|
||||||
|
entry(msgs::CIA402_TORQUE_SLOPE_6087, 0x00, 32, "Torque Slope"),
|
||||||
|
entry(msgs::CIA402_OPERATION_MODE_6060, 0x00, 8, "Mode Of Operation"),
|
||||||
|
entry(0x0000, 0x00, 8, "Padding"),
|
||||||
|
};
|
||||||
|
|
||||||
|
EthercatPdoConfig tx_pdo;
|
||||||
|
tx_pdo.index = msgs::CANOPEN_TPDO1_MAP_1A00;
|
||||||
|
tx_pdo.sync_manager = 3;
|
||||||
|
tx_pdo.rx = false;
|
||||||
|
tx_pdo.entries = {
|
||||||
|
entry(msgs::CIA402_STATUS_WORD_6041, 0x00, 16, "Status Word"),
|
||||||
|
entry(msgs::CIA402_ACTUAL_POSITION_6064, 0x00, 32, "Actual Position"),
|
||||||
|
entry(msgs::CIA402_ACTUAL_VELOCITY_606C, 0x00, 32, "Actual Velocity"),
|
||||||
|
entry(msgs::CIA402_ACTUAL_TORQUE_6077, 0x00, 16, "Actual Torque"),
|
||||||
|
entry(msgs::CIA402_MODE_DISPLAY_6061, 0x00, 8, "Mode Of Operation Display"),
|
||||||
|
entry(msgs::CIA402_ERROR_CODE_603F, 0x00, 16, "Error Code"),
|
||||||
|
entry(0x0000, 0x00, 8, "Padding"),
|
||||||
|
};
|
||||||
|
|
||||||
|
EthercatPdoMapping mapping;
|
||||||
|
mapping.vendor_id = VENDOR_ID;
|
||||||
|
mapping.product_code = PRODUCT_CODE;
|
||||||
|
mapping.name = "EYOU ServoModule ECAT V145 CiA402";
|
||||||
|
mapping.rx_pdos.push_back(std::move(rx_pdo));
|
||||||
|
mapping.tx_pdos.push_back(std::move(tx_pdo));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_EYOU_CIA402_PDO_MAPPING_H
|
||||||
43
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h
vendored
Normal file
43
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef CMVR_ES_EYOU_MOTOR_H
|
||||||
|
#define CMVR_ES_EYOU_MOTOR_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "cmvr/config/motor_config/motor_config.pb.h"
|
||||||
|
#include "devices/motor/abstract_motor.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_protocol.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
class EyouMotor final : public AbstractMotor {
|
||||||
|
public:
|
||||||
|
EyouMotor(const config::MotorConfigItem& config,
|
||||||
|
std::shared_ptr<Cia402Protocol> cia402_protocol,
|
||||||
|
std::unique_ptr<EyouMotorAdapter> vendor_adapter);
|
||||||
|
|
||||||
|
std::string typeName() const override { return "EyouMotor"; }
|
||||||
|
bool init() override;
|
||||||
|
void setLimitQ(double ub, double lb) override;
|
||||||
|
void setLimitQd(double qd) override;
|
||||||
|
bool calibrateZeroQ() override;
|
||||||
|
bool brakeRelease() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool hasDependencies_() const;
|
||||||
|
bool hasValidConversion_() const;
|
||||||
|
bool writeVendorPositionLimits_() const;
|
||||||
|
bool writeVendorVelocityLimit_() const;
|
||||||
|
std::int32_t radToCounts_(double angle_rad) const;
|
||||||
|
std::uint32_t radPerSecToCounts_(double velocity_rad_s) const;
|
||||||
|
|
||||||
|
std::shared_ptr<Cia402Protocol> cia402_protocol_;
|
||||||
|
std::unique_ptr<EyouMotorAdapter> vendor_adapter_;
|
||||||
|
double encoder_counts_per_rev_{0.0};
|
||||||
|
double gear_ratio_{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_EYOU_MOTOR_H
|
||||||
33
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h
vendored
Normal file
33
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef CMVR_ES_EYOU_MOTOR_ADAPTER_H
|
||||||
|
#define CMVR_ES_EYOU_MOTOR_ADAPTER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/motor_vendor_adapter.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
class EyouMotorAdapter final : public MotorVendorAdapter {
|
||||||
|
public:
|
||||||
|
explicit EyouMotorAdapter(std::shared_ptr<EthercatMotorBusRuntime> bus_runtime);
|
||||||
|
~EyouMotorAdapter() override = default;
|
||||||
|
|
||||||
|
bool initNode(std::uint8_t node_id) override;
|
||||||
|
bool writePositionLimits(std::uint8_t node_id,
|
||||||
|
std::int32_t lower_limit,
|
||||||
|
std::int32_t upper_limit) override;
|
||||||
|
bool writeVelocityLimit(std::uint8_t node_id,
|
||||||
|
std::uint32_t velocity_limit) override;
|
||||||
|
bool calibrateZero(std::uint8_t node_id,
|
||||||
|
std::int32_t& zeroed_position) override;
|
||||||
|
bool brakeRelease(std::uint8_t node_id) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<EthercatMotorBusRuntime> bus_runtime_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_EYOU_MOTOR_ADAPTER_H
|
||||||
20
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_objects.h
vendored
Normal file
20
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_objects.h
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef CMVR_ES_EYOU_OBJECTS_H
|
||||||
|
#define CMVR_ES_EYOU_OBJECTS_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cmvr::device::eyou {
|
||||||
|
|
||||||
|
inline constexpr std::uint16_t EYOU_SOFT_LIMIT_STATE_2003 = 0x2003;
|
||||||
|
|
||||||
|
inline constexpr std::uint16_t EYOU_BRAKE_CONTROL_2014 = 0x2014;
|
||||||
|
|
||||||
|
inline constexpr std::uint16_t EYOU_OVER_SPEED_THRESHOLD_2024 = 0x2024;
|
||||||
|
inline constexpr std::uint16_t EYOU_FIRST_ENCODER_VALUE_202A = 0x202A;
|
||||||
|
inline constexpr std::uint16_t EYOU_SECOND_ENCODER_VALUE_202B = 0x202B;
|
||||||
|
|
||||||
|
inline constexpr std::uint16_t EYOU_STORE_PARAMETERS_1010 = 0x1010;
|
||||||
|
|
||||||
|
} // namespace cmvr::device::eyou
|
||||||
|
|
||||||
|
#endif // CMVR_ES_EYOU_OBJECTS_H
|
||||||
25
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/motor_vendor_adapter.h
vendored
Normal file
25
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/motor_vendor_adapter.h
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef CMVR_ES_MOTOR_VENDOR_ADAPTER_H
|
||||||
|
#define CMVR_ES_MOTOR_VENDOR_ADAPTER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
class MotorVendorAdapter {
|
||||||
|
public:
|
||||||
|
virtual ~MotorVendorAdapter() = default;
|
||||||
|
|
||||||
|
virtual bool initNode(std::uint8_t node_id) = 0;
|
||||||
|
virtual bool writePositionLimits(std::uint8_t node_id,
|
||||||
|
std::int32_t lower_limit,
|
||||||
|
std::int32_t upper_limit) = 0;
|
||||||
|
virtual bool writeVelocityLimit(std::uint8_t node_id,
|
||||||
|
std::uint32_t velocity_limit) = 0;
|
||||||
|
virtual bool calibrateZero(std::uint8_t node_id,
|
||||||
|
std::int32_t& zeroed_position) = 0;
|
||||||
|
virtual bool brakeRelease(std::uint8_t node_id) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
|
|
||||||
|
#endif // CMVR_ES_MOTOR_VENDOR_ADAPTER_H
|
||||||
@ -0,0 +1,872 @@
|
|||||||
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_protocol.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "common/base/logging/logger.h"
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_objects.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
Cia402Protocol::Cia402Protocol(std::shared_ptr<EthercatMotorBusRuntime> bus_runtime,
|
||||||
|
const config::Cia402ProtocolConfig& config)
|
||||||
|
: bus_runtime_(std::move(bus_runtime)),
|
||||||
|
config_(config)
|
||||||
|
{
|
||||||
|
comm_proto = CommProto::ETHERCAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::initNode(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_ || !bus_runtime_->hasMotor(node_id)) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] missing EtherCAT motor: "
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateNodePdos_(node_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (readActualPosition_(node_id, actual_position)) {
|
||||||
|
state.target_position = actual_position;
|
||||||
|
}
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::commandProfilePosition(const std::uint8_t node_id,
|
||||||
|
const double target_q,
|
||||||
|
const double max_qd,
|
||||||
|
const double max_qdd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.target_position = radToCounts_(target_q, state);
|
||||||
|
state.profile_velocity = std::abs(radPerSecToCounts_(max_qd, state));
|
||||||
|
if (max_qdd > 0.0) {
|
||||||
|
const auto profile_acceleration = std::abs(radPerSec2ToCounts_(max_qdd, state));
|
||||||
|
state.profile_acceleration = profile_acceleration;
|
||||||
|
state.profile_deceleration = profile_acceleration;
|
||||||
|
}
|
||||||
|
writeProfilePositionTarget_(node_id, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::commandProfileVelocity(const std::uint8_t node_id,
|
||||||
|
const double target_qd,
|
||||||
|
const double max_qdd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.target_velocity = radPerSecToCounts_(target_qd, state);
|
||||||
|
if (max_qdd > 0.0) {
|
||||||
|
const auto profile_acceleration = std::abs(radPerSec2ToCounts_(max_qdd, state));
|
||||||
|
state.profile_acceleration = profile_acceleration;
|
||||||
|
state.profile_deceleration = profile_acceleration;
|
||||||
|
}
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::commandCyclicPosition(const std::uint8_t node_id,
|
||||||
|
const double target_q,
|
||||||
|
const double target_qd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.target_position = radToCounts_(target_q, state);
|
||||||
|
state.target_velocity = radPerSecToCounts_(target_qd, state);
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::commandCyclicVelocity(const std::uint8_t node_id,
|
||||||
|
const double target_qd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.target_velocity = radPerSecToCounts_(target_qd, state);
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::commandCyclicTorque(const std::uint8_t node_id,
|
||||||
|
const double target_tau)
|
||||||
|
{
|
||||||
|
(void)target_tau;
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] cyclic torque command is not implemented, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::setMode(const std::uint8_t node_id, const msgs::RunMode mode)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
if (!prepareSafeTargetsForMode_(node_id, mode, state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeTargetsForMode_(node_id, mode, state);
|
||||||
|
state.mode = mode;
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs::RunMode Cia402Protocol::getMode(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
std::int8_t mode_display = 0;
|
||||||
|
if (readModeDisplay_(node_id, mode_display)) {
|
||||||
|
return fromCia402Mode_(mode_display);
|
||||||
|
}
|
||||||
|
return nodeState_(node_id).mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::setLimitQdd(const std::uint8_t node_id,
|
||||||
|
const double u_qdd,
|
||||||
|
const double l_qdd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.limit_qdd = std::max(std::abs(u_qdd), std::abs(l_qdd));
|
||||||
|
if (hasValidConversion_(node_id, state)) {
|
||||||
|
const auto profile_acceleration = std::abs(radPerSec2ToCounts_(state.limit_qdd, state));
|
||||||
|
state.profile_acceleration = profile_acceleration;
|
||||||
|
state.profile_deceleration = profile_acceleration;
|
||||||
|
writeAccelerationLimitsToDictionary_(node_id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::setLimitQd(const std::uint8_t node_id, const double qd)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.limit_qd = std::abs(qd);
|
||||||
|
if (hasValidConversion_(node_id, state)) {
|
||||||
|
state.profile_velocity = std::abs(radPerSecToCounts_(state.limit_qd, state));
|
||||||
|
writeVelocityLimitToDictionary_(node_id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::setLimitQ(const std::uint8_t node_id,
|
||||||
|
const double ub,
|
||||||
|
const double lb)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.limit_q_ub = ub;
|
||||||
|
state.limit_q_lb = lb;
|
||||||
|
if (hasValidConversion_(node_id, state)) {
|
||||||
|
writePositionLimitsToDictionary_(node_id, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::calibrateZeroQ(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] zero calibration is vendor-specific, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::reachedTargetQ(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
std::uint16_t statusword = 0;
|
||||||
|
if (!readStatusword_(node_id, statusword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return targetReached_(cia402::statusword(statusword));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::setMotorConversion(
|
||||||
|
const std::uint8_t node_id,
|
||||||
|
const double encoder_counts_per_rev,
|
||||||
|
const double gear_ratio)
|
||||||
|
{
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.encoder_counts_per_rev = encoder_counts_per_rev;
|
||||||
|
state.gear_ratio = gear_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::torqueOn(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
std::uint16_t statusword = 0;
|
||||||
|
if (readStatusword_(node_id, statusword) && cia402::statusword(statusword).fault != 0) {
|
||||||
|
if (!writeControlword_(node_id, cia402::faultResetControlword())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(config_.status_poll_period_ms()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prepareSafeTargetsForMode_(node_id, msgs::RUN_MODE_PROFILE_POSITION, state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.mode = msgs::RUN_MODE_PROFILE_POSITION;
|
||||||
|
state.target_velocity = 0;
|
||||||
|
state.target_torque = 0;
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
if (!bus_runtime_->writePdo<std::int8_t>(node_id, msgs::CIA402_OPERATION_MODE_6060, 0x00,
|
||||||
|
toCia402Mode_(state.mode))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!writeControlwordAndWait_(node_id, cia402::shutdownControlword(),
|
||||||
|
cia402::DeviceState::ReadyToSwitchOn,
|
||||||
|
"Ready To Switch On")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeControlwordAndWait_(node_id, cia402::switchOnControlword(),
|
||||||
|
cia402::DeviceState::SwitchedOn,
|
||||||
|
"Switched On")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeControlwordAndWait_(node_id, cia402::enableOperationControlword(),
|
||||||
|
cia402::DeviceState::OperationEnabled,
|
||||||
|
"Operation Enabled")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (readActualPosition_(node_id, actual_position)) {
|
||||||
|
state.target_position = actual_position;
|
||||||
|
}
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
|
||||||
|
state.controlword = cia402::profilePositionControlword(true);
|
||||||
|
if (!writeControlword_(node_id, state.controlword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(config_.profile_position_trigger_delay_ms()));
|
||||||
|
|
||||||
|
state.controlword = cia402::profilePositionControlword(false);
|
||||||
|
return writeControlword_(node_id, state.controlword);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::torqueOff(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (readActualPosition_(node_id, actual_position)) {
|
||||||
|
state.target_position = actual_position;
|
||||||
|
}
|
||||||
|
state.target_velocity = 0;
|
||||||
|
state.target_torque = 0;
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
waitVelocityNearZero_(node_id, "torqueOff");
|
||||||
|
|
||||||
|
if (!writeControlwordAndWait_(node_id, cia402::switchOnControlword(),
|
||||||
|
cia402::DeviceState::SwitchedOn,
|
||||||
|
"Switched On")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeControlwordAndWait_(node_id, cia402::shutdownControlword(),
|
||||||
|
cia402::DeviceState::ReadyToSwitchOn,
|
||||||
|
"Ready To Switch On")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::brakeRelease(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] brake release is vendor-specific, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::quickStop(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.target_velocity = 0;
|
||||||
|
state.target_torque = 0;
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
|
||||||
|
state.controlword = cia402::quickStopControlword();
|
||||||
|
if (!writeControlword_(node_id, state.controlword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return waitVelocityNearZero_(node_id, "quickStop");
|
||||||
|
}
|
||||||
|
|
||||||
|
double Cia402Protocol::getQ(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (!readActualPosition_(node_id, actual_position)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
const auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return countsToRad_(actual_position, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Cia402Protocol::getQd(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
std::int32_t actual_velocity = 0;
|
||||||
|
if (!readActualVelocity_(node_id, actual_velocity)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
const auto& state = nodeState_(node_id);
|
||||||
|
if (!hasValidConversion_(node_id, state)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return countsToRadPerSec_(actual_velocity, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::syncTargetToActualPosition(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (!readActualPosition_(node_id, actual_position)) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] failed to read actual position, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.target_position = actual_position;
|
||||||
|
state.target_velocity = 0;
|
||||||
|
state.target_torque = 0;
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int8_t Cia402Protocol::toCia402Mode_(const msgs::RunMode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case msgs::RUN_MODE_PROFILE_POSITION:
|
||||||
|
return 1;
|
||||||
|
case msgs::RUN_MODE_PROFILE_VELOCITY:
|
||||||
|
return 3;
|
||||||
|
case msgs::RUN_MODE_HOMING:
|
||||||
|
return 6;
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_POSITION:
|
||||||
|
return 8;
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY:
|
||||||
|
return 9;
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_CURRENT:
|
||||||
|
return 10;
|
||||||
|
default:
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs::RunMode Cia402Protocol::fromCia402Mode_(const std::int8_t mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case 1:
|
||||||
|
return msgs::RUN_MODE_PROFILE_POSITION;
|
||||||
|
case 3:
|
||||||
|
return msgs::RUN_MODE_PROFILE_VELOCITY;
|
||||||
|
case 6:
|
||||||
|
return msgs::RUN_MODE_HOMING;
|
||||||
|
case 8:
|
||||||
|
return msgs::RUN_MODE_CYCLIC_SYNC_POSITION;
|
||||||
|
case 9:
|
||||||
|
return msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY;
|
||||||
|
case 10:
|
||||||
|
return msgs::RUN_MODE_CYCLIC_SYNC_CURRENT;
|
||||||
|
default:
|
||||||
|
return msgs::RUN_MODE_UNSPECIFIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cia402::Controlword Cia402Protocol::nextControlword_(const cia402::Statusword statusword)
|
||||||
|
{
|
||||||
|
if (statusword.fault != 0) {
|
||||||
|
return cia402::faultResetControlword();
|
||||||
|
}
|
||||||
|
if (cia402::hasState(statusword, cia402::DeviceState::SwitchOnDisabled)) {
|
||||||
|
return cia402::shutdownControlword();
|
||||||
|
}
|
||||||
|
if (cia402::hasState(statusword, cia402::DeviceState::ReadyToSwitchOn)) {
|
||||||
|
return cia402::switchOnControlword();
|
||||||
|
}
|
||||||
|
if (cia402::hasState(statusword, cia402::DeviceState::SwitchedOn)) {
|
||||||
|
return cia402::enableOperationControlword();
|
||||||
|
}
|
||||||
|
if (cia402::hasState(statusword, cia402::DeviceState::OperationEnabled)) {
|
||||||
|
return cia402::enableOperationControlword();
|
||||||
|
}
|
||||||
|
return cia402::shutdownControlword();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::isOperationEnabled_(const cia402::Statusword statusword)
|
||||||
|
{
|
||||||
|
return cia402::isOperationEnabled(statusword);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::targetReached_(const cia402::Statusword statusword)
|
||||||
|
{
|
||||||
|
return cia402::targetReached(statusword);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t Cia402Protocol::radToCounts_(const double angle_rad,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
const double rev = angle_rad / (2.0 * M_PI);
|
||||||
|
return static_cast<std::int32_t>(
|
||||||
|
std::llround(rev * state.gear_ratio * state.encoder_counts_per_rev));
|
||||||
|
}
|
||||||
|
|
||||||
|
double Cia402Protocol::countsToRad_(const std::int32_t counts,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
return static_cast<double>(counts) /
|
||||||
|
(state.gear_ratio * state.encoder_counts_per_rev) * 2.0 * M_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t Cia402Protocol::radPerSecToCounts_(
|
||||||
|
const double velocity_rad_s,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
const double rev_per_sec = velocity_rad_s / (2.0 * M_PI);
|
||||||
|
return static_cast<std::int32_t>(
|
||||||
|
std::llround(rev_per_sec * state.gear_ratio * state.encoder_counts_per_rev));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t Cia402Protocol::radPerSec2ToCounts_(
|
||||||
|
const double acceleration_rad_s2,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
const double rev_per_sec2 = acceleration_rad_s2 / (2.0 * M_PI);
|
||||||
|
return static_cast<std::int32_t>(
|
||||||
|
std::llround(rev_per_sec2 * state.gear_ratio * state.encoder_counts_per_rev));
|
||||||
|
}
|
||||||
|
|
||||||
|
double Cia402Protocol::countsToRadPerSec_(
|
||||||
|
const std::int32_t velocity_counts_s,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
return static_cast<double>(velocity_counts_s) /
|
||||||
|
(state.gear_ratio * state.encoder_counts_per_rev) * 2.0 * M_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cia402Protocol::NodeState& Cia402Protocol::nodeState_(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
return nodes_[node_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cia402Protocol::NodeState* Cia402Protocol::findNodeState_(
|
||||||
|
const std::uint8_t node_id) const
|
||||||
|
{
|
||||||
|
const auto it = nodes_.find(node_id);
|
||||||
|
if (it == nodes_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::hasValidConversion_(const std::uint8_t node_id,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
if (state.encoder_counts_per_rev > 0.0 && state.gear_ratio > 0.0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] missing conversion config for node "
|
||||||
|
<< static_cast<int>(node_id)
|
||||||
|
<< ": encoder_counts_per_rev=" << state.encoder_counts_per_rev
|
||||||
|
<< ", gear_ratio=" << state.gear_ratio;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::validateNodePdos_(const std::uint8_t node_id) const
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct RequiredEntry {
|
||||||
|
std::uint16_t index;
|
||||||
|
std::uint8_t subindex;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
const RequiredEntry required[] = {
|
||||||
|
{msgs::CIA402_CONTROL_WORD_6040, 0x00, "controlword"},
|
||||||
|
{msgs::CIA402_TARGET_POSITION_607A, 0x00, "target position"},
|
||||||
|
{msgs::CIA402_TARGET_VELOCITY_60FF, 0x00, "target velocity"},
|
||||||
|
{msgs::CIA402_TARGET_TORQUE_6071, 0x00, "target torque"},
|
||||||
|
{msgs::CIA402_PROFILE_ACCELERATION_6083, 0x00, "profile acceleration"},
|
||||||
|
{msgs::CIA402_PROFILE_DECELERATION_6084, 0x00, "profile deceleration"},
|
||||||
|
{msgs::CIA402_PROFILE_VELOCITY_6081, 0x00, "profile velocity"},
|
||||||
|
{msgs::CIA402_OPERATION_MODE_6060, 0x00, "operation mode"},
|
||||||
|
{msgs::CIA402_STATUS_WORD_6041, 0x00, "statusword"},
|
||||||
|
{msgs::CIA402_ACTUAL_POSITION_6064, 0x00, "actual position"},
|
||||||
|
{msgs::CIA402_ACTUAL_VELOCITY_606C, 0x00, "actual velocity"},
|
||||||
|
{msgs::CIA402_ACTUAL_TORQUE_6077, 0x00, "actual torque"},
|
||||||
|
{msgs::CIA402_MODE_DISPLAY_6061, 0x00, "mode display"},
|
||||||
|
{msgs::CIA402_ERROR_CODE_603F, 0x00, "error code"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& entry : required) {
|
||||||
|
if (!bus_runtime_->hasPdoEntry(node_id, entry.index, entry.subindex)) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] missing PDO entry for node "
|
||||||
|
<< static_cast<int>(node_id)
|
||||||
|
<< ": " << entry.name
|
||||||
|
<< " 0x" << std::hex << entry.index
|
||||||
|
<< ":" << static_cast<int>(entry.subindex) << std::dec;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::readStatusword_(const std::uint8_t node_id,
|
||||||
|
std::uint16_t& statusword) const
|
||||||
|
{
|
||||||
|
return bus_runtime_ &&
|
||||||
|
bus_runtime_->readPdo<std::uint16_t>(node_id, msgs::CIA402_STATUS_WORD_6041, 0x00,
|
||||||
|
statusword);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::readActualPosition_(const std::uint8_t node_id,
|
||||||
|
std::int32_t& actual_position) const
|
||||||
|
{
|
||||||
|
return bus_runtime_ &&
|
||||||
|
bus_runtime_->readPdo<std::int32_t>(node_id, msgs::CIA402_ACTUAL_POSITION_6064, 0x00,
|
||||||
|
actual_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::readActualVelocity_(const std::uint8_t node_id,
|
||||||
|
std::int32_t& actual_velocity) const
|
||||||
|
{
|
||||||
|
return bus_runtime_ &&
|
||||||
|
bus_runtime_->readPdo<std::int32_t>(node_id, msgs::CIA402_ACTUAL_VELOCITY_606C, 0x00,
|
||||||
|
actual_velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::readModeDisplay_(const std::uint8_t node_id,
|
||||||
|
std::int8_t& mode_display) const
|
||||||
|
{
|
||||||
|
return bus_runtime_ &&
|
||||||
|
bus_runtime_->readPdo<std::int8_t>(node_id, msgs::CIA402_MODE_DISPLAY_6061, 0x00,
|
||||||
|
mode_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::writeControlword_(const std::uint8_t node_id,
|
||||||
|
const cia402::Controlword controlword)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& state = nodeState_(node_id);
|
||||||
|
state.controlword = controlword;
|
||||||
|
return bus_runtime_->writePdo<std::uint16_t>(node_id, msgs::CIA402_CONTROL_WORD_6040,
|
||||||
|
0x00, controlword.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::writeControlwordAndWait_(
|
||||||
|
const std::uint8_t node_id,
|
||||||
|
const cia402::Controlword controlword,
|
||||||
|
const cia402::DeviceState target_state,
|
||||||
|
const char* state_name)
|
||||||
|
{
|
||||||
|
if (!writeControlword_(node_id, controlword)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return waitStatus_(node_id, target_state, state_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::waitStatus_(const std::uint8_t node_id,
|
||||||
|
const cia402::DeviceState target_state,
|
||||||
|
const char* state_name) const
|
||||||
|
{
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(config_.state_transition_timeout_ms());
|
||||||
|
std::uint16_t last_statusword = 0;
|
||||||
|
do {
|
||||||
|
if (readStatusword_(node_id, last_statusword) &&
|
||||||
|
cia402::hasState(cia402::statusword(last_statusword), target_state)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(config_.status_poll_period_ms()));
|
||||||
|
} while (std::chrono::steady_clock::now() < deadline);
|
||||||
|
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] timeout waiting for "
|
||||||
|
<< state_name << ", node=" << static_cast<int>(node_id)
|
||||||
|
<< ", last_statusword=0x" << std::hex << last_statusword << std::dec;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::waitVelocityNearZero_(const std::uint8_t node_id,
|
||||||
|
const char* action_name) const
|
||||||
|
{
|
||||||
|
const auto* state = findNodeState_(node_id);
|
||||||
|
if (state == nullptr || !hasValidConversion_(node_id, *state)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tolerance_counts = std::max<std::int32_t>(
|
||||||
|
1, std::abs(radPerSecToCounts_(config_.stopped_velocity_tolerance_rad_s(), *state)));
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(config_.velocity_stop_timeout_ms());
|
||||||
|
std::int32_t last_velocity = 0;
|
||||||
|
do {
|
||||||
|
if (readActualVelocity_(node_id, last_velocity) &&
|
||||||
|
std::abs(last_velocity) <= tolerance_counts) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(config_.status_poll_period_ms()));
|
||||||
|
} while (std::chrono::steady_clock::now() < deadline);
|
||||||
|
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] timeout waiting velocity near zero "
|
||||||
|
<< "during " << action_name
|
||||||
|
<< ", node=" << static_cast<int>(node_id)
|
||||||
|
<< ", last_velocity=" << last_velocity
|
||||||
|
<< ", tolerance=" << tolerance_counts;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::writePositionLimitsToDictionary_(
|
||||||
|
const std::uint8_t node_id,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(state.limit_q_lb) || !std::isfinite(state.limit_q_ub) ||
|
||||||
|
state.limit_q_ub <= state.limit_q_lb) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto lower_limit = radToCounts_(state.limit_q_lb, state);
|
||||||
|
const auto upper_limit = radToCounts_(state.limit_q_ub, state);
|
||||||
|
|
||||||
|
const bool ok =
|
||||||
|
bus_runtime_->writeSdo<std::int32_t>(node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x01, lower_limit) &&
|
||||||
|
bus_runtime_->writeSdo<std::int32_t>(node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x02, upper_limit);
|
||||||
|
if (!ok) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] failed to write software position "
|
||||||
|
<< "limits to dictionary, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", lower=" << lower_limit
|
||||||
|
<< ", upper=" << upper_limit;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::writeVelocityLimitToDictionary_(
|
||||||
|
const std::uint8_t node_id,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(state.limit_qd) || state.limit_qd <= 0.0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto velocity_limit =
|
||||||
|
static_cast<std::uint32_t>(std::abs(radPerSecToCounts_(state.limit_qd, state)));
|
||||||
|
const bool ok =
|
||||||
|
bus_runtime_->writeSdo<std::uint32_t>(node_id, msgs::CIA402_MAX_PROFILE_VELOCITY_607F,
|
||||||
|
0x00, velocity_limit);
|
||||||
|
if (!ok) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] failed to write velocity limit "
|
||||||
|
<< "to dictionary, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", velocity_limit=" << velocity_limit;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::writeAccelerationLimitsToDictionary_(
|
||||||
|
const std::uint8_t node_id,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!std::isfinite(state.limit_qdd) || state.limit_qdd <= 0.0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto acceleration_limit =
|
||||||
|
static_cast<std::uint32_t>(std::abs(radPerSec2ToCounts_(state.limit_qdd, state)));
|
||||||
|
const bool ok =
|
||||||
|
bus_runtime_->writeSdo<std::uint32_t>(node_id, msgs::CIA402_PROFILE_ACCELERATION_6083,
|
||||||
|
0x00, acceleration_limit) &&
|
||||||
|
bus_runtime_->writeSdo<std::uint32_t>(node_id, msgs::CIA402_PROFILE_DECELERATION_6084,
|
||||||
|
0x00, acceleration_limit);
|
||||||
|
if (!ok) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] failed to write acceleration limits "
|
||||||
|
<< "to dictionary, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", acceleration_limit=" << acceleration_limit;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::writeProfilePositionTarget_(const std::uint8_t node_id,
|
||||||
|
NodeState& state)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.profile_velocity <= 0 && state.limit_qd > 0.0) {
|
||||||
|
state.profile_velocity = std::abs(radPerSecToCounts_(state.limit_qd, state));
|
||||||
|
}
|
||||||
|
if (state.profile_acceleration <= 0 && state.limit_qdd > 0.0) {
|
||||||
|
const auto profile_acceleration = std::abs(radPerSec2ToCounts_(state.limit_qdd, state));
|
||||||
|
state.profile_acceleration = profile_acceleration;
|
||||||
|
state.profile_deceleration = profile_acceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.controlword = cia402::enableOperationControlword();
|
||||||
|
writeNode_(node_id, state);
|
||||||
|
|
||||||
|
state.controlword = cia402::profilePositionControlword(true);
|
||||||
|
writeControlword_(node_id, state.controlword);
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(config_.profile_position_trigger_delay_ms()));
|
||||||
|
|
||||||
|
state.controlword = cia402::profilePositionControlword(false);
|
||||||
|
writeControlword_(node_id, state.controlword);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Cia402Protocol::prepareSafeTargetsForMode_(const std::uint8_t node_id,
|
||||||
|
const msgs::RunMode mode,
|
||||||
|
NodeState& state)
|
||||||
|
{
|
||||||
|
state.target_velocity = 0;
|
||||||
|
state.target_torque = 0;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case msgs::RUN_MODE_PROFILE_POSITION:
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_POSITION: {
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (!readActualPosition_(node_id, actual_position)) {
|
||||||
|
CMVR_LOG(ERROR) << "[Cia402Protocol] failed to read actual "
|
||||||
|
<< "position before switching mode, node="
|
||||||
|
<< static_cast<int>(node_id)
|
||||||
|
<< ", mode=" << static_cast<int>(mode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.target_position = actual_position;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case msgs::RUN_MODE_PROFILE_VELOCITY:
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY:
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_CURRENT:
|
||||||
|
case msgs::RUN_MODE_HOMING:
|
||||||
|
case msgs::RUN_MODE_UNSPECIFIED:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::writeTargetsForMode_(const std::uint8_t node_id,
|
||||||
|
const msgs::RunMode mode,
|
||||||
|
const NodeState& state) const
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case msgs::RUN_MODE_PROFILE_POSITION:
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_TARGET_POSITION_607A,
|
||||||
|
0x00, state.target_position);
|
||||||
|
if (bus_runtime_->hasPdoEntry(node_id, msgs::CIA402_PROFILE_VELOCITY_6081, 0x00)) {
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_PROFILE_VELOCITY_6081,
|
||||||
|
0x00, state.profile_velocity);
|
||||||
|
}
|
||||||
|
if (bus_runtime_->hasPdoEntry(node_id, msgs::CIA402_PROFILE_ACCELERATION_6083, 0x00)) {
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_PROFILE_ACCELERATION_6083,
|
||||||
|
0x00, state.profile_acceleration);
|
||||||
|
}
|
||||||
|
if (bus_runtime_->hasPdoEntry(node_id, msgs::CIA402_PROFILE_DECELERATION_6084, 0x00)) {
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_PROFILE_DECELERATION_6084,
|
||||||
|
0x00, state.profile_deceleration);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case msgs::RUN_MODE_PROFILE_VELOCITY:
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_TARGET_VELOCITY_60FF,
|
||||||
|
0x00, state.target_velocity);
|
||||||
|
if (bus_runtime_->hasPdoEntry(node_id, msgs::CIA402_PROFILE_ACCELERATION_6083, 0x00)) {
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_PROFILE_ACCELERATION_6083,
|
||||||
|
0x00, state.profile_acceleration);
|
||||||
|
}
|
||||||
|
if (bus_runtime_->hasPdoEntry(node_id, msgs::CIA402_PROFILE_DECELERATION_6084, 0x00)) {
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_PROFILE_DECELERATION_6084,
|
||||||
|
0x00, state.profile_deceleration);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_POSITION:
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_TARGET_POSITION_607A,
|
||||||
|
0x00, state.target_position);
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_TARGET_VELOCITY_60FF,
|
||||||
|
0x00, state.target_velocity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY:
|
||||||
|
bus_runtime_->writePdo<std::int32_t>(node_id, msgs::CIA402_TARGET_VELOCITY_60FF,
|
||||||
|
0x00, state.target_velocity);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case msgs::RUN_MODE_CYCLIC_SYNC_CURRENT:
|
||||||
|
bus_runtime_->writePdo<std::int16_t>(node_id, msgs::CIA402_TARGET_TORQUE_6071,
|
||||||
|
0x00, state.target_torque);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cia402Protocol::writeNode_(const std::uint8_t node_id, NodeState& state)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint16_t statusword = 0;
|
||||||
|
if (readStatusword_(node_id, statusword)) {
|
||||||
|
const auto status = cia402::statusword(statusword);
|
||||||
|
state.controlword = nextControlword_(status);
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (!isOperationEnabled_(status) &&
|
||||||
|
readActualPosition_(node_id, actual_position) &&
|
||||||
|
actual_position != 0) {
|
||||||
|
state.target_position = actual_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus_runtime_->writePdo<std::uint16_t>(node_id, msgs::CIA402_CONTROL_WORD_6040, 0x00,
|
||||||
|
state.controlword.value);
|
||||||
|
bus_runtime_->writePdo<std::int8_t>(node_id, msgs::CIA402_OPERATION_MODE_6060, 0x00,
|
||||||
|
toCia402Mode_(state.mode));
|
||||||
|
|
||||||
|
writeTargetsForMode_(node_id, state.mode, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
174
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor.cpp
vendored
Normal file
174
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor.cpp
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "common/base/logging/logger.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
EyouMotor::EyouMotor(const config::MotorConfigItem& config,
|
||||||
|
std::shared_ptr<Cia402Protocol> cia402_protocol,
|
||||||
|
std::unique_ptr<EyouMotorAdapter> vendor_adapter)
|
||||||
|
: cia402_protocol_(std::move(cia402_protocol)),
|
||||||
|
vendor_adapter_(std::move(vendor_adapter))
|
||||||
|
{
|
||||||
|
info_.id = config.id();
|
||||||
|
info_.joint_name = config.joint_name();
|
||||||
|
info_.limit_q_lb = config.limit_q_lb();
|
||||||
|
info_.limit_q_ub = config.limit_q_ub();
|
||||||
|
info_.limit_qd = config.limit_qd();
|
||||||
|
info_.limit_qdd = config.limit_qdd();
|
||||||
|
encoder_counts_per_rev_ = config.encoder_counts_per_rev();
|
||||||
|
gear_ratio_ = config.gear_ratio();
|
||||||
|
node_id_ = static_cast<std::uint8_t>(info_.id);
|
||||||
|
id_ = info_.joint_name;
|
||||||
|
protocol_ = cia402_protocol_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::init()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!hasDependencies_() || !hasValidConversion_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cia402_protocol_->initNode(node_id_)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] failed to init CiA402 node: " << info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!vendor_adapter_->initNode(node_id_)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] failed to init vendor adapter: " << info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cia402_protocol_->setMotorConversion(node_id_, encoder_counts_per_rev_, gear_ratio_);
|
||||||
|
|
||||||
|
cia402_protocol_->setLimitQd(node_id_, info_.limit_qd);
|
||||||
|
if (!writeVendorVelocityLimit_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (info_.limit_qdd > 0.0) {
|
||||||
|
cia402_protocol_->setLimitQdd(node_id_, info_.limit_qdd, -info_.limit_qdd);
|
||||||
|
}
|
||||||
|
if (!writeVendorPositionLimits_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyouMotor::setLimitQ(const double ub, const double lb)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
info_.limit_q_ub = ub;
|
||||||
|
info_.limit_q_lb = lb;
|
||||||
|
if (!hasDependencies_() || !hasValidConversion_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writeVendorPositionLimits_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EyouMotor::setLimitQd(const double qd)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
info_.limit_qd = qd;
|
||||||
|
if (!hasDependencies_() || !hasValidConversion_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cia402_protocol_->setLimitQd(node_id_, info_.limit_qd);
|
||||||
|
writeVendorVelocityLimit_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::calibrateZeroQ()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!hasDependencies_() || !hasValidConversion_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t zeroed_position = 0;
|
||||||
|
if (!vendor_adapter_->calibrateZero(node_id_, zeroed_position)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] zero calibration failed: " << info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!cia402_protocol_->syncTargetToActualPosition(node_id_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!writeVendorPositionLimits_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::brakeRelease()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
if (!hasDependencies_()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return vendor_adapter_->brakeRelease(node_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::hasDependencies_() const
|
||||||
|
{
|
||||||
|
if (!cia402_protocol_ || !vendor_adapter_) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] missing protocol or vendor adapter: "
|
||||||
|
<< info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cia402_protocol_->comm_proto != MotorProtocolInterface::CommProto::ETHERCAT) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] invalid protocol for motor: " << info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::hasValidConversion_() const
|
||||||
|
{
|
||||||
|
if (encoder_counts_per_rev_ > 0.0 && gear_ratio_ > 0.0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotor] missing encoder conversion config: "
|
||||||
|
<< info_.joint_name
|
||||||
|
<< ", encoder_counts_per_rev=" << encoder_counts_per_rev_
|
||||||
|
<< ", gear_ratio=" << gear_ratio_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::writeVendorPositionLimits_() const
|
||||||
|
{
|
||||||
|
if (!std::isfinite(info_.limit_q_lb) || !std::isfinite(info_.limit_q_ub) ||
|
||||||
|
info_.limit_q_ub <= info_.limit_q_lb) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
cia402_protocol_->setLimitQ(node_id_, info_.limit_q_ub, info_.limit_q_lb);
|
||||||
|
return vendor_adapter_->writePositionLimits(node_id_,
|
||||||
|
radToCounts_(info_.limit_q_lb),
|
||||||
|
radToCounts_(info_.limit_q_ub));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotor::writeVendorVelocityLimit_() const
|
||||||
|
{
|
||||||
|
if (!std::isfinite(info_.limit_qd) || info_.limit_qd <= 0.0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vendor_adapter_->writeVelocityLimit(node_id_, radPerSecToCounts_(info_.limit_qd));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t EyouMotor::radToCounts_(const double angle_rad) const
|
||||||
|
{
|
||||||
|
const double rev = angle_rad / (2.0 * M_PI);
|
||||||
|
return static_cast<std::int32_t>(
|
||||||
|
std::llround(rev * gear_ratio_ * encoder_counts_per_rev_));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t EyouMotor::radPerSecToCounts_(const double velocity_rad_s) const
|
||||||
|
{
|
||||||
|
const double rev_per_sec = std::abs(velocity_rad_s) / (2.0 * M_PI);
|
||||||
|
return static_cast<std::uint32_t>(
|
||||||
|
std::llround(rev_per_sec * gear_ratio_ * encoder_counts_per_rev_));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
221
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_adapter.cpp
vendored
Normal file
221
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_adapter.cpp
vendored
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
|
#include "common/base/logging/logger.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_objects.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
|
||||||
|
EyouMotorAdapter::EyouMotorAdapter(
|
||||||
|
std::shared_ptr<EthercatMotorBusRuntime> bus_runtime)
|
||||||
|
: bus_runtime_(std::move(bus_runtime))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotorAdapter::initNode(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
return bus_runtime_ && bus_runtime_->hasMotor(node_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotorAdapter::writePositionLimits(const std::uint8_t node_id,
|
||||||
|
const std::int32_t lower_limit,
|
||||||
|
const std::int32_t upper_limit)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto write_limits = [&]() {
|
||||||
|
return bus_runtime_->writeSdo<std::uint32_t>(node_id, eyou::EYOU_SOFT_LIMIT_STATE_2003,
|
||||||
|
0x00, 0) &&
|
||||||
|
bus_runtime_->writeSdo<std::int32_t>(
|
||||||
|
node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x02, upper_limit) &&
|
||||||
|
bus_runtime_->writeSdo<std::int32_t>(
|
||||||
|
node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x01, lower_limit) &&
|
||||||
|
bus_runtime_->writeSdo<std::uint32_t>(
|
||||||
|
node_id, eyou::EYOU_SOFT_LIMIT_STATE_2003,
|
||||||
|
0x00, 0x4C494D54);
|
||||||
|
};
|
||||||
|
const auto readback_matches = [&]() {
|
||||||
|
std::int32_t actual_lower = 0;
|
||||||
|
std::int32_t actual_upper = 0;
|
||||||
|
return bus_runtime_->readSdo<std::int32_t>(
|
||||||
|
node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x01, actual_lower) &&
|
||||||
|
bus_runtime_->readSdo<std::int32_t>(
|
||||||
|
node_id, msgs::CIA402_SOFTWARE_POSITION_LIMIT_607D,
|
||||||
|
0x02, actual_upper) &&
|
||||||
|
actual_lower == lower_limit &&
|
||||||
|
actual_upper == upper_limit;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bool ok = write_limits() && readback_matches();
|
||||||
|
if (!ok) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to write software position "
|
||||||
|
<< "limits, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", soft_limit_state=" << 0x4C494D54
|
||||||
|
<< ", lower=" << lower_limit
|
||||||
|
<< ", upper=" << upper_limit;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotorAdapter::writeVelocityLimit(const std::uint8_t node_id,
|
||||||
|
const std::uint32_t velocity_limit)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t actual_velocity_limit = 0;
|
||||||
|
const bool ok =
|
||||||
|
bus_runtime_->writeSdo<std::uint32_t>(node_id, eyou::EYOU_OVER_SPEED_THRESHOLD_2024,
|
||||||
|
0x00, velocity_limit) &&
|
||||||
|
bus_runtime_->readSdo<std::uint32_t>(node_id, eyou::EYOU_OVER_SPEED_THRESHOLD_2024,
|
||||||
|
0x00, actual_velocity_limit) &&
|
||||||
|
actual_velocity_limit == velocity_limit;
|
||||||
|
if (!ok) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to write over speed "
|
||||||
|
<< "threshold, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", expected=" << velocity_limit
|
||||||
|
<< ", actual=" << actual_velocity_limit;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotorAdapter::calibrateZero(const std::uint8_t node_id,
|
||||||
|
std::int32_t& zeroed_position)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus_runtime_->writeSdo<std::uint32_t>(node_id, eyou::EYOU_SOFT_LIMIT_STATE_2003,
|
||||||
|
0x00, 0)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to disable software position "
|
||||||
|
<< "limit before home offset calibration, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus_runtime_->writeSdo<std::int32_t>(node_id, msgs::CIA402_HOME_OFFSET_607C,
|
||||||
|
0x00, 0)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to clear home offset, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
||||||
|
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
if (!bus_runtime_->readSdo<std::int32_t>(node_id, msgs::CIA402_ACTUAL_POSITION_6064,
|
||||||
|
0x00, actual_position)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to read actual position "
|
||||||
|
<< "after clearing home offset, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual_position == std::numeric_limits<std::int32_t>::min()) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] invalid actual position for home "
|
||||||
|
<< "offset calibration, node=" << static_cast<int>(node_id)
|
||||||
|
<< ", actual_position=" << actual_position;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto home_offset = static_cast<std::int32_t>(-actual_position);
|
||||||
|
if (!bus_runtime_->writeSdo<std::int32_t>(node_id, msgs::CIA402_HOME_OFFSET_607C,
|
||||||
|
0x00, home_offset)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to write home offset, node="
|
||||||
|
<< static_cast<int>(node_id)
|
||||||
|
<< ", home_offset=" << home_offset;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus_runtime_->writeSdo<std::uint32_t>(
|
||||||
|
node_id, eyou::EYOU_STORE_PARAMETERS_1010,
|
||||||
|
0x01,
|
||||||
|
0x65766173)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to save home offset parameter, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
||||||
|
|
||||||
|
std::int32_t home_offset_readback = 0;
|
||||||
|
if (!bus_runtime_->readSdo<std::int32_t>(node_id, msgs::CIA402_HOME_OFFSET_607C,
|
||||||
|
0x00, home_offset_readback)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to read back home offset, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (home_offset_readback != home_offset) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] home offset readback mismatch, node="
|
||||||
|
<< static_cast<int>(node_id)
|
||||||
|
<< ", expected=" << home_offset
|
||||||
|
<< ", actual=" << home_offset_readback;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus_runtime_->readSdo<std::int32_t>(node_id, msgs::CIA402_ACTUAL_POSITION_6064,
|
||||||
|
0x00, zeroed_position)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to read actual position "
|
||||||
|
<< "after writing home offset, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std::abs(static_cast<long long>(zeroed_position)) > 10000) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] home offset did not zero actual position, "
|
||||||
|
<< "node=" << static_cast<int>(node_id)
|
||||||
|
<< ", actual_position_before=" << actual_position
|
||||||
|
<< ", home_offset=" << home_offset
|
||||||
|
<< ", home_offset_readback=" << home_offset_readback
|
||||||
|
<< ", actual_position_after=" << zeroed_position
|
||||||
|
<< ", tolerance_counts=" << 10000;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EyouMotorAdapter::brakeRelease(const std::uint8_t node_id)
|
||||||
|
{
|
||||||
|
if (!bus_runtime_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!bus_runtime_->writeSdo<std::uint8_t>(
|
||||||
|
node_id, eyou::EYOU_BRAKE_CONTROL_2014,
|
||||||
|
0x01,
|
||||||
|
1)) {
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] failed to release brake, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto deadline =
|
||||||
|
std::chrono::steady_clock::now() + std::chrono::milliseconds{1000};
|
||||||
|
do {
|
||||||
|
std::uint8_t brake_state = 0;
|
||||||
|
if (bus_runtime_->readSdo<std::uint8_t>(
|
||||||
|
node_id, eyou::EYOU_BRAKE_CONTROL_2014,
|
||||||
|
0x02, brake_state) &&
|
||||||
|
(brake_state == 1 || brake_state == 2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{10});
|
||||||
|
} while (std::chrono::steady_clock::now() < deadline);
|
||||||
|
|
||||||
|
CMVR_LOG(ERROR) << "[EyouMotorAdapter] brake release timeout, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
309
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_device_manager_real_test.cpp
vendored
Normal file
309
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_device_manager_real_test.cpp
vendored
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "cmvr/config/device_manager_config/device_manager_config.pb.h"
|
||||||
|
#include "common/config/config_files.h"
|
||||||
|
#include "devices/motor/abstract_motor.h"
|
||||||
|
#include "devices/motor/manager/include/motor_manager.h"
|
||||||
|
#include "manager/device_manager/include/device_manager.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kMotorManagerId = "ethercat_motors";
|
||||||
|
constexpr const char* kMotorConfigFile =
|
||||||
|
"devices/motor/ethercat_motors_two_real_test.pb.txt";
|
||||||
|
constexpr std::array<int, 4> kFourMotorIds{1, 2, 3, 4};
|
||||||
|
constexpr std::chrono::milliseconds kCyclicCommandPeriod{1};
|
||||||
|
constexpr std::chrono::milliseconds kPrintPeriod{100};
|
||||||
|
constexpr std::chrono::milliseconds kStatsSamplePeriod{10};
|
||||||
|
constexpr std::chrono::milliseconds kHoldAfterTrajectoryDuration{500};
|
||||||
|
constexpr std::chrono::milliseconds kFourMotorTrajectoryDuration{20000};
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
constexpr double kFourMotorAmplitudeRad = 0.2;
|
||||||
|
constexpr double kFourMotorPeriodS = 1.0;
|
||||||
|
constexpr std::array<double, 4> kFourMotorPhaseRad{0.0, 0.0, 0.0, 0.0};
|
||||||
|
|
||||||
|
class DeviceManagerDestroyGuard {
|
||||||
|
public:
|
||||||
|
~DeviceManagerDestroyGuard()
|
||||||
|
{
|
||||||
|
DeviceManager::destroyInstance();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TrackingErrorStats {
|
||||||
|
std::int64_t sample_count{0};
|
||||||
|
double sum_error{0.0};
|
||||||
|
double sum_error_sq{0.0};
|
||||||
|
double max_abs_error{0.0};
|
||||||
|
double sin_projection{0.0};
|
||||||
|
double cos_projection{0.0};
|
||||||
|
|
||||||
|
void add(const double error, const double theta)
|
||||||
|
{
|
||||||
|
++sample_count;
|
||||||
|
sum_error += error;
|
||||||
|
sum_error_sq += error * error;
|
||||||
|
max_abs_error = std::max(max_abs_error, std::fabs(error));
|
||||||
|
sin_projection += error * std::sin(theta);
|
||||||
|
cos_projection += error * std::cos(theta);
|
||||||
|
}
|
||||||
|
|
||||||
|
double mean() const
|
||||||
|
{
|
||||||
|
return sample_count > 0 ? sum_error / static_cast<double>(sample_count) : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double rms() const
|
||||||
|
{
|
||||||
|
return sample_count > 0
|
||||||
|
? std::sqrt(sum_error_sq / static_cast<double>(sample_count))
|
||||||
|
: 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double fundamentalAmplitude() const
|
||||||
|
{
|
||||||
|
if (sample_count == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
const double scale = 2.0 / static_cast<double>(sample_count);
|
||||||
|
return scale * std::sqrt(sin_projection * sin_projection +
|
||||||
|
cos_projection * cos_projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
double phaseRad() const
|
||||||
|
{
|
||||||
|
return std::atan2(cos_projection, sin_projection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
double normalizePhaseRad(double phase)
|
||||||
|
{
|
||||||
|
while (phase > kPi) {
|
||||||
|
phase -= 2.0 * kPi;
|
||||||
|
}
|
||||||
|
while (phase < -kPi) {
|
||||||
|
phase += 2.0 * kPi;
|
||||||
|
}
|
||||||
|
return phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
double radToDeg(const double rad)
|
||||||
|
{
|
||||||
|
return rad * 180.0 / kPi;
|
||||||
|
}
|
||||||
|
|
||||||
|
config::DeviceManagerConfig createEthercatOnlyDeviceManagerConfig()
|
||||||
|
{
|
||||||
|
config::DeviceManagerConfig config;
|
||||||
|
config.set_name("eyou_motor_device_manager_real_test");
|
||||||
|
config.set_version("test");
|
||||||
|
config.set_init_all_motors_when_no_active_joints(true);
|
||||||
|
|
||||||
|
auto* motor_entry = config.add_devices();
|
||||||
|
motor_entry->set_id(kMotorManagerId);
|
||||||
|
motor_entry->set_type(config::DeviceConfigEntry::DEVICE_TYPE_MOTOR_SYSTEM);
|
||||||
|
motor_entry->set_config_file(kMotorConfigFile);
|
||||||
|
motor_entry->set_enable(true);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMotorState(const int motor_id, const std::shared_ptr<AbstractMotor>& motor)
|
||||||
|
{
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
std::cout << "motor_id=" << motor_id
|
||||||
|
<< ", joint_name=" << motor->jointName()
|
||||||
|
<< ", q=" << motor->getQ() << " rad"
|
||||||
|
<< ", qd=" << motor->getQd() << " rad/s"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(EyouMotorDeviceManagerRealTest, InitFourEthercatMotorsAndPrintState)
|
||||||
|
{
|
||||||
|
ConfigHelper::setConfigRootFromFile("cmvr-es/config/cmvr_es.pb.txt");
|
||||||
|
DeviceManagerDestroyGuard guard;
|
||||||
|
|
||||||
|
auto& device_manager =
|
||||||
|
DeviceManager::getInstance(createEthercatOnlyDeviceManagerConfig());
|
||||||
|
auto motor_manager = device_manager.getDevice<MotorManager>(kMotorManagerId);
|
||||||
|
ASSERT_NE(motor_manager, nullptr);
|
||||||
|
|
||||||
|
// for (int motor_id = 1; motor_id <= 4; ++motor_id) {
|
||||||
|
// printMotorState(motor_id, motor_manager->getMotor(motor_id));
|
||||||
|
// }
|
||||||
|
|
||||||
|
auto motor = motor_manager->getMotor(4);
|
||||||
|
motor->calibrateZeroQ();
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_PROFILE_VELOCITY);
|
||||||
|
motor->commandProfileVelocity(-2,5);
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 1; i <= 50; ++i)
|
||||||
|
{
|
||||||
|
auto q = motor->getQ();
|
||||||
|
auto qd = motor->getQd();
|
||||||
|
std::cout << "q=" << q << ", qd=" << qd << std::endl;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
motor->quickStop();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorDeviceManagerRealTest, CommandFourCyclicPositionSinTrajectory)
|
||||||
|
{
|
||||||
|
ConfigHelper::setConfigRootFromFile("cmvr-es/config/cmvr_es.pb.txt");
|
||||||
|
DeviceManagerDestroyGuard guard;
|
||||||
|
|
||||||
|
auto& device_manager =
|
||||||
|
DeviceManager::getInstance(createEthercatOnlyDeviceManagerConfig());
|
||||||
|
auto motor_manager = device_manager.getDevice<MotorManager>(kMotorManagerId);
|
||||||
|
ASSERT_NE(motor_manager, nullptr);
|
||||||
|
|
||||||
|
std::array<std::shared_ptr<AbstractMotor>, kFourMotorIds.size()> motors;
|
||||||
|
for (std::size_t i = 0; i < kFourMotorIds.size(); ++i) {
|
||||||
|
const int motor_id = kFourMotorIds[i];
|
||||||
|
motors[i] = motor_manager->getMotor(static_cast<std::uint8_t>(motor_id));
|
||||||
|
ASSERT_NE(motors[i], nullptr);
|
||||||
|
printMotorState(motor_id, motors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "calibrate zero for four EtherCAT motors" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
const int motor_id = kFourMotorIds[i];
|
||||||
|
std::cout << "before calibrateZeroQ: ";
|
||||||
|
printMotorState(motor_id, motors[i]);
|
||||||
|
ASSERT_TRUE(motors[i]->calibrateZeroQ());
|
||||||
|
std::cout << "after calibrateZeroQ: ";
|
||||||
|
printMotorState(motor_id, motors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& motor : motors) {
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
}
|
||||||
|
for (const auto& motor : motors) {
|
||||||
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, kFourMotorIds.size()> center_q{};
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
center_q[i] = motors[i]->getQ();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double omega = 2.0 * kPi / kFourMotorPeriodS;
|
||||||
|
std::cout << "command four motors in CSP, duration="
|
||||||
|
<< kFourMotorTrajectoryDuration.count()
|
||||||
|
<< " ms, command_period=" << kCyclicCommandPeriod.count()
|
||||||
|
<< " ms, amplitude=" << kFourMotorAmplitudeRad
|
||||||
|
<< " rad, period=" << kFourMotorPeriodS
|
||||||
|
<< " s" << std::endl;
|
||||||
|
|
||||||
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto total_ticks = kFourMotorTrajectoryDuration / kCyclicCommandPeriod;
|
||||||
|
std::array<TrackingErrorStats, kFourMotorIds.size()> error_stats;
|
||||||
|
double max_error_spread_rad = 0.0;
|
||||||
|
double sum_error_spread_sq = 0.0;
|
||||||
|
std::int64_t error_spread_sample_count = 0;
|
||||||
|
for (std::int64_t tick = 0; tick <= total_ticks; ++tick) {
|
||||||
|
const auto elapsed = tick * kCyclicCommandPeriod;
|
||||||
|
const double t_s = static_cast<double>(elapsed.count()) / 1000.0;
|
||||||
|
|
||||||
|
std::array<double, kFourMotorIds.size()> target_q{};
|
||||||
|
std::array<double, kFourMotorIds.size()> target_qd{};
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
const double theta = omega * t_s + kFourMotorPhaseRad[i];
|
||||||
|
target_q[i] = center_q[i] + kFourMotorAmplitudeRad * (1.0 - std::cos(theta));
|
||||||
|
target_qd[i] = kFourMotorAmplitudeRad * omega * std::sin(theta);
|
||||||
|
ASSERT_TRUE(motors[i]->commandCyclicPosition(target_q[i], target_qd[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed.count() % kStatsSamplePeriod.count() == 0) {
|
||||||
|
double min_error = std::numeric_limits<double>::max();
|
||||||
|
double max_error = std::numeric_limits<double>::lowest();
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
const double theta = omega * t_s + kFourMotorPhaseRad[i];
|
||||||
|
const double error = motors[i]->getQ() - target_q[i];
|
||||||
|
error_stats[i].add(error, theta);
|
||||||
|
min_error = std::min(min_error, error);
|
||||||
|
max_error = std::max(max_error, error);
|
||||||
|
}
|
||||||
|
const double error_spread = max_error - min_error;
|
||||||
|
max_error_spread_rad = std::max(max_error_spread_rad, error_spread);
|
||||||
|
sum_error_spread_sq += error_spread * error_spread;
|
||||||
|
++error_spread_sample_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (elapsed.count() % kPrintPeriod.count() == 0) {
|
||||||
|
// std::cout << "t=" << elapsed.count() << " ms" << std::endl;
|
||||||
|
// for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
// std::cout << " motor_id=" << kFourMotorIds[i]
|
||||||
|
// << ", target_q=" << target_q[i]
|
||||||
|
// << " rad, target_qd=" << target_qd[i]
|
||||||
|
// << " rad/s, q=" << motors[i]->getQ()
|
||||||
|
// << " rad, qd=" << motors[i]->getQd()
|
||||||
|
// << " rad/s" << std::endl;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
std::this_thread::sleep_until(start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hold_start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto hold_ticks = kHoldAfterTrajectoryDuration / kCyclicCommandPeriod;
|
||||||
|
for (std::int64_t tick = 0; tick <= hold_ticks; ++tick) {
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
ASSERT_TRUE(motors[i]->commandCyclicPosition(center_q[i], 0.0));
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_until(hold_start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "after four motor CSP trajectory" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
printMotorState(kFourMotorIds[i], motors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const double reference_phase = error_stats.front().phaseRad();
|
||||||
|
const double rms_error_spread =
|
||||||
|
error_spread_sample_count > 0
|
||||||
|
? std::sqrt(sum_error_spread_sq / static_cast<double>(error_spread_sample_count))
|
||||||
|
: 0.0;
|
||||||
|
std::cout << "four motor CSP tracking error statistics, sample_period="
|
||||||
|
<< kStatsSamplePeriod.count()
|
||||||
|
<< " ms, samples=" << error_stats.front().sample_count
|
||||||
|
<< ", max_error_spread=" << max_error_spread_rad
|
||||||
|
<< " rad, rms_error_spread=" << rms_error_spread
|
||||||
|
<< " rad" << std::endl;
|
||||||
|
for (std::size_t i = 0; i < motors.size(); ++i) {
|
||||||
|
const double phase = error_stats[i].phaseRad();
|
||||||
|
const double relative_phase = normalizePhaseRad(phase - reference_phase);
|
||||||
|
const double relative_phase_ms = relative_phase / omega * 1000.0;
|
||||||
|
std::cout << " motor_id=" << kFourMotorIds[i]
|
||||||
|
<< ", mean_error=" << error_stats[i].mean()
|
||||||
|
<< " rad, rms_error=" << error_stats[i].rms()
|
||||||
|
<< " rad, max_abs_error=" << error_stats[i].max_abs_error
|
||||||
|
<< " rad, error_fundamental_amp="
|
||||||
|
<< error_stats[i].fundamentalAmplitude()
|
||||||
|
<< " rad, error_phase=" << phase
|
||||||
|
<< " rad (" << radToDeg(phase)
|
||||||
|
<< " deg), relative_phase_to_motor1=" << relative_phase
|
||||||
|
<< " rad (" << radToDeg(relative_phase)
|
||||||
|
<< " deg, " << relative_phase_ms
|
||||||
|
<< " ms)" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
559
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_real_test.cpp
vendored
Normal file
559
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_real_test.cpp
vendored
Normal file
@ -0,0 +1,559 @@
|
|||||||
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
|
#include "devices/motor/abstract_motor.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_protocol.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kMotorId = 1;
|
||||||
|
constexpr std::chrono::milliseconds kModeSettleDelay{100};
|
||||||
|
constexpr std::chrono::milliseconds kCommandSamplePeriod{100};
|
||||||
|
constexpr std::chrono::milliseconds kCyclicCommandPeriod{1};
|
||||||
|
constexpr std::chrono::milliseconds kFeedbackSampleDuration{5000};
|
||||||
|
constexpr double kDefaultGearRatio = 101.0;
|
||||||
|
constexpr double kEncoderCountsPerMotorRev = 65536.0;
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
|
||||||
|
config::MotorGroupConfig createSingleSlaveGroup()
|
||||||
|
{
|
||||||
|
config::MotorGroupConfig group;
|
||||||
|
group.set_id("eyou_motor_real_test");
|
||||||
|
group.set_bus_type(config::MOTOR_BUS_ETHERCAT);
|
||||||
|
group.set_vendor(config::MOTOR_VENDOR_EYOU);
|
||||||
|
group.set_protocol(config::MOTOR_PROTOCOL_ETHERCAT_CIA402);
|
||||||
|
|
||||||
|
auto* ethercat = group.mutable_ethercat();
|
||||||
|
ethercat->set_master_index(0);
|
||||||
|
ethercat->set_cycle_us(1000);
|
||||||
|
ethercat->set_slave_op_timeout_ms(12000);
|
||||||
|
ethercat->set_slave_state_poll_period_ms(10);
|
||||||
|
|
||||||
|
auto* cia402 = ethercat->mutable_cia402();
|
||||||
|
cia402->set_profile_position_trigger_delay_ms(2);
|
||||||
|
cia402->set_state_transition_timeout_ms(1200);
|
||||||
|
cia402->set_velocity_stop_timeout_ms(2000);
|
||||||
|
cia402->set_status_poll_period_ms(10);
|
||||||
|
cia402->set_stopped_velocity_tolerance_rad_s(0.001);
|
||||||
|
|
||||||
|
auto* dc = ethercat->mutable_dc();
|
||||||
|
dc->set_enable(true);
|
||||||
|
dc->set_reference_motor_id(kMotorId);
|
||||||
|
dc->set_sync0_cycle_us(1000);
|
||||||
|
dc->set_sync0_shift_us(0);
|
||||||
|
dc->set_sync_reference_clock_period(1);
|
||||||
|
dc->set_assign_activate(768);
|
||||||
|
dc->set_sync_monitor_period_ms(1000);
|
||||||
|
|
||||||
|
auto* slave = ethercat->add_slaves();
|
||||||
|
slave->set_motor_id(kMotorId);
|
||||||
|
slave->set_alias(0);
|
||||||
|
slave->set_position(0);
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RuntimeStopGuard {
|
||||||
|
public:
|
||||||
|
explicit RuntimeStopGuard(std::shared_ptr<EthercatMotorBusRuntime> runtime)
|
||||||
|
: runtime_(std::move(runtime))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~RuntimeStopGuard()
|
||||||
|
{
|
||||||
|
if (runtime_) {
|
||||||
|
runtime_->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<EthercatMotorBusRuntime> runtime_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<EthercatMotorBusRuntime> startRuntime()
|
||||||
|
{
|
||||||
|
auto runtime = std::make_shared<EthercatMotorBusRuntime>();
|
||||||
|
runtime->setPdoMapping(createEyouCia402PdoMapping());
|
||||||
|
if (!runtime->init(createSingleSlaveGroup())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!runtime->start()) {
|
||||||
|
runtime->stop();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
return runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Cia402Protocol> createProtocol(
|
||||||
|
const std::shared_ptr<EthercatMotorBusRuntime>& runtime)
|
||||||
|
{
|
||||||
|
return std::make_shared<Cia402Protocol>(runtime, runtime->config().cia402());
|
||||||
|
}
|
||||||
|
|
||||||
|
config::MotorConfigItem createMotorConfig()
|
||||||
|
{
|
||||||
|
config::MotorConfigItem config;
|
||||||
|
config.set_id(kMotorId);
|
||||||
|
config.set_joint_name("ethercat_test_joint");
|
||||||
|
config.set_limit_q_lb(-6.14);
|
||||||
|
config.set_limit_q_ub(6.14);
|
||||||
|
config.set_limit_qd(10.0);
|
||||||
|
config.set_limit_qdd(10.0);
|
||||||
|
config.set_encoder_counts_per_rev(kEncoderCountsPerMotorRev);
|
||||||
|
config.set_gear_ratio(kDefaultGearRatio);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractMotor> createMotor(
|
||||||
|
const std::shared_ptr<EthercatMotorBusRuntime>& runtime)
|
||||||
|
{
|
||||||
|
auto motor = std::make_unique<EyouMotor>(
|
||||||
|
createMotorConfig(),
|
||||||
|
createProtocol(runtime),
|
||||||
|
std::make_unique<EyouMotorAdapter>(runtime));
|
||||||
|
if (!motor->init()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return motor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMotorState(const char* label, AbstractMotor& motor)
|
||||||
|
{
|
||||||
|
std::cout << label
|
||||||
|
<< ": motor_q=" << motor.getQ() << " rad"
|
||||||
|
<< ", motor_qd=" << motor.getQd() << " rad/s"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string hex16(const std::uint16_t value)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "0x" << std::uppercase << std::hex << std::setw(4) << std::setfill('0')
|
||||||
|
<< value;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void printRawEthercatFeedback(const char* label,
|
||||||
|
const std::shared_ptr<EthercatMotorBusRuntime>& runtime)
|
||||||
|
{
|
||||||
|
std::uint16_t statusword = 0;
|
||||||
|
std::int8_t mode_display = 0;
|
||||||
|
std::int32_t actual_position = 0;
|
||||||
|
std::int32_t actual_velocity = 0;
|
||||||
|
std::int16_t actual_torque = 0;
|
||||||
|
std::uint16_t error_code = 0;
|
||||||
|
|
||||||
|
runtime->readPdo<std::uint16_t>(kMotorId, msgs::CIA402_STATUS_WORD_6041, 0x00, statusword);
|
||||||
|
runtime->readPdo<std::int8_t>(kMotorId, msgs::CIA402_MODE_DISPLAY_6061, 0x00, mode_display);
|
||||||
|
runtime->readPdo<std::int32_t>(kMotorId, msgs::CIA402_ACTUAL_POSITION_6064, 0x00,
|
||||||
|
actual_position);
|
||||||
|
runtime->readPdo<std::int32_t>(kMotorId, msgs::CIA402_ACTUAL_VELOCITY_606C, 0x00,
|
||||||
|
actual_velocity);
|
||||||
|
runtime->readPdo<std::int16_t>(kMotorId, msgs::CIA402_ACTUAL_TORQUE_6077, 0x00,
|
||||||
|
actual_torque);
|
||||||
|
runtime->readPdo<std::uint16_t>(kMotorId, msgs::CIA402_ERROR_CODE_603F, 0x00, error_code);
|
||||||
|
|
||||||
|
std::cout << label
|
||||||
|
<< ": statusword=" << hex16(statusword)
|
||||||
|
<< ", mode_display=" << static_cast<int>(mode_display)
|
||||||
|
<< ", actual_position=" << actual_position
|
||||||
|
<< ", actual_velocity=" << actual_velocity
|
||||||
|
<< ", actual_torque=" << actual_torque
|
||||||
|
<< ", error_code=" << hex16(error_code)
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sampleMotorState(AbstractMotor& motor,
|
||||||
|
const std::chrono::milliseconds duration)
|
||||||
|
{
|
||||||
|
for (auto elapsed = std::chrono::milliseconds{0};
|
||||||
|
elapsed < duration;
|
||||||
|
elapsed += kCommandSamplePeriod) {
|
||||||
|
std::this_thread::sleep_for(kCommandSamplePeriod);
|
||||||
|
std::cout << "t=" << (elapsed + kCommandSamplePeriod).count() << " ms";
|
||||||
|
printMotorState("", motor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double nearbySafeTarget(const double current_q, const double delta_rad)
|
||||||
|
{
|
||||||
|
return current_q + (current_q > 0.0 ? -std::abs(delta_rad) : std::abs(delta_rad));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, ReadMotorStateOnly)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
printMotorState("motor state", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
for (int i = 1; i <= 10; ++i) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
printMotorState("motor state", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, CalibrateZeroQPrintBeforeAndAfter)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOff());
|
||||||
|
printMotorState("before calibrateZeroQ", *motor);
|
||||||
|
ASSERT_TRUE(motor->calibrateZeroQ());
|
||||||
|
printMotorState("after calibrateZeroQ", *motor);
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, CommandProfilePosition)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_PROFILE_POSITION);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->commandProfilePosition(-3.0, 0.5, 1.0));
|
||||||
|
sampleMotorState(*motor, kFeedbackSampleDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, CommandProfileVelocity)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_PROFILE_VELOCITY);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
std::cout << "motor.commandProfileVelocity(0.3 rad/s, 1.0 rad/s^2)" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->commandProfileVelocity(0.3, 1.0));
|
||||||
|
sampleMotorState(*motor, kFeedbackSampleDuration);
|
||||||
|
|
||||||
|
std::cout << "motor.commandProfileVelocity(0 rad/s, 1.0 rad/s^2)" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->commandProfileVelocity(0.0, 1.0));
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{1000});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, CommandCyclicPosition)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds trajectory_duration{15000};
|
||||||
|
const double period_s = 6.0;
|
||||||
|
const double amplitude_rad = 3;
|
||||||
|
const double phase_rad = 0.0;
|
||||||
|
const double center_q = motor->getQ();
|
||||||
|
const double omega = 2.0 * kPi / period_s;
|
||||||
|
|
||||||
|
std::cout << "motor.commandCyclicPosition(sin), center_q=" << center_q
|
||||||
|
<< " rad, period=" << period_s
|
||||||
|
<< " s, amplitude=" << amplitude_rad
|
||||||
|
<< " rad, phase=" << phase_rad
|
||||||
|
<< " rad, command_period=" << kCyclicCommandPeriod.count()
|
||||||
|
<< " ms" << std::endl;
|
||||||
|
|
||||||
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto total_ticks = trajectory_duration / kCyclicCommandPeriod;
|
||||||
|
for (std::int64_t tick = 0; tick <= total_ticks; ++tick) {
|
||||||
|
const auto elapsed = tick * kCyclicCommandPeriod;
|
||||||
|
const double t_s = static_cast<double>(elapsed.count()) / 1000.0;
|
||||||
|
const double theta = omega * t_s + phase_rad;
|
||||||
|
const double target_q = center_q + amplitude_rad * std::sin(theta);
|
||||||
|
const double target_qd = amplitude_rad * omega * std::cos(theta);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->commandCyclicPosition(target_q, target_qd));
|
||||||
|
if (elapsed.count() % kCommandSamplePeriod.count() == 0) {
|
||||||
|
std::cout << "t=" << elapsed.count()
|
||||||
|
<< " ms, target_q=" << target_q
|
||||||
|
<< " rad, target_qd=" << target_qd
|
||||||
|
<< " rad/s";
|
||||||
|
printMotorState("", *motor);
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_until(start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, CommandCyclicVelocity)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds trajectory_duration{15000};
|
||||||
|
const double period_s = 6.0;
|
||||||
|
const double velocity_amplitude_rad_s = 5.0;
|
||||||
|
const double phase_rad = 0.0;
|
||||||
|
const double omega = 2.0 * kPi / period_s;
|
||||||
|
|
||||||
|
std::cout << "motor.commandCyclicVelocity(sin), period=" << period_s
|
||||||
|
<< " s, velocity_amplitude=" << velocity_amplitude_rad_s
|
||||||
|
<< " rad/s, phase=" << phase_rad
|
||||||
|
<< " rad, command_period=" << kCyclicCommandPeriod.count()
|
||||||
|
<< " ms" << std::endl;
|
||||||
|
|
||||||
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto total_ticks = trajectory_duration / kCyclicCommandPeriod;
|
||||||
|
for (std::int64_t tick = 0; tick <= total_ticks; ++tick) {
|
||||||
|
const auto elapsed = tick * kCyclicCommandPeriod;
|
||||||
|
const double t_s = static_cast<double>(elapsed.count()) / 1000.0;
|
||||||
|
const double theta = omega * t_s + phase_rad;
|
||||||
|
const double target_qd = velocity_amplitude_rad_s * std::sin(theta);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->commandCyclicVelocity(target_qd));
|
||||||
|
if (elapsed.count() % kCommandSamplePeriod.count() == 0) {
|
||||||
|
std::cout << "t=" << elapsed.count()
|
||||||
|
<< " ms, target_qd=" << target_qd
|
||||||
|
<< " rad/s";
|
||||||
|
printMotorState("", *motor);
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_until(start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "motor.commandCyclicVelocity(0 rad/s)" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->commandCyclicVelocity(0.0));
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{500});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, QuickStopAfterTwoSeconds)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOff());
|
||||||
|
ASSERT_TRUE(motor->calibrateZeroQ());
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds run_duration{2000};
|
||||||
|
const double period_s = 6.0;
|
||||||
|
const double velocity_amplitude_rad_s = 4.5;
|
||||||
|
const double phase_rad = 0.0;
|
||||||
|
const double omega = 2.0 * kPi / period_s;
|
||||||
|
|
||||||
|
std::cout << "motor.commandCyclicVelocity(sin), then quickStop at "
|
||||||
|
<< run_duration.count()
|
||||||
|
<< " ms, period=" << period_s
|
||||||
|
<< " s, velocity_amplitude=" << velocity_amplitude_rad_s
|
||||||
|
<< " rad/s, phase=" << phase_rad
|
||||||
|
<< " rad, command_period=" << kCyclicCommandPeriod.count()
|
||||||
|
<< " ms" << std::endl;
|
||||||
|
|
||||||
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto total_ticks = run_duration / kCyclicCommandPeriod;
|
||||||
|
for (std::int64_t tick = 0; tick <= total_ticks; ++tick) {
|
||||||
|
const auto elapsed = tick * kCyclicCommandPeriod;
|
||||||
|
const double t_s = static_cast<double>(elapsed.count()) / 1000.0;
|
||||||
|
const double theta = omega * t_s + phase_rad;
|
||||||
|
const double target_qd = velocity_amplitude_rad_s * std::sin(theta);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->commandCyclicVelocity(target_qd));
|
||||||
|
if (elapsed.count() % kCommandSamplePeriod.count() == 0) {
|
||||||
|
std::cout << "t=" << elapsed.count()
|
||||||
|
<< " ms, target_qd=" << target_qd
|
||||||
|
<< " rad/s";
|
||||||
|
printMotorState("", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_until(start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "motor.quickStop()" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->quickStop());
|
||||||
|
printMotorState("after quickStop", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{1000});
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, QuickStopInProfilePosition)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOff());
|
||||||
|
ASSERT_TRUE(motor->calibrateZeroQ());
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_PROFILE_POSITION);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds quick_stop_time{1000};
|
||||||
|
const double start_q = motor->getQ();
|
||||||
|
const double target_q = nearbySafeTarget(start_q, 4.0);
|
||||||
|
const double max_qd = 2.0;
|
||||||
|
const double max_qdd = 10.0;
|
||||||
|
|
||||||
|
std::cout << "motor.commandProfilePosition(" << target_q
|
||||||
|
<< " rad, " << max_qd
|
||||||
|
<< " rad/s, " << max_qdd
|
||||||
|
<< " rad/s^2), then quickStop at "
|
||||||
|
<< quick_stop_time.count() << " ms" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->commandProfilePosition(target_q, max_qd, max_qdd));
|
||||||
|
std::cout << "wait " << quick_stop_time.count()
|
||||||
|
<< " ms before quickStop" << std::endl;
|
||||||
|
sampleMotorState(*motor, quick_stop_time);
|
||||||
|
printRawEthercatFeedback("raw feedback before quickStop", runtime);
|
||||||
|
|
||||||
|
std::cout << "motor.quickStop()" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->quickStop());
|
||||||
|
printMotorState("after quickStop", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{1000});
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, QuickStopInProfileVelocity)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOff());
|
||||||
|
ASSERT_TRUE(motor->calibrateZeroQ());
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_PROFILE_VELOCITY);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds quick_stop_time{2000};
|
||||||
|
const double target_qd = motor->getQ() > 0.0 ? -2.0 : 2.0;
|
||||||
|
const double max_qdd = 10.0;
|
||||||
|
|
||||||
|
std::cout << "motor.commandProfileVelocity(" << target_qd
|
||||||
|
<< " rad/s, " << max_qdd
|
||||||
|
<< " rad/s^2), then quickStop at "
|
||||||
|
<< quick_stop_time.count() << " ms" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->commandProfileVelocity(target_qd, max_qdd));
|
||||||
|
std::cout << "wait " << quick_stop_time.count()
|
||||||
|
<< " ms before quickStop" << std::endl;
|
||||||
|
sampleMotorState(*motor, quick_stop_time);
|
||||||
|
printRawEthercatFeedback("raw feedback before quickStop", runtime);
|
||||||
|
|
||||||
|
std::cout << "motor.quickStop()" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->quickStop());
|
||||||
|
printMotorState("after quickStop", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{1000});
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EyouMotorRealTest, QuickStopInCyclicPosition)
|
||||||
|
{
|
||||||
|
auto runtime = startRuntime();
|
||||||
|
ASSERT_NE(runtime, nullptr);
|
||||||
|
RuntimeStopGuard runtime_guard(runtime);
|
||||||
|
|
||||||
|
auto motor = createMotor(runtime);
|
||||||
|
ASSERT_NE(motor, nullptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->torqueOff());
|
||||||
|
ASSERT_TRUE(motor->calibrateZeroQ());
|
||||||
|
ASSERT_TRUE(motor->torqueOn());
|
||||||
|
motor->setMode(msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
|
std::this_thread::sleep_for(kModeSettleDelay);
|
||||||
|
|
||||||
|
const std::chrono::milliseconds run_duration{2000};
|
||||||
|
const double start_q = motor->getQ();
|
||||||
|
const double target_qd = start_q > 0.0 ? -2.0 : 2.0;
|
||||||
|
|
||||||
|
std::cout << "motor.commandCyclicPosition(linear), start_q=" << start_q
|
||||||
|
<< " rad, target_qd=" << target_qd
|
||||||
|
<< " rad/s, then quickStop at "
|
||||||
|
<< run_duration.count()
|
||||||
|
<< " ms, command_period=" << kCyclicCommandPeriod.count()
|
||||||
|
<< " ms" << std::endl;
|
||||||
|
|
||||||
|
const auto start_time = std::chrono::steady_clock::now();
|
||||||
|
const auto total_ticks = run_duration / kCyclicCommandPeriod;
|
||||||
|
for (std::int64_t tick = 0; tick <= total_ticks; ++tick) {
|
||||||
|
const auto elapsed = tick * kCyclicCommandPeriod;
|
||||||
|
const double t_s = static_cast<double>(elapsed.count()) / 1000.0;
|
||||||
|
const double target_q = start_q + target_qd * t_s;
|
||||||
|
|
||||||
|
ASSERT_TRUE(motor->commandCyclicPosition(target_q, target_qd));
|
||||||
|
if (elapsed.count() % kCommandSamplePeriod.count() == 0) {
|
||||||
|
std::cout << "t=" << elapsed.count()
|
||||||
|
<< " ms, target_q=" << target_q
|
||||||
|
<< " rad, target_qd=" << target_qd
|
||||||
|
<< " rad/s";
|
||||||
|
printMotorState("", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_until(start_time + (tick + 1) * kCyclicCommandPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "motor.quickStop()" << std::endl;
|
||||||
|
ASSERT_TRUE(motor->quickStop());
|
||||||
|
printMotorState("after quickStop", *motor);
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
sampleMotorState(*motor, std::chrono::milliseconds{1000});
|
||||||
|
printRawEthercatFeedback("raw feedback", runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::device
|
||||||
@ -23,19 +23,25 @@ public:
|
|||||||
|
|
||||||
void setMode(msgs::RunMode mode) override;
|
void setMode(msgs::RunMode mode) override;
|
||||||
msgs::RunMode getMode() override;
|
msgs::RunMode getMode() override;
|
||||||
void torqueOff() override;
|
bool torqueOn() override;
|
||||||
|
bool torqueOff() override;
|
||||||
|
bool brakeRelease() override;
|
||||||
|
bool quickStop() override;
|
||||||
|
|
||||||
void setLimitQ(double ub, double lb) override;
|
void setLimitQ(double ub, double lb) override;
|
||||||
void setLimitQd(double qd) override;
|
void setLimitQd(double qd) override;
|
||||||
void setLimitQdd(double u_qdd, double l_qdd) override;
|
void setLimitQdd(double u_qdd, double l_qdd) override;
|
||||||
|
|
||||||
void brake() override;
|
|
||||||
void setQ(double q) override;
|
|
||||||
void setTarget(double q, double qd) override;
|
|
||||||
void setTarget(double qd) override;
|
|
||||||
bool calibrateZeroQ() override;
|
bool calibrateZeroQ() override;
|
||||||
bool reachedTargetQ() override;
|
bool reachedTargetQ() override;
|
||||||
void setQd(double qd) override;
|
bool commandProfilePosition(double target_q,
|
||||||
|
double max_qd = 0.0,
|
||||||
|
double max_qdd = 0.0) override;
|
||||||
|
bool commandProfileVelocity(double target_qd, double max_qdd = 0.0) override;
|
||||||
|
bool commandCyclicPosition(double target_q,
|
||||||
|
double target_qd = 0.0) override;
|
||||||
|
bool commandCyclicVelocity(double target_qd) override;
|
||||||
|
bool commandCyclicTorque(double target_tau) override;
|
||||||
double getQ() override;
|
double getQ() override;
|
||||||
double getQd() override;
|
double getQd() override;
|
||||||
|
|
||||||
@ -44,6 +50,7 @@ public:
|
|||||||
const std::vector<double>& velocities);
|
const std::vector<double>& velocities);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool holdPosition_();
|
||||||
double clampQ_(double q) const;
|
double clampQ_(double q) const;
|
||||||
double clampQd_(double qd) const;
|
double clampQd_(double qd) const;
|
||||||
std::shared_ptr<simulate::MujocoWorld> worldLocked_() const;
|
std::shared_ptr<simulate::MujocoWorld> worldLocked_() const;
|
||||||
|
|||||||
@ -54,11 +54,27 @@ msgs::RunMode MujocoMotor::getMode()
|
|||||||
return mode_;
|
return mode_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::torqueOff()
|
bool MujocoMotor::torqueOn()
|
||||||
{
|
{
|
||||||
brake();
|
return holdPosition_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoMotor::torqueOff()
|
||||||
|
{
|
||||||
|
const bool ok = holdPosition_();
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
mode_ = msgs::RUN_MODE_UNSPECIFIED;
|
mode_ = msgs::RUN_MODE_UNSPECIFIED;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoMotor::brakeRelease()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoMotor::quickStop()
|
||||||
|
{
|
||||||
|
return holdPosition_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::setLimitQ(const double ub, const double lb)
|
void MujocoMotor::setLimitQ(const double ub, const double lb)
|
||||||
@ -82,38 +98,78 @@ void MujocoMotor::setLimitQdd(const double u_qdd, const double l_qdd)
|
|||||||
info_.limit_qdd = std::max(limit_qdd_upper_, std::abs(limit_qdd_lower_));
|
info_.limit_qdd = std::max(limit_qdd_upper_, std::abs(limit_qdd_lower_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::brake()
|
bool MujocoMotor::holdPosition_()
|
||||||
{
|
{
|
||||||
const auto world = worldLocked_();
|
const auto world = worldLocked_();
|
||||||
double q = 0.0;
|
double q = 0.0;
|
||||||
if (!world || !world->getJointPosition(info_.joint_name, q)) {
|
if (!world || !world->getJointPosition(info_.joint_name, q)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
target_q_ = q;
|
target_q_ = q;
|
||||||
mode_ = msgs::RUN_MODE_CYCLIC_SYNC_POSITION;
|
mode_ = msgs::RUN_MODE_CYCLIC_SYNC_POSITION;
|
||||||
world->setJointTargetState(info_.joint_name, q, 0.0);
|
world->setJointTargetState(info_.joint_name, q, 0.0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::setQ(const double q)
|
bool MujocoMotor::commandProfilePosition(const double target_q,
|
||||||
|
const double max_qd,
|
||||||
|
const double max_qdd)
|
||||||
{
|
{
|
||||||
setTarget(q, 0.0);
|
(void)max_qdd;
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
const auto world = worldLocked_();
|
||||||
|
if (!world) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mode_ = msgs::RUN_MODE_PROFILE_POSITION;
|
||||||
|
target_q_ = clampQ_(target_q);
|
||||||
|
const double profile_qd = max_qd > 0.0 ? max_qd : info_.limit_qd;
|
||||||
|
return world->setJointTargetState(info_.joint_name, target_q_, clampQd_(profile_qd));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::setTarget(const double q, const double qd)
|
bool MujocoMotor::commandProfileVelocity(const double target_qd, const double max_qdd)
|
||||||
|
{
|
||||||
|
(void)max_qdd;
|
||||||
|
std::scoped_lock lock(mtx_);
|
||||||
|
const auto world = worldLocked_();
|
||||||
|
if (!world) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mode_ = msgs::RUN_MODE_PROFILE_VELOCITY;
|
||||||
|
return world->setJointTargetVelocity(info_.joint_name, clampQd_(target_qd));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoMotor::commandCyclicPosition(const double target_q,
|
||||||
|
const double target_qd)
|
||||||
{
|
{
|
||||||
std::scoped_lock lock(mtx_);
|
std::scoped_lock lock(mtx_);
|
||||||
const auto world = worldLocked_();
|
const auto world = worldLocked_();
|
||||||
if (!world) {
|
if (!world) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
target_q_ = clampQ_(q);
|
mode_ = msgs::RUN_MODE_CYCLIC_SYNC_POSITION;
|
||||||
world->setJointTargetState(info_.joint_name, target_q_, clampQd_(qd));
|
target_q_ = clampQ_(target_q);
|
||||||
|
return world->setJointTargetState(info_.joint_name, target_q_, clampQd_(target_qd));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::setTarget(const double qd)
|
bool MujocoMotor::commandCyclicVelocity(const double target_qd)
|
||||||
{
|
{
|
||||||
setQd(qd);
|
std::scoped_lock lock(mtx_);
|
||||||
|
const auto world = worldLocked_();
|
||||||
|
if (!world) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mode_ = msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY;
|
||||||
|
return world->setJointTargetVelocity(info_.joint_name, clampQd_(target_qd));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoMotor::commandCyclicTorque(const double target_tau)
|
||||||
|
{
|
||||||
|
(void)target_tau;
|
||||||
|
CMVR_LOG(ERROR) << "[MujocoMotor] cyclic torque command is not implemented: "
|
||||||
|
<< info_.joint_name;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MujocoMotor::calibrateZeroQ()
|
bool MujocoMotor::calibrateZeroQ()
|
||||||
@ -140,16 +196,6 @@ bool MujocoMotor::reachedTargetQ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoMotor::setQd(const double qd)
|
|
||||||
{
|
|
||||||
std::scoped_lock lock(mtx_);
|
|
||||||
const auto world = worldLocked_();
|
|
||||||
if (!world) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
world->setJointTargetVelocity(info_.joint_name, clampQd_(qd));
|
|
||||||
}
|
|
||||||
|
|
||||||
double MujocoMotor::getQ()
|
double MujocoMotor::getQ()
|
||||||
{
|
{
|
||||||
const auto world = worldLocked_();
|
const auto world = worldLocked_();
|
||||||
|
|||||||
@ -22,6 +22,8 @@ namespace cmvr {
|
|||||||
info_.limit_q_ub = config.limit_q_ub();
|
info_.limit_q_ub = config.limit_q_ub();
|
||||||
info_.limit_qd = config.limit_qd() > 0.0 ? config.limit_qd() : 0.5;
|
info_.limit_qd = config.limit_qd() > 0.0 ? config.limit_qd() : 0.5;
|
||||||
info_.limit_qdd = config.limit_qdd() > 0.0 ? config.limit_qdd() : 10.0;
|
info_.limit_qdd = config.limit_qdd() > 0.0 ? config.limit_qdd() : 10.0;
|
||||||
|
encoder_counts_per_rev_ = config.encoder_counts_per_rev();
|
||||||
|
gear_ratio_ = config.gear_ratio();
|
||||||
node_id_ = info_.id;
|
node_id_ = info_.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +39,19 @@ namespace cmvr {
|
|||||||
}
|
}
|
||||||
if (protocol_->comm_proto == MotorProtocolInterface::CommProto::CANOPEN ) {
|
if (protocol_->comm_proto == MotorProtocolInterface::CommProto::CANOPEN ) {
|
||||||
auto canopen_protocol = std::dynamic_pointer_cast<Ti5MotorCanopenProtocol>(protocol_);
|
auto canopen_protocol = std::dynamic_pointer_cast<Ti5MotorCanopenProtocol>(protocol_);
|
||||||
|
if (!canopen_protocol) {
|
||||||
|
CMVR_LOG(ERROR) << "[Ti5Motor] invalid CANopen protocol for motor: "
|
||||||
|
<< info_.joint_name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (encoder_counts_per_rev_ <= 0.0 || gear_ratio_ <= 0.0) {
|
||||||
|
CMVR_LOG(ERROR) << "[Ti5Motor] missing encoder conversion config: "
|
||||||
|
<< info_.joint_name
|
||||||
|
<< ", encoder_counts_per_rev=" << encoder_counts_per_rev_
|
||||||
|
<< ", gear_ratio=" << gear_ratio_;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
protocol_->setMotorConversion(node_id_, encoder_counts_per_rev_, gear_ratio_);
|
||||||
// torqueOff(node_id_);
|
// torqueOff(node_id_);
|
||||||
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_RESET_COMMUNICATION);
|
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_RESET_COMMUNICATION);
|
||||||
// canopen_protocol->torqueOff(node_id_);
|
// canopen_protocol->torqueOff(node_id_);
|
||||||
@ -45,8 +60,8 @@ namespace cmvr {
|
|||||||
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_ENTER_PRE_OPERATIONAL);
|
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_ENTER_PRE_OPERATIONAL);
|
||||||
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_START_REMOTE_NODE);
|
canopen_protocol->seedNmtRequest(node_id_,msgs::NMT_START_REMOTE_NODE);
|
||||||
canopen_protocol->setMode(node_id_,msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
canopen_protocol->setMode(node_id_,msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
// canopen_protocol->seedSdoRequest(node_id_, msgs::CS_WRITE_TWO_BYTES, msgs::CONTROL_WORD_6040, msgs::SUB_INDEX_0, 0x06,15);
|
// canopen_protocol->seedSdoRequest(node_id_, msgs::CS_WRITE_TWO_BYTES, msgs::CIA402_CONTROL_WORD_6040, msgs::SUB_INDEX_0, 0x06,15);
|
||||||
// canopen_protocol->seedSdoRequest(node_id_, msgs::CS_WRITE_TWO_BYTES, msgs::CONTROL_WORD_6040, msgs::SUB_INDEX_0, 0x0F,15);
|
// canopen_protocol->seedSdoRequest(node_id_, msgs::CS_WRITE_TWO_BYTES, msgs::CIA402_CONTROL_WORD_6040, msgs::SUB_INDEX_0, 0x0F,15);
|
||||||
canopen_protocol->setLimitQ(node_id_, info_.limit_q_ub, info_.limit_q_lb);
|
canopen_protocol->setLimitQ(node_id_, info_.limit_q_ub, info_.limit_q_lb);
|
||||||
canopen_protocol->setLimitQd(node_id_, info_.limit_qd);
|
canopen_protocol->setLimitQd(node_id_, info_.limit_qd);
|
||||||
canopen_protocol->setLimitQdd(node_id_, info_.limit_qdd, -info_.limit_qdd);
|
canopen_protocol->setLimitQdd(node_id_, info_.limit_qdd, -info_.limit_qdd);
|
||||||
@ -55,6 +70,10 @@ namespace cmvr {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double encoder_counts_per_rev_{0.0};
|
||||||
|
double gear_ratio_{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_rpdo1.h"
|
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_rpdo1.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_rpdo2.h"
|
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_rpdo2.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace cmvr {
|
namespace cmvr {
|
||||||
namespace device {
|
namespace device {
|
||||||
@ -29,28 +30,40 @@ namespace cmvr {
|
|||||||
|
|
||||||
bool initNode(uint8_t node_id) override;
|
bool initNode(uint8_t node_id) override;
|
||||||
|
|
||||||
void setMode(uint8_t node_id, msgs::RunMode mode);
|
void setMode(uint8_t node_id, msgs::RunMode mode) override;
|
||||||
void setTarget(uint8_t node_id, double angle_rad, double vel) override;
|
|
||||||
void setTarget(uint8_t node_id, double vel) override;
|
|
||||||
void setQ(uint8_t node_id, double angle_rad) override;
|
|
||||||
void setLimitQ(uint8_t node_id, double ub, double lb) override;
|
void setLimitQ(uint8_t node_id, double ub, double lb) override;
|
||||||
void setLimitQd(uint8_t node_id, double qd) override;
|
void setLimitQd(uint8_t node_id, double qd) override;
|
||||||
void setLimitQdd(uint8_t node_id, double u_qdd,double l_qdd) override;
|
void setLimitQdd(uint8_t node_id, double u_qdd,double l_qdd) override;
|
||||||
bool calibrateZeroQ(uint8_t node_id) override;
|
bool calibrateZeroQ(uint8_t node_id) override;
|
||||||
void brake(uint8_t node_id) override;
|
bool torqueOn(uint8_t node_id) override;
|
||||||
|
bool torqueOff(uint8_t node_id) override;
|
||||||
|
bool brakeRelease(uint8_t node_id) override;
|
||||||
|
bool quickStop(uint8_t node_id) override;
|
||||||
bool reachedTargetQ(uint8_t node_id) override;
|
bool reachedTargetQ(uint8_t node_id) override;
|
||||||
|
|
||||||
double getQ(uint8_t node_id) override;
|
double getQ(uint8_t node_id) override;
|
||||||
double getQd(uint8_t node_id) override;
|
double getQd(uint8_t node_id) override;
|
||||||
|
|
||||||
void setQd(uint8_t node_id, double qd) override;
|
bool commandProfilePosition(uint8_t node_id,
|
||||||
void setQdd(uint8_t node_id, double qdd) override;
|
double target_q,
|
||||||
|
double max_qd,
|
||||||
void torqueOff(uint8_t node_id) override;
|
double max_qdd) override;
|
||||||
|
bool commandProfileVelocity(uint8_t node_id,
|
||||||
|
double target_qd,
|
||||||
|
double max_qdd) override;
|
||||||
|
bool commandCyclicPosition(uint8_t node_id,
|
||||||
|
double target_q,
|
||||||
|
double target_qd) override;
|
||||||
|
bool commandCyclicVelocity(uint8_t node_id,
|
||||||
|
double target_qd) override;
|
||||||
|
bool commandCyclicTorque(uint8_t node_id, double target_tau) override;
|
||||||
|
void setMotorConversion(uint8_t node_id,
|
||||||
|
double encoder_counts_per_rev,
|
||||||
|
double gear_ratio) override;
|
||||||
|
|
||||||
void seedNmtRequest(uint8_t node_id, msgs::NmtCommand command, uint32_t delay_ms = 10);
|
void seedNmtRequest(uint8_t node_id, msgs::NmtCommand command, uint32_t delay_ms = 10);
|
||||||
void seedSdoRequest(uint8_t node_id, msgs::CommandSpecifier cs, msgs::ObIndex index,
|
void seedSdoRequest(uint8_t node_id, msgs::CommandSpecifier cs, uint32_t index,
|
||||||
msgs::ObSubIndex sub_index, uint32_t data, uint32_t delay_ms = 10);
|
uint32_t sub_index, uint32_t data, uint32_t delay_ms = 10);
|
||||||
|
|
||||||
void configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel);
|
void configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel);
|
||||||
void configPdo(uint8_t node_id);
|
void configPdo(uint8_t node_id);
|
||||||
@ -70,14 +83,20 @@ namespace cmvr {
|
|||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr double GearRatio = 101.0; // 电机减速比
|
|
||||||
static constexpr double RADTODEG = 180.0 / M_PI;
|
static constexpr double RADTODEG = 180.0 / M_PI;
|
||||||
|
static constexpr double Ti5VelocityUnitScale = 100.0;
|
||||||
|
static constexpr double Ti5AccelerationTimeScale = 1000.0;
|
||||||
|
|
||||||
|
struct MotorConversion {
|
||||||
|
double encoder_counts_per_rev{0.0};
|
||||||
|
double gear_ratio{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<AbstractCanbus> can_client_{nullptr};
|
std::shared_ptr<AbstractCanbus> can_client_{nullptr};
|
||||||
|
|
||||||
// key node_id
|
// key node_id
|
||||||
// std::unordered_map<uint8_t,msgs::RunMode> cur_mode_{};
|
// std::unordered_map<uint8_t,msgs::RunMode> cur_mode_{};
|
||||||
std::unordered_map<uint8_t,uint32_t> last_Qd_{};
|
std::unordered_map<uint8_t, MotorConversion> motor_conversions_{};
|
||||||
std::unordered_map<uint8_t,uint32_t> last_Qdd_{};
|
|
||||||
std::shared_ptr<device::CanSender<msgs::RobotDetail> > can_sender_{nullptr};
|
std::shared_ptr<device::CanSender<msgs::RobotDetail> > can_sender_{nullptr};
|
||||||
std::shared_ptr<device::MessageManager<msgs::RobotDetail> > message_manager_{nullptr};
|
std::shared_ptr<device::MessageManager<msgs::RobotDetail> > message_manager_{nullptr};
|
||||||
|
|
||||||
@ -94,11 +113,7 @@ namespace cmvr {
|
|||||||
std::map<uint8_t, motor::Ti5MotorRPDO1 *> rpdo1_commands_{};
|
std::map<uint8_t, motor::Ti5MotorRPDO1 *> rpdo1_commands_{};
|
||||||
std::map<uint8_t, motor::Ti5MotorRPDO2 *> rpdo2_commands_{};
|
std::map<uint8_t, motor::Ti5MotorRPDO2 *> rpdo2_commands_{};
|
||||||
|
|
||||||
void setPPTargetPosBySdo(uint8_t node_id, int32_t pos);
|
void writeProfilePositionTargetBySdo(uint8_t node_id, int32_t pos);
|
||||||
|
|
||||||
void setPPTargetPosByPdo(uint8_t node_id, int32_t pos);
|
|
||||||
|
|
||||||
void setCSPTargetPosByPdo(uint8_t node_id, int32_t pos);
|
|
||||||
|
|
||||||
|
|
||||||
void configTPDO1(uint8_t node_id);
|
void configTPDO1(uint8_t node_id);
|
||||||
@ -107,6 +122,14 @@ namespace cmvr {
|
|||||||
void configRPDO1(uint8_t node_id, bool enable);
|
void configRPDO1(uint8_t node_id, bool enable);
|
||||||
void configRPDO2(uint8_t node_id, bool enable);
|
void configRPDO2(uint8_t node_id, bool enable);
|
||||||
|
|
||||||
|
const MotorConversion* conversionForNode(uint8_t node_id) const;
|
||||||
|
double radToCounts(double angle_rad, const MotorConversion& conversion) const;
|
||||||
|
double countsToRad(int32_t counts, const MotorConversion& conversion) const;
|
||||||
|
double radPerSecToVelocityRaw(double velocity_rad_s, const MotorConversion& conversion) const;
|
||||||
|
uint32_t radPerSec2ToAccelerationRaw(double acceleration_rad_s2,
|
||||||
|
const MotorConversion& conversion) const;
|
||||||
|
double velocityRawToRadPerSec(int32_t velocity_raw, const MotorConversion& conversion) const;
|
||||||
|
|
||||||
|
|
||||||
bool waitUntil(std::function<bool()> condition, int timeout_ms) {
|
bool waitUntil(std::function<bool()> condition, int timeout_ms) {
|
||||||
auto start = std::chrono::steady_clock::now();
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
// Created by lgv on 2025/7/24.
|
// Created by lgv on 2025/7/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_sdo_response.h"
|
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_sdo_response.h"
|
||||||
|
|
||||||
using namespace cmvr::device::motor;
|
using namespace cmvr::device::motor;
|
||||||
@ -19,17 +20,17 @@ void Ti5MotorSdoResponse::ParseSdoData(const msgs::SdoFrame &sdo_response,
|
|||||||
|
|
||||||
|
|
||||||
switch (sdo_response.index()) {
|
switch (sdo_response.index()) {
|
||||||
case msgs::CONTROL_WORD_6040:
|
case msgs::CIA402_CONTROL_WORD_6040:
|
||||||
motor_status->set_ctrl_word(sdo_response.data());
|
motor_status->set_ctrl_word(sdo_response.data());
|
||||||
break;
|
break;
|
||||||
case msgs::STATUS_WORD_6041:
|
case msgs::CIA402_STATUS_WORD_6041:
|
||||||
motor_status->set_status_word(sdo_response.data());
|
motor_status->set_status_word(sdo_response.data());
|
||||||
break;
|
break;
|
||||||
case msgs::ACTUAL_POSITION_6064:
|
case msgs::CIA402_ACTUAL_POSITION_6064:
|
||||||
motor_status->set_position(static_cast<int32_t>(sdo_response.data()));
|
motor_status->set_position(static_cast<int32_t>(sdo_response.data()));
|
||||||
CMVR_LOG(INFO) << "pos = " << motor_status->position();
|
CMVR_LOG(INFO) << "pos = " << motor_status->position();
|
||||||
break;
|
break;
|
||||||
case msgs::POSITION_OFFSET_2008:
|
case msgs::CANOPEN_POSITION_OFFSET_2008:
|
||||||
motor_status->set_position_offset(sdo_response.data());
|
motor_status->set_position_offset(sdo_response.data());
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
@ -19,16 +19,4 @@ void Ti5MotorTPDO2::Parse(const std::uint8_t *bytes, int32_t length, msgs::Robot
|
|||||||
|
|
||||||
motor_status->set_position(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0]);
|
motor_status->set_position(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0]);
|
||||||
motor_status->set_speed(bytes[7] << 24 | bytes[6] << 16 | bytes[5] << 8 | bytes[4]);
|
motor_status->set_speed(bytes[7] << 24 | bytes[6] << 16 | bytes[5] << 8 | bytes[4]);
|
||||||
|
|
||||||
|
|
||||||
double gearRatio = 101.0;
|
|
||||||
double radToDeg = 180.0 / M_PI;
|
|
||||||
auto speed = (motor_status->speed() * 360.0) / (radToDeg * gearRatio * 100.0);
|
|
||||||
|
|
||||||
auto angle_rad = (motor_status->position() * 360.0) / (gearRatio * 65536.0 * radToDeg);
|
|
||||||
|
|
||||||
// CMVR_LOG(INFO) << " Motor ID " << int(this->node_id_) << " pos = " << angle_rad << " rad speed = " << speed << " rad/s";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
// Created by lgv on 2025/8/1.
|
// Created by lgv on 2025/8/1.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "cmvr/msgs/cia402.pb.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/ti5_motor_canopen_protocol.h"
|
#include "motor/drivers/ti5_canopen/include/ti5_motor_canopen_protocol.h"
|
||||||
#include "canbus/canopen/register.h"
|
#include "canbus/canopen/register.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_tpdo1.h"
|
#include "motor/drivers/ti5_canopen/include/protocol/ti5_motor_tpdo1.h"
|
||||||
@ -94,45 +95,153 @@ bool Ti5MotorCanopenProtocol::initNode(uint8_t node_id) {
|
|||||||
return ErrorCode::OK;
|
return ErrorCode::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Ti5MotorCanopenProtocol::setMotorConversion(
|
||||||
|
const uint8_t node_id,
|
||||||
|
const double encoder_counts_per_rev,
|
||||||
|
const double gear_ratio) {
|
||||||
|
motor_conversions_[node_id] = {encoder_counts_per_rev, gear_ratio};
|
||||||
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::seedSdoRequest(uint8_t node_id, CommandSpecifier cs, ObIndex index, ObSubIndex sub_index,
|
const Ti5MotorCanopenProtocol::MotorConversion*
|
||||||
|
Ti5MotorCanopenProtocol::conversionForNode(const uint8_t node_id) const {
|
||||||
|
const auto it = motor_conversions_.find(node_id);
|
||||||
|
if (it != motor_conversions_.end() &&
|
||||||
|
it->second.encoder_counts_per_rev > 0.0 &&
|
||||||
|
it->second.gear_ratio > 0.0) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
CMVR_LOG(ERROR) << "[Ti5MotorCanopenProtocol] missing conversion config for node "
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Ti5MotorCanopenProtocol::radToCounts(
|
||||||
|
const double angle_rad,
|
||||||
|
const MotorConversion& conversion) const {
|
||||||
|
return (angle_rad * RADTODEG) / 360.0 *
|
||||||
|
conversion.gear_ratio * conversion.encoder_counts_per_rev;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Ti5MotorCanopenProtocol::countsToRad(
|
||||||
|
const int32_t counts,
|
||||||
|
const MotorConversion& conversion) const {
|
||||||
|
return (counts * 360.0) /
|
||||||
|
(conversion.gear_ratio * conversion.encoder_counts_per_rev * RADTODEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Ti5MotorCanopenProtocol::radPerSecToVelocityRaw(
|
||||||
|
const double velocity_rad_s,
|
||||||
|
const MotorConversion& conversion) const {
|
||||||
|
return ((velocity_rad_s * RADTODEG) * conversion.gear_ratio * Ti5VelocityUnitScale) /
|
||||||
|
360.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Ti5MotorCanopenProtocol::radPerSec2ToAccelerationRaw(
|
||||||
|
const double acceleration_rad_s2,
|
||||||
|
const MotorConversion& conversion) const {
|
||||||
|
const auto raw = ((std::abs(acceleration_rad_s2) * RADTODEG) *
|
||||||
|
conversion.gear_ratio * Ti5VelocityUnitScale) /
|
||||||
|
360.0 / Ti5AccelerationTimeScale;
|
||||||
|
return static_cast<uint32_t>(std::abs(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
double Ti5MotorCanopenProtocol::velocityRawToRadPerSec(
|
||||||
|
const int32_t velocity_raw,
|
||||||
|
const MotorConversion& conversion) const {
|
||||||
|
return (velocity_raw * 360.0) /
|
||||||
|
(conversion.gear_ratio * Ti5VelocityUnitScale * RADTODEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Ti5MotorCanopenProtocol::seedSdoRequest(uint8_t node_id, CommandSpecifier cs, uint32_t index, uint32_t sub_index,
|
||||||
uint32_t data, uint32_t delay_ms) {
|
uint32_t data, uint32_t delay_ms) {
|
||||||
sdo_commands_[node_id]->SetFrameData(cs, index, sub_index, data);
|
sdo_commands_[node_id]->SetFrameData(cs, index, sub_index, data);
|
||||||
can_sender_->Update(sdo_commands_[node_id]->ID());
|
can_sender_->Update(sdo_commands_[node_id]->ID());
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setQ(uint8_t node_id, double angle_rad) {
|
bool Ti5MotorCanopenProtocol::commandProfilePosition(uint8_t node_id,
|
||||||
auto cmd = (angle_rad * RADTODEG) / 360.0 * GearRatio * 65536.0;
|
double target_q,
|
||||||
|
double max_qd,
|
||||||
switch (getMode(node_id)) {
|
double max_qdd) {
|
||||||
// case RUN_MODE_CYCLIC_SYNC_POSITION:
|
const auto* conversion = conversionForNode(node_id);
|
||||||
// setCSPTargetPosByPdo(node_id, static_cast<int32_t>(cmd));
|
if (!conversion) {
|
||||||
// break;
|
return false;
|
||||||
case RUN_MODE_PROFILE_POSITION:
|
|
||||||
// setPPTargetPosByPdo(node_id, static_cast<int32_t>(cmd));
|
|
||||||
setPPTargetPosBySdo(node_id, static_cast<int32_t>(cmd));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if (max_qd > 0.0) {
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_VELOCITY_6081,
|
||||||
|
SUB_INDEX_0,
|
||||||
|
static_cast<uint32_t>(std::abs(radPerSecToVelocityRaw(max_qd, *conversion))),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
if (max_qdd > 0.0) {
|
||||||
|
const auto accel = radPerSec2ToAccelerationRaw(max_qdd, *conversion);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_ACCELERATION_6083,
|
||||||
|
SUB_INDEX_0, accel, 0);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_DECELERATION_6084,
|
||||||
|
SUB_INDEX_0, accel, 0);
|
||||||
|
}
|
||||||
|
writeProfilePositionTargetBySdo(node_id, static_cast<int32_t>(radToCounts(target_q, *conversion)));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setTarget(uint8_t node_id, double angle_rad, double vel) {
|
bool Ti5MotorCanopenProtocol::commandProfileVelocity(uint8_t node_id,
|
||||||
auto pos_cmd = (angle_rad * RADTODEG) / 360.0 * GearRatio * 65536.0;
|
double target_qd,
|
||||||
auto speed = ((vel * RADTODEG) * GearRatio * 100.0) / 360.0;
|
double max_qdd) {
|
||||||
|
const auto* conversion = conversionForNode(node_id);
|
||||||
|
if (!conversion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (max_qdd > 0.0) {
|
||||||
|
const auto accel = radPerSec2ToAccelerationRaw(max_qdd, *conversion);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_ACCELERATION_6083,
|
||||||
|
SUB_INDEX_0, accel, 0);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_DECELERATION_6084,
|
||||||
|
SUB_INDEX_0, accel, 0);
|
||||||
|
}
|
||||||
|
const auto speed = radPerSecToVelocityRaw(target_qd, *conversion);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_TARGET_VELOCITY_60FF,
|
||||||
|
SUB_INDEX_0,
|
||||||
|
static_cast<uint32_t>(static_cast<int32_t>(std::llround(speed))),
|
||||||
|
0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Ti5MotorCanopenProtocol::commandCyclicPosition(uint8_t node_id,
|
||||||
|
double target_q,
|
||||||
|
double target_qd) {
|
||||||
|
const auto* conversion = conversionForNode(node_id);
|
||||||
|
if (!conversion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto pos_cmd = radToCounts(target_q, *conversion);
|
||||||
|
auto speed = radPerSecToVelocityRaw(target_qd, *conversion);
|
||||||
rpdo1_commands_[node_id]->SetTargetPos(pos_cmd);
|
rpdo1_commands_[node_id]->SetTargetPos(pos_cmd);
|
||||||
rpdo1_commands_[node_id]->SetTargetVel(uint32_t(std::abs(speed)));
|
rpdo1_commands_[node_id]->SetTargetVel(uint32_t(std::abs(speed)));
|
||||||
can_sender_->Update(rpdo1_commands_[node_id]->ID());
|
can_sender_->Update(rpdo1_commands_[node_id]->ID());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Ti5MotorCanopenProtocol::commandCyclicVelocity(uint8_t node_id,
|
||||||
void Ti5MotorCanopenProtocol::setTarget(uint8_t node_id, double vel) {
|
double target_qd) {
|
||||||
auto speed = ((vel * RADTODEG) * GearRatio * 100.0) / 360.0;
|
const auto* conversion = conversionForNode(node_id);
|
||||||
|
if (!conversion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto speed = radPerSecToVelocityRaw(target_qd, *conversion);
|
||||||
rpdo2_commands_[node_id]->SetTargetVel(int16_t(speed));
|
rpdo2_commands_[node_id]->SetTargetVel(int16_t(speed));
|
||||||
can_sender_->Update(rpdo2_commands_[node_id]->ID());
|
can_sender_->Update(rpdo2_commands_[node_id]->ID());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Ti5MotorCanopenProtocol::commandCyclicTorque(uint8_t node_id, double target_tau) {
|
||||||
|
(void)target_tau;
|
||||||
|
CMVR_LOG(ERROR) << "[Ti5MotorCanopenProtocol] cyclic torque command is not implemented, node="
|
||||||
|
<< static_cast<int>(node_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setPPTargetPosBySdo(uint8_t node_id, int32_t pos) {
|
void Ti5MotorCanopenProtocol::writeProfilePositionTargetBySdo(uint8_t node_id, int32_t pos) {
|
||||||
controlword_t cw = {};
|
controlword_t cw = {};
|
||||||
cw.switch_on = 1;
|
cw.switch_on = 1;
|
||||||
cw.enable_voltage = 1;
|
cw.enable_voltage = 1;
|
||||||
@ -141,44 +250,19 @@ void Ti5MotorCanopenProtocol::setPPTargetPosBySdo(uint8_t node_id, int32_t pos)
|
|||||||
cw.change_set_immediately = 1;
|
cw.change_set_immediately = 1;
|
||||||
|
|
||||||
// 1. 设置目标位置
|
// 1. 设置目标位置
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TARGET_POSITION_607A, SUB_INDEX_0, pos);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_TARGET_POSITION_607A, SUB_INDEX_0, pos);
|
||||||
|
|
||||||
|
|
||||||
// 2. 设置触发位(bit4 = 1)
|
// 2. 设置触发位(bit4 = 1)
|
||||||
cw.new_set_point = 1;
|
cw.new_set_point = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
|
|
||||||
|
|
||||||
// 3. 清除触发位(bit4 = 0),准备下一次触发
|
// 3. 清除触发位(bit4 = 0),准备下一次触发
|
||||||
cw.new_set_point = 0;
|
cw.new_set_point = 0;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setPPTargetPosByPdo(uint8_t node_id, int32_t pos) {
|
|
||||||
// 触发目标位置运动
|
|
||||||
controlword_t cw;
|
|
||||||
cw.value = 0x0F;
|
|
||||||
cw.new_set_point = 1;
|
|
||||||
cw.change_set_immediately = 1;
|
|
||||||
|
|
||||||
rpdo1_commands_[node_id]->SetTargetPos(pos);
|
|
||||||
rpdo1_commands_[node_id]->SetCtrlWord(cw.value);
|
|
||||||
can_sender_->Update(rpdo1_commands_[node_id]->ID());
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
|
|
||||||
cw.new_set_point = 0;
|
|
||||||
rpdo1_commands_[node_id]->SetCtrlWord(cw.value);
|
|
||||||
can_sender_->Update(rpdo1_commands_[node_id]->ID());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setCSPTargetPosByPdo(uint8_t node_id, int32_t pos) {
|
|
||||||
rpdo1_commands_[node_id]->SetTargetPos(pos);
|
|
||||||
rpdo1_commands_[node_id]->SetCtrlWord(0x0F);
|
|
||||||
can_sender_->Update(rpdo1_commands_[node_id]->ID());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setMode(uint8_t node_id, msgs::RunMode mode) {
|
void Ti5MotorCanopenProtocol::setMode(uint8_t node_id, msgs::RunMode mode) {
|
||||||
// cur_mode_[node_id] = mode;
|
// cur_mode_[node_id] = mode;
|
||||||
|
|
||||||
@ -188,36 +272,36 @@ void Ti5MotorCanopenProtocol::setMode(uint8_t node_id, msgs::RunMode mode) {
|
|||||||
controlword_t cw = {};
|
controlword_t cw = {};
|
||||||
cw.quick_stop = 1;
|
cw.quick_stop = 1;
|
||||||
cw.enable_voltage = 1;
|
cw.enable_voltage = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value, 20);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value, 20);
|
||||||
// configRPDO1(node_id, false);
|
// configRPDO1(node_id, false);
|
||||||
// configRPDO2(node_id, false);
|
// configRPDO2(node_id, false);
|
||||||
|
|
||||||
// 1 : 先设置模式
|
// 1 : 先设置模式
|
||||||
auto data = static_cast<uint32_t>(mode);
|
auto data = static_cast<uint32_t>(mode);
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, OPERATION_MODE_6060, SUB_INDEX_0, data);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CIA402_OPERATION_MODE_6060, SUB_INDEX_0, data);
|
||||||
|
|
||||||
|
|
||||||
// 3 : 状态机步进 —— Switch On & Enable Operation(0x0F)
|
// 3 : 状态机步进 —— Switch On & Enable Operation(0x0F)
|
||||||
cw.switch_on = 1;
|
cw.switch_on = 1;
|
||||||
cw.enable_operation = 1;
|
cw.enable_operation = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value, 20);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value, 20);
|
||||||
|
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case RUN_MODE_PROFILE_POSITION: {
|
case RUN_MODE_PROFILE_POSITION: {
|
||||||
// 4 : 设置目标位置(为当前位置)
|
// 4 : 设置目标位置(为当前位置)
|
||||||
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TARGET_POSITION_607A, SUB_INDEX_0, cur_pos);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_TARGET_POSITION_607A, SUB_INDEX_0, cur_pos);
|
||||||
|
|
||||||
|
|
||||||
// 5 : 触发位置运动(new_set_point 翻转)
|
// 5 : 触发位置运动(new_set_point 翻转)
|
||||||
cw.new_set_point = 1;
|
cw.new_set_point = 1;
|
||||||
cw.change_set_immediately = 1;
|
cw.change_set_immediately = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
|
|
||||||
// 6 : 清除 new_set_point(必须,不清除则无法再次触发新目标)
|
// 6 : 清除 new_set_point(必须,不清除则无法再次触发新目标)
|
||||||
cw.new_set_point = 0;
|
cw.new_set_point = 0;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,19 +309,19 @@ void Ti5MotorCanopenProtocol::setMode(uint8_t node_id, msgs::RunMode mode) {
|
|||||||
// configRPDO1(node_id, true);
|
// configRPDO1(node_id, true);
|
||||||
// 设置目标位置为当前位置
|
// 设置目标位置为当前位置
|
||||||
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TARGET_POSITION_607A, SUB_INDEX_0, cur_pos);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_TARGET_POSITION_607A, SUB_INDEX_0, cur_pos);
|
||||||
|
|
||||||
//3 : 使能 15
|
//3 : 使能 15
|
||||||
cw.enable_operation = 1;
|
cw.enable_operation = 1;
|
||||||
cw.switch_on = 1;
|
cw.switch_on = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RUN_MODE_PROFILE_VELOCITY: {
|
case RUN_MODE_PROFILE_VELOCITY: {
|
||||||
cw.enable_operation = 1;
|
cw.enable_operation = 1;
|
||||||
cw.switch_on = 1;
|
cw.switch_on = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +329,7 @@ void Ti5MotorCanopenProtocol::setMode(uint8_t node_id, msgs::RunMode mode) {
|
|||||||
// configRPDO2(node_id, true);
|
// configRPDO2(node_id, true);
|
||||||
cw.enable_operation = 1;
|
cw.enable_operation = 1;
|
||||||
cw.switch_on = 1;
|
cw.switch_on = 1;
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, cw.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -262,9 +346,9 @@ void Ti5MotorCanopenProtocol::seedNmtRequest(uint8_t node_id, msgs::NmtCommand c
|
|||||||
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel) {
|
void Ti5MotorCanopenProtocol::configProfile(uint8_t node_id, uint32_t speed, uint32_t accel, uint32_t decel) {
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_SPEED_6081, SUB_INDEX_0, speed);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_VELOCITY_6081, SUB_INDEX_0, speed);
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_ACCELERATION_6083, SUB_INDEX_0, accel);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_ACCELERATION_6083, SUB_INDEX_0, accel);
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_DECELERATION_6084, SUB_INDEX_0, decel);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_DECELERATION_6084, SUB_INDEX_0, decel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -272,128 +356,128 @@ void Ti5MotorCanopenProtocol::configTPDO1(uint8_t node_id) {
|
|||||||
//TDPO1 配置 状态字 和 控制字
|
//TDPO1 配置 状态字 和 控制字
|
||||||
// 1: 失能 pdo
|
// 1: 失能 pdo
|
||||||
uint32_t cob_id = TPDO1_BASE_ID_180 + node_id;
|
uint32_t cob_id = TPDO1_BASE_ID_180 + node_id;
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_COMM_1800, SUB_INDEX_1, cob_id | (1U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_COMM_1800, SUB_INDEX_1, cob_id | (1U << 31));
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO1_MAP_1A00, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_0, 0);
|
||||||
|
|
||||||
// 2: 配置为异步
|
// 2: 配置为异步
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO1_COMM_1800, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO1_COMM_1800, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
||||||
|
|
||||||
// 3:配置约束时间 unit:0.1ms
|
// 3:配置约束时间 unit:0.1ms
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, TPDO1_COMM_1800, SUB_INDEX_3, 10);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CANOPEN_TPDO1_COMM_1800, SUB_INDEX_3, 10);
|
||||||
|
|
||||||
// 4 : 配置周期发送时间 unit : ms 0 为 数据改变时发送
|
// 4 : 配置周期发送时间 unit : ms 0 为 数据改变时发送
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, TPDO1_COMM_1800, SUB_INDEX_5, 0);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CANOPEN_TPDO1_COMM_1800, SUB_INDEX_5, 0);
|
||||||
|
|
||||||
// 5 :映射控制字
|
// 5 :映射控制字
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_MAP_1A00, SUB_INDEX_1,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_1,
|
||||||
CONTROL_WORD_6040 << 16 | SUB_INDEX_0 << 8 | 16);
|
CIA402_CONTROL_WORD_6040 << 16 | SUB_INDEX_0 << 8 | 16);
|
||||||
|
|
||||||
//6 : 映射状态字
|
//6 : 映射状态字
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_MAP_1A00, SUB_INDEX_2,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_2,
|
||||||
STATUS_WORD_6041 << 16 | SUB_INDEX_0 << 8 | 16);
|
CIA402_STATUS_WORD_6041 << 16 | SUB_INDEX_0 << 8 | 16);
|
||||||
|
|
||||||
//7 : 映射模式
|
//7 : 映射模式
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_MAP_1A00, SUB_INDEX_3,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_3,
|
||||||
MODE_DISPLAY_6061 << 16 | SUB_INDEX_0 << 8 | 8);
|
CIA402_MODE_DISPLAY_6061 << 16 | SUB_INDEX_0 << 8 | 8);
|
||||||
|
|
||||||
//8 映射错误码
|
//8 映射错误码
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_MAP_1A00, SUB_INDEX_4,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_4,
|
||||||
ERROR_CODE_603F << 16 | SUB_INDEX_0 << 8 | 16);
|
CIA402_ERROR_CODE_603F << 16 | SUB_INDEX_0 << 8 | 16);
|
||||||
|
|
||||||
//9 写入该PDO映射对象总个数
|
//9 写入该PDO映射对象总个数
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO1_MAP_1A00, SUB_INDEX_0, 4);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO1_MAP_1A00, SUB_INDEX_0, 4);
|
||||||
|
|
||||||
//10 使能
|
//10 使能
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO1_COMM_1800, SUB_INDEX_1, cob_id | (0U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO1_COMM_1800, SUB_INDEX_1, cob_id | (0U << 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::configTPDO2(uint8_t node_id) {
|
void Ti5MotorCanopenProtocol::configTPDO2(uint8_t node_id) {
|
||||||
// 1: 失能 pdo
|
// 1: 失能 pdo
|
||||||
uint32_t cob_id = TPDO2_BASE_ID_280 + node_id;
|
uint32_t cob_id = TPDO2_BASE_ID_280 + node_id;
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO2_COMM_1801, SUB_INDEX_1, cob_id | (1U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO2_COMM_1801, SUB_INDEX_1, cob_id | (1U << 31));
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO2_MAP_1A01, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO2_MAP_1A01, SUB_INDEX_0, 0);
|
||||||
|
|
||||||
// 2: 配置为异步
|
// 2: 配置为异步
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO2_COMM_1801, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO2_COMM_1801, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
||||||
|
|
||||||
// 3:配置约束时间 unit:0.1ms
|
// 3:配置约束时间 unit:0.1ms
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, TPDO2_COMM_1801, SUB_INDEX_3, 100);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CANOPEN_TPDO2_COMM_1801, SUB_INDEX_3, 100);
|
||||||
|
|
||||||
// 4 : 配置周期发送时间 unit : ms
|
// 4 : 配置周期发送时间 unit : ms
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, TPDO2_COMM_1801, SUB_INDEX_5, 0);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CANOPEN_TPDO2_COMM_1801, SUB_INDEX_5, 0);
|
||||||
|
|
||||||
// 5 :映射当前位置
|
// 5 :映射当前位置
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO2_MAP_1A01, SUB_INDEX_1,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO2_MAP_1A01, SUB_INDEX_1,
|
||||||
ACTUAL_POSITION_6064 << 16 | SUB_INDEX_0 << 8 | 32);
|
CIA402_ACTUAL_POSITION_6064 << 16 | SUB_INDEX_0 << 8 | 32);
|
||||||
|
|
||||||
//6 : 映射当前速度
|
//6 : 映射当前速度
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO2_MAP_1A01, SUB_INDEX_2,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO2_MAP_1A01, SUB_INDEX_2,
|
||||||
ACTUAL_SPEED_606C << 16 | SUB_INDEX_0 << 8 | 32);
|
CIA402_ACTUAL_VELOCITY_606C << 16 | SUB_INDEX_0 << 8 | 32);
|
||||||
|
|
||||||
//9 写入该PDO映射对象总个数
|
//9 写入该PDO映射对象总个数
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, TPDO2_MAP_1A01, SUB_INDEX_0, 2);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_TPDO2_MAP_1A01, SUB_INDEX_0, 2);
|
||||||
|
|
||||||
//10 使能
|
//10 使能
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TPDO2_COMM_1801, SUB_INDEX_1, cob_id | (0U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_TPDO2_COMM_1801, SUB_INDEX_1, cob_id | (0U << 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::configRPDO1(uint8_t node_id, bool enable) {
|
void Ti5MotorCanopenProtocol::configRPDO1(uint8_t node_id, bool enable) {
|
||||||
// 1: 失能 pdo
|
// 1: 失能 pdo
|
||||||
uint32_t cob_id = RPDO1_BASE_ID_200 + node_id;
|
uint32_t cob_id = RPDO1_BASE_ID_200 + node_id;
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO1_COMM_1400, SUB_INDEX_1, cob_id | (1U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO1_COMM_1400, SUB_INDEX_1, cob_id | (1U << 31));
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO1_MAP_1600, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO1_MAP_1600, SUB_INDEX_0, 0);
|
||||||
|
|
||||||
// 2: 配置为
|
// 2: 配置为
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO1_COMM_1400, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO1_COMM_1400, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
||||||
|
|
||||||
// // 3:配置约束时间 unit:0.1ms
|
// // 3:配置约束时间 unit:0.1ms
|
||||||
// seedSdoRequest(node_id,CS_WRITE_TWO_BYTES,RPDO1_COMM_1400,SUB_INDEX_3,10);
|
// seedSdoRequest(node_id,CS_WRITE_TWO_BYTES,CANOPEN_RPDO1_COMM_1400,SUB_INDEX_3,10);
|
||||||
//
|
//
|
||||||
// // 4 : 配置周期发送时间 unit : ms 0 为 数据改变时发送
|
// // 4 : 配置周期发送时间 unit : ms 0 为 数据改变时发送
|
||||||
// seedSdoRequest(node_id,CS_WRITE_TWO_BYTES,RPDO1_COMM_1400,SUB_INDEX_5,0);
|
// seedSdoRequest(node_id,CS_WRITE_TWO_BYTES,CANOPEN_RPDO1_COMM_1400,SUB_INDEX_5,0);
|
||||||
|
|
||||||
// 5 :映射位置
|
// 5 :映射位置
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO1_MAP_1600, SUB_INDEX_1,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO1_MAP_1600, SUB_INDEX_1,
|
||||||
TARGET_POSITION_607A << 16 | SUB_INDEX_0 << 8 | 32);
|
CIA402_TARGET_POSITION_607A << 16 | SUB_INDEX_0 << 8 | 32);
|
||||||
|
|
||||||
//6 : 映射控制字
|
//6 : 映射控制字
|
||||||
|
|
||||||
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO1_MAP_1600, SUB_INDEX_2,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO1_MAP_1600, SUB_INDEX_2,
|
||||||
PROFILE_SPEED_6081 << 16 | SUB_INDEX_0 << 8 | 32);
|
CIA402_PROFILE_VELOCITY_6081 << 16 | SUB_INDEX_0 << 8 | 32);
|
||||||
|
|
||||||
|
|
||||||
//7 写入该PDO映射对象总个数
|
//7 写入该PDO映射对象总个数
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO1_MAP_1600, SUB_INDEX_0, 2);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO1_MAP_1600, SUB_INDEX_0, 2);
|
||||||
|
|
||||||
//8 使能
|
//8 使能
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO1_COMM_1400, SUB_INDEX_1, cob_id | (0U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO1_COMM_1400, SUB_INDEX_1, cob_id | (0U << 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::configRPDO2(uint8_t node_id, bool enable) {
|
void Ti5MotorCanopenProtocol::configRPDO2(uint8_t node_id, bool enable) {
|
||||||
// 1: 失能 pdo
|
// 1: 失能 pdo
|
||||||
uint32_t cob_id = RPDO2_BASE_ID_300 + node_id;
|
uint32_t cob_id = RPDO2_BASE_ID_300 + node_id;
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO2_COMM_1401, SUB_INDEX_1, cob_id | (1U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO2_COMM_1401, SUB_INDEX_1, cob_id | (1U << 31));
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO2_MAP_1601, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO2_MAP_1601, SUB_INDEX_0, 0);
|
||||||
|
|
||||||
if (!enable) return;
|
if (!enable) return;
|
||||||
|
|
||||||
// 2: 配置为
|
// 2: 配置为
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO2_COMM_1401, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO2_COMM_1401, SUB_INDEX_2, SYNC_EVENT_DRIVEN);
|
||||||
|
|
||||||
|
|
||||||
// 5 :映射位置
|
// 5 :映射位置
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO2_MAP_1601, SUB_INDEX_1,
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO2_MAP_1601, SUB_INDEX_1,
|
||||||
TARGET_SPEED_60FF << 16 | SUB_INDEX_0 << 8 | 32);
|
CIA402_TARGET_VELOCITY_60FF << 16 | SUB_INDEX_0 << 8 | 32);
|
||||||
|
|
||||||
|
|
||||||
//7 写入该PDO映射对象总个数
|
//7 写入该PDO映射对象总个数
|
||||||
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, RPDO2_MAP_1601, SUB_INDEX_0, 1);
|
seedSdoRequest(node_id, CS_WRITE_ONE_BYTE, CANOPEN_RPDO2_MAP_1601, SUB_INDEX_0, 1);
|
||||||
|
|
||||||
//8 使能
|
//8 使能
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, RPDO2_COMM_1401, SUB_INDEX_1, cob_id | (0U << 31));
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_RPDO2_COMM_1401, SUB_INDEX_1, cob_id | (0U << 31));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -405,36 +489,49 @@ void Ti5MotorCanopenProtocol::configPdo(uint8_t node_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) {
|
void Ti5MotorCanopenProtocol::setLimitQdd(uint8_t node_id, double u_qdd, double l_qdd) {
|
||||||
auto accel = ((u_qdd * RADTODEG) * GearRatio * 100.0) / 360.0 / 1000.0;
|
const auto* conversion = conversionForNode(node_id);
|
||||||
auto decel = ((l_qdd * RADTODEG) * GearRatio * 100.0) / 360.0 / 1000.0;
|
if (!conversion) {
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_ACCELERATION_6083, SUB_INDEX_0, std::abs(accel));
|
return;
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_DECELERATION_6084, SUB_INDEX_0, std::abs(decel));
|
}
|
||||||
|
auto accel = ((u_qdd * RADTODEG) * conversion->gear_ratio * Ti5VelocityUnitScale) /
|
||||||
|
360.0 / Ti5AccelerationTimeScale;
|
||||||
|
auto decel = ((l_qdd * RADTODEG) * conversion->gear_ratio * Ti5VelocityUnitScale) /
|
||||||
|
360.0 / Ti5AccelerationTimeScale;
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_ACCELERATION_6083, SUB_INDEX_0, std::abs(accel));
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_PROFILE_DECELERATION_6084, SUB_INDEX_0, std::abs(decel));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setLimitQd(uint8_t node_id, double qd) {
|
void Ti5MotorCanopenProtocol::setLimitQd(uint8_t node_id, double qd) {
|
||||||
auto speed = ((qd * RADTODEG) * GearRatio * 100.0) / 360.0;
|
const auto* conversion = conversionForNode(node_id);
|
||||||
// seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, MAX_SPEED_607F, SUB_INDEX_0, speed);
|
if (!conversion) {
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_SPEED_6081, SUB_INDEX_0, speed);
|
return;
|
||||||
|
}
|
||||||
|
auto speed = radPerSecToVelocityRaw(qd, *conversion);
|
||||||
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_MAX_PROFILE_VELOCITY_607F, SUB_INDEX_0, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setLimitQ(uint8_t node_id, double ub, double lb) {
|
void Ti5MotorCanopenProtocol::setLimitQ(uint8_t node_id, double ub, double lb) {
|
||||||
ub = (ub * RADTODEG) / 360.0 * GearRatio * 65536.0;
|
const auto* conversion = conversionForNode(node_id);
|
||||||
lb = (lb * RADTODEG) / 360.0 * GearRatio * 65536.0;
|
if (!conversion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ub = radToCounts(ub, *conversion);
|
||||||
|
lb = radToCounts(lb, *conversion);
|
||||||
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, SOFTWARE_POSITION_LIMIT_607D, SUB_INDEX_1, lb);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_SOFTWARE_POSITION_LIMIT_607D, SUB_INDEX_1, lb);
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, SOFTWARE_POSITION_LIMIT_607D, SUB_INDEX_2, ub);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_SOFTWARE_POSITION_LIMIT_607D, SUB_INDEX_2, ub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Ti5MotorCanopenProtocol::calibrateZeroQ(uint8_t node_id) {
|
bool Ti5MotorCanopenProtocol::calibrateZeroQ(uint8_t node_id) {
|
||||||
// 0: 设置控制字为 0x06,确保停机状态
|
// 0: 设置控制字为 0x06,确保停机状态
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x06, 1000);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x06, 1000);
|
||||||
|
|
||||||
// 1: 清除偏置值 0x2008 ← 0
|
// 1: 清除偏置值 0x2008 ← 0
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, POSITION_OFFSET_2008, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_POSITION_OFFSET_2008, SUB_INDEX_0, 0);
|
||||||
|
|
||||||
// 2: 等待确认清除成功
|
// 2: 等待确认清除成功
|
||||||
seedSdoRequest(node_id, CS_READ_REQUEST, POSITION_OFFSET_2008, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_READ_REQUEST, CANOPEN_POSITION_OFFSET_2008, SUB_INDEX_0, 0);
|
||||||
if (!waitUntil([&]() {
|
if (!waitUntil([&]() {
|
||||||
return GetRobotDetail()->motors().at(node_id).position_offset() == 0;
|
return GetRobotDetail()->motors().at(node_id).position_offset() == 0;
|
||||||
}, 1000)) {
|
}, 1000)) {
|
||||||
@ -443,18 +540,18 @@ bool Ti5MotorCanopenProtocol::calibrateZeroQ(uint8_t node_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3: 读取当前位置 0x6064
|
// 3: 读取当前位置 0x6064
|
||||||
seedSdoRequest(node_id, CS_READ_REQUEST, ACTUAL_POSITION_6064, SUB_INDEX_0, 0, 20);
|
seedSdoRequest(node_id, CS_READ_REQUEST, CIA402_ACTUAL_POSITION_6064, SUB_INDEX_0, 0, 20);
|
||||||
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
auto cur_pos = GetRobotDetail()->motors().at(node_id).position();
|
||||||
|
|
||||||
// 4: 将当前位置写入偏置寄存器
|
// 4: 将当前位置写入偏置寄存器
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, POSITION_OFFSET_2008, SUB_INDEX_0, cur_pos);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_POSITION_OFFSET_2008, SUB_INDEX_0, cur_pos);
|
||||||
|
|
||||||
// 5: 保存参数到永久区(0x2000 ← 1)
|
// 5: 保存参数到永久区(0x2000 ← 1)
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, USER_SAVE_PARA_2000, SUB_INDEX_0, 1, 100);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CANOPEN_USER_SAVE_PARA_2000, SUB_INDEX_0, 1, 100);
|
||||||
|
|
||||||
|
|
||||||
// 6: 确认写入成功
|
// 6: 确认写入成功
|
||||||
seedSdoRequest(node_id, CS_READ_REQUEST, POSITION_OFFSET_2008, SUB_INDEX_0, 0, 20);
|
seedSdoRequest(node_id, CS_READ_REQUEST, CANOPEN_POSITION_OFFSET_2008, SUB_INDEX_0, 0, 20);
|
||||||
if (!waitUntil([&]() {
|
if (!waitUntil([&]() {
|
||||||
return GetRobotDetail()->motors().at(node_id).position_offset() == cur_pos;
|
return GetRobotDetail()->motors().at(node_id).position_offset() == cur_pos;
|
||||||
}, 500)) {
|
}, 500)) {
|
||||||
@ -465,16 +562,28 @@ bool Ti5MotorCanopenProtocol::calibrateZeroQ(uint8_t node_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::brake(uint8_t node_id) {
|
bool Ti5MotorCanopenProtocol::torqueOn(uint8_t node_id) {
|
||||||
|
setMode(node_id, msgs::RUN_MODE_CYCLIC_SYNC_POSITION);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Ti5MotorCanopenProtocol::brakeRelease(uint8_t node_id) {
|
||||||
|
(void)node_id;
|
||||||
|
CMVR_LOG(ERROR) << "[Ti5MotorCanopenProtocol] brakeRelease is not implemented";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Ti5MotorCanopenProtocol::quickStop(uint8_t node_id) {
|
||||||
// // 开机未使能电机时调用
|
// // 开机未使能电机时调用
|
||||||
// seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
// seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
||||||
// 6 抱闸 0 : 立即停机 自由
|
// 6 抱闸 0 : 立即停机 自由
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, QUICK_STOP_DECEL_6085, SUB_INDEX_0, 0XFFFFFFF0);
|
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, CIA402_QUICK_STOP_DECELERATION_6085, SUB_INDEX_0, 0XFFFFFFF0);
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, QUICK_STOP_OPTION_605A, SUB_INDEX_0, 6);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_QUICK_STOP_OPTION_605A, SUB_INDEX_0, 6);
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x02, 100);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x02, 100);
|
||||||
|
|
||||||
// 必须要发送 0xf 才能按照6085中设定的减速度减速
|
// 必须要发送 0xf 才能按照6085中设定的减速度减速
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ti5MotorCanopenProtocol::reachedTargetQ(uint8_t node_id) {
|
bool Ti5MotorCanopenProtocol::reachedTargetQ(uint8_t node_id) {
|
||||||
@ -483,62 +592,37 @@ bool Ti5MotorCanopenProtocol::reachedTargetQ(uint8_t node_id) {
|
|||||||
return st.target_reached == 1;
|
return st.target_reached == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setQd(uint8_t node_id, double qd) {
|
bool Ti5MotorCanopenProtocol::torqueOff(uint8_t node_id) {
|
||||||
auto speed = ((qd * RADTODEG) * GearRatio * 100.0) / 360.0;
|
|
||||||
switch (getMode(node_id)) {
|
|
||||||
case msgs::RUN_MODE_CYCLIC_SYNC_POSITION:
|
|
||||||
case msgs::RUN_MODE_PROFILE_POSITION: {
|
|
||||||
auto it = last_Qd_.find(node_id);
|
|
||||||
if (it == last_Qd_.end() || it->second != speed) {
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_SPEED_6081, SUB_INDEX_0, uint32_t(std::abs(speed)),
|
|
||||||
0);
|
|
||||||
last_Qd_[node_id] = speed;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case msgs::RUN_MODE_PROFILE_VELOCITY:
|
|
||||||
case msgs::RUN_MODE_CYCLIC_SYNC_VELOCITY: {
|
|
||||||
// 在速度模式下,直接设置目标速度
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, TARGET_SPEED_60FF, SUB_INDEX_0, uint32_t(speed), 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::setQdd(uint8_t node_id, double qdd) {
|
|
||||||
uint32_t accel = ((std::abs(qdd) * RADTODEG) * GearRatio * 100.0 * 65536.0) / (360.0 * 1000.0);
|
|
||||||
auto it = last_Qdd_.find(node_id);
|
|
||||||
if (it == last_Qdd_.end() || it->second != accel) {
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_ACCELERATION_6083, SUB_INDEX_0, accel);
|
|
||||||
seedSdoRequest(node_id, CS_WRITE_FOUR_BYTES, PROFILE_DECELERATION_6084, SUB_INDEX_0, accel);
|
|
||||||
last_Qdd_[node_id] = accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Ti5MotorCanopenProtocol::torqueOff(uint8_t node_id) {
|
|
||||||
// 0 : 立即停机 自由
|
// 0 : 立即停机 自由
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, QUICK_STOP_OPTION_605A, SUB_INDEX_0, 0);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_QUICK_STOP_OPTION_605A, SUB_INDEX_0, 0);
|
||||||
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x02, 20);
|
seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x02, 20);
|
||||||
// 必须要发送 0xf 才能按照6085中设定的减速度减速
|
// 必须要发送 0xf 才能按照6085中设定的减速度减速
|
||||||
// seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
// seedSdoRequest(node_id, CS_WRITE_TWO_BYTES, CIA402_CONTROL_WORD_6040, SUB_INDEX_0, 0x0F);
|
||||||
|
|
||||||
// 停机之后,要重新使能?
|
// 停机之后,要重新使能?
|
||||||
// cur_mode_[node_id] = msgs::RUN_MODE_UNSPECIFIED;
|
// cur_mode_[node_id] = msgs::RUN_MODE_UNSPECIFIED;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
double Ti5MotorCanopenProtocol::getQ(uint8_t node_id) {
|
double Ti5MotorCanopenProtocol::getQ(uint8_t node_id) {
|
||||||
|
const auto* conversion = conversionForNode(node_id);
|
||||||
|
if (!conversion) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
auto data_ptr = std::make_unique<msgs::RobotDetail>();
|
auto data_ptr = std::make_unique<msgs::RobotDetail>();
|
||||||
message_manager_->GetSensorData(data_ptr.get());
|
message_manager_->GetSensorData(data_ptr.get());
|
||||||
auto cnt = data_ptr->motors().at(node_id).position();
|
auto cnt = data_ptr->motors().at(node_id).position();
|
||||||
return (cnt * 360.0) / (GearRatio * 65536.0 * RADTODEG);
|
return countsToRad(cnt, *conversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
double Ti5MotorCanopenProtocol::getQd(uint8_t node_id) {
|
double Ti5MotorCanopenProtocol::getQd(uint8_t node_id) {
|
||||||
|
const auto* conversion = conversionForNode(node_id);
|
||||||
|
if (!conversion) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
auto data_ptr = std::make_unique<msgs::RobotDetail>();
|
auto data_ptr = std::make_unique<msgs::RobotDetail>();
|
||||||
message_manager_->GetSensorData(data_ptr.get());
|
message_manager_->GetSensorData(data_ptr.get());
|
||||||
auto cnt = data_ptr->motors().at(node_id).speed();
|
auto cnt = data_ptr->motors().at(node_id).speed();
|
||||||
return (cnt * 360.0) / (GearRatio * 100.0 * RADTODEG);
|
return velocityRawToRadPerSec(cnt, *conversion);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ target_link_libraries(motor_manager
|
|||||||
PRIVATE
|
PRIVATE
|
||||||
cmvr_es::device::ti5_canopen_motor_driver
|
cmvr_es::device::ti5_canopen_motor_driver
|
||||||
cmvr_es::device::mujoco_motor_driver
|
cmvr_es::device::mujoco_motor_driver
|
||||||
|
cmvr_es::device::ethercat_motor_driver
|
||||||
cmvr_es::ik_solver
|
cmvr_es::ik_solver
|
||||||
glog
|
glog
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,21 +1,27 @@
|
|||||||
#include "motor/manager/include/motor_manager.h"
|
#include "devices/motor/manager/include/motor_manager.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
|
#include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
|
||||||
#include "common/base/logging/logger.h"
|
#include "common/base/logging/logger.h"
|
||||||
#include "common/config/config_files.h"
|
#include "common/config/config_files.h"
|
||||||
#include "../../bus_runtime/abstract_motor_bus_runtime.h"
|
#include "devices/motor/bus_runtime/abstract_motor_bus_runtime.h"
|
||||||
#include "motor/bus_runtime/can/include/can_motor_bus_runtime.h"
|
#include "devices/motor/bus_runtime/can/include/can_motor_bus_runtime.h"
|
||||||
#include "motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
#include "devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h"
|
||||||
#include "motor/bus_runtime/mujoco/include/mujoco_motor_bus_runtime.h"
|
#include "devices/motor/bus_runtime/mujoco/include/mujoco_motor_bus_runtime.h"
|
||||||
#include "motor/drivers/mujoco/include/mujoco_motor.h"
|
#include "devices/motor/drivers/ethercat_motor/include/cia402/cia402_protocol.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/ti5_motor.h"
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h"
|
||||||
#include "motor/drivers/ti5_canopen/include/ti5_motor_canopen_protocol.h"
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h"
|
||||||
|
#include "devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h"
|
||||||
|
#include "devices/motor/drivers/mujoco/include/mujoco_motor.h"
|
||||||
|
#include "devices/motor/drivers/ti5_canopen/include/ti5_motor.h"
|
||||||
|
#include "devices/motor/drivers/ti5_canopen/include/ti5_motor_canopen_protocol.h"
|
||||||
|
|
||||||
namespace cmvr::device {
|
namespace cmvr::device {
|
||||||
|
|
||||||
@ -91,11 +97,17 @@ bool MotorManager::init()
|
|||||||
all_ok = false;
|
all_ok = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!bus_runtime->start()) {
|
|
||||||
|
const bool start_before_motor_init =
|
||||||
|
motor_group_cfg.bus_type() == config::MOTOR_BUS_ETHERCAT;
|
||||||
|
if (start_before_motor_init && !bus_runtime->start()) {
|
||||||
bus_runtime->stop();
|
bus_runtime->stop();
|
||||||
all_ok = false;
|
all_ok = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (start_before_motor_init) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
}
|
||||||
|
|
||||||
auto motors = createMotors_(motor_group_cfg, selected_motor_cfgs, bus_runtime);
|
auto motors = createMotors_(motor_group_cfg, selected_motor_cfgs, bus_runtime);
|
||||||
if (motors.empty()) {
|
if (motors.empty()) {
|
||||||
@ -116,6 +128,11 @@ bool MotorManager::init()
|
|||||||
all_ok = false;
|
all_ok = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!start_before_motor_init && !bus_runtime->start()) {
|
||||||
|
bus_runtime->stop();
|
||||||
|
all_ok = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bus_runtimes_.push_back(std::move(bus_runtime));
|
bus_runtimes_.push_back(std::move(bus_runtime));
|
||||||
}
|
}
|
||||||
@ -398,8 +415,19 @@ std::shared_ptr<AbstractMotorBusRuntime> MotorManager::createBusRuntime_(
|
|||||||
return std::make_shared<CanMotorBusRuntime>();
|
return std::make_shared<CanMotorBusRuntime>();
|
||||||
case config::MOTOR_BUS_MUJOCO:
|
case config::MOTOR_BUS_MUJOCO:
|
||||||
return std::make_shared<MujocoMotorBusRuntime>();
|
return std::make_shared<MujocoMotorBusRuntime>();
|
||||||
case config::MOTOR_BUS_ETHERCAT:
|
case config::MOTOR_BUS_ETHERCAT: {
|
||||||
return std::make_shared<EthercatMotorBusRuntime>();
|
auto runtime = std::make_shared<EthercatMotorBusRuntime>();
|
||||||
|
if (group_cfg.vendor() == config::MOTOR_VENDOR_EYOU &&
|
||||||
|
group_cfg.protocol() == config::MOTOR_PROTOCOL_ETHERCAT_CIA402) {
|
||||||
|
runtime->setPdoMapping(createEyouCia402PdoMapping());
|
||||||
|
return runtime;
|
||||||
|
}
|
||||||
|
CMVR_LOG(ERROR) << "[MotorManager] unsupported EtherCAT motor: vendor="
|
||||||
|
<< config::MotorVendor_Name(group_cfg.vendor())
|
||||||
|
<< ", protocol=" << config::MotorProtocol_Name(group_cfg.protocol())
|
||||||
|
<< ", group=" << group_cfg.id();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
CMVR_LOG(ERROR) << "[MotorManager] unsupported motor bus type: "
|
CMVR_LOG(ERROR) << "[MotorManager] unsupported motor bus type: "
|
||||||
<< config::MotorBusType_Name(group_cfg.bus_type())
|
<< config::MotorBusType_Name(group_cfg.bus_type())
|
||||||
@ -534,6 +562,14 @@ std::vector<std::shared_ptr<AbstractMotor>> MotorManager::createEthercatMotors_(
|
|||||||
CMVR_LOG(ERROR) << "[MotorManager] missing EtherCAT config: " << group_cfg.id();
|
CMVR_LOG(ERROR) << "[MotorManager] missing EtherCAT config: " << group_cfg.id();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
if (group_cfg.vendor() != config::MOTOR_VENDOR_EYOU ||
|
||||||
|
group_cfg.protocol() != config::MOTOR_PROTOCOL_ETHERCAT_CIA402) {
|
||||||
|
CMVR_LOG(ERROR) << "[MotorManager] unsupported EtherCAT motor: vendor="
|
||||||
|
<< config::MotorVendor_Name(group_cfg.vendor())
|
||||||
|
<< ", protocol=" << config::MotorProtocol_Name(group_cfg.protocol())
|
||||||
|
<< ", group=" << group_cfg.id();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
for (const auto& motor_cfg : motor_cfgs) {
|
for (const auto& motor_cfg : motor_cfgs) {
|
||||||
if (!ethercat_bus_runtime->slaveForMotor(motor_cfg.id())) {
|
if (!ethercat_bus_runtime->slaveForMotor(motor_cfg.id())) {
|
||||||
CMVR_LOG(ERROR) << "[MotorManager] missing EtherCAT slave config for motor id "
|
CMVR_LOG(ERROR) << "[MotorManager] missing EtherCAT slave config for motor id "
|
||||||
@ -542,11 +578,22 @@ std::vector<std::shared_ptr<AbstractMotor>> MotorManager::createEthercatMotors_(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CMVR_LOG(ERROR) << "[MotorManager] EtherCAT motor creation is not implemented: vendor="
|
auto protocol = std::make_shared<Cia402Protocol>(
|
||||||
<< config::MotorVendor_Name(group_cfg.vendor())
|
ethercat_bus_runtime, group_cfg.ethercat().cia402());
|
||||||
<< ", protocol=" << config::MotorProtocol_Name(group_cfg.protocol())
|
|
||||||
<< ", group=" << group_cfg.id();
|
std::vector<std::shared_ptr<AbstractMotor>> motors;
|
||||||
|
motors.reserve(motor_cfgs.size());
|
||||||
|
for (const auto& cfg : motor_cfgs) {
|
||||||
|
auto motor = std::make_shared<EyouMotor>(
|
||||||
|
cfg, protocol, std::make_unique<EyouMotorAdapter>(ethercat_bus_runtime));
|
||||||
|
if (!motor->init()) {
|
||||||
|
CMVR_LOG(ERROR) << "[MotorManager] failed to init EYOU EtherCAT motor: "
|
||||||
|
<< cfg.joint_name();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
motors.push_back(std::move(motor));
|
||||||
|
}
|
||||||
|
return motors;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace cmvr::device
|
} // namespace cmvr::device
|
||||||
|
|||||||
@ -15,7 +15,8 @@ namespace cmvr {
|
|||||||
public:
|
public:
|
||||||
enum class CommProto : uint8_t {
|
enum class CommProto : uint8_t {
|
||||||
CANOPEN = 1,
|
CANOPEN = 1,
|
||||||
CUSTOM = 2
|
ETHERCAT = 2,
|
||||||
|
CUSTOM = 3
|
||||||
};
|
};
|
||||||
virtual ~MotorProtocolInterface() = default;
|
virtual ~MotorProtocolInterface() = default;
|
||||||
|
|
||||||
@ -26,9 +27,6 @@ namespace cmvr {
|
|||||||
*/
|
*/
|
||||||
virtual bool initNode(uint8_t node_id) = 0;
|
virtual bool initNode(uint8_t node_id) = 0;
|
||||||
|
|
||||||
virtual void setQ(uint8_t node_id, double angle_rad) = 0;
|
|
||||||
virtual void setTarget(uint8_t node_id, double angle_rad,double vel) = 0;
|
|
||||||
virtual void setTarget(uint8_t node_id,double vel) = 0;
|
|
||||||
virtual void setMode(uint8_t node_id,msgs::RunMode mode ) = 0;
|
virtual void setMode(uint8_t node_id,msgs::RunMode mode ) = 0;
|
||||||
virtual msgs::RunMode getMode(uint8_t node_id) = 0;
|
virtual msgs::RunMode getMode(uint8_t node_id) = 0;
|
||||||
virtual void setLimitQdd(uint8_t node_id, double u_qdd,double l_qdd) = 0;
|
virtual void setLimitQdd(uint8_t node_id, double u_qdd,double l_qdd) = 0;
|
||||||
@ -36,12 +34,35 @@ namespace cmvr {
|
|||||||
virtual void setLimitQ(uint8_t node_id, double ub, double lb) = 0;
|
virtual void setLimitQ(uint8_t node_id, double ub, double lb) = 0;
|
||||||
virtual bool calibrateZeroQ(uint8_t node_id) = 0;
|
virtual bool calibrateZeroQ(uint8_t node_id) = 0;
|
||||||
virtual bool reachedTargetQ(uint8_t node_id) = 0;
|
virtual bool reachedTargetQ(uint8_t node_id) = 0;
|
||||||
virtual void setQd(uint8_t node_id, double qd) = 0;
|
// target_q: rad, max_qd: rad/s, max_qdd: rad/s^2.
|
||||||
virtual void setQdd(uint8_t node_id,double qdd) = 0;
|
// Profile Position 写入目标位置和轮廓速度/加速度,并触发一次新目标。
|
||||||
// virtual void setVelocity(uint8_t node_id, double velocity) = 0;
|
virtual bool commandProfilePosition(uint8_t node_id,
|
||||||
// virtual void clearError(uint8_t node_id) = 0;
|
double target_q,
|
||||||
virtual void brake(uint8_t node_id) = 0;
|
double max_qd,
|
||||||
virtual void torqueOff(uint8_t node_id) = 0;
|
double max_qdd) = 0;
|
||||||
|
// target_qd: rad/s, max_qdd: rad/s^2.
|
||||||
|
// Profile Velocity 写入目标速度和轮廓加速度。
|
||||||
|
virtual bool commandProfileVelocity(uint8_t node_id,
|
||||||
|
double target_qd,
|
||||||
|
double max_qdd) = 0;
|
||||||
|
// target_q: rad, target_qd: rad/s.
|
||||||
|
// Cyclic Position 周期写入目标位置和目标速度。
|
||||||
|
virtual bool commandCyclicPosition(uint8_t node_id,
|
||||||
|
double target_q,
|
||||||
|
double target_qd) = 0;
|
||||||
|
// target_qd: rad/s.
|
||||||
|
// Cyclic Velocity 周期写入目标速度。
|
||||||
|
virtual bool commandCyclicVelocity(uint8_t node_id,
|
||||||
|
double target_qd) = 0;
|
||||||
|
// target_tau: N*m.
|
||||||
|
virtual bool commandCyclicTorque(uint8_t node_id, double target_tau) = 0;
|
||||||
|
virtual void setMotorConversion(uint8_t node_id,
|
||||||
|
double encoder_counts_per_rev,
|
||||||
|
double gear_ratio) = 0;
|
||||||
|
virtual bool torqueOn(uint8_t node_id) = 0;
|
||||||
|
virtual bool torqueOff(uint8_t node_id) = 0;
|
||||||
|
virtual bool brakeRelease(uint8_t node_id) = 0;
|
||||||
|
virtual bool quickStop(uint8_t node_id) = 0;
|
||||||
|
|
||||||
virtual double getQ(uint8_t node_id) = 0;
|
virtual double getQ(uint8_t node_id) = 0;
|
||||||
virtual double getQd(uint8_t node_id) = 0;
|
virtual double getQd(uint8_t node_id) = 0;
|
||||||
|
|||||||
@ -26,6 +26,9 @@ using namespace cmvr::device;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using GroupJointSelection = std::unordered_map<std::string, std::unordered_set<std::string>>;
|
||||||
|
using MotorJointSelections = std::unordered_map<std::string, GroupJointSelection>;
|
||||||
|
|
||||||
void logSection(const char* title)
|
void logSection(const char* title)
|
||||||
{
|
{
|
||||||
CMVR_LOG(INFO) << "---------------- " << title << " ----------------";
|
CMVR_LOG(INFO) << "---------------- " << title << " ----------------";
|
||||||
@ -71,6 +74,32 @@ bool motorGroupHasJoint(const cmvr::config::MotorGroupConfig& motor_group,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addAllMotorJoints(const std::string& motor_system_id,
|
||||||
|
const cmvr::config::MotorRootConfig& root_cfg,
|
||||||
|
MotorJointSelections& selections)
|
||||||
|
{
|
||||||
|
auto& group_selection = selections[motor_system_id];
|
||||||
|
for (const auto& motor_group : root_cfg.motor().motor_groups()) {
|
||||||
|
if (motor_group.id().empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& selected_joints = group_selection[motor_group.id()];
|
||||||
|
for (const auto& motor : motor_group.motors().motors()) {
|
||||||
|
if (!motor.joint_name().empty()) {
|
||||||
|
selected_joints.insert(motor.joint_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selected_joints.empty()) {
|
||||||
|
group_selection.erase(motor_group.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group_selection.empty()) {
|
||||||
|
selections.erase(motor_system_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
template std::shared_ptr<AbstractAGV> DeviceManager::getDevice(const std::string& device_id);
|
template std::shared_ptr<AbstractAGV> DeviceManager::getDevice(const std::string& device_id);
|
||||||
@ -256,8 +285,7 @@ void DeviceManager::log_device_plan_() const
|
|||||||
|
|
||||||
void DeviceManager::pre_scan_robot_arm_dependencies_() const
|
void DeviceManager::pre_scan_robot_arm_dependencies_() const
|
||||||
{
|
{
|
||||||
using GroupJointSelection = std::unordered_map<std::string, std::unordered_set<std::string>>;
|
MotorJointSelections selections;
|
||||||
std::unordered_map<std::string, GroupJointSelection> selections;
|
|
||||||
std::unordered_map<std::string, config::MotorRootConfig> motor_roots;
|
std::unordered_map<std::string, config::MotorRootConfig> motor_roots;
|
||||||
|
|
||||||
for (const auto& entry : cfg_.devices()) {
|
for (const auto& entry : cfg_.devices()) {
|
||||||
@ -387,6 +415,15 @@ void DeviceManager::pre_scan_robot_arm_dependencies_() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selections.empty() && cfg_.init_all_motors_when_no_active_joints()) {
|
||||||
|
CMVR_LOG(INFO) << "[DeviceManager]: No active motor joints from RobotArm; "
|
||||||
|
<< "initialize all configured motors because "
|
||||||
|
<< "init_all_motors_when_no_active_joints=true";
|
||||||
|
for (const auto& [motor_system_id, root_cfg] : motor_roots) {
|
||||||
|
addAllMotorJoints(motor_system_id, root_cfg, selections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotorManager::clearActiveJoints();
|
MotorManager::clearActiveJoints();
|
||||||
for (auto& [motor_system_id, group_selection] : selections) {
|
for (auto& [motor_system_id, group_selection] : selections) {
|
||||||
MotorManager::setActiveJoints(motor_system_id, std::move(group_selection));
|
MotorManager::setActiveJoints(motor_system_id, std::move(group_selection));
|
||||||
|
|||||||
BIN
dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat
vendored
Executable file
BIN
dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat
vendored
Executable file
Binary file not shown.
3
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf
vendored
Normal file
3
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
MASTER0_DEVICE="a0:ad:9f:c4:c2:2c"
|
||||||
|
DEVICE_MODULES="generic"
|
||||||
|
UPDOWN_INTERFACES="eno1"
|
||||||
113
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_140412
vendored
Normal file
113
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_140412
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# EtherCAT master configuration file for use with ethercatctl.
|
||||||
|
#
|
||||||
|
# vim: spelllang=en spell tw=78
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main Ethernet devices.
|
||||||
|
#
|
||||||
|
# The MASTER<X>_DEVICE variable specifies the Ethernet device for a master
|
||||||
|
# with index 'X'.
|
||||||
|
#
|
||||||
|
# Specify the MAC address (hexadecimal with colons) of the Ethernet device to
|
||||||
|
# use. Example: "00:00:08:44:ab:66"
|
||||||
|
#
|
||||||
|
# Alternatively, a network interface name can be specified. The interface
|
||||||
|
# name will be resolved to a MAC address using the 'ip' command.
|
||||||
|
# Example: "eth0"
|
||||||
|
#
|
||||||
|
# The broadcast address "ff:ff:ff:ff:ff:ff" has a special meaning: It tells
|
||||||
|
# the master to accept the first device offered by any Ethernet driver.
|
||||||
|
#
|
||||||
|
# The MASTER<X>_DEVICE variables also determine, how many masters will be
|
||||||
|
# created: A non-empty variable MASTER0_DEVICE will create one master, adding
|
||||||
|
# a non-empty variable MASTER1_DEVICE will create a second master, and so on.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# MASTER0_DEVICE="00:00:08:44:ab:66"
|
||||||
|
# MASTER0_DEVICE="eth0"
|
||||||
|
#
|
||||||
|
MASTER0_DEVICE=""
|
||||||
|
#MASTER1_DEVICE=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Backup Ethernet devices
|
||||||
|
#
|
||||||
|
# The MASTER<X>_BACKUP variables specify the devices used for redundancy. They
|
||||||
|
# behaves nearly the same as the MASTER<X>_DEVICE variable, except that it
|
||||||
|
# does not interpret the ff:ff:ff:ff:ff:ff address.
|
||||||
|
#
|
||||||
|
#MASTER0_BACKUP=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Ethernet driver modules to use for EtherCAT operation.
|
||||||
|
#
|
||||||
|
# Specify a non-empty list of Ethernet drivers, that shall be used for
|
||||||
|
# EtherCAT operation.
|
||||||
|
#
|
||||||
|
# Except for the generic Ethernet driver module, the init script will try to
|
||||||
|
# unload the usual Ethernet driver modules in the list and replace them with
|
||||||
|
# the EtherCAT-capable ones. If a certain (EtherCAT-capable) driver is not
|
||||||
|
# found, a warning will appear.
|
||||||
|
#
|
||||||
|
# Possible values: 8139too, e100, e1000, e1000e, r8169, generic, ccat, igb,
|
||||||
|
# igc, genet, dwmac-intel, stmmac-pci.
|
||||||
|
# Separate multiple drivers with spaces.
|
||||||
|
# A list of all matching kernel versions can be found here:
|
||||||
|
# https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html
|
||||||
|
#
|
||||||
|
# Note: The e100, e1000, e1000e, r8169, ccat, igb and igc drivers are not
|
||||||
|
# built by default. Enable them with the --enable-<driver> configure switches.
|
||||||
|
#
|
||||||
|
DEVICE_MODULES=""
|
||||||
|
|
||||||
|
# If you have any issues about network interfaces not being configured
|
||||||
|
# properly, systemd may need some additional infos about your setup.
|
||||||
|
# Have a look at the service file, you'll find some details there.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# List of interfaces to bring up and down automatically.
|
||||||
|
#
|
||||||
|
# Specify a space-separated list of interface names (such as eth0 or
|
||||||
|
# enp0s1) that shall be brought up on `ethercatctl start` and down on
|
||||||
|
# `ethercatctl stop`.
|
||||||
|
#
|
||||||
|
# When using the generic driver, the corresponding Ethernet device has to be
|
||||||
|
# activated before the master is started, otherwise all frames will time out.
|
||||||
|
# This the perfect use-case for `UPDOWN_INTERFACES`.
|
||||||
|
#
|
||||||
|
UPDOWN_INTERFACES=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Default SII caching method.
|
||||||
|
#
|
||||||
|
# Set the start-up caching method for all masters. The integer value
|
||||||
|
# determines, which fields are used to look up a cached SII page. It is a
|
||||||
|
# bit-field consisting of the following flags. A value of zero disables SII
|
||||||
|
# caching (default). A typical value is 7 (use vendor ID, product code and
|
||||||
|
# revision number for lookup).
|
||||||
|
#
|
||||||
|
# - 1: Vendor ID (always used)
|
||||||
|
# - 2: Product code (always used)
|
||||||
|
# - 4: Revision number
|
||||||
|
# - 8: Serial number
|
||||||
|
# - 16: Alias address
|
||||||
|
#
|
||||||
|
# Please keep in mind that in case serial number or alias address is enabled,
|
||||||
|
# only slaves with a non-zero serial number or alias benefit from caching.
|
||||||
|
#
|
||||||
|
SII_CACHING=0
|
||||||
|
|
||||||
|
#
|
||||||
|
# Flags for loading kernel modules.
|
||||||
|
#
|
||||||
|
# This can usually be left empty. Adjust this variable, if you have problems
|
||||||
|
# with module loading.
|
||||||
|
#
|
||||||
|
#MODPROBE_FLAGS="-b"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
3
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_155334
vendored
Normal file
3
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf.bak.20260702_155334
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
MASTER0_DEVICE="a0:ad:9f:c4:c2:2c"
|
||||||
|
DEVICE_MODULES="generic"
|
||||||
|
UPDOWN_INTERFACES="eno1"
|
||||||
123
dependency/x86/third_party/ethercat/v1.7.0/etc/init.d/ethercat
vendored
Executable file
123
dependency/x86/third_party/ethercat/v1.7.0/etc/init.d/ethercat
vendored
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Init script for EtherCAT
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006-2021 Florian Pose, Ingenieurgemeinschaft IgH
|
||||||
|
#
|
||||||
|
# This file is part of the IgH EtherCAT Master.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License version 2, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with the IgH EtherCAT Master; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# vim: expandtab
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: ethercat
|
||||||
|
# Required-Start: $local_fs $syslog $network
|
||||||
|
# Should-Start: $time ntp
|
||||||
|
# Required-Stop: $local_fs $syslog $network
|
||||||
|
# Should-Stop: $time ntp
|
||||||
|
# Default-Start: 3 5
|
||||||
|
# Default-Stop: 0 1 2 6
|
||||||
|
# Short-Description: EtherCAT master
|
||||||
|
# Description: EtherCAT master 1.7.0
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ETHERCATCTL="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl -c /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
exit_success() {
|
||||||
|
if [ -r /etc/rc.status ]; then
|
||||||
|
rc_reset
|
||||||
|
rc_status -v
|
||||||
|
rc_exit
|
||||||
|
else
|
||||||
|
echo " done"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
exit_fail() {
|
||||||
|
if [ -r /etc/rc.status ]; then
|
||||||
|
rc_failed
|
||||||
|
rc_status -v
|
||||||
|
rc_exit
|
||||||
|
else
|
||||||
|
echo " failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ -r /etc/rc.status ]; then
|
||||||
|
. /etc/rc.status
|
||||||
|
rc_reset
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${1}" in
|
||||||
|
|
||||||
|
start)
|
||||||
|
echo -n "Starting EtherCAT master 1.7.0 "
|
||||||
|
|
||||||
|
if $ETHERCATCTL start; then
|
||||||
|
exit_success
|
||||||
|
else
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
echo -n "Shutting down EtherCAT master 1.7.0 "
|
||||||
|
|
||||||
|
if $ETHERCATCTL stop; then
|
||||||
|
exit_success
|
||||||
|
else
|
||||||
|
exit_fail
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart)
|
||||||
|
$0 stop || exit 1
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
|
||||||
|
status)
|
||||||
|
$ETHERCATCTL status
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "USAGE: $0 {start|stop|restart|status}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -r /etc/rc.status ]; then
|
||||||
|
rc_exit
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
113
dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat
vendored
Normal file
113
dependency/x86/third_party/ethercat/v1.7.0/etc/sysconfig/ethercat
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# EtherCAT master configuration file for use with init.d.
|
||||||
|
#
|
||||||
|
# vim: spelllang=en spell tw=78
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main Ethernet devices.
|
||||||
|
#
|
||||||
|
# The MASTER<X>_DEVICE variable specifies the Ethernet device for a master
|
||||||
|
# with index 'X'.
|
||||||
|
#
|
||||||
|
# Specify the MAC address (hexadecimal with colons) of the Ethernet device to
|
||||||
|
# use. Example: "00:00:08:44:ab:66"
|
||||||
|
#
|
||||||
|
# Alternatively, a network interface name can be specified. The interface
|
||||||
|
# name will be resolved to a MAC address using the 'ip' command.
|
||||||
|
# Example: "eth0"
|
||||||
|
#
|
||||||
|
# The broadcast address "ff:ff:ff:ff:ff:ff" has a special meaning: It tells
|
||||||
|
# the master to accept the first device offered by any Ethernet driver.
|
||||||
|
#
|
||||||
|
# The MASTER<X>_DEVICE variables also determine, how many masters will be
|
||||||
|
# created: A non-empty variable MASTER0_DEVICE will create one master, adding
|
||||||
|
# a non-empty variable MASTER1_DEVICE will create a second master, and so on.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# MASTER0_DEVICE="00:00:08:44:ab:66"
|
||||||
|
# MASTER0_DEVICE="eth0"
|
||||||
|
#
|
||||||
|
MASTER0_DEVICE=""
|
||||||
|
#MASTER1_DEVICE=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Backup Ethernet devices
|
||||||
|
#
|
||||||
|
# The MASTER<X>_BACKUP variables specify the devices used for redundancy. They
|
||||||
|
# behaves nearly the same as the MASTER<X>_DEVICE variable, except that it
|
||||||
|
# does not interpret the ff:ff:ff:ff:ff:ff address.
|
||||||
|
#
|
||||||
|
#MASTER0_BACKUP=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Ethernet driver modules to use for EtherCAT operation.
|
||||||
|
#
|
||||||
|
# Specify a non-empty list of Ethernet drivers, that shall be used for
|
||||||
|
# EtherCAT operation.
|
||||||
|
#
|
||||||
|
# Except for the generic Ethernet driver module, the init script will try to
|
||||||
|
# unload the usual Ethernet driver modules in the list and replace them with
|
||||||
|
# the EtherCAT-capable ones. If a certain (EtherCAT-capable) driver is not
|
||||||
|
# found, a warning will appear.
|
||||||
|
#
|
||||||
|
# Possible values: 8139too, e100, e1000, e1000e, r8169, generic, ccat, igb,
|
||||||
|
# igc, genet, dwmac-intel, stmmac-pci.
|
||||||
|
# Separate multiple drivers with spaces.
|
||||||
|
# A list of all matching kernel versions can be found here:
|
||||||
|
# https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html
|
||||||
|
#
|
||||||
|
# Note: The e100, e1000, e1000e, r8169, ccat, igb and igc drivers are not
|
||||||
|
# built by default. Enable them with the --enable-<driver> configure switches.
|
||||||
|
#
|
||||||
|
DEVICE_MODULES=""
|
||||||
|
|
||||||
|
# If you have any issues about network interfaces not being configured
|
||||||
|
# properly, systemd may need some additional infos about your setup.
|
||||||
|
# Have a look at the service file, you'll find some details there.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# List of interfaces to bring up and down automatically.
|
||||||
|
#
|
||||||
|
# Specify a space-separated list of interface names (such as eth0 or
|
||||||
|
# enp0s1) that shall be brought up on `ethercatctl start` and down on
|
||||||
|
# `ethercatctl stop`.
|
||||||
|
#
|
||||||
|
# When using the generic driver, the corresponding Ethernet device has to be
|
||||||
|
# activated before the master is started, otherwise all frames will time out.
|
||||||
|
# This the perfect use-case for `UPDOWN_INTERFACES`.
|
||||||
|
#
|
||||||
|
UPDOWN_INTERFACES=""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Default SII caching method.
|
||||||
|
#
|
||||||
|
# Set the start-up caching method for all masters. The integer value
|
||||||
|
# determines, which fields are used to look up a cached SII page. It is a
|
||||||
|
# bit-field consisting of the following flags. A value of zero disables SII
|
||||||
|
# caching (default). A typical value is 7 (use vendor ID, product code and
|
||||||
|
# revision number for lookup).
|
||||||
|
#
|
||||||
|
# - 1: Vendor ID (always used)
|
||||||
|
# - 2: Product code (always used)
|
||||||
|
# - 4: Revision number
|
||||||
|
# - 8: Serial number
|
||||||
|
# - 16: Alias address
|
||||||
|
#
|
||||||
|
# Please keep in mind that in case serial number or alias address is enabled,
|
||||||
|
# only slaves with a non-zero serial number or alias benefit from caching.
|
||||||
|
#
|
||||||
|
SII_CACHING=0
|
||||||
|
|
||||||
|
#
|
||||||
|
# Flags for loading kernel modules.
|
||||||
|
#
|
||||||
|
# This can usually be left empty. Adjust this variable, if you have problems
|
||||||
|
# with module loading.
|
||||||
|
#
|
||||||
|
#MODPROBE_FLAGS="-b"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
3216
dependency/x86/third_party/ethercat/v1.7.0/include/ecrt.h
vendored
Normal file
3216
dependency/x86/third_party/ethercat/v1.7.0/include/ecrt.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
106
dependency/x86/third_party/ethercat/v1.7.0/include/ectty.h
vendored
Normal file
106
dependency/x86/third_party/ethercat/v1.7.0/include/ectty.h
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH
|
||||||
|
*
|
||||||
|
* This file is part of the IgH EtherCAT master userspace library.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library is free software; you can
|
||||||
|
* redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||||
|
* Public License as published by the Free Software Foundation; version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library is distributed in the hope that
|
||||||
|
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with the IgH EtherCAT master userspace library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/** \file
|
||||||
|
*
|
||||||
|
* EtherCAT virtual TTY interface.
|
||||||
|
*
|
||||||
|
* \defgroup TTYInterface EtherCAT Virtual TTY Interface
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __ECTTY_H__
|
||||||
|
#define __ECTTY_H__
|
||||||
|
|
||||||
|
#include <linux/termios.h>
|
||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* Data types
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
struct ec_tty;
|
||||||
|
typedef struct ec_tty ec_tty_t; /**< \see ec_tty */
|
||||||
|
|
||||||
|
/** Operations on the virtual TTY interface.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int (*cflag_changed)(void *, tcflag_t); /**< Called when the serial
|
||||||
|
* settings shall be changed. The
|
||||||
|
* \a cflag argument contains the
|
||||||
|
* new settings. */
|
||||||
|
} ec_tty_operations_t;
|
||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* Global functions
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/** Create a virtual TTY interface.
|
||||||
|
*
|
||||||
|
* \param ops Set of callbacks.
|
||||||
|
* \param cb_data Arbitrary data, that is passed to any callback.
|
||||||
|
*
|
||||||
|
* \return Pointer to the interface object, otherwise an ERR_PTR value.
|
||||||
|
*/
|
||||||
|
ec_tty_t *ectty_create(
|
||||||
|
const ec_tty_operations_t *ops,
|
||||||
|
void *cb_data
|
||||||
|
);
|
||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* TTY interface methods
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/** Releases a virtual TTY interface.
|
||||||
|
*/
|
||||||
|
void ectty_free(
|
||||||
|
ec_tty_t *tty /**< TTY interface. */
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Reads data to send from the TTY interface.
|
||||||
|
*
|
||||||
|
* If there are data to send, they are copied into the \a buffer. At maximum,
|
||||||
|
* \a size bytes are copied. The actual number of bytes copied is returned.
|
||||||
|
*
|
||||||
|
* \return Number of bytes copied.
|
||||||
|
*/
|
||||||
|
unsigned int ectty_tx_data(
|
||||||
|
ec_tty_t *tty, /**< TTY interface. */
|
||||||
|
uint8_t *buffer, /**< Buffer for data to transmit. */
|
||||||
|
size_t size /**< Available space in \a buffer. */
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Pushes received data to the TTY interface.
|
||||||
|
*/
|
||||||
|
void ectty_rx_data(
|
||||||
|
ec_tty_t *tty, /**< TTY interface. */
|
||||||
|
const uint8_t *buffer, /**< Buffer with received data. */
|
||||||
|
size_t size /**< Number of bytes in \a buffer. */
|
||||||
|
);
|
||||||
|
|
||||||
|
/****************************************************************************/
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
#endif
|
||||||
43
dependency/x86/third_party/ethercat/v1.7.0/lib/cmake/ethercat/ethercat-config.cmake
vendored
Normal file
43
dependency/x86/third_party/ethercat/v1.7.0/lib/cmake/ethercat/ethercat-config.cmake
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#----------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||||
|
#
|
||||||
|
# This file is part of the IgH EtherCAT Master.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License version 2, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with the IgH EtherCAT Master; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# vim: tw=78
|
||||||
|
#
|
||||||
|
#----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
find_library(EtherCAT_LIBRARY
|
||||||
|
NAMES ethercat
|
||||||
|
PATHS /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
find_path(EtherCAT_INCLUDE_DIR
|
||||||
|
NAMES ecrt.h
|
||||||
|
PATHS /home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/include
|
||||||
|
)
|
||||||
|
|
||||||
|
mark_as_advanced(EtherCAT_LIBRARY EtherCAT_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if(NOT TARGET EtherLab::ethercat)
|
||||||
|
add_library(EtherLab::ethercat SHARED IMPORTED)
|
||||||
|
set_target_properties(EtherLab::ethercat PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${EtherCAT_INCLUDE_DIR}"
|
||||||
|
IMPORTED_LOCATION "${EtherCAT_LIBRARY}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
BIN
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.a
vendored
Normal file
BIN
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.a
vendored
Normal file
Binary file not shown.
41
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.la
vendored
Executable file
41
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.la
vendored
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
# libethercat.la - a libtool library file
|
||||||
|
# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15build2
|
||||||
|
#
|
||||||
|
# Please DO NOT delete this file!
|
||||||
|
# It is necessary for linking the library.
|
||||||
|
|
||||||
|
# The name that we can dlopen(3).
|
||||||
|
dlname='libethercat.so.1'
|
||||||
|
|
||||||
|
# Names of this library.
|
||||||
|
library_names='libethercat.so.1.2.0 libethercat.so.1 libethercat.so'
|
||||||
|
|
||||||
|
# The name of the static archive.
|
||||||
|
old_library='libethercat.a'
|
||||||
|
|
||||||
|
# Linker flags that cannot go in dependency_libs.
|
||||||
|
inherited_linker_flags=''
|
||||||
|
|
||||||
|
# Libraries that this one depends upon.
|
||||||
|
dependency_libs=''
|
||||||
|
|
||||||
|
# Names of additional weak libraries provided by this library
|
||||||
|
weak_library_names=''
|
||||||
|
|
||||||
|
# Version information for libethercat.
|
||||||
|
current=3
|
||||||
|
age=2
|
||||||
|
revision=0
|
||||||
|
|
||||||
|
# Is this an already installed library?
|
||||||
|
installed=yes
|
||||||
|
|
||||||
|
# Should we warn about portability when linking against -modules?
|
||||||
|
shouldnotlink=no
|
||||||
|
|
||||||
|
# Files to dlopen/dlpreopen
|
||||||
|
dlopen=''
|
||||||
|
dlpreopen=''
|
||||||
|
|
||||||
|
# Directory that this library needs to be installed in:
|
||||||
|
libdir='/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib'
|
||||||
1
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so
vendored
Symbolic link
1
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libethercat.so.1.2.0
|
||||||
1
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1
vendored
Symbolic link
1
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1
vendored
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libethercat.so.1.2.0
|
||||||
BIN
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1.2.0
vendored
Executable file
BIN
dependency/x86/third_party/ethercat/v1.7.0/lib/libethercat.so.1.2.0
vendored
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
34
dependency/x86/third_party/ethercat/v1.7.0/lib/pkgconfig/libethercat.pc
vendored
Normal file
34
dependency/x86/third_party/ethercat/v1.7.0/lib/pkgconfig/libethercat.pc
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# pkgconfig file for ethercat library
|
||||||
|
#
|
||||||
|
# Copyright 2021 Bjarne von Horn (vh at igh dot de)
|
||||||
|
#
|
||||||
|
# This file is part of the ethercat library.
|
||||||
|
#
|
||||||
|
# The ethercat library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
# option) any later version.
|
||||||
|
#
|
||||||
|
# The ethercat library is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||||
|
# License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with the ethercat library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# vim: tw=78 noexpandtab
|
||||||
|
#
|
||||||
|
|
||||||
|
prefix=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/lib
|
||||||
|
includedir=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/include
|
||||||
|
|
||||||
|
Name: libethercat
|
||||||
|
Description: Client support library for the EtherCAT Master
|
||||||
|
URL: http://www.etherlab.org
|
||||||
|
Version: 1.7.0
|
||||||
|
Libs: -L${libdir} -lethercat
|
||||||
|
Cflags: -I${includedir}
|
||||||
38
dependency/x86/third_party/ethercat/v1.7.0/lib/systemd/system/ethercat.service
vendored
Normal file
38
dependency/x86/third_party/ethercat/v1.7.0/lib/systemd/system/ethercat.service
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#
|
||||||
|
# EtherCAT master kernel modules
|
||||||
|
#
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=EtherCAT Master Kernel Modules
|
||||||
|
|
||||||
|
# Fine tuning of the startup dependencies below are recommended
|
||||||
|
# to provide a reliable startup routine.
|
||||||
|
# The dependencies below can be either uncommented after copying
|
||||||
|
# this file to /etc/systemd/system or by creating overrides:
|
||||||
|
# Copy the needed dependencies into
|
||||||
|
# /etc/systemd/system/ethercat.service.d/50-dependencies.conf
|
||||||
|
# in a [Unit] section.
|
||||||
|
|
||||||
|
#
|
||||||
|
# Uncomment this, if the generic Ethernet driver is used. It assures, that the
|
||||||
|
# network interfaces are configured, before the master starts.
|
||||||
|
#
|
||||||
|
#Requires=network.target # Stop master, if network is stopped
|
||||||
|
#After=network.target # Start master, after network is ready
|
||||||
|
|
||||||
|
#
|
||||||
|
# Uncomment this, if a native Ethernet driver is used. It assures, that the
|
||||||
|
# network interfaces are configured, after the Ethernet drivers have been
|
||||||
|
# replaced. Otherwise, the networking configuration tools could be confused.
|
||||||
|
#
|
||||||
|
#Before=network-pre.target
|
||||||
|
#Wants=network-pre.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl start
|
||||||
|
ExecStop=/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl stop
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
253
dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl
vendored
Executable file
253
dependency/x86/third_party/ethercat/v1.7.0/sbin/ethercatctl
vendored
Executable file
@ -0,0 +1,253 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Start script for EtherCAT to use with systemd
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006-2021 Florian Pose, Ingenieurgemeinschaft IgH
|
||||||
|
#
|
||||||
|
# This file is part of the IgH EtherCAT Master.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License version 2, as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT Master is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with the IgH EtherCAT Master; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# vim: expandtab sw=4 tw=78
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
LSMOD="/sbin/lsmod"
|
||||||
|
MODPROBE="/sbin/modprobe"
|
||||||
|
RMMOD="/sbin/rmmod"
|
||||||
|
MODINFO="/sbin/modinfo"
|
||||||
|
IP="/sbin/ip"
|
||||||
|
|
||||||
|
ETHERCAT="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/bin/ethercat"
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ "$1" = "-c" ]; then
|
||||||
|
ETHERCAT_CONFIG="$2"
|
||||||
|
COMMAND="$3"
|
||||||
|
else
|
||||||
|
ETHERCAT_CONFIG="/home/lgv/cmvr/0-workspace/cmvr-es/dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf"
|
||||||
|
COMMAND="$1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if [ ! -r ${ETHERCAT_CONFIG} ]; then
|
||||||
|
echo ${ETHERCAT_CONFIG} not existing;
|
||||||
|
exit 6
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck source=/etc/ethercat.conf
|
||||||
|
. ${ETHERCAT_CONFIG}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
is_mac_address() {
|
||||||
|
local x='[0-9a-fA-F]'
|
||||||
|
echo "$1" | grep -qE "^($x$x:){5}$x$x\$" -
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
parse_mac_address() {
|
||||||
|
local DEVICENAMETOMAC
|
||||||
|
if [ -z "${1}" ] || is_mac_address "${1}"; then
|
||||||
|
MAC="${1}"
|
||||||
|
else
|
||||||
|
DEVICENAMETOMAC=$("${IP}" address show dev "${1}" |
|
||||||
|
awk '/link\/ether/ { print $2; }')
|
||||||
|
if is_mac_address "${DEVICENAMETOMAC}"; then
|
||||||
|
MAC="${DEVICENAMETOMAC}"
|
||||||
|
else
|
||||||
|
echo Invalid MAC address or interface name \""${1}"\" \
|
||||||
|
in ${ETHERCAT_CONFIG}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
case "$COMMAND" in
|
||||||
|
|
||||||
|
start)
|
||||||
|
# bring up all updown interfaces before anything else
|
||||||
|
for interface in $UPDOWN_INTERFACES; do
|
||||||
|
$IP link set dev $interface up
|
||||||
|
done
|
||||||
|
|
||||||
|
# construct DEVICES and BACKUPS from configuration variables
|
||||||
|
DEVICES=""
|
||||||
|
BACKUPS=""
|
||||||
|
MASTER_INDEX=0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
DEVICE=$(eval echo "\${MASTER${MASTER_INDEX}_DEVICE}")
|
||||||
|
BACKUP=$(eval echo "\${MASTER${MASTER_INDEX}_BACKUP}")
|
||||||
|
if [ -z "${DEVICE}" ]; then break; fi
|
||||||
|
|
||||||
|
if [ ${MASTER_INDEX} -gt 0 ]; then
|
||||||
|
DEVICES=${DEVICES},
|
||||||
|
BACKUPS=${BACKUPS},
|
||||||
|
fi
|
||||||
|
|
||||||
|
parse_mac_address "${DEVICE}"
|
||||||
|
DEVICES=${DEVICES}${MAC}
|
||||||
|
|
||||||
|
parse_mac_address "${BACKUP}"
|
||||||
|
BACKUPS=${BACKUPS}${MAC}
|
||||||
|
|
||||||
|
MASTER_INDEX=$((${MASTER_INDEX} + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${DEVICES}" ]; then
|
||||||
|
echo "ERROR: No network cards for EtherCAT specified."
|
||||||
|
echo -n "Please edit ${ETHERCAT_CONFIG} with root permissions"
|
||||||
|
echo -n " and set MASTER0_DEVICE variable to either a "
|
||||||
|
echo "network interface name (like eth0) or to a MAC address."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODULE_PARAMS=(
|
||||||
|
main_devices="${DEVICES}"
|
||||||
|
backup_devices="${BACKUPS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -n "$SII_CACHING" ]; then
|
||||||
|
MODULE_PARAMS+=(sii_caching="$SII_CACHING")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# load master module
|
||||||
|
if ! ${MODPROBE} ${MODPROBE_FLAGS} ec_master "${MODULE_PARAMS[@]}"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOADED_MODULES=ec_master
|
||||||
|
|
||||||
|
# check for modules to replace
|
||||||
|
for MODULE in ${DEVICE_MODULES}; do
|
||||||
|
ECMODULE=ec_${MODULE}
|
||||||
|
if ! ${MODINFO} "${ECMODULE}" > /dev/null; then
|
||||||
|
continue # ec_* module not found
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${MODULE}" != "generic" ] && [ "${MODULE}" != "ccat" ]; then
|
||||||
|
# unload standard module and check if unloading was successful
|
||||||
|
${RMMOD} "${MODULE}" 2> /dev/null || true
|
||||||
|
if ${LSMOD} | grep "^${MODULE//-/_} " > /dev/null; then
|
||||||
|
# could not unload module
|
||||||
|
${RMMOD} ${LOADED_MODULES}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ${MODPROBE} ${MODPROBE_FLAGS} "${ECMODULE}"; then
|
||||||
|
if [ "${MODULE}" != "generic" ] && [ "${MODULE}" != "ccat" ]; then
|
||||||
|
${MODPROBE} ${MODPROBE_FLAGS} "${MODULE}" # try to restore
|
||||||
|
fi
|
||||||
|
${RMMOD} ${LOADED_MODULES}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOADED_MODULES="${ECMODULE} ${LOADED_MODULES}"
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stop)
|
||||||
|
# unload EtherCAT device modules
|
||||||
|
for MODULE in ${DEVICE_MODULES} master; do
|
||||||
|
ECMODULE=ec_${MODULE}
|
||||||
|
if ! ${LSMOD} | grep -q "^${ECMODULE//-/_} "; then
|
||||||
|
continue # ec_* module not loaded
|
||||||
|
fi
|
||||||
|
if ! ${RMMOD} "${ECMODULE}"; then
|
||||||
|
exit 1
|
||||||
|
fi;
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# load standard modules again
|
||||||
|
for MODULE in ${DEVICE_MODULES}; do
|
||||||
|
if [ "${MODULE}" == "generic" ] || [ "${MODULE}" == "ccat" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
${MODPROBE} ${MODPROBE_FLAGS} "${MODULE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# bring down all updown interfaces
|
||||||
|
for interface in $UPDOWN_INTERFACES; do
|
||||||
|
$IP link set dev $interface down
|
||||||
|
done
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
restart)
|
||||||
|
$0 stop || exit 1
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
status)
|
||||||
|
echo "Checking for EtherCAT master 1.7.0 "
|
||||||
|
|
||||||
|
# count masters in configuration file
|
||||||
|
MASTER_COUNT=0
|
||||||
|
while true; do
|
||||||
|
DEVICE=$(eval echo "\${MASTER${MASTER_COUNT}_DEVICE}")
|
||||||
|
if [ -z "${DEVICE}" ]; then break; fi
|
||||||
|
MASTER_COUNT=$((${MASTER_COUNT} + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
RESULT=0
|
||||||
|
|
||||||
|
for i in $(seq 0 "$((${MASTER_COUNT} - 1))"); do
|
||||||
|
echo -n "Master${i} "
|
||||||
|
|
||||||
|
# Check if the master is in idle or operation phase
|
||||||
|
${ETHERCAT} master --master "${i}" 2>/dev/null | \
|
||||||
|
grep -qE 'Phase:[[:space:]]*Idle|Phase:[[:space:]]*Operation'
|
||||||
|
EXITCODE=$?
|
||||||
|
|
||||||
|
if [ ${EXITCODE} -eq 0 ]; then
|
||||||
|
echo " running"
|
||||||
|
else
|
||||||
|
echo " dead"
|
||||||
|
RESULT=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit ${RESULT}
|
||||||
|
;;
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "USAGE: $0 [-c path/to/ethercat.conf] {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
80
dependency/x86/third_party/ethercat/v1.7.0/share/bash-completion/completions/ethercat
vendored
Normal file
80
dependency/x86/third_party/ethercat/v1.7.0/share/bash-completion/completions/ethercat
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Copyright (C) 2022 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||||
|
#
|
||||||
|
# This file is part of the IgH EtherCAT master userspace library.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT master userspace library is free software; you can
|
||||||
|
# redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||||
|
# Public License as published by the Free Software Foundation; version 2.1
|
||||||
|
# of the License.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT master userspace library is distributed in the hope that
|
||||||
|
# it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with the IgH EtherCAT master userspace library. If not, see
|
||||||
|
# <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
_ethercat_completions()
|
||||||
|
{
|
||||||
|
local ethercat_commands="alias config crc cstruct data debug domains download eoe foe_read foe_write graph master pdos reg_read reg_write rescan sdos sii_read sii_write slaves soe_read soe_write states upload version xml"
|
||||||
|
local options="--help --force --quiet --verbose --master "
|
||||||
|
if [ "$COMP_CWORD" -eq 1 ] ; then
|
||||||
|
COMPREPLY=($(compgen -W "$ethercat_commands --help" -- "${COMP_WORDS[1]}"))
|
||||||
|
elif [[ "${COMP_WORDS[1]}" != "--help" && ! "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-a|-p|--alias|--position$ ]] ; then
|
||||||
|
case "${COMP_WORDS[1]}" in
|
||||||
|
"alias" | "config" | "cstruct" | "slaves" | "sdos" | "sii_read" | "upload" | "xml")
|
||||||
|
options+="--alias --position"
|
||||||
|
;;
|
||||||
|
"crc")
|
||||||
|
options+="reset"
|
||||||
|
;;
|
||||||
|
"debug")
|
||||||
|
options+="0 1 2"
|
||||||
|
;;
|
||||||
|
"domains")
|
||||||
|
options+="--domain"
|
||||||
|
;;
|
||||||
|
"download" | "reg_read" | "soe_read" | "soe_write")
|
||||||
|
if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-t|--type$ ]] ; then
|
||||||
|
options="bool int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double string octet_string unicode_string sm8 sm16 sm32 sm64"
|
||||||
|
else
|
||||||
|
options+="--alias --position --type"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"foe_read" | "foe_write")
|
||||||
|
if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-o|--output-file$ ]] ; then
|
||||||
|
COMPREPLY=($(compgen -o filenames -A file -- "${COMP_WORDS[$COMP_CWORD]}"))
|
||||||
|
else
|
||||||
|
options+="--alias --position --output-file"
|
||||||
|
COMPREPLY=($(compgen -o filenames -A file -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}"))
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
"graph")
|
||||||
|
options+="DC CRC"
|
||||||
|
;;
|
||||||
|
"pdos")
|
||||||
|
if [[ "${COMP_WORDS[COMP_CWORD-1]}" =~ ^-s|--skin$ ]] ; then
|
||||||
|
options="default etherlab"
|
||||||
|
else
|
||||||
|
options+="--alias --position --skin"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"sii_write")
|
||||||
|
options+="--alias --position"
|
||||||
|
COMPREPLY=($(compgen -o filenames -A file -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}"))
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
"states")
|
||||||
|
options+="--alias --position INIT PREOP BOOT SAFEOP OP"
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
COMPREPLY+=($(compgen -W "$options" -- "${COMP_WORDS[$COMP_CWORD]}"))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _ethercat_completions ethercat
|
||||||
5560
docs/EYOU_ServoModule_ECAT_V145.xml
Normal file
5560
docs/EYOU_ServoModule_ECAT_V145.xml
Normal file
File diff suppressed because it is too large
Load Diff
5495
docs/EYOU_ServoModule_ECAT_V145_no_slot.xml
Normal file
5495
docs/EYOU_ServoModule_ECAT_V145_no_slot.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,51 @@
|
|||||||
# EtherCAT 电机接入教程
|
# EtherCAT 电机接入教程
|
||||||
|
|
||||||
这份文档只说明新增一种 EtherCAT 电机需要改哪里、怎么写。
|
当前已接入意优 `EYOU_ServoModule_ECAT_V145`,协议为 EtherCAT CoE + CiA402,PDO 使用 `docs/EYOU_ServoModule_ECAT_V145_no_slot.xml` 中的 `0x1600/0x1A00` 映射。
|
||||||
|
|
||||||
## 1. 增加 vendor
|
ESI XML 作为厂商通信说明和对照资料保存,运行时不直接解析 XML。实际 PDO 映射写在:
|
||||||
|
|
||||||
修改 `protos/cmvr/config/motor_config/motor_config.proto`:
|
```text
|
||||||
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h
|
||||||
```proto
|
|
||||||
enum MotorVendor {
|
|
||||||
MOTOR_VENDOR_UNKNOWN = 0;
|
|
||||||
MOTOR_VENDOR_TI5 = 1;
|
|
||||||
MOTOR_VENDOR_MUJOCO = 2;
|
|
||||||
MOTOR_VENDOR_XXX = 3;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`MOTOR_VENDOR_XXX` 改成真实厂商名,例如 `MOTOR_VENDOR_FOO`。不要复用 `TI5`。
|
## 1. 系统依赖
|
||||||
|
|
||||||
## 2. 写电机配置
|
IgH EtherCAT userspace 已安装在:
|
||||||
|
|
||||||
新增配置文件:
|
```text
|
||||||
|
dependency/x86/third_party/ethercat/v1.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
真机运行前,系统里还需要安装/加载 IgH master 内核模块。使用仓库脚本启动 EtherCAT master:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1
|
||||||
|
script/ethercat/status_ethercat.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `eno1` 是连接 EtherCAT 从站的网卡。脚本会读取该网卡 MAC,写入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency/x86/third_party/ethercat/v1.7.0/etc/ethercat.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
并通过 bundled IgH 的 `ethercatctl -c` 启动 master。能看到 EYOU slave 后再启动程序。
|
||||||
|
|
||||||
|
停止 EtherCAT:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1
|
||||||
|
```
|
||||||
|
|
||||||
|
如果这张网卡要恢复给普通网络使用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1 --restore-network
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 电机配置
|
||||||
|
|
||||||
|
新增或修改:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
cmvr-es/config/devices/motor/ethercat_motors.pb.txt
|
cmvr-es/config/devices/motor/ethercat_motors.pb.txt
|
||||||
@ -34,20 +60,28 @@ motor {
|
|||||||
motor_groups {
|
motor_groups {
|
||||||
id: "right_arm_ethercat"
|
id: "right_arm_ethercat"
|
||||||
bus_type: MOTOR_BUS_ETHERCAT
|
bus_type: MOTOR_BUS_ETHERCAT
|
||||||
vendor: MOTOR_VENDOR_XXX
|
vendor: MOTOR_VENDOR_EYOU
|
||||||
protocol: MOTOR_PROTOCOL_ETHERCAT_CIA402
|
protocol: MOTOR_PROTOCOL_ETHERCAT_CIA402
|
||||||
tool_frame: "R_FINGER_TIP"
|
|
||||||
|
|
||||||
ethercat {
|
ethercat {
|
||||||
master_id: "eth0"
|
master_index: 0
|
||||||
cycle_us: 1000
|
cycle_us: 1000
|
||||||
slaves { motor_id: 1 slave_index: 0 vendor_id: 0x00000000 product_code: 0x00000000 }
|
|
||||||
slaves { motor_id: 2 slave_index: 1 vendor_id: 0x00000000 product_code: 0x00000000 }
|
cia402 {
|
||||||
slaves { motor_id: 3 slave_index: 2 vendor_id: 0x00000000 product_code: 0x00000000 }
|
profile_position_trigger_delay_ms: 2
|
||||||
slaves { motor_id: 4 slave_index: 3 vendor_id: 0x00000000 product_code: 0x00000000 }
|
state_transition_timeout_ms: 1200
|
||||||
slaves { motor_id: 5 slave_index: 4 vendor_id: 0x00000000 product_code: 0x00000000 }
|
velocity_stop_timeout_ms: 2000
|
||||||
slaves { motor_id: 6 slave_index: 5 vendor_id: 0x00000000 product_code: 0x00000000 }
|
status_poll_period_ms: 10
|
||||||
slaves { motor_id: 7 slave_index: 6 vendor_id: 0x00000000 product_code: 0x00000000 }
|
stopped_velocity_tolerance_rad_s: 0.001
|
||||||
|
}
|
||||||
|
|
||||||
|
slaves { motor_id: 1 alias: 0 position: 0 }
|
||||||
|
slaves { motor_id: 2 alias: 0 position: 1 }
|
||||||
|
slaves { motor_id: 3 alias: 0 position: 2 }
|
||||||
|
slaves { motor_id: 4 alias: 0 position: 3 }
|
||||||
|
slaves { motor_id: 5 alias: 0 position: 4 }
|
||||||
|
slaves { motor_id: 6 alias: 0 position: 5 }
|
||||||
|
slaves { motor_id: 7 alias: 0 position: 6 }
|
||||||
}
|
}
|
||||||
|
|
||||||
joint_limits {
|
joint_limits {
|
||||||
@ -63,21 +97,26 @@ motor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
motors {
|
motors {
|
||||||
motors { id: 1 joint_name: "R_SHOULDER_P" }
|
motors { id: 1 joint_name: "R_SHOULDER_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 2 joint_name: "R_SHOULDER_R" }
|
motors { id: 2 joint_name: "R_SHOULDER_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 3 joint_name: "R_SHOULDER_Y" }
|
motors { id: 3 joint_name: "R_SHOULDER_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 4 joint_name: "R_ELBOW_R" }
|
motors { id: 4 joint_name: "R_ELBOW_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 5 joint_name: "R_WRIST_P" }
|
motors { id: 5 joint_name: "R_WRIST_P" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 6 joint_name: "R_WRIST_Y" }
|
motors { id: 6 joint_name: "R_WRIST_Y" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
motors { id: 7 joint_name: "R_WRIST_R" }
|
motors { id: 7 joint_name: "R_WRIST_R" encoder_counts_per_rev: 65536 gear_ratio: 101.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`motors.motors.id` 是系统内的电机逻辑 id。`ethercat.slaves.motor_id` 必须和它对应。
|
字段说明:
|
||||||
|
|
||||||
`joint_limits` 按 `joint_name` 读取,和 CAN、MuJoCo 电机保持同一个风格。
|
- `master_index`:IgH master 编号,第一张 EtherCAT master 是 `0`。
|
||||||
|
- `alias`:EtherCAT alias,一般为 `0`。
|
||||||
|
- `position`:slave 在 EtherCAT 链路上的顺序,从 `0` 开始。
|
||||||
|
- `motor_id`:系统内电机 id,必须和 `motors.motors.id` 对应。
|
||||||
|
- `encoder_counts_per_rev`:电机编码器每转 count 数,用于 `rad <-> count` 换算。
|
||||||
|
- `gear_ratio`:电机轴到关节输出轴的减速比,用于接口层 `rad/rad/s` 和驱动器 raw count/counts/s 换算。
|
||||||
|
|
||||||
## 3. 注册设备
|
## 3. 注册设备
|
||||||
|
|
||||||
@ -98,15 +137,9 @@ devices {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. 机械臂使用 EtherCAT group
|
## 4. 机械臂使用 EtherCAT 电机
|
||||||
|
|
||||||
修改机械臂配置,例如:
|
修改机械臂配置中的 motor backend:
|
||||||
|
|
||||||
```text
|
|
||||||
cmvr-es/config/devices/arm/arm.pb.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
把 motor backend 改成:
|
|
||||||
|
|
||||||
```proto
|
```proto
|
||||||
motor {
|
motor {
|
||||||
@ -122,116 +155,66 @@ motor {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 实现 bus runtime
|
## 5. 代码结构
|
||||||
|
|
||||||
EtherCAT 总线资源放在:
|
EtherCAT 总线运行时:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h
|
cmvr-es/devices/motor/bus_runtime/ethercat/include/ethercat_motor_bus_runtime.h
|
||||||
cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp
|
cmvr-es/devices/motor/bus_runtime/ethercat/src/ethercat_motor_bus_runtime.cpp
|
||||||
```
|
```
|
||||||
|
|
||||||
`EthercatMotorBusRuntime` 负责:
|
它负责:
|
||||||
|
|
||||||
|
- 打开 IgH master
|
||||||
|
- 创建 domain
|
||||||
|
- 使用 `vendor + protocol` 选择 PDO mapping
|
||||||
|
- 按 mapping 配置 `0x1600/0x1A00` PDO
|
||||||
|
- 注册每个非 padding PDO entry 的 offset
|
||||||
|
- 启动 cyclic loop
|
||||||
|
- 按 `motor_id + index + subindex` 提供通用 PDO 读写接口
|
||||||
|
|
||||||
|
CiA402 EtherCAT 电机驱动:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
读取 ethercat 配置
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/cia402/cia402_protocol.h
|
||||||
初始化 EtherCAT master
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/cia402/cia402_objects.h
|
||||||
扫描/校验 slave_index、vendor_id、product_code
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/motor_vendor_adapter.h
|
||||||
启动 cyclic loop
|
cmvr-es/devices/motor/drivers/ethercat_motor/src/cia402/cia402_protocol.cpp
|
||||||
保存 command/feedback buffer
|
|
||||||
停止 cyclic loop
|
|
||||||
```
|
```
|
||||||
|
|
||||||
bus runtime 不创建具体电机,也不关心厂商;它只保存总线连接、线程和数据缓存。
|
EYOU 私有适配:
|
||||||
|
|
||||||
## 6. 增加具体电机 driver
|
|
||||||
|
|
||||||
新增目录:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
cmvr-es/devices/motor/drivers/xxx_ethercat/
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor.h
|
||||||
CMakeLists.txt
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_motor_adapter.h
|
||||||
include/xxx_ethercat_motor.h
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_cia402_pdo_mapping.h
|
||||||
include/xxx_ethercat_motor_protocol.h
|
cmvr-es/devices/motor/drivers/ethercat_motor/include/vendor/eyou/eyou_objects.h
|
||||||
src/xxx_ethercat_motor.cpp
|
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor.cpp
|
||||||
src/xxx_ethercat_motor_protocol.cpp
|
cmvr-es/devices/motor/drivers/ethercat_motor/src/vendor/eyou/eyou_motor_adapter.cpp
|
||||||
```
|
```
|
||||||
|
|
||||||
`XxxEthercatMotor` 继承 `AbstractMotor`。
|
它负责:
|
||||||
|
|
||||||
`XxxEthercatMotorProtocol` 继承 `MotorProtocolInterface`,把 `setTarget`、`setQd`、`getQ` 等接口转换成 EtherCAT command/feedback。
|
- CiA402 `6040/6041` 状态机
|
||||||
|
- 设置 `6060` 运行模式
|
||||||
|
- 写 `607A/60FF/6071`
|
||||||
|
- 读 `6064/606C/6077/603F`
|
||||||
|
- `rad` 和 encoder count 的换算
|
||||||
|
|
||||||
## 7. 在 MotorManager 里创建 EtherCAT 电机
|
## 6. 编译验证
|
||||||
|
|
||||||
修改:
|
|
||||||
|
|
||||||
```text
|
|
||||||
cmvr-es/devices/motor/manager/src/motor_manager.cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
在 `MotorManager::createEthercatMotors_()` 里按 `vendor + protocol` 创建具体电机:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto ethercat_bus_runtime =
|
|
||||||
std::dynamic_pointer_cast<EthercatMotorBusRuntime>(bus_runtime);
|
|
||||||
|
|
||||||
if (group_cfg.vendor() == config::MOTOR_VENDOR_XXX &&
|
|
||||||
group_cfg.protocol() == config::MOTOR_PROTOCOL_ETHERCAT_CIA402) {
|
|
||||||
auto protocol = std::make_shared<XxxEthercatMotorProtocol>(ethercat_bus_runtime);
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<AbstractMotor>> motors;
|
|
||||||
motors.reserve(motor_cfgs.size());
|
|
||||||
for (const auto& cfg : motor_cfgs) {
|
|
||||||
auto motor = std::make_shared<XxxEthercatMotor>(cfg);
|
|
||||||
motor->setProtocol(protocol);
|
|
||||||
if (!motor->init()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
motors.push_back(std::move(motor));
|
|
||||||
}
|
|
||||||
return motors;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 加入 CMake
|
|
||||||
|
|
||||||
修改:
|
|
||||||
|
|
||||||
```text
|
|
||||||
cmvr-es/devices/motor/manager/CMakeLists.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
给 `motor_manager` 链接新增的具体 EtherCAT 电机 target。
|
|
||||||
|
|
||||||
修改:
|
|
||||||
|
|
||||||
```text
|
|
||||||
cmvr-es/devices/motor/CMakeLists.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
加入:
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
add_subdirectory(drivers/xxx_ethercat)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 9. 验证配置
|
|
||||||
|
|
||||||
先验证 proto:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./output/bin/protoc \
|
cmake -S . -B cmake-build-debug
|
||||||
--encode=cmvr.config.MotorRootConfig \
|
cmake --build cmake-build-debug --target motor_manager
|
||||||
-I protos \
|
|
||||||
protos/cmvr/config/motor_config/motor_config.proto \
|
|
||||||
< cmvr-es/config/devices/motor/ethercat_motors.pb.txt \
|
|
||||||
> /tmp/ethercat_motors.pb.bin
|
|
||||||
```
|
```
|
||||||
|
|
||||||
再编译:
|
真机验证顺序:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cmake --build cmake-build-debug --target mujoco_manual_ui_test
|
sudo script/ethercat/start_ethercat.sh eno1
|
||||||
|
script/ethercat/status_ethercat.sh
|
||||||
|
cmake --build cmake-build-debug --target cmvr_es
|
||||||
```
|
```
|
||||||
|
|
||||||
真机联调时先只验证初始化日志:master 打开、slave 数量、vendor/product 校验、cyclic loop 启动、每个 motor 注册成功。然后再下发运动命令。
|
第一次联调先不要大幅度运动。先看 master 是否打开、slave 是否进入 OP、状态字是否更新,再给单个电机小角度目标。
|
||||||
|
|||||||
BIN
docs/意优CANopen&EtherCAT应用手册V2.2.pdf
Normal file
BIN
docs/意优CANopen&EtherCAT应用手册V2.2.pdf
Normal file
Binary file not shown.
@ -34,6 +34,7 @@ message DeviceManagerConfig {
|
|||||||
string version = 2;
|
string version = 2;
|
||||||
string description = 3;
|
string description = 3;
|
||||||
repeated DeviceConfigEntry devices = 4;
|
repeated DeviceConfigEntry devices = 4;
|
||||||
|
bool init_all_motors_when_no_active_joints = 20;
|
||||||
}
|
}
|
||||||
message DeviceManagerRootConfig {
|
message DeviceManagerRootConfig {
|
||||||
DeviceManagerConfig device_manager = 1;
|
DeviceManagerConfig device_manager = 1;
|
||||||
|
|||||||
@ -10,6 +10,8 @@ message MotorConfigItem {
|
|||||||
double limit_q_ub = 4;
|
double limit_q_ub = 4;
|
||||||
double limit_qd = 5;
|
double limit_qd = 5;
|
||||||
double limit_qdd = 6;
|
double limit_qdd = 6;
|
||||||
|
double encoder_counts_per_rev = 7;
|
||||||
|
double gear_ratio = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MotorList {
|
message MotorList {
|
||||||
@ -18,9 +20,26 @@ message MotorList {
|
|||||||
|
|
||||||
message EthercatSlaveConfig {
|
message EthercatSlaveConfig {
|
||||||
int32 motor_id = 1;
|
int32 motor_id = 1;
|
||||||
int32 slave_index = 2;
|
uint32 alias = 2;
|
||||||
uint32 vendor_id = 3;
|
uint32 position = 3;
|
||||||
uint32 product_code = 4;
|
}
|
||||||
|
|
||||||
|
message Cia402ProtocolConfig {
|
||||||
|
uint32 profile_position_trigger_delay_ms = 1;
|
||||||
|
uint32 state_transition_timeout_ms = 2;
|
||||||
|
uint32 velocity_stop_timeout_ms = 3;
|
||||||
|
uint32 status_poll_period_ms = 4;
|
||||||
|
double stopped_velocity_tolerance_rad_s = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EtherCATDcConfig {
|
||||||
|
optional bool enable = 1;
|
||||||
|
optional int32 reference_motor_id = 2;
|
||||||
|
optional uint32 sync0_cycle_us = 3;
|
||||||
|
optional int32 sync0_shift_us = 4;
|
||||||
|
optional uint32 sync_reference_clock_period = 5;
|
||||||
|
optional uint32 assign_activate = 6;
|
||||||
|
optional uint32 sync_monitor_period_ms = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SocketCanConfig {
|
message SocketCanConfig {
|
||||||
@ -29,8 +48,12 @@ message SocketCanConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message EtherCATConfig {
|
message EtherCATConfig {
|
||||||
string master_id = 1;
|
uint32 master_index = 1;
|
||||||
int32 cycle_us = 2;
|
int32 cycle_us = 2;
|
||||||
|
Cia402ProtocolConfig cia402 = 3;
|
||||||
|
EtherCATDcConfig dc = 4;
|
||||||
|
optional uint32 slave_op_timeout_ms = 5;
|
||||||
|
optional uint32 slave_state_poll_period_ms = 6;
|
||||||
repeated EthercatSlaveConfig slaves = 10;
|
repeated EthercatSlaveConfig slaves = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +72,7 @@ enum MotorVendor {
|
|||||||
MOTOR_VENDOR_UNKNOWN = 0;
|
MOTOR_VENDOR_UNKNOWN = 0;
|
||||||
MOTOR_VENDOR_TI5 = 1;
|
MOTOR_VENDOR_TI5 = 1;
|
||||||
MOTOR_VENDOR_MUJOCO = 2;
|
MOTOR_VENDOR_MUJOCO = 2;
|
||||||
|
MOTOR_VENDOR_EYOU = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MotorProtocol {
|
enum MotorProtocol {
|
||||||
@ -63,7 +87,6 @@ message MotorGroupConfig {
|
|||||||
MotorBusType bus_type = 2;
|
MotorBusType bus_type = 2;
|
||||||
MotorVendor vendor = 3;
|
MotorVendor vendor = 3;
|
||||||
MotorProtocol protocol = 4;
|
MotorProtocol protocol = 4;
|
||||||
string tool_frame = 5;
|
|
||||||
|
|
||||||
oneof bus_config {
|
oneof bus_config {
|
||||||
SocketCanConfig can = 10;
|
SocketCanConfig can = 10;
|
||||||
|
|||||||
@ -5,8 +5,8 @@ package cmvr.msgs;
|
|||||||
message SdoFrame {
|
message SdoFrame {
|
||||||
uint32 node_id = 1; // 节点ID
|
uint32 node_id = 1; // 节点ID
|
||||||
CommandSpecifier cs = 2; // SDO命令字
|
CommandSpecifier cs = 2; // SDO命令字
|
||||||
ObIndex index = 3; // 对象字典索引
|
uint32 index = 3; // 对象字典索引
|
||||||
ObSubIndex sub_index = 4; // 子索引
|
uint32 sub_index = 4; // 子索引
|
||||||
uint32 data = 5; // 数据区
|
uint32 data = 5; // 数据区
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,81 +110,38 @@ enum NmtCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 索引
|
// CANopen communication object dictionary indexes.
|
||||||
enum ObIndex {
|
// CiA402 drive-profile objects are defined in cia402.proto.
|
||||||
INDEX_ZERO = 0;
|
enum CanopenObjectIndex {
|
||||||
USER_SAVE_PARA_2000 = 0x2000; // 下发命令 1 保存参数
|
CANOPEN_OBJECT_INDEX_ZERO = 0;
|
||||||
POSITION_OFFSET_2008 = 0x2008; // 位置偏移,子索引 0x00,用于设置零点、起始位置
|
|
||||||
// Error Codes
|
|
||||||
ERROR_CODE_6007 = 0x6007;
|
|
||||||
ERROR_CODE_603F = 0x603F;
|
|
||||||
|
|
||||||
// Control and Status
|
CANOPEN_PRODUCER_HEARTBEAT_TIME_1017 = 0x1017;
|
||||||
CONTROL_WORD_6040 = 0x6040;
|
|
||||||
STATUS_WORD_6041 = 0x6041;
|
|
||||||
|
|
||||||
// Operation Modes
|
|
||||||
OPERATION_MODE_6060 = 0x6060;
|
|
||||||
MODE_DISPLAY_6061 = 0x6061;
|
|
||||||
|
|
||||||
// Actual Values
|
|
||||||
ACTUAL_POSITION_6064 = 0x6064;
|
|
||||||
ACTUAL_SPEED_606C = 0x606C;
|
|
||||||
ACTUAL_CURRENT_6078 = 0x6078;
|
|
||||||
|
|
||||||
// Torque-related
|
|
||||||
TARGET_TORQUE_6071 = 0x6071;
|
|
||||||
MAX_TORQUE_6072 = 0x6072;
|
|
||||||
DEMAND_TORQUE_6074 = 0x6074;
|
|
||||||
|
|
||||||
// Position-related
|
|
||||||
TARGET_POSITION_607A = 0x607A;
|
|
||||||
SOFTWARE_POSITION_LIMIT_607D = 0x607D; // Sub-indexes: 1, 2
|
|
||||||
|
|
||||||
// Speed-related
|
|
||||||
MAX_SPEED_607F = 0x607F;
|
|
||||||
PROFILE_SPEED_6081 = 0x6081;
|
|
||||||
PROFILE_ACCELERATION_6083 = 0x6083;
|
|
||||||
PROFILE_DECELERATION_6084 = 0x6084;
|
|
||||||
|
|
||||||
// Same as DEMAND_TORQUE? Verify correctness.
|
|
||||||
// TORQUE_SLOPE_6074 = 0x6074;
|
|
||||||
|
|
||||||
// PID Control
|
|
||||||
CURRENT_LOOP_PID_60F6 = 0x60F6; // Sub-indexes: 1, 2
|
|
||||||
SPEED_LOOP_PID_60F9 = 0x60F9; // Sub-indexes: 1, 2
|
|
||||||
POSITION_LOOP_PID_60FB = 0x60FB; // Sub-indexes: 1, 2, 3
|
|
||||||
|
|
||||||
// Target Speed
|
|
||||||
TARGET_SPEED_60FF = 0x60FF;
|
|
||||||
|
|
||||||
QUICK_STOP_OPTION_605A = 0x605A;
|
|
||||||
QUICK_STOP_DECEL_6085 = 0x6085;
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// PDO 通信参数对象(Communication Object)
|
// PDO 通信参数对象(Communication Object)
|
||||||
RPDO1_COMM_1400 = 0x1400;
|
CANOPEN_RPDO1_COMM_1400 = 0x1400;
|
||||||
RPDO2_COMM_1401 = 0x1401;
|
CANOPEN_RPDO2_COMM_1401 = 0x1401;
|
||||||
RPDO3_COMM_1402 = 0x1402;
|
CANOPEN_RPDO3_COMM_1402 = 0x1402;
|
||||||
RPDO4_COMM_1403 = 0x1403;
|
CANOPEN_RPDO4_COMM_1403 = 0x1403;
|
||||||
|
|
||||||
TPDO1_COMM_1800 = 0x1800;
|
CANOPEN_TPDO1_COMM_1800 = 0x1800;
|
||||||
TPDO2_COMM_1801 = 0x1801;
|
CANOPEN_TPDO2_COMM_1801 = 0x1801;
|
||||||
TPDO3_COMM_1802 = 0x1802;
|
CANOPEN_TPDO3_COMM_1802 = 0x1802;
|
||||||
TPDO4_COMM_1803 = 0x1803;
|
CANOPEN_TPDO4_COMM_1803 = 0x1803;
|
||||||
|
|
||||||
// PDO 映射对象(Mapping Object)
|
// PDO 映射对象(Mapping Object)
|
||||||
RPDO1_MAP_1600 = 0x1600;
|
CANOPEN_RPDO1_MAP_1600 = 0x1600;
|
||||||
RPDO2_MAP_1601 = 0x1601;
|
CANOPEN_RPDO2_MAP_1601 = 0x1601;
|
||||||
RPDO3_MAP_1602 = 0x1602;
|
CANOPEN_RPDO3_MAP_1602 = 0x1602;
|
||||||
RPDO4_MAP_1603 = 0x1603;
|
CANOPEN_RPDO4_MAP_1603 = 0x1603;
|
||||||
|
|
||||||
TPDO1_MAP_1A00 = 0x1A00;
|
CANOPEN_TPDO1_MAP_1A00 = 0x1A00;
|
||||||
TPDO2_MAP_1A01 = 0x1A01;
|
CANOPEN_TPDO2_MAP_1A01 = 0x1A01;
|
||||||
TPDO3_MAP_1A02 = 0x1A02;
|
CANOPEN_TPDO3_MAP_1A02 = 0x1A02;
|
||||||
TPDO4_MAP_1A03 = 0x1A03;
|
CANOPEN_TPDO4_MAP_1A03 = 0x1A03;
|
||||||
|
|
||||||
PRODUCER_HEARTBEAT_TIME = 0x1017;
|
// Ti5 vendor-specific objects used through CANopen SDO.
|
||||||
|
CANOPEN_USER_SAVE_PARA_2000 = 0x2000;
|
||||||
|
CANOPEN_POSITION_OFFSET_2008 = 0x2008;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 子索引
|
// 子索引
|
||||||
@ -198,5 +155,3 @@ enum ObSubIndex {
|
|||||||
SUB_INDEX_6 = 6;
|
SUB_INDEX_6 = 6;
|
||||||
SUB_INDEX_7 = 7;
|
SUB_INDEX_7 = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
64
protos/cmvr/msgs/cia402.proto
Normal file
64
protos/cmvr/msgs/cia402.proto
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package cmvr.msgs;
|
||||||
|
|
||||||
|
// CiA402 object dictionary indexes shared by CANopen and EtherCAT CoE drives.
|
||||||
|
enum Cia402ObjectIndex {
|
||||||
|
CIA402_OBJECT_INDEX_ZERO = 0;
|
||||||
|
|
||||||
|
CIA402_ERROR_CODE_603F = 0x603F;
|
||||||
|
|
||||||
|
CIA402_CONTROL_WORD_6040 = 0x6040;
|
||||||
|
CIA402_STATUS_WORD_6041 = 0x6041;
|
||||||
|
|
||||||
|
CIA402_QUICK_STOP_OPTION_605A = 0x605A;
|
||||||
|
CIA402_SHUTDOWN_OPTION_605B = 0x605B;
|
||||||
|
CIA402_DISABLE_OPERATION_OPTION_605C = 0x605C;
|
||||||
|
CIA402_HALT_OPTION_605D = 0x605D;
|
||||||
|
CIA402_FAULT_REACTION_OPTION_605E = 0x605E;
|
||||||
|
|
||||||
|
CIA402_OPERATION_MODE_6060 = 0x6060;
|
||||||
|
CIA402_MODE_DISPLAY_6061 = 0x6061;
|
||||||
|
|
||||||
|
CIA402_POSITION_DEMAND_VALUE_6062 = 0x6062;
|
||||||
|
CIA402_ACTUAL_POSITION_6064 = 0x6064;
|
||||||
|
CIA402_MAX_FOLLOWING_ERROR_6065 = 0x6065;
|
||||||
|
CIA402_POSITION_WINDOW_6067 = 0x6067;
|
||||||
|
CIA402_POSITION_WINDOW_TIME_6068 = 0x6068;
|
||||||
|
|
||||||
|
CIA402_VELOCITY_DEMAND_VALUE_606B = 0x606B;
|
||||||
|
CIA402_ACTUAL_VELOCITY_606C = 0x606C;
|
||||||
|
CIA402_VELOCITY_WINDOW_606D = 0x606D;
|
||||||
|
CIA402_VELOCITY_WINDOW_TIME_606E = 0x606E;
|
||||||
|
CIA402_VELOCITY_THRESHOLD_606F = 0x606F;
|
||||||
|
CIA402_VELOCITY_THRESHOLD_TIME_6070 = 0x6070;
|
||||||
|
|
||||||
|
CIA402_TARGET_TORQUE_6071 = 0x6071;
|
||||||
|
CIA402_MAX_TORQUE_6072 = 0x6072;
|
||||||
|
CIA402_TORQUE_DEMAND_VALUE_6074 = 0x6074;
|
||||||
|
CIA402_MOTOR_RATED_TORQUE_6076 = 0x6076;
|
||||||
|
CIA402_ACTUAL_TORQUE_6077 = 0x6077;
|
||||||
|
CIA402_ACTUAL_CURRENT_6078 = 0x6078;
|
||||||
|
CIA402_DC_LINK_VOLTAGE_6079 = 0x6079;
|
||||||
|
|
||||||
|
CIA402_TARGET_POSITION_607A = 0x607A;
|
||||||
|
CIA402_HOME_OFFSET_607C = 0x607C;
|
||||||
|
CIA402_SOFTWARE_POSITION_LIMIT_607D = 0x607D;
|
||||||
|
CIA402_MAX_PROFILE_VELOCITY_607F = 0x607F;
|
||||||
|
|
||||||
|
CIA402_PROFILE_VELOCITY_6081 = 0x6081;
|
||||||
|
CIA402_PROFILE_ACCELERATION_6083 = 0x6083;
|
||||||
|
CIA402_PROFILE_DECELERATION_6084 = 0x6084;
|
||||||
|
CIA402_QUICK_STOP_DECELERATION_6085 = 0x6085;
|
||||||
|
CIA402_TORQUE_SLOPE_6087 = 0x6087;
|
||||||
|
|
||||||
|
CIA402_GEAR_RATIO_6091 = 0x6091;
|
||||||
|
CIA402_VELOCITY_OFFSET_60B1 = 0x60B1;
|
||||||
|
CIA402_TORQUE_OFFSET_60B2 = 0x60B2;
|
||||||
|
CIA402_INTERPOLATION_DATA_RECORD_60C1 = 0x60C1;
|
||||||
|
CIA402_INTERPOLATION_TIME_PERIOD_60C2 = 0x60C2;
|
||||||
|
CIA402_FOLLOWING_ERROR_ACTUAL_VALUE_60F4 = 0x60F4;
|
||||||
|
CIA402_TARGET_VELOCITY_60FF = 0x60FF;
|
||||||
|
|
||||||
|
CIA402_SUPPORTED_DRIVE_MODES_6502 = 0x6502;
|
||||||
|
}
|
||||||
@ -114,5 +114,3 @@ message MotorStatus {
|
|||||||
uint32 status_word = 44;
|
uint32 status_word = 44;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
import "cmvr/msgs/canopen.proto";
|
|
||||||
import "cmvr/msgs/motor.proto";
|
import "cmvr/msgs/motor.proto";
|
||||||
package cmvr.msgs;
|
package cmvr.msgs;
|
||||||
|
|
||||||
|
|||||||
@ -33,5 +33,6 @@ third_party/modbus/3.1.11
|
|||||||
third_party/visp/3.7.0
|
third_party/visp/3.7.0
|
||||||
third_party/mainif/0.0.5
|
third_party/mainif/0.0.5
|
||||||
third_party/matplotplusplus/1.2.0
|
third_party/matplotplusplus/1.2.0
|
||||||
|
third_party/ethercat/v1.7.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
133
script/ethercat/start_ethercat.sh
Executable file
133
script/ethercat/start_ethercat.sh
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
sudo script/ethercat/start_ethercat.sh [iface] [ethercat_dev] [start_wait_sec]
|
||||||
|
Any pure numeric argument is treated as start_wait_sec.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1
|
||||||
|
sudo script/ethercat/start_ethercat.sh 10
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1 10
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1 /dev/EtherCAT1
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1 /dev/EtherCAT0 10
|
||||||
|
sudo script/ethercat/start_ethercat.sh eno1 10 /dev/EtherCAT0
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
DEVICE_MODULES=generic IgH device module list.
|
||||||
|
IGH_ROOT=... Override bundled IgH install path.
|
||||||
|
ETHERCAT_CONF=... Override ethercatctl config path.
|
||||||
|
ETHERCAT_DEV=/dev/EtherCAT0 Override EtherCAT character device node.
|
||||||
|
START_WAIT_SEC=5 Seconds to wait for link/slave discovery.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo "error: please run with sudo." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFACE="${IFACE:-eno1}"
|
||||||
|
ETHERCAT_DEV="${ETHERCAT_DEV:-/dev/EtherCAT0}"
|
||||||
|
START_WAIT_SEC="${START_WAIT_SEC:-5}"
|
||||||
|
|
||||||
|
NON_NUMERIC_ARG_COUNT=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [[ "${arg}" =~ ^[0-9]+$ ]]; then
|
||||||
|
START_WAIT_SEC="${arg}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${NON_NUMERIC_ARG_COUNT}" in
|
||||||
|
0)
|
||||||
|
IFACE="${arg}"
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
ETHERCAT_DEV="${arg}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "error: unexpected argument: ${arg}" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
NON_NUMERIC_ARG_COUNT=$((NON_NUMERIC_ARG_COUNT + 1))
|
||||||
|
done
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
IGH_ROOT="${IGH_ROOT:-${REPO_ROOT}/dependency/x86/third_party/ethercat/v1.7.0}"
|
||||||
|
DEVICE_MODULES="${DEVICE_MODULES:-generic}"
|
||||||
|
ETHERCAT_CONF="${ETHERCAT_CONF:-${IGH_ROOT}/etc/ethercat.conf}"
|
||||||
|
ETHERCATCTL="${IGH_ROOT}/sbin/ethercatctl"
|
||||||
|
ETHERCAT="${IGH_ROOT}/bin/ethercat"
|
||||||
|
|
||||||
|
if [[ ! -d "/sys/class/net/${IFACE}" ]]; then
|
||||||
|
echo "error: network interface '${IFACE}' does not exist." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "${ETHERCATCTL}" ]]; then
|
||||||
|
echo "error: ethercatctl not found: ${ETHERCATCTL}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "${ETHERCAT}" ]]; then
|
||||||
|
echo "error: ethercat command not found: ${ETHERCAT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAC="$(cat "/sys/class/net/${IFACE}/address")"
|
||||||
|
|
||||||
|
echo "EtherCAT interface: ${IFACE}"
|
||||||
|
echo "EtherCAT MAC: ${MAC}"
|
||||||
|
echo "EtherCAT device: ${ETHERCAT_DEV}"
|
||||||
|
echo "IgH root: ${IGH_ROOT}"
|
||||||
|
echo "IgH config: ${ETHERCAT_CONF}"
|
||||||
|
echo "Device modules: ${DEVICE_MODULES}"
|
||||||
|
echo "Start wait: ${START_WAIT_SEC}s"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${ETHERCAT_CONF}")"
|
||||||
|
|
||||||
|
if [[ -f "${ETHERCAT_CONF}" ]]; then
|
||||||
|
echo "Stopping existing EtherCAT master with current config..."
|
||||||
|
"${ETHERCATCTL}" -c "${ETHERCAT_CONF}" stop >/dev/null 2>&1 || true
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >"${ETHERCAT_CONF}" <<EOF
|
||||||
|
MASTER0_DEVICE="${MAC}"
|
||||||
|
DEVICE_MODULES="${DEVICE_MODULES}"
|
||||||
|
UPDOWN_INTERFACES="${IFACE}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if command -v nmcli >/dev/null 2>&1; then
|
||||||
|
nmcli device disconnect "${IFACE}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ip addr flush dev "${IFACE}"
|
||||||
|
ip link set "${IFACE}" up
|
||||||
|
|
||||||
|
"${ETHERCATCTL}" -c "${ETHERCAT_CONF}" start
|
||||||
|
|
||||||
|
if [[ -e "${ETHERCAT_DEV}" ]]; then
|
||||||
|
chmod 666 "${ETHERCAT_DEV}"
|
||||||
|
else
|
||||||
|
echo "warning: ${ETHERCAT_DEV} not found; skip chmod. Check with: ls -l /dev/EtherCAT*" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
for ((i = 0; i < START_WAIT_SEC; ++i)); do
|
||||||
|
if "${ETHERCAT}" slaves 2>/dev/null | grep -qE '^[0-9]+[[:space:]]'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
"${ETHERCAT}" master
|
||||||
|
"${ETHERCAT}" slaves || true
|
||||||
62
script/ethercat/status_ethercat.sh
Executable file
62
script/ethercat/status_ethercat.sh
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
script/ethercat/status_ethercat.sh
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
IGH_ROOT=... Override bundled IgH install path.
|
||||||
|
ETHERCAT_CONF=... Override ethercatctl config path.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
IGH_ROOT="${IGH_ROOT:-${REPO_ROOT}/dependency/x86/third_party/ethercat/v1.7.0}"
|
||||||
|
ETHERCAT_CONF="${ETHERCAT_CONF:-${IGH_ROOT}/etc/ethercat.conf}"
|
||||||
|
ETHERCATCTL="${IGH_ROOT}/sbin/ethercatctl"
|
||||||
|
ETHERCAT="${IGH_ROOT}/bin/ethercat"
|
||||||
|
|
||||||
|
if [[ ! -x "${ETHERCATCTL}" ]]; then
|
||||||
|
echo "error: ethercatctl not found: ${ETHERCATCTL}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -x "${ETHERCAT}" ]]; then
|
||||||
|
echo "error: ethercat command not found: ${ETHERCAT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== ${ETHERCAT_CONF} =="
|
||||||
|
if [[ -f "${ETHERCAT_CONF}" ]]; then
|
||||||
|
sed -n '1,80p' "${ETHERCAT_CONF}"
|
||||||
|
else
|
||||||
|
echo "missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== kernel modules =="
|
||||||
|
lsmod | grep -E '(^ec_master|^ec_generic|^ec_)' || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== ethercatctl =="
|
||||||
|
"${ETHERCATCTL}" -c "${ETHERCAT_CONF}" status || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== master =="
|
||||||
|
"${ETHERCAT}" master || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== slaves =="
|
||||||
|
"${ETHERCAT}" slaves || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== pdos =="
|
||||||
|
"${ETHERCAT}" pdos || true
|
||||||
70
script/ethercat/stop_ethercat.sh
Executable file
70
script/ethercat/stop_ethercat.sh
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
sudo script/ethercat/stop_ethercat.sh [iface] [--restore-network]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1
|
||||||
|
sudo script/ethercat/stop_ethercat.sh eno1 --restore-network
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
IGH_ROOT=... Override bundled IgH install path.
|
||||||
|
ETHERCAT_CONF=... Override ethercatctl config path.
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(id -u)" -ne 0 ]]; then
|
||||||
|
echo "error: please run with sudo." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFACE="eno1"
|
||||||
|
RESTORE_NETWORK="false"
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "${arg}" in
|
||||||
|
--restore-network)
|
||||||
|
RESTORE_NETWORK="true"
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "error: unknown option: ${arg}" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
IFACE="${arg}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
IGH_ROOT="${IGH_ROOT:-${REPO_ROOT}/dependency/x86/third_party/ethercat/v1.7.0}"
|
||||||
|
ETHERCAT_CONF="${ETHERCAT_CONF:-${IGH_ROOT}/etc/ethercat.conf}"
|
||||||
|
ETHERCATCTL="${IGH_ROOT}/sbin/ethercatctl"
|
||||||
|
|
||||||
|
if [[ ! -x "${ETHERCATCTL}" ]]; then
|
||||||
|
echo "error: ethercatctl not found: ${ETHERCATCTL}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${ETHERCATCTL}" -c "${ETHERCAT_CONF}" stop
|
||||||
|
|
||||||
|
if [[ "${RESTORE_NETWORK}" == "true" ]]; then
|
||||||
|
ip link set "${IFACE}" up
|
||||||
|
if command -v nmcli >/dev/null 2>&1; then
|
||||||
|
nmcli device connect "${IFACE}" || true
|
||||||
|
fi
|
||||||
|
echo "Stopped EtherCAT and requested normal network restore on ${IFACE}."
|
||||||
|
else
|
||||||
|
echo "Stopped EtherCAT. Normal network restore skipped."
|
||||||
|
echo "Use '--restore-network' if this interface should return to NetworkManager."
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user