AI辅助嵌入式开发工作流 — 小白实操手册 v2.0

定位:用AI辅助开发,但不走弯路、不留技术债。
核心理念:AI是杠杆,你的工程思维才是地基。
作者:资深嵌入式工程师 + 上下文构建工程师


写给小白的一番话

你以为我要教你"用AI写代码"?不。

我要教你用AI构建一个能活很多年的嵌入式项目

市面上的教程教的是:把需求丢给AI,复制代码,烧录,成功。——然后三个月后代码没法改、没法扩展、别人看不懂。这不是你的错,是那些教程的错。

我会做的:每教一个模块开发,就教你如何把这个模块的架构设计、接口约定、约束条件写成上下文,让AI在后续开发中始终理解"这个模块在哪里、边界是什么、为什么会这样设计"。这样当你需要改需求、加功能、修复Bug时,你的AI上下文就是一份活的项目蓝图,而不是每次新建Session从头解释。

现代LLM的上下文窗口已经可以到100万Token。但这不意味着你要把所有东西都塞进去——塞得多不如塞得对。

手册结构(建议学习顺序)

第一章:前置准备(工具+认知)
第二章:好上下文 vs 差上下文(具体例子)
第三章:架构设计优先(先画蓝图再动手)
第四章:模块化开发 + 上下文分层结构
第五章:避免上下文污染与项目README模板
第六章:提示词模板库
第七章:5个实战项目(手把手带做)
第八章:调试与排错
第九章:附录

第一章:前置准备

1.1 你的工具箱

电脑一台(Windows/Mac/Linux随意)

AI助手(主力推荐Claude,因为它的代码能力和上下文理解是目前最强的)

开发板

  • STM32F103C8T6最小系统板(约50元)+ ST-Link(约20元)
  • 入门资料最全,社区最大

开发软件

  • Keil MDK(STM32开发,30天试用)
  • VSCode + PlatformIO(ESP32或想更快入门)
  • Arduino IDE(最简单,但不适合深学)

1.2 你必须建立的认知

认知1:AI是杠杆,不是替代者

AI能帮你写代码,但不能替你理解需求、设计架构、评估风险。一个嵌入式项目的质量,90%取决于你在AI介入前做了多少架构思考。

认知2:上下文是有结构的,不是越多越好

把一整本芯片手册丢给AI,不如告诉AI:当前在调哪个模块、这个模块的输入输出是什么、上次为什么这样设计。

认知3:项目要分Session管理,不是都在一个对话里

混乱的Session管理会导致:AI不知道你之前做了什么设计决策、重复解释项目背景浪费Token、上下文污染导致AI给出矛盾的代码。


第二章:好上下文 vs 差上下文

2.1 什么是"上下文"

上下文 = AI在当前对话中能看到的所有信息。包括:System Prompt、对话历史、你粘贴的代码片段、你描述的项目背景。

上下文构建:有意识地组织这些信息,让AI每次回复都基于准确、完整、无歧义的项目理解。

2.2 好上下文 vs 差上下文 — 两个真实对比

差上下文示例:

“我之前写了一个串口通信的代码,现在想让LED根据收到的命令闪烁,帮我改一下。”

AI不知道你用的是什么芯片、什么通信协议、LED在哪个引脚——这样的请求,AI只能猜测,大概率会出错。

好上下文示例:

【项目背景】
芯片:STM32F103C8T6
项目类型:工业传感器数据采集
当前阶段:已完成串口DMA接收,正在扩展LED状态指示功能

【已完成模块】
1. usart_dma.c/h - USART1 DMA接收,PB10=USART1_RX,波特率115200
2. ringbuffer.c/h - 环形缓冲区,深度256字节
3. 协议格式:自定义帧,帧头0xAA 0x55,数据长度+数据+CRC16

【本次需求】
新增led_cmd_handler.c/h:
- 收到字节0x01 → LED1亮(PA5输出高)
- 收到字节0x00 → LED1灭(PA5输出低)
- LED初始化已写在gpio_ctrl.c中,函数为LED1_Init()和LED1_SetState(uint8_t on)

【约束】
- 不能破坏现有DMA接收逻辑
- 新增代码必须放在单独的.c文件
- 遵循已有编码规范:匈牙利命名法,Tab缩进4字符

两者核心差距:好上下文包含了芯片型号、当前阶段、已完成模块的接口、具体引脚、协议格式——这些都是AI"猜不到"的信息。


第三章:架构设计优先

这是小白最容易跳过的步骤,也是未来技术债的根源。

3.1 什么是嵌入式软件架构

架构 = 这个项目分成哪几个模块,每个模块负责什么,模块之间怎么通信。

没有架构的项目:代码都堆在main.c里,GPIO配置分散在十几个地方,想改一个功能要改二十个文件。

有架构的项目:模块划分清晰,一个模块出问题不影响其他模块,每个模块有明确的输入输出接口,AI可以针对特定模块开发。

3.2 嵌入式分层架构(推荐给所有新手)

┌─────────────────────────────────────┐
│     应用层(Application)            │  ← main.c + 业务逻辑
├─────────────────────────────────────┤
│     驱动抽象层(HAL Abstraction)   │  ← 硬件无关的驱动接口
├─────────────────────────────────────┤
│     硬件驱动层(Peripheral Drivers)│  ← GPIO/USART/DMA/TIM
├─────────────────────────────────────┤
│     硬件层(Hardware)              │  ← 芯片寄存器配置
└─────────────────────────────────────┘

应用层:写业务逻辑。比如"收到温度>30°C就亮红灯"。

驱动抽象层:定义统一的驱动接口,不直接操作硬件。比如TemperatureSensor_Init()TemperatureSensor_Read()

硬件驱动层:具体实现。比如基于DS18B20的实现。

这样做的好处:当你的硬件换了(比如从STM32换成ESP32),只需要重写驱动层,应用层代码不用动。

3.3 用AI辅助架构设计

我是一个嵌入式工程师,正在设计一个[项目名称]项目。

项目需求:[描述要实现的功能]
芯片型号:[芯片型号]
存储限制:Flash [X]KB,RAM [X]KB

请帮我完成以下架构设计:
1. 推荐分层架构方案
2. 推荐模块划分(每个模块的名字、职责、接口)
3. 模块之间的依赖关系
4. 建议先开发哪个模块
5. 给出每个模块的头文件接口声明(.h)

请用工程化的语言描述,不要只给概念。

