我写了一个 AI Agent,它能直接操控 STM32 硬件:从代码生成到编译烧录到自动调 Bug,全程不用人管

开源项目 Gary CLI 技术深度解析:如何用大模型 + Function Calling 构建嵌入式开发的全自动闭环


〇、写在前面

我是一个嵌入式工程师。

每次开始一个新项目,我的工作流是这样的:打开 STM32CubeMX 点半天配置外设 → Keil 里写代码 → 编译 → 烧录 → 发现 LED 不亮 → 翻 Reference Manual 查寄存器 → 改代码 → 再烧 → 发现串口没输出 → 检查时钟树 → 再改 → HardFault → 崩溃。

一个呼吸灯程序,从想到做完可能要 40 分钟。其中真正在"思考逻辑"的时间不超过 5 分钟,剩下 35 分钟全在跟工具链和硬件搏斗。

我开始思考:大模型已经能写代码了,但它能不能再往前一步——直接帮我编译、烧录、甚至自动修 Bug?

于是我做了 Gary CLI(持矛者)——一个能直接操控物理硬件的 AI Agent。你只需要用中文对它说一句话,它就能自动完成从代码生成到芯片上跑起来的全部流程。

本文将完整介绍 Gary 的设计思路、技术架构、核心算法和实现细节。全文约 12000 字,建议收藏慢慢看。


一、Gary 是什么

一句话定义:Gary 是一个基于命令行的 AI 智能体(Agent),能用自然语言驱动 STM32 的完整开发流程——代码生成、交叉编译、物理烧录、运行验证、自动调试。

它不是一个 VS Code 插件,不是一个 Web 界面,而是一个纯命令行工具。你在终端里跟它对话,它直接操控你桌上的 STM32 开发板。

与现有工具的区别

市面上已有很多"AI 写嵌入式代码"的工具(ChatGPT、Copilot、Cursor),但它们的能力都止步于代码生成——生成的代码对不对、能不能编译、烧到板子上跑不跑得起来,全靠你自己验证。

Gary 的关键区别在于:它能验证自己的输出,并自主修复错误。

ChatGPT / Copilot Gary CLI
代码生成
编译 ❌(复制出来自己编译) ✅ 自动调用 GCC
烧录 ✅ 自动 SWD 烧录
运行验证 ✅ 读串口 + 寄存器
错误自动修复 ✅ 最多 8 轮迭代
硬件感知 ✅ 知道你接了什么外设

这就是 Agent 和 Copilot 的本质区别:Agent 有执行能力和反馈回路,Copilot 只有建议能力。


二、核心架构

Gary 的架构分为四层:

┌─────────────────────────────────────────────────┐
│                  用户交互层                       │
│        Rich TUI + PromptToolkit 命令行           │
├─────────────────────────────────────────────────┤
│                  AI 对话引擎                      │
│     OpenAI API 兼容 + 流式输出 + 工具调用        │
├────────────┬──────────────┬─────────────────────┤
│  代码编译   │   硬件控制    │    扩展工具          │
│  GCC Cross │  SWD 烧录    │  技能系统 (Skills)   │
│  Compiler  │  寄存器读取   │  PID / I2C / PWM    │
│            │  串口监控     │  字模 / 信号分析     │
├────────────┴──────────────┴─────────────────────┤
│                  物理硬件层                       │
│         STM32 目标板 (SWD + UART)               │
└─────────────────────────────────────────────────┘

2.1 AI 对话引擎

Gary 的 AI 对话引擎基于 OpenAI API 兼容协议,这意味着它可以接入任何兼容该协议的大模型服务。我们实测支持了 7 种以上的后端:

服务商 模型 特点
DeepSeek deepseek-chat 性价比之王,默认推荐
Gemini gemini-3-flash 综合能力最强
Kimi kimi-k2.5 中文理解力强
Ollama 本地模型 完全离线,数据不出机

AI 引擎的核心是 Function Calling(工具调用)。我们给大模型注册了一组工具函数,模型在对话中可以自主决定调用哪些工具、以什么参数调用。

