参数(Parameter)是节点的运行时配置值,允许在不修改代码的情况下调整节点行为。与硬编码常量不同,参数可以在启动时设置,也可以在运行时动态修改。
| 类型 | Python | C++ | 示例 |
|---|---|---|---|
| 整数 | int | int64_t | 42 |
| 浮点 | float | double | 3.14 |
| 字符串 | str | std::string | "hello" |
| 布尔 | bool | bool | true |
| 字节数组 | bytes | std::vector<uint8_t> | [0x01, 0x02] |
| 整数数组 | list[int] | std::vector<int64_t> | [1, 2, 3] |
| 浮点数组 | list[float] | std::vector<double> | [1.0, 2.5] |
| 字符串数组 | list[str] | std::vector<std::string> | ["a","b"] |
| 布尔数组 | list[bool] | std::vector<bool> | [true, false] |
#!/usr/bin/env python3
"""PID控制器节点 - 演示ROS2参数系统完整用法"""
import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import ParameterDescriptor, FloatingRange, IntegerRange
from std_msgs.msg import Float64
class PidControllerNode(Node):
"""PID控制器节点,所有参数可动态调整"""
def __init__(self):
super().__init__('pid_controller')
# ====== 声明参数(带描述和约束)======
# KP - 比例增益
self.declare_parameter(
'kp', 1.0,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_DOUBLE,
description='比例增益(Kp),控制响应速度',
floating_range=[FloatingRange(from_value=0.0, to_value=100.0)],
read_only=False
)
)
# KI - 积分增益
self.declare_parameter(
'ki', 0.1,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_DOUBLE,
description='积分增益(Ki),消除稳态误差',
floating_range=[FloatingRange(from_value=0.0, to_value=10.0)],
)
)
# KD - 微分增益
self.declare_parameter(
'kd', 0.05,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_DOUBLE,
description='微分增益(Kd),抑制超调',
floating_range=[FloatingRange(from_value=0.0, to_value=10.0)],
)
)
# 目标值
self.declare_parameter(
'setpoint', 0.0,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_DOUBLE,
description='目标设定值',
)
)
# 采样频率
self.declare_parameter(
'sample_rate', 100,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_INTEGER,
description='采样频率(Hz)',
integer_range=[IntegerRange(from_value=1, to_value=1000, step=1)],
)
)
# 输出限幅
self.declare_parameter(
'output_limit', 10.0,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_DOUBLE,
description='输出限幅值',
floating_range=[FloatingRange(from_value=0.0, to_value=100.0)],
)
)
# 启用标志
self.declare_parameter(
'enabled', True,
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_BOOL,
description='是否启用控制器',
)
)
# 话题名称
self.declare_parameter(
'topic_prefix', 'pid',
ParameterDescriptor(
type=rcl_interfaces.msg.ParameterType.PARAMETER_STRING,
description='话题前缀',
)
)
# 读取初始参数
self.kp = self.get_parameter('kp').value
self.ki = self.get_parameter('ki').value
self.kd = self.get_parameter('kd').value
self.setpoint = self.get_parameter('setpoint').value
self.output_limit = self.get_parameter('output_limit').value
self.enabled = self.get_parameter('enabled').value
# PID状态
self.integral = 0.0
self.prev_error = 0.0
# 发布者
self.output_pub = self.create_publisher(Float64, 'control_output', 10)
# 定时器
rate = self.get_parameter('sample_rate').value
self.timer = self.create_timer(1.0 / rate, self.control_loop)
# 添加参数变化回调
self.add_on_set_parameters_callback(self.parameter_callback)
self.get_logger().info(
f'PID控制器启动 - Kp={self.kp}, Ki={self.ki}, Kd={self.kd}, '
f'Setpoint={self.setpoint}'
)
def control_loop(self):
"""PID控制循环"""
if not self.enabled:
return
# 模拟:使用正弦波作为测量值
import math, time
measurement = math.sin(time.time()) * 5.0
# PID计算
error = self.setpoint - measurement
self.integral += error * (1.0 / self.get_parameter('sample_rate').value)
# 积分限幅(防积分饱和)
integral_limit = self.output_limit / max(self.ki, 0.001)
self.integral = max(-integral_limit, min(integral_limit, self.integral))
derivative = (error - self.prev_error) * self.get_parameter('sample_rate').value
output = self.kp * error + self.ki * self.integral + self.kd * derivative
# 输出限幅
output = max(-self.output_limit, min(self.output_limit, output))
self.prev_error = error
msg = Float64()
msg.data = output
self.output_pub.publish(msg)
def parameter_callback(self, params):
"""参数变化回调"""
for param in params:
if param.name == 'kp':
self.kp = param.value
self.get_logger().info(f'Kp 更新为: {self.kp}')
elif param.name == 'ki':
self.ki = param.value
self.get_logger().info(f'Ki 更新为: {self.ki}')
elif param.name == 'kd':
self.kd = param.value
self.get_logger().info(f'Kd 更新为: {self.kd}')
elif param.name == 'setpoint':
self.setpoint = param.value
self.integral = 0.0 # 重置积分
self.prev_error = 0.0
self.get_logger().info(f'设定值更新为: {self.setpoint}')
elif param.name == 'enabled':
self.enabled = param.value
if not self.enabled:
self.integral = 0.0
self.get_logger().info(f'控制器{"启用" if self.enabled else "禁用"}')
elif param.name == 'output_limit':
self.output_limit = param.value
self.get_logger().info(f'输出限幅更新为: {self.output_limit}')
return rclpy.parameter.SetParametersResult(successful=True)
def main(args=None):
rclpy.init(args=args)
node = PidControllerNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
参数可以通过YAML文件在启动时批量加载:
# config/pid_controller.yaml
pid_controller:
ros__parameters:
kp: 2.5
ki: 0.3
kd: 0.1
setpoint: 10.0
sample_rate: 200
output_limit: 15.0
enabled: true
topic_prefix: "left_motor_pid"
# 启动时加载YAML
ros2 run my_pkg pid_controller --ros-args \
--params-file config/pid_controller.yaml
# 或在launch文件中加载
import launch
import launch_ros.actions
node = launch_ros.actions.Node(
package='my_pkg',
executable='pid_controller',
parameters=['config/pid_controller.yaml']
)
// pid_controller_cpp.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float64.hpp"
#include "rcl_interfaces/msg/set_parameters_result.hpp"
#include <functional>
#include <string>
class PidControllerCpp : public rclcpp::Node {
public:
PidControllerCpp() : Node("pid_controller_cpp"),
integral_(0.0), prev_error_(0.0) {
// 声明参数
this->declare_parameter("kp", 1.0);
this->declare_parameter("ki", 0.1);
this->declare_parameter("kd", 0.05);
this->declare_parameter("setpoint", 0.0);
this->declare_parameter("output_limit", 10.0);
this->declare_parameter("enabled", true);
// 读取参数
kp_ = this->get_parameter("kp").as_double();
ki_ = this->get_parameter("ki").as_double();
kd_ = this->get_parameter("kd").as_double();
// 参数回调
param_callback_handle_ = this->add_on_set_parameters_callback(
std::bind(&PidControllerCpp::on_parameter_change, this,
std::placeholders::_1));
// 发布者
output_pub_ = this->create_publisher<std_msgs::msg::Float64>(
"control_output_cpp", 10);
// 定时器
timer_ = this->create_wall_timer(
std::chrono::milliseconds(10),
std::bind(&PidControllerCpp::control_loop, this));
RCLCPP_INFO(this->get_logger(),
"C++ PID控制器启动 - Kp=%.2f, Ki=%.2f, Kd=%.2f",
kp_, ki_, kd_);
}
private:
void control_loop() {
if (!this->get_parameter("enabled").as_bool()) return;
double setpoint = this->get_parameter("setpoint").as_double();
double measurement = 0.0; // 简化
double error = setpoint - measurement;
double limit = this->get_parameter("output_limit").as_double();
integral_ += error * 0.01;
double derivative = (error - prev_error_) / 0.01;
double output = kp_ * error + ki_ * integral_ + kd_ * derivative;
output = std::max(-limit, std::min(limit, output));
prev_error_ = error;
auto msg = std_msgs::msg::Float64();
msg.data = output;
output_pub_->publish(msg);
}
rcl_interfaces::msg::SetParametersResult on_parameter_change(
const std::vector<rclcpp::Parameter> ¶ms) {
for (const auto& p : params) {
if (p.get_name() == "kp") {
kp_ = p.as_double();
RCLCPP_INFO(this->get_logger(), "Kp -> %.2f", kp_);
} else if (p.get_name() == "ki") {
ki_ = p.as_double();
RCLCPP_INFO(this->get_logger(), "Ki -> %.2f", ki_);
} else if (p.get_name() == "kd") {
kd_ = p.as_double();
RCLCPP_INFO(this->get_logger(), "Kd -> %.2f", kd_);
}
}
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
return result;
}
rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr output_pub_;
rclcpp::TimerBase::SharedPtr timer_;
OnSetParametersCallbackHandle::SharedPtr param_callback_handle_;
double kp_, ki_, kd_, integral_, prev_error_;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PidControllerCpp>());
rclcpp::shutdown();
return 0;
}
# 列出节点所有参数
ros2 param list /pid_controller
# 获取参数值
ros2 param get /pid_controller kp
# 设置参数值(运行时动态修改)
ros2 param set /pid_controller kp 2.5
# 查看参数类型和描述
ros2 param describe /pid_controller kp
# 导出所有参数为YAML
ros2 param dump /pid_controller
# 从YAML文件加载参数
ros2 param load /pid_controller config/pid_controller.yaml
运行PID控制器节点,使用 ros2 param set 动态调整Kp/Ki/Kd,观察控制输出的变化。
使用 ros2 param dump 导出参数,修改后用 ros2 param load 重新加载。
在参数回调中添加验证逻辑:如果Kp设为负数,拒绝修改并返回错误。
经验值:+150 XP