叉车适配案例

来自MDCS wiki2
Artheru讨论 | 贡献2026年5月16日 (六) 19:26的版本 (Initial bilingual draft (auto-published))
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索


概述 / 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:

  1. 基本运动 / Basic motion: 站点对点行驶,调 `basespeed`、`steerCmd` 极性。
  2. 激光雷达外参 / Lidar extrinsics: 用 标定激光雷达外参
  3. 叉齿水平 / Fork-tine horizontality: 用水平仪 + IMU 读数对齐。
  4. 托盘检测先验 / Pallet priors: 真实托盘测量 `pocketWidth`,写入 CAD。
  5. 带载试运行 / Loaded run-in: 不同载重 + 偏心载下跑同一路径,确认偏移收敛。
  6. 高位放料 / 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.

相关页面 / See also