【Unitree Go1】Low Level制御の概要について

初めに


本記事では、Low Level制御における概要を解説させていただきます。
角度制御によるstand upプログラムの実例も交えた解説になります。

 

環境


環境は、次のようなものを想定しています。

  • 機種: R&D(Edu), R&D+(Edu+)(Proではないもの)
  • unitree_legged_sdk: v3.5.1以上(本記事ではv3.8.6を使用)

※ Low Level制御を行う場合、付属のハンガーキャスターを使用し、ハンガーキャスターによって十分に安全を確認できるまでは絶対にハンガーキャスターを使用してください

 

1.Low Level制御の概要


Low Levelの制御方法

Low Level制御でロボットに指令を送る方法は3通りあります。
1.角度指令
角度指令を与えてロボットを制御する方法で、example_positionがこれに当たります。この場合、角度剛性Kpもセットで指定する必要があります。
2.速度指令
速度指令を与えてロボットを制御する方法で、example_velocityがこれに当たります。この場合、速度剛性Kdもセットで指定する必要があります。
3.トルク指令
モーターのトルクで制御する方法で、example_torqueがこれに当たります。セットとなる剛性はなく、一番難易度が高い制御になると思います。

物理パラメーターについて

Go1の物理パラメータについては、以下のgithubの情報が最も詳しいです:
unitree_ros/robots/go1_description/xacro/const.xacro – github

Unitree社の提供している歩行パターンについて

デフォルトの走行パターンは、バイナリデータとなっており、参考にできない形になっています。
しかし、unitree_guide – githubでは、別にlow level制御による歩行パターンの例があり、歩行パターン生成の参考になります。(ただし安定性は非常に低い走行です)

Low Levelの実行環境について

十分に安全を確認できるまでは、ハンガーキャスターに吊るした状態で行ってください。
また、L1+L2+startを押して、デフォルトの走行モードをOFFにしてから行ってください。

 

2.実例:角度制御によるstand upプログラム


実際に弊社で作成したプログラムは下記のようなものになります:

1.最初の姿勢 → 伏せの姿勢 (10秒かけて)
2.伏せの姿勢 → 立ちの姿勢 (10秒かけて)
という流れのプログラムになります。
※最初にやる場合は、ハンガーキャスターから吊るした状態で行ってください

 

今回使用したコード(クリックすると開きます)
/*****************************************************************
 Copyright (c) 2020, Unitree Robotics.Co.Ltd. All rights reserved.
 Modified by TechShare.inc in 2023.
******************************************************************/

#include "unitree_legged_sdk/unitree_legged_sdk.h"
#include <math.h>
#include <iostream>
#include <stdio.h>
#include <stdint.h>

using namespace std;
using namespace UNITREE_LEGGED_SDK;

const double PI=3.141592653589;

class Custom
{
public:
    Custom(uint8_t level) : safe(LeggedType::Go1),
                            udp(level, 8090, "192.168.123.10", 8007)
    {
      udp.InitCmdData(cmd);
    }
    void UDPRecv();
    void UDPSend();
    void RobotControl();

    Safety safe;
    UDP udp;
    LowCmd cmd = {0};
    LowState state = {0};
    double time_consume = 0;
    int motiontime = 0;
    int rate_count = 0;
    float dt = 0.002;     // 0.001~0.01

    double posCmd[4][3];
    const double P_STIFF = 80.0;
    double posStiffCmd[4][3] = {{P_STIFF, P_STIFF, P_STIFF}, {P_STIFF, P_STIFF, P_STIFF}, {P_STIFF, P_STIFF, P_STIFF}, {P_STIFF, P_STIFF, P_STIFF}};
    double init_pos[4][3];

    void set_position(double position[4][3])
    {
        cmd.motorCmd[FR_0].q = position[0][0];
        cmd.motorCmd[FR_1].q = position[0][1];
        cmd.motorCmd[FR_2].q = position[0][2];
        cmd.motorCmd[FL_0].q = position[1][0];
        cmd.motorCmd[FL_1].q = position[1][1];
        cmd.motorCmd[FL_2].q = position[1][2];
        cmd.motorCmd[RR_0].q = position[2][0];
        cmd.motorCmd[RR_1].q = position[2][1];
        cmd.motorCmd[RR_2].q = position[2][2];
        cmd.motorCmd[RL_0].q = position[3][0];
        cmd.motorCmd[RL_1].q = position[3][1];
        cmd.motorCmd[RL_2].q = position[3][2];
        return;
    }

    void set_posStiffness(double posS[4][3])
    {
        cmd.motorCmd[FR_0].Kp = posS[0][0];
        cmd.motorCmd[FR_1].Kp = posS[0][1];
        cmd.motorCmd[FR_2].Kp = posS[0][2];
        cmd.motorCmd[FL_0].Kp = posS[1][0];
        cmd.motorCmd[FL_1].Kp = posS[1][1];
        cmd.motorCmd[FL_2].Kp = posS[1][2];
        cmd.motorCmd[RR_0].Kp = posS[2][0];
        cmd.motorCmd[RR_1].Kp = posS[2][1];
        cmd.motorCmd[RR_2].Kp = posS[2][2];
        cmd.motorCmd[RL_0].Kp = posS[3][0];
        cmd.motorCmd[RL_1].Kp = posS[3][1];
        cmd.motorCmd[RL_2].Kp = posS[3][2];
        return;
    }