举个例子,当用户说"帮我做个 LED 闪烁",大模型内部的推理链条是:

思考:用户想让 LED 闪烁,需要:
  1. 先检查硬件状态 → 调用 stm32_hardware_status()
  2. 发现是 STM32F103C8T6,已连接
  3. 生成 PA0 GPIO 翻转代码
  4. 编译 + 烧录 → 调用 stm32_auto_flash_cycle()
  5. 检查串口输出,确认 Gary:BOOT 启动标志
  6. 读取 GPIOA 寄存器,确认引脚配置正确
  7. 返回用户:"编译烧录成功,LED 已在 PA0 闪烁"

这整个过程中,用户只说了一句话,AI 自主完成了 6 步工具调用。

2.2 编译系统

编译器封装在 compiler.py 中,核心是调用 arm-none-eabi-gcc 交叉编译器。但比直接调 GCC 多做了很多事:

自动链接管理:根据芯片型号自动选择正确的启动文件(startup_stm32f103xb.s)、链接脚本(STM32F103C8Tx_FLASH.ld)和 HAL 库源文件。

依赖分析:如果代码中使用了 HAL_I2C_Master_Transmit(),编译器会自动链接 stm32f1xx_hal_i2c.c,不需要用户手动指定。

错误预处理:在编译前进行静态分析,拦截常见错误。例如检测到代码中使用了 sprintf()(会引入 _sbrk 依赖导致链接失败),自动标记并在 AI 修复时提醒。

# 编译器自动选择 HAL 源文件(简化示意)
def _detect_hal_modules(source_code: str) -> list:
    modules = ["stm32f1xx_hal.c", "stm32f1xx_hal_cortex.c",
               "stm32f1xx_hal_rcc.c", "stm32f1xx_hal_gpio.c"]
    if "HAL_I2C_" in source_code:
        modules.append("stm32f1xx_hal_i2c.c")
    if "HAL_TIM_" in source_code:
        modules.append("stm32f1xx_hal_tim.c")
    if "HAL_ADC_" in source_code:
        modules.append("stm32f1xx_hal_adc.c")
    return modules

编译产物是一个 .bin 文件,直接用于烧录。

2.3 硬件控制

这是 Gary 和普通 AI 编程助手的本质区别——它能直接操控物理硬件。

SWD 烧录

通过 pyocd 库驱动 ST-Link / CMSIS-DAP / J-Link 调试器,将编译好的 .bin 文件写入 STM32 的 Flash。烧录后自动复位,程序立即运行。

# 烧录核心逻辑(简化)
def flash(bin_path: str) -> dict:
    target = pyocd.connect()
    target.flash.program(bin_path, base_address=0x08000000)
    target.reset()
    return {"ok": True, "size": os.path.getsize(bin_path)}
寄存器读取

这是最关键的调试能力。通过 SWD 接口,Gary 可以实时读取 MCU 内部的所有寄存器:

  • RCC 寄存器:确认外设时钟是否使能
  • GPIO 寄存器:确认引脚配置(输入/输出/AF/模拟)
  • TIM 寄存器:确认定时器是否启动、PWM 是否输出
  • I2C/SPI 寄存器:确认通信外设状态
  • SCB_CFSR/HFSR:HardFault 故障分析
# 读取关键寄存器(简化)
REGS = {
    "RCC_APB2ENR": 0x40021018,   # 外设时钟使能
    "GPIOA_CRL":   0x40010800,   # GPIO 配置
    "GPIOA_ODR":   0x4001080C,   # GPIO 输出
    "SCB_CFSR":    0xE000ED28,   # 故障状态
}
for name, addr in REGS.items():
    value = target.read32(addr)
    print(f"{name} = 0x{value:08X}")

AI 拿到寄存器值后,能进行精确的诊断。例如:

  • GPIOA_CRL = 0x44444444 → 所有 PA0-PA7 都是浮空输入(默认复位状态),说明 GPIO 没有被初始化
  • SCB_CFSR = 0x00000200 → PRECISERR 位被置位,说明访问了无效的外设地址(通常是忘了开时钟)
  • I2C1_SR1 = 0x00000400 → AF 位被置位,说明 I2C 总线产生了仲裁丢失
