基于 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()

下一步可以做的

  1. 保存运行时状态——结合 FlashDB 让脚本在重启后保留变量
  2. 热更新——通过串口或网络接收新的 .lua 脚本,写入内部 Flash
  3. 事件驱动——GPIO 中断触发执行指定的 Lua 脚本
  4. 多脚本调度——实现脚本优先级、定时执行等高级功能
posted @ 2026-06-24 15:27  口嗨养生博  阅读(29)  评论(0)    收藏  举报