3.4 如何让AI生成项目蓝图

核心理念:先有Prompt,再有蓝图。 不要让AI直接生成随意的架构文档——你要先通过Prompt框定需求,AI在约束下做设计。

第一步:给AI发这个Prompt
我是一个嵌入式软件工程师,需要你帮我为以下项目生成一份完整的架构设计文档(项目蓝图)。

## 项目基本信息
- 项目名称:[项目名称]
- 芯片型号:[型号,例如 STM32F103C8T6]
- 资源约束:Flash ≤ [X]KB,RAM ≤ [X]KB
- 开发板/外设:[具体型号或连接描述]

## 核心功能需求
1. [功能1]
2. [功能2]
3. [功能3]

## 已有约束
- 已有代码/模块:[描述已完成的模块]
- 编码规范:[命名风格,例如匈牙利命名法,Tab缩进4字符]
- 禁止事项:[例如禁止在中断里使用浮点运算]

## 你的任务
请生成一份完整的架构设计文档,必须包含以下章节:
1. 项目概述(一句话描述 + 资源约束确认)
2. 架构概览(分层架构说明)
3. 模块清单(每个模块:名称、职责、对外接口、依赖模块、状态=未开发)
4. 模块依赖关系(用文字描述)
5. 推荐开发顺序
6. 已知约束与风险

输出时注意:
- 每个模块的头文件接口用C语言声明(.h格式)
- 不要写任何.c实现代码,只需要接口声明
- 模块数量控制在5-8个
第二步:AI会生成这样的蓝图文档
# [项目名称] 架构设计文档
版本:v1.0 | 日期:[日期] | 状态:进行中

## 1. 项目概述
- 功能:[一句话描述]
- 目标芯片:[型号]
- 资源约束:Flash ≤ [X]KB, RAM ≤ [X]KB

## 2. 架构概览
[用文字描述分层结构和模块划分]

## 3. 模块清单

### 3.1 [模块A]
- 职责:...
- 对外接口:
  - `void ModuleA_Init(void);`
  - `uint8_t ModuleA_DoSomething(uint8_t input);`
- 依赖模块:ModuleB
- 状态:[未开发/开发中/已完成/已测试]

## 4. 模块依赖图
[文字描述依赖关系]

## 5. 开发顺序
1. ModuleA(无依赖,优先开发)
2. ModuleB(依赖ModuleA)

## 6. 已知约束
- [约束1]
- [约束2]

这份文档就是你的项目蓝图,也是你每次开新Session时的上下文起点。

第三步:生成后的处理

AI生成的蓝图通常还需要你做两件事:

1. 验证接口设计是否合理

重点检查:

  • 模块之间的接口是否对等(A调用B,B是否也需要回调A?)
  • 是否有循环依赖(A→B→C→A)
  • 接口参数是否合理(数据结构有没有越界风险)

2. 根据你的实际情况手动调整

AI不知道你的芯片引脚占用、已有代码的具体接口,需要你手动修正。

3. 保存到项目目录

将最终版本保存为docs/architecture.md(或README.md),作为项目的基准蓝图。


第四章:模块化开发 + 上下文分层结构

核心理念:一个模块一个模块开发,每个模块都有一套完整的上下文。

4.1 模块开发7步法

第1步:把项目蓝图发给AI
第2步:告诉AI本次要开发的模块
第3步:让AI生成模块代码框架(.h + .c)
第4步:Review框架,确认接口设计合理
第5步:让AI逐个实现接口函数
第6步:编译测试
第7步:如果测试通过,更新项目蓝图(标记模块状态)

关键在Step 4:接口设计不合理时,不要急着让AI写代码。先让AI解释它为什么这样设计,你再判断是否需要调整。这是避免返工的最重要一步。

4.2 模块开发上下文模板

每次开发新模块时,给AI的上下文应该包含:

【项目蓝图摘要】
[粘贴项目架构文档中的相关部分]

【本次开发模块】
模块名:[模块名]
所在层级:[应用层/驱动抽象层/硬件驱动层]
对外接口:
- [接口1]
- [接口2]

【已有实现】
[列出已完成的相关模块和它们的接口]

【本次任务】
[描述要实现的功能]

【约束】
- [资源约束:例如中断里不能使用浮点运算]
- [接口约束:必须兼容已有XX接口]
- [编码规范:命名/缩进/注释风格]

【验收标准】
[用可测试的语言描述模块应该达到什么效果]

4.3 上下文分层结构 — 理论

推荐用三层结构组织项目上下文:

第一层:项目蓝图(Project Blueprint)

整个项目的架构概述,模块列表,模块间关系图。这部分内容固定,不需要每次Session重复输入。

第二层:模块上下文(Module Context)

当前正在开发的模块的详情:接口、约束、已知问题。每次只放1-2个模块。

第三层:即时任务(Task Context)

本次要解决的具体问题、错误信息、你尝试过的方法。只在当前Task的Session里使用。

4.4 三层结构的判断标准

这是最常见的困惑:“我的这条信息应该放蓝图、模块上下文、还是即时任务?”

判断方法:看这个信息"会不会变"

信息类型 放哪层 判断依据
模块清单、分层架构、模块依赖关系 项目蓝图 整个项目周期内基本不变
当前开发模块的接口、约束、硬件配置 模块上下文 在这个模块开发期间不变
本次要解决的具体Bug、错误信息、尝试过的方法 即时任务 每次任务都不同

常见错误:把即时任务的信息放进了蓝图

错误示例(把"这次要修DMA错位"写进了项目蓝图)→ 下次开新Session,蓝图里还带着旧Bug信息,AI会被误导。

正确做法:Bug信息只放在当前Session的即时任务里,修复完成后删除,蓝图保持干净。

4.5 Session管理核心原则

应该开新Session的场景:

  • 切换到另一个模块开发
  • Session里的上下文已经积累得很长,需要压缩
  • 第二天继续开发

应该保持在同一个Session的场景:

  • 同一个模块的迭代开发
  • 正在调试一个Bug,需要看错误历史
  • 还在处理同一个功能

开新Session前必做的总结Prompt:

请总结当前项目状态,包括:
1. 已完成模块列表 + 每个模块的对外接口
2. 正在进行的工作
3. 已知的未解决问题
4. 下一步计划

用结构化的格式输出,方便下次粘贴到新Session。

4.6 Token节省技巧

技巧1:删除无用的对话历史

