sim2real!so-arm100 机械臂 Mujoco 仿真与实机控制

视频讲解:

sim2real!so-arm100 机械臂 Mujoco 仿真与实机控制

结合前面做好的so-arm100以及封装好的机械臂控制代码(封装的代码已经考虑了offset和实际的关节限位)

接下来我们用Mujoco仿真,将仿真中的关节位置实时发送给实机的机械臂进行控制。

仓库代码分成两个,mujoco部分作为控制客户端,so-arm100部分作为被控服务端。通信传输使用zmq的进程间通信。

Mujoco部分:GitHub - LitchiCheng/mujoco-learning

SO-ARM100部分:GitHub - LitchiCheng/SO-ARM100: Some Test code on SO-ARM100

需要注意:so-arm100如果参考过lerobot进行标定的话,其零位位置和mujoco中的home是不一致的,存在偏置,如下图

所以需要进行offset偏置,要不然实际控制会不一致。

首先在mujoco中的控制代码就比较简单,直接实时获取qpos,然后通过通信转发关节位置即可。

复制代码
import mujoco
import time
import mujoco_viewer
import numpy as np
import math
import zmq
import json

# # ZMQ配置
# ZMQ_IP = "127.0.0.1"  # 绑定到所有可用网络接口
# ZMQ_PORT = "5555"
# context = zmq.Context()
# socket = context.socket(zmq.PUB)
# socket.bind(f"tcp://{ZMQ_IP}:{ZMQ_PORT}")

# print(f"仿真端发布者启动,绑定到:tcp://{ZMQ_IP}:{ZMQ_PORT}")

# ZMQ IPC配置 - 使用进程间通信协议
ZMQ_IPC_PATH = "ipc:///tmp/robot_arm_comm.ipc"  # IPC文件路径
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind(ZMQ_IPC_PATH)  # 绑定到IPC路径

print(f"仿真端发布者启动,绑定到:{ZMQ_IPC_PATH}")

def send_data_to_robot(processed_q_deg):
    """将预处理后的关节角通过ZMQ发布"""
    # 数据封装为JSON
    data = {
        "joint_pos": processed_q_deg,  # 关节角(角度)
        "timestamp": time.time(),      # 时间戳(用于同步)
        "control_mode": "position"     # 控制模式(位置控制/速度控制)
    }
    # 转换为JSON字符串并发布
    json_data = json.dumps(data)
    socket.send_string(json_data)

class Test(mujoco_viewer.CustomViewer):
    def __init__(self, path):
        super().__init__(path, 1.5, azimuth=135, elevation=-30)
        self.path = path
    
    def runBefore(self):
        pass
    
    def runFunc(self):
        sim_joint_rad = self.data.qpos[:6].copy()
        sim_joint_deg = [math.degrees(q) for q in sim_joint_rad]
        send_data_to_robot(sim_joint_deg)
        time.sleep(0.01)  # 控制发送频率

try:
    test = Test("./model/trs_so_arm100/scene.xml")
    test.run_loop()
except KeyboardInterrupt:
    print("仿真程序被用户中断")
finally:
    # 关闭ZMQ连接
    socket.close()
    context.term()

在电机控制的程序中做好接收即可

复制代码
import zmq
import json
import sys
import os
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
from hardware import FeetechMotor as fm

import time

# 初始化机械臂
motor = fm.FeetechMotor(1, "/dev/ttyACM0")
motor.connect()

# # ZMQ配置
# ZMQ_IP = "127.0.0.1"
# ZMQ_PORT = "5555"
# context = zmq.Context()
# socket = context.socket(zmq.SUB)
# socket.connect(f"tcp://{ZMQ_IP}:{ZMQ_PORT}")
# # 订阅所有消息(空字符串表示订阅所有主题)
# socket.setsockopt_string(zmq.SUBSCRIBE, "")

# print(f"等待仿真端发布数据... 连接到:tcp://{ZMQ_IP}:{ZMQ_PORT}")

# ZMQ IPC配置 - 使用进程间通信协议
ZMQ_IPC_PATH = "ipc:///tmp/robot_arm_comm.ipc"  # 与发布者相同的IPC路径
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect(ZMQ_IPC_PATH)  # 连接到IPC路径
socket.setsockopt_string(zmq.SUBSCRIBE, "")  # 订阅所有消息

print(f"控制端订阅者启动,连接到:{ZMQ_IPC_PATH}")

# 接收并解析数据
try:
    while True:
        # 接收数据
        data = socket.recv_string().strip()
        if not data:
            continue
        
        # 解析JSON数据
        json_data = json.loads(data)
        joint_pos_deg = json_data["joint_pos"]
        control_mode = json_data["control_mode"]
        
        # 发送控制指令给实际机械臂
        if control_mode == "position":
            for i in range(1, 7):
                motor.setMotorId(i)
                motor.setSpeed(1000)  # 设置目标速度为1000步/秒
                motor.printFlag(False)  # 打开打印
                if i == 1 or i == 2:
                    motor.setPID(16, 16, 0)
                else:   
                    motor.setPID(32, 32, 0)  # 设置PID参数
                motor.setPosition(joint_pos_deg[i - 1])
                # print(f"Motor ID {i} set to position {joint_pos_deg[i - 1]}°")
        
        # 短暂延迟,避免CPU占用过高
        # time.sleep(0.01)
        
except KeyboardInterrupt:
    print("程序被用户中断")
except Exception as e:
    print(f"接收/控制错误:{e}")
finally:
    # 关闭连接与机械臂
    socket.close()
    context.term()
    motor.disconnect()

同时运行两边的程序,在mujoco中控制机械臂关节及使用拖动均可以看到实际机械臂也同样进行1:1运动。