叉车适配案例
概述 / Overview
叉车(含牵引车、堆高车)是仓储 AMR 中最复杂的车型 —— 既要做 巡线行走、又要做 托盘取放、再加上叉齿升降与平衡。本页用一台真实在产叉车(凌鸟 LB14)的源码 (`D:\src\cookbook\adaption-reference\苏州凌鸟\CartAdapters\LB14\`,~330 行可读) 串起完整适配流程。
Forklifts (incl. tractors, stackers) are the most complex class of warehouse AMR — they do line-following, pallet pick-and-place, plus fork lift / tilt control and balance. This page walks through a real adaptation using Linbir LB14 source (`D:\src\cookbook\adaption-reference\苏州凌鸟\CartAdapters\LB14\`, ~330 LOC) as the running example.
硬件清单 / Hardware
| 部件 / Part | 典型规格 / Typical spec |
|---|---|
| 底盘 / Chassis | 三轮(前驱单舵 + 后双轮被动)/ Three-wheel (front steer + 2 rear passive) |
| 驱动电机 / Drive motor | 永磁同步 1.5–4 kW |
| 转向编码器 / Steer encoder | 绝对值,分辨率 ≥ 14 bit |
| 叉齿马达 / Fork motor | 液压泵 + 阀,行程 100–3000 mm |
| 倾角传感 / Tilt sensor | IMU + 阀块行程编码 |
| 前向激光 / Front lidar | 2D,高度对齐叉孔 / 2D at fork height |
| 后向激光(含侧扫)/ Rear lidar | 用于反向行驶 + 双车联动 V 槽观测 / reverse + V-groove for twin-car coordination |
| 负载传感 / Load sensor | 叉齿底油压或称重模块 / hydraulic pressure or load-cell |
| 通讯 / Comm | PLC 经 RS485 / Modbus 或专用 CAN / PLC via RS485 / Modbus, or proprietary CAN |
1. Medulla 适配 / Medulla side
关键文件:`D:\src\cookbook\adaption-reference\苏州凌鸟\CartAdapters\LB14\MedullaAdapter\` - `LB14Cart.cs` — `CartDefinition` 子类,声明 IO 与 LadderLogic - `Omron485.cs` / `OmronRoutine.cs` — RS485 报文层(与欧姆龙 PLC 通讯) - `LB14Remote.cs` — 与厂商遥控手柄的协议
最少需要的 IO 声明 / Minimum IO surface:
public class LB14Cart : CartDefinition
{
[AsInitParam] public string plcIp = "192.168.0.100";
[AsInitParam] public int plcPort = 502;
[AsInitParam] public float wheelBase = 1320; // mm
[AsInitParam] public float forkOffset = 760; // tine root to drive wheel
// 上位 IO(调度 / Clumsy 下发的意图)/ Upper IO (intent flowing down)
[AsUpperIO] public float vCmd; // 前进速度 / forward velocity (mm/s)
[AsUpperIO] public float steerCmd; // 转向角 / steer angle (rad)
[AsUpperIO] public float forkHeightTgt; // 叉齿目标高度 / fork target height
[AsUpperIO] public float forkTiltTgt; // 叉门架倾角 / fork tilt
[AsUpperIO] public bool brakeOn;
[AsUpperIO] public bool hornOn;
// 下位 IO(硬件回报的状态)/ Lower IO (status rising up)
[AsLowerIO] public float vEst;
[AsLowerIO] public float steerEst;
[AsLowerIO] public float forkHeight;
[AsLowerIO] public bool forkAtTarget;
[AsLowerIO] public float loadKg;
[AsLowerIO] public bool eStop;
[AsLowerIO] public int batteryPercent;
public override void Init()
{
// 1) 连接 PLC,握手 / connect & handshake
plcConn = OmronRoutine.Connect(plcIp, plcPort);
// 2) 注册看门狗 / register watchdog (LadderLogic ticks every ~50 ms)
plcConn.AttachStateCallback(OnPLCStatus);
}
[UseLadderLogic(IntervalMs = 50)]
public class ControlLoop : LadderLogic<LB14Cart>
{
public override void Run(LB14Cart self)
{
// 把 Upper IO 写到 PLC
self.plcConn.WriteRegisters(
vCmd: (int)(self.vCmd * 10),
steerCmd: (int)(self.steerCmd * 10000),
forkTgt: (int)self.forkHeightTgt
);
// 读 Lower IO
var s = self.plcConn.ReadStatus();
self.vEst = s.vEst / 10f;
self.steerEst = s.steerEst / 10000f;
self.forkHeight = s.forkHeight;
self.forkAtTarget = s.forkAtTarget;
self.loadKg = s.loadKg;
self.eStop = s.eStop;
self.batteryPercent = s.battery;
}
}
}
2. Clumsy 自动驾驶适配 / Clumsy autopilot side
关键文件:`D:\src\cookbook\adaption-reference\苏州凌鸟\CartAdapters\LB14\ClumsyPilot\` - `AGV.cs` (255 行) — `SimpleAgvInterface` 子类,业务级动作 `Fetch / Put / ExitSite / ReverseGo1` - `Movements.cs` — 叉车特有的 `MovementDefinition` 子类 - `Configs.cs` / `CSGTypeF3CDef.cs` — 参数配置
叉车特有的 Movement / Movements specific to forklifts:
public class AGV : SimpleAgvInterface
{
public int baseSpeed = (int) Configuration.conf.basicSpeed;
private SteeringLineFollowing lastDriver;
private SteeringLineFollowingReverse lastDriverR;
// 取货 / Fetch
public void Fetch(double srcX, double srcY, int srcid,
double dstX, double dstY, int dstid,
double pivotX, double pivotY)
{
Queue(
async () =>
{
// 1) 巡线到 srcX/Y(叉孔正前方)
DriveTask.WaitDriveTask(new SteeringLineFollowing
{
srcX = currentX, srcY = currentY,
dstX = srcX, dstY = srcY,
basespeed = baseSpeed
}.Follow());
},
async () =>
{
// 2) 升叉到取货高度
SetUpperIO("forkHeightTgt", Configuration.conf.fetchHeight);
await WaitForLowerIO("forkAtTarget", timeout: 5000);
},
async () =>
{
// 3) 自动识别托盘并对位
DriveTask.WaitDriveTask(new AutoFetchGood
{
aX = srcX, aY = srcY, bX = dstX, bY = dstY,
initSpeed = 500, inShelfSpeed = 150, stopDist = 80
}.Get());
},
async () =>
{
// 4) 升叉离地 50 mm
SetUpperIO("forkHeightTgt", forkHeight + 50);
await WaitForLowerIO("forkAtTarget", 4000);
},
async () =>
{
// 5) 倒退离开工位
DriveTask.WaitDriveTask(new SteeringLineFollowingReverse
{
srcX = dstX, srcY = dstY,
dstX = srcX, dstY = srcY,
basespeed = (int)(baseSpeed * 0.6)
}.ReverseFollow());
});
}
}
3. SimpleComposer 端 / SimpleComposer side
ClumsyCar 子类的最简实现见 SimpleCore 开发指南。叉车多了一个 带载包络 状态:
A ClumsyCar subclass is minimal; the extra piece for forklifts is a carrying-load envelope state:
[CarType(Name = "LB14", Title = "凌鸟LB14 叉车 / Linbir LB14 forklift")]
public class LB14 : ClumsyCar
{
public override Envelope GetActiveEnvelope()
{
if (carryingLoad)
return loadedEnvelope; // 带载包络 / loaded envelope (longer)
if (forkHeight > 500)
return highRiseEnvelope; // 高位包络(重心抬高)
return base.GetActiveEnvelope();
}
}
4. 调试与标定 / Calibration & tuning
按下列顺序: Order:
- 基本运动 / Basic motion: 站点对点行驶,调 `basespeed`、`steerCmd` 极性。
- 激光雷达外参 / Lidar extrinsics: 用 标定激光雷达外参。
- 叉齿水平 / Fork-tine horizontality: 用水平仪 + IMU 读数对齐。
- 托盘检测先验 / Pallet priors: 真实托盘测量 `pocketWidth`,写入 CAD。
- 带载试运行 / Loaded run-in: 不同载重 + 偏心载下跑同一路径,确认偏移收敛。
- 高位放料 / High-rise placement: 仅在 0–500–1500–2500 mm 多个高度测稳定性。
5. 常见问题 / Common pitfalls
- 液压响应滞后导致超调 / Hydraulic lag → 在 Movement 中加 下一目标提前 200 ms 的预设。
- 叉孔对齐偏 / Pocket misalign → 90% 是激光雷达水平度问题。
- 带载横向漂 / Lateral drift loaded → 检查 `wheelBase`、`forkOffset` 标定。
- 反向行驶失败 / Reverse line-follow fails → 后向激光遮挡或 `lastDriverR` 没设置成员。
- 高位倒料 / Tip-over at height → 高位包络未声明,调度允许了限速过高的路径。
6. 双车联动叉车 / Twin-car forklifts
叉车做 双车联动 时,作为 领头常见,跟随车需用后向激光雷达观测领头叉车背面的 V 槽。详见双车联动页。
When a forklift participates in twin-car coordination, it is typically the leader; the follower observes the leader's rear-mounted V-groove via rear lidar. See the twin-car page for details.