在长Session中,定期让AI总结并删除旧的中间讨论:

从现在起,请假设我们之前的所有调试讨论已经完成。
基于以下项目状态继续工作:
[粘贴项目状态总结]

技巧2:不要重复粘贴相同代码

如果AI之前已经看过某段代码,你只需要说"参考之前在usart_dma.c第30-50行的实现"而不是把代码再贴一遍。

技巧3:明确任务边界

差:"帮我看看这个代码有什么问题"

好:"第15-30行的函数返回值有溢出风险,请检查并修复"

技巧4:用标记分隔新旧上下文

开新Session时,在粘贴项目总结后加一句:

以上是项目历史状态。现在我要开始一个新任务:
[新任务描述]

4.7 何时同上下文,何时开新Session

可以同上下文持续交互的场景

1. 同一模块的迭代开发

LED模块:从初始化 → 加熄灭函数 → 加呼吸灯效果,每次都在同一个Session里,AI知道LED模块的接口和已有的代码。

2. 调试Bug的过程

AI发现了竞态条件 → 你问怎么改 → 改了之后还是丢数据 → AI分析双缓冲方案。调试需要看历史,AI需要知道之前的修复尝试。

3. 需求渐进式明确

先说"我想做温度监测" → AI给出架构建议 → 加OLED显示 → 再加阈值报警。需求还没想清楚时,在同一Session逐步完善。

4. 架构Review和讨论

AI提出分层建议 → 你追问"不想改已有的usart模块怎么办" → AI给出最小侵入方案。讨论需要完整上下文。

5. 验证代码是否正确

AI测试了5种边界情况,发现2处问题 → 逐一修复 → AI逐一验证。AI需要记住它发现了什么问题,才能逐一确认修复结果。

必须开新Session的场景

1. 完全切换到新模块开发

之前在做usart_dma模块,现在要开始温度传感器模块 → 新开Session。

2. 上下文已经积累过长

判断标准:AI回复开始变慢,或开始"忘记"之前的一些细节。先在旧Session让AI做总结,开新Session粘贴总结继续。

3. 第二天继续开发

前一晚完成了LED模块和按键模块,第二天继续做串口通信。开新Session,粘贴项目状态总结。

4. 发现了之前的设计错误,需要重新审视

之前按照设计做了usart_dma和ringbuffer,现在发现缓冲区大小设计不合理。开新Session,直接说"之前的设计有问题"。

5. 换了AI模型

从Claude换到Kimi,或者换到DeepSeek → 新模型没有旧上下文。

6. 遇到了死胡同,需要换思路

尝试了3种方案都不行,陷入僵局 → 开新Session,跳出旧框架。

判断流程图
你现在有一个新任务/问题
      │
      ├─ 是否需要理解之前Session的内容?
      │    │
      │    ├─ 否 → 开新Session
      │    │
      │    └─ 是 → 旧Session的上下文是否已经很长?
      │              │
      │              ├─ 否(<20轮)→ 继续用旧Session
      │              │
      │              └─ 是 → 先在旧Session里做总结
      │                       开新Session粘贴总结
完整示例:旧Session总结 → 新Session接续

背景:你在做usart_dma模块,进行了2天的调试,现在需要开新Session继续。

Step 1 — 旧Session里,让AI总结项目状态:

请用以下格式总结当前项目状态:
【进行中】…
【未解决问题】…
【下一步】…

AI回复:

【项目名称】:工业传感器数据采集
【最后更新】:2026-03-28
【芯片】:STM32F103C8T6

## 已完成模块

| 模块名 | 层级 | 状态 | 关键接口 |
|--------|------|------|---------|
| gpio_ctrl | 硬件驱动层 | ✅完成 | `LED1_Init(); LED1_SetState(uint8_t on);` |
| usart.c/h | 硬件驱动层 | ✅完成 | `USART1_Init(void);` |
| ringbuffer.c/h | 驱动抽象层 | ✅完成 | `RB_Init(RingBuffer_t *rb); RB_Put(RingBuffer_t *rb, uint8_t byte);` |

## 进行中
正在调试usart_dma.c的接收中断,问题是DMA接收到的数据与串口助手发送的数据相比有错位。

## 未解决问题
1. DMA数据错位:发送0xAA 0x55 0x02 0x01 0x02,收到0xAA 0x55 0x02 0x00 0x02,怀疑是DMA半传输中断和主循环同时读缓冲区导致竞态条件

## 下一步
实现双缓冲方案解决竞态条件,然后继续做protocol.c的帧解析

Step 2 — 复制AI的回复,保存到项目目录session_summary_20260328.md

Step 3 — 开新Session,粘贴以下内容作为开头:

【项目名称】:工业传感器数据采集
【本次任务】:解决DMA接收数据错位问题,实现双缓冲方案

## 项目历史状态(2026-03-28旧Session结束时保存)

【已完成模块】
| 模块名 | 层级 | 状态 | 关键接口 |
|--------|------|------|---------|
| gpio_ctrl | 硬件驱动层 | ✅完成 | `LED1_Init(); LED1_SetState(uint8_t on);` |
| usart.c/h | 硬件驱动层 | ✅完成 | `USART1_Init(void);` |
| ringbuffer.c/h | 驱动抽象层 | ✅完成 | `RB_Init(); RB_Put(); RB_Get();` |

【进行中】
正在调试usart_dma.c的接收中断,DMA数据错位问题

【未解决问题】
1. DMA数据错位:发送0xAA 0x55 0x02 0x01 0x02,收到0xAA 0x55 0x02 0x00 0x02
2. 怀疑原因:半传输中断和主循环同时读缓冲区,竞态条件

【下一步】
实现双缓冲方案

## 本次任务详细说明

请帮我设计双缓冲方案,要求:
1. 禁止在中断和主循环同时操作同一缓冲区
2. 使用两个128字节缓冲区交替工作
3. 给出具体的.h/.c实现

效果:新Session的AI可以立即知道项目历史、当前问题、下一步,不需要重新解释项目背景。

4.8 用Git管理项目版本(选读)

如果你只用AI对话管理项目,Session结束后的代码历史也随之消失。Git可以把这部分记忆持久化。AI Session管理与Git版本管理是互补的:Session管理AI对话上下文,Git管理代码文件历史。

Git与本文工作流的对应关系
维度 Session管理 Git管理
管理的对象 AI对话上下文 代码文件
时间范围 单次开发会话 项目全生命周期
记录的内容 决策、问题、上下文 代码变更、版本快照
用途 AI回复质量 代码回溯、协作
最小Git入门(5个命令解决80%场景)
# 1. 初始化项目仓库
git init

