跳到主要内容

AGV 调度系统

一、设备简介

AGV (自动导引车) 是智能工厂物流的设备侧执行单元, 承担产线配送、成品搬运、工位配送、跨车间物料中转。本案例用 1 台调度工控机 + 多台 AGV + Darra Runtime 实现:

  • 单调度中心管理 32 台 AGV, 任务队列 + 路径规划 + 交通管制 + 充电调度 + 电梯对接 全部内置;
  • 每台 AGV 也跑 Darra Runtime (嵌入式工控机), EtherCAT 周期 1ms 控制差速伺服 + 激光 SLAM 导航;
  • 调度中心 ↔ AGV 用 MQTT TLS, 心跳 500ms, 延迟 < 50ms;
  • 上层系统可通过 DarraIPC / OPC UA / REST 下发有限设备任务或读取状态, Darra 不实现上层业务调度系统;
  • 大屏 3D 地图 实时显示所有 AGV 位置 / 任务 / 电量。

单车载硬件 ¥4.8 万, 调度中心 ¥3 万, 10 台车队总投入约 ¥60 万, 可扩展到 50 辆车而设备侧控制架构不变。

二、项目模块组成

模块职责载体
调度引擎任务分配 / 路径规划 / 交通管制 / 充电调度Runtime 工艺对象
3D 地图服务SLAM 建图 + 动态显示 + 路径可视化Map Service
任务队列FIFO / 优先级 / 时间窗, 支持紧急插队Queue Manager
车载 PLC差速驱动 + 举升 + 避障 + 充电对接每车 Runtime
导航定位激光 SLAM + RFID 校准 + 二维码辅助Nav FB
MQTT 通讯车 ↔ 中心心跳 + 任务下发 + 状态上报MQTT Client
DarraIPC上层系统下发有限设备任务的标准 SDK 入口IPC Server
电梯 / 门对接EtherNet/IP 或 Modbus 与电梯 PLC / 自动门通讯Device Bridge
充电管理电量阈值触发 + 充电桩排队 + 充电桩联锁Charge FB
HMI 大屏3D 车间鸟瞰 + AGV 图标 + 任务看板HMI Designer
信息化MQTT / OPC UA 上云 + 报表IIoT Module

三、机械装配架构 (部署布局)

布局要点:

  1. 调度中心 设在车间控制室, 双网口 (上行 办公网 + MES / 下行 Wi-Fi AP 到 AGV);
  2. 车间 Wi-Fi 6 全覆盖, 每 30×30m 一个 AP, 漫游切换 < 50ms;
  3. 地面 RFID (路口 + 工位 + 充电桩) 定位校准, 典型布置 200 张;
  4. 地面二维码 (走廊中段) 备用定位, 典型 500 张;
  5. 2 座充电桩 布置在物流入口附近, 一备一用, 支持自动对接充电;
  6. 电梯 / 自动门 分别通过 EtherNet/IP / Modbus 和调度中心握手, AGV 不直接对接电梯。

四、程序架构

调度中心

车载

AGV 接口 FB 清单

FB 名调用方输入输出说明
FB_AGV_AssignTask调度中心AgvId / TaskAccepted下发任务
FB_AGV_CancelTask调度中心AgvId / TaskIdDone取消
FB_AGV_GoToCharge调度中心AgvId / DockIdDone去充电
FB_AGV_Report车载State / Pose / BatteryAck上报状态
FB_PathPlanner_Plan调度中心Start / End / TimeWinPathA* 规划
FB_TrafficControl_Request车载NodeId / TimeWinGranted / Wait路口预约
FB_Differential_MoveTo车载TargetXY / SpeedDone走到某点
FB_SLAM_Locate车载ScanPose / Cov激光定位
FB_Charge_Dock车载DockIdCharging对接充电

五、硬件 BOM (国产优先)

5.1 单台 AGV

设备型号 / 规格数量单价 (¥)小计 (¥)
嵌入式工控机研华 ARK-1221L / N100 / 8GB12,8002,800
轮毂电机 + 驱动400W 差速 + 汇川 SV66023,0006,000
从动万向轮承重 500kg2300600
EtherCAT IO 模块汇川 H5U 8DI+8DO+4AI11,5001,500
激光雷达思岚 RPLIDAR A3 2D / 360°15,0005,000
深度相机 (可选)Intel RealSense D435i12,8002,800
RFID 读写器UHF 地面标签11,5001,500
二维码相机海康 MV-ID201312,5002,500
超声波传感器4 路前后左右4150600
急停按钮蘑菇头 + 光幕1800800
锂电池组48V / 60Ah 磷酸铁锂16,0006,000
充电接口自动对接触头1800800
车身机械含举升 500kg118,00018,000
单车合计¥48,900

