无人机实体#
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 模型会在内部反转全部四个转向。
自带的模型有:
|
|
说明 |
|---|---|---|
|
|
Crazyflie 2.X,X 型旋翼布局 |
|
|
Crazyflie 2.X,plus 型旋翼布局 |
|
|
竞速四旋翼 |
设置螺旋桨 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)
另请参阅#
训练无人机悬停:基于该实体构建的完整强化学习环境。
训练运动策略:同样的训练流程应用于足式机器人。
DroneEntity 参考:完整的类 API。