4. ArduPilot Lua运行环境
ArduPilot 的 Lua 脚本并不是“随便丢进飞控就能跑”的普通文件,它运行在飞控固件内部一个受控的沙箱环境中。理解这个运行环境的边界,是写出稳定脚本的前提。本节围绕五个关键问题展开:脚本放在哪里、上电后如何被加载、可用内存有多少、脚本以什么节奏执行、以及如何看到调试输出。
一、脚本存放位置:SD 卡是唯一入口
ArduPilot 的 Lua 脚本统一存放在飞控 SD 卡的 /APM/scripts/ 目录下。飞控启动时,文件系统会挂载 SD 卡,Lua 引擎随后扫描该目录。需要注意几个硬性规则:
- 脚本扩展名必须是
.lua,其他扩展名不会被加载。 - 目录名必须是
scripts,且位于APM目录内,路径大小写敏感。 - 脚本文件名建议使用小写字母、数字和下划线,避免空格和中文。
- 脚本数量不宜过多,每个脚本都会占用内存和启动时间。
典型目录结构如下:
/APM/
├── logs/
├── terrain/
└── scripts/
├── hello.lua
├── battery_monitor.lua
└── mission_utils.lua
如果 SD 卡没有正确挂载,或者目录名写成 Script、lua,脚本都不会被执行。这是初学者最常见的“脚本没反应”原因之一。
二、启动流程:从固件初始化到脚本执行
Lua 脚本的启动不是独立进程,而是嵌入在 ArduPilot 主循环中的。整体流程可以概括为:固件启动 → 文件系统挂载 → Lua 引擎初始化 → 扫描脚本目录 → 逐个加载脚本 → 注册回调 → 进入主循环调度。
脚本加载后,通常会注册 update() 或 loop() 回调。ArduPilot 主循环以固定频率调用这些回调,脚本本身不需要写 while true 死循环,否则会阻塞整个调度器。
三、内存限制:约 100KB 的脚本池
Lua 脚本运行在独立的内存池中,不同飞控平台略有差异,但通常可用内存约为 100KB 左右。这个数字看起来不大,但足够运行多个中小型脚本。真正的问题在于内存碎片和不当使用。
| 资源项 | 典型限制 | 说明 |
|---|---|---|
| Lua 内存池 | 约 100KB | 所有脚本共享,不是每个脚本独立 |
| 单脚本大小 | 建议 < 20KB | 过大脚本加载慢、易失败 |
| 脚本数量 | 建议 ≤ 5 个 | 过多会拖慢启动 |
| 全局变量 | 尽量避免 | 全局变量常驻内存,增加碎片 |
| 字符串拼接 | 避免循环内拼接 | 产生大量临时对象 |
常见误区:在 update() 中频繁创建 table、字符串或闭包,会快速消耗内存并导致脚本被卸载。应尽量复用变量,把常量定义在脚本顶层。
四、执行周期:脚本不是实时任务
Lua 脚本由 ArduPilot 调度器按周期调用,常见周期为 10Hz 到 50Hz,具体取决于脚本注册方式和飞控负载。它不适合做微秒级实时控制,更适合做逻辑判断、状态监控、任务辅助和参数调整。
一个典型的周期脚本结构如下:
local counter = 0
function update()
counter = counter + 1
if counter % 10 == 0 then
local volt = battery:voltage()
if volt < 10.5 then
gcs:send_text(0, "Battery low: " .. volt)
end
end
return update, 100 -- 100ms 后再次调用
end
return update, 1000
这里的 return update, 100 表示下一次调用间隔为 100ms,即约 10Hz。脚本必须返回下一次调用的延迟,否则不会继续执行。这个机制让脚本可以灵活控制自己的执行频率,但也要求开发者明确知道自己在做什么。
五、调试输出方式:gcs:send_text 与日志
Lua 脚本最常用的调试手段是 gcs:send_text(),它会把消息发送到地面站的消息栏。参数为消息级别和字符串:
gcs:send_text(0, "INFO: script started")
gcs:send_text(4, "WARN: battery voltage low")
gcs:send_text(6, "ERROR: sensor read failed")
级别数字越大,消息越严重。0 为普通信息,4 为警告,6 为错误。地面站和日志中都能看到这些消息。除此之外,还可以使用 gcs:send_named_float() 发送命名浮点值,方便在日志中绘图分析。
调试建议:不要在每个周期都发送消息,否则会刷屏并占用通信带宽。可以用计数器降频,例如每 10 次循环发送一次。调试完成后及时删除或注释掉调试输出。
六、案例与复盘:一个电池监控脚本
假设我们要写一个电池低压告警脚本,放在 /APM/scripts/battery_monitor.lua:
local LOW_VOLT = 10.5
local count = 0
function update()
count = count + 1
if count % 20 == 0 then
local volt = battery:voltage()
if volt < LOW_VOLT then
gcs:send_text(4, "Battery low: " .. volt .. "V")
end
end
return update, 100
end
return update, 1000
复盘要点:脚本放在正确目录;使用局部变量减少内存占用;每 20 次循环才检查一次,降低负载;返回下一次调用间隔;调试信息只在异常时发送。如果脚本没有运行,依次检查 SD 卡挂载、目录名、扩展名、脚本语法和内存是否超限。
掌握脚本存放位置、启动流程、内存限制、执行周期和调试输出这五个要素,就能让 Lua 脚本在 ArduPilot 中稳定运行,而不是变成“写了但没反应”的黑盒。