串口监控

通过 pyserial 监听 USART1 输出。我们约定了一个协议:程序启动后第一行必须打印 Gary:BOOT,作为程序成功运行的标志。

这样 AI 就能判断:

  • 收到 Gary:BOOT → 程序启动成功
  • 收到乱码 → 波特率不匹配(通常是系统时钟配置错误)
  • 什么都没收到 → 程序卡死了(可能是 HardFault 或死循环)

三、闭环自愈——Gary 的核心竞争力

代码生成能力,ChatGPT 也有。编译能力,Makefile 也有。但自动发现问题并修复——这才是 Gary 真正的护城河。

3.1 闭环调试的完整流程

        ┌──────────────────────────┐
        │    AI 生成/修改代码       │ ◄─── 用户需求
        └─────────┬────────────────┘
                  │
                  ▼
        ┌──────────────────────────┐
        │    编译                   │
        └─────────┬────────────────┘
                  │ 失败?→ AI 读 GCC 错误信息 → 修复代码 ─┐
                  ▼                                        │
        ┌──────────────────────────┐                       │
        │    SWD 烧录              │                       │
        └─────────┬────────────────┘                       │
                  │ 失败?→ 重连探针重试 ──────────────────┤
                  ▼                                        │
        ┌──────────────────────────┐                       │
        │    串口监控               │                       │
        └─────────┬────────────────┘                       │
                  │ 无 Gary:BOOT?→ AI 分析卡死原因 ──────┤
                  ▼                                        │
        ┌──────────────────────────┐                       │
        │    寄存器分析             │                       │
        └─────────┬────────────────┘                       │
                  │ 异常?→ AI 分析寄存器修复代码 ─────────┘
                  ▼
              ✅ 成功

每一轮的结果(编译错误 / 串口输出 / 寄存器值)都会作为上下文回传给 AI,AI 据此决定下一步操作。最多执行 8 轮迭代,绝大多数问题在 2-3 轮内修复。

3.2 一个真实的自愈案例

用户说:帮我用 I2C1 读取 MPU6050 加速度,串口打印

第 1 轮:AI 生成代码,编译成功,烧录成功。但串口无输出。

  • 寄存器分析:RCC_APB2ENR = 0x00000004(仅 GPIOA 时钟开了)
  • 诊断:忘了开 GPIOB 时钟(I2C1 的 SCL/SDA 在 PB6/PB7)和 I2C1 时钟
  • 修复:补上 __HAL_RCC_GPIOB_CLK_ENABLE()__HAL_RCC_I2C1_CLK_ENABLE()

第 2 轮:编译烧录成功,串口收到 Gary:BOOT,但紧接着输出 I2C Error

  • 寄存器分析:I2C1_SR1 = 0x00000002(ADDR 位未置位 → 无 ACK)
  • 诊断:I2C 地址错误。代码用了 0x68,但 MPU6050 的 AD0 引脚接高电平时地址是 0x69
  • 修复:两个地址都尝试(先发 0x68,无 ACK 则发 0x69)

第 3 轮:编译烧录成功,串口输出正常数据 AccX=1024 AccY=-50 AccZ=16380。寄存器全部正常。✅ 完成。

3 轮自动修复,用户只说了一句话。整个过程用时约 25 秒。

3.3 常见故障的诊断模式

经过大量测试,我们总结了 STM32 最常见的故障模式和对应的寄存器特征:

现象 寄存器特征 根因 自动修复策略
程序完全不运行 SCB_CFSR != 0 HardFault 分析 CFSR 各位含义
GPIO 不输出 GPIOA_CRL = 0x44444444 GPIO 未初始化 检查 GPIO_Init 代码
I2C 无应答 I2C1_SR1 bit1 = 0 地址错误/未接线 切换地址或提示检查硬件
串口乱码 输出非 ASCII 波特率不匹配 检查系统时钟配置
定时器不工作 TIM2_CR1 bit0 = 0 定时器未启动 补 HAL_TIM_Start
PWM 无输出 TIM2_CCER = 0 通道未使能 补 HAL_TIM_PWM_Start
外设时钟未开 RCC_APBxENR 对应位为 0 缺少 CLK_ENABLE 自动补时钟使能

