写这篇内容之前,先说个现象:我做 STM32 开发这几年,身边同事的桌面上从清一色的 Keil 图标,慢慢变成了 VS Code 和黑乎乎的终端窗口。原以为只是编辑器之争,深入用下来才发现,这其实是整套工具链和开发方式的切换,尤其是在 AI 编程助手普及之后,VS Code 这套开放生态的优势被放大了数倍。这一期就把我实际搭建 STM32 + VS Code 开发环境的完整过程、踩过的坑,以及怎么把 AI 编程真正接入嵌入式工作流,一次说透。
1. 为什么我把 STM32 开发环境从 Keil 迁到 VS Code
1.1 传统 IDE 的痛点让我越来越不想打开 Keil
先不急着否定 Keil。MDK 作为 STM32 入门的标准工具,工程模板多、教程多、遇到问题能搜到大量现成答案,这些优势不可否认。早期我所有外设驱动都是在 Keil 里写的,调试器用 J-Link 和 ST-Link 都顺手。但随着项目数量增加,几个问题越来越明显:编辑器的代码补全还停留在十年前水平;多文件跳转和全局搜索一旦工程大一点就卡;Git 集成基本靠外部工具;最要命的是跨平台能力差,我偶尔想在 Linux 或远程服务器上编译一下,MDK 完全给不了支持。
还有一点很关键,就是 AI 编程工具的生态。现在主流的 AI 编程插件几乎都是优先支持 VS Code 和 JetBrains 系,对齐到 Keil 上的插件少得可怜,即便能用也不是原生体验。2025 年做嵌入式开发,不顺手用 AI 辅助真的有点吃亏——这不是偷懒,而是把重复劳动压缩,把时间花在更值得的架构设计和协议实现上。所以迁移环境这件事,本质上是向现代开发工作流靠拢。
1.2 这套工具链的组成与选型思路
我从头到尾用的方案是:STM32CubeMX(生成初始化代码)+ VS Code(编辑器)+ GCC ARM 工具链(编译)+ OpenOCD(调试烧录) + ST-Link(调试器)。
每个组件的位置和作用我理一下:
| 组件 | 作用 | 为什么选它 |
|---|---|---|
| STM32CubeMX | 生成工程骨架、外设初始化代码、时钟树配置 | 官方工具,HAL 库代码自动生成,减少手写出错 |
| VS Code | 代码编辑、项目管理、插件生态 | 轻量、跨平台、AI 插件丰富,远程开发也方便 |
| ARM GCC(arm-none-eabi-gcc) | 编译、链接 | 免费开源,命令行可控,适合脚本化和 CI |
| OpenOCD | 烧录、调试代理 | 开源调试器,配合 ST-Link 即可完成任务 |
| ST-Link | 硬件调试器 | 板载即可,成本低,几乎人手一个 |
这套组合要理解成一个流水线:CubeMX 负责“生成”,VS Code 负责“编辑”,GCC 负责“编译”,OpenOCD 负责“烧录和调试”。每一环都可以单独替换,比如把调试器换成 J-Link,只要把 OpenOCD 的配置文件换掉就行;把编译工具换成 CLang 也并非不可能。这种解耦正是 VS Code 方案的魅力:没有被某个厂商锁死。
实际搭建的时候,我推荐直接安装 ST 官方的 STM32CubeCLT(Command Line Tool)。它把 ARM GCC、OpenOCD 以及 STM32CubeProgrammer 的命令行工具打包在一起,一次安装就解决了编译器和调试器两大依赖,省得手动去 Arm Developer 官网和 OpenOCD 官网分别下载。
2. 手把手搭建:从安装软件到点亮一颗 LED
2.1 安装 VS Code 与核心插件
VS Code 的安装本身不多说,官方安装包装完即可。但插件不要贪多,核心就这几个:
- C/C++(Microsoft 官方)——提供 IntelliSense 代码补全、语法高亮、调试支持,是整个环境的基础。
- Cortex-Debug——配合 OpenOCD 或 PyOCD 做 ARM Cortex-M 调试,比自带的调试器好用得多。
- Makefile Tools(可选,如果使用 Makefile 工程这就用得上)——提供 Makefile 目标浏览和编译任务识别。
- CMake Tools(如果走 CMake 工程路线)——替代 Makefile 工具链,CubeMX 也可以生成 CMake 工程。
插件安装完第一件事,我建议先用arm-none-eabi-gcc --version在终端里确认编译器可用。如果提示找不到命令,说明环境变量没配好,这一步不解决后面全卡住。
2.2 安装编译器、OpenOCD 与 make
如果你和我一样装了 STM32CubeCLT,那么编译器和 OpenOCD 都在里面。Windows 下默认路径类似:
C:\ST\STM32CubeCLT_1.15.0\GNU-tools-for-STM32\bin\arm-none-eabi-gcc.exe C:\ST\STM32CubeCLT_1.15.0\OpenOCD\bin\openocd.exe需要手动加环境变量。右键“此电脑”→“属性”→“高级系统设置”→“环境变量”,在 Path 里加入上述 bin 路径。添加后重开终端,测试三个命令:
arm-none-eabi-gcc --version openocd --version make --version如果make提示找不到,说明系统里还没有 GNU Make。CubeMX 生成的 Makefile 工程需要 make 来驱动,这一步缺了编译起不来。我一般用 Chocolatey 装,一行命令:
choco install make不想装 Chocolatey 的话,去 GnuWin32 或 EzWin32 下载 make.exe 并把所在目录加入 Path 也行。社区版的 GNU ARM Embedded Toolchain 也经常附带 make 的独立包,多看两眼就好。
有一点要提醒:VS Code 对 Makefile 工程的支持主要通过任务(Task)实现,不是打开目录就能自动编译。所以配置 tasks.json 是必不可少的一步。
2.3 用 STM32CubeMX 生成基础工程
CubeMX 的图形化界面本身不复杂,但我见过不少新手卡在“选芯片”这一步。我的建议是:先确认你的板子具体是哪个型号、Flash 多大、引脚编号是多少,再在 CubeMX 里搜索对应的 MCU。比如经典的蓝丸板用的是 STM32F103C8T6,重点信息是 Cortex-M3 内核、64KB Flash、20KB RAM、LQFP48 封装。
CubeMX 里的关键设置流程:
- 新建工程,选择自己的 MCU 型号。
- 配置时钟树(RCC)。外部晶振如果用的 8MHz HSE,就在 RCC 里选择 Crystal/Ceramic Resonator,然后在 Clock Configuration 页面把 HCLK 调到你需要的频率,比如 72MHz。
- 配置外设。点亮 LED 最基础的就是一个 GPIO 输出引脚,把它设为 GPIO_Output,初始电平按板子实际情况设。
- 在 Project Manager 页面设置工程名、存放路径。
- Toolchain / IDE 选择:这里注意,我推荐选
Makefile,这样会生成一整套带 Makefile 的 GCC 工程。当然也可以选CMake,看你自己路线。 - 点击 GENERATE CODE,等待生成完成。
生成后的目录里会有Core、Drivers等目录,以及一个Makefile。接下来就用 VS Code 打开这个工程文件夹。
这里有个很实用的经验:CubeMX 每次重新生成代码时会覆盖用户代码区域之外的初始化部分,但/* USER CODE BEGIN */和/* USER CODE END */包裹的代码会被保留。所以自己的业务逻辑务必写在 USER CODE 段里面,否则重新生成时可能被冲掉。刚开始用 VS Code 方案的朋友尤其容易在这块翻车。
2.4 配置编译任务与一键烧录命令
打开工程后,按下Ctrl+Shift+P,输入Tasks: Configure Task,选择“使用模板创建 tasks.json 文件”,然后从模板中选择Others。我这边直接给出已经验证可用的配置:
{ "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "make", "args": ["-j8"], "options": { "cwd": "${workspaceFolder}" }, "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"] }, { "label": "flash", "type": "shell", "command": "openocd", "args": [ "-f", "interface/stlink.cfg", "-f", "target/stm32f1x.cfg", "-c", "program build/main.elf verify reset exit" ], "options": { "cwd": "${workspaceFolder}" }, "dependsOn": ["build"] } ] }这个配置里有两个任务:build负责编译,flash负责编译后烧录。-j8表示并行编译 8 个任务,编译更快。program build/main.elf verify reset exit这条命令的意思是:把 ELF 文件烧录到芯片,校验,复位,然后退出 OpenOCD。
实际执行时,按Ctrl+Shift+B默认执行 build,烧录就到命令面板里运行“任务:运行任务”,选择 flash,或者直接在终端手动输入:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program build/main.elf verify reset exit"注意 target 配置文件要和芯片系列对应,STM32F1 就用stm32f1x.cfg,F4 就用stm32f4x.cfg。如果你用的是 ST 官方开发板,OpenOCD 的 board 目录里一般有对应板卡的 cfg 文件,直接board/st_nucleo_f103rb.cfg也行,比手动指定 interface 和 target 更省事。
2.5 配置 Cortex-Debug 实现 F5 一键调试
编译和烧录解决后,下一步就是调试。VS Code 的调试需要两份配置:一个是launch.json定义调试会话,一个是c_cpp_properties.json让 IntelliSense 知道头文件和宏定义。
先看 launch.json。
在 VS Code 左侧“运行和调试”面板,点击“创建 launch.json 文件”,选择Cortex-Debug: OpenOCD调试类型,然后修改为类似下面这样:
{ "version": "0.2.0", "configurations": [ { "name": "STM32 Debug", "type": "cortex-debug", "request": "launch", "servertype": "openocd", "device": "STM32F103C8", "interface": "swd", "executable": "${workspaceFolder}/build/main.elf", "svdFile": "${workspaceFolder}/STM32F103.svd", "runToEntryPoint": "main", "cwd": "${workspaceFolder}", "serverargs": [ "-f", "interface/stlink.cfg", "-f", "target/stm32f1x.cfg" ] } ] }其中executable指定编译生成的 ELF,svdFile是寄存器描述文件,有了它调试时可以实时查看外设寄存器状态。SVD 文件在 STM32CubeF1 固件包里或者从芯片厂商官网下载,放到工程目录后把路径配好。
配置完成后再设置c_cpp_properties.json。这一步是新手问得最多的地方,因为很多从 Keil 转过来的朋友一打开 VS Code 就看到满屏红色波浪线,像代码全错了一样,其实只是 IntelliSense 不知道头文件在哪。我在这个文件里放的是实际生效配置:
{ "configurations": [ { "name": "STM32", "includePath": [ "${workspaceFolder}/Core/Inc", "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc", "${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc/Legacy", "${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include", "${workspaceFolder}/Drivers/CMSIS/Include" ], "defines": [ "STM32F103xB", "USE_HAL_DRIVER" ], "compilerPath": "C:/ST/STM32CubeCLT_1.15.0/GNU-tools-for-STM32/bin/arm-none-eabi-gcc.exe", "cStandard": "c11", "intelliSenseMode": "gcc-arm" } ], "version": 4 }这份配置值的两个要点:第一,includePath必须包含所有头文件目录,尤其是 HAL 驱动目录和 CMSIS 目录,漏一个就会红一片;第二,defines里的STM32F103xB要和芯片型号匹配,不同 F1 型号的宏定义不同,F103C8 用STM32F103xB,F103ZET6 用STM32F103xE,写错会导致条件编译片段对不上,同样疯狂报红。
配置完成后按 F5,Cortex-Debug 会自动启动 OpenOCD 服务并连接 ST-Link,程序会停在 main 函数入口,接下来就可以像用其他 IDE 一样打断点、看变量、看寄存器了。
3. 把 AI 编程能力接进 STM32 开发流程
3.1 嵌入式场景下的 AI 编程工具选型
标题里说的“嵌入式软件 AI 编程”不是噱头,而是我现在工作的常态。2025 年的 AI 编程工具已经发展出两条路线,我们做嵌入式的人得分开看:
路线一是“补全和问答”型,代表是 GitHub Copilot、通义灵码、CodeGeeX 这类。它们嵌入 VS Code,写代码时自动补全下一段,选中代码可以对话解释。它们胜在轻量,但面对 STM32 这种需要精确配置寄存器的场景,补全质量一般,仍然需要自己动手校正。
路线二是“Agent 任务”型,代表是 Claude Code、OpenAI Codex CLI、Cline 这类。它们能读写工程文件、执行终端命令、查看编译输出,按自然语言任务自动修改代码。特别是 Claude Code 这种命令行 Agent,我试过直接让它“把串口轮询改成 DMA 接收 + 空闲中断”,它能自己翻代码、改驱动、重构回调函数,我只需要做代码审查。
还有一类值得提:本地大模型 + Continue 插件,像 Ollama 跑的 Qwen,配合 Continue 这个开源插件接入 VS Code。适合对数据敏感、不能把代码传到云端 API 的场景。嵌入式项目有时涉及未公开的板卡和协议,本地部署更安心。
我的建议是:不想折腾的用通义灵码或 Copilot 这类,先把补全用起来;愿意花时间调教工作流的,优先试 Continue/Cline 接 DeepSeek API,成本低,质量也可控。
3.2 在 VS Code 里接入 AI 的两种实用方式
方式一:通过插件接入云端大模型 API。以 Continue 插件为例,安装后在配置里新建一个聊天模型,填入 API Key 和 Base URL。DeepSeek 的 OpenAI 兼容接口以https://api.deepseek.com为 Base URL,模型名填deepseek-chat即可。之后框选代码,让它在侧边栏分析、生成、解释。我实测下来最常用的场景是“把这个 HAL 初始化函数改成不用 DMA 的版本”以及“帮我解释这行位操作的含义”。
方式二:在 VS Code 终端里跑命令行 Agent。比如 Claude Code 的 CLI,在项目根目录启动后,用自然语言描述任务,它会自己读取文件、修改代码、执行 make 编译。这种方式一开始让我有点不适,因为把自己不太能掌控的修改权交给了 AI,但用熟了之后,习惯变成:先要求它“不要动用户代码段以外的初始化区域”“只修改指定函数”,再逐条检查 Diff,最后编译验证。相对可控,效率也高。
还要重点提一下“skill”概念。Cline 这类工具支持定义固定提示词模板,我在实际项目中维护了一个stm32-skill.md,里面写好了几条固定的工作流。比如“生成 GPIO 驱动”“生成 DMA+空闲中断串口框架”“生成 Modbus CRC 计算”,调用的时候直接让 AI 按模板执行,输出稳定很多。这比随手打一句“帮我写个驱动”强十倍,因为模板里写明了芯片型号、HAL 版本、引脚定义、错误处理风格等约束条件。
3.3 嵌入式专属提示词的写法:让大模型不再“胡编”
不少朋友抱怨 AI 生成的 STM32 代码一编译全是错,其实大部分问题出在提示词没给足上下文。大模型不像人,不会自己去看你的原理图和型号规格书,它只能根据你提供的信息推断。我给一个实际的模板,你直接抄就能用:
你是一名精通 STM32 的嵌入式软件工程师。 请为 STM32F103C8T6 编写一段 HAL 库代码,实现按键控制 LED 的翻转。 硬件连接: - PA0 接按键,按下为低电平,悬空为高; - PB0 接 LED,高电平点亮; 要求: - 使用 STM32Cube HAL,不混用标准库; - 按键消抖时间 20ms; - 按一次按键翻转一次 LED,长按不重复翻转; - 函数命名以 BSP_ 开头,提供 .c 和 .h,代码要能直接编译; - 不要解释,直接输出代码。看到没有,核心要素就三个字:芯片(STM32F103C8T6)、库(HAL)、行为(按键控制 LED,消抖 20ms,翻转模式)。很多 AI 出错的场景,是因为用户只写了“写一个按键控制 LED”,没有指定 HAL、没指定引脚、没指定消抖方式,AI 就会用自己训练数据里最常见的方式发挥,可最常见的方式可能来自 Arduino,也可能来自标准库,虽然也能跑但这个项目里就未必合适。
我还常用一个专门做代码审查的提示词:
你是一名嵌入式硬件安全审查专家,请审查下面的 STM32 HAL 代码: 1. 是否存在与外设初始化冲突的重复配置; 2. 中断回调中是否有耗时操作; 3. 是否遗漏了外设时钟使能; 4. GPIO 配置是否有上下拉、速度、模式冲突; 5. 是否有潜在的编译警告。 请逐条列出问题,并给出修改建议。这个提示词特别适合在接受 AI 生成代码之后,做一轮自动 review,很多低级错误比如忘记__HAL_RCC_GPIOB_CLK_ENABLE()都会被揪出来。
3.4 实战:用 AI 生成按键控制 LED 代码并编译验证
下面这一节完全是实录。我新建了一个 CubeMX 工程,只初始化了 RCC 和 GPIO,其余都交给 AI 去填。我向 Claude 发送了上文写好的模板提示词,它返回的代码经过我微调后是:
void BSP_KeyLed_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } void BSP_KeyLed_Process(void) { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { HAL_Delay(20); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET) { } } } }然后在 main 里调用:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); BSP_KeyLed_Init(); while (1) { BSP_KeyLed_Process(); } }整个流程从提问到编译通过大概十分钟,其中我自己改动的时间不到五分钟。这里最值得说的不是 AI 多厉害,而是人机分工:CubeMX 管最底层时钟和芯片初始化,AI 管逻辑代码,我管接线验证和检查。这个分工模式基本确定了我后续做复杂项目时的工作流:骨架给 CubeMX,模块逻辑给 AI,驱动细节和问题排查留给人。
4. 常见问题与排查技巧实录
4.1 编译没问题,VS Code 却满屏红色波浪线
这是从 Keil 切换到 VS Code 后几乎每个人都会遇到的第一道坎。症状很典型:终端里 make 编译通过,固件也烧进去了,但 VS Code 编辑器里到处都是红色波浪线,点开一看提示“无法打开源文件 stm32f1xx_hal.h”。
原因通常是c_cpp_properties.json里的 includePath 没配全,或者 defines 少了芯片型号宏。像 STM32F103C8T6 需要STM32F103xB,这个宏影响的是 HAL 库里条件编译的分支选择,少了它会有一堆头文件定义冲突或缺失,红色波浪线自然一片。
解决办法就是把上文给出的c_cpp_properties.json对号入座改一遍。编译器的路径也确认要指向 arm-none-eabi-gcc.exe,这个路径不对同样会引发各种奇怪的 IntelliSense 错误。
还有个小技巧:调完后在代码里随便找一个头文件包含,按Ctrl+点击能跳转进去,基本就代表路径配置成功了。如果不能跳转,逐项排查 includePath 里的相对路径是否和工程目录匹配。CubeMX 生成的 Makefile 工程目录结构是固定的:Core/Inc、Drivers/...,照着填就行。
4.2 make 命令无法识别或者编译报“未找到命令”
Windows 下最容易出现这个问题。打开 VS Code 终端,输入make提示“不是内部或外部命令”,而arm-none-eabi-gcc --version却有输出,说明编译器的 path 加进去了,但 make 没加。
更诡异的情况是:系统终端里 make 能用,VS Code 终端里不能用。这是因为 VS Code 的终端环境变量来自它启动时读取的配置,如果 VS Code 是在添加环境变量之前启动的,终端里还是旧环境。重启 VS Code 就能解决,或者在终端里输入$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')手动刷新一次。
如果实在不想碰 make,也可以回到 CubeMX 重新生成 CMake 工程,然后改用 CMake Tools 插件。CMake 工程用 CMakeLists.txt 驱动,调 CMake + Ninja 方式编译,效果等价,环境变量问题会少一些。
4.3 OpenOCD 烧录失败或者连接不上芯片
烧录调试时报错的种类不少,但最常见的是这几种:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
| “Error: open failed” | ST-Link 驱动没有正确安装,或者端口被占用 | 安装 ST 官方 ST-Link 驱动;关闭其他占用 ST-Link 的程序 |
| “target not halted” | 接线松动、目标芯片供电异常,或芯片进入低功耗/读保护状态 | 检查 SWDIO/SWCLK/GND 接线;确保 3.3V 供电;用 STM32CubeProgrammer 连接并解除读保护 |
| “Info : Unable to match requested speed” | 目标板稳定性差,调试速度太高 | 在 OpenOCD 参数里加-c "adapter speed 1000"降速 |
| “Error: flash write failed” | Flash 保护,或程序在运行中断开了调试器 | 先用 STM32CubeProgrammer 的“Full chip erase”清一次再试 |
我遇到最多的还是“target not halted”,尤其是用了国产 ST-Link 拷贝版的时候。这种问题往往不是软件配置错,而是接线太长或者供电不稳定。排查时先把 SWD 线缩短到 10cm 以内,调试时钟降到 1MHz,绝大多数情况能解决。
如果 OpenOCD 一直报找不到目标芯片,可以换用 STM32CubeProgrammer 的 GUI 模式连接一下试试。它出错的提示比 OpenOCD 更智能,能直接告诉你读保护状态、连接方式对不对,是很好的辅助排查工具。
4.4 AI 生成代码的坑:我用几次踩出来的血泪教训
AI 编程终究不是万能灵药,尤其在嵌入式这种硬件耦合很强的领域。我总结过几个高频坑:
第一,AI 容易混用库版本。你让它生成 STM32F1 的代码,它记住了 HAL 库和标准库两套 API,有时输出一个GPIOB->BSRR = ...然后又接了一段HAL_Delay,混合时代码可能编译通过但行为异常。规避办法是在提示词里明确“只用 HAL,不要混用标准库”,生成后检查有没有裸操作寄存器的代码。
第二,AI 经常不知道你的 CubeMX 已经初始化了哪些外设。如果 CubeMX 里已经初始化了 LED 引脚,AI 又在自己的代码里重复调用HAL_GPIO_Init,它会覆盖之前的配置。这属于“上下文缺失”问题,所以提示词里最好写明“GPIO 由 CubeMX 初始化,不要重复初始化,只添加业务逻辑”。
第三,AI 写的延时阻塞在裸机工程里没问题,但一旦到了带实时系统的工程,它的 HAL_Delay 策略就会出问题。让它生成串口接收时,它默认给一个大循环轮询,如果在 RTOS 环境里这就是灾难。所以我在提示词里如果涉及中断或 DMA,都会明确“不要在阻塞代码里等待数据”,否则它默认走轮询路线。
我现在的习惯是:AI 生成的代码默认不信任,先过一遍编译,再过一遍代码逻辑,最后上板验证。就算这样,它依然帮我节省了大量搭基础框架的时间。把 AI 当实习生用,初稿它来,审核我管,这才是比较健康的关系。
最后再分享一个我这几个月一直在用的安排:VS Code 里左侧是代码,右侧常驻一个 Continue 对话窗口。遇到不熟悉的芯片外设,把 HAL 驱动源码里对应的局部代码发给它,让它用一句话解释角色和注意事项。遇到大片重复的初始化逻辑,就选中代码让它“总结成函数并给出调用点”。这些动作虽然不起眼,但积少成多,每天省出来的时间够多,而且我对代码库的理解也一直在加深。对嵌入式工程师来说,这算是普通编辑器和 AI 辅助编辑器最本质的差别。