5.2 调度中心

设备型号数量单价 (¥)小计 (¥)
调度工控机研华 IPC-610 / i7-12700 / 32GB18,5008,500
服务器机柜4U 标准11,5001,500
UPS山特 1KVA12,8002,800
车间大屏威纶通 TK8112IP 65 寸115,00015,000
工业交换机华为 S573512,8002,800
Wi-Fi 6 AP锐捷 RG-AP840 吸顶61,2007,200
中心合计¥37,800

5.3 基础设施

项目规格数量单价 (¥)小计 (¥)
充电桩48V / 30A 自动对接215,00030,000
地面 RFID 标签UHF200306,000
地面二维码聚酯薄膜耐磨500105,000
基础设施¥41,000

10 辆 AGV 全套: 10 × 4.89 万 + 3.78 万 + 4.1 万 ≈ ¥57.7 万

六、IO 模块配置

6.1 车载 IO 清单

地址信号方向用途
I0.0~I0.3SEN_Bumper[1..4]DI前后左右安全边
I0.4SEN_LoadPresentDI载具到位
I0.5SB_EStopDI急停 (常闭)
I0.6SEN_ChargeContactDI充电触头接触
I0.7SEN_LiftTop / BottomDI举升到位
Q0.0MTR_LiftUpDO举升
Q0.1MTR_LiftDownDO下降
Q0.2LMP_RunningDO运行灯
Q0.3LMP_WarningDO警告闪烁
Q0.4BUZ_AlarmDO蜂鸣器 (倒车 / 转弯)
Q0.5VALVE_BrakeDO电磁制动
AI0.0Battery_VoltageAI电池电压 (0~50V)
AI0.1Motor_I_LAI左电机电流
AI0.2Motor_I_RAI右电机电流

6.2 调度中心与基础设施 IO

地址信号方向对接设备
Q100.0ELE_Call_UpDO电梯呼梯 (向 上)
Q100.1ELE_Call_DownDO电梯呼梯 (向下)
Q100.2DOOR_OpenDO自动门开
I100.0ELE_AtFloorDI电梯到达
I100.1DOOR_OpenedDI门已开
Q101.0~Q101.1CHG_Enable[1..2]DO充电桩使能
I101.0~I101.1CHG_Busy[1..2]DI充电桩占用

6.3 IO 绑定多台 AGV 的操作流程

  1. 车载 IDE: 建立一个 AGV Template 工程 → 定义上述 IO 别名;
  2. 批量下发: 10 辆 AGV 共用同一 Template, 仅通过 "车辆 ID" 区分;
  3. 车辆注册: 每辆 AGV 开机上电后自动向调度中心注册 (MQTT topic: agv/{id}/register), 携带自身 IO 状态;
  4. 调度中心绑定: 收到注册后在内部建立 G_Agv[id] 结构体, 订阅该车所有状态话题;
  5. 电梯 / 门绑定: 调度中心作为电梯和门的唯一对接方, AGV 不直接操作, 仅发 "请求过门" 任务给调度;
  6. 充电桩: 每个桩在调度中心的 G_Chargers 数组里, 被分配给某辆 AGV 后 CHG_Enable 接通;
  7. 安全域: 每辆 AGV 急停直接断自身伺服 + 走电磁制动, 不依赖调度中心。

七、核心数据结构

7.1 AGV 状态 UDT

TYPE UDT_AGV :
STRUCT
nId : INT;
sSerial : STRING[16];
eState : E_AgvState; // Idle/Moving/Loaded/Charging/Fault/Offline
rX : REAL; // mm
rY : REAL; // mm
rTheta : REAL; // rad
rVel : REAL; // m/s
rBattery : REAL; // %
sCurrTaskId : STRING[32];
rProgress : REAL;
bLoaded : BOOL;
tLastHeart : LTIME; // 心跳
nFaultCode : INT;
END_STRUCT
END_TYPE

7.2 任务 UDT

TYPE UDT_Task :
STRUCT
sId : STRING[32];
eType : E_TaskType; // Move/Pick/Place/Charge/MoveLiftDeliver
nPriority : INT; // 1~10
rFrom_X : REAL;
rFrom_Y : REAL;
rTo_X : REAL;
rTo_Y : REAL;
tDeadline : LTIME;
nAssignedTo : INT; // AGV Id, 0=unassigned
eStatus : E_TaskStatus; // Pending/Assigned/Running/Done/Failed
sSource : STRING[32]; // 上层系统/手动
END_STRUCT
END_TYPE

