坐标转换算法示例

1. 坐标转换算法示例

本节为坐标变换算法使用示例,完整源码见 examples/cpp/ex_coord_transform.cpp。该示例演示四元数↔欧拉角、欧拉角→旋转矩阵、位姿矩阵↔旋转矩阵、坐标系转换(正解/逆解)以及逆解结果通过 servoJ 下发运动。完整代码如下:

C++
/**
 * ex_coord_transform.cpp
 * @brief 坐标变换算法示例程序 —— 展示常用的坐标系转换接口
 * @attention
 *   - 需要控制器连接正常、已上电
 *   - 可用命令行参数 -ip 指定控制器 IP(默认 192.168.1.13)
 * @note 运行步骤
 *       编译: cd build && cmake .. && make
 *       运行: ./ex_coord_transform
 */

// MSVC: 启用 M_PI / M_PI_2 等 POSIX 数学常量
#define _USE_MATH_DEFINES
#include "tl_api.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef _WIN32
#include 
#endif

// 跨平台兜底:部分编译器在严格 C++ 模式下不定义 M_PI / M_PI_2
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923
#endif

// ============================================================
// 工具函数
// ============================================================

void print_result(const std::string& msg, int ret)
{
    if (ret == tl::Result::SUCCESS) {
        std::cout << "  [成功] " << msg << std::endl;
    } else {
        std::cout << "  [失败] " << msg << ",错误码: " << ret << std::endl;
    }
}

void print_separator(const std::string& title)
{
    std::cout << "\n========== " << title << " ==========" << std::endl;
}

/// @brief 打印 double 向量
void print_vector(const std::string& name, const std::vector& v, int width = 6)
{
    std::cout << "  " << name << " [" << v.size() << "] = {";
    for (size_t i = 0; i < v.size(); ++i) {
        std::cout << std::fixed << std::setprecision(4) << v[i];
        if (i < v.size() - 1) std::cout << ", ";
    }
    std::cout << "}" << std::endl;
}

/// @brief 打印矩阵(行主序)
void print_matrix(const std::string& name, const std::vector& m, int rows, int cols)
{
    std::cout << "  " << name << " [" << m.size() << "]:" << std::endl;
    for (int r = 0; r < rows; ++r) {
        std::cout << "    ";
        for (int c = 0; c < cols; ++c) {
            int idx = r * cols + c;
            if (idx < (int)m.size()) {
                std::cout << std::fixed << std::setprecision(4) << std::setw(10) << m[idx] << " ";
            }
        }
        std::cout << std::endl;
    }
}

// ============================================================
// 主函数
// ============================================================