    void get_position(double (&position)[4][3])
    {
        udp.GetRecv(state);
        position[0][0] = state.motorState[FR_0].q;
        position[0][1] = state.motorState[FR_1].q;
        position[0][2] = state.motorState[FR_2].q;
        position[1][0] = state.motorState[FL_0].q;
        position[1][1] = state.motorState[FL_1].q;
        position[1][2] = state.motorState[FL_2].q;
        position[2][0] = state.motorState[RR_0].q;
        position[2][1] = state.motorState[RR_1].q;
        position[2][2] = state.motorState[RR_2].q;
        position[3][0] = state.motorState[RL_0].q;
        position[3][1] = state.motorState[RL_1].q;
        position[3][2] = state.motorState[RL_2].q;
        return;
    }

    double Fuse[4][3] ={{-0.02, 1.3, -2.8}, {-0.02, 1.3, -2.8}, {-0.02, 1.3, -2.8}, {-0.02, 1.3, -2.8}};

    double Tachi[4][3] ={{-0.27, 0.8, -1.6}, {-0.27, 0.8, -1.6}, {-0.27, 0.8, -1.6}, {-0.27, 0.8, -1.6}};
};

void Custom::UDPRecv()
{  
    udp.Recv();
}

void Custom::UDPSend()
{  
    udp.Send();
}

double jointCurveInterpolation(double initPos, double targetPos, double rate)
{
    double p;
    rate = std::min(std::max(rate, 0.0), 1.0);
    // p = initPos * (1 - rate) + targetPos * rate;
    double theta = (1 + cos(rate*PI))/2;
    p = initPos * theta + targetPos * (1 -theta);
    return p;
}

void positionCurve(double position[4][3], double initPosition[4][3], double targetPosition[4][3], double rate)
{
    for (int i = 0; i < 4; i++){
        for (int j = 0; j < 3; j++){
            position[i][j] = jointCurveInterpolation(initPosition[i][j], targetPosition[i][j], rate);
        }
    }
    return;
}

void printPosition(double Pos[4][3])
{
    for (int i = 0; i < 4; i++){
        for (int j = 0; j < 3; j++){
            printf("%f, ", Pos[i][j]);
        }
    }
    printf("\n");
    return;
}

void substitution(double (&Pos0)[4][3], double (&Pos1)[4][3])
{
    std::copy(&Pos1[0][0], &Pos1[0][0] + 4*3, &Pos0[0][0]);
    return;
}

void Custom::RobotControl() 
{
    motiontime += 2;
    udp.GetRecv(state);

    double pos[4][3];
    get_position(pos);

    if( motiontime < 500){
        get_position(init_pos);
        if(motiontime%100 == 0){
          printf("init pos: ");
          printPosition(pos);
        }
    }
    else{
        if(motiontime < 1000){
            substitution(posCmd, init_pos);
        }
        else if(motiontime < 11000){
            rate_count += 2;
            double rate = rate_count/10000.0 ;
            positionCurve(posCmd, init_pos, Fuse, rate);
        }
        else if(motiontime < 11100){
            rate_count = 0;
        }
        else if(motiontime < 21100){
            rate_count += 2;
            double rate = rate_count/10000.0 ;
            positionCurve(posCmd, Fuse, Tachi, rate);
        }
        else{
            printf("end move");
        }

        if(motiontime%100 == 0){
          printf("current pos: ");
          printPosition(pos);
          printf("command pos: ");
          printPosition(posCmd);
        }
    
        set_position(posCmd);
        set_posStiffness(posStiffCmd);
    }

    udp.SetSend(cmd);
}

int main(int argc, char const *argv[])
{
    std::cout << "Communication level is set to LOW-level." << std::endl
              << "WARNING: Make sure the robot is hung up." << std::endl
              << "Press Enter to continue..." << std::endl;
    std::cin.ignore();

    Custom custom(LOWLEVEL);
    // InitEnvironment();
    LoopFunc loop_control("control_loop", custom.dt,    boost::bind(&Custom::RobotControl, &custom));
    LoopFunc loop_udpSend("udp_send",     custom.dt, 3, boost::bind(&Custom::UDPSend,      &custom));
    LoopFunc loop_udpRecv("udp_recv",     custom.dt, 3, boost::bind(&Custom::UDPRecv,      &custom));

    loop_udpSend.start();
    loop_udpRecv.start();
    loop_control.start();

    while(1){
        sleep(10);
    };

    return 0; 
}

 

なお、角度剛性Kpとは、NM/radで表現される値であり、目標のモーター角と現在のモーター角の差分[rad]からトルク[NM]を算出するものとなります。
example_positionでは、5.0NM/radに設定されていますが、本プログラムでは80.0NM/radに設定しています。

 

おわりに

Low Level制御における概要を見てきました。
この記事が少しでも役に立てたのなら幸いです。

 

関連記事

【Unitree Go1】外部PC上から無線で unitree_ros_to_real を実行する:ローレベル制御

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です