这些模式被编码在 System Prompt 中,作为 AI 的"诊断经验库"。


四、System Prompt 工程——教 AI 做嵌入式工程师

Gary 的 System Prompt 是整个项目中调试时间最长的部分。一个好的 System Prompt 能让 AI 的代码质量提升一个数量级。

4.1 代码风格约束

我们在 Prompt 中明确规定了 STM32 HAL 代码的生成规范:

## 代码规范(关键部分)

1. **禁止使用 printf/sprintf/malloc**
   这些函数依赖 newlib 的堆管理(_sbrk),会导致链接失败。
   使用自定义 Debug_Print 函数代替。

2. **必须包含启动标志**
   main() 进入 while(1) 前必须打印 "Gary:BOOT"。
   这是程序成功运行的唯一标志。

3. **时钟配置**
   F103: 默认使用 HSI 8MHz → PLL × 6 = 48MHz。
   不使用 HSE(不是所有板子都焊了晶振)。

4. **中断和定时器**
   SysTick 中断给 HAL_Delay 使用,必须在 SysTick_Handler 中调用 HAL_IncTick()。
   
5. **GPIO 初始化顺序**
   先开时钟 → 再配置 GPIO → 最后初始化外设。
   忘了开时钟是 STM32 新手最常见的错误。

4.2 自愈策略

System Prompt 中包含了详细的自愈决策树:

## 自愈策略

### 编译失败
- undefined reference to _sbrk → 检测 sprintf/printf/malloc,替换为手写实现
- undefined reference to HAL_xxx → 补充对应 HAL 源文件
- multiple definition → 检查头文件重复包含

### 运行失败(串口无 Gary:BOOT)
1. 读取 SCB_CFSR
   - PRECISERR (bit9) → 访问了未使能时钟的外设
   - UNDEFINSTR (bit16) → 无效指令(栈溢出 / 函数指针错误)
   - IACCVIOL (bit0) → 取指令失败(Flash 地址非法)
2. 如果 CFSR 全零,检查 PC 值
   - PC 在 Flash 范围内 → 程序卡在某个循环(可能是 I2C 等待应答)
   - PC = 0xFFFFFFFF → 栈破坏

### I2C 问题
- SR1 NACK → 设备地址错误或未接线
- SR1 ARLO → 总线仲裁丢失(多主模式冲突)
- SR1 BERR → 总线错误(SDA 被拉低卡死)

4.3 提示词的迭代过程

System Prompt 不是一次写成的,而是经过了一个多月的迭代

  1. V1(纯代码生成):只要求 AI 生成代码,不包含任何调试策略。结果:50% 的代码编译都通不过。
  2. V2(加入编译约束):禁止 sprintf、强制包含启动文件。编译成功率提升到 85%。
  3. V3(加入寄存器诊断):把寄存器地址和含义写进 Prompt。AI 开始能自己分析 HardFault。
  4. V4(加入自愈策略):把故障模式和修复方法写进 Prompt。首次实现 3 轮内自动修复大部分问题。
  5. V5(加入时钟树知识):把 F103 的时钟树、APB 分频、PLL 配置写进 Prompt。解决了大量"串口乱码"问题。

每次迭代都是从真实的失败案例中提炼出的。


五、工具函数设计——让 AI 有手有脚

5.1 Function Calling 工具链

Gary 给 AI 注册了 20+ 个工具函数,按功能分类:

类别 工具 用途
基础 stm32_hardware_status 查询芯片型号、探针连接、工具链状态
stm32_compile 编译 C 代码为 .bin
stm32_flash SWD 烧录到芯片
stm32_serial_read 读取串口输出
stm32_read_registers 读取 MCU 寄存器
高级 stm32_auto_flash_cycle 一键编译→烧录→验证→自愈(核心循环)
stm32_generate_font 中文字符→OLED 点阵 C 数组
调试 stm32_pid_tune PID 自动调参
stm32_i2c_scan I2C 总线设备扫描
stm32_pin_conflict 引脚冲突检测
stm32_pwm_sweep PWM 频率扫描
stm32_signal_capture ADC 信号采集分析
stm32_memory_map Flash/RAM 占用分析
stm32_peripheral_test 外设冒烟测试代码生成
stm32_servo_calibrate 舵机角度校准
stm32_power_estimate 功耗估算

