本文介绍Franka机器人C++代码库——libfranka的使用方法。包含笔者在使用过程中的一点心得体会。笔者基于libfranka 0.8.0 版本进行开发调试,仅限于 Linux 系统。相关介绍也可以参考此文。
这里假设读者已具备以下基础知识:
(内容持续更新中…)
环境要求:
Franka机器人的核心功能及开发工具安装流程请参考此文。
这里首先介绍下libfranka的源码安装编译方法,注意如果已经采用apt-get方法安装ros-kinetic-libfranka,那么此步骤可以跳过,libfranka已经被默认安装在ROS目录下(/opt/ros/kinetic/)。但是,如果想要安装最新版本的libfranka,必须采用源码编译,且在此之前必须删除之前安装的libfranka和franka_ros以防冲突。
sudo apt remove "*libfranka*"
第1步,删除完成后,首先安装依赖库:
sudo apt install build-essential cmake git libpoco-dev libeigen3-dev
第2步,找到一个合适的位置,从Github下载源码,并进入libfranka源文件夹:
git clone --recursive https://github.com/frankaemika/libfranka
cd libfranka
第3步,在源文件夹下创造一个名为build的路径,并在其中启动CMake编译:
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
libfranka自带的例程对于理解libfrank非常有效,本节简单介绍下例程的使用方法。机器人上电后,首先通过浏览器进入Desk界面,此时机器人是锁定状态(黄色灯亮),在Desk界面接触锁定,进入准备运动模式(蓝色灯亮)。
以Linux系统(Ubuntu 16.04)为例,首先找到libfranka文件夹所在路径。如果读者是采用apt-get install安装的,那么去/opt/ros/kinetic/下找;如果读者采用源码编译,那么去保持源码的路径下找。
在libfranka路径下首先找到 examples 文件夹,该文件夹下保存了所有的示例C++代码。如要运行,在libfranka/build/examples/ 路径下已有生成的可执行文件。此时保持机器人控制柜与计算机工作站连接,且机器人处于准备运动模式(蓝色灯亮),执行如下指令:
./communication_test "172.16.0.2"
这里以通信测试为例,其它类似,注意第一个参数是机器人的IP地址。官方推荐使用机器人控制柜与计算机工作站连接,当然连接机器人底座亦可,只不过IP地址要换成hostname,笔者不曾尝试过。开始运行时例程会提示保持急停按钮在操作者手中,并按Enter继续。可以看到机器人运动到一个位姿,终端输出通信测试结果。
Franka机器人控制必须以实时(Real-Time)方式进行,如上图所示。libfranka中,定义了一个名为"franka"的命名空间,其中最重要的是“franka::Robot”类,该类用于与机器人通信(I/O)。libfranka提供多种控制信号输入模式,笛卡尔空间位置、速度,关节空间位置、速度,关节力矩。这些均由一个多态的“franka::Robot::control”方法实现。
控制算法主要通过回调函数实现,官方例程如下,这是一个lambda闭包表达式:
double time = 0.0;
auto control_callback = [&time](const franka::RobotState& robot_state,
franka::Duration time_step) -> franka::JointPositions {
time += time_step.toSec(); // Update time at the beginning of the callback.
franka::JointPositions output = getJointPositions(time);
if (time >= max_time) {
// Return MotionFinished at the end of the trajectory.
return franka::MotionFinished(output);
}
return output;
}
注意Franka机器人工作频率是1kHz,也就是说回调函数必须在1ms之内执行完成。回调函数有两个参数
通过 franka::Duration 类对象的 toSec() 方法返回时间信息(double,通常是0.001)可以实现插值。上述例程的控制模式为 franka::JointPositions 。结束这个回调函数循环使用 franka::MotionFinished 该函数的输入参数是当前控制模式下最后一个控制指令(如例程中式 franka::JointPositions 类型变量),返回值也是也是该类型指令对象,只不过该指令对象的 motion_finished 属性被赋值为 true。
无论是运动生成还是控制,信号都必须满足关节空间和笛卡尔空间的约束条件。约束条件在FCI文档中给出。如果任一约束条件未能满足,程序将抛出一个错误并终止运动。例如如果用户给定的初始位置与机器人实际当前位置差太远,系统将抛出 start_pose_invalid 错误并终止运动。
libfranka提供4种类型的运动生成器,如下图所示:
下面给出一个简单的关节位置运动生成示例,其它类似:
#include
#include
#include
#include
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << "IP-Address" << std::endl;
return -1;
}
try
{
franka::Robot robot(argv[1]);
robot.setCollisionBehavior(
{
{
20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0}}, {
{
20.0, 20.0, 20.0, 20.0, 20.0, 20.0, 20.0}},
{
{
10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0}}, {
{
10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0}},
{
{
20.0, 20.0, 20.0, 20.0, 20.0, 20.0}}, {
{
20.0, 20.0, 20.0, 20.0, 20.0, 20.0}},
{
{
10.0, 10.0, 10.0, 10.0, 10.0, 10.0}}, {
{
10.0, 10.0, 10.0, 10.0, 10.0, 10.0}});
robot.setJointImpedance({
{
3000, 3000, 3000, 2500, 2500, 2000, 2000}});
robot.setCartesianImpedance({
{
3000, 3000, 3000, 300, 300, 300}});
std::cout << "Please make sure to have the user stop button at hand!" << std::endl
<< "Press Enter to continue..." << std::endl;
std::cin.ignore();
std::array<double, 7> initial_position;
double time = 0.0;
robot.control([&initial_position, &time](const franka::RobotState& robot_state,
franka::Duration period) -> franka::JointPositions {
time += period.toSec();
if (time == 0.0) {
initial_position = robot_state.q_d;
}
double delta_angle = M_PI / 8.0 * (1 - std::cos(M_PI / 2.5 * time));
franka::JointPositions output = {
{
initial_position[0], initial_position[1],
initial_position[2], initial_position[3],
initial_position[4], initial_position[5],
initial_position[6] + delta_angle}};
if (time >= 5) {
std::cout << std::endl << "Finished motion, shutting down robot" << std::endl;
return franka::MotionFinished(output);
}
return output;
});
}
catch(const franka::Exception& e)
{
std::cerr << e.what() << std::endl;
return -1;
}
return 0;
}
首先声明一个 franka::Robot 类对象,初始化参数为机器人IP地址。然后设定其初始化参数,注意参数设定必须在执行控制指令前,切不可在控制过程中。
机器人运动生成与控制采用 franka::Robot::control 方法实现,该方法的输入参数是一个函数模板,即回调函数,如前所述。回调函数内完成1ms控制周期内的控制指令后,通过 franka::MotionFinished 函数结束控制过程。
具体操作及其它功能可参考官方文档及例程。
事实上,如果单纯使用运动生成器,那么 franka::Robot::control 会默认调用内部的阻抗控制器(kJointImpedance 和 kCartesianImpedance)。当然,也可以同时使用,即 franka::Robot::control 方法可以有两个参数,如下述官方模板:
std::function<franka::Torques(const franka::RobotState&, franka::Duration)>
my_external_controller_callback;
// Define my_external_controller_callback
...
std::function<franka::JointVelocities(const franka::RobotState&, franka::Duration)>
my_external_motion_generator_callback;
// Define my_external_motion_generator_callback
...
try {
franka::Robot robot("" );
// only a motion generator
robot.control(my_external_motion_generator_callback);
// only an external controller
robot.control(my_external_controller_callback);
// a motion generator and an external controller
robot.control(my_external_motion_generator_callback, my_external_controller_callback);
} catch (franka::Exception const& e) {
std::cout << e.what() << std::endl;
return -1;
}
return 0;
}
可以看出,当发生错误时,libfranka会抛出“franka::Exception”异常。
对于控制器设计,libfranka提供了关节力矩作为输入信号。一个简单的零力控制方法如下,这里机器人本身提供了重力补偿:
robot.control([&](const franka::RobotState&, franka::Duration) -> franka::Torques {
return {
{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
});
注意无论是自己设计运动生成器还是控制器,都必须保证信号足够平滑,也就是说两个控制周期之间输入的变化不要太大。官方手册给出了franka机器人的一些约束条件。为了应对不太理想的网络连接,libfranka本身提供了信号处理机制来平滑化输入信号,减少其控制周期间的变化。这是一个比率限制(rate limiting)和一个低通滤波(low-pass filter)。在libfranka 0.5.0 版本之后,低通滤波就被基本放弃了。事实上 franka::Robot::control 方法还有两个额外的参数用于此功能,详情可参考官方手册相关章节。注意第一次测试自定义控制器时尽量不要使能这些平滑机制,因为可能会造成不稳定,方法如下:
// With rate limiting and filter
robot.control(my_external_controller);
// Identical to the previous line (default true, 100.0 Hz cutoff)
robot.control(my_external_controller, true, 100.0);
// Without rate limiting but with low-pass filter (100.0 Hz)
robot.control(my_external_controller, false);
// Without rate limiting and without low-pass filter
robot.control(my_external_controller, false, 1000.0);
对于实时通信过程中较大量的丢包问题,这些平滑机制也于事无补。官方例程中给出了一个通信性能测试例程(communication_test),在libfranka/build/example中。franka::RobotState 结构体的属性 control_command_success_rate 可以返回实时通信成功率。
libfranka运动生成器和控制器的作用机理可参考下图:
控制机器人的信号实际上是带有下标d的信号(desired),用户可以实时获取该信号来检查信号被滤波或者是否有丢包现象。
Franka 自带重点摩擦补偿机制。由于有关节力矩传感器,使得其零力控制性能相对于UR之类的机器人更加灵敏顺畅。打开示教模式(白色灯亮),用户可以通过机器人末端的Pilot实现拖动示教,并记录一些信息。libfranka中,机器人状态由 franka::RobotState 结构体管理,实时读取的频率也是1kHz,读取数据由 franka::Robot::read 函数管理。
franka::RobotState 结构体提供了丰富的信息,主要包括三类:
对于一个简单的读取操作,可以使用 franka::Robot::readOnce 函数来简化流程:
franka::RobotState state = robot.readOnce();
持续的实时读取依然需要回调函数,官方例程如下:
size_t count = 0;
robot.read([&count](const franka::RobotState& robot_state) {
// Printing to std::cout adds a delay. This is acceptable for a read loop such as this,
// but should not be done in a control loop.
std::cout << robot_state << std::endl;
return count++ < 100;
});
注意,franka::Robot:;read 函数的定义如下:
void franka::Robot::read(std::function< bool(const RobotState &)> read_callback)
可以看出,其参数是一个返回布尔型数据的回调函数,当返回 false 时,状态读取停止。注意在示教模式下(白色灯亮),不可以设置机器人的阻抗控制参数。
实际上,读取依靠的是 franka::RobotState 结构体,即 franka::Robot::read 函数的输入参数。而该参数在 franka::Robot::control 函数中也有,用起来都是当作反映机器人当前状态信息的常量。因此,很多时候并不需要单独使用 franka::Robot::read 函数专门读取数据,在控制环中也可以读取。但是,如前所述,每个控制周期内留个程序执行的时间一般小于300微妙,对于上述例程中用count输出信息的操作在实际控制环路中要尽量避免,使用读取函数就可以接受。
具体操作及其它功能可参考例程“echo_robot_state.cpp”。
libfranka自带的模型库可以提供运动学与动力学参数,包括:
模型库由franka::Model类管理,注意使用时需要加入头文件:
#include
注意初始化一个franka::Model对象通常采用franka::Robot对象的loadModel()方法,返回值是一个franka::Model对象:
franka::Robot robot(argv[1]);
franka::Model model = robot.loadModel();
使用机器人模型库读者可以计算任意姿态下的参数,不必是机器人当前姿态下的参数。具体用法可参考例程。
Franka 自带的电爪可以做很多事情,使用非常方便。电爪的控制是非实时(blocking)的,如下图所示。
电爪的控制由 franka::Gripper 类管理。使用该类时注意加入头文件:
#include
#include
...
franka::Gripper gripper("" );
franka::Robot robot("" );
电爪的IP地址和机器人是一样的。
一些机器人的参数不能在实时控制环路中修改,必须在环路外已非实时指令的方式设定。一些典型的指令是:
末端执行器也可以在Desk界面的Settings中设置,默认是自带的电爪。