# 2. 查看当前变更
git status
git diff

# 3. 提交变更(每次完成一个小功能就提交)
git add .
git commit -m "描述这次改了什么"

# 4. 查看提交历史(这就是你的开发日志)
git log --oneline

# 5. 创建实验分支(想尝试新方案时,不影响主线)
git checkout -b experiment/dma-fix
# 测试完成后合并回主线
git checkout main
git merge experiment/dma-fix
commit message写什么
场景 commit message示例
完成一个模块 feat: 完成gpio_ctrl模块
修复一个Bug fix: 修复DMA接收数据错位问题
更新文档 docs: 更新README模块状态
重构代码 refactor: 简化protocol_parse状态机

commit message不要写:

  • "update" —— 改了啥?
  • "fix bug" —— 哪个bug?
模块开发7步法 + Git版本控制

把Git commit融入模块开发流程,每一步都有对应的版本控制动作:

第1步:把项目蓝图发给AI
       → git commit: "docs: 初始项目蓝图"
第2步:告诉AI本次要开发的模块
       → git checkout -b feature/gpio_ctrl
第3步:让AI生成模块代码框架
       → git commit: "feat: gpio_ctrl框架生成"
第4步:Review框架
       → git diff 检查变更范围
第5步:让AI逐个实现接口函数
       → 每个小功能完成即commit
第6步:编译测试
       → git commit: "feat: gpio_ctrl实现完成,编译通过"
第7步:更新项目蓝图
       → git commit: "docs: 更新README模块状态",然后git merge回main
Session总结 → commit message的对应

Session总结是最适合写成commit message的内容,两者是天然对应的:

Session总结:
【已完成模块】gpio_ctrl ✅
【进行中】usart_dma 🔧
【未解决问题】DMA数据错位
【下一步】实现双缓冲

对应的commit message:
git commit -m "feat: 完成gpio_ctrl模块

- gpio_ctrl模块开发完成
- 实现LED_Init/LED_On/LED_Off/LED_Toggle接口
- 依赖:无
- 下一步:usart_dma模块开发"

好处:三个月后翻git log,比翻Session历史快得多。Git commit历史就是你的项目开发日志。

完整工作流(AI + Git双保险)
每次开发任务
    │
    ├── Session管理(AI对话层面)
    │     ├── 开新Session前:粘贴上次Session总结
    │     └── 开发完成后:生成新的Session总结
    │
    └── Git管理(代码文件层面)
          ├── 每次小功能完成:git commit
          ├── Session结束:git commit -m "[Session总结]"
          └── 新Session开始:git log --oneline 回顾历史

第五章:避免上下文污染与项目README模板

提示:把项目状态总结保存到项目目录的README.md里,每次开新Session时粘贴使用。格式见本章5.2节。

5.1 避免上下文污染

上下文污染:AI在之前的对话中形成了某种理解,这个理解是错的或者已经过时了,但它仍然在影响后续回复。

处理方法: 当你在新session发现AI对项目有误解时,明确纠正:

[旧session中的理解可能有误,请以下面的为准]
项目使用的是STM32F103,不是STM32F401;
LED在PA5,不是PC13;
[纠正更多错误认知]

5.2 项目README模板

每个项目根目录都应该有一个README.md:

# [项目名称]

## 概述
[一行话描述项目做什么]

## 芯片与资源
- 芯片:[型号]
- Flash/RAM:[X]KB/[X]KB
- 开发板:[板子型号]

## 架构
[简述分层架构和模块划分]

## 模块状态

| 模块 | 状态 | 备注 |
|------|------|------|
| gpio_ctrl | ✅完成 | |
| usart_dma | ✅完成 | |
| led_cmd_handler | 🔧开发中 | 正在调试命令解析 |

## 最新项目状态([日期])
[粘贴最近的AI总结]

## 开发日志
- 2026-03-28:完成LED命令处理模块单元测试
- 2026-03-27:完成串口DMA接收模块

## 快速开始
[描述如何编译、烧录、运行]

第六章:提示词模板库

模板A:项目架构设计请求

请为以下嵌入式项目设计架构:

【基本信息】
- 项目名称:[名称]
- 芯片型号:[型号]
- 资源约束:Flash ≤ [X]KB,RAM ≤ [X]KB

【核心功能需求】
1. [功能1]
2. [功能2]
3. [功能3]

【已有约束】
- 已有硬件:[描述已连接的硬件]
- 已有代码:[描述已有的模块和接口]
- 编码规范:[命名风格/缩进规范等]

请输出:
1. 推荐的分层架构方案(必须包含应用层+驱动层)
2. 模块划分(模块名 + 职责 + 接口列表)
3. 模块依赖关系描述
4. 开发顺序建议
5. 每个模块的头文件模板(.h,不实现.c)

模板B:模块开发请求

请实现以下嵌入式模块:

【项目背景】
[粘贴项目架构文档的相关部分]

【本模块信息】
- 模块名:[模块名]
- 所属层级:[应用层/驱动抽象层/硬件驱动层]
- 依赖模块:[依赖的模块列表]

【功能需求】
[详细描述要实现的功能]

【接口定义】
[描述本模块需要提供的接口]

【约束条件】
1. [约束1]
2. [约束2]

【编码规范】
- 命名:[命名规范]
- 缩进:[缩进规范]
- 注释:[注释要求]

请生成完整的.h和.c文件,并说明每个函数的作用。

模板C:调试求助

【项目阶段】:[新项目开发/调试中/维护中]
【芯片/开发板】:[型号]
【开发环境】:[KEIL/IAR/Arduino等,含版本号]

【问题描述】
[一句话描述问题,如"LED不亮但没有报错"]

【出现的现象】
- 预期:[应该发生什么]
- 实际:[发生了什么]

【错误信息】(如有)
[粘贴完整错误信息]

【相关代码】
[只粘贴和问题相关的代码片段,不要贴整个文件]

【已尝试的排查】(如有)
1. [排查1]:[结果]
2. [排查2]:[结果]

请帮我:
1. 分析根本原因
2. 给出修复方案
3. 如何验证修复

模板D:项目状态总结(用于Session交接)

【项目名称】:[项目名称]
【最后更新】:[日期]
【芯片】:[型号]

## 已完成模块