5.2 stm32_auto_flash_cycle——最核心的函数

这个函数封装了完整的"编译→烧录→验证→反馈"闭环,是 Gary 自愈能力的入口:

def stm32_auto_flash_cycle(code: str, max_attempts: int = 8) -> dict:
    """
    自动编译-烧录-验证循环。
    每轮结果回传给 AI,AI 据此决定是否需要修复代码。
    """
    for attempt in range(max_attempts):
        # 1. 编译
        comp = compile(code)
        if not comp["ok"]:
            return {"success": False, "step": "compile", 
                    "error": comp["error"],
                    "remaining": max_attempts - attempt - 1}
        
        # 2. 烧录
        flash_result = flash(comp["bin_path"])
        if not flash_result["ok"]:
            # 烧录失败:重连探针重试一次
            reconnect()
            flash_result = flash(comp["bin_path"])
            if not flash_result["ok"]:
                return {"success": False, "step": "flash",
                        "error": flash_result["error"]}
        
        # 3. 等待串口输出
        time.sleep(0.5)
        serial_output = serial_read(timeout=3.0)
        boot_ok = "Gary:" in serial_output
        
        # 4. 读取关键寄存器
        registers = read_all_registers()
        hardfault = registers.get("SCB_CFSR", 0) != 0
        
        # 5. 判断结果
        if boot_ok and not hardfault:
            return {"success": True, "attempt": attempt + 1,
                    "serial": serial_output,
                    "registers": registers}
        
        # 6. 返回失败信息(AI 据此修复)
        return {"success": False, "step": "runtime",
                "boot_ok": boot_ok,
                "serial": serial_output,
                "registers": registers,
                "hardfault": hardfault,
                "remaining": max_attempts - attempt - 1}

AI 每次收到这个函数的返回值后,会分析 registersserial 字段,决定修改代码的哪些部分,然后再次调用这个函数——形成闭环。

5.3 PID 自动调参——AI 做控制工程师

PID 调参是嵌入式中最经典的"需要经验"的任务。Gary 的 PID 调参工具实现了完全自动化:

工作流程

  1. 生成含 PID 调试输出的代码,串口以固定格式打印:PID:t=20,sp=1000,pv=850,out=45,err=150
  2. 烧录运行,采集 3-5 秒的串口数据
  3. 解析数据,计算响应指标:超调量、上升时间、调节时间、振荡次数、稳态误差
  4. 基于改进 Ziegler-Nichols 规则推荐新参数
  5. 修改代码中的 Kp/Ki/Kd,重新烧录
  6. 重复直到满足收敛条件(超调 < 10%,振荡 < 3 次,稳态误差 < 2%)

调参策略

响应特征 参数调整
超调过大 (> 20%) 降 Kp × 0.7,升 Kd × 1.3
响应太慢 (上升时间 > 目标 2 倍) 升 Kp × 1.5
振荡过多 (> 5 次) 降 Kp × 0.6,升 Kd × 1.5
稳态误差大 (> 3%) 升 Ki × 1.5
# PID 响应分析(核心逻辑)
def analyze_pid_response(data: list) -> dict:
    setpoint = data[0]["sp"]
    pv_values = [d["pv"] for d in data]
    
    # 超调量
    peak = max(pv_values)
    overshoot = (peak - setpoint) / setpoint * 100
    
    # 上升时间 (10% → 90%)
    target_10 = setpoint * 0.1
    target_90 = setpoint * 0.9
    rise_time = find_crossing_time(pv_values, target_90) - \
                find_crossing_time(pv_values, target_10)
    
    # 振荡次数(穿越设定值的次数 / 2)
    crossings = count_setpoint_crossings(pv_values, setpoint)
    oscillations = crossings // 2
    
    # 稳态误差(最后 20% 数据的平均偏差)
    tail = pv_values[int(len(pv_values) * 0.8):]
    steady_error = abs(sum(tail) / len(tail) - setpoint) / setpoint * 100
    
    return {
        "overshoot_pct": overshoot,
        "rise_time_ms": rise_time,
        "oscillations": oscillations,
        "steady_error_pct": steady_error,
    }

