1. 为什么嵌入式C项目需要一套“会说话”的规则文件做嵌入式C开发的人大多有过这种体验接手一个跑了三四年的老项目打开某个驱动文件发现同一个工程里居然有三种命名风格——有人写HAL_GPIO_Init有人写hal_gpio_init还有人写HalGpioInit。更头疼的是硬件抽象层HAL的接口定义得随心所欲换个芯片平台上层业务代码几乎要重写一遍。这不是技术能力问题而是缺少一套被工具强制执行、被团队共同认可的规则。Cursor 这类 AI 编辑器进入嵌入式开发者的视野之后很多人第一反应是“用它写代码快”但真正拉开效率差距的是Cursor Rules这个机制。它允许你把团队的代码风格、HAL 分层约定、甚至芯片外设的初始化顺序写成一份机器可读的规则文件让 AI 在生成每一行代码之前就“知道规矩”。这比事后 code review 抓风格问题要高效得多也比写一份没人看的 Wiki 文档要实在。这篇文章面向的是有 C 语言基础、正在做嵌入式项目STM32、ESP32、GD32 等平台都适用、并且愿意把 AI 工具真正落地到工程实践中的开发者。我会从规则文件的设计思路讲起一直讲到完整的配置流程和实际踩过的坑。核心目标只有一个让你看完之后能直接在自己的项目里搭出一套可用的 Cursor Rules把代码风格和硬件抽象层的约束固化下来。需要提前说明的是Cursor 的规则机制本身并不复杂难的是“写什么规则”。规则写得太松AI 照样乱来写得太死AI 生成的代码又僵化得没法用。这个度怎么把握是后面几个章节要重点拆解的内容。2. Cursor Rules 在嵌入式C场景下的能力边界2.1 规则文件到底能约束什么Cursor Rules 本质上是一份放在项目目录下的 Markdown 文件通常叫.cursorrules或者放在.cursor/rules/目录下它会在 AI 生成代码时作为系统提示的一部分被注入。这意味着你写的每一条规则都会直接影响 AI 的输出倾向。在嵌入式 C 项目里它能约束的东西比你想的多命名规范函数前缀、变量命名法蛇形还是驼峰、宏定义全大写加前缀、类型定义用_t后缀等。文件组织头文件放哪里、源文件放哪里、HAL 层和驱动层怎么分目录。接口约定HAL 层对外暴露的函数必须返回统一错误码、初始化函数必须成对出现init/deinit、中断服务函数命名规则。代码结构禁止在头文件里定义变量、禁止使用动态内存分配、寄存器操作必须用位带或者宏封装。注释风格函数头注释模板、文件头版权声明、关键寄存器的配置说明。但要注意规则文件不是编译器它不能“强制”任何东西。它的作用是在 AI 生成代码的那一刻施加影响。如果你自己手写代码规则文件管不着你。所以它的定位是让 AI 成为团队里最守规矩的那个成员。2.2 它解决不了的问题别指望它有些开发者对 Cursor Rules 抱有不切实际的期待觉得写一份规则文件就能让 AI 自动搞定整个 HAL 层。实际用下来以下几个事情它做不好第一跨文件的架构一致性。AI 在生成单个文件时能看到规则但它对整个项目的全局状态感知有限。比如你要求“所有外设初始化必须在bsp_init.c里统一调用”AI 在写某个具体驱动时可能会忘记这条因为它看不到bsp_init.c的当前内容。第二硬件相关的时序约束。规则文件可以写“SPI 初始化必须先配置时钟再配置引脚”但具体的时钟分频系数、引脚复用编号这些必须靠你在规则里给出明确的值或者让 AI 去查参考手册——而 AI 查手册的能力并不可靠。第三编译级别的正确性。规则能让代码风格统一但不能保证volatile加对了地方、不能保证中断优先级配置正确。这些还是得靠人。所以我的建议是规则文件管“怎么写”人管“写什么”和“对不对”。把风格和接口约定交给规则把硬件逻辑和时序验证留给自己。2.3 和传统代码检查工具的分工有人会问我已经有clang-format和cppcheck了为什么还要 Cursor Rules这两者的工作时机完全不同。clang-format是在代码写完之后格式化cppcheck是在代码写完之后做静态分析。而 Cursor Rules 是在代码还没生成出来的时候就施加影响。换句话说前者是“事后纠错”后者是“事前预防”。实际项目中两者是互补的。规则文件负责让 AI 生成的代码在风格上就八九不离十clang-format做最后的格式统一cppcheck兜底逻辑问题。我通常会在规则文件里直接写明“生成的代码必须能通过项目根目录下的.clang-format配置”这样 AI 会主动对齐格式。3. 规则文件的分层设计从代码风格到硬件抽象3.1 第一层全局代码风格规则这一层是最基础的也是收益最直接的。我习惯把规则文件分成几个区块第一个区块就是全局风格。# 全局代码风格规则 ## 命名规范 - 函数名小写字母 下划线模块前缀 动作如 hal_gpio_init、drv_uart_send - 变量名小写字母 下划线如 rx_buffer、tx_len - 宏定义全大写 下划线模块前缀如 HAL_GPIO_PIN_MAX、DRV_UART_BAUD_115200 - 类型定义小写 _t 后缀如 hal_gpio_cfg_t、drv_uart_handle_t - 枚举typedef enum 全大写成员如 HAL_OK、HAL_ERROR ## 文件结构 - 每个 .c 文件必须包含对应的 .h 文件 - 头文件使用 #ifndef / #define / #endif 防止重复包含 - 头文件中只放声明不放定义extern 变量除外 - 源文件顺序包含头文件 → 宏定义 → 静态变量 → 静态函数声明 → 公开函数实现 → 静态函数实现 ## 注释规范 - 每个函数必须有函数头注释包含功能描述、参数说明、返回值说明 - 文件头必须包含文件名、作者、日期、版本、简要说明 - 关键寄存器操作必须有行内注释说明配置意图这份规则看起来简单但实际用起来效果很明显。以前 AI 生成代码时函数名可能是init_gpio、GPIOInit、gpio_init随机切换现在基本稳定在hal_gpio_init这种格式上。有一个细节值得注意规则里给的示例要足够具体。如果你只写“函数名用小写加下划线”AI 可能生成initgpio这种没有分隔的写法。给出hal_gpio_init这样的完整示例AI 的模仿准确率会高很多。3.2 第二层硬件抽象层的接口约定这一层是嵌入式项目的核心。HAL 层设计得好不好直接决定了换芯片时上层代码要改多少。我在规则文件里会明确约定 HAL 层的几个关键设计原则。# 硬件抽象层HAL规则 ## 分层原则 - HAL 层只依赖芯片厂商的底层库如 STM32 HAL、ESP-IDF不依赖任何业务逻辑 - 驱动层DRV调用 HAL 层业务层APP调用驱动层 - 禁止跨层调用APP 不能直接调 HALDRV 不能直接操作寄存器 ## 接口规范 - 所有 HAL 函数返回 hal_status_t 类型枚举HAL_OK / HAL_ERROR / HAL_BUSY / HAL_TIMEOUT - 初始化函数命名hal_外设_init反初始化hal_外设_deinit - 配置结构体命名hal_外设_cfg_t通过指针传入初始化函数 - 读写函数命名hal_外设_read / hal_外设_write ## 错误处理 - 所有可能失败的 HAL 函数必须返回错误码禁止用 void 返回值 - 调用 HAL 函数后必须检查返回值禁止忽略 - 错误码定义在 hal_status.h 中禁止在业务代码里自定义错误码 ## 中断处理 - 中断服务函数命名hal_外设_irq_handler - 中断服务函数中禁止调用阻塞函数 - 中断与主循环的数据传递使用环形缓冲区或标志位这套约定写进规则文件之后AI 生成的 HAL 代码质量提升非常明显。以前它可能会写出void uart_init()这种没有错误返回的函数现在会主动返回hal_status_t并且在调用底层库之后检查返回值。这里有个经验规则文件里要明确写出“禁止”什么。AI 对“禁止”类指令的遵守程度比“建议”类高。比如你写“建议使用错误码”AI 可能有时候忘记但你写“禁止用 void 返回值”它基本不会违反。3.3 第三层芯片平台相关的约束如果你的项目锁定在某个特定平台规则文件里可以加入平台相关的约束。比如用 STM32 的项目# STM32 平台特定规则 ## 外设初始化 - 使用 STM32 HAL 库禁止直接操作寄存器除非 HAL 库不支持 - GPIO 初始化必须配置引脚、模式、上拉/下拉、速度 - 时钟使能必须在 GPIO 初始化之前完成 - 中断优先级分组统一使用 NVIC_PRIORITYGROUP_4 ## 内存管理 - 禁止使用 malloc / free所有内存静态分配 - 栈大小在启动文件中配置禁止在运行时调整 - 大数组必须加 static 或放在全局区避免栈溢出 ## 低功耗 - 进入低功耗前必须关闭未使用的外设时钟 - 唤醒源必须明确配置这些规则看起来琐碎但每一条都是实际项目中踩过坑总结出来的。比如“时钟使能必须在 GPIO 初始化之前”这是 STM32 开发中最常见的错误之一AI 如果不被告知生成的代码顺序可能是反的。3.4 规则文件的组织方式Cursor 支持两种规则文件组织方式单一.cursorrules文件或者.cursor/rules/目录下的多个文件。我的建议是项目初期用单一文件简单直接。项目变大之后拆成多个文件按主题分style.md、hal.md、platform.md、testing.md。每个文件开头写清楚适用范围比如“本规则适用于src/hal/目录下的所有文件”。拆分的另一个好处是不同模块可以有不同的规则。比如 HAL 层要求严格错误处理而测试代码可以宽松一些。4. 完整配置流程从零搭起一套可用的规则体系4.1 环境准备与 Cursor 基础配置先确认你的 Cursor 版本。规则文件功能在较新的版本中才完善建议更新到最新版。安装过程不复杂官网下载对应平台的安装包一路下一步即可。首次启动时会引导你选择主题、快捷键方案这些按个人习惯来就行。如果你习惯中文界面可以在设置里搜索 “language”把显示语言切换为简体中文。不过我的建议是保持英文界面因为很多技术术语翻译过来反而不好搜索而且规则文件本身用英文写兼容性更好——虽然 Cursor 对中文规则的支持没问题但中英混写时偶尔会出现解析歧义。接下来是项目准备。假设你有一个嵌入式 C 项目目录结构大概是这样my-embedded-project/ ├── src/ │ ├── hal/ │ ├── drv/ │ └── app/ ├── inc/ │ ├── hal/ │ ├── drv/ │ └── app/ ├── tests/ ├── .clang-format └── Makefile在项目根目录下创建.cursorrules文件。如果你用的是.cursor/rules/目录方式就创建.cursor/rules/目录里面放多个.md文件。4.2 规则文件的编写顺序与验证方法写规则文件不要一次写完那样很难验证哪条规则有效、哪条没效果。我的做法是分批写、分批验证。第一批只写命名规范和文件结构。写完之后让 AI 生成一个简单的 GPIO 驱动文件看它是否遵守了命名规则。如果发现它还在用驼峰命名说明规则描述不够明确需要加示例。第二批加 HAL 接口约定。让 AI 生成一个 UART 初始化函数检查返回类型、参数结构体、错误处理是否符合预期。第三批加平台特定规则。让 AI 生成一个完整的 SPI 初始化流程检查时钟使能顺序、引脚配置、中断优先级。每批验证通过之后再写下一批。这样做的原因是规则文件越长AI 对每条规则的注意力越分散。分批验证能确保每条规则都真正生效。验证的时候有个技巧用相同的提示词对比加规则前后的生成结果。比如提示词都是“写一个 STM32 的 UART 初始化函数”加规则前 AI 可能生成void UART_Init()加规则后应该生成hal_status_t hal_uart_init(const hal_uart_cfg_t *cfg)。对比一目了然。4.3 让 AI 自己检查规则遵守情况Cursor 有一个很实用的功能你可以在对话中让 AI 检查当前代码是否符合规则文件。比如选中一段代码输入“检查这段代码是否符合项目规则”AI 会对照.cursorrules逐条检查并指出问题。这个功能在 code review 阶段特别好用。我通常会在提交代码前让 AI 过一遍改动看有没有违反规则的地方。虽然不能完全替代人工 review但能抓出大部分风格和接口问题。还有一个进阶用法把规则文件的内容作为提示词的一部分让 AI 生成一个“规则检查清单”然后你拿着这个清单去 review 别人的代码。这样即使不用 Cursor规则也能发挥作用。4.4 与版本控制系统的配合规则文件应该提交到 Git 仓库和代码一起管理。这样团队里每个人用的都是同一套规则。如果有新人加入拉下代码就自动获得了规则约束不需要额外培训。但要注意一点规则文件的修改要经过团队讨论。我见过一个项目某个人在规则文件里加了一条“所有函数必须加static除非明确需要外部链接”结果 AI 生成的代码大量使用static导致单元测试没法链接这些函数。规则文件的影响面很大改之前最好在团队里同步一下。另外可以在 CI 流程里加一步检查.cursorrules文件是否存在、是否被意外删除。虽然听起来有点过度但实际项目中确实发生过规则文件被误删、AI 生成代码质量突然下降的情况。5. 实测中遇到的坑与应对策略5.1 规则冲突导致的“精神分裂”最常见的问题是规则之间互相矛盾。比如你在风格规则里写“函数名用小写加下划线”又在 HAL 规则里写“初始化函数用Init后缀”AI 就会困惑到底是hal_uart_init还是hal_uart_Init这种冲突在规则文件变长之后特别容易出现。我的应对方法是在规则文件开头写一个“优先级声明”明确哪类规则优先。比如# 规则优先级 1. 平台特定规则 HAL 规则 全局风格规则 2. 当规则冲突时以更具体的规则为准 3. 禁止类规则优先于建议类规则有了这个声明AI 在遇到冲突时会有明确的取舍依据。实测下来规则冲突导致的生成异常能减少七八成。5.2 AI “假装遵守”规则的情况有时候 AI 生成的代码看起来符合规则但仔细一看是“表面功夫”。比如规则要求“所有 HAL 函数返回错误码”AI 确实返回了hal_status_t但函数体里永远返回HAL_OK根本不检查底层库的返回值。这种情况靠规则文件本身很难完全避免因为 AI 只是在模仿格式没有真正理解意图。我的做法是在规则里加一条“错误处理必须包含对底层库返回值的检查禁止无条件返回HAL_OK”。同时在验证阶段专门测试错误路径——比如让 AI 生成一个“当底层库返回错误时如何处理”的代码片段看它是否真的做了检查。5.3 规则文件过长导致的“遗忘”规则文件超过一定长度之后AI 对前面内容的记忆会衰减。我实测下来超过 500 行的规则文件AI 对开头部分的遵守率会明显下降。解决办法有两个一是拆分规则文件按目录或模块分开AI 在处理某个文件时只加载相关的规则二是把最重要的规则放在最前面并且用加粗、列表等方式突出显示。还有一个技巧在规则文件里用“必须”“禁止”“始终”这类强指令词比“建议”“可以”“尽量”的遵守率高很多。这不是玄学而是 AI 对指令性语言的敏感度确实更高。5.4 不同 AI 模型对规则的遵守差异Cursor 支持切换不同的底层模型。实测下来不同模型对规则文件的遵守程度差异很大。有的模型对格式类规则遵守得很好但对逻辑类规则如错误处理容易忽略有的模型则相反。我的建议是选定一个模型之后针对它调优规则文件。不要频繁切换模型否则规则文件的效果会不稳定。如果必须切换切换后重新跑一遍验证流程看哪些规则需要调整措辞。6. 从规则到习惯让约束真正落地规则文件写好了配置流程跑通了但真正的挑战才刚刚开始怎么让团队里每个人都用起来。我的经验是不要一上来就推全套规则。先挑一条最痛的问题——比如命名混乱——写一条规则让 AI 生成代码时统一命名。等大家感受到“AI 生成的代码不用改命名了”这个好处之后再逐步加规则。每次加规则都对应一个实际痛点这样推行阻力最小。另外规则文件不是写完就完了。项目在演进芯片平台可能换团队习惯可能变规则也要跟着更新。我通常每个季度 review 一次规则文件把过时的删掉把新踩的坑加进去。这个过程本身就是团队技术积累的一部分。最后分享一个我自己的习惯每次 AI 生成的代码违反了规则我不会只改代码而是会想“规则文件里是不是缺了这条”。如果是就补进去。这样规则文件会越来越完善AI 的表现也会越来越稳定。说到底规则文件不是给 AI 看的是给团队看的——它把那些“只可意会”的经验变成了“可以言传”的条款。
