基于 RTThread 的 Lua 脚本编译嵌入与 CLI 运行框架
基于 RTThread 的 Lua 脚本编译嵌入与 CLI 运行框架
告别手敲命令:将 Lua 脚本编译进固件,用
lua_run一键运行。
一、痛点
在上一篇文章中,我们让 GD32F527V-START 跑起了 Lua 5.3.4,并且注册了 gpio_mode、gpio_write 等扩展函数。但目前的交互方式有诸多不便:
# 每次都要手动进入交互模式敲代码
msh /> lua
> gpio_mode(28, 1)
> gpio_write(28, 1)
> ...
> [Ctrl+D] 退出
问题:
- ❌ 每次重启都要重新输入
- ❌ 多行脚本难以管理
- ❌ 无法开机自启
- ❌ 不支持多个脚本切换
本文的方案:将 Lua 脚本编译进固件,通过 msh 命令运行。
二、方案设计
2.1 工作流程
┌──────────────────────────────────────────────────────┐
│ 开发阶段 │
│ │
│ 编辑 .lua 文件 │
│ applications/scripts/led_blink.lua │
│ │ │
│ ▼ (IAR Pre-build step) │
│ lua2inc.py 工具 │
│ │ │
│ ▼ │
│ lua_script_table.inc ← 自动生成的 C 头文件 │
│ │ │
│ ▼ (编译) │
│ 固件中包含所有脚本字符串 │
│ │
│ 运行阶段 │
│ msh > lua_run led_blink │
│ │ │
│ ▼ │
│ lua_cmd.c → 查找脚本 → luaL_dostring() → 执行 │
└──────────────────────────────────────────────────────┘
2.2 为什么选择编译嵌入而非文件系统
| 方案 | 优点 | 缺点 |
|---|---|---|
| 编译嵌入(本方案) | 零运行时开销,无 Flash 磨损,不占用额外硬件 | 改脚本要重编译 |
| 内部 Flash + 文件系统 | 可热更新,不依赖 IDE | Flash 有擦写寿命,实现复杂 |
| 外部 SPI Flash | 容量大,可拆卸 | 需要额外硬件 |
综合考虑,编译嵌入方案最适合开发阶段的快速迭代。
三、工具链:lua2inc.py
3.1 功能
将 scripts/*.lua 转换为 C 语言头文件,每个脚本变成 lua_script_t 结构体数组的一个条目:
# tools/lua2inc.py
# 用法:
# python lua2inc.py <scripts_dir> <output_inc>
# 示例:
# python lua2inc.py applications/scripts applications/lua_script_table.inc
3.2 生成的 .inc 文件结构
/* Auto-generated by lua2inc.py - DO NOT EDIT */
#include "lua_script.h"
const lua_script_t lua_script_table[] = {
{"led_blink",
"gpio_mode(28, 1)\n"
"print(\"LED blinking...\")\n"
"for i = 1, 10 do\n"
" gpio_write(28, 1)\n"
" rtos_delay(200)\n"
" gpio_write(28, 0)\n"
" rtos_delay(200)\n"
"end\n"
"print(\"Done!\")\n"},
{"sensor_read",
"..."},
{NULL, NULL} /* 哨兵结束 */
};
static const char *lua_script_names[] = {
"led_blink",
"sensor_read",
NULL
};
3.3 IAR Pre-build 集成
在 IAR 工程选项 → Build Actions → Pre-build command line 中填入:
python $PROJ_DIR$\tools\lua2inc.py $PROJ_DIR$\applications\scripts $PROJ_DIR$\applications\lua_script_table.inc
这样每次编译前自动执行转换,确保 .inc 文件与 .lua 源码同步。
四、脚本注册表
4.1 类型定义
/* applications/lua_script.h */
typedef struct
{
const char *name; /* 脚本名称(不带 .lua 后缀) */
const char *source; /* Lua 源码字符串 */
} lua_script_t;
extern const lua_script_t lua_script_table[];
extern const char *lua_script_names[];
4.2 脚本查找与执行核心逻辑
/* applications/lua_cmd.c */
int lua_run_script(const char *name)
{
const lua_script_t *entry = lua_script_table;
/* 按名称查找 */
while (entry->name != NULL)
{
if (rt_strcmp(entry->name, name) == 0)
break;
entry++;
}
if (entry->name == NULL)
{
rt_kprintf("Script '%s' not found.\n", name);
return -RT_ERROR;
}
/* 创建 Lua 状态 */
lua_State *L = luaL_newstate();
if (L == NULL)
return -RT_ENOMEM;
/* 打开标准库(自动加载 rtext 扩展) */
luaL_openlibs(L);
/* 执行脚本 */
int ret = luaL_dostring(L, entry->source);
if (ret != LUA_OK)
rt_kprintf("Lua error: %s\n", lua_tostring(L, -1));
lua_close(L);
return (ret == LUA_OK) ? RT_EOK : -RT_ERROR;
}
五、msh CLI 命令
5.1 lua_run — 运行脚本
msh /> lua_run led_blink
LED blinking on PB12...
Done!
msh />
实现原理:MSH_CMD_EXPORT_ALIAS 注册 lua_run 命令,接收脚本名称参数,调用 lua_run_script()。
5.2 lua_list — 列出所有脚本
msh /> lua_list
Available Lua scripts:
led_blink
sensor_read
network_test
msh />
5.3 与交互模式的对比
| 模式 | 命令 | 适用场景 |
|---|---|---|
| 交互式 | lua → 逐行输入 → Ctrl+D |
调试、临时测试 |
| 脚本运行 | lua_run <name> |
预置功能、自动化测试 |
| 开机自启 | 在 main() 中调用 lua_run_script() |
产品模式 |
六、完整示例:LED 闪烁脚本
6.1 编写脚本
-- applications/scripts/led_blink.lua
gpio_mode(28, 1) -- PB12, output
print("LED blinking on PB12...")
for i = 1, 10 do
gpio_write(28, 1) -- 亮
rtos_delay(200)
gpio_write(28, 0) -- 灭
rtos_delay(200)
end
print("Done!")
6.2 编译运行
# 确保 Pre-build 已配置好,直接编译
# IAR Build → 自动运行 lua2inc.py → 编译 → 下载
msh /> lua_run led_blink
LED blinking on PB12...
Done!
七、扩展:开机自启脚本
如果想让某个脚本在系统启动时自动运行,只需在 main.c 中调用:
/* applications/main.c */
int main(void)
{
/* 初始化 LED */
rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
/* 开机自动运行 Lua 脚本 */
lua_run_script("led_blink");
while (1)
{
rt_pin_write(LED1_PIN, !rt_pin_read(LED1_PIN));
rt_thread_mdelay(500);
}
}
八、文件结构总结
applications/
├── main.c ← 应用主程序
├── linit.c ← 自定义 Lua 库加载(注入 rtext)
├── lua_cmd.c ← lua_run / lua_list 命令
├── lua_script.h ← 脚本注册表类型定义
├── lua_script_table.inc ← 自动生成的脚本表(由 lua2inc.py 生成)
└── scripts/
└── led_blink.lua ← Lua 源码(手动编辑)
tools/
└── lua2inc.py ← .lua → .inc 转换工具
九、总结与展望
通过本文的方案,我们实现了:
| 功能 | 状态 |
|---|---|
| ✅ 将 .lua 文件编译进固件 | lua2inc.py + IAR Pre-build |
✅ lua_run <name> 运行脚本 |
lua_cmd.c |
✅ lua_list 列出脚本 |
lua_cmd.c |
| ✅ GPIO/LED 控制 | rtext 扩展库 |
| ✅ 开机自启 | 在 main.c 中调用 lua_run_script() |
下一步可以做的
- 保存运行时状态——结合 FlashDB 让脚本在重启后保留变量
- 热更新——通过串口或网络接收新的 .lua 脚本,写入内部 Flash
- 事件驱动——GPIO 中断触发执行指定的 Lua 脚本
- 多脚本调度——实现脚本优先级、定时执行等高级功能
浙公网安备 33010602011771号