7.3 路径节点 UDT (A* 结果)

TYPE UDT_PathNode :
STRUCT
nId : INT;
rX : REAL;
rY : REAL;
tEnterBy : LTIME; // 时间窗开始
tLeaveBy : LTIME; // 时间窗结束
eNodeType : E_NodeType; // Point/Junction/ChargeDock/Elevator
END_STRUCT
END_TYPE

7.4 充电桩 UDT

TYPE UDT_Charger :
STRUCT
nId : INT;
eState : E_ChargerState; // Free/Occupied/Fault
nAgvId : INT; // 当前占用的 AGV
rPower_kW : REAL;
tStartAt : LTIME;
END_STRUCT
END_TYPE

7.5 全局变量

VAR_GLOBAL
G_Agvs : ARRAY[1..32] OF UDT_AGV;
G_Tasks : ARRAY[0..999] OF UDT_Task;
G_Chargers : ARRAY[1..4] OF UDT_Charger;
G_Map : UDT_MapGraph; // 节点 + 边
G_Traffic : ARRAY[0..99] OF UDT_NodeBooking; // 路口时间窗
G_TaskCnt : DINT;
END_VAR

八、核心程序代码

8.1 FB_Dispatcher (任务 → 车辆分配, 多车调度核心)

FUNCTION_BLOCK FB_Dispatcher
VAR
i, bestId : INT;
rMinCost : REAL;
stCost : REAL;
END_VAR

FOR iTask := 0 TO 999 DO
IF G_Tasks[iTask].eStatus = E_TaskStatus.Pending THEN
rMinCost := 1.0E9;
bestId := 0;

// 在空闲 AGV 中找成本最低的 (距离 + 电量)
FOR i := 1 TO 32 DO
IF G_Agvs[i].eState = E_AgvState.Idle
AND G_Agvs[i].rBattery > 25.0 THEN
stCost := EuclidDist(G_Agvs[i], G_Tasks[iTask])
+ (100.0 - G_Agvs[i].rBattery) * 2.0
+ PriorityBias(G_Tasks[iTask].nPriority);
IF stCost < rMinCost THEN
rMinCost := stCost;
bestId := i;
END_IF;
END_IF;
END_FOR;

IF bestId > 0 THEN
// 规划路径
FB_PathPlanner_Plan(
Start := G_Agvs[bestId],
End := G_Tasks[iTask],
Path => aPath
);

IF PathValid(aPath) THEN
G_Tasks[iTask].nAssignedTo := bestId;
G_Tasks[iTask].eStatus := E_TaskStatus.Assigned;
G_Agvs[bestId].sCurrTaskId := G_Tasks[iTask].sId;
FB_AGV_AssignTask(
AgvId := bestId,
Task := G_Tasks[iTask],
Path := aPath
);
END_IF;
END_IF;
END_IF;
END_FOR;

8.2 FB_PathPlanner (A* + 时间窗, 多车无死锁)

FUNCTION_BLOCK FB_PathPlanner_Plan
VAR_INPUT
Start : UDT_Pose;
End : UDT_Pose;
END_VAR
VAR_OUTPUT
Path : ARRAY[0..99] OF UDT_PathNode;
END_VAR
VAR
openSet, closedSet : /* 优先队列 */ ;
current : INT;
END_VAR

// A* 算法主循环 (伪代码)
openSet.Add(NearestNode(Start));

WHILE openSet.Count > 0 DO
current := openSet.PopLowestF();

IF current = NearestNode(End) THEN
Path := Reconstruct(current);
// 预约时间窗
FB_TrafficControl_Book(Path);
RETURN;
END_IF;

closedSet.Add(current);

FOR neighbor IN G_Map.Neighbors(current) DO
IF closedSet.Contains(neighbor) THEN CONTINUE; END_IF;

// 检查时间窗是否冲突
IF G_Traffic[neighbor].BookedBy <> 0
AND G_Traffic[neighbor].BookedBy <> currentAGV THEN
CONTINUE;
END_IF;

tentativeG := G_Values[current] + Dist(current, neighbor);

IF tentativeG < G_Values[neighbor] THEN
CameFrom[neighbor] := current;
G_Values[neighbor] := tentativeG;
F_Values[neighbor] := tentativeG + Heuristic(neighbor, End);
openSet.Add(neighbor);
END_IF;
END_FOR;
END_WHILE;

