无人机实体#

DroneEntity 是一种四旋翼飞行器,其驱动量是四个螺旋桨的转速。与机械臂不同,它不接受关节力矩或位置:为每个螺旋桨设置以 RPM(转/分钟)为单位的角速度,Genesis World 会把这些转速转换为使无人机升空和转向的空气动力。

下面的示例使用 Genesis World 自带的 Crazyflie 2.X 模型。关于类 API,参见 DroneEntity 参考。

螺旋桨 RPM 如何转化为运动#

每个仿真步中,Genesis World 读取每个螺旋桨的指令 RPM,并对该螺旋桨所属的连杆施加两件事:

  • 推力,沿螺旋桨局部 +Z 轴的力:F = KF · rpm²。

  • 反作用力矩,绕同一轴的偏航力矩:τ = KM · rpm² · spin,其中 spin 对逆时针螺旋桨为 +1,对顺时针螺旋桨为 -1。

KF(推力系数)和 KM(力矩系数)来自无人机的 URDF,因此是模型的固定属性。推力随 RPM 的平方增长,所以控制是非线性的:RPM 翻倍,升力大约变为四倍。

由此得出两个推论,它们构成了四旋翼控制的全部基础:

  • 共同 RPM 决定高度。 四个螺旋桨等速旋转产生纯垂直推力。存在一个悬停 RPM,使总推力与重力平衡。

  • 差速 RPM 决定姿态。 让一侧相对另一侧加速会使无人机倾斜(roll/pitch),从而改变推力方向并产生水平运动。让逆时针一对相对顺时针一对加速会留下净偏航力矩,使无人机原地旋转。

Note

无人机实体不支持碰撞检测。它们只通过上述螺旋桨力和重力与世界交互。

最小示例:悬停#

用于程控飞行的完整可运行脚本是 examples/drone/fly.py。下面是最小的能让无人机保持空中的程序:创建它,然后在每一步把每个螺旋桨设为悬停 RPM。

import genesis as gs

gs.init(backend=gs.cpu)

scene = gs.Scene(
    sim_options=gs.options.SimOptions(dt=0.01),  # gravity defaults to (0, 0, -9.81) m/s2
)
scene.add_entity(gs.morphs.Plane())
drone = scene.add_entity(
    gs.morphs.Drone(file="urdf/drones/cf2x.urdf", pos=(0.0, 0.0, 0.5)),
)

scene.build()

hover_rpm = 14468.429  # balances gravity for the CF2X model
for _ in range(1000):
    drone.set_propellers_rpm([hover_rpm, hover_rpm, hover_rpm, hover_rpm])
    scene.step()

悬停 RPM 因模型而异:它是使 4 · KF · rpm² 等于无人机重量的值,对于自带的 Crazyflie 2.X,约为 14468 RPM。

无人机 morph#

morph 是通过 gs.morphs.Drone 加载的 URDF。默认值与 Crazyflie 模型匹配,因此通常只需 file 和 pos:

drone = scene.add_entity(
    gs.morphs.Drone(
        file="urdf/drones/cf2x.urdf",
        model="CF2X",  # "CF2X", "CF2P", or "RACE"
        pos=(0.0, 0.0, 0.5),  # meters, Z-up
        euler=(0.0, 0.0, 0.0),  # scipy extrinsic x-y-z, degrees
        propellers_link_name=("prop0_link", "prop1_link", "prop2_link", "prop3_link"),
        propellers_spin=(-1, 1, -1, 1),  # per propeller: -1 = CW, +1 = CCW
    ),
)

propellers_link_name 固定了螺旋桨被索引的顺序:set_propellers_rpm 按位置把 RPM 数组映射到这些连杆。propellers_spin 给出每个螺旋桨的旋转方向,它决定偏航反作用力矩的符号。RACE 模型会在内部反转全部四个转向。

自带的模型有:

model

file

说明

CF2X

urdf/drones/cf2x.urdf

Crazyflie 2.X,X 型旋翼布局

CF2P

urdf/drones/cf2p.urdf

Crazyflie 2.X,plus 型旋翼布局

RACE

urdf/drones/racer.urdf

竞速四旋翼

设置螺旋桨 RPM#

set_propellers_rpm 是唯一的驱动调用。它为每个螺旋桨接受一个 RPM:

propellers_rpm  # shape ([n_envs,] n_propellers), non-negative

Warning

每一步恰好调用一次 set_propellers_rpm,且在 scene.step() 之前。同一步中第二次调用会报错,因为每次调用都会累加驱动旋转动画的螺旋桨转数。

若想移动而非悬停,可让各个螺旋桨相对悬停 RPM 偏移。交互式示例 examples/drone/interactive_drone.py 把方向映射为围绕公共推力的逐螺旋桨偏移:

# cur_dir is a 4-vector of per-propeller deltas in [-1, 1]
clipped_dir = np.clip(self.cur_dir, -1.0, 1.0)
rpms = self.thrust + clipped_dir * self.rotation_delta
return np.clip(rpms, 0, 25000)  # keep RPM within the motor's range

方向向量直接体现了这种模式:前进使用 (1, 1, -1, -1)(前侧一对转速升高、后侧一对降低,于是无人机向前俯仰),而偏航式旋转使用 (-1, 1, -1, 1),与螺旋桨的转向一致。

闭环控制#

开环 RPM 时序会产生漂移。要飞向目标位置,需要基于无人机状态做闭环,DroneEntity 通过继承自 RigidEntity 的 getter 暴露这些状态:

pos = drone.get_pos()  # shape ([n_envs,] 3), meters
vel = drone.get_vel()  # shape ([n_envs,] 3), m/s
quat = drone.get_quat()  # shape ([n_envs,] 4), (w, x, y, z)

examples/drone/quadcopter_controller.py 实现了一个级联 PID 控制器(位置 → 速度 → 姿态),其最后一级是混控器,把期望的推力、roll、pitch 和 yaw 修正转换为四个电机 RPM:

M1 = self.__base_rpm + (thrust - roll - pitch - yaw - x_vel + y_vel)
M2 = self.__base_rpm + (thrust - roll + pitch + yaw + x_vel + y_vel)
M3 = self.__base_rpm + (thrust + roll + pitch - yaw + x_vel - y_vel)
M4 = self.__base_rpm + (thrust + roll - pitch + yaw - x_vel - y_vel)

每项修正按照各自的符号模式在四个电机上加减。用 examples/drone/fly_route.py 运行完整的点到点飞行,该脚本驱动这个控制器,并在应用前把每个 RPM 限制在安全范围内。

多环境#

RPM 控制可跨 并行环境 批处理。用 n_envs 构建,然后为每个环境传入一行 RPM:

scene.build(n_envs=32)

# shape (n_envs, n_propellers)
rpms = np.tile([hover_rpm] * drone.n_propellers, (32, 1))
drone.set_propellers_rpm(rpms)

另请参阅#