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 |
三、机械装配架构 (部署布局)
布局要点:
- 调度中心 设在车间控制室, 双网口 (上行 办公网 + MES / 下行 Wi-Fi AP 到 AGV);
- 车间 Wi-Fi 6 全覆盖, 每 30×30m 一个 AP, 漫游切换 < 50ms;
- 地面 RFID (路口 + 工位 + 充电桩) 定位校准, 典型布置 200 张;
- 地面二维码 (走廊中段) 备用定位, 典型 500 张;
- 2 座充电桩 布置在物流入口附近, 一备一用, 支持自动对接充电;
- 电梯 / 自动门 分别通过 EtherNet/IP / Modbus 和调度中心握手, AGV 不直接对接电梯。
四、程序架构
调度中心
车载
AGV 接口 FB 清单
| FB 名 | 调用方 | 输入 | 输出 | 说明 |
|---|---|---|---|---|
FB_AGV_AssignTask | 调度中心 | AgvId / Task | Accepted | 下发任务 |
FB_AGV_CancelTask | 调度中心 | AgvId / TaskId | Done | 取消 |
FB_AGV_GoToCharge | 调度中心 | AgvId / DockId | Done | 去充电 |
FB_AGV_Report | 车载 | State / Pose / Battery | Ack | 上报状态 |
FB_PathPlanner_Plan | 调度中心 | Start / End / TimeWin | Path | A* 规划 |
FB_TrafficControl_Request | 车载 | NodeId / TimeWin | Granted / Wait | 路口预约 |
FB_Differential_MoveTo | 车载 | TargetXY / Speed | Done | 走到某点 |
FB_SLAM_Locate | 车载 | Scan | Pose / Cov | 激光定位 |
FB_Charge_Dock | 车载 | DockId | Charging | 对接充电 |
五、硬件 BOM (国产优先)
5.1 单台 AGV
| 设备 | 型号 / 规格 | 数量 | 单价 (¥) | 小计 (¥) |
|---|---|---|---|---|
| 嵌入式工控机 | 研华 ARK-1221L / N100 / 8GB | 1 | 2,800 | 2,800 |
| 轮毂电机 + 驱动 | 400W 差速 + 汇川 SV660 | 2 | 3,000 | 6,000 |
| 从动万向轮 | 承重 500kg | 2 | 300 | 600 |
| EtherCAT IO 模块 | 汇川 H5U 8DI+8DO+4AI | 1 | 1,500 | 1,500 |
| 激光雷达 | 思岚 RPLIDAR A3 2D / 360° | 1 | 5,000 | 5,000 |
| 深度相机 (可选) | Intel RealSense D435i | 1 | 2,800 | 2,800 |
| RFID 读写器 | UHF 地面标签 | 1 | 1,500 | 1,500 |
| 二维码相机 | 海康 MV-ID2013 | 1 | 2,500 | 2,500 |
| 超声波传感器 | 4 路前后左右 | 4 | 150 | 600 |
| 急停按钮 | 蘑菇头 + 光幕 | 1 | 800 | 800 |
| 锂电池组 | 48V / 60Ah 磷酸铁锂 | 1 | 6,000 | 6,000 |
| 充电接口 | 自动对接触头 | 1 | 800 | 800 |
| 车身机械 | 含举升 500kg | 1 | 18,000 | 18,000 |
| 单车合计 | ¥48,900 |
5.2 调度中心
| 设备 | 型号 | 数量 | 单价 (¥) | 小计 (¥) |
|---|---|---|---|---|
| 调度工控机 | 研华 IPC-610 / i7-12700 / 32GB | 1 | 8,500 | 8,500 |
| 服务器机柜 | 4U 标准 | 1 | 1,500 | 1,500 |
| UPS | 山特 1KVA | 1 | 2,800 | 2,800 |
| 车间大屏 | 威纶通 TK8112IP 65 寸 | 1 | 15,000 | 15,000 |
| 工业交换机 | 华为 S5735 | 1 | 2,800 | 2,800 |
| Wi-Fi 6 AP | 锐捷 RG-AP840 吸顶 | 6 | 1,200 | 7,200 |
| 中心合计 | ¥37,800 |
5.3 基础设施
| 项目 | 规格 | 数量 | 单价 (¥) | 小计 (¥) |
|---|---|---|---|---|
| 充电桩 | 48V / 30A 自动对接 | 2 | 15,000 | 30,000 |
| 地面 RFID 标签 | UHF | 200 | 30 | 6,000 |
| 地面二维码 | 聚酯薄膜耐磨 | 500 | 10 | 5,000 |
| 基础设施 | ¥41,000 |
10 辆 AGV 全套: 10 × 4.89 万 + 3.78 万 + 4.1 万 ≈ ¥57.7 万。
六、IO 模块配置
6.1 车载 IO 清单
| 地址 | 信号 | 方向 | 用途 |
|---|---|---|---|
I0.0~I0.3 | SEN_Bumper[1..4] | DI | 前后左右安全边 |
I0.4 | SEN_LoadPresent | DI | 载具到位 |
I0.5 | SB_EStop | DI | 急停 (常闭) |
I0.6 | SEN_ChargeContact | DI | 充电触头接触 |
I0.7 | SEN_LiftTop / Bottom | DI | 举升到位 |
Q0.0 | MTR_LiftUp | DO | 举升 |
Q0.1 | MTR_LiftDown | DO | 下降 |
Q0.2 | LMP_Running | DO | 运行灯 |
Q0.3 | LMP_Warning | DO | 警告闪烁 |
Q0.4 | BUZ_Alarm | DO | 蜂鸣器 (倒车 / 转弯) |
Q0.5 | VALVE_Brake | DO | 电磁制动 |
AI0.0 | Battery_Voltage | AI | 电池电压 (0~50V) |
AI0.1 | Motor_I_L | AI | 左电机电流 |
AI0.2 | Motor_I_R | AI | 右电机电流 |
6.2 调度中心与基础设施 IO
| 地址 | 信号 | 方向 | 对接设备 |
|---|---|---|---|
Q100.0 | ELE_Call_Up | DO | 电梯呼梯 (向 上) |
Q100.1 | ELE_Call_Down | DO | 电梯呼梯 (向下) |
Q100.2 | DOOR_Open | DO | 自动门开 |
I100.0 | ELE_AtFloor | DI | 电梯到达 |
I100.1 | DOOR_Opened | DI | 门已开 |
Q101.0~Q101.1 | CHG_Enable[1..2] | DO | 充电桩使能 |
I101.0~I101.1 | CHG_Busy[1..2] | DI | 充电桩占用 |
6.3 IO 绑定多台 AGV 的操作流程
- 车载 IDE: 建立一个 AGV Template 工程 → 定义上述 IO 别名;
- 批量下发: 10 辆 AGV 共用同一 Template, 仅通过 "车辆 ID" 区分;
- 车辆注册: 每辆 AGV 开机上电后自动向调度中心注册 (MQTT
topic: agv/{id}/register), 携带自身 IO 状态; - 调度中心绑定: 收到注册后在内部建立
G_Agv[id]结构体, 订阅该车所有状态话题; - 电梯 / 门绑定: 调度中心作为电梯和门的唯一对接方, AGV 不直接操作, 仅发 "请求过门" 任务给调度;
- 充电桩: 每个桩在调度中心的
G_Chargers数组里, 被分配给某辆 AGV 后CHG_Enable接通; - 安全域: 每辆 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 辆 AGV 手动遥控走全场, 激光雷达 SLAM 生成 2D 地图;
- 地图标注: IDE 地图编辑器里标注 工位 / 充电桩 / 路口 / 电梯 / 门 等关键节点;
- RFID / 二维码铺设: 路口、工位、充电桩贴标签, 更新地图坐标;
- AGV 注册: 10 辆 AGV 开机自动注册到调度中心, 各自 ID 在屏幕上出现;
- 单车定位: 每辆 AGV 手动放置 → 读取 SLAM + RFID 定位精度, 要求 ±20mm;
- 路径测试: 单辆从 A 到 B 来回跑 10 趟, 确认轨迹不偏;
- 多车测试: 同时 3 辆跑相交路径, 验证路口时间窗 / 互相让行;
- 充电演练: 低电车自动去充电 → 充满自动归队;
- 电梯演练: 跨楼层任务 → 调度 + 电梯联动 → AGV 进出电梯;
- MES 联调: 从 MES 下发 100 个真实任务, 观察完成率 / 延迟;
- 大屏验证: 3D 地图实时位置与真实一致, 任务看板刷新。
9.2 常见故障排查
| 现象 | 排查点 |
|---|---|
| SLAM 定位漂移 | 环境是否变化 (家具移动), 重建图 |
| 路口死锁 | FB_TrafficControl 时间窗交集 / 路径规划是否 A* 标准实现 |
| 多车碰撞 | 减速圈 / 车间最小间距参数 / 超声波灵敏度 |
| MQTT 掉线频繁 | Wi-Fi 漫游切换 / AP 布置密度 / 信道干扰 |
| 充电对接失败 | 机械对齐精度 / 触头污染 / 充电桩通讯 |
| 电梯不开门 | EtherNet/IP 连通 / 电梯厂商协议版本 |
| 任务卡在 Pending | Dispatcher 筛选条件 (电量 / 空闲车) / 路径规划是否成功 |
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 工程 + 工艺对象", 用户拥有完整的设备侧修改权, 每一次优化都不用等厂商排期。