| 模块名 | 层级 | 状态 | 关键接口 |
|--------|------|------|---------|
| [模块A] | 驱动层 | ✅完成 | `void ModA_Init(void);` |
| [模块B] | 应用层 | ✅完成 | `uint8_t ModB_Run(uint8_t input);` |

## 进行中
[描述当前正在开发的内容]

## 未解决问题
1. [问题1]:怀疑原因[原因],已尝试[方法]
2. [问题2]:...

## 下一步
[描述接下来的开发计划]

## 最新代码片段参考
[如果有问题需要AI关注,粘贴关键代码]

模板E:代码Review请求

请审查以下嵌入式代码,重点关注:
1. 潜在Bug(内存泄漏、越界、竞态条件)
2. 嵌入式特定风险(中断安全、功耗、资源使用)
3. 可维护性(命名、注释、结构)

[粘贴代码]

请按以下格式输出:
【高风险】:[问题描述] → [建议修复]
【中风险】:...
【低风险/建议】:...
【总体评价】:对这个模块质量的评估

模板F:Session初始化(开新Session时用)

【项目名称】:[名称]
【本次任务】:[你要做什么]

## 项目历史状态(上次Session结束时保存)
[粘贴上次Session总结的项目状态]

## 本次任务详细说明
[描述你要做的具体任务]

第七章:5个实战项目

每个项目都包含:架构设计 → 模块上下文 → 具体实现 → 验收测试

项目1:LED流水灯(架构入门)

完整实现示例:下面演示一个项目如何从头到尾走完7步法,AI给出了完整可编译代码。

Step 1-2 — 你发出架构设计请求:

请为STM32F103C8T6的LED流水灯项目设计架构。硬件:PA0=红灯,PA1=黄灯,PA2=绿灯(低电平点亮,即输出0亮灯)。要求:分层架构、延时用定时器实现、灯灭时保持低功耗。请给出模块划分、接口设计、头文件声明。

Step 3 — AI生成的架构设计:

模块划分:gpio_hw / led_driver / timer / waterlight_app

头文件声明:

// gpio_hw.h
#ifndef GPIO_HW_H
#define GPIO_HW_H
#include <stdint.h>
void GPIO_HW_Init(void);
#endif

// led_driver.h
#ifndef LED_DRIVER_H
#define LED_DRIVER_H
typedef enum { LED_RED, LED_YELLOW, LED_GREEN } LED_ID_t;
void LED_Driver_Init(void);
void LED_On(LED_ID_t id);
void LED_Off(LED_ID_t id);
void LED_Toggle(LED_ID_t id);
#endif

// timer.h
#ifndef TIMER_H
#define TIMER_H
void Timer_Init(void);
void Timer_DelayMs(uint32_t ms);
#endif

// waterlight_app.h
#ifndef WATERLIGHT_APP_H
#define WATERLIGHT_APP_H
void WaterLight_Run(void);
#endif

Step 4 — Review后确认接口合理,进入实现。

Step 5 — AI生成的完整可编译代码:

// gpio_hw.c
#include "gpio_hw.h"
#include "stm32f1xx_hal.h"
void GPIO_HW_Init(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2, GPIO_PIN_SET);
}

// led_driver.c
#include "led_driver.h"
#include "gpio_hw.h"
static const uint16_t led_pin[3] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2};
void LED_Driver_Init(void) { GPIO_HW_Init(); }
void LED_On(LED_ID_t id)  { HAL_GPIO_WritePin(GPIOA, led_pin[id], GPIO_PIN_RESET); }
void LED_Off(LED_ID_t id) { HAL_GPIO_WritePin(GPIOA, led_pin[id], GPIO_PIN_SET); }
void LED_Toggle(LED_ID_t id) { HAL_GPIO_TogglePin(GPIOA, led_pin[id]); }

// timer.c
#include "timer.h"
#include "stm32f1xx_hal.h"
void Timer_Init(void) { HAL_InitTick(TICK_INT_PRIORITY); }
void Timer_DelayMs(uint32_t ms) { HAL_Delay(ms); }

// waterlight_app.c
#include "waterlight_app.h"
#include "led_driver.h"
#include "timer.h"
void WaterLight_Run(void) {
    LED_ID_t sequence[3] = {LED_RED, LED_YELLOW, LED_GREEN};
    while (1) {
        for (int i = 0; i < 3; i++) {
            LED_On(sequence[i]);
            Timer_DelayMs(1000);
            LED_Off(sequence[i]);
        }
    }
}

// main.c
#include "main.h"
int main(void) {
    HAL_Init();
    SystemClock_Config();
    Timer_Init();
    LED_Driver_Init();
    WaterLight_Run();
}

Step 6 — 编译测试: KEIL MDK编译0 errors,0 warnings,烧录后三个灯按顺序各亮1秒。

Step 7 — 测试通过后,更新项目蓝图(README.md)


项目2:按键检测+状态机(模块化入门)

一句话:按键控制LED状态切换,短按亮/灭,长按进入闪烁模式

你发给AI的Prompt:

项目:按键控制LED状态切换。
硬件:PA5=LED(低电平点亮),PC13=按键(按下接地,低电平有效),上拉电阻。

状态机设计:
- STATE_IDLE:LED灭
- STATE_ON:LED亮
- STATE_BLINK:LED闪烁
- 按键短按(<500ms):切换STATE_IDLE ↔ STATE_ON
- 按键长按(≥500ms):切换到STATE_BLINK

要求:
- 状态机用switch-case实现
- 按键去抖用定时器,不用delay
- 每个状态对应一个处理函数

请设计模块划分和状态转移图,然后生成完整的.h/.c实现。

AI回复 — 头文件:

// key_driver.h
#ifndef KEY_DRIVER_H
#define KEY_DRIVER_H
#include <stdint.h>

typedef enum {
    KEY_STATE_IDLE,
    KEY_STATE_PRESSED,
    KEY_STATE_LONG_PRESSED
} Key_State_t;

void KeyDriver_Init(void);
Key_State_t KeyDriver_Scan(void);  // 返回按键状态
#endif

// led_fsm.h
#ifndef LED_FSM_H
#define LED_FSM_H
#include "key_driver.h"

typedef enum {
    STATE_IDLE,   // LED灭
    STATE_ON,     // LED亮
    STATE_BLINK   // LED闪烁
} App_State_t;

void LED_FSM_Init(void);
void LED_FSM_Run(Key_State_t key_event);  // 根据按键事件驱动状态机
#endif