8.3 FB_TrafficControl (路口时间窗预约)

FUNCTION_BLOCK FB_TrafficControl_Request
VAR_INPUT
AgvId : INT;
NodeId : INT;
TimeWin : UDT_TimeWindow;
END_VAR
VAR_OUTPUT
Granted : BOOL;
WaitMs : DINT;
END_VAR

IF G_Traffic[NodeId].BookedBy = 0
OR G_Traffic[NodeId].BookedBy = AgvId THEN
G_Traffic[NodeId].BookedBy := AgvId;
G_Traffic[NodeId].Window := TimeWin;
Granted := TRUE;
WaitMs := 0;
ELSIF TimeWin.tEnd < G_Traffic[NodeId].Window.tStart THEN
// 本次通过时间早于已预约, 允许通过
Granted := TRUE;
WaitMs := 0;
ELSE
Granted := FALSE;
WaitMs := LTIME_TO_DINT(G_Traffic[NodeId].Window.tEnd - LTIME()) / 1000;
END_IF;

8.4 FB_ChargeScheduler (电量管理)

FUNCTION_BLOCK FB_ChargeScheduler
VAR
i, j : INT;
END_VAR

FOR i := 1 TO 32 DO
IF G_Agvs[i].eState = E_AgvState.Idle
AND G_Agvs[i].rBattery < 20.0 THEN
// 找空闲充电桩
FOR j := 1 TO 4 DO
IF G_Chargers[j].eState = E_ChargerState.Free THEN
G_Chargers[j].nAgvId := i;
G_Chargers[j].eState := E_ChargerState.Occupied;
FB_AGV_GoToCharge(AgvId := i, DockId := j);
EXIT;
END_IF;
END_FOR;
END_IF;

IF G_Agvs[i].eState = E_AgvState.Charging
AND G_Agvs[i].rBattery > 95.0 THEN
// 充满回到空闲, 释放桩
FOR j := 1 TO 4 DO
IF G_Chargers[j].nAgvId = i THEN
G_Chargers[j].eState := E_ChargerState.Free;
G_Chargers[j].nAgvId := 0;
END_IF;
END_FOR;
G_Agvs[i].eState := E_AgvState.Idle;
END_IF;
END_FOR;

8.5 FB_ElevatorBridge (电梯对接)

FUNCTION_BLOCK FB_ElevatorBridge
VAR_INPUT
Req : UDT_ElevatorRequest; // 来自 AGV
END_VAR
VAR
fbEnip : FB_EnipClient;
END_VAR

// 1. 向电梯 PLC 发呼梯
fbEnip(
Target := 'Elevator_PLC',
Var := 'Call_' + INT_TO_STRING(Req.nTargetFloor),
Value := TRUE,
Execute := TRUE
);

// 2. 等待 "到达该楼层"
IF fbEnip.Done AND Elevator_AtFloor = Req.nTargetFloor
AND Elevator_DoorOpen THEN
// 3. 通知 AGV 进电梯
FB_AGV_Resume(AgvId := Req.nAgvId);
END_IF;

// 4. AGV 进电梯后发第二次呼梯到目标楼层

8.6 DarraIPC - 上层系统下发有限设备任务

// MES 端调用: c.write("NewTask", JSON)
// 调度中心捕获后写入任务队列

IF IPC_NewTask_Received THEN
stTask := JSON_Parse(IPC_TaskJSON);
G_TaskCnt := G_TaskCnt + 1;
stTask.sId := 'T' + DINT_TO_STRING(G_TaskCnt);
stTask.eStatus := E_TaskStatus.Pending;
G_Tasks[G_TaskCnt MOD 1000] := stTask;

IPC_Reply := '{"ok":true,"taskId":"'+stTask.sId+'"}';
END_IF;

九、运行与调试

9.1 首次部署流程

  1. 现场建图: 让 1 辆 AGV 手动遥控走全场, 激光雷达 SLAM 生成 2D 地图;
  2. 地图标注: IDE 地图编辑器里标注 工位 / 充电桩 / 路口 / 电梯 / 门 等关键节点;
  3. RFID / 二维码铺设: 路口、工位、充电桩贴标签, 更新地图坐标;
  4. AGV 注册: 10 辆 AGV 开机自动注册到调度中心, 各自 ID 在屏幕上出现;
  5. 单车定位: 每辆 AGV 手动放置 → 读取 SLAM + RFID 定位精度, 要求 ±20mm;
  6. 路径测试: 单辆从 A 到 B 来回跑 10 趟, 确认轨迹不偏;
  7. 多车测试: 同时 3 辆跑相交路径, 验证路口时间窗 / 互相让行;
  8. 充电演练: 低电车自动去充电 → 充满自动归队;
  9. 电梯演练: 跨楼层任务 → 调度 + 电梯联动 → AGV 进出电梯;
  10. MES 联调: 从 MES 下发 100 个真实任务, 观察完成率 / 延迟;
  11. 大屏验证: 3D 地图实时位置与真实一致, 任务看板刷新。