六、技能系统——让 Gary 可扩展

6.1 设计哲学

Gary 本体只包含最基础的编译烧录能力。所有高级工具(PID 调参、I2C 扫描、PWM 扫描等)都以 Skill(技能包) 的形式存在。

这么设计的原因:

  • 解耦:核心代码保持精简,不会因为某个工具的 Bug 影响整体
  • 可分享:用户可以把自己的工具打包分享给别人
  • 可扩展:社区可以贡献各种领域的工具包(电机控制、传感器驱动、通信协议...)

6.2 Skill 的标准结构

~/.gary/skills/
├── pid_tuner/
│   ├── skill.json        ← 元信息(名称、版本、作者、标签、依赖)
│   ├── tools.py          ← 工具函数(Python,必须导出 TOOLS_MAP)
│   ├── schemas.json      ← AI 调用格式(OpenAI Function Calling Schema)
│   ├── prompt.md         ← 追加到 System Prompt 的内容
│   ├── requirements.txt  ← Python 依赖
│   └── README.md         ← 人类文档

三个核心文件

  • tools.py:Python 函数,每个函数接收参数、返回 dict
  • schemas.json:告诉 AI 这些函数的名称、参数、用途(OpenAI Function Calling 格式)
  • prompt.md:告诉 AI 在什么场景下应该使用这些函数

这三个文件缺一不可——没有 schemas.json,AI 不知道怎么调用;没有 prompt.md,AI 不知道什么时候该调用。

6.3 安装和使用

# 最简方式:直接从 .py 文件安装(自动包装成 Skill)
/skill install stm32_extra_tools.py

# 安装别人分享的 zip
/skill install gary_skill_motor_control.zip

# 从 Git 仓库安装
/skill install https://github.com/someone/gary-skill-sensor.git

# 自己创建新 Skill
/skill create my_tool "我的工具描述"
# → 生成模板目录,编辑后 /skill reload 即可

# 打包分享
/skill export my_tool
# → gary_skill_my_tool.zip

6.4 热加载机制

Skill 安装后无需重启 Gary。Skills Manager 通过 Python 的 importlib 动态加载模块,并将新工具的 TOOLS_MAPTOOL_SCHEMAS 实时注入到 AI 对话引擎中。