AI回复 — 核心状态机实现:

// led_fsm.c
#include "led_fsm.h"
#include "led_driver.h"

static App_State_t current_state = STATE_IDLE;

void LED_FSM_Init(void) {
    current_state = STATE_IDLE;
    LED_Off(LED_RED);
}

void LED_FSM_Run(Key_State_t key_event) {
    App_State_t next_state = current_state;

    switch (current_state) {
        case STATE_IDLE:
            if (key_event == KEY_STATE_PRESSED) {
                next_state = STATE_ON;
            }
            break;

        case STATE_ON:
            if (key_event == KEY_STATE_PRESSED) {
                next_state = STATE_IDLE;
            } else if (key_event == KEY_STATE_LONG_PRESSED) {
                next_state = STATE_BLINK;
            }
            break;

        case STATE_BLINK:
            if (key_event == KEY_STATE_PRESSED) {
                next_state = STATE_IDLE;
            }
            break;
    }

    if (next_state != current_state) {
        current_state = next_state;
        switch (current_state) {
            case STATE_IDLE: LED_Off(LED_RED); break;
            case STATE_ON:   LED_On(LED_RED);  break;
            case STATE_BLINK: /* 闪烁由定时器中断控制 */ break;
        }
    }
}

项目3:串口+DMA+环形缓冲区(工程化核心)

这是嵌入式最重要的基础模块,建议完整掌握。

你发给AI的Prompt:

项目:STM32F103 USART1 DMA接收 + 环形缓冲区
硬件:PA9=USART1_TX,PA10=USART1_RX,波特率115200,数据位8,停止位1,无校验

自定义协议帧格式:
[0xAA][0x55][数据长度][数据...][CRC16高8位][CRC16低8位]

要求:
- 驱动层:usart.c/h,封装USART初始化和DMA配置
- 环形缓冲:ringbuffer.c/h,深度128字节,线程安全
- 应用层:protocol.c/h,实现帧解析和CRC校验

请设计完整的模块架构和头文件,然后实现环形缓冲区和协议解析的核心代码。

AI回复 — 头文件:

// ringbuffer.h
#ifndef RINGBUFFER_H
#define RINGBUFFER_H
#include <stdint.h>
#include <stdbool.h>

typedef struct {
    uint8_t *buffer;
    uint16_t size;
    uint16_t head;  // 写指针
    uint16_t tail;  // 读指针
} RingBuffer_t;

void RB_Init(RingBuffer_t *rb, uint8_t *buf, uint16_t size);
bool RB_Put(RingBuffer_t *rb, uint8_t byte);
bool RB_Get(RingBuffer_t *rb, uint8_t *byte);
uint16_t RB_Available(RingBuffer_t *rb);
void RB_Clear(RingBuffer_t *rb);
#endif

// protocol.h
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
#include <stdbool.h>

#define FRAME_HEADER_0 0xAA
#define FRAME_HEADER_1 0x55

typedef struct {
    uint8_t data_len;
    uint8_t data[128];
    uint16_t crc;
} Frame_t;

bool Protocol_Parse(RingBuffer_t *rb, Frame_t *out_frame);
uint16_t Protocol_CalcCRC16(const uint8_t *data, uint16_t len);
bool Protocol_Validate(const Frame_t *frame);
#endif

AI回复 — 环形缓冲区核心实现:

// ringbuffer.c
#include "ringbuffer.h"
#include <string.h>

void RB_Init(RingBuffer_t *rb, uint8_t *buf, uint16_t size) {
    rb->buffer = buf;
    rb->size = size;
    rb->head = 0;
    rb->tail = 0;
}

bool RB_Put(RingBuffer_t *rb, uint8_t byte) {
    uint16_t next_head = (rb->head + 1) % rb->size;
    if (next_head == rb->tail) {
        return false;  // 缓冲区满
    }
    rb->buffer[rb->head] = byte;
    rb->head = next_head;
    return true;
}

bool RB_Get(RingBuffer_t *rb, uint8_t *byte) {
    if (rb->tail == rb->head) {
        return false;  // 缓冲区空
    }
    *byte = rb->buffer[rb->tail];
    rb->tail = (rb->tail + 1) % rb->size;
    return true;
}

uint16_t RB_Available(RingBuffer_t *rb) {
    if (rb->head >= rb->tail) {
        return rb->head - rb->tail;
    } else {
        return rb->size - rb->tail + rb->head;
    }
}

void RB_Clear(RingBuffer_t *rb) {
    rb->head = 0;
    rb->tail = 0;
}

AI回复 — 协议解析核心实现:

// protocol.c
#include "protocol.h"