int main(int argc, char* argv[])
{
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
#endif
    // 解析命令行参数: ./ex_coord_transform [-ip 192.168.1.13],默认 192.168.1.13
    std::string ip = "192.168.1.13";
    for (int i = 1; i + 1 < argc; ++i) {
        if (std::string(argv[i]) == "-ip" || std::string(argv[i]) == "--ip") {
            ip = argv[i + 1];
            break;
        }
    }
    // ---- 获取 SDK 版本 ----
    {
        std::string version = tl::get_library_version();
        std::cout << "[信息] SDK 版本: " << version << std::endl;
    }

    // ---- 双端口连接 ----
    print_separator("连接控制器");
    tl::SOCKETFD sock = tl::connect_robot(ip, "6001");
    if (sock < 0) {
        std::cerr << "[ERROR] 连接 6001 端口失败" << std::endl;
        return -1;
    }
    tl::SOCKETFD sock_servo = tl::connect_robot(ip, "7000");
    if (sock_servo < 0) {
        std::cerr << "[ERROR] 连接 7000 端口失败" << std::endl;
        tl::disconnect_robot(sock);
        return -1;
    }
    std::cout << "[信息] 双端口连接成功 (6001=" << sock << ", 7000=" << sock_servo << ")" << std::endl;

    tl::Result ret;

    // ---- 上电(servoJ 需要上电状态)----
    {
        print_separator("清错与上电");
        tl::set_current_mode(sock, tl::RobotMode::TEACH);
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        tl::clear_error(sock);
        std::this_thread::sleep_for(std::chrono::milliseconds(300));
        tl::set_servo_poweroff(sock);
        std::this_thread::sleep_for(std::chrono::milliseconds(300));

        tl::ServoState servo_state;
        ret = tl::get_servo_state(sock, servo_state);
        if (ret != tl::Result::SUCCESS) {
            std::cerr << "[ERROR] 获取伺服状态失败: " << ret << std::endl;
            tl::disconnect_robot(sock);
            tl::disconnect_robot(sock_servo);
            return -1;
        }
        std::cout << "  [信息] 当前伺服状态: " << static_cast(servo_state)
                  << " (STOP=停止 READY=就绪 ALARM=报警 RUNNING=运行)" << std::endl;

        if (servo_state == tl::ServoState::ALARM) {
            std::cerr << "[ERROR] 伺服处于报警状态,无法上电" << std::endl;
            tl::disconnect_robot(sock);
            tl::disconnect_robot(sock_servo);
            return -1;
        }

        if (servo_state != tl::ServoState::RUNNING) {
            std::cout << "  [信息] 伺服未运行,执行上电流程..." << std::endl;
            ret = tl::set_servo_state(sock, tl::ServoState::READY);
            if (ret != tl::Result::SUCCESS && ret != tl::Result::OPERATION_NOT_ALLOWED) {
                std::cerr << "[ERROR] 设置伺服就绪失败: " << ret << std::endl;
                tl::disconnect_robot(sock);
                tl::disconnect_robot(sock_servo);
                return -1;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(500));

            ret = tl::set_servo_poweron(sock);
            if (ret != tl::Result::SUCCESS) {
                std::cerr << "[ERROR] 上电失败: " << ret << std::endl;
                tl::disconnect_robot(sock);
                tl::disconnect_robot(sock_servo);
                return -1;
            }
            std::this_thread::sleep_for(std::chrono::seconds(2));
            std::cout << "  [信息] 上电成功" << std::endl;
        } else {
            std::cout << "  [信息] 伺服已运行 (servo_state=RUNNING)" << std::endl;
        }
    }

    // ============================================================
    // 1. 四元数 ↔ 欧拉角 (RPY) — 正反验证
    // ============================================================
    // 单位说明:
    //   四元数 [x, y, z, w]      — 无量纲(归一化)
    //   欧拉角 [roll, pitch, yaw] — 弧度 rad
    //   旋转矩阵 (3×3 行主序)     — 无量纲
    print_separator("四元数 ↔ 欧拉角");

    // 1a. 欧拉角 → 四元数 → 欧拉角(正反验证)
    {
        std::cout << "--- 1a. RPY ↔ 四元数 正反验证 ---" << std::endl;
        std::vector rpy_in = {0.2, 0.3, 0.5}; // rad
        std::vector quat, rpy_out;

        ret = tl::get_rpy2quat(sock, rpy_in, quat);
        print_result("tl::get_rpy2quat(RPY→四元数)", ret);
        if (ret != tl::Result::SUCCESS) return -1;
        print_vector("  输入欧拉角 [rad]", rpy_in);
        print_vector("  中间四元数 [x, y, z, w]", quat);

        ret = tl::get_quat2rpy(sock, quat, rpy_out);
        print_result("tl::get_quat2rpy(四元数→RPY)", ret);
        if (ret != tl::Result::SUCCESS) return -1;
        print_vector("  还原欧拉角 [rad]", rpy_out);

        if (rpy_out.size() >= 3 && rpy_in.size() >= 3) {
            double err = fabs(rpy_out[0] - rpy_in[0])
                       + fabs(rpy_out[1] - rpy_in[1])
                       + fabs(rpy_out[2] - rpy_in[2]);
            std::cout << "  正反变换误差 (应接近0): " << err << std::endl;
        }
    }

    // 1b. 欧拉角 → 旋转矩阵
    {
        std::cout << "\n--- 1b. get_rpy2r: 欧拉角转旋转矩阵 ---" << std::endl;
        std::vector rpy = {0.0, 0.0, M_PI_2}; // rad
        std::vector rot_mat;

        ret = tl::get_rpy2r(sock, rpy, rot_mat);
        print_result("get_rpy2r", ret);
        print_vector("输入欧拉角 [rad]", rpy);
        if (ret == tl::Result::SUCCESS) {
            print_matrix("输出旋转矩阵 (3x3, 行主序, 无量纲)", rot_mat, 3, 3);
        }
    }

    // ============================================================
    // 2. 位姿矩阵 ↔ 旋转矩阵 — 正反验证
    // ============================================================
    // 单位说明:
    //   旋转矩阵 R (3×3)     — 无量纲
    //   平移向量 T [Tx,Ty,Tz] — 毫米 mm
    //   位姿矩阵 TR (4×4)  — R 部分无量纲,T 部分单位 mm
    // 位姿矩阵 (行主序 16 元素):
    //   [Rxx Rxy Rxz Tx]  第 0 行
    //   [Ryx Ryy Ryz Ty]  第 1 行
    //   [Rzx Rzy Rzz Tz]  第 2 行
    //   [  0   0   0  1]  第 3 行
    print_separator("位姿矩阵 ↔ 旋转矩阵");

    // 2a. TR → R → TR(正反验证)
    {
        std::cout << "--- 2a. TR ↔ R 正反验证 ---" << std::endl;
        // 构造一个绕 Z 转 90°、平移 (100, 200, 300) mm 的位姿矩阵
        double cos90 = cos(M_PI_2), sin90 = sin(M_PI_2);
        std::vector tr_in = {
            cos90, -sin90, 0.0, 100.0,
            sin90,  cos90, 0.0, 200.0,
            0.0,    0.0,   1.0, 300.0,
            0.0,    0.0,   0.0, 1.0
        };
        std::vector R, tr_out;

        ret = tl::get_tr2r(sock, tr_in, R);
        print_result("tl::get_tr2r(TR→R)", ret);
        if (ret != tl::Result::SUCCESS) return -1;
        print_matrix("输入位姿矩阵 (4x4, R无量纲, T:mm)", tr_in, 4, 4);
        print_matrix("中间旋转矩阵 (3x3, 无量纲)", R, 3, 3);

        ret = tl::get_r2tr(sock, R, tr_out);
        print_result("tl::get_r2tr(R→TR)", ret);
        if (ret != tl::Result::SUCCESS) return -1;
        print_matrix("还原位姿矩阵 (4x4, R无量纲, T:mm)", tr_out, 4, 4);

        // 说明: get_tr2r 只提取旋转矩阵,平移 T 在此丢弃;
        // get_r2tr 的输入不含平移,还原出的 TR 平移部分恒为 0。
        // SDK 无 "R+T 构造 TR" 接口,完整位姿矩阵需由调用方自行拼装 T。
        std::cout << "  注: get_tr2r 仅输出旋转矩阵,平移 T 不参与往返;" << std::endl;
        std::cout << "      还原位姿矩阵的平移部分为 0,需调用方另行拼装。" << std::endl;

        // 对比旋转部分(TR→R→TR 链路仅保证旋转信息无损)
        // 注意: 行主序 4x4 中旋转块位于 0,1,2, 4,5,6, 8,9,10,
        //       位置 3/7/11 是平移列,不能按连续 0..8 遍历。
        if (tr_out.size() >= 16 && tr_in.size() >= 16) {
            const int rot_idx[9] = {0, 1, 2, 4, 5, 6, 8, 9, 10};
            double r_err = 0.0;
            for (int k = 0; k < 9; ++k) {
                r_err += fabs(tr_out[rot_idx[k]] - tr_in[rot_idx[k]]);
            }
            std::cout << "  旋转矩阵误差 (应接近0): " << r_err << std::endl;
        }
    }

    // ============================================================
    // 3. 坐标系转换(获取机器人当前位姿在不同坐标系下的表示)
    // ============================================================
    // 单位说明:
    //   关节坐标 [J1..J6] — 度 °
    //   基坐标 [X,Y,Z,Rx,Ry,Rz] — 位移 mm,姿态 rad
    //   工具/用户坐标 — 同基坐标
    print_separator("坐标系转换");
    {
        std::cout << "--- 3. get_origin_coord_to_target_coord ---" << std::endl;
        std::cout << "  坐标系: 0=关节(°)  1=基坐标(mm+rad)  2=工具(mm+rad)  3=用户(mm+rad)" << std::endl;

        // 从关节坐标系(0) 转换到基坐标系(1)
        // 关节角度: J1=10°, J2=20°, J3=30°, J4=0°, J5=0°, J6=0°
        std::vector joint_pos = {10, 20, 30, 0, 0, 0};
        std::vector cartesian_pos;
        bool convert_state = false;

        ret = tl::get_origin_coord_to_target_coord(sock, tl::Coord::JOINT, joint_pos, tl::Coord::BASE, cartesian_pos, convert_state);
        print_result("tl::get_origin_coord_to_target_coord(关节→基坐标) 正解", ret);
        print_vector("输入关节角度 [°]", joint_pos);
        if (ret == tl::Result::SUCCESS) {
            std::cout << "  输出基坐标 — 前3位为位移[mm],后3位为姿态[rad]:" << std::endl;
            print_vector("  基坐标", cartesian_pos);
            if (cartesian_pos.size() >= 6) {
                std::cout << "    位移 X=" << cartesian_pos[0] << "mm, Y=" << cartesian_pos[1]
                          << "mm, Z=" << cartesian_pos[2] << "mm" << std::endl;
                std::cout << "    姿态 Rx=" << cartesian_pos[3] << "rad, Ry=" << cartesian_pos[4]
                          << "rad, Rz=" << cartesian_pos[5] << "rad" << std::endl;
                std::cout << "    姿态换算(°): Rx=" << cartesian_pos[3] * 180.0 / M_PI
                          << ", Ry=" << cartesian_pos[4] * 180.0 / M_PI
                          << ", Rz=" << cartesian_pos[5] * 180.0 / M_PI << std::endl;
            }
        }

        // 逆解: 基坐标系(1) → 关节坐标系(0)
        // 使用正解输出的基坐标作为输入,验证正反解一致性
        if (ret == tl::Result::SUCCESS && cartesian_pos.size() >= 6) {
            std::vector joint_back;
            ret = tl::get_origin_coord_to_target_coord(sock, tl::Coord::BASE, cartesian_pos, tl::Coord::JOINT, joint_back, convert_state);
            print_result("tl::get_origin_coord_to_target_coord(基坐标→关节) 逆解", ret);
            std::cout << "  输入基坐标:" << std::endl;
            std::cout << "    位移 [mm]: X=" << cartesian_pos[0] << ", Y=" << cartesian_pos[1]
                      << ", Z=" << cartesian_pos[2] << std::endl;
            std::cout << "    姿态 [rad]: Rx=" << cartesian_pos[3] << ", Ry=" << cartesian_pos[4]
                      << ", Rz=" << cartesian_pos[5] << std::endl;
            if (ret == tl::Result::SUCCESS) {
                print_vector("  逆解关节角度 [°]", joint_back);
                // 对比原始关节角度,计算误差
                double err = 0.0;
                for (size_t i = 0; i < joint_pos.size() && i < joint_back.size(); ++i) {
                    err += fabs(joint_pos[i] - joint_back[i]);
                }
                std::cout << "  正反解误差 (应接近0): " << err << std::endl;
            }
        }
    }

    // ============================================================
    // 4. 逆解关节坐标 → servoJ 下发运动
    // ============================================================
    // 演示流程: 给定基坐标 → 逆解到关节角 → 通过 servoJ 发送到控制器
    print_separator("逆解 + servoJ 运动");

    // 取一个基坐标点,逆解为关节角
    std::vector target_pose = {300, 0, 100, 3.141, 0, 0}; // X=300mm, Y=0, Z=100mm
    std::vector target_joints;
    bool convert_state2 = false;

    ret = tl::get_origin_coord_to_target_coord(sock, tl::Coord::BASE, target_pose, tl::Coord::JOINT, target_joints, convert_state2);
    print_result("逆解: 基坐标→关节", ret);
    if (ret == tl::Result::SUCCESS) {
        print_vector("目标基坐标 [mm, rad]", target_pose);
        print_vector("逆解关节角度 [°]", target_joints);

        // 通过 servoJ 发送关节角度到控制器
        // servoJ 需要连接 7000 端口,已在开头连接
        std::cout << "\n  --- 通过 servoJ 下发逆解结果 ---" << std::endl;

        // servoJ 约束参数(7 元素: 6 本体轴 + 1 外部轴)
        // 注意: SDK 底层协议要求 servoJ 相关向量固定为 7 元素
        std::vector vmax = {30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0};
        std::vector amax = {60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0};
        std::vector jmax = {100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0};

        ret = tl::open_servoJ(sock_servo, vmax, amax, jmax);
        print_result("open_servoJ", ret);
        if (ret == tl::Result::SUCCESS) {
            // 逐步发送逆解出的关节角度,确保 servoJ 跟踪到位
            // 分 50 步从当前角度平滑过渡到目标角度
            // q 必须为 7 元素,与 open_servoJ 一致
            std::vector current = {0, 0, 0, 0, 0, 0, 0};
            int steps = 50;
            for (int i = 0; i < steps; ++i) {
                std::vector q(7);
                size_t n_axes = target_joints.size();
                if (n_axes > q.size()) n_axes = q.size();
                for (size_t j = 0; j < n_axes; ++j) {
                    q[j] = current[j] + (target_joints[j] - current[j]) * (i + 1) / steps;
                }
                // 多余元素由 q(7) 初始化保持 0,无需额外处理
                tl::set_servoJ_pos(sock_servo, q);
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
            // servoJ 是实时跟踪模式,等待固定时间确保运动到位
            std::cout << "  [信息] 等待 servoJ 运动到位..." << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(5));

            ret = tl::close_servoJ(sock_servo);
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            print_result("close_servoJ", ret);
        }
    }

    // ---- 断开连接 ----
    print_separator("断开连接");
    tl::set_current_mode(sock, tl::RobotMode::TEACH);
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    tl::disconnect_robot(sock);
    tl::disconnect_robot(sock_servo);
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    std::cout << "[信息] 已断开连接,机械臂切换到示教模式" << std::endl;

    std::cout << "\n[信息] 坐标变换示例程序运行完毕" << std::endl;
    return 0;
}