9.2 常见故障排查

现象排查点
SLAM 定位漂移环境是否变化 (家具移动), 重建图
路口死锁FB_TrafficControl 时间窗交集 / 路径规划是否 A* 标准实现
多车碰撞减速圈 / 车间最小间距参数 / 超声波灵敏度
MQTT 掉线频繁Wi-Fi 漫游切换 / AP 布置密度 / 信道干扰
充电对接失败机械对齐精度 / 触头污染 / 充电桩通讯
电梯不开门EtherNet/IP 连通 / 电梯厂商协议版本
任务卡在 PendingDispatcher 筛选条件 (电量 / 空闲车) / 路径规划是否成功

9.3 日常维护

  • 每天: 检查 AGV 电池电压 / 充电桩触头;
  • 每周: 激光雷达镜头清洁 / RFID 标签完好;
  • 每月: 地图校验 (有无新增障碍物) / MQTT 消息统计;
  • 每季度: 伺服电机与减速机保养。

十、Darra 功能对照表

需求Darra 功能
单调度中心管 32 台车内置调度引擎 + 任务队列
路径规划无死锁A* + 时间窗 FB_TrafficControl
多优先级任务任务 UDT + Priority 字段
电量管理 + 自动充电FB_ChargeScheduler
电梯 / 自动门对接FB_ElevatorBridge + EtherNet/IP
上层系统下发有限设备任务DarraIPC 开放 SDK
多车型混用AGV UDT + 能力标记
3D 车间鸟瞰HMI 3D 部件
SLAM + RFID 双定位FB_SLAM_Nav + FB_RFID_Correct
车载 1ms EtherCAT车载嵌入式 Runtime
断网重连MQTT 心跳 + 自动重连
大屏实时状态HMI Designer + MQTT 订阅
充电桩管理FB_Charger
安全急停硬线 STO + 电磁制动

十一、项目总结

成果指标

  • 车队规模: 10~32 辆 (单调度上限), 扩展到 50+ 通过多调度集群;
  • 定位精度: ±20mm (SLAM) / ±5mm (RFID 校准后);
  • 任务响应: < 200ms (从 MES 下发到 AGV 启动);
  • 多车冲突解决: < 50ms (时间窗算法);
  • 单车硬件成本: ¥4.89 万, 调度中心 + 10 车全套 ≈ ¥57.7 万;
  • 中间件简化: 减少设备侧中间件定制;
  • 部署周期: 2~4 周 (含建图), 传统方案 6~10 周;
  • 可扩展性: 加车只需 1 天标定 + 注册。

适配场景延伸

  • 产线物料搬运 (工位配送 / 跨线中转 / 补料);
  • 产线物料配送 (库房 → 工位);
  • 成品中转 (产线 → 成品库);
  • 跨车间搬运 (涂装 → 装配 → 成品暂存);
  • 医院药品 / 标本运送;
  • 半导体洁净室物料;
  • 食品冷链内部物流;
  • 数据中心 IT 资产搬运。

扩展方向

  • 动态任务插队: MES 紧急订单插入时实时重规划, 配合数字孪生验证;
  • AI 视觉避障: 识别行人 / 动物 / 落地物件, 不只是障碍物 (深度学习模型);
  • 队列充电: 充电桩不够时 AGV 在等待区排队;
  • 集群调度: 多调度中心 Active-Active, 单中心故障自动接管;
  • 5G + TSN: 替代 Wi-Fi, 时延 1ms 级, 支持高速协同;
  • PLC 与 AGV 同屏运维: 车间大屏 + Darra Cloud 远程看车, 配合 HMI 大屏案例;
  • 与机器人联动: AGV 到工位后机器人自动上下料, 配合多机器人案例。

Darra 让 "AGV 设备侧控制" 从 "外部调度系统黑盒 + 厂家定制" 变成 "PLC 工程 + 工艺对象", 用户拥有完整的设备侧修改权, 每一次优化都不用等厂商排期。