Wiki
從零編寫模擬硬體外掛
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
本節用 Jazzy 的框架管理介面實現雙關節模擬硬體。保持關節名稱與上層控制器不變,只替換後端。
1.程式目標
read 按時間步長推進一階響應,write 取框架命令並檢查、限幅。介面由框架根據 URDF 建立,外掛不手動返回指向成員陣列的介面控制代碼。
2.示例 1:完整標頭檔案
儲存為 include/jazzy_control_hardware/two_joint_system.hpp:
#ifndef JAZZY_CONTROL_HARDWARE__TWO_JOINT_SYSTEM_HPP_
#define JAZZY_CONTROL_HARDWARE__TWO_JOINT_SYSTEM_HPP_
#include <array>
#include <string>
#include "hardware_interface/system_interface.hpp"
namespace jazzy_control_hardware
{
class TwoJointSystem : public hardware_interface::SystemInterface
{
public:
hardware_interface::CallbackReturn on_init(
const hardware_interface::HardwareComponentInterfaceParams & params) override;
hardware_interface::CallbackReturn on_configure(const rclcpp_lifecycle::State &) override;
hardware_interface::CallbackReturn on_activate(const rclcpp_lifecycle::State &) override;
hardware_interface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State &) override;
hardware_interface::return_type read(const rclcpp::Time &, const rclcpp::Duration &) override;
hardware_interface::return_type write(const rclcpp::Time &, const rclcpp::Duration &) override;
protected:
std::array<std::string, 2> position_name_, velocity_name_;
std::array<double, 2> q_{}, dq_{}, applied_{};
};
}
#endif
3.示例 2:完整實現
儲存為 src/two_joint_system.cpp:
#include "jazzy_control_hardware/two_joint_system.hpp"
#include <algorithm>
#include <cmath>
#include "pluginlib/class_list_macros.hpp"
namespace jazzy_control_hardware
{
hardware_interface::CallbackReturn TwoJointSystem::on_init(
const hardware_interface::HardwareComponentInterfaceParams & params)
{
if (SystemInterface::on_init(params) != hardware_interface::CallbackReturn::SUCCESS) {
return hardware_interface::CallbackReturn::ERROR;
}
if (info_.joints.size() != 2) {return hardware_interface::CallbackReturn::ERROR;}
for (std::size_t i = 0; i < 2; ++i) {
const auto & joint = info_.joints[i];
if (joint.command_interfaces.size() != 1 ||
joint.command_interfaces[0].name != "position" ||
joint.state_interfaces.size() != 2 ||
joint.state_interfaces[0].name != "position" ||
joint.state_interfaces[1].name != "velocity") {
return hardware_interface::CallbackReturn::ERROR;
}
position_name_[i] = joint.name + "/position";
velocity_name_[i] = joint.name + "/velocity";
}
// 不手动导出指向 q_ 的句柄;由框架根据 URDF 创建接口。
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn TwoJointSystem::on_configure(const rclcpp_lifecycle::State &)
{
q_.fill(0.0); dq_.fill(0.0); applied_.fill(0.0);
for (std::size_t i = 0; i < 2; ++i) {
set_state(position_name_[i], q_[i]);
set_state(velocity_name_[i], dq_[i]);
set_command(position_name_[i], q_[i]);
}
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn TwoJointSystem::on_activate(const rclcpp_lifecycle::State &)
{
for (std::size_t i = 0; i < 2; ++i) {
q_[i] = get_state(position_name_[i]);
if (!std::isfinite(q_[i])) {return hardware_interface::CallbackReturn::ERROR;}
applied_[i] = q_[i]; set_command(position_name_[i], q_[i]);
}
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::CallbackReturn TwoJointSystem::on_deactivate(const rclcpp_lifecycle::State &)
{
applied_ = q_; dq_.fill(0.0);
for (std::size_t i = 0; i < 2; ++i) {
set_command(position_name_[i], q_[i]); set_state(velocity_name_[i], 0.0);
}
return hardware_interface::CallbackReturn::SUCCESS;
}
hardware_interface::return_type TwoJointSystem::read(
const rclcpp::Time &, const rclcpp::Duration & period)
{
const double dt = period.seconds();
if (dt <= 0.0) {return hardware_interface::return_type::OK;}
const double alpha = 1.0 - std::exp(-dt / 0.2);
for (std::size_t i = 0; i < 2; ++i) {
const double step = alpha * (applied_[i] - q_[i]);
q_[i] += step; dq_[i] = step / dt;
set_state(position_name_[i], q_[i]);
set_state(velocity_name_[i], dq_[i]);
}
return hardware_interface::return_type::OK;
}
hardware_interface::return_type TwoJointSystem::write(
const rclcpp::Time &, const rclcpp::Duration &)
{
std::array<double, 2> next{};
for (std::size_t i = 0; i < 2; ++i) {
const double target = get_command(position_name_[i]);
if (!std::isfinite(target)) {return hardware_interface::return_type::ERROR;}
next[i] = std::clamp(target, -1.0, 1.0);
}
applied_ = next;
return hardware_interface::return_type::OK;
}
}
PLUGINLIB_EXPORT_CLASS(jazzy_control_hardware::TwoJointSystem, hardware_interface::SystemInterface)
3.1.關鍵函式說明
on_init(params) 先呼叫父類,讓框架儲存和處理硬體描述,然後檢查兩關節與介面組合。引數型別為 HardwareComponentInterfaceParams,與所選 Jazzy 原始碼版本對應。
on_configure 初始化教學模型,並用 set_state、set_command 設定框架儲存。真實裝置的編碼器反饋不能在配置時隨意清零。
on_activate 讀取已有位置,將初始目標與反饋對齊。on_deactivate 保持模型當前位置。
read(time, period) 使用實際 period 計算模型步長,再將狀態寫入介面。write 從 get_command 取得目標,先檢查完整一組,再更新 applied_。
3.2.成員陣列與框架介面是否相同
q_ 是模擬裝置狀態,applied_ 是裝置已接收的目標。框架介面另有儲存;set_state 把模型值釋出到介面,get_command 從介面取出目標。它們不是自動共享同一塊 double 地址。
4.示例 3:外掛註冊與構建
儲存 hardware_plugins.xml:
<library path="jazzy_control_hardware">
<class name="jazzy_control_hardware/TwoJointSystem"
type="jazzy_control_hardware::TwoJointSystem"
base_class_type="hardware_interface::SystemInterface"/>
</library>
替換完整 CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(jazzy_control_hardware)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ament_cmake REQUIRED)
find_package(hardware_interface REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
add_library(${PROJECT_NAME} SHARED src/two_joint_system.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(${PROJECT_NAME} hardware_interface pluginlib rclcpp rclcpp_lifecycle)
pluginlib_export_plugin_description_file(hardware_interface hardware_plugins.xml)
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
install(DIRECTORY include/ DESTINATION include)
install(DIRECTORY urdf launch config DESTINATION share/${PROJECT_NAME})
ament_export_include_directories(include)
ament_export_libraries(${PROJECT_NAME})
ament_export_dependencies(hardware_interface pluginlib rclcpp rclcpp_lifecycle)
ament_package()
再把 two_joint.urdf 的 hardware/plugin 改為:
<plugin>jazzy_control_hardware/TwoJointSystem</plugin>
5.編譯執行與預期現象
停止舊例項,再執行:
cd ~/ros2_control_jazzy_ws
colcon build --symlink-install --packages-select jazzy_control_hardware
source install/setup.bash
ros2 launch jazzy_control_hardware two_joint.launch.py
另一個已 source 的終端傳送:
ros2 topic pub --once /forward_position_controller/commands \
std_msgs/msg/Float64MultiArray "{data: [0.5, -0.3]}"
ros2 topic echo /joint_states
反饋應逐漸接近 [0.5, -0.3]。0.2 秒是教學模型的時間常數,不是對真實裝置的辨識結果;[-1,1] 是本模型明確採用的位置範圍。
原始碼對照:ros2_control/hardware_interface/include/hardware_interface/hardware_component_interface.hpp、ros2_control_demos/example_1/hardware/rrbot.cpp。
返回 Jazzy 總覽 · 上一篇:YAML與spawner啟動過程 · 下一篇:狀態介面與命令介面原始碼