3. Lua语言快速入门
ArduPilot 的 Lua 脚本运行在飞控固件内置的 Lua 解释器上,受限于内存与实时性,脚本必须短小、确定、可预测。因此学习 Lua 不能只停留在“能跑”,而要理解它的数据类型、控制流与表结构如何影响脚本在飞控中的执行行为。本节围绕语法基础、数据类型、控制结构、函数、表与模块化六个要点展开,帮助你把 Lua 写成适合飞控环境的自动化脚本。
一、语法基础:轻量、动态、以块为界
Lua 是动态类型语言,变量无需声明类型,赋值即创建。语句之间不需要分号,代码块用 then/end、do/end、function/end 成对界定。注释用 -- 单行、--[[ ]] 多行。局部变量用 local 声明,这是飞控脚本中最重要的习惯:全局变量会长期占用内存并可能被其他脚本污染,而 local 变量在作用域结束后可被回收。
-- 推荐:局部变量,作用域清晰
local count = 0
local name = "copter"
-- 不推荐:全局变量,长期驻留内存
count = 0
name = "copter"
二、数据类型:八种基础类型与飞控常用值
Lua 共有 nil、boolean、number、string、table、function、userdata、thread 八种类型。飞控脚本中最常用的是 number、boolean、string、table 和 function。需要注意:Lua 5.3 之后 number 区分整数与浮点,而 ArduPilot 使用的版本对整数运算更友好;字符串可用单引号或双引号,也可用 .. 拼接;nil 表示“不存在”,常用于判断参数是否有效。
| 类型 | 示例 | 飞控脚本中的典型用途 |
|---|---|---|
| nil | local x = nil | 表示未初始化或参数缺失 |
| boolean | local armed = true | 解锁状态、条件判断 |
| number | local alt = 12.5 | 高度、速度、PID 参数 |
| string | local mode = "AUTO" | 模式名、日志文本 |
| table | local p = {k=1} | 参数集合、配置表、队列 |
| function | local f = function() end | 回调、定时任务、事件处理 |
三、控制结构:条件、循环与提前返回
Lua 提供 if/elseif/else、while、repeat/until、for 四种控制结构。飞控脚本中应优先使用 for 与 if,避免死循环和长时间阻塞。特别要注意:脚本运行在调度器中,任何循环都必须有明确退出条件,否则会拖慢甚至阻塞飞控主循环。
-- 数值 for:遍历 1 到 5
for i = 1, 5 do
gcs:send_text(0, "index " .. i)
end
-- 泛型 for:遍历表
local params = {kp = 0.8, ki = 0.2, kd = 0.05}
for key, value in pairs(params) do
gcs:send_text(0, key .. "=" .. value)
end
-- 条件判断:提前返回,减少嵌套
local function check_alt(alt)
if alt == nil then return false end
if alt < 5 then return false end
return true
end
while true do end 是危险写法。若确实需要周期执行,应使用 ArduPilot 提供的 update() 回调或定时器,而不是自己写死循环。四、函数:一等公民与回调机制
Lua 中函数是一等值,可以赋值给变量、作为参数传递、作为返回值。ArduPilot 脚本框架正是利用这一点,把 update()、init() 等函数注册为回调。函数支持多返回值、可变参数 ... 和闭包。闭包在飞控中常用于保存状态,例如记录上一次的飞行模式或累计计数。
-- 多返回值
local function min_max(a, b)
if a < b then return a, b end
return b, a
end
-- 闭包保存状态
local function make_counter()
local n = 0
return function()
n = n + 1
return n
end
end
local counter = make_counter()
gcs:send_text(0, "count=" .. counter())
五、表:Lua 唯一的数据结构
表(table)是 Lua 的核心,既可作为数组,也可作为字典、对象、模块。表用 {} 创建,索引从 1 开始,支持 # 取长度、table.insert 插入、table.remove 删除、ipairs 顺序遍历、pairs 键值遍历。在飞控脚本中,表常用于组织参数、航点列表、状态缓存和配置项。
-- 数组式表
local waypoints = {10, 20, 30}
table.insert(waypoints, 40)
gcs:send_text(0, "len=" .. #waypoints)
-- 字典式表
local config = {
mode = "AUTO",
speed = 8.5,
failsafe = true
}
gcs:send_text(0, config.mode)
六、模块化编程:require 与返回表
模块化让脚本可复用、可测试。Lua 的模块本质是“返回一个表的文件”,通过 require 加载。ArduPilot 脚本目录中,可以把公共函数放入独立文件,再在主脚本中引入。模块内部应使用 local 隐藏实现细节,只暴露必要接口。
-- 文件:my_utils.lua
local M = {}
function M.clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
return M
-- 主脚本中引用
local utils = require("my_utils")
local out = utils.clamp(15, 0, 10)
七、案例与复盘:一个模式监控脚本
下面这个脚本综合了本节知识点:用局部变量保存状态,用表组织配置,用函数封装逻辑,用条件与循环判断飞行模式变化,并通过 gcs:send_text 输出提示。它体现了飞控脚本“短小、确定、可读”的编写原则。
local config = { report_interval = 100 }
local last_mode = nil
local counter = 0
local function update()
counter = counter + 1
if counter % config.report_interval ~= 0 then return end
local mode = vehicle:get_mode()
if mode ~= last_mode then
gcs:send_text(0, "mode changed to " .. mode)
last_mode = mode
end
end
return update, 1000
复盘要点:第一,所有状态变量都用 local,避免全局污染;第二,用表集中管理配置,便于修改;第三,用取模运算控制上报频率,避免刷屏;第四,函数职责单一,便于调试。常见误区包括:把 update() 写成阻塞循环、在循环中频繁创建表导致内存抖动、忘记处理 nil 导致脚本报错退出。掌握这些基础后,后续章节的参数读写、MAVLink 通信与任务控制都会建立在这一套 Lua 语法与模块化思维之上。