# 热加载核心逻辑
spec = importlib.util.spec_from_file_location(name, tools_py_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# 提取并注册
TOOLS_MAP.update(module.TOOLS_MAP)
TOOL_SCHEMAS.extend(module.TOOL_SCHEMAS)

# AI 下一次对话就能使用新工具了

七、字模生成——让 OLED 显示中文

在嵌入式开发中,OLED 显示中文是一个经典难题。传统方案是用 PCtoLCD 等取模软件手动一个一个字取,非常繁琐。

Gary 内置了字模生成工具 stm32_generate_font,核心原理是用系统字体库直接渲染

from PIL import Image, ImageDraw, ImageFont

def generate_font_data(text: str, size: int = 16) -> list:
    """用系统字体渲染文字为位图,转换为 C 数组"""
    font = ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoSansSC-Regular.ttf", size)
    
    result = []
    for char in text:
        # 创建位图画布
        img = Image.new('1', (size, size), 0)
        draw = ImageDraw.Draw(img)
        draw.text((0, 0), char, font=font, fill=1)
        
        # 转换为 C 数组(逐行扫描)
        pixels = list(img.getdata())
        bytes_data = []
        for row in range(size):
            for col_byte in range(size // 8):
                byte = 0
                for bit in range(8):
                    px_idx = row * size + col_byte * 8 + bit
                    if pixels[px_idx]:
                        byte |= (0x80 >> bit)
                bytes_data.append(byte)
        
        result.append({"char": char, "data": bytes_data})
    
    return result

用户只需说:OLED 上显示中文"温度:25℃",Gary 自动:

  1. 提取需要的字符集:"温度:25℃"
  2. 调用字模工具渲染每个字符
  3. 生成 C 数组嵌入代码
  4. 编译烧录

优势:不需要任何外部取模软件,支持所有 Unicode 字符(包括中日韩、emoji),字模质量取决于系统字体(Noto Sans SC 效果很好)。


八、BYOK——支持任意大模型

8.1 为什么做 BYOK

BYOK(Bring Your Own Key)是 Gary 的核心设计原则之一:

  1. 成本可控:嵌入式开发者的 AI 调用量不大(每次对话 10-50 次 API 调用),用 DeepSeek 一天花不了 1 块钱
  2. 数据隐私:代码不经过我们的服务器,直接发到你选择的 AI 服务商
  3. 模型自由:想用最强的 GPT-4o 可以,想用免费的 GLM-4-Flash 也行,想完全离线用 Ollama 也没问题

8.2 配置向导

gary config 提供了交互式配置:

? 选择 AI 服务商:
  ❯ DeepSeek(推荐)
    OpenAI
    Kimi / Moonshot
    Google Gemini
    通义千问
    智谱 GLM
    Ollama(本地)
    自定义

? 输入 API Key: sk-xxxxxxxxxxxxxxxx
? 选择模型: deepseek-chat

✓ 配置已保存。测试连接...
✓ API 连通正常。Token 消耗测试通过。

8.3 Ollama 本地部署

对于对数据安全要求极高的场景(国防、航空等),Gary 支持完全本地运行:

# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh

# 下载模型(推荐 14B 以上,7B 的函数调用能力不够)
ollama pull qwen2.5-coder:14b

# 配置 Gary
gary config
# 选择 Ollama → 输入模型名 → 完成

所有数据(代码、对话、API 调用)都在本机完成,不经过任何外部服务器。


九、环境诊断——一键排查所有问题

嵌入式开发的环境配置是新手最大的拦路虎。gary doctor 一键诊断所有组件:

  ■ AI 接口
    ✓ API Key   sk-abc...xyz
    ✓ Base URL  https://api.deepseek.com/v1
    ✓ Model     deepseek-chat
    ✓ API 连通   测试通过

  ■ 编译工具链
    ✓ arm-none-eabi-gcc  (GNU Arm) 13.2.1
    ✓ HAL 库      STM32F1xx, STM32F4xx
    ✓ CMSIS Core

  ■ Python 依赖
    ✓ openai     1.30.0
    ✓ rich       13.7.0
    ✓ pyserial   3.5
    ✓ pyocd      0.36.0

  ■ 硬件探针
    ✓ ST-Link V2  (066EFF...)
    ✓ 串口 /dev/ttyUSB0 @ 115200

  ✅ 所有核心配置正常,Gary 已就绪!

任何一项失败都会给出明确的修复建议:

  ✗ arm-none-eabi-gcc  未找到
    修复:sudo apt install gcc-arm-none-eabi
    
  ✗ API 连通  超时
    修复:检查网络或设置代理 export HTTPS_PROXY=...

十、实战效果

10.1 效率对比

我们在 10 个常见的 STM32 任务上对比了传统开发和 Gary 的耗时:

任务 传统开发 Gary 加速比
LED 闪烁 15 分钟 8 秒 112x
PWM 呼吸灯 25 分钟 12 秒 125x
UART 串口打印 20 分钟 10 秒 120x
I2C 读取传感器 40 分钟 25 秒 96x
OLED 中文显示 60 分钟 30 秒 120x
数码管动态扫描 45 分钟 20 秒 135x
PID 电机控速 3 小时 5 分钟 36x
ADC 电压采集 20 分钟 12 秒 100x
多外设综合 2 小时 3 分钟 40x
新板冒烟测试 30 分钟 20 秒 90x

注:传统开发时间包含查手册、配置 CubeMX、写代码、调试。Gary 时间从输入命令到程序跑起来。

10.2 可靠性数据

在 100 次随机任务测试中:

  • 首次编译成功率:95%
  • 3 轮内自愈成功率:85%
  • 8 轮内自愈成功率:92%
  • 需要人工介入:8%(主要是硬件接线问题,软件无法解决)

十一、支持的硬件

芯片系列

系列 代表型号 Flash RAM 状态
STM32F0 F030, F072 16-128 KB 4-16 KB ✅ 已支持
STM32F1 F103C8T6 (BluePill) 64-512 KB 20-64 KB ✅ 默认
STM32F3 F303CC 256 KB 40 KB ✅ 已支持
STM32F4 F401, F407, F411 256-1024 KB 64-128 KB ✅ 已支持
ESP32 🚧 开发中

调试器

调试器 接口 状态
ST-Link V2 SWD ✅ 最常用
CMSIS-DAP SWD ✅ 开源方案
J-Link SWD ✅ 专业级

接线

只需 4 根线:

ST-Link          STM32
  SWDIO ────────→ PA13
  SWCLK ────────→ PA14
  GND ──────────── GND
  3.3V ─────────── 3.3V

加一根 USB-TTL 的串口线(PA9/PA10)可以监控程序输出,但不是必须的。


十二、未来规划

短期(1-3 个月)

  • ESP32 支持:接入 ESP-IDF 编译链和 esptool.py 烧录
  • MicroPython 模式:通过 REPL 直接在芯片上执行 Python 代码,不需要编译
  • 技能市场:在线浏览和安装社区贡献的 Skill

中期(3-6 个月)

  • 可视化波形:串口数据实时绘图(类似简易示波器)
  • STM32CubeMX 项目导入:兼容 .ioc 配置文件
  • 多芯片项目:支持一个项目中同时操控多块 STM32

长期

  • VS Code 扩展:图形化界面
  • 硬件原理图分析:上传原理图,AI 自动理解硬件连接
  • PCB 布局建议:基于电路分析给出走线建议

十三、安装与上手

一键安装

# Linux / macOS / WSL
curl -fsSL https://www.garycli.com/install.sh | bash

# Windows (PowerShell)
irm https://www.garycli.com/install.ps1 | iex

手动安装

git clone https://github.com/garydev/gary-cli.git
cd gary-cli
pip install -r requirements.txt
sudo apt install gcc-arm-none-eabi   # Ubuntu
pip install pyocd pyserial
gary config   # 配置 AI 接口
gary doctor   # 环境诊断

5 分钟上手

# 1. 连接硬件(ST-Link + STM32 BluePill)
# 2. 启动 Gary
gary --connect

# 3. 开始对话
Gary > 帮我做个 LED 闪烁,PA0 引脚
  ✓ 编译烧录成功 3.1KB,LED 已在 PA0 闪烁

Gary > 改成呼吸灯
  ✓ 已改为 PWM 呼吸效果

Gary > 加一个按键 PA1,按下切换呼吸速度
  ✓ 增量添加按键逻辑

Gary > 再加个 OLED 显示当前速度
  ✓ I2C 扫描到 0x3C (SSD1306),OLED 显示正常

就是这么简单。


十四、总结

Gary 要回答的核心问题是:AI 能不能真正替代嵌入式工程师的日常工作?

答案是:日常的 80% 可以。

那些重复性的、有明确模式的工作——GPIO 配置、I2C 通信、PWM 输出、串口打印——AI 做得比人快 100 倍,而且不会忘记开时钟、不会把引脚配错模式。

剩下的 20%——复杂的算法设计、创新的硬件方案、极端的性能优化——仍然需要工程师的经验和创造力。

Gary 的定位不是"替代工程师",而是让工程师把 80% 的时间省下来,专注于真正需要思考的 20%

这才是 AI 在嵌入式领域的正确打开方式。


项目地址https://github.com/PrettyMyGirlZyy4Embedded/garycli

官网https://www.garycli.com

协议:Apache-2.0 开源

如果觉得有用,请给个 ⭐ Star,这是对开源项目最好的支持。

"Just Gary Do it."

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