uint16_t Protocol_CalcCRC16(const uint8_t *data, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

bool Protocol_Parse(RingBuffer_t *rb, Frame_t *out_frame) {
    uint8_t byte;
    static uint8_t state = 0;
    static uint8_t expected_len = 0;
    static uint8_t data_idx = 0;

    while (RB_Available(rb) > 0) {
        RB_Get(rb, &byte);

        switch (state) {
            case 0:
                if (byte == FRAME_HEADER_0) state = 1;
                break;
            case 1:
                if (byte == FRAME_HEADER_1) state = 2;
                else state = 0;
                break;
            case 2:
                expected_len = byte;
                if (expected_len > 128) { state = 0; break; }
                data_idx = 0;
                state = 3;
                break;
            case 3:
                out_frame->data[data_idx++] = byte;
                if (data_idx >= expected_len) state = 4;
                break;
            case 4:
                out_frame->crc = (uint16_t)byte << 8;
                state = 5;
                break;
            case 5:
                out_frame->crc |= byte;
                out_frame->data_len = expected_len;
                state = 0;
                return Protocol_Validate(out_frame);
        }
    }
    return false;
}

bool Protocol_Validate(const Frame_t *frame) {
    uint16_t calc_crc = Protocol_CalcCRC16(frame->data, frame->data_len);
    return (calc_crc == frame->crc);
}

项目4:温湿度采集系统(多模块协同)

一句话:每2秒采集一次DHT11温湿度,通过串口打印,超温报警

你发给AI的Prompt:

项目:温湿度监测+报警系统
芯片:STM32F103C8T6
硬件:
- PB12=DHT11数据引脚(单总线)
- PA9=USART1_TX(连接电脑,波特率9600)
- PA5=报警LED(低电平点亮)

要求:
- 分层架构:sensor层(驱动)/data层(处理)/app层(业务)
- 温湿度数据结构:typedef struct { uint8_t temp; uint8_t hum; } DHT11_Data_t;
- 串口打印格式:"Temp: XX°C, Hum: XX%"
- 报警:温度>35°C时LED快闪(200ms间隔)

请给出完整模块架构、每个模块的头文件,以及sensor和data层的核心实现。

AI回复 — 模块架构:

模块划分:
- sensor_dht11(硬件驱动层):DHT11单总线读取原始数据
- sensor_manager(驱动抽象层):定时采集、过滤、屏蔽硬件差异
- uart_print(硬件驱动层):串口打印格式化字符串
- alarm(应用层):温度判断、报警逻辑
- app(应用层):主循环,协调各模块

AI回复 — 头文件:

// sensor_dht11.h
#ifndef SENSOR_DHT11_H
#define SENSOR_DHT11_H
#include <stdint.h>
typedef struct {
    uint8_t temp;
    uint8_t hum;
} DHT11_Data_t;

bool DHT11_Read(DHT11_Data_t *data);
#endif

// sensor_manager.h
#ifndef SENSOR_MANAGER_H
#define SENSOR_MANAGER_H
#include "sensor_dht11.h"
void SensorMgr_Init(void);
void SensorMgr_Task(void);  // 每2秒调用一次
DHT11_Data_t SensorMgr_GetData(void);
#endif

// alarm.h
#ifndef ALARM_H
#define ALARM_H
#include <stdbool.h>
#include "sensor_dht11.h"
void Alarm_Init(void);
void Alarm_SetThreshold(uint8_t temp_threshold);
void Alarm_Update(const DHT11_Data_t *data);  // 每秒调用
#endif

AI回复 — sensor_manager核心实现:

// sensor_manager.c
#include "sensor_manager.h"
#include "uart_print.h"
#include "alarm.h"

static DHT11_Data_t current_data = {0, 0};

void SensorMgr_Init(void) {
    // 硬件初始化已在main中完成
}

void SensorMgr_Task(void) {
    DHT11_Data_t raw;
    if (DHT11_Read(&raw)) {
        current_data.temp = raw.temp;
        current_data.hum = raw.hum;

        char buf[32];
        snprintf(buf, sizeof(buf), "Temp: %d°C, Hum: %d%%\r\n",
                 raw.temp, raw.hum);
        UART_Print(buf);

        Alarm_Update(&current_data);
    }
}

DHT11_Data_t SensorMgr_GetData(void) {
    return current_data;
}

项目5:OLED菜单系统(综合应用)

一句话:用OLED屏幕显示可交互菜单,用按键控制上下选择和确认

你发给AI的Prompt:

项目:嵌入式菜单系统
芯片:STM32F103C8T6
硬件:
- PB6=I2C1_SCL,PB7=I2C1_SDA(OLED 0.96寸,SSD1306驱动,I2C地址0x3C)
- PC13=按键K1(上下选择),PA5=按键K2(确认)

功能:
- 屏幕128×64像素,字符模式8行×16列
- 菜单:显示温度/显示时间/设置阈值/关于

要求:
- 菜单模块与应用逻辑分离
- oled.c/h:支持清屏、显示字符、显示数字
- menu.c/h:结构体数组定义菜单项,最多8个菜单项
- key.c/h:短按消抖300ms,长按1s检测

请设计完整的分层架构,给出头文件和menu模块的核心实现。

AI回复 — 头文件:

// oled.h
#ifndef OLED_H
#define OLED_H
#include <stdint.h>
#include <stdbool.h>

#define OLED_ADDR 0x3C

void OLED_Init(void);
void OLED_Clear(void);
void OLED_SetPos(uint8_t x, uint8_t y);  // x=列(0-127), y=页(0-7)
void OLED_Puts(uint8_t x, uint8_t y, const char *str);
void OLED_PrintNum(uint8_t x, uint8_t y, int32_t num);
#endif

// menu.h
#ifndef MENU_H
#define MENU_H
#include <stdint.h>
#include <stdbool.h>

typedef void (*MenuAction_t)(void);

typedef struct {
    const char *title;
    MenuAction_t action;
} MenuItem_t;

typedef struct {
    const char *name;
    MenuItem_t *items;
    uint8_t item_count;
    int8_t selected;  // 当前选中项,-1表示无
} Menu_t;

void Menu_Init(Menu_t *menu);
void Menu_Up(Menu_t *menu);
void Menu_Down(Menu_t *menu);
void Menu_Confirm(Menu_t *menu);
void Menu_Render(const Menu_t *menu);
#endif

AI回复 — menu核心实现:

// menu.c
#include "menu.h"
#include "oled.h"

void Menu_Init(Menu_t *menu) {
    menu->selected = 0;
}

void Menu_Up(Menu_t *menu) {
    if (menu->selected > 0) {
        menu->selected--;
    }
}

void Menu_Down(Menu_t *menu) {
    if (menu->selected < (int8_t)(menu->item_count - 1)) {
        menu->selected++;
    }
}

void Menu_Confirm(Menu_t *menu) {
    if (menu->selected >= 0 && menu->selected < (int8_t)menu->item_count) {
        MenuItem_t *item = &menu->items[menu->selected];
        if (item->action != 0) {
            item->action();
        }
    }
}

void Menu_Render(const Menu_t *menu) {
    OLED_Clear();
    for (uint8_t i = 0; i < menu->item_count; i++) {
        if (i == (uint8_t)menu->selected) {
            OLED_Puts(0, i, "> ");
        } else {
            OLED_Puts(0, i, "  ");
        }
        OLED_Puts(16, i, menu->items[i].title);
    }
}

第八章:调试与排错

本章展示如何用AI辅助调试的完整对话过程,而不只是展示调试的技术知识。

8.1 用AI做代码Review

请审查以下嵌入式代码,重点关注:
1. 潜在Bug(如越界,空指针、竞态条件)
2. 编码规范问题
3. 可优化的地方(如性能、安全性、可维护性)

每条问题请给出严重程度(高/中/低)和修复建议。

[粘贴代码]

8.2 常见错误分类与解决

Flash相关错误

错误信息 原因 解决方案
“No Algorithm found for: 0x08000000” Flash算法未配置 KEIL → Debug → Settings → Flash Download → Add “STM32F10x Med-density Flash”
“Error: L6406E: no space” 代码超出Flash容量 优化代码/换更大芯片/启用代码压缩

串口相关错误

现象 原因 排查方法
输出乱码 波特率不匹配 确认代码和串口助手都用9600或115200
乱码+随机字符 晶振配置错误 确认HSE_VALUE=8MHz或外部晶振值正确配置
丢字符 缓冲区溢出 检查DMA接收是否及时处理数据

DMA相关错误

现象 原因 解决方案
数据错位 DMA和CPU同时操作缓冲区 加临界区保护(__disable_irq/__enable_irq)
中断不触发 中断优先级配置错误 检查NVIC配置,确保DMA中断优先级高于其他实时中断

8.3 AI辅助调试 — 完整对话示例

场景:DMA接收数据错位,展示如何用AI一步步定位问题和修复。


你(第一句话):

【项目阶段】:调试中
【芯片/开发板】:STM32F103C8T6 + ST-Link
【开发环境】:Keil MDK 5.38

【问题描述】
串口DMA接收数据错位

【出现的现象】
- 预期:发送 "AA 55 02 01 02",收到的数据应完全一致
- 实际:每次收到 "AA 55 02 00 02",第4字节(0x01)变成了0x00

【错误信息】
编译无报错,只有运行结果错误

【相关代码】(usart_dma.c 核心部分)
void DMA1_Channel5_IRQHandler(void) {
    if (DMA_GetITStatus(DMA1_IT_HT5)) {
        DMA_ClearITPendingBit(DMA1_IT_HT5);
        data_half_ready = 1;
    }
    if (DMA_GetITStatus(DMA1_IT_TC5)) {
        DMA_ClearITPendingBit(DMA1_IT_TC5);
        data_full_ready = 1;
    }
}

// 主循环
while (1) {
    if (data_half_ready) {
        data_half_ready = 0;
        ProcessData(0, 128);
    }
    if (data_full_ready) {
        data_full_ready = 0;
        ProcessData(128, 256);
    }
}

【已尝试的排查】
1. 换了3根USB线,结果一样 → 排除硬件连接问题
2. 用示波器量TX引脚,发送数据正确 → 排除发送端问题
3. 不用DMA,直接阻塞接收,数据正确 → 确定是DMA相关问题

请帮我:
1. 分析根本原因
2. 给出修复方案
3. 如何验证修复

AI回复(第一轮分析):

根本原因分析:竞态条件(Race Condition)

你的代码存在一个经典的DMA双缓冲竞态问题:DMA半传输中断(HT)触发后、数据被主循环处理前,DMA已经开始了第二轮接收,覆盖了缓冲区的前半部分。

修复方案:采用双缓冲 + 临界区保护

方案A(推荐):只用TC中断,不用HT中断。串口数据量小的情况下,半满中断反而增加复杂度。

方案B:如果需要及时处理,用双缓冲。两个128字节缓冲区交替,DMA写buf0时主循环读buf1,反之亦然。


你(追问方案B):

方案B的详细实现,给我.h和.c文件,包括双缓冲和临界区保护的具体代码。


AI回复(修复代码):

// usart_dma.h
#ifndef USART_DMA_H
#define USART_DMA_H
#include <stdint.h>
#include <stdbool.h>

#define DMA_BUF_SIZE 128

typedef struct {
    uint8_t buf0[DMA_BUF_SIZE];
    uint8_t buf1[DMA_BUF_SIZE];
    volatile uint8_t active_buf;  // 0=buf0被DMA写, 1=buf1被DMA写
    volatile bool data_ready;
    uint8_t ready_buf;
} DMA_Context_t;

extern DMA_Context_t dma_ctx;

void USART_DMA_Init(void);
uint8_t* DMA_GetReadyBuf(void);
void DMA_Swap(void);
#endif

建议:对于普通串口收包,只用TC中断是更实用的方案:

  • 缓冲区设256字节或更大
  • 只用传输完成中断(TC),不用半传输中断(HT)
  • 主循环检测data_ready标志,一次性处理完整帧

这样完全避免竞态条件,实现最简单。


你(采纳简化方案):

好,我用只用TC中断的简化方案。帮我写一个更简单的实现。


AI回复(简化方案):

// usart_dma.h
#ifndef USART_DMA_H
#define USART_DMA_H
#include <stdint.h>
#include <stdbool.h>

#define DMA_BUF_SIZE 256

extern volatile bool dma_transfer_complete;

void USART_DMA_Init(void);
bool DMA_IsDataReady(void);
void DMA_ClearFlag(void);
#endif
// usart_dma.c
#include "usart_dma.h"

volatile bool dma_transfer_complete = false;
uint8_t dma_rx_buf[DMA_BUF_SIZE];

void USART_DMA_Init(void) {
    dma_transfer_complete = false;

    __HAL_DMA_DISABLE(&hdma_usart1_rx);
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;  // 非循环模式,收满256字节停下来
    __HAL_DMA_ENABLE(&hdma_usart1_rx);

    HAL_UART_Receive_DMA(&huart1, dma_rx_buf, DMA_BUF_SIZE);
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    dma_transfer_complete = true;
}

bool DMA_IsDataReady(void) {
    return dma_transfer_complete;
}

void DMA_ClearFlag(void) {
    dma_transfer_complete = false;
    HAL_UART_Receive_DMA(&huart1, dma_rx_buf, DMA_BUF_SIZE);
}

你(验证结果):

发5帧数据测试,全部正确。问题解决了。


8.4 本案例的教训

  • 调试要带上下文:发了完整的环境+现象+代码+排查过程,AI才能准确定位
  • 追问要具体:问"方案B的详细实现"比问"怎么修"更容易得到可用的代码
  • 采纳简化方案:不是每个场景都需要复杂方案,单TC中断就解决了问题
  • 验证要量化:发5帧全部正确 → 比"感觉好了"更有说服力

第九章:附录:工具链与资源

工具 用途 获取
Keil MDK STM32开发 www.keil.com(30天试用)
STM32CubeMX 代码生成/引脚配置 www.st.com/stm32cubemx(免费)
PlatformIO ESP32/多平台开发 VSCode插件市场(免费)
SSCOM 串口调试助手 百度搜索下载
STM32CubeProgrammer 独立烧录工具 www.st.com(免费)
Git 版本管理 git-scm.com(免费)

本手册由资深嵌入式工程师编写,结合上下文构建工程最佳实践。强调架构优先、模块化开发、可持续的项目上下文管理。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