ROS达妙机械臂main函数
翻译过来叫“节点句柄”。你可以把它理解为一部**“公共对讲机”**。有了它,你的程序才能向外发布话题(比如机械臂的实时位置),或者接收外界的指令。,它专门用来读取只属于你这个程序的私有配置文件(也就是我们在 yaml 文件里写的串口号。程序会用红色字体打印一句错误提示,然后直接结束(:这是一个安全检查。如果串口没插好,或者电机没连上,:这是所有 ROS C++ 程序必须写的第一句话。的初始化函数:
ros::init(argc, argv, "damiao_hardware_node");
ros::NodeHandle nh;
ros::NodeHandle nh_private("~");
-
ros::init:你的程序来到 ROS 世界,第一件事就是去注册中心登记名字:“大家好,我是damiao_hardware_node”。 -
nh与nh_private("~"):你向系统申请了两部通讯工具。-
nh(公共大喇叭):用来向全网广播你的关节状态(/joint_states)。 -
nh_private(私密对讲机):带波浪号~,专门用来悄悄读取 launch 文件里加载的属于你自己的motor_config.yaml配置文件,绝不会和其他节点弄混。
-
damiao::DmHW robot;
if (!robot.init(nh, nh_private))
{
ROS_ERROR("Failed to initialize Damiao Hardware Interface!");
return -1;
}
init:串口初始化,使能电机,开启读取电机数据线程
注册关节,位置接口:ros上层得知要控制电机,只需要王cmd_pos的地址写数据就行
controller_manager::ControllerManager cm(&robot, nh);
为什么必须要传 &robot?(解决内存共享问题)
-
robot是什么:它是你自己写的DmHW类的实例。在这个实例的内存里,存着你之前注册的各种接口(jointStateInterface_、positionJointInterface_),以及电机实际位置 (pos) 和目标位置 (cmd_pos) 的物理内存地址。 -
传递
&robot的底层逻辑:ControllerManager(cm) 本身只是一个纯数学算法库,它生来就是没有数据的。当你使用取地址符&把robot传给它时,cm就直接获得了访问robot内部变量的权限。 -
使用结果:从此以后,
cm内部的控制算法在计算时,不需要调用任何函数去向robot索要数据,而是直接去读写robot内存里的pos和cmd_pos变量。这在 C++ 中叫做“零拷贝数据交互”,效率极高。
2. 为什么必须要传 nh?(解决 ROS 通信问题)
-
nh是什么:ros::NodeHandle是 ROS 系统提供的通信接口。有了它,C++ 程序才能在 ROS 网络中发布话题(Topic)或创建服务(Service)。 -
传递
nh的底层逻辑:cm需要接收外部的指令(比如你想启动哪个控制器、停止哪个控制器)。通过接收你传进来的nh,cm会在后台自动为你生成并注册一系列标准的 ROS 服务(Services)。 -
使用结果:只要这行代码一执行,你在终端里输入
rosservice list,就会立刻看到多出了一大堆服务,比如:-
/controller_manager/load_controller -
/controller_manager/switch_controller -
/controller_manager/list_controllers此时,外部程序就可以通过调用这些服务,来操控你的 C++ 代码。
-
这行代码仅仅是完成了“初始化”。在真正的机器人运行中,它是按以下 3 个步骤被使用的:
第一步:代码层面的初始化(就是这行代码本身) 当 C++ 的 main 函数执行到这一行时,cm 对象被创建。它绑定了底层硬件内存(&robot),并在 ROS 网络中注册了控制服务(利用 nh)。此时,算法处于待机状态,没有任何运算发生。
第二步:外部唤醒(Launch 文件中的 spawner 节点) 在你的 .launch 文件中,有一行专门针对它的指令: <node name="controller_spawner" pkg="controller_manager" type="spawner" args="joint_state_controller arm_controller"/>
-
这个叫
spawner的脚本会去调用第一步里cm创建的 ROS 服务。 -
它对
cm下达指令:“请立刻加载joint_state_controller和arm_controller这两个控制算法,并让它们开始运行。” -
此时,
cm会根据指令,在内存中把这两个算法实例化。
第三步:循环调用运算(C++ 代码中的 cm.update) 在 C++ 的 while(ros::ok()) 死循环中,你写了 cm.update(current_time, elapsed_time);。
-
每次执行到这句话,
cm就会触发刚才加载的那两个控制算法。 -
算法会利用第一步拿到的
&robot权限,读取电机的实际角度。 -
算法(如
arm_controller)接收 MoveIt 传来的目标轨迹,利用 PID 公式计算出当前的控制量,并直接把计算结果覆盖写回到&robot的cmd_pos内存地址中。 -
随后,你的
robot.write()函数就会把这个新的cmd_pos变成 CAN 数据发给达妙电机。
ros::AsyncSpinner spinner(1);
spinner.start();
ros::AsyncSpinner:这是 ROS 提供的一个异步多线程处理器(Spinner 意为“轮询器”)。
(1):括号里的 1 代表你想分配几个独立的子线程。写 1 就意味着你在主线程(跑 while 循环的那个)之外,额外开辟了 1 个平行的后台线程,专门用来处理 ROS 的消息回调。
在后面有while循环,需要有独立的子线程去处理movelt发过来的数据
spinner.start();
函数作用:启动这个后台线程。
ros::Rate rate(200);
ros::Time last_time = ros::Time::now();
代表频率是 200Hz。意思是告诉程序:“接下来你要干的活,必须每秒钟执行 200 次
记录下程序运行到这一行的当前绝对时间。
ROS_INFO("Damiao Hardware Node Started!");
打印出一句话:“达妙硬件节点已启动!”
while (ros::ok())
{
死循环
ros::Time current_time = ros::Time::now();
ros::Duration elapsed_time = current_time - last_time;
last_time = current_time;
看一眼现在几点了,存到 current_time(当前时间)里。
用现在的时刻减去刚才记录的时刻,算出了一个时间段 elapsed_time(流逝的时间差)。
控制管家 cm 里的 PID 算法(比如算速度、算加速度)极度依赖物理时间。如果时间算不准,算出来的控制指令就是错的,机械臂就会疯狂发抖。
robot.read(current_time, elapsed_time); // 读电机
调用你写的底层硬件代码,通过串口去问达妙电机:“你现在实际上转到多少度了?” 然后把度数存进共享内存里。
cm.update(current_time, elapsed_time); // 算控制
控制管家(ControllerManager)出场!它把刚才读上来的真实角度,和 MoveIt 下发的目标角度做对比。结合时间差(elapsed_time),它经过一顿复杂的数学运算,算出:“为了消除误差,下一刻电机应该转到某某位置。” 并把这个新位置写进内存。
robot.write(current_time, elapsed_time); // 写电机
再次调用底层代码,把刚才管家算出来的新目标位置,打包成 CAN 通信的十六进制指令,顺着数据线狠狠地发给物理电机。电机收到后,就会嗡嗡地转动一丝丝距离。
更多推荐


所有评论(0)