+
+
-
- 这套内容最适合想系统学习嵌入式软件的同学、准备校招社招面试的开发者,以及希望把零散知识点整理成长期参考资料的人。
From 3013bf0da148575fdd7a62f3dc222d8acfb6edc5 Mon Sep 17 00:00:00 2001
From: liueggy <3157487230@qq.com>
Date: Sat, 4 Apr 2026 10:20:40 +0800
Subject: [PATCH 06/13] Improve chapter readability and remove emoji-heavy note
styling
This revision starts turning the markdown collection into a cleaner reference set: chapter one is rewritten into a more complete tutorial-style structure, the surrounding notes have emoji removed for a more stable technical tone, and link normalization is updated so code fences no longer confuse markdown-link checks.
Constraint: Preserve the existing repository layout and chapter entry files while improving readability in place
Constraint: Keep the current GitHub Pages/VitePress site working during content cleanup
Rejected: Rewrite all chapters in one pass | too much uncontrolled content churn for one review cycle
Rejected: Only strip emoji without restructuring chapter one | would not establish the target writing standard for later chapters
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Continue future chapter rewrites in small passes using the same explanatory structure established in chapter one
Tested: npm run docs:fix-links; npm run docs:check-links; npm run docs:build
Not-tested: Human editorial review of every chapter after emoji removal
---
.../Readme.md" | 1318 ++++++++++-------
.../README.md" | 27 +-
.../README.md" | 43 +-
.../README.md" | 32 +-
05-EmbeddedLinux/README.md | 61 +-
06-NetworkIot/README.md | 26 +-
07-Debug_Optimization/README.md | 27 +-
.../README.md" | 40 +-
09-2025_AI_on_MCU/README.md | 28 +-
README.md | 93 +-
books/README.md | 2 -
scripts/docs-tools.mjs | 37 +-
.../README.md" | 6 -
...x\351\235\242\350\257\225\351\242\2301.md" | 7 -
...x\351\235\242\350\257\225\351\242\2302.md" | 27 +-
.../README.md" | 1 -
...06\351\235\242\350\257\225\351\242\230.md" | 21 +-
17 files changed, 905 insertions(+), 891 deletions(-)
diff --git "a/01-C\350\257\255\350\250\200\345\237\272\347\241\200\344\270\216\350\277\233\351\230\266/Readme.md" "b/01-C\350\257\255\350\250\200\345\237\272\347\241\200\344\270\216\350\277\233\351\230\266/Readme.md"
index df55bec..40df833 100644
--- "a/01-C\350\257\255\350\250\200\345\237\272\347\241\200\344\270\216\350\277\233\351\230\266/Readme.md"
+++ "b/01-C\350\257\255\350\250\200\345\237\272\347\241\200\344\270\216\350\277\233\351\230\266/Readme.md"
@@ -1,746 +1,909 @@
-## 🔵 第一层:C/C++ 语言基础与进阶(必修)
+## 第一层:C/C++ 语言基础与进阶(必修)
-### ✅ 变量 / 数据类型 / 关键字 / 常量
+这一章是嵌入式软件开发的基础。无论后续学习驱动、RTOS、Linux 还是网络协议,最终都要回到数据如何表示、内存如何组织、代码如何编译和调试这些底层问题。
+
+建议学习目标:
+
+- 能区分常见数据类型、变量、常量、作用域和生命周期。
+- 能理解栈、堆、指针、数组、字符串在内存中的关系。
+- 能读懂常见的位操作、结构体、枚举、宏和修饰符用法。
+- 能用编译器和调试器分析一个简单 C 程序的构建与运行过程。
+
+---
+
+### 变量 / 数据类型 / 关键字 / 常量
+
+#### 变量(Variable)
+
+变量本质上是程序中一块带名字的存储区域。程序运行时,变量名帮助我们访问某个地址上的数据。
+
+基本规则:
+
+- 声明时需要指定类型。
+- 使用前应初始化,避免读取未定义值。
+- 变量是否可见、何时创建和销毁,取决于其作用域和存储期。
-#### 📌 变量(Variable)
-- 用于在程序中存储数据的具名内存区域。
-- 声明格式:`类型 变量名 [= 初始值];`
```c
int count = 10;
-float temperature;
-char c = 'A';
+float temperature = 36.5f;
+char grade = 'A';
```
-- 局部变量:函数内声明,仅在函数内部可用。
-- 全局变量:函数外声明,整个文件或项目中可见(根据作用域)。
-#### 📌 数据类型(Data Types)
-- **整型**:`int`, `short`, `long`, `long long`, `unsigned`
-- **浮点型**:`float`, `double`
-- **字符型**:`char`
-- **派生类型**:指针、数组、结构体等
+常见分类:
+
+- 局部变量:定义在函数或代码块内部,只在当前作用域内可见。
+- 全局变量:定义在函数外部,整个文件或其他文件中可见。
+- 形参:函数调用时临时接收外部输入。
+
```c
-unsigned int u = 100;
-long long big_number = 12345678900LL;
+int g_mode = 1; // 全局变量
+
+void print_status(void) {
+ int local_count = 0; // 局部变量
+ local_count++;
+}
```
-#### 📌 关键字(Keywords)
-常用 C 关键字解释如下:
-| 关键字 | 说明 |
-|------------|------------------------------|
-| `const` | 定义只读变量 |
-| `volatile` | 防止编译器优化,常用于寄存器 |
-| `static` | 变量作用域或函数仅在本文件可见 |
-| `extern` | 声明外部变量/函数 |
-| `typedef` | 为数据类型取别名 |
+工程建议:
+
+- 局部变量优先于全局变量。
+- 全局变量应限制数量,并用清晰命名表达用途。
+- 嵌入式场景下,硬件寄存器镜像、状态标志、缓冲区等全局对象要特别注意并发访问。
+
+#### 数据类型(Data Types)
+
+数据类型决定了数据占用多少字节、如何解释这些比特、可以参与哪些运算。
+
+常见基本类型:
+
+- 整型:`char`、`short`、`int`、`long`、`long long`
+- 无符号整型:`unsigned char`、`unsigned int` 等
+- 浮点型:`float`、`double`
+- 布尔型:`_Bool` 或 `stdbool.h` 中的 `bool`
+- 派生类型:数组、指针、结构体、联合体、枚举
+
```c
-static int counter = 0; // 内部链接
-extern int g_value; // 声明外部变量
-volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问
+#include
+
+uint8_t status = 0x5A;
+int32_t speed = -120;
+float voltage = 3.3f;
+double ratio = 1.0 / 3.0;
```
-#### 📌 常量(Constant)
-- **字面常量**:如 `10`, `3.14`, `'a'`, `"abc"`
-- **符号常量**:用 `#define` 或 `const` 定义
+在嵌入式开发中,推荐优先使用固定宽度整数类型:
+
+- `uint8_t`、`int16_t`、`uint32_t`
+- 好处是跨平台时大小明确,便于寄存器映射、通信协议和文件格式定义
+
+注意:
+
+- `int`、`long` 的位宽与编译器和平台有关,不适合直接用于协议字段定义。
+- 浮点运算在部分 MCU 上成本较高,应结合硬件是否有 FPU 决定是否大量使用。
+
+#### 关键字(Keywords)
+
+关键字是语言保留字,不能作为变量名使用。
+
+嵌入式开发最常见的一组关键字如下:
+
+| 关键字 | 作用 | 常见场景 |
+| --- | --- | --- |
+| `const` | 定义只读对象 | 查表、配置参数 |
+| `volatile` | 告诉编译器不要擅自优化访问 | 寄存器、中断共享变量 |
+| `static` | 控制作用域和存储期 | 文件内私有函数、静态局部变量 |
+| `extern` | 声明外部定义的变量或函数 | 多文件工程 |
+| `typedef` | 给类型起别名 | 提高可读性 |
+| `sizeof` | 计算对象或类型大小 | 缓冲区长度、数组长度 |
+
```c
- #define PI 3.14159
-const int MAX_SIZE = 100;
+static int s_error_count = 0;
+extern int g_system_state;
+
+typedef struct {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+} rtc_date_t;
```
----
+#### 常量(Constant)
-### ✅ 栈 和 堆(内存管理)
-#### 栈 (stack):自动分配内存,函数退出即释放。
-1. 核心特性
-- 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
-- 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
-- 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
-- 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
-
-2. 存储内容
-- 局部变量:函数内部定义的变量。
-- 返回地址:函数执行完毕后返回的位置。
-- 函数参数:调用函数时传递的参数。
-- 寄存器值:保存调用前的寄存器状态,以便恢复。
-
-3. 工作原理
-- 栈指针(ESP):指向当前栈顶的内存地址。
-- 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
-- 示例代码:
-```c
-void func(int a, int b) {
- int sum = a + b; // sum存储在栈上
- // ...
-} // 函数返回时,sum和参数a、b自动释放
-```
-4. 优缺点
-- 优点:无需手动管理内存,速度快,不会内存泄漏。
-- 缺点:生命周期固定(函数结束即释放),空间有限。
-
-#### 堆 (heap):使用 `malloc` / `free` 手动分配和释放
-1. 核心特性
-
-- 手动分配与释放:使用malloc/calloc/realloc分配,free释放。
-- 动态生命周期:内存块的生命周期由程序员控制,可跨函数使用。
-- 碎片化问题:频繁分配和释放可能导致内存碎片,降低空间利用率。
-- 慢速访问:需通过指针间接访问,效率低于栈。
-
-2. 存储内容
-- 动态分配的对象:如malloc返回的内存块。
-- 大型数据结构:如数组、链表、树等需要动态调整大小的结构。
-- 跨函数数据:需要在函数调用结束后继续存在的数据。
-
-3. 工作原理
-- 内存管理器:操作系统提供的堆管理器负责分配和回收内存。
-- 空闲链表:堆管理器维护空闲内存块列表,分配时查找合适大小的块。
-- 示例代码:
-```c
-void createArray() {
- int* arr = (int*)malloc(10 * sizeof(int)); // 从堆分配内存
- if (arr != NULL) {
- arr[0] = 100; // 使用堆内存
- // ...
- }
- free(arr); // 手动释放内存
-}
-```
-4. 常见函数
-- malloc(size_t size):分配指定字节的内存,不初始化。
-- calloc(size_t num, size_t size):分配内存并初始化为 0。
-- realloc(void* ptr, size_t new_size):调整已分配内存的大小。
-- free(void* ptr):释放内存,必须与malloc配对使用。
+常量是在程序执行期间不应被修改的值。
-**栈 vs 堆的对比**
-| 特性 | 栈(Stack) | 堆(Heap) |
-|--------------|--------------------------------------|---------------------------------------------|
-| 分配方式 | 自动(由编译器管理) | 手动(如 `malloc`/`free` 或 `new`/`delete`)|
-| 生命周期 | 函数调用期间自动创建和销毁 | 程序员控制,需手动释放 |
-| 内存空间 | 连续、有限(通常几 MB) | 不连续、较大(受限于物理内存) |
-| 访问速度 | 快(通过寄存器快速访问) | 慢(通过指针间接访问) |
-| 内存碎片 | 不存在(先进后出结构) | 可能产生(频繁分配和释放) |
-| 使用场景 | 局部变量、函数调用栈帧 | 动态数据结构、跨函数共享数据 |
+常见形式:
----
+- 字面量:`10`、`3.14`、`'A'`、`"UART"`
+- 宏常量:`#define BUF_SIZE 128`
+- `const` 常量:`const int timeout_ms = 1000;`
+- 枚举常量:`STATE_IDLE`
-### 指针
-#### 指针的基本概念
+```c
+#define PI 3.1415926f
+const int max_retry = 3;
+```
-**指针的作用:** 可以通过指针间接访问内存
+宏常量和 `const` 的区别:
-- 内存编号是从0开始记录的,一般用十六进制数字表示
-- 可以利用指针变量保存地址
+- `#define` 发生在预处理阶段,本质是文本替换。
+- `const` 有类型,受编译器检查,更安全。
+建议:
+- 能用 `const` 的地方优先用 `const`。
+- 宏更适合做条件编译、寄存器位定义、通用模板。
-#### 指针变量的定义和使用
+---
+
+### 栈 和 堆(内存管理)
-指针变量定义语法: `数据类型 * 变量名;`
+理解栈和堆,是写对 C 程序、避免内存错误的关键。
-**示例:**
+#### 栈 (stack):自动分配内存,函数退出即释放
-```cpp
-int main() {
+栈由编译器和 CPU 调用约定共同管理,主要用于保存函数调用现场。
- //1、指针的定义
- int a = 10; //定义整型变量a
-
- //指针定义语法: 数据类型 * 变量名 ;
- int * p;
+典型内容:
- //指针变量赋值
- p = &a; //指针指向变量a的地址
- cout << &a << endl; //打印数据a的地址
- cout << p << endl; //打印指针变量p
+- 局部变量
+- 函数参数
+- 返回地址
+- 部分寄存器保存值
- //2、指针的使用
- //通过*操作指针变量指向的内存
- cout << "*p = " << *p << endl;
+特征:
- system("pause");
+- 分配和释放速度快
+- 生命周期明确,函数结束后自动回收
+- 空间相对有限
+- 不适合存放过大的局部数组
- return 0;
+```c
+void process(void) {
+ int value = 10;
+ char name[16] = "uart";
}
```
-指针变量和普通变量的区别
+风险点:
-- 普通变量存放的是数据,指针变量存放的是地址
-- 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
+- 大数组放在栈上可能导致栈溢出。
+- 不能返回局部变量地址。
-总结1: 我们可以通过 & 符号 获取变量的地址
+```c
+int *bad_func(void) {
+ int local = 10;
+ return &local; // 错误:返回了已经失效的地址
+}
+```
-总结2:利用指针可以记录地址
+#### 堆 (heap):使用 `malloc` / `free` 手动分配和释放
-总结3:对指针变量解引用,可以操作指针指向的内存
+堆用于运行时动态申请内存,适合大小在编译时无法确定的数据。
+特征:
+- 生命周期由程序员控制
+- 可跨函数存在
+- 申请和释放速度慢于栈
+- 频繁申请释放可能造成碎片
-#### 指针所占内存空间
+```c
+#include
-提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
+void example(void) {
+ int *buffer = malloc(10 * sizeof(int));
+ if (buffer == NULL) {
+ return;
+ }
-**示例:**
+ buffer[0] = 100;
+ free(buffer);
+ buffer = NULL;
+}
+```
-```cpp
-int main() {
+常见函数:
- int a = 10;
+- `malloc(size)`:申请指定字节数,不初始化
+- `calloc(n, size)`:申请并清零
+- `realloc(ptr, new_size)`:调整大小
+- `free(ptr)`:释放内存
- int * p;
- p = &a; //指针指向数据a的地址
+工程建议:
- cout << *p << endl; //* 解引用
- cout << sizeof(p) << endl;
- cout << sizeof(char *) << endl;
- cout << sizeof(float *) << endl;
- cout << sizeof(double *) << endl;
+- 资源受限 MCU 项目中,尽量谨慎使用动态内存。
+- 如果必须使用,要统一封装内存分配策略,并明确释放时机。
+- `free(ptr)` 后建议把指针置为 `NULL`。
- system("pause");
+**栈 vs 堆的对比**
- return 0;
-}
-```
+| 特性 | 栈(Stack) | 堆(Heap) |
+| --- | --- | --- |
+| 管理方式 | 自动管理 | 手动管理 |
+| 生命周期 | 作用域结束自动释放 | 程序员决定 |
+| 分配速度 | 快 | 相对较慢 |
+| 空间大小 | 较小 | 通常更大 |
+| 碎片问题 | 基本没有 | 可能出现 |
+| 常见用途 | 局部变量、函数调用 | 动态缓冲区、链表、对象池 |
-总结:所有指针类型在32位操作系统下是4个字节,64位操作系统为8个字节
+---
-#### 空指针和野指针
+### 指针
+
+指针是 C 语言最核心、也最容易出错的特性之一。它的本质是“保存地址的变量”。
-**空指针**:指针变量指向内存中编号为0的空间
+#### 指针的基本概念
-**用途:** 初始化指针变量
+指针变量中保存的是某个对象的地址,而不是对象本身。
-**注意:** 空指针指向的内存是不可以访问的
+```c
+int value = 10;
+int *ptr = &value;
+```
+上面代码中:
-**示例1:空指针**
+- `value` 是整型变量
+- `&value` 是变量 `value` 的地址
+- `ptr` 是一个“指向 int 的指针”
+- `*ptr` 表示访问 `ptr` 指向的内容
-```cpp
-int main() {
+要点:
- //指针变量p指向内存地址编号为0的空间
- int * p = NULL;
+- `&` 取地址
+- `*` 解引用
+- 指针类型必须和目标对象类型匹配
- //访问空指针报错
- //内存编号0 ~255为系统占用内存,不允许用户访问
- cout << *p << endl;
+#### 指针变量的定义和使用
- system("pause");
+语法:
- return 0;
-}
+```c
+数据类型 *指针变量名;
```
+示例:
+```c
+#include
-**野指针**:指针变量指向非法的内存空间
+int main(void) {
+ int a = 10;
+ int *p = &a;
-**示例2:野指针**
+ printf("a = %d\n", a);
+ printf("&a = %p\n", (void *)&a);
+ printf("p = %p\n", (void *)p);
+ printf("*p = %d\n", *p);
-```cpp
-int main() {
+ *p = 20;
+ printf("a = %d\n", a);
+ return 0;
+}
+```
+
+理解重点:
- //指针变量p指向内存地址编号为0x1100的空间
- int * p = (int *)0x1100;
+- `p` 存的是地址
+- `*p` 才是地址对应位置上的值
+- 修改 `*p` 就是在修改 `a`
- //访问野指针报错
- cout << *p << endl;
+#### 指针所占内存空间
- system("pause");
+指针本身也是变量,所以它也占内存。
+
+```c
+#include
- return 0;
+int main(void) {
+ int *p = NULL;
+ printf("%zu\n", sizeof(p));
+ return 0;
}
```
-总结:空指针和野指针都不是我们申请的空间,因此不要访问。
+常见情况:
+- 32 位系统中通常是 4 字节
+- 64 位系统中通常是 8 字节
-#### const修饰指针
+注意:
+- 不同类型的指针本身大小通常相同
+- 但它们解引用后的对象大小不同
-const修饰指针有三种情况
+#### 空指针和野指针
-1. const修饰指针 --- 常量指针
-2. const修饰常量 --- 指针常量
+空指针:
-1. const既修饰指针,又修饰常量
+- 值为 `NULL`
+- 表示当前不指向有效对象
+- 常用作初始化值
+```c
+int *p = NULL;
+```
-**示例:**
+野指针:
-```cpp
-int main() {
+- 指向未知或已失效地址的指针
+- 访问野指针通常导致崩溃或不可预期行为
- int a = 10;
- int b = 10;
+常见来源:
- //const修饰的是指针,指针指向可以改,指针指向的值不可以更改
- const int * p1 = &a;
- p1 = &b; //正确
- //*p1 = 100; 报错
-
+- 指针未初始化
+- 返回局部变量地址
+- `free` 后继续使用
- //const修饰的是常量,指针指向不可以改,指针指向的值可以更改
- int * const p2 = &a;
- //p2 = &b; //错误
- *p2 = 100; //正确
+```c
+int *p;
+// p 未初始化,此时就是危险指针
+```
- //const既修饰指针又修饰常量
- const int * const p3 = &a;
- //p3 = &b; //错误
- //*p3 = 100; //错误
+#### const修饰指针
- system("pause");
+`const` 和指针结合时容易混淆,建议分三种情况记忆。
- return 0;
-}
+1. 指向常量的指针
+
+```c
+const int *p = &value;
```
-技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
+- 可以修改 `p` 指向别处
+- 不能通过 `p` 修改 `value`
+2. 常量指针
+```c
+int *const p = &value;
+```
-#### 指针和数组
-核心概念
-- 数组本质:连续存储多个指针变量。
-- 用途:常用于处理多个字符串或动态分配的内存块。
+- `p` 本身不能再指向别处
+- 可以通过 `p` 修改 `value`
-**作用:** 利用指针访问数组中元素
+3. 指向常量的常量指针
-**示例:**
+```c
+const int *const p = &value;
+```
-```cpp
-int main() {
+- 既不能改指向
+- 也不能通过它改值
- int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
+#### 指针和数组
- int * p = arr; //指向数组的指针
+数组名在多数表达式中会退化为指向首元素的指针。
- cout << "第一个元素: " << arr[0] << endl;
- cout << "指针访问第一个元素: " << *p << endl;
+```c
+int arr[4] = {10, 20, 30, 40};
+int *p = arr;
- for (int i = 0; i < 10; i++)
- {
- //利用指针遍历数组
- cout << *p << endl;
- p++;
- }
+printf("%d\n", arr[0]);
+printf("%d\n", *(p + 1));
+```
- system("pause");
+理解重点:
- return 0;
-}
-```
+- `arr` 代表首元素地址
+- `arr[i]` 等价于 `*(arr + i)`
+注意:
+- `sizeof(arr)` 得到整个数组大小
+- `sizeof(p)` 得到指针变量大小
#### 指针和函数
-**作用:** 利用指针作函数参数,可以修改实参的值(和前边形参相反)
+指针可以作为函数参数,也可以作为函数返回值。
-**示例:**
+1. 指针作为参数:实现“按地址传递”
-```cpp
-//值传递
-void swap1(int a ,int b)
-{
- int temp = a;
- a = b;
- b = temp;
-}
-//地址传递
-void swap2(int * p1, int *p2)
-{
- int temp = *p1;
- *p1 = *p2;
- *p2 = temp;
+```c
+void swap_int(int *a, int *b) {
+ int temp = *a;
+ *a = *b;
+ *b = temp;
}
+```
+
+2. 指针作为返回值:常用于返回数组、动态内存或对象地址
-int main() {
+```c
+int *find_max(int *arr, int n) {
+ int *max_ptr = &arr[0];
+ for (int i = 1; i < n; ++i) {
+ if (arr[i] > *max_ptr) {
+ max_ptr = &arr[i];
+ }
+ }
+ return max_ptr;
+}
+```
- int a = 10;
- int b = 20;
- swap1(a, b); // 值传递不会改变实参
+#### 指针数组函数
- swap2(&a, &b); //地址传递会改变实参
+这里容易混淆三个概念:
- cout << "a = " << a << endl;
+- 指针数组:数组中每个元素都是指针
+- 数组指针:指向整个数组的指针
+- 函数指针:保存函数入口地址的指针
- cout << "b = " << b << endl;
+示例:
- system("pause");
+```c
+int a = 1, b = 2, c = 3;
+int *ptr_array[3] = {&a, &b, &c}; // 指针数组
- return 0;
-}
+int arr[3] = {1, 2, 3};
+int (*array_ptr)[3] = &arr; // 数组指针
```
-总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
+---
+### 函数指针 / 函数指针数组
+函数指针保存的是函数入口地址,适合做回调、状态机、命令分发表等。
#### 指针、数组、函数
-- **函数指针声明**:`int (*fp)(int)` 表示指向返回 `int` 的函数的指针
-- **函数指针数组**:用于策略模式或注册多个处理函数
-
-**案例描述:** 封装一个函数,利用冒泡排序,实现对整型数组的升序排序
-例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
+函数指针声明示例:
+```c
+int add(int a, int b) {
+ return a + b;
+}
+int main(void) {
+ int (*fp)(int, int) = add;
+ int result = fp(3, 4);
+ return result;
+}
+```
-**示例:**
+函数指针数组示例:
-```cpp
-//冒泡排序函数
-void bubbleSort(int * arr, int len) //int * arr 也可以写为int arr[]
-{
- for (int i = 0; i < len - 1; i++)
- {
- for (int j = 0; j < len - 1 - i; j++)
- {
- if (arr[j] > arr[j + 1])
- {
- int temp = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = temp;
- }
- }
- }
+```c
+int add(int a, int b) { return a + b; }
+int sub(int a, int b) { return a - b; }
+
+int main(void) {
+ int (*ops[2])(int, int) = {add, sub};
+ int x = ops[0](10, 5);
+ int y = ops[1](10, 5);
+ return x + y;
}
+```
-//打印数组函数
-void printArray(int arr[], int len)
-{
- for (int i = 0; i < len; i++)
- {
- cout << arr[i] << endl;
- }
-}
+嵌入式常见用途:
+
+- 菜单命令表
+- 驱动操作接口
+- 中断回调表
+- 状态机动作分发
-int main() {
+---
- int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
- int len = sizeof(arr) / sizeof(int);
+### 表达式、语句、运算符
- bubbleSort(arr, len);
+这三个概念经常一起出现,但含义不同。
- printArray(arr, len);
+- 表达式:会产生一个值
+- 语句:完成一个动作
+- 运算符:用于构造表达式
- system("pause");
+示例:
- return 0;
+```c
+a + b; // 表达式
+x = a + b; // 赋值语句
+if (x > 0) { // 条件语句
+ x--;
}
```
+常见运算符分类:
+
+| 类别 | 示例 | 用途 |
+| --- | --- | --- |
+| 算术运算符 | `+ - * / %` | 数值运算 |
+| 关系运算符 | `> < >= <= == !=` | 比较大小和相等性 |
+| 逻辑运算符 | `&& || !` | 条件组合 |
+| 位运算符 | `& | ^ ~ << >>` | 寄存器与标志位操作 |
+| 赋值运算符 | `= += -=` | 修改变量值 |
+| 条件运算符 | `?:` | 简短条件选择 |
+
+注意:
+
+- `=` 是赋值,`==` 是比较
+- `&&` 和 `||` 具有短路特性
+- 位运算和逻辑运算不要混用
-### ✅ 表达式、语句、运算符
-- 表达式:`a + b`, `x++`, `p[i]`
-- 运算符:`+`, `-`, `*`, `/`, `%`, `&&`, `||`, `!`, `==`, `!=`
-- 语句:控制流程结构 `if`, `for`, `while`, `switch`
+---
+
+### 数组 / 字符串
-### ✅ 数组 / 字符串
#### 数组(Array)
-- 数组是一组相同类型数据的有序集合,在内存中连续存储。
-- 一维数组定义:`类型 数组名[大小];`
+
+数组是一组相同类型元素的连续存储空间。
```c
-int arr[5] = {1, 2, 3, 4, 5};
-char str[10] = "Hello";
+int adc_values[4] = {100, 200, 300, 400};
```
-- 数组索引从 0 开始,访问方式如:arr[2] 表示第三个元素。
-- 多维数组:int matrix[3][4]; 表示 3 行 4 列矩阵。
-#### 字符串(String)
-- 字符串是以空字符 '\0' 结尾的字符数组。
-- 定义方式:
+特点:
+
+- 元素类型相同
+- 内存连续
+- 下标从 0 开始
+- 越界访问不会自动报错,但后果严重
+
+常见操作:
+
```c
-char str1[] = "Hello"; // 自动添加 '\0'
-char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
-```
-- 常用字符串函数
-```
-// 需包含头文件
-strlen(str); // 计算长度(不含 '\0')
-strcpy(dest, str); // 拷贝
-strcmp(a, b); // 比较字符串
-strcat(a, b); // 将 b 拼接到 a 后面
+for (int i = 0; i < 4; ++i) {
+ printf("%d\n", adc_values[i]);
+}
```
-```c
- #include
-char msg[20];
-strcpy(msg, "Hi"); // msg: "Hi"
-strcat(msg, " there"); // msg: "Hi there"
+二维数组示例:
+
+```c
+int matrix[2][3] = {
+ {1, 2, 3},
+ {4, 5, 6}
+};
```
-### ✅ 结构体 / 共用体 / 枚举 / 位域
-#### 结构体
-**定义:**
+#### 字符串(String)
-结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
-**使用场景:**
-- 表示传感器、外设状态、网络包头等复杂数据
+C 语言字符串本质上是以 `'\0'` 结尾的字符数组。
-- 多变量统一传参,提升代码组织性
+```c
+char name[] = "UART";
+char cmd[8] = {'R', 'E', 'S', 'E', 'T', '\0'};
+```
+
+常见注意点:
+
+- 字符串必须有结束符 `'\0'`
+- 使用 `strcpy`、`sprintf` 时要注意缓冲区溢出
+- 嵌入式项目中推荐优先使用带长度限制的函数,如 `snprintf`
-语法:
```c
+#include
-struct StructName {
- int id;
- float value;
- char name[20];
-};
+char buf[16];
+snprintf(buf, sizeof(buf), "id=%d", 12);
```
-示例:
-```c
-struct SensorData {
- int id;
- float temperature;
- char location[20];
-};
+---
+
+### 结构体 / 共用体 / 枚举 / 位域
+
+#### 结构体
-struct SensorData s1 = {1, 36.5, "room_1"};
-printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);
+结构体用于把多个不同类型的数据组织成一个整体。
+
+```c
+typedef struct {
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+} rtc_time_t;
```
-#### 共用体
-**定义:**
+使用场景:
-共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
-**使用场景:**
-- 节省内存:如嵌入式协议帧解析
+- 协议帧
+- 设备配置
+- 任务上下文
+- 传感器数据打包
-- 多种数据格式的重解释
+注意内存对齐:
-语法:
-```c
+- 编译器可能插入填充字节
+- 设计通信协议或寄存器镜像时要特别小心
-union UnionName {
- int i;
- float f;
- char c;
-};
-```
-示例:
```c
+typedef struct {
+ uint8_t header;
+ uint32_t data;
+} packet_t;
+```
-union Data {
- int i;
- float f;
-};
+#### 共用体
+
+共用体(联合体)中的成员共享同一段内存。
-union Data d;
-d.i = 10;
-printf("i = %d\n", d.i);
-d.f = 3.14;
-printf("f = %.2f\n", d.f); // 修改 f 会破坏 i
+```c
+typedef union {
+ uint32_t value;
+ uint8_t bytes[4];
+} data_u;
```
-注意事项:
-- 占用内存大小为最大成员的大小
-- 修改一个成员后,其他成员的值不可预测
+特点:
+
+- 所有成员起始地址相同
+- 同一时刻通常只使用其中一种解释方式
+- 常用于协议解析、字节拆分、底层调试
#### 枚举(Enumeration)
-**定义:**
-枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。
+枚举适合表示一组离散状态或命令。
-**使用场景:**
-- 定义状态机状态
+```c
+typedef enum {
+ STATE_IDLE = 0,
+ STATE_RUN,
+ STATE_ERROR
+} system_state_t;
+```
-- 表示设备运行模式、错误码
+好处:
-语法:
-```c
+- 提高可读性
+- 降低魔法数字使用
+- 适合状态机表达
-enum Color { RED, GREEN, BLUE };
-enum Color color = GREEN;
-```
-示例:
-```c
+#### 位域(Bit Field)
-enum State {
- STATE_IDLE,
- STATE_ACTIVE,
- STATE_ERROR = 100, // 可手动赋值
- STATE_SLEEP
-};
+位域允许把结构体成员映射为若干个比特位。
-enum State current = STATE_ACTIVE;
-printf("State: %d\n", current); // 输出 1
+```c
+typedef struct {
+ unsigned ready : 1;
+ unsigned error : 1;
+ unsigned mode : 2;
+ unsigned reserve : 4;
+} status_flag_t;
```
-特性:
-- 默认从 0 开始递增
-- 可强制设定起始值
+注意:
-#### 位域(Bit Field)
-**定义:**
+- 位域布局与编译器实现有关
+- 对外协议、硬件寄存器定义中通常更推荐使用掩码和位运算
-位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。
+---
-**使用场景:**
-- 配置寄存器映射
+### 位操作
-- 网络协议比特位字段解析
+位操作在嵌入式开发中非常常见,尤其是寄存器配置和状态位处理。
-- 内存空间紧张场景
+常见操作:
-语法:
-```c
+| 操作 | 写法 | 说明 |
+| --- | --- | --- |
+| 按位与 | `a & b` | 清零某些位 |
+| 按位或 | `a | b` | 置位某些位 |
+| 按位异或 | `a ^ b` | 翻转某些位 |
+| 按位取反 | `~a` | 所有位取反 |
+| 左移 | `a << n` | 常用于构造位掩码 |
+| 右移 | `a >> n` | 提取高位或做缩放 |
-struct Flags {
- unsigned int ready : 1;
- unsigned int error : 1;
- unsigned int mode : 2;
-};
-```
示例:
+
```c
+#define BIT(n) (1U << (n))
-struct Flags f;
-f.ready = 1;
-f.error = 0;
-f.mode = 3; // 占2位,最大为11(二进制)即3
+uint32_t reg = 0;
-printf("ready = %d, mode = %d\n", f.ready, f.mode);
+reg |= BIT(3); // 置位 bit3
+reg &= ~BIT(3); // 清除 bit3
+reg ^= BIT(2); // 翻转 bit2
+
+if (reg & BIT(0)) {
+ // 检测 bit0 是否为 1
+}
```
-注意事项:
-- 位域不能取地址(&f.ready 不合法)
+工程建议:
-- 字段数值不能超过位数范围(2^n - 1)
+- 用宏统一管理位定义
+- 避免直接写魔法数字,例如 `0x20`
+- 位操作前确认数据类型是无符号类型,避免移位歧义
-- 与具体编译器实现密切相关(跨平台需小心)
+---
-### ✅ 位操作
-- 嵌入式开发中用于设置寄存器位、控制硬件
-```c
-#define LED_PIN (1 << 2)
-PORT |= LED_PIN; // 置位
-PORT &= ~LED_PIN; // 清零
-PORT ^= LED_PIN; // 翻转
-```
+### 关键语义 & 修饰符
-### ✅ 关键语义 & 修饰符
#### `const`(只读限定符)
+
+`const` 表示对象在当前语义下不可修改。
+
```c
-const int a = 10;
-void print(const char* msg); // msg 不可修改
+const uint16_t table_size = 128;
```
+用途:
+
+- 防止误修改
+- 明确接口语义
+- 提升代码可读性
+
#### `volatile`(防止优化)
+
+`volatile` 告诉编译器:该变量的值可能在程序控制之外发生变化,每次访问都必须真正读写内存。
+
```c
-volatile int *reg = (int *)0x40021000; // 硬件寄存器访问
+volatile uint32_t *uart_sr = (uint32_t *)0x40011000;
+volatile int g_flag = 0;
```
+常见场景:
+
+- 外设寄存器
+- 中断与主循环共享变量
+- 多线程共享的状态位
+
+注意:
+
+- `volatile` 不能代替锁,也不能保证原子性
+
#### `static`(静态变量/内部链接)
+
+`static` 有两类常见用法:
+
+1. 修饰局部变量:只初始化一次,生命周期延长到整个程序运行期
+2. 修饰全局变量/函数:仅在当前文件内可见
+
```c
-static int count = 0; // 静态变量,函数调用间保留值
+static int calc_crc(uint8_t *buf, int len);
```
#### `extern`(外部变量声明)
+
+用于声明变量或函数定义在别处。
+
```c
-extern int global_var;
+extern uint8_t g_uart_rx_buf[128];
```
#### `register`(提示变量存放寄存器)
-```c
-register int speed;
-```
+
+历史上用于提示编译器尽量把变量放入寄存器,现在基本由优化器自行决定,现代代码中很少使用。
#### `auto`(默认局部变量)
-```c
-auto int a = 10; // 一般可省略 auto
-```
-### ✅ 内存存储类型与生命周期
+在 C 语言中,普通局部变量默认就是 `auto`,因此几乎不会显式书写。
+
+---
-| 存储类型 | 生命周期 | 作用域 | 关键字 |
-|------------|-----------------|-------------------|--------------|
-| 栈(stack) | 函数调用期间 | 局部变量 | auto |
-| 静态区 | 程序全程 | 局部/全局 | static |
-| 堆(heap) | 手动管理 | 全局 | malloc/free |
-| 寄存器 | 函数调用期间 | 局部 | register |
+### 内存存储类型与生命周期
-### ✅ 编译与调试基础
+从存储角度看,变量可以分为不同区域和不同生命周期。
+
+| 类型 | 存放位置 | 生命周期 | 典型示例 |
+| --- | --- | --- | --- |
+| 局部变量 | 栈 | 进入作用域到离开作用域 | 函数内临时变量 |
+| 静态变量 | 数据段/BSS | 程序整个运行期 | `static int count;` |
+| 全局变量 | 数据段/BSS | 程序整个运行期 | `int g_flag;` |
+| 动态内存 | 堆 | 手动申请到手动释放 | `malloc` 返回值 |
+| 字符串常量 | 常量区 | 程序整个运行期 | `"hello"` |
+
+理解这个表,能帮助你回答很多问题:
+
+- 为什么局部变量地址不能返回
+- 为什么 `static` 局部变量函数退出后还存在
+- 为什么 `malloc` 的内存不 `free` 会泄漏
+
+---
+
+### 编译与调试基础
#### C 编译四阶段(以 GCC 为例)
+
+一个 C 文件通常经历以下阶段:
+
+1. 预处理:展开头文件、宏、条件编译
+2. 编译:把 C 代码翻译为汇编
+3. 汇编:把汇编翻译为目标文件
+4. 链接:把多个目标文件和库文件合并成可执行文件
+
```bash
-gcc -E main.c -o main.i # 预处理
-gcc -S main.c -o main.s # 编译为汇编
-gcc -c main.c -o main.o # 汇编为目标文件
-gcc main.o -o main # 链接生成可执行文件
+gcc -E main.c -o main.i
+gcc -S main.c -o main.s
+gcc -c main.c -o main.o
+gcc main.o -o app
```
#### Makefile 示例
+
```makefile
CC = gcc
+CFLAGS = -Wall -Wextra -O2
TARGET = app
-OBJS = main.o utils.o
+OBJS = main.o uart.o
$(TARGET): $(OBJS)
- $(CC) -o $@ $^
+ $(CC) $(OBJS) -o $(TARGET)
%.o: %.c
- $(CC) -c $< -o $@
+ $(CC) $(CFLAGS) -c $< -o $@
clean:
- rm -f *.o $(TARGET)
+ rm -f $(OBJS) $(TARGET)
```
#### GCC 编译参数
-| 参数 | 含义 |
-|------|------|
-| `-Wall` | 开启所有警告 |
-| `-g` | 含调试信息 |
-| `-O2` | 优化等级 |
-| `-I` | 头文件路径 |
-| `-L`/`-l` | 库路径和链接 |
-| `-D` | 宏定义 |
-#### GDB 基础调试
+常见选项:
+
+- `-Wall -Wextra`:开启更多警告
+- `-O0/-O2/-Os`:优化等级
+- `-g`:生成调试信息
+- `-I`:头文件路径
+- `-D`:定义宏
+- `-c`:只编译不链接
+
+嵌入式场景下常见组合:
+
```bash
-gdb ./main
-(gdb) break main
-(gdb) run
-(gdb) next / step
-(gdb) print var
-(gdb) continue
+arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -O2 -g -Wall -ffunction-sections -fdata-sections
+```
+
+#### GDB 基础调试
+
+常用命令:
+
+```gdb
+break main
+run
+next
+step
+print variable
+backtrace
+continue
```
+建议掌握:
+
+- 断点设置与删除
+- 单步执行
+- 变量查看
+- 调用栈分析
+- 内存查看
+
#### 内联汇编
+
+当需要直接访问特殊指令或做极致性能优化时,可以使用内联汇编。
+
```c
-int result;
-__asm__ __volatile__ (
- "movl $5, %%eax;"
- "movl $3, %%ebx;"
- "addl %%ebx, %%eax;"
- "movl %%eax, %0;"
- : "=r"(result)
- :
- : "%eax", "%ebx"
-);
-printf("result = %d\n", result); // 输出 8
+__asm volatile ("nop");
```
+注意:
+
+- 仅在确有必要时使用
+- 需要结合目标架构手册理解
+- 可读性差,移植性较低
+
---
## 排序算法
+
+排序算法在嵌入式场景中常用于:
+
+- 采样值排序
+- 中值滤波
+- 优先级整理
+- 小规模数据处理
+
### 冒泡排序(Bubble Sort)
#### 原理:
-相邻元素两两比较,把最大的“冒”到最后。
+相邻元素两两比较,把较大的元素逐步“冒泡”到末尾。
#### 时间复杂度:
-* 最坏/平均:O(n²)
-* 最好:O(n)(加优化判断)
+- 平均:`O(n^2)`
+- 最好:`O(n)`(带提前结束优化)
#### 适用场景:
-数据量小、逻辑简单、嵌入式环境友好
+数据量很小、实现要求简单、教学和验证用例。
#### 示例代码:
@@ -749,110 +912,119 @@ void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
int swapped = 0;
for (int j = 0; j < n - i - 1; ++j) {
- if (arr[j] > arr[j+1]) {
- int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
+ if (arr[j] > arr[j + 1]) {
+ int temp = arr[j];
+ arr[j] = arr[j + 1];
+ arr[j + 1] = temp;
swapped = 1;
}
}
- if (!swapped) break; // 优化:已排序
+ if (!swapped) {
+ break;
+ }
}
}
```
----
-
### 选择排序(Selection Sort)
#### 原理:
-每轮从未排序区间中选择最小值放到前面。
+每轮选择未排序区间中的最小值,放到当前起始位置。
#### 时间复杂度:
-* 所有情况:O(n²)
+- 平均/最坏/最好:`O(n^2)`
#### 适用场景:
-嵌入式设备中内存访问代价高,交换少
+交换次数要求少、数据量小、实现简单的场景。
#### 示例代码:
```c
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; ++i) {
- int min = i;
+ int min_index = i;
for (int j = i + 1; j < n; ++j) {
- if (arr[j] < arr[min])
- min = j;
+ if (arr[j] < arr[min_index]) {
+ min_index = j;
+ }
}
- if (min != i) {
- int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
+ if (min_index != i) {
+ int temp = arr[i];
+ arr[i] = arr[min_index];
+ arr[min_index] = temp;
}
}
}
```
----
-
### 插入排序(Insertion Sort)
#### 原理:
-每次将一个元素插入到已排序部分的合适位置。
+把当前元素插入到前面已经有序的区间中。
#### 时间复杂度:
-* 最坏/平均:O(n²)
-* 最好:O(n)
+- 平均/最坏:`O(n^2)`
+- 最好:`O(n)`
#### 适用场景:
-数据量小、数据基本有序时表现好
+数据量小或数据基本有序时效果较好。
#### 示例代码:
```c
void insertion_sort(int arr[], int n) {
for (int i = 1; i < n; ++i) {
- int key = arr[i], j = i - 1;
+ int key = arr[i];
+ int j = i - 1;
while (j >= 0 && arr[j] > key) {
- arr[j+1] = arr[j];
+ arr[j + 1] = arr[j];
j--;
}
- arr[j+1] = key;
+ arr[j + 1] = key;
}
}
```
----
-
### 快速排序(Quick Sort)
#### 原理:
-分治法。选定基准,左边小于它,右边大于它。
+选一个基准值,把数组划分为左右两个区间,再递归排序。
#### 时间复杂度:
-* 最坏:O(n²)
-* 平均:O(n log n)
+- 平均:`O(n log n)`
+- 最坏:`O(n^2)`
#### 适用场景:
-高性能需求、数据量较大(慎用递归栈)
+一般性能较好,适合较大规模数据;但在栈空间有限的 MCU 上需要注意递归深度。
#### 示例代码:
```c
int partition(int arr[], int low, int high) {
- int pivot = arr[high], i = low - 1;
+ int pivot = arr[high];
+ int i = low - 1;
+
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
++i;
- int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
+ int temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
}
}
- int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
+
+ int temp = arr[i + 1];
+ arr[i + 1] = arr[high];
+ arr[high] = temp;
return i + 1;
}
@@ -865,83 +1037,110 @@ void quick_sort(int arr[], int low, int high) {
}
```
----
-
### 归并排序(Merge Sort)
#### 原理:
-分治法。将数组分成两半排序后合并。
+采用分治法,先拆分,再合并两个有序区间。
#### 时间复杂度:
-* 所有情况:O(n log n)
+- 平均/最坏/最好:`O(n log n)`
#### 适用场景:
-追求稳定排序、高精度处理、实时传感器数据等(需额外内存)
+需要稳定排序时较合适,但会额外占用内存。
#### 示例代码:
```c
-void merge(int arr[], int l, int m, int r) {
- int n1 = m-l+1, n2 = r-m;
- int L[n1], R[n2];
+void merge(int arr[], int left, int mid, int right) {
+ int n1 = mid - left + 1;
+ int n2 = right - mid;
- for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
- for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];
+ int L[n1];
+ int R[n2];
- int i = 0, j = 0, k = l;
- while (i < n1 && j < n2)
- arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
+ for (int i = 0; i < n1; ++i) {
+ L[i] = arr[left + i];
+ }
+ for (int j = 0; j < n2; ++j) {
+ R[j] = arr[mid + 1 + j];
+ }
+
+ int i = 0, j = 0, k = left;
+ while (i < n1 && j < n2) {
+ if (L[i] <= R[j]) {
+ arr[k++] = L[i++];
+ } else {
+ arr[k++] = R[j++];
+ }
+ }
- while (i < n1) arr[k++] = L[i++];
- while (j < n2) arr[k++] = R[j++];
+ while (i < n1) {
+ arr[k++] = L[i++];
+ }
+ while (j < n2) {
+ arr[k++] = R[j++];
+ }
}
-void merge_sort(int arr[], int l, int r) {
- if (l < r) {
- int m = (l + r) / 2;
- merge_sort(arr, l, m);
- merge_sort(arr, m+1, r);
- merge(arr, l, m, r);
+void merge_sort(int arr[], int left, int right) {
+ if (left < right) {
+ int mid = left + (right - left) / 2;
+ merge_sort(arr, left, mid);
+ merge_sort(arr, mid + 1, right);
+ merge(arr, left, mid, right);
}
}
```
----
-
### 堆排序(Heap Sort)
#### 原理:
-利用堆结构(大根堆),反复取出最大元素构造有序序列。
+先构建大根堆,再不断把堆顶元素交换到数组末尾。
#### 时间复杂度:
-* 所有情况:O(n log n)
+- 平均/最坏/最好:`O(n log n)`
#### 适用场景:
-嵌入式中对最值处理(最大温度等)、优先级调度
+需要较稳定的最坏时间复杂度,同时不希望额外申请大块内存。
#### 示例代码:
```c
void heapify(int arr[], int n, int i) {
- int largest = i, l = 2*i + 1, r = 2*i + 2;
- if (l < n && arr[l] > arr[largest]) largest = l;
- if (r < n && arr[r] > arr[largest]) largest = r;
+ int largest = i;
+ int left = 2 * i + 1;
+ int right = 2 * i + 2;
+
+ if (left < n && arr[left] > arr[largest]) {
+ largest = left;
+ }
+ if (right < n && arr[right] > arr[largest]) {
+ largest = right;
+ }
+
if (largest != i) {
- int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
+ int temp = arr[i];
+ arr[i] = arr[largest];
+ arr[largest] = temp;
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
- for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
- for (int i = n-1; i > 0; i--) {
- int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
+ for (int i = n / 2 - 1; i >= 0; --i) {
+ heapify(arr, n, i);
+ }
+
+ for (int i = n - 1; i > 0; --i) {
+ int temp = arr[0];
+ arr[0] = arr[i];
+ arr[i] = temp;
heapify(arr, i, 0);
}
}
@@ -949,14 +1148,23 @@ void heap_sort(int arr[], int n) {
---
-## 📌 总结对比表
+## 总结对比表
+
+| 排序算法 | 平均时间复杂度 | 空间复杂度 | 稳定性 | 适合场景 |
+| --- | --- | --- | --- | --- |
+| 冒泡排序 | `O(n^2)` | `O(1)` | 是 | 很小规模数据、教学演示 |
+| 选择排序 | `O(n^2)` | `O(1)` | 否 | 希望减少交换次数 |
+| 插入排序 | `O(n^2)` | `O(1)` | 是 | 数据基本有序、小规模数组 |
+| 快速排序 | `O(n log n)` | `O(log n)` | 否 | 大多数通用高性能场景 |
+| 归并排序 | `O(n log n)` | `O(n)` | 是 | 稳定排序要求高 |
+| 堆排序 | `O(n log n)` | `O(1)` | 否 | 内存受限且需控制最坏复杂度 |
+
+本章建议重点掌握以下内容:
-| 排序算法 | 时间复杂度(平均) | 空间复杂度 | 稳定性 | 嵌入式适用性 |
-| ---- | ---------- | -------- | --- | -------- |
-| 冒泡排序 | O(n²) | O(1) | ✅ | ✅(小数据) |
-| 选择排序 | O(n²) | O(1) | ❌ | ✅ |
-| 插入排序 | O(n²) | O(1) | ✅ | ✅(近似有序) |
-| 快速排序 | O(n log n) | O(log n) | ❌ | ⚠️(递归栈) |
-| 归并排序 | O(n log n) | O(n) | ✅ | ⚠️(额外内存) |
-| 堆排序 | O(n log n) | O(1) | ❌ | ✅ |
+- 变量、作用域、类型和生命周期的关系
+- 栈、堆、指针、数组之间的内存模型
+- `const`、`volatile`、`static`、`extern` 的工程语义
+- 位操作、结构体和固定宽度类型在嵌入式中的实际用途
+- 编译、链接、调试的基本流程
+如果这一章掌握扎实,后续学习寄存器、驱动、中断、RTOS 和 Linux 内核时会顺畅很多。
diff --git "a/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md" "b/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
index 860699b..d6241c3 100644
--- "a/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
+++ "b/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
@@ -2,9 +2,9 @@
---
-## 🔹 嵌入式系统概览
+## 嵌入式系统概览
-### 📌 嵌入式系统定义与特点
+### 嵌入式系统定义与特点
**定义**:
- 专用性:针对特定任务优化,如汽车 ABS 防抱死系统仅负责刹车控制。
@@ -19,7 +19,7 @@
---
-### 📌 系统构成(MCU、存储器、传感器、外设)
+### 系统构成(MCU、存储器、传感器、外设)
| 模块 | 功能说明 |
|------------|--------------------------------------|
@@ -60,7 +60,6 @@
- **内核位数**:8位(适合简单控制)、32位(主流)、64位(高性能应用)。
- **片上外设**:集成ADC、DAC、PWM等功能模块,减少外部芯片依赖。
-
### 存储器:程序与数据的载体
#### 1. **Flash存储器**
- **功能**:存储程序代码(固件),掉电不丢失。
@@ -82,7 +81,6 @@
- **EEPROM**:可擦写可编程只读存储器,适合存储少量关键参数(如设备ID)。
- **FRAM**:铁电随机存储器,读写速度快、寿命长(10^12次擦写),用于数据记录。
-
### 外设接口:与外部世界的桥梁
#### 1. **GPIO(通用输入输出)**
- **功能**:数字信号输入/输出(如控制LED、读取按键状态)。
@@ -110,7 +108,6 @@
- 通过占空比控制输出电压平均值,用于电机调速、LED调光。
- 频率范围:几Hz~MHz(如舵机控制需50Hz PWM)。
-
### 传感器:感知物理世界的窗口
#### 1. **常见类型**
- **环境传感器**:
@@ -128,7 +125,6 @@
- **数字接口**:I2C(如SHT30)、SPI(如ADXL345)。
- **模拟接口**:输出电压值,需通过MCU的ADC转换(如模拟光照传感器)。
-
### 通信模块:连接万物的纽带
#### 1. **短距离通信**
- **WiFi**:
@@ -153,7 +149,6 @@
- **Modbus**:
- 主从协议,支持RS-232/RS-485,广泛用于工业设备通信。
-
### 电源管理:续航与稳定性的保障
#### 1. **电源转换**
- **LDO(低压差线性稳压器)**:
@@ -195,7 +190,6 @@
- 平时MCU处于休眠,加速度计检测运动状态。
- 定时唤醒GPS模块采集位置数据,通过BLE上传手机。
-
### 开发与调试工具
#### 1. **硬件工具**
- **开发板**:STM32 Nucleo、Arduino、ESP32 DevKit。
@@ -220,9 +214,9 @@
---
-## 🔹 架构与启动流程
+## 架构与启动流程
-### 📌 Cortex-M 内核结构
+### Cortex-M 内核结构
- 32 位精简指令集(Thumb 指令集)
- 内建 NVIC(中断控制器)
@@ -231,7 +225,7 @@
---
-### 📌 启动文件 Startup.s
+### 启动文件 Startup.s
- 用汇编语言书写的启动文件,完成向量表定义、初始化堆栈、调用 `main()`。
@@ -245,7 +239,7 @@ Reset_Handler:
---
-### 📌 启动流程简要
+### 启动流程简要
1. MCU 上电 → 执行 `Reset_Handler`
2. 设置 SP(栈顶)
@@ -255,7 +249,7 @@ Reset_Handler:
---
-## 🔹 编译器与链接器
+## 编译器与链接器
### 嵌入式工具链详解
@@ -287,7 +281,6 @@ Reset_Handler:
- `arm-none-eabi-ld`:链接器。
- `arm-none-eabi-objcopy`:格式转换工具(如生成.bin/.hex文件)。
-
### 链接脚本(.ld)深入解析
#### 1. **核心作用**
- **内存分区**:定义Flash、RAM等存储器区域的起始地址和大小。
@@ -338,7 +331,6 @@ SECTIONS
} > FLASH
```
-
### STM32存储器布局详解
#### 1. **物理内存映射(以STM32F4为例)**
```
@@ -380,7 +372,6 @@ SECTIONS
GPIOA_MODER |= 0x01; // PA0设为输出模式
```
-
### 编译与链接流程
#### 1. **编译阶段**
```
@@ -405,7 +396,6 @@ SECTIONS
- 工具:ST-Link、J-Link、OpenOCD等。
- 流程:将.bin/.hex文件写入MCU的Flash起始地址(如0x08000000)。
-
### 常见问题与调试技巧
#### 1. **链接错误**
- **符号未定义**:
@@ -440,7 +430,6 @@ SECTIONS
.vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
```
-
### 面试高频问题
1. **.data和.bss的区别**:
- `.data`存储已初始化的全局变量,占用Flash和RAM;
diff --git "a/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md" "b/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
index a0d5714..8666236 100644
--- "a/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
+++ "b/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
@@ -1,12 +1,12 @@
-# 🟠 第三层:驱动开发与外设编程
+# 第三层:驱动开发与外设编程
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
以下从底层原理到实践应用进行深度扩展:
-## 🔹 寄存器级开发
-#### 📌 地址映射与寄存器偏移
+## 寄存器级开发
+#### 地址映射与寄存器偏移
- **总线架构**:
- AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
- 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
@@ -14,7 +14,7 @@
- 每个外设包含多个寄存器,通过基地址+偏移量访问。
- 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。
-#### 📌 位操作技巧
+#### 位操作技巧
- **原子操作宏**:
```c
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
@@ -28,9 +28,8 @@
GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
```
-
-### 🔹 通用外设驱动
-#### 📌 GPIO(通用输入输出)
+### 通用外设驱动
+#### GPIO(通用输入输出)
- **模式配置**:
- 输入模式:浮空输入、上拉输入、下拉输入、模拟输入。
- 输出模式:推挽输出、开漏输出(需外部上拉)。
@@ -49,7 +48,7 @@
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能NVIC中断
```
-#### 📌 UART/USART
+#### UART/USART
- **波特率计算**:
- 公式:`波特率 = 系统时钟 / (16 * USARTDIV)`
- 示例:系统时钟72MHz,波特率115200,则USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625。
@@ -66,7 +65,7 @@
}
```
-#### 📌 SPI(串行外设接口)
+#### SPI(串行外设接口)
- **模式配置**:
- 时钟极性(CPOL):0(空闲时SCLK为低)或1(空闲时SCLK为高)。
- 时钟相位(CPHA):0(第一个边沿采样)或1(第二个边沿采样)。
@@ -75,14 +74,14 @@
- 主模式:控制SCK时钟,负责发起通信。
- 从模式:接收SCK时钟,响应主设备请求。
-#### 📌 I2C(集成电路间总线)
+#### I2C(集成电路间总线)
- **寻址方式**:
- 7位地址:0x00~0x7F,其中0x00为广播地址。
- 10位地址:扩展寻址,用于特殊设备。
- **多主竞争解决**:
- 通过SDA线的电平检测实现总线仲裁,先检测到SDA线被拉低的主设备退出竞争。
-#### 📌 ADC(模拟-to-数字转换器)
+#### ADC(模拟-to-数字转换器)
- **采样时间配置**:
- 采样时间越长,转换结果越精确,但转换速度越慢。
- 示例:STM32F4的ADC采样时间可配置为3、15、28、56、84、112、144、480周期。
@@ -107,9 +106,8 @@
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
```
-
-### 🔹 复杂外设支持
-#### 📌 DMA 控制器
+### 复杂外设支持
+#### DMA 控制器
- **通道选择**:
- 每个DMA控制器包含多个通道,不同外设对应不同通道。
- 示例:USART1_RX对应DMA2通道5,USART1_TX对应DMA2通道4。
@@ -125,14 +123,14 @@
// ...其他配置
```
-#### 📌 看门狗(Watchdog)
+#### 看门狗(Watchdog)
- **独立看门狗(IWDG)**:
- 由专用低速时钟(LSI,约32kHz)驱动,即使主时钟故障仍能工作。
- 喂狗时间范围:典型值10ms~16s。
- **窗口看门狗(WWDG)**:
- 喂狗时间必须在窗口范围内(上限值~下限值),防止程序在异常状态下喂狗。
-#### 📌 CAN(控制器局域网)
+#### CAN(控制器局域网)
- **位时序配置**:
- 由同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)组成。
- 示例:波特率500kbps,系统时钟42MHz,位时序配置为:
@@ -149,9 +147,8 @@
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
```
-
-### 🔹 开发库 & 工具链
-#### 📌 STM32 HAL(硬件抽象层)
+### 开发库 & 工具链
+#### STM32 HAL(硬件抽象层)
- **HAL库架构**:
- 核心层:提供外设初始化、控制和状态检查函数。
- 回调函数:通过弱函数(weak)实现,用户可重写。
@@ -168,7 +165,7 @@
}
```
-#### 📌 STM32 LL(低层驱动)
+#### STM32 LL(低层驱动)
- **优势**:
- 代码体积更小,执行效率更高。
- 更接近寄存器操作,适合性能敏感场景。
@@ -180,7 +177,7 @@
| 执行效率 | 低 | 高 |
| 学习难度 | 低 | 高 |
-#### 📌 STM32CubeMX
+#### STM32CubeMX
- **时钟树配置**:
- 基于PLL(锁相环)生成系统时钟,需合理配置倍频系数和分频系数。
- 示例:配置系统时钟为180MHz:
@@ -190,8 +187,7 @@
- **中间件集成**:
- 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。
-
-### 🔹 实战技巧与常见问题
+### 实战技巧与常见问题
#### 1. **外设初始化流程**
1. 使能外设时钟。
2. 配置GPIO复用功能(如需要)。
@@ -224,7 +220,6 @@ void EXTI0_IRQHandler(void) {
- 检测SPI/I2C总线波形,验证通信时序。
- 检测PWM波形,验证占空比和频率。
-
### 六、面试高频问题
1. **HAL与LL库的选择标准**:
- 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
diff --git "a/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md" "b/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
index 10e5d7c..d512241 100644
--- "a/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
+++ "b/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
@@ -1,11 +1,11 @@
-# 🟣 第四层:实时操作系统(RTOS)
+# 第四层:实时操作系统(RTOS)
本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
---
-## 🔹 RTOS 基础概念
+## RTOS 基础概念
### 什么是 RTOS?
**RTOS**(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
@@ -19,7 +19,6 @@
- 高优先级任务可立即抢占低优先级任务。
- 示例:飞行控制系统中,传感器数据采集任务优先级高于显示任务。
-
### 常见 RTOS
- FreeRTOS(开源、广泛使用)
- RT-Thread(国产开源,图形化支持强)
@@ -37,15 +36,14 @@
**主流 RTOS 对比**
| RTOS | 开源 | 应用领域 | 特点 |
|------------|----------|--------------------------|----------------------------------------------|
-| FreeRTOS | ✅ | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
-| RT-Thread | ✅ | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
-| μC/OS | ⚠️ 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
-| VxWorks | ❌ | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
-
+| FreeRTOS | | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
+| RT-Thread | | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
+| μC/OS | 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
+| VxWorks | | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
---
-## 🔹 任务管理
+## 任务管理
### 任务创建与内存布局
```c
@@ -75,7 +73,6 @@ xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
或挂起API
```
-
### 任务优先级与调度算法
- 抢占式调度:
- 基于任务优先级,高优先级任务可立即抢占当前运行任务。
@@ -87,7 +84,7 @@ xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
---
-## 🔹 时间管理
+## 时间管理
### 任务延时实现
```c
@@ -125,7 +122,7 @@ void vTimerCallback(TimerHandle_t xTimer) {
---
-## 🔹 线程间通信
+## 线程间通信
### 队列(Queue)
- 特性:
@@ -253,7 +250,6 @@ if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
-
### 事件组(Event Group)
- 类似标志位,可用于多任务同步
```c
@@ -275,7 +271,7 @@ EventBits_t uxBits = xEventGroupWaitBits(
---
-## 🔹 资源管理
+## 资源管理
### 内存管理方式
#### 静态分配(推荐)
@@ -335,7 +331,6 @@ if (pvBuffer == NULL) {
}
```
-
### 临界区保护
- 关中断:
```c
@@ -358,7 +353,7 @@ xSemaphoreGive(xMutex); // 释放锁
---
-## 🔹 FreeRTOS 配置与移植
+## FreeRTOS 配置与移植
### 配置项(FreeRTOSConfig.h)
| 参数 | 描述 | 示例值 |
@@ -369,7 +364,6 @@ xSemaphoreGive(xMutex); // 释放锁
| `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) |
| `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) |
-
### 移植步骤
1. 提供 SysTick 定时器实现
2. 提供上下文切换代码(汇编)
@@ -405,7 +399,7 @@ PendSV_NoSave:
```
---
-## 🔹 RTOS 调试与性能分析
+## RTOS 调试与性能分析
### 调试工具与技术
- 任务状态查看:
@@ -449,7 +443,7 @@ UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
- 关键任务(如传感器采样)设高优先级,非关键任务(如显示更新)设低优先级。
---
-## 🔹 实践应用场景
+## 实践应用场景
- 多任务协同:传感器数据采集 + 通信模块处理
- 响应式控制:定时器 + 外部中断 + 优先级控制
diff --git a/05-EmbeddedLinux/README.md b/05-EmbeddedLinux/README.md
index 0396436..0c5714f 100644
--- a/05-EmbeddedLinux/README.md
+++ b/05-EmbeddedLinux/README.md
@@ -1,20 +1,20 @@
-# 🟢 第五层:嵌入式 Linux 开发基础
+# 第五层:嵌入式 Linux 开发基础
嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
---
-## 🔹 嵌入式 Linux 系统概览
+## 嵌入式 Linux 系统概览
-### 📌 嵌入式 Linux 特点
+### 嵌入式 Linux 特点
- 可裁剪、可定制、模块化强
- 支持多种架构(ARM、MIPS、RISC-V 等)
- 社区支持强大(开源内核、驱动丰富)
-### 📌 系统组成
+### 系统组成
```text
[Bootloader] → [Kernel] → [Root File System] → [User Application]
@@ -27,9 +27,9 @@
---
-## 🔹 启动流程详解
+## 启动流程详解
-### 📌 通用启动流程
+### 通用启动流程
```text
Power On →
@@ -42,7 +42,7 @@ Power On →
Shell / App
```
-### 📌 U-Boot(主流 Bootloader)
+### U-Boot(主流 Bootloader)
- 二阶段:SPL(初始化内存)+ U-Boot 本体
- 功能:串口输出、TFTP 下载、引导内核、环境变量配置等
@@ -55,15 +55,15 @@ bootz 0x80008000 - 0x83000000
---
-## 🔹 设备树(Device Tree)
+## 设备树(Device Tree)
-### 📌 基本概念
+### 基本概念
- 描述硬件资源的结构化信息
- 独立于内核源码,提高可移植性
- 文件类型:`.dts`(源文件)、`.dtsi`(包含文件)、`.dtb`(二进制)
-### 📌 示例结构
+### 示例结构
```dts
uart1: serial@40011000 {
@@ -74,7 +74,7 @@ uart1: serial@40011000 {
};
```
-### 📌 编译设备树
+### 编译设备树
```bash
make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
@@ -157,9 +157,9 @@ done
---
-## 🔹 Linux 驱动开发模型
+## Linux 驱动开发模型
-### 📌 驱动分层模型
+### 驱动分层模型
```text
[硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
@@ -169,7 +169,7 @@ done
- **设备(device)**:描述具体外设
- **驱动(driver)**:实现对设备的控制逻辑
-### 📌 字符设备驱动框架
+### 字符设备驱动框架
```c
struct file_operations fops = {
@@ -182,7 +182,7 @@ struct file_operations fops = {
int major = register_chrdev(0, "mydev", &fops);
```
-### 📌 平台驱动开发流程
+### 平台驱动开发流程
1. 定义 `platform_device`
2. 编写并注册 `platform_driver`
@@ -191,15 +191,15 @@ int major = register_chrdev(0, "mydev", &fops);
---
-## 🔹 根文件系统构建
+## 根文件系统构建
-### 📌 常见文件系统类型
+### 常见文件系统类型
- ext3/ext4:标准 Linux 文件系统
- squashfs:只读压缩文件系统,适合嵌入式
- initramfs:内存文件系统
-### 📌 文件系统布局(典型)
+### 文件系统布局(典型)
```
/
@@ -215,7 +215,7 @@ int major = register_chrdev(0, "mydev", &fops);
└── home/ → 用户主目录
```
-### 📌 构建方式
+### 构建方式
- BusyBox + 自制文件结构
- Buildroot:快速构建定制系统
@@ -223,9 +223,9 @@ int major = register_chrdev(0, "mydev", &fops);
---
-## 🔹 工具链与调试手段
+## 工具链与调试手段
-### 📌 交叉编译工具链
+### 交叉编译工具链
- gcc-arm-linux-gnueabi
- arm-none-eabi-gcc
@@ -234,7 +234,7 @@ int major = register_chrdev(0, "mydev", &fops);
export CROSS_COMPILE=arm-linux-
```
-### 📌 GDB 调试
+### GDB 调试
- GDB Server + Remote Debug
```bash
@@ -242,7 +242,7 @@ gdb-multiarch vmlinux
target remote :1234
```
-### 📌 常用调试工具
+### 常用调试工具
| 工具 | 用途 |
|-------------|----------------------------|
@@ -255,7 +255,7 @@ target remote :1234
---
-## 🔹 常见开发平台
+## 常见开发平台
| 平台 | 特点 |
|-------------|------------------------------|
@@ -266,7 +266,7 @@ target remote :1234
---
-### 🔹 嵌入式系统安全基础
+### 嵌入式系统安全基础
1. 威胁模型分析
- 物理攻击:
- 探针访问调试接口(JTAG/SWD)读取 Flash 内容。
@@ -293,7 +293,7 @@ target remote :1234
---
-### 🔹 安全启动(Secure Boot)
+### 安全启动(Secure Boot)
> 保证启动时加载的固件是可信的
@@ -356,7 +356,7 @@ HAL_MPU_ConfigRegion(&MPU_InitStruct);
---
-### 🔹 固件加密与防逆向
+### 固件加密与防逆向
1. **AES 加密固件**,防止泄露源码逻辑
@@ -384,10 +384,9 @@ HAL_MPU_ConfigRegion(&MPU_InitStruct);
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
-
---
-### 🔹 权限隔离与防护
+### 权限隔离与防护
1. MPU(内存保护单元)配置
```c
@@ -433,7 +432,7 @@ uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param
---
-### 🔹 Bootloader 开发建议
+### Bootloader 开发建议
- 通用功能:下载、校验、重启、回滚
- 支持双分区升级(Slot A / Slot B)
@@ -442,6 +441,6 @@ uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param
---
-## 🔚 小结
+## 小结
嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
diff --git a/06-NetworkIot/README.md b/06-NetworkIot/README.md
index 09c6d1b..363d316 100644
--- a/06-NetworkIot/README.md
+++ b/06-NetworkIot/README.md
@@ -1,6 +1,6 @@
-# 🟣 第六层:网络通信与物联网协议(Network & IoT)
+# 第六层:网络通信与物联网协议(Network & IoT)
本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
@@ -91,7 +91,6 @@ Socket 通信是构建大多数网络应用(如网页浏览、文件传输、
* **优点:** 跨网络通信能力强、支持复杂的网络应用、可靠性高(TCP)、适合大数据量传输。
* **缺点:** 相对复杂,需要理解网络协议栈;资源开销相对较大(TCP 连接维护开销)。
-
### 串口通信 vs Socket 通信:对比总结
| 特性 | 串口通信 (UART/RS-232) | Socket 通信 (TCP/UDP) |
@@ -104,7 +103,6 @@ Socket 通信是构建大多数网络应用(如网页浏览、文件传输、
| **复杂性** | **相对简单** | **相对复杂** (需理解网络编程和协议) |
| **应用场景** | 调试、嵌入式设备间通信、简单传感器连接 | 互联网应用、物联网云连接、复杂网络数据传输 |
-
---
## 无线通信协议
@@ -175,7 +173,6 @@ BLE 的核心是 **GATT (Generic Attribute Profile) 协议**,它定义了数
**MQTT (Message Queuing Telemetry Transport)** 是一种**轻量级、发布/订阅模式**的消息传输协议。它专为**资源受限的设备**(如物联网 IoT 设备)以及**低带宽、高延迟或不稳定网络**环境而设计。因其高效、可靠地传输少量数据的能力,MQTT 在物联网领域得到了广泛应用。
-
#### 1. 核心概念与架构
MQTT 协议由以下三个主要组件构成:
@@ -373,14 +370,13 @@ HTTPS = HTTP + TLS/SSL 加密
* **SHA-1**(160 位)
* **SHA-256**(256 位)
-
### CoAP / LwM2M
- 适合低功耗终端的简化协议,UDP 传输,可压缩
- 用于 NB-IoT、LwIP 等网络栈中
---
-### 🔹 安全通信实践
+### 安全通信实践
1. TLS 握手优化
- 预共享密钥(PSK)模式:
减少证书验证开销,适合资源受限设备。
@@ -414,10 +410,9 @@ int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
}
```
-
---
-### 🔹 安全测试
+### 安全测试
1. 固件逆向分析
- 工具链:
@@ -465,7 +460,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
| 网络层 | IP, ICMP, ARP | 地址与路由 |
| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
-
#### TCP 与 UDP 区别
| 特性 | TCP | UDP |
@@ -475,7 +469,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
-
#### 嵌入式 TCP/IP 协议栈组件
- **LwIP(Lightweight IP)**
@@ -488,7 +481,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
- 与 FreeRTOS 配套的 TCP/IP 协议栈
- **Nut/Net、CycloneTCP**:其他常用协议栈
-
#### 嵌入式 TCP/IP 通信流程(以 LwIP 为例)
1. **初始化网络接口**:配置 IP、MAC、网关
@@ -497,8 +489,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
4. **接收/发送数据**:`recv()`, `send()`
5. **关闭连接**:`close()`
-
-
#### 常用 API 示例(LwIP BSD socket)
```c
@@ -529,8 +519,6 @@ close(sock);
---
-
-
## 云平台接入 & OTA 实现
### 云平台对接
@@ -629,7 +617,7 @@ int main(void) {
}
}
```
-#### ✅ OTA 流程核心步骤
+#### OTA 流程核心步骤
1. 检查版本更新(HTTP/MQTT 下载 manifest)
2. 下载固件(二进制)
@@ -639,7 +627,7 @@ int main(void) {
6. Bootloader 引导进入新固件
7. 若失败则回滚(Fail-safe 机制)
-#### ✅ 常用升级协议
+#### 常用升级协议
- HTTP / HTTPS
- MQTT + Base64 二进制块传输
@@ -647,7 +635,7 @@ int main(void) {
---
-## ✅ 推荐学习顺序
+## 推荐学习顺序
1. 学习 UART 通信与基本网络 socket 原理
2. 掌握 Wi-Fi / BLE 开发流程(推荐 ESP32/nRF52)
@@ -656,7 +644,7 @@ int main(void) {
---
-## 📌 常见问题 FAQ
+## 常见问题 FAQ
| 问题 | 解答 |
|------|------|
diff --git a/07-Debug_Optimization/README.md b/07-Debug_Optimization/README.md
index ed1d256..007d6f0 100644
--- a/07-Debug_Optimization/README.md
+++ b/07-Debug_Optimization/README.md
@@ -1,11 +1,11 @@
-# ⚡ 第七层:调试与性能优化
+# 第七层:调试与性能优化
---
-## ✅ 常用调试工具
+## 常用调试工具
-### 🔹 JTAG / SWD 接口
+### JTAG / SWD 接口
- **JTAG**(Joint Test Action Group)标准调试接口,支持多设备级联。
- **SWD**(Serial Wire Debug)是 ARM Cortex 系列的简化调试协议,仅使用两根线(SWDIO, SWCLK),适用于资源受限设备。
@@ -33,9 +33,7 @@ HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
```
-
-
-### 🔹 GDB + OpenOCD 调试
+### GDB + OpenOCD 调试
- **GDB**:GNU 调试器,支持断点、单步、查看变量等操作。
- **OpenOCD**:Open On-Chip Debugger,用于连接 GDB 和硬件调试接口(如 ST-Link)。
@@ -78,7 +76,7 @@ arm-none-eabi-gdb path/to/firmware.elf
(gdb) p $r0 # 查看特定寄存器(如R0)
```
-### 🔹 逻辑分析仪 / 示波器
+### 逻辑分析仪 / 示波器
- **逻辑分析仪**:用于捕捉数字信号波形,分析通信协议(如 I2C, SPI)。
- 逻辑分析仪典型场景:
@@ -110,21 +108,21 @@ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
```
使用示波器测量:频率应为 1kHz,高电平时间 500μs(占空比 50%)。
-### 🔹 printf / 串口调试
+### printf / 串口调试
- 常用 `printf()` 输出信息到串口查看程序执行流程。
- 可与 RTT(Real Time Transfer)配合实现非阻塞调试输出。
-### 🔹 断点调试
+### 断点调试
- 在 IDE(如 STM32CubeIDE)中设置断点暂停程序运行,查看寄存器、内存、变量。
- 适合调试初始化流程、外设配置错误等问题。
---
-## ✅ 性能与功耗优化
+## 性能与功耗优化
-### 🔹 FreeRTOS Trace 与分析工具
+### FreeRTOS Trace 与分析工具
- 使用 FreeRTOS+Trace 工具(Percepio)记录任务切换、上下文切换、CPU 占用率。
- 可通过 `vTraceEnable()` 开启追踪。
@@ -148,8 +146,7 @@ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
```
-
-### 🔹 SystemView 分析工具
+### SystemView 分析工具
- SEGGER 提供的实时系统分析工具。
- 与 FreeRTOS 集成,通过 SWO 接口获取任务执行时间、事件追踪等信息。
@@ -158,12 +155,12 @@ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
- 上下文切换频率:过高表示任务调度不合理。
- 中断响应时间:从中断触发到 ISR 执行的时间差。
-### 🔹 STM32CubeMonitor
+### STM32CubeMonitor
- ST 官方提供的可视化变量监控与数据图示工具。
- 可用于实时观察寄存器值、ADC 曲线、温度、电压等参数。
-### 🔹 低功耗模式优化
+### 低功耗模式优化
#### Cortex-M 支持三种主要低功耗模式:
diff --git "a/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md" "b/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
index b68e6bc..ede9b75 100644
--- "a/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
+++ "b/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
@@ -1,8 +1,8 @@
-# 📦 第八层:项目实战与工具链
+# 第八层:项目实战与工具链
-## ✅ 工程管理
+## 工程管理
-### 🔹 Git 版本控制
+### Git 版本控制
#### 1. **Git 分支策略**
- **主干分支(main/master)**:
@@ -47,8 +47,7 @@ git push origin v1.0.0
git tag -l
```
-
-### 🔹 Makefile、CMake 构建工具
+### Makefile、CMake 构建工具
#### 1. **Makefile 基础**
- **简单示例**:
@@ -113,8 +112,7 @@ git tag -l
)
```
-
-### 🔹 Jenkins/GitHub Actions CI 流水线
+### Jenkins/GitHub Actions CI 流水线
#### 1. **GitHub Actions 配置**
- **编译与测试工作流**:
@@ -206,10 +204,9 @@ git tag -l
}
```
+## 项目实践
-## ✅ 项目实践
-
-### 🔹 嵌入式应用框架设计
+### 嵌入式应用框架设计
#### 1. **分层架构**
```
@@ -258,8 +255,7 @@ project/
└── Makefile # Makefile
```
-
-### 🔹 通用 BSP 构建
+### 通用 BSP 构建
#### 1. **设计原则**
- **硬件无关性**:上层代码不直接访问硬件寄存器。
@@ -362,8 +358,7 @@ void bsp_led_set(led_t led, led_state_t state) {
}
```
-
-### 🔹 模块化驱动结构
+### 模块化驱动结构
#### 1. **驱动分层**
- **硬件层**:直接操作寄存器的低级驱动。
@@ -458,8 +453,7 @@ const spi_interface_t* spi_get_interface(void) {
}
```
-
-### 🔹 OTA 升级方案设计
+### OTA 升级方案设计
#### 1. **双分区架构**
```
@@ -612,7 +606,6 @@ typedef struct {
**验证安装**:
打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
-
### STM32CubeIDE
**官网链接**:
@@ -626,7 +619,6 @@ typedef struct {
**验证安装**:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
-
### CLion
**官网链接**:
@@ -641,7 +633,6 @@ typedef struct {
**验证安装**:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
-
## 2. **调试工具**
### OpenOCD
@@ -669,7 +660,6 @@ typedef struct {
**验证安装**:
在终端中运行 `openocd --version`,若显示版本信息则安装成功。
-
### GDB
**官网链接**:
@@ -683,7 +673,6 @@ typedef struct {
**验证安装**:
在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
-
### ST-Link/V2
**官网链接**:
@@ -702,7 +691,6 @@ typedef struct {
**验证安装**:
在终端中运行 `st-info --version`,若显示版本信息则安装成功。
-
## 3. **静态代码分析**
### CppCheck
@@ -729,7 +717,6 @@ typedef struct {
**验证安装**:
在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
-
### Clang-Tidy
**官网链接**:
@@ -753,7 +740,6 @@ typedef struct {
**验证安装**:
在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
-
### SonarQube
**官网链接**:
@@ -770,7 +756,6 @@ typedef struct {
**验证安装**:
在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
-
## 4. **单元测试**
### Unity
@@ -788,7 +773,6 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
-
### CMock
**官网链接**:
@@ -804,7 +788,6 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
-
### Google Test
**官网链接**:
@@ -828,8 +811,7 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
-
-## 📚 资源汇总
+## 资源汇总
| **工具** | **官网链接** | **安装指南** |
|------------------|---------------------------------------------|-------------------------------------------|
diff --git a/09-2025_AI_on_MCU/README.md b/09-2025_AI_on_MCU/README.md
index 3a67e6b..154c52d 100644
--- a/09-2025_AI_on_MCU/README.md
+++ b/09-2025_AI_on_MCU/README.md
@@ -1,8 +1,8 @@
# 第九层:2025 新趋势
-## ✅ AI on MCU / Edge AI
+## AI on MCU / Edge AI
-### 🔹 TinyML / TensorFlow Lite Micro
+### TinyML / TensorFlow Lite Micro
#### 1. **概念与优势**
- **TinyML**:将机器学习模型部署到资源受限的微控制器(MCU)上,实现边缘智能。
@@ -69,8 +69,7 @@
| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
-
-### 🔹 STM32 AI 开发套件
+### STM32 AI 开发套件
#### 1. **硬件平台**
- **STM32H7系列**:高性能MCU,支持DSP和FPU,适合运行复杂AI模型。
@@ -91,8 +90,7 @@
- **模型优化**:使用STM32Cube.AI的量化工具将模型压缩至8位或更少。
- **内存管理**:优化模型和中间数据的内存布局,减少RAM占用。
-
-### 🔹 模型量化与部署
+### 模型量化与部署
#### 1. **量化技术**
- **权重量化**:将浮点权重转换为整数(通常8位或更少)。
@@ -107,8 +105,7 @@
| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
| 模型更新 | 设计OTA机制,支持模型动态更新 |
-
-### 🔹 AI + 外设驱动融合案例
+### AI + 外设驱动融合案例
#### 1. **智能传感器处理**
- **场景**:基于加速度计数据的活动识别。
@@ -152,10 +149,9 @@
2. 使用AI模型分析频谱特征,识别潜在故障。
3. 通过BLE将结果发送至云端。
+## 安全性
-## ✅ 安全性
-
-### 🔹 安全启动(Secure Boot)
+### 安全启动(Secure Boot)
#### 1. **原理与流程**
1. **硬件信任根**:
@@ -191,8 +187,7 @@
}
```
-
-### 🔹 TPM 安全芯片接入
+### TPM 安全芯片接入
#### 1. **TPM 2.0 概述**
- **功能**:
@@ -250,8 +245,7 @@
- **安全通信**:TPM生成和存储TLS密钥,保护通信数据。
- **设备身份认证**:基于TPM的唯一密钥实现设备身份识别。 `
-
-## 🚀 实战案例
+## 实战案例
### 1. **工业设备预测性维护**
- **需求**:基于振动传感器数据预测设备故障。
@@ -269,8 +263,7 @@
- 通过安全启动确保固件未被篡改。
- 使用TPM存储用户认证密钥。
-
-## 🔗 参考资源
+## 参考资源
1. **AI on MCU**:
- [TensorFlow Lite Micro](https://www.tensorflow.org/lite/microcontrollers)
@@ -286,5 +279,4 @@
- [STMicroelectronics AI Demo](https://www.st.com/en/evaluation-tools/stm32ai-discovery.html)
- [ESP32 TinyML Examples](https://github.com/tensorflow/tflite-micro-arduino-examples)
-
AI与安全是2025年嵌入式领域的两大核心趋势。通过将AI算法部署到边缘设备,可实现实时智能决策,同时降低网络带宽和云端计算成本。而安全性则是保障设备和数据可信的基础,从安全启动到加密通信,再到TPM硬件级保护,构建多层次安全防护体系。在实际项目中,需根据具体需求选择合适的AI模型和安全方案,平衡性能、功耗和安全性。
diff --git a/README.md b/README.md
index 5305456..1cac092 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,6 @@
-
### 概述
欢迎来到本项目,这里拥有**系统、全面**且贴近实战的2025年嵌入式软件开发学习路线和知识点总结。
涵盖包括**C语言、驱动开发、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链**等嵌入式软件开发所需知识点
@@ -372,8 +371,6 @@ void createArray() {
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
-
-
#### 指针变量的定义和使用
指针变量定义语法: `数据类型 * 变量名;`
@@ -415,8 +412,6 @@ int main() {
总结3:对指针变量解引用,可以操作指针指向的内存
-
-
#### 指针所占内存空间
提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?
@@ -453,7 +448,6 @@ int main() {
**注意:** 空指针指向的内存是不可以访问的
-
**示例1:空指针**
```cpp
@@ -472,8 +466,6 @@ int main() {
}
```
-
-
**野指针**:指针变量指向非法的内存空间
**示例2:野指针**
@@ -495,10 +487,8 @@ int main() {
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
-
#### const修饰指针
-
const修饰指针有三种情况
1. const修饰指针 --- 常量指针
@@ -506,7 +496,6 @@ const修饰指针有三种情况
1. const既修饰指针,又修饰常量
-
**示例:**
```cpp
@@ -520,7 +509,6 @@ int main() {
p1 = &b; //正确
//*p1 = 100; 报错
-
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
@@ -539,8 +527,6 @@ int main() {
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
-
-
#### 指针和数组
核心概念
- 数组本质:连续存储多个指针变量。
@@ -573,8 +559,6 @@ int main() {
}
```
-
-
#### 指针和函数
**作用:** 利用指针作函数参数,可以修改实参的值(和前边形参相反)
@@ -617,8 +601,6 @@ int main() {
总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递
-
-
#### 指针、数组、函数
- **函数指针声明**:`int (*fp)(int)` 表示指向返回 `int` 的函数的指针
- **函数指针数组**:用于策略模式或注册多个处理函数
@@ -627,8 +609,6 @@ int main() {
例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
-
-
**示例:**
```cpp
@@ -1266,7 +1246,6 @@ void heap_sort(int arr[], int n) {
- **内核位数**:8位(适合简单控制)、32位(主流)、64位(高性能应用)。
- **片上外设**:集成ADC、DAC、PWM等功能模块,减少外部芯片依赖。
-
### 存储器:程序与数据的载体
#### 1. **Flash存储器**
- **功能**:存储程序代码(固件),掉电不丢失。
@@ -1288,7 +1267,6 @@ void heap_sort(int arr[], int n) {
- **EEPROM**:可擦写可编程只读存储器,适合存储少量关键参数(如设备ID)。
- **FRAM**:铁电随机存储器,读写速度快、寿命长(10^12次擦写),用于数据记录。
-
### 外设接口:与外部世界的桥梁
#### 1. **GPIO(通用输入输出)**
- **功能**:数字信号输入/输出(如控制LED、读取按键状态)。
@@ -1316,7 +1294,6 @@ void heap_sort(int arr[], int n) {
- 通过占空比控制输出电压平均值,用于电机调速、LED调光。
- 频率范围:几Hz~MHz(如舵机控制需50Hz PWM)。
-
### 传感器:感知物理世界的窗口
#### 1. **常见类型**
- **环境传感器**:
@@ -1334,7 +1311,6 @@ void heap_sort(int arr[], int n) {
- **数字接口**:I2C(如SHT30)、SPI(如ADXL345)。
- **模拟接口**:输出电压值,需通过MCU的ADC转换(如模拟光照传感器)。
-
### 通信模块:连接万物的纽带
#### 1. **短距离通信**
- **WiFi**:
@@ -1359,7 +1335,6 @@ void heap_sort(int arr[], int n) {
- **Modbus**:
- 主从协议,支持RS-232/RS-485,广泛用于工业设备通信。
-
### 电源管理:续航与稳定性的保障
#### 1. **电源转换**
- **LDO(低压差线性稳压器)**:
@@ -1401,7 +1376,6 @@ void heap_sort(int arr[], int n) {
- 平时MCU处于休眠,加速度计检测运动状态。
- 定时唤醒GPS模块采集位置数据,通过BLE上传手机。
-
### 开发与调试工具
#### 1. **硬件工具**
- **开发板**:STM32 Nucleo、Arduino、ESP32 DevKit。
@@ -1493,7 +1467,6 @@ Reset_Handler:
- `arm-none-eabi-ld`:链接器。
- `arm-none-eabi-objcopy`:格式转换工具(如生成.bin/.hex文件)。
-
### 链接脚本(.ld)深入解析
#### 1. **核心作用**
- **内存分区**:定义Flash、RAM等存储器区域的起始地址和大小。
@@ -1544,7 +1517,6 @@ SECTIONS
} > FLASH
```
-
### STM32存储器布局详解
#### 1. **物理内存映射(以STM32F4为例)**
```
@@ -1586,7 +1558,6 @@ SECTIONS
GPIOA_MODER |= 0x01; // PA0设为输出模式
```
-
### 编译与链接流程
#### 1. **编译阶段**
```
@@ -1611,7 +1582,6 @@ SECTIONS
- 工具:ST-Link、J-Link、OpenOCD等。
- 流程:将.bin/.hex文件写入MCU的Flash起始地址(如0x08000000)。
-
### 常见问题与调试技巧
#### 1. **链接错误**
- **符号未定义**:
@@ -1646,7 +1616,6 @@ SECTIONS
.vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
```
-
### 面试高频问题
1. **.data和.bss的区别**:
- `.data`存储已初始化的全局变量,占用Flash和RAM;
@@ -1779,7 +1748,7 @@ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
---
---
-# 🟠 第三层:驱动开发与外设编程
+# 第三层:驱动开发与外设编程
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
以下从底层原理到实践应用进行深度扩展:
@@ -1807,7 +1776,6 @@ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
```
-
### 通用外设驱动
#### GPIO(通用输入输出)
- **模式配置**:
@@ -1886,7 +1854,6 @@ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
```
-
### 复杂外设支持
#### DMA 控制器
- **通道选择**:
@@ -1928,7 +1895,6 @@ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
```
-
### 开发库 & 工具链
#### STM32 HAL(硬件抽象层)
- **HAL库架构**:
@@ -1969,7 +1935,6 @@ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
- **中间件集成**:
- 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。
-
### 实战技巧与常见问题
#### 1. **外设初始化流程**
1. 使能外设时钟。
@@ -2003,7 +1968,6 @@ void EXTI0_IRQHandler(void) {
- 检测SPI/I2C总线波形,验证通信时序。
- 检测PWM波形,验证占空比和频率。
-
### 面试高频问题
1. **HAL与LL库的选择标准**:
- 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
@@ -2019,11 +1983,10 @@ void EXTI0_IRQHandler(void) {
- 优点:释放CPU资源,实现高速数据传输。
- 缺点:配置复杂,占用总线带宽。
-
---
---
-# 🟣 第四层:实时操作系统(RTOS)
+# 第四层:实时操作系统(RTOS)
本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
@@ -2043,7 +2006,6 @@ void EXTI0_IRQHandler(void) {
- 高优先级任务可立即抢占低优先级任务。
- 示例:飞行控制系统中,传感器数据采集任务优先级高于显示任务。
-
### 常见 RTOS
- FreeRTOS(开源、广泛使用)
- RT-Thread(国产开源,图形化支持强)
@@ -2066,7 +2028,6 @@ void EXTI0_IRQHandler(void) {
| μC/OS | 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
| VxWorks | | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
-
---
## 任务管理
@@ -2099,7 +2060,6 @@ xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
或挂起API
```
-
### 任务优先级与调度算法
- 抢占式调度:
- 基于任务优先级,高优先级任务可立即抢占当前运行任务。
@@ -2277,7 +2237,6 @@ if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
-
### 事件组(Event Group)
- 类似标志位,可用于多任务同步
```c
@@ -2359,7 +2318,6 @@ if (pvBuffer == NULL) {
}
```
-
### 临界区保护
- 关中断:
```c
@@ -2393,7 +2351,6 @@ xSemaphoreGive(xMutex); // 释放锁
| `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) |
| `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) |
-
### 移植步骤
1. 提供 SysTick 定时器实现
2. 提供上下文切换代码(汇编)
@@ -2482,7 +2439,7 @@ UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
---
---
-# 🟢 第五层:嵌入式 Linux 开发基础
+# 第五层:嵌入式 Linux 开发基础
嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
@@ -2866,7 +2823,6 @@ HAL_MPU_ConfigRegion(&MPU_InitStruct);
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
-
---
### 权限隔离与防护
@@ -2922,7 +2878,6 @@ uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param
- 防止电量中断、写失败后的砖机风险
- 可设置升级标志位(Upgrade Flag)
-
## 小结
嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
@@ -2930,7 +2885,7 @@ uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param
---
---
-# 🟣 第六层:网络通信与物联网协议(Network & IoT)
+# 第六层:网络通信与物联网协议(Network & IoT)
本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
@@ -3080,7 +3035,6 @@ HTTPS = HTTP + TLS/SSL 加密
* **SHA-1**(160 位)
* **SHA-256**(256 位)
-
### CoAP / LwM2M
- 适合低功耗终端的简化协议,UDP 传输,可压缩
- 用于 NB-IoT、LwIP 等网络栈中
@@ -3121,7 +3075,6 @@ int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
}
```
-
---
### 安全测试
@@ -3172,7 +3125,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
| 网络层 | IP, ICMP, ARP | 地址与路由 |
| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
-
#### TCP 与 UDP 区别
| 特性 | TCP | UDP |
@@ -3182,7 +3134,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
-
#### 嵌入式 TCP/IP 协议栈组件
- **LwIP(Lightweight IP)**
@@ -3195,7 +3146,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
- 与 FreeRTOS 配套的 TCP/IP 协议栈
- **Nut/Net、CycloneTCP**:其他常用协议栈
-
#### 嵌入式 TCP/IP 通信流程(以 LwIP 为例)
1. **初始化网络接口**:配置 IP、MAC、网关
@@ -3204,8 +3154,6 @@ bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
4. **接收/发送数据**:`recv()`, `send()`
5. **关闭连接**:`close()`
-
-
#### 常用 API 示例(LwIP BSD socket)
```c
@@ -3236,8 +3184,6 @@ close(sock);
---
-
-
## 云平台接入 & OTA 实现
### 云平台对接
@@ -3361,8 +3307,6 @@ int main(void) {
3. 理解 MQTT 协议与平台接入逻辑
4. 实践 OTA 升级流程,构建远程维护能力
-
-
## 常见问题 FAQ
| 问题 | 解答 |
@@ -3408,8 +3352,6 @@ HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
```
-
-
### GDB + OpenOCD 调试
- **GDB**:GNU 调试器,支持断点、单步、查看变量等操作。
@@ -3523,7 +3465,6 @@ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
```
-
### SystemView 分析工具
- SEGGER 提供的实时系统分析工具。
@@ -3722,7 +3663,6 @@ git push origin v1.0.0
git tag -l
```
-
### Makefile、CMake 构建工具
#### 1. **Makefile 基础**
@@ -3788,7 +3728,6 @@ git tag -l
)
```
-
### Jenkins/GitHub Actions CI 流水线
#### 1. **GitHub Actions 配置**
@@ -3881,7 +3820,6 @@ git tag -l
}
```
-
## 项目实践
### 嵌入式应用框架设计
@@ -3933,7 +3871,6 @@ project/
Makefile # Makefile
```
-
### 通用 BSP 构建
#### 1. **设计原则**
@@ -4037,7 +3974,6 @@ void bsp_led_set(led_t led, led_state_t state) {
}
```
-
### 模块化驱动结构
#### 1. **驱动分层**
@@ -4133,7 +4069,6 @@ const spi_interface_t* spi_get_interface(void) {
}
```
-
### OTA 升级方案设计
#### 1. **双分区架构**
@@ -4287,7 +4222,6 @@ typedef struct {
**验证安装**:
打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
-
### STM32CubeIDE
**官网链接**:
@@ -4301,7 +4235,6 @@ typedef struct {
**验证安装**:
启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
-
### CLion
**官网链接**:
@@ -4316,7 +4249,6 @@ typedef struct {
**验证安装**:
启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
-
## 2. **调试工具**
### OpenOCD
@@ -4344,7 +4276,6 @@ typedef struct {
**验证安装**:
在终端中运行 `openocd --version`,若显示版本信息则安装成功。
-
### GDB
**官网链接**:
@@ -4358,7 +4289,6 @@ typedef struct {
**验证安装**:
在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
-
### ST-Link/V2
**官网链接**:
@@ -4377,7 +4307,6 @@ typedef struct {
**验证安装**:
在终端中运行 `st-info --version`,若显示版本信息则安装成功。
-
## 3. **静态代码分析**
### CppCheck
@@ -4404,7 +4333,6 @@ typedef struct {
**验证安装**:
在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
-
### Clang-Tidy
**官网链接**:
@@ -4428,7 +4356,6 @@ typedef struct {
**验证安装**:
在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
-
### SonarQube
**官网链接**:
@@ -4445,7 +4372,6 @@ typedef struct {
**验证安装**:
在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
-
## 4. **单元测试**
### Unity
@@ -4463,7 +4389,6 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
-
### CMock
**官网链接**:
@@ -4479,7 +4404,6 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
-
### Google Test
**官网链接**:
@@ -4503,7 +4427,6 @@ typedef struct {
**验证安装**:
创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
-
## 资源汇总
| **工具** | **官网链接** | **安装指南** |
@@ -4595,7 +4518,6 @@ typedef struct {
| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
-
### STM32 AI 开发套件
#### 1. **硬件平台**
@@ -4617,7 +4539,6 @@ typedef struct {
- **模型优化**:使用STM32Cube.AI的量化工具将模型压缩至8位或更少。
- **内存管理**:优化模型和中间数据的内存布局,减少RAM占用。
-
### 模型量化与部署
#### 1. **量化技术**
@@ -4633,7 +4554,6 @@ typedef struct {
| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
| 模型更新 | 设计OTA机制,支持模型动态更新 |
-
### AI + 外设驱动融合案例
#### 1. **智能传感器处理**
@@ -4678,7 +4598,6 @@ typedef struct {
2. 使用AI模型分析频谱特征,识别潜在故障。
3. 通过BLE将结果发送至云端。
-
## 安全性
### 安全启动(Secure Boot)
@@ -4717,7 +4636,6 @@ typedef struct {
}
```
-
### TPM 安全芯片接入
#### 1. **TPM 2.0 概述**
@@ -4776,7 +4694,6 @@ typedef struct {
- **安全通信**:TPM生成和存储TLS密钥,保护通信数据。
- **设备身份认证**:基于TPM的唯一密钥实现设备身份识别。 `
-
## 实战案例
### 1. **工业设备预测性维护**
@@ -4795,7 +4712,6 @@ typedef struct {
- 通过安全启动确保固件未被篡改。
- 使用TPM存储用户认证密钥。
-
## 参考资源
1. **AI on MCU**:
@@ -4812,7 +4728,6 @@ typedef struct {
- [STMicroelectronics AI Demo](https://www.st.com/en/evaluation-tools/stm32ai-discovery.html)
- [ESP32 TinyML Examples](https://github.com/tensorflow/tflite-micro-arduino-examples)
-
---
---
# 免责声明
diff --git a/books/README.md b/books/README.md
index d201435..bd60be7 100644
--- a/books/README.md
+++ b/books/README.md
@@ -34,7 +34,6 @@
通过网盘分享的文件:电子书汇总.rar
链接: https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9 提取码: tex9
-
> 资源全面无套路,只求star一下本项目,让更多需要的人可以学习!
> 如果资源失效请第一时间 issue 或通过邮箱联系我!
@@ -52,4 +51,3 @@

-
diff --git a/scripts/docs-tools.mjs b/scripts/docs-tools.mjs
index bf224f9..319124c 100644
--- a/scripts/docs-tools.mjs
+++ b/scripts/docs-tools.mjs
@@ -284,22 +284,35 @@ function normalizeLink(fromFile, rawTarget) {
function processMarkdownFile(filePath, mode) {
const original = fs.readFileSync(filePath, 'utf8');
- let nextContent = original;
const brokenLinks = [];
+ let inFence = false;
+ const nextContent = original
+ .split(/\r?\n/)
+ .map((line) => {
+ if (/^\s*```/.test(line)) {
+ inFence = !inFence;
+ return line;
+ }
- nextContent = nextContent.replace(INTERNAL_LINK_RE, (fullMatch, text, target) => {
- const normalized = normalizeLink(filePath, target);
- if (normalized.broken) {
- brokenLinks.push(target);
- return fullMatch;
- }
+ if (inFence) {
+ return line;
+ }
- if (!normalized.changed) {
- return fullMatch;
- }
+ return line.replace(INTERNAL_LINK_RE, (fullMatch, text, target) => {
+ const normalized = normalizeLink(filePath, target);
+ if (normalized.broken) {
+ brokenLinks.push(target);
+ return fullMatch;
+ }
+
+ if (!normalized.changed) {
+ return fullMatch;
+ }
- return `[${text}](${normalized.nextTarget})`;
- });
+ return `[${text}](${normalized.nextTarget})`;
+ });
+ })
+ .join('\n');
const changed = nextContent !== original;
if (mode === 'fix' && changed) {
diff --git "a/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md" "b/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
index 26fdefe..96a5782 100644
--- "a/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
+++ "b/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
@@ -13,7 +13,6 @@
### (二)嵌入式开发典型场景
覆盖工业控制(人机界面HMI)、医疗设备(便携式诊断仪界面)、汽车电子(车载信息娱乐系统IVI)、智能家居(智能家电控制面板)、手持终端(工业PDA、POS机)等领域,成为嵌入式GUI开发首选框架。
-
## 二、环境搭建与工具链
### (一)开发环境搭建
1. **宿主环境**
@@ -37,7 +36,6 @@ make install
```
编译后,生成适配目标平台的Qt库,用于嵌入式应用开发。
-
## 三、核心机制与基础开发
### (一)信号与槽机制
1. **异步事件驱动**
@@ -67,7 +65,6 @@ connect(btn, &QPushButton::clicked, led, &LedControl::toggleLed);
2. **自定义控件**
针对嵌入式外设(如旋钮、仪表盘),继承`QWidget`/`QQuickItem`开发自定义控件,复用Qt绘图系统(`QPainter`/`QSGNode`)实现硬件状态可视化。
-
## 四、嵌入式功能开发模块
### (一)定时器(QTimer)
1. **硬件轮询**
@@ -133,7 +130,6 @@ signals:
QThreadPool::globalInstance()->start(new SerialWorker());
```
-
## 五、嵌入式外设交互开发
### (一)多媒体应用开发
1. **音频播放**
@@ -195,7 +191,6 @@ if (serial->open(QIODevice::ReadWrite)) {
}
```
-
## 六、进阶优化与平台适配
### (一)性能优化策略
1. **资源裁剪**
@@ -220,7 +215,6 @@ if (serial->open(QIODevice::ReadWrite)) {
- 使用`linuxdeployqt`工具打包Qt应用及依赖库,生成独立可执行包,适配不同嵌入式系统。
- 通过Yocto Project、Buildroot将Qt应用集成到系统镜像,实现出厂预装。
-
## 七、生态与学习资源
### 官方资源
- [Qt 嵌入式开发文档](https://doc.qt.io/qt - for - embedded - linux/index.html):涵盖框架架构、平台适配、性能优化等内容。
diff --git "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2301.md" "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2301.md"
index 5753b9a..947ac7c 100644
--- "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2301.md"
+++ "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2301.md"
@@ -140,7 +140,6 @@ Linux 支持 5 种文件类型,如下图所示:

-
# 20、Linux 的目录结构是怎样的?
> 这个问题,一般不会问。更多是实际使用时,需要知道。
@@ -209,7 +208,6 @@ Linux 通过 inode 节点表将文件的逻辑结构和物理结构进行转换
* 硬链接指向一个 inode 节点,而软链接则是创建一个新的 inode 节点。
* 删除硬链接文件,不会删除原文件,删除软链接文件,会把原文件删除。
-
# 23、RAID 是什么?
RAID 全称为独立磁盘冗余阵列(Redundant Array of Independent Disks),基本思想就是把多个相对便宜的硬盘组合起来,成为一个硬盘阵列组,使性能达到甚至超过一个价格昂贵、 容量巨大的硬盘。RAID 通常被用在服务器电脑上,使用完全相同的硬盘组成一个逻辑扇区,因此操作系统只会把它当做一个硬盘。
@@ -240,7 +238,6 @@ RAID 分为不同的等级,各个不同的等级均在数据可靠性及读写
* 8.1 设置 nginx_waf 模块防止 SQL 注入。
* 8.2 把 Web 服务使用 www 用户启动,更改网站目录的所有者和所属组为 www 。
-
# 25、什么叫 CC 攻击?什么叫 DDOS 攻击?
CC 攻击,主要是用来攻击页面的,模拟多个用户不停的对你的页面进行访问,从而使你的系统资源消耗殆尽。
@@ -283,7 +280,6 @@ SQL注入,是从正常的 WWW 端口访问,而且表面看起来跟一般的
> 用户变量由系统用户来生成和定义,变量的值可以通过命令 "echo $<变量名>" 查看。
-
# 29、Shell 脚本中 if 语法如何嵌套?
```c
@@ -457,7 +453,6 @@ AAA11111BBBAAA11111BBB11122222222222
在物理页面管理上实现了基于区的伙伴系统(zone based buddy system)。对不同区的内存 使用单独的伙伴系统(buddy system)管理,而且独立地监控空闲页。
相应接口`alloc_pages(gfp_mask, order)`,`_ _get_free_pages(gfp_mask, order)`等。
-
# 40、Linux 虚拟文件系统的关键数据结构有哪些?(至少写出四个)
* struct super_block
@@ -465,7 +460,6 @@ AAA11111BBBAAA11111BBB11122222222222
* struct fil
* struct dentry
-
# 41、对文件或设备的操作函数保存在那个数据结构中?
struct file_operations
@@ -605,7 +599,6 @@ Linux系统内核,shell,文件系统和应用程序四部分组成
2)硬链接指向一个i节点,而软链接则是创建一个新的i节点
3)删除硬链接文件,不会删除原文件,删除软链接文件,会把原文件删除
-
# 62、如何规划一台Linux主机,步骤是怎样?
1、确定机器是做什么用的,比如是做 WEB 、DB、还是游戏服务器。
diff --git "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2302.md" "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2302.md"
index c335783..4e413fe 100644
--- "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2302.md"
+++ "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/Linux\351\235\242\350\257\225\351\242\2302.md"
@@ -65,7 +65,6 @@ $ chmod -R u+r directory 递归地给 directory 目录下所有文件和子目
使用哪一个命令可以查看自己文件系统的磁盘空间配额呢?
-
### 答案:
使用命令repquota 能够显示出一个文件系统的配额信息
@@ -186,7 +185,6 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
### 答案:
一般都是使用 & 在命令结尾来让程序自动运行。(命令后可以不追加空格)
-
## 问题十九:
利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进程的信息?
@@ -202,12 +200,10 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
哪个命令专门用来查看后台任务?
-
### 答案:
`job -l`
-
## 问题二十一:
把后台任务调到前台执行使用什么命令?把停下的后台任务在后台执行起来用什么命令?
@@ -223,7 +219,6 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
终止进程用什么命令? 带什么参数?
-
### 答案:
kill [-s <信息名称或编号>][程序] 或 kill [-l <信息编号>]
@@ -236,7 +231,6 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
怎么查看系统支持的所有信号?
-
### 答案:
`kill -l`
@@ -245,7 +239,6 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
搜索文件用什么命令? 格式是怎么样的?
-
### 答案:
find <指定目录> <指定条件> <指定动作>
@@ -274,17 +267,14 @@ Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用
使用什么命令查看用过的命令列表?
-
### 答案:
`history`
-
## 问题二十七:
使用什么命令查看磁盘使用空间? 空闲空间呢?
-
### 答案:
df -hl
@@ -304,7 +294,6 @@ Filesystem Size Used Avail Use% Mounted on /dev/hda2 45G 19G 24G 44% /
使用什么命令查看 ip 地址及接口信息?
-
### 答案:
ifconfig
@@ -313,7 +302,6 @@ ifconfig
查看各类环境变量用什么命令?
-
### 答案:
查看所有 env
@@ -323,7 +311,6 @@ ifconfig
通过什么命令指定命令提示符?
-
### 答案:
\u:显示当前用户账号
@@ -356,7 +343,6 @@ ifconfig
查找命令的可执行文件是去哪查找的? 怎么对其进行设置及添加?
-
### 答案:
whereis [-bfmsu][-B <目录>...][-M <目录>...][-S <目录>...][文件...]
@@ -384,7 +370,6 @@ which 只能查可执行文件
whereis 只能查二进制文件、说明文档,源文件等
-
## 问题三十四:
怎么对命令进行取别名?
@@ -421,7 +406,6 @@ daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
当你需要给命令绑定一个宏或者按键的时候,应该怎么做呢?
-
### 答案:
可以使用bind命令,bind可以很方便地在shell中实现宏或按键的绑定。
@@ -444,9 +428,7 @@ daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
如果一个linux新手想要知道当前系统支持的所有命令的列表,他需要怎么做?
-
-###
-### 答案:
+### ### 答案:
使用命令compgen -c,可以打印出所有支持的命令列表。
@@ -494,7 +476,6 @@ daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
如果你的助手想要打印出当前的目录栈,你会建议他怎么做?
-
### 答案:
使用Linux 命令dirs可以将当前的目录栈打印出来。
@@ -511,7 +492,6 @@ daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
你的系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正在运行的进程移除呢?
-
### 答案:
使用linux命令 ’disown -r ’可以将所有正在运行的进程移除。
@@ -522,7 +502,6 @@ daemon /bin/sh 搜索/etc/passwd 有 root 关键字的所有行
bash shell 中的hash 命令有什么作用?
-
### 答案:
linux命令’hash’管理着一个内置的哈希表,记录了已执行过的命令的完整路径, 用该命令可以打印出你所使用过的命令以及执行的次数。
@@ -541,7 +520,6 @@ linux命令’hash’管理着一个内置的哈希表,记录了已执行过
哪一个bash内置命令能够进行数学运算。
-
### 答案:
bash shell 的内置命令let 可以进行整型数的数学运算。
@@ -559,7 +537,6 @@ linux命令’hash’管理着一个内置的哈希表,记录了已执行过
怎样一页一页地查看一个大文件的内容呢?
-
### 答案:
通过管道将命令”cat file_name.txt” 和 ’more’ 连接在一起可以实现这个需要.
@@ -572,7 +549,6 @@ linux命令’hash’管理着一个内置的哈希表,记录了已执行过
数据字典属于哪一个用户的?
-
### 答案:
数据字典是属于’SYS’用户的,用户‘SYS’ 和 ’SYSEM’是由系统默认自动创建的
@@ -583,7 +559,6 @@ linux命令’hash’管理着一个内置的哈希表,记录了已执行过
怎样查看一个linux命令的概要与用法?假设你在/bin目录中偶然看到一个你从没见过的的命令,怎样才能知道它的作用和用法呢?
-
### 答案:
使用命令whatis 可以先出显示出这个命令的用法简要,比如,你可以使用whatis zcat 去查看‘zcat’的介绍以及使用简要。
diff --git "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md" "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
index fc3e5ff..0da492b 100644
--- "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
+++ "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
@@ -2,7 +2,6 @@

-
---
或扫描二维码
diff --git "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230.md" "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230.md"
index 8da4fc3..1c4306d 100644
--- "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230.md"
+++ "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\216\237\347\220\206\351\235\242\350\257\225\351\242\230.md"
@@ -53,7 +53,7 @@ HTTP 是超文本传输协议,也就是HyperText Transfer Protocol。它可以
### 超文本
HTTP 传输的内容是「超文本」 我们先来理解「文本」,在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等,在 HTTP 眼里这些都算做「文本」。再来理解「超文本」,它就是超越了普通文本的文本,它是文字、图片、视频等的混合体最关键有超链接,能从一个超文本跳转到另外一个超文本。HTML 就是最常见的超文本了,它本身只是纯文字文件,但内部用很多标签定义了图片、视频等的链接,在经过浏览器的解释,呈现给我们的就是一个文字、有画面的网页了。OK,经过了对 HTTP 里这三个名词的详细解释,就可以给出比「超文本传输协议」这七个字更准确更有技术含量的答案:HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」
-⚠️注意: HTTP 不是用于从互联网服务器传输超文本到本地浏览器的协议,也可以是服务器到服务器,所以采用两点之间的描述会更准确
+注意: HTTP 不是用于从互联网服务器传输超文本到本地浏览器的协议,也可以是服务器到服务器,所以采用两点之间的描述会更准确
# 3.HTTP 的特点?HTTP 有哪些缺点?
@@ -89,7 +89,7 @@ GET /home HTTP/1.1
```
HTTP/1.1 200 OK
```
-响应报文的起始行也叫做状态行。由 http版本、状态码和原因 三部分组成。⚠️注意:在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循 ABNF 语法规范
+响应报文的起始行也叫做状态行。由 http版本、状态码和原因 三部分组成。注意:在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循 ABNF 语法规范
### 头部
展示一下请求头和响应头在报文中的位置:
@@ -104,7 +104,6 @@ HTTP/1.1 200 OK
### 实体
就是具体的数据了,也就是 body 部分。请求报文对应请求体, 响应报文对应响应体。
-
# 5.如何理解 HTTP 的请求方法?
http/1.1 规定了以下请求方法(注意,都是大写):
@@ -124,7 +123,6 @@ http/1.1 规定了以下请求方法(注意,都是大写):
* Content-Encoding
* Content-Type
-
# 7.对于定长和不定长的数据,HTTP 是怎么传输的?
### 定长包体
对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度。我们用一个nodejs服务器来模拟一下:
@@ -243,10 +241,6 @@ HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 146515
-
-
-
-
$ curl -I http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg
$ curl -H "Range: bytes=0-10" http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg -v
@@ -333,7 +327,6 @@ Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
-
--00000010101
Content-Type: text/plain
Content-Range: bytes 0-9/96
@@ -399,7 +392,6 @@ data2
值得一提的是,multipart/form-data 格式最大的特点在于:每一个表单元素都是独立的资源表述。另外,你可能在写业务的过程中,并没有注意到其中还有boundary的存在,如果你打开抓包工具,确实可以看到不同的表单元素被拆分开了,之所以在平时感觉不到,是以为浏览器和 HTTP 给你封装了这一系列操作。而且,在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。
-
# 10.如何理解 HTTP 代理?
我们知道在 HTTP 是基于请求-响应模型的协议,一般由客户端发请求,服务器来进行响应。当然,也有特殊情况,就是代理服务器的情况。引入代理之后,作为代理的服务器相当于一个中间人的角色,对于客户端而言,表现为服务器进行响应;而对于源服务器,表现为客户端发起请求,具有双重身份。那代理服务器到底是用来做什么的呢?
@@ -440,7 +432,6 @@ GET / HTTP/1.1
```
这样就可以解决X-Forwarded-For带来的问题了
-
# 11.说说 HTTP1.1 相比 HTTP1.0 提高了什么性能?
### HTTP1.1 相比 HTTP1.0 性能上的改进:
* 使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
@@ -485,7 +476,6 @@ HTTP2 是可以在一个连接中并发多个请求或回应,而不用按照
5. 服务器推送
HTTP2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务不再是被动地响应,也可以主动向客户端发送消息。举例来说,在浏览器刚请求 HTML 的时候,就提前把可能会用到的 JS、CSS 文件等静态资源主动发给客户端,减少延时的等待,也就是服务器推送(Server Push,也叫 Cache Push)
-
# 13.HTTP2 有哪些缺陷?HTTP3 做了哪些优化?
HTTP2 主要的问题在于,多个 HTTP 请求在复用一个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求的。所以一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
@@ -515,7 +505,6 @@ HTTPS 要建立一个连接,要花费 6 次交互,先是建立三次握手
* HTTP 的端口号是 80,HTTPS 的端口号是 443。
* HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的
-
# 15.HTTPS 解决了 HTTP 的哪些问题?
HTTP 由于是明文传输,所以安全上存在以下三个风险:
* 窃听风险,比如通信链路上可以获取通信内容,用户号容易没。
@@ -530,7 +519,6 @@ HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决
* 身份证书:证明淘宝是真的淘宝网
可见,只要自身不做「恶」,SSL/TLS 协议是能保证通信是安全的
-
# 16.HTTPS 是如何解决上面的三个风险的?
混合加密的方式实现信息的机密性,解决了窃听的风险。
@@ -560,7 +548,6 @@ HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决
通过数字证书的方式保证服务器公钥的身份,解决冒充的风险
-
# 17.HTTPS 是如何建立连接的?其间交互了什么?
SSL/TLS 协议基本流程:
1.TCP 三次同步握手
@@ -628,7 +615,6 @@ TCP 三次握手和四次挥手也是面试题的热门考点,它们分别对
经过若干秒后,小红也说完了,小红说,我说完了,现在可以挂断了
小明收到消息后,又等了若干时间后,挂断了电话。
-
# 20.说说TCP传输的三次握手四次挥手策略
### 三次握手
@@ -668,7 +654,6 @@ HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。
* JWT 的 Cookie 信息存储在客户端,而不是服务端内存中。也就是说,JWT 直接本地进行验证就可以,验证完毕后,这个 Token 就会在 Session 中随请求一起发送到服务器,通过这种方式,可以节省服务器资源,并且 token 可以进行多次验证。
* JWT 支持跨域认证,Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是常说的跨域认证。
-
# 22.OSI与TCP/IP各层的结构与功能,都有哪些协议?

@@ -682,7 +667,6 @@ HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。
-
### 运输层
运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
@@ -758,7 +742,6 @@ TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快

-
# 28.HTTP长连接,短连接
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
From 7c69b2a00c9234c227e3da18c46c54f5fdd71cda Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Sat, 4 Apr 2026 03:36:11 +0000
Subject: [PATCH 07/13] Simplify homepage: remove verbose descriptions and
streamline layout
Agent-Logs-Url: https://github.com/liueggy/Embed_book/sessions/3b083a51-b0de-44de-9e59-dcce2f85d951
Co-authored-by: liueggy <161451396+liueggy@users.noreply.github.com>
---
index.md | 82 +++++++-------------------------------------------------
1 file changed, 10 insertions(+), 72 deletions(-)
diff --git a/index.md b/index.md
index 5309334..e4dc90c 100644
--- a/index.md
+++ b/index.md
@@ -1,123 +1,61 @@
---
layout: home
-title: 2025 嵌入式软件开发图谱
+title: 嵌入式软件开发图谱
titleTemplate: false
hero:
- name: 2025 嵌入式软件开发图谱
- text: 结构化的嵌入式软件学习与查阅入口
- tagline: 基于原始 Markdown 知识库整理,按学习路径、专题方向和资料类型提供更清晰的阅读入口。
+ name: 嵌入式软件开发图谱
+ text: 系统化学习路径与技术参考
actions:
- theme: brand
- text: 查看学习地图
- link: /study-map
- - theme: alt
- text: 进入主线内容
+ text: 开始学习
link: /01-C语言基础与进阶/
+ - theme: alt
+ text: 学习地图
+ link: /study-map
---
-
- 当前首页定位为文档索引页,重点是帮助你更快找到章节,而不是展示博客式内容。
-
-
-## 阅读方式
-
-如果你是第一次系统学习,建议按学习顺序进入;如果你是带着具体问题来查资料,可以直接从专题或资源入口进入。
-
-
-
-## 主线内容
+## 核心课程
-## 扩展专题
+## 专题方向
-
-## 资料与补充
-
-
-
-## 附图
-
-
-
-
-
-
From 21ee8e6b99596bbc8fc99b59155edd1ac62724c6 Mon Sep 17 00:00:00 2001
From: liueggy <3157487230@qq.com>
Date: Sat, 4 Apr 2026 11:28:08 +0800
Subject: [PATCH 08/13] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=8E=E7=BB=AD?=
=?UTF-8?q?=E7=AB=A0=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../README.md" | 787 +++++++--------
.../README.md" | 554 ++++++-----
.../README.md" | 574 +++++------
05-EmbeddedLinux/README.md | 595 ++++++------
06-NetworkIot/README.md | 738 +++++---------
07-Debug_Optimization/README.md | 379 +++-----
.../README.md" | 907 +++---------------
09-2025_AI_on_MCU/README.md | 374 +++-----
README.md | 17 +-
books/README.md | 71 +-
.../README.md" | 376 ++++----
.../README.md" | 40 +-
12 files changed, 2059 insertions(+), 3353 deletions(-)
diff --git "a/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md" "b/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
index d6241c3..34057bb 100644
--- "a/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
+++ "b/02-\345\265\214\345\205\245\345\274\217\347\263\273\347\273\237\345\237\272\347\241\200\347\237\245\350\257\206/README.md"
@@ -1,216 +1,219 @@
# 第二层:嵌入式系统基础知识
+这一章用于建立嵌入式系统的整体认知。它回答的是“嵌入式系统由什么组成、如何启动、如何调试、如何理解芯片手册与工具链”这些根问题。
+
+建议学习目标:
+
+- 建立 MCU、存储器、外设、传感器、通信模块之间的系统视角。
+- 理解从上电、启动到应用运行的基本链路。
+- 能读懂基础链接脚本、启动文件和芯片数据手册中的关键字段。
+- 对后续驱动开发、RTOS 和 Embedded Linux 学习形成背景认知。
+
+阅读建议:先看系统构成与架构,再看启动流程、工具链和数据手册阅读方法。
+
---
## 嵌入式系统概览
### 嵌入式系统定义与特点
-**定义**:
-- 专用性:针对特定任务优化,如汽车 ABS 防抱死系统仅负责刹车控制。
-- 嵌入性:隐藏于设备内部,用户通常意识不到其存在(如微波炉中的控制系统)。
-- 计算系统:包含硬件(处理器、传感器)和软件(固件、驱动)。
+嵌入式系统是为特定任务设计的专用计算系统,通常集成在设备内部,直接服务于控制、采集、通信、显示或执行等功能。
-**特点**:
- - 资源受限(低功耗、内存小)
- - 实时性要求高
- - 高可靠性与稳定性
- - 通常运行裸机或 RTOS
+典型特征:
----
+- 专用性强:面向单一或有限场景优化,而不是做通用计算。
+- 资源受限:CPU 主频、内存、存储空间和功耗通常受到严格限制。
+- 强调实时性:很多系统要求在确定时间内完成响应。
+- 高可靠性:工业、汽车、医疗等场景对稳定性要求极高。
+- 软硬件耦合紧密:代码常直接与寄存器、外设和总线交互。
+
+嵌入式系统并不总是“小而简单”。从 8 位 MCU 到运行 Linux 的多核 SoC,都属于嵌入式系统范畴,只是复杂度和工程方法不同。
### 系统构成(MCU、存储器、传感器、外设)
-| 模块 | 功能说明 |
-|------------|--------------------------------------|
-| MCU | 核心控制器,如 ARM Cortex-M |
-| Flash/SRAM | 存储程序代码与运行数据 |
-| 外设 | GPIO、UART、SPI、I2C、ADC、PWM 等 |
-| 传感器 | 温湿度、光照、姿态等 |
-| 通信模块 | WiFi、BLE、LoRa、CAN、NB-IoT 等 |
-| 电源管理 | 电池、LDO、DC-DC,支持低功耗模式 |
+一个典型嵌入式系统通常由以下模块组成:
-```
-+--------------------------+
-| MCU |
-| +----------------------+ |
-| | Flash / RAM | |
-| | GPIO / UART / ADC | |
-| +----------------------+ |
-+-----------|--------------+
- |
- +------+------+
- | 外设 / 传感器 |
- +-------------+
-```
+| 模块 | 作用 |
+| --- | --- |
+| MCU / MPU / SoC | 运算、控制和调度核心 |
+| Flash / EEPROM / NAND / NOR | 程序和持久化数据存储 |
+| SRAM / DDR | 运行时数据和缓冲区 |
+| GPIO / UART / SPI / I2C / ADC / PWM | 与外部设备交互 |
+| 传感器 | 采集环境、姿态、位置、电流等数据 |
+| 通信模块 | Wi-Fi、BLE、LoRa、CAN、以太网、4G/5G 等 |
+| 电源管理 | 稳压、充电、低功耗切换和电池管理 |
-嵌入式系统的硬件构成是一个有机整体,各模块协同工作实现特定功能。以下从核心组件到通信架构进行深度解析:
+可以把它理解为三层结构:
+
+1. 计算与控制核心
+2. 数据采集与通信接口
+3. 电源与系统支撑部分
----
### MCU(微控制器):嵌入式系统的心脏
-#### 1. **核心功能**
-- **运算与控制**:执行程序指令,处理传感器数据,控制外设。
-- **典型架构**:
- - **ARM Cortex-M**:主流低功耗MCU(如STM32、Nordic nRF系列)。
- - **RISC-V**:开源架构,灵活定制(如SiFive、平头哥玄铁系列)。
- - **8051/AVR**:传统8位MCU,低成本(如Arduino Uno基于ATmega328P)。
-
-#### 2. **关键参数**
-- **主频**:决定运算速度(如STM32F103主频72MHz,STM32H7主频480MHz)。
-- **内核位数**:8位(适合简单控制)、32位(主流)、64位(高性能应用)。
-- **片上外设**:集成ADC、DAC、PWM等功能模块,减少外部芯片依赖。
+
+MCU 负责执行程序、处理中断、访问外设和调度系统行为。
+
+常见架构:
+
+- ARM Cortex-M:当前 MCU 主流,常见于 STM32、GD32、nRF、NXP 等系列。
+- RISC-V:开源指令集,近年来快速增长。
+- AVR / 8051:传统 8 位 MCU,适合低成本或教学场景。
+
+选择 MCU 时常看的参数:
+
+- 主频和内核类型
+- Flash / SRAM 容量
+- 外设数量和种类
+- 功耗水平
+- 封装、成本和供货情况
+- 工具链和社区支持
+
+对于初学者,最重要的不是记住某个型号,而是理解:MCU 的差异主要体现在“算力、存储、外设和功耗”四个维度。
### 存储器:程序与数据的载体
-#### 1. **Flash存储器**
-- **功能**:存储程序代码(固件),掉电不丢失。
-- **分类**:
- - **NOR Flash**:读取速度快,支持XIP(就地执行),适合代码存储。
- - **NAND Flash**:容量大、成本低,适合存储大量数据(如SSD、SD卡)。
-- **典型应用**:
- - MCU内置Flash(如STM32F407含1MB Flash)。
- - 外部SPI Flash(如W25Q系列,用于存储文件系统或配置参数)。
-
-#### 2. **SRAM(静态随机存取存储器)**
-- **功能**:运行时数据存储(如变量、堆栈)。
-- **特点**:速度快(纳秒级访问),但成本高、容量小。
-- **容量配置**:
- - 小型MCU:几KB~几十KB(如Arduino Uno含2KB SRAM)。
- - 高性能MCU:数百KB~MB级(如STM32H7含1MB SRAM)。
-
-#### 3. **其他存储类型**
-- **EEPROM**:可擦写可编程只读存储器,适合存储少量关键参数(如设备ID)。
-- **FRAM**:铁电随机存储器,读写速度快、寿命长(10^12次擦写),用于数据记录。
+
+嵌入式系统中常见的存储器包括以下几类:
+
+#### 1. Flash
+
+- 通常用于存放程序代码和常量数据
+- 掉电不丢失
+- 可分为片内 Flash 和外部 Flash
+
+#### 2. SRAM
+
+- 用于运行时变量、栈、堆和缓冲区
+- 速度快,但容量通常有限
+- 掉电后数据丢失
+
+#### 3. EEPROM / FRAM
+
+- 适合存放参数、校准值、设备 ID 等少量关键数据
+- 擦写寿命通常比 Flash 更适合做参数存储
+
+#### 4. 外部存储
+
+- NAND Flash、eMMC、SD 卡等,适合较大容量场景
+- 常见于 Embedded Linux 或带文件系统的设备
+
+工程上要重点理解三件事:
+
+- 程序通常放在哪
+- 变量和栈通常放在哪
+- 参数和日志适合放在哪
### 外设接口:与外部世界的桥梁
-#### 1. **GPIO(通用输入输出)**
-- **功能**:数字信号输入/输出(如控制LED、读取按键状态)。
-- **特性**:
- - 可配置上拉/下拉电阻。
- - 支持中断触发(如外部按键按下时唤醒MCU)。
-
-#### 2. **通信接口**
-| **接口** | **特点** | **典型应用** |
-|----------|--------------------------|----------------------------------|
-| **UART** | 全双工,异步,2线(TX/RX) | 调试信息输出、与模块通信(如GPS) |
-| **SPI** | 全双工,同步,4线(SCK/MOSI/MISO/CS) | 高速数据传输(如OLED屏幕) |
-| **I2C** | 半双工,同步,2线(SCL/SDA) | 多设备通信(如连接多个传感器) |
-| **CAN** | 差分信号,抗干扰强,多主模式 | 汽车电子(如ECU间通信) |
-
-#### 3. **模拟接口**
-- **ADC(模拟-to-数字转换器)**:
- - 将模拟信号(如电压、电流)转换为数字值。
- - 精度通常为10~16位(如STM32的12位ADC,分辨率4096级)。
-- **DAC(数字-to-模拟转换器)**:
- - 将数字值转换为模拟电压输出(如音频信号生成)。
-
-#### 4. **定时控制**
-- **PWM(脉冲宽度调制)**:
- - 通过占空比控制输出电压平均值,用于电机调速、LED调光。
- - 频率范围:几Hz~MHz(如舵机控制需50Hz PWM)。
+
+外设接口决定了 MCU 如何连接传感器、执行器和其他芯片。
+
+常见接口:
+
+| 接口 | 特点 | 典型用途 |
+| --- | --- | --- |
+| GPIO | 最基础的数字输入输出 | 按键、LED、中断输入 |
+| UART / USART | 点对点异步串口 | 调试输出、模块通信 |
+| SPI | 全双工、速度快 | 显示屏、Flash、高速传感器 |
+| I2C | 两线总线、便于挂多个设备 | 温湿度、IMU、RTC |
+| ADC | 模拟量采样 | 电压、电流、传感器输出 |
+| PWM | 占空比控制 | 电机、蜂鸣器、LED 调光 |
+| CAN / RS-485 / Ethernet | 工业与网络通信 | 车载、工控、联网设备 |
+
+学习外设时建议始终带着这几个问题:
+
+- 时钟从哪里来
+- 引脚如何复用
+- 数据如何收发
+- 中断或 DMA 怎样配合
+- 出错时如何定位
### 传感器:感知物理世界的窗口
-#### 1. **常见类型**
-- **环境传感器**:
- - **温湿度**:DHT22、SHT30(精度±0.3°C)。
- - **光照**:BH1750(测量范围1~65535 lux)。
- - **气压**:BMP280(海拔测量误差±1m)。
-- **运动传感器**:
- - **加速度计**:ADXL345(检测倾斜、振动)。
- - **陀螺仪**:MPU6050(测量角速度,用于姿态解算)。
-- **其他**:
- - **气体传感器**:MQ-2(检测烟雾、液化气)。
- - **红外传感器**:HC-SR501(人体感应)。
-
-#### 2. **接口方式**
-- **数字接口**:I2C(如SHT30)、SPI(如ADXL345)。
-- **模拟接口**:输出电压值,需通过MCU的ADC转换(如模拟光照传感器)。
+
+传感器把外部物理量转换成可处理的电信号或数字数据。
+
+常见类型:
+
+- 环境类:温湿度、气压、光照、空气质量
+- 运动类:加速度计、陀螺仪、磁力计
+- 电气类:电压、电流、功率、霍尔传感器
+- 人机类:触摸、红外、人体存在检测
+
+接入方式一般分为两类:
+
+- 数字接口:I2C、SPI、UART
+- 模拟接口:通过 ADC 采样
+
+工程重点不只是“接上能读”,还包括:
+
+- 供电与噪声
+- 采样频率
+- 校准
+- 滤波和异常值处理
### 通信模块:连接万物的纽带
-#### 1. **短距离通信**
-- **WiFi**:
- - 标准:802.11b/g/n(如ESP8266、ESP32)。
- - 应用:智能家居(如智能插座)、数据上传至云端。
-- **BLE(蓝牙低功耗)**:
- - 传输距离:10~100m,功耗极低(如Nordic nRF52系列)。
- - 应用:可穿戴设备(如心率带)、Beacon定位。
-
-#### 2. **中长距离通信**
-- **LoRa**:
- - 扩频技术,传输距离5~15km,低功耗(如Semtech SX1278)。
- - 应用:物联网广域覆盖(如智能抄表)。
-- **NB-IoT**:
- - 蜂窝网络,覆盖全国,功耗极低(如Quectel BC660K)。
- - 应用:远程监控(如井盖状态监测)。
-
-#### 3. **工业总线**
-- **CAN总线**:
- - 传输速率:500kbps~1Mbps,抗干扰强。
- - 应用:汽车电子(如车身控制模块)、工业自动化。
-- **Modbus**:
- - 主从协议,支持RS-232/RS-485,广泛用于工业设备通信。
+
+通信模块决定设备如何与外部系统交换数据。
+
+按距离和用途可粗略分为:
+
+- 短距离:UART、SPI、I2C、USB
+- 局域无线:Wi-Fi、BLE
+- 广域低功耗:LoRa、NB-IoT
+- 工业网络:CAN、RS-485、Modbus
+- 高带宽网络:以太网、4G/5G
+
+选择通信方式时主要看:
+
+- 数据量大小
+- 实时性要求
+- 传输距离
+- 功耗预算
+- 成本和部署环境
### 电源管理:续航与稳定性的保障
-#### 1. **电源转换**
-- **LDO(低压差线性稳压器)**:
- - 优点:电路简单,输出纹波小(如AMS1117-3.3)。
- - 缺点:效率低(输入输出压差越大,效率越低)。
-- **DC-DC转换器**:
- - 升压/降压,效率高(可达90%以上,如TPS62160)。
- - 适合电池供电设备(如充电宝)。
-
-#### 2. **低功耗设计**
-- **休眠模式**:
- - MCU进入休眠,关闭非必要外设,仅保留唤醒源(如RTC时钟)。
- - 典型功耗:STM32L4系列休眠电流低至0.5μA。
-- **动态电压调整**:
- - 根据工作负载动态降低MCU电压(如ARM Cortex-M4的FlexPowerControl)。
----
+电源管理不仅关系到续航,还直接影响系统稳定性。
+
+典型关注点:
+
+- 电源输入范围
+- 稳压方式:LDO 还是 DC-DC
+- 峰值电流和压降
+- 唤醒源和低功耗模式
+- 充电管理和电池保护
+
+很多“软件问题”本质上是电源问题,例如:
+
+- 上电不稳定
+- 通信时随机复位
+- 电机启动导致掉电
+- 休眠功耗异常
+
### 系统集成与典型架构
-#### 1. **最小系统**
-- MCU + 晶振 + 复位电路 + 电源。
-- 示例:STM32最小系统板(核心板)。
-#### 2. **扩展架构**
-```
- ┌─────────────────────────────────────────┐
- │ 应用层 │
- │ (用户逻辑:如温湿度采集、数据处理) │
- ├─────────────────────────────────────────┤
- │ 驱动层 │
- │ (传感器驱动、通信协议栈、外设控制) │
- ├─────────────────────────────────────────┤
- │ 硬件层 │
- │ MCU ── 存储器 ── 外设 ── 传感器 ── 通信 │
- └─────────────────────────────────────────┘
+一个常见嵌入式系统通常可抽象为三层:
+
+```text
+应用层 -> 业务逻辑、状态机、控制策略
+驱动层 -> 外设访问、协议封装、中断处理
+硬件层 -> MCU、存储器、传感器、执行器、电源
```
-#### 3. **低功耗设计案例**
-- **智能手环**:
- - 平时MCU处于休眠,加速度计检测运动状态。
- - 定时唤醒GPS模块采集位置数据,通过BLE上传手机。
+在复杂项目中,还会加入:
-### 开发与调试工具
-#### 1. **硬件工具**
-- **开发板**:STM32 Nucleo、Arduino、ESP32 DevKit。
-- **调试器**:ST-Link、J-Link(用于程序下载和调试)。
-- **逻辑分析仪**:Saleae Logic(分析通信协议波形)。
+- BSP(板级支持包)
+- 中间件(文件系统、网络协议栈、GUI、RTOS)
+- 生产诊断和升级机制
-#### 2. **软件工具**
-- **IDE**:Keil MDK、STM32CubeIDE、Arduino IDE。
-- **驱动配置**:STM32CubeMX(自动生成初始化代码)。
-- **调试工具**:OpenOCD(开源调试协议)、GDB(调试器)。
+### 开发与调试工具
----
-### 面试高频问题
-1. **SPI与I2C的区别**:
- - SPI:高速(可达数十Mbps),4线,点对点;I2C:低速(标准100kbps),2线,支持多设备。
+常见工具可以分为四类:
-2. **如何选择合适的通信协议**:
- - 短距离高速:SPI;多设备低速:I2C;长距离抗干扰:CAN;广域低功耗:LoRa/NB-IoT。
+1. 开发环境:Keil、STM32CubeIDE、VS Code、CLion
+2. 配置工具:STM32CubeMX、PinMux、设备树工具
+3. 下载调试:ST-Link、J-Link、OpenOCD、GDB
+4. 硬件测量:逻辑分析仪、示波器、电流表
-3. **低功耗设计的关键策略**:
- - 休眠模式、动态电压调整、关闭非必要外设、使用低功耗通信协议(如BLE)。
+学习时不要把工具当成知识本身,而要关注它们分别解决什么问题。
---
@@ -218,344 +221,230 @@
### Cortex-M 内核结构
-- 32 位精简指令集(Thumb 指令集)
-- 内建 NVIC(中断控制器)
-- 支持两种堆栈:MSP(主栈)和 PSP(进程栈)
-- 寄存器组:R0-R15、LR、PC、xPSR
+Cortex-M 是最常见的 MCU 内核系列之一。理解其核心结构有助于理解中断、异常和启动流程。
----
+关键组成:
+
+- 通用寄存器:R0-R12
+- 栈指针:MSP / PSP
+- 链接寄存器:LR
+- 程序计数器:PC
+- 程序状态寄存器:xPSR
+- NVIC:中断控制器
+- SysTick:系统节拍定时器
+
+对应用开发最重要的是:
+
+- 中断进入时栈如何保存现场
+- 异常向量如何跳转
+- 主栈和进程栈的差异
### 启动文件 Startup.s
-- 用汇编语言书写的启动文件,完成向量表定义、初始化堆栈、调用 `main()`。
+启动文件通常由汇编编写,负责完成上电后的最早期初始化。
+
+典型职责:
+
+- 设置初始栈顶
+- 定义中断向量表
+- 调用系统初始化函数
+- 清零 `.bss`
+- 拷贝 `.data`
+- 最终跳转到 `main`
+
+示例:
```asm
Reset_Handler:
- LDR R0, =_estack ; 设置栈顶地址
+ LDR R0, =_estack
MOV SP, R0
- BL SystemInit ; 时钟初始化
- BL main ; 跳转到主函数
+ BL SystemInit
+ BL main
```
----
-
### 启动流程简要
-1. MCU 上电 → 执行 `Reset_Handler`
-2. 设置 SP(栈顶)
-3. 初始化 `.data` 和 `.bss` 段
-4. 调用 `SystemInit()`(通常配置系统时钟)
-5. 跳转执行用户 `main()` 函数
+一个 MCU 上电后的典型启动顺序如下:
+
+1. 上电或复位
+2. CPU 从固定启动地址取指
+3. 读取向量表中的栈顶与复位入口
+4. 执行 `Reset_Handler`
+5. 初始化内存段和系统时钟
+6. 调用 `main`
+
+如果没有理解这条链路,后续学习 Bootloader、RAM 启动、中断重定向时会很吃力。
---
-## 编译器与链接器
-
-### 嵌入式工具链详解
-
-#### 1. **Keil MDK(Microcontroller Development Kit)**
-- **特点**:
- - 商业软件,支持ARM Cortex-M/R/A全系列处理器。
- - 集成μVision IDE、ARM编译器、调试器,界面友好。
- - 针对STM32等芯片提供Device Family Pack(DFP),简化外设配置。
-- **应用场景**:
- - 企业级产品开发(如医疗设备、工业控制)。
- - 需高效调试功能(如硬件断点、实时变量监控)。
-
-#### 2. **IAR EWARM(Embedded Workbench for ARM)**
-- **特点**:
- - 编译效率高,生成代码体积比GCC小10%-20%。
- - 调试器支持高级功能(如指令级调试、功耗分析)。
- - 跨平台支持(Windows、Linux、Mac)。
-- **应用场景**:
- - 对代码体积敏感的场景(如MCU Flash空间有限)。
- - 汽车电子(符合ISO 26262功能安全标准)。
-
-#### 3. **GCC (arm-none-eabi)**
-- **特点**:
- - 开源免费,基于GNU工具链(GCC、GDB、Binutils)。
- - 跨平台支持,适合Linux开发者。
- - 可通过命令行(CLI)集成到自动化构建流程(如Makefile、CMake)。
-- **典型工具**:
- - `arm-none-eabi-gcc`:编译器。
- - `arm-none-eabi-ld`:链接器。
- - `arm-none-eabi-objcopy`:格式转换工具(如生成.bin/.hex文件)。
-
-### 链接脚本(.ld)深入解析
-#### 1. **核心作用**
-- **内存分区**:定义Flash、RAM等存储器区域的起始地址和大小。
-- **段分配**:将代码段(.text)、数据段(.data)、BSS段(.bss)等映射到指定内存区域。
-- **地址对齐**:确保关键数据(如中断向量表)位于特定地址。
-
-#### 2. **MEMORY 区块解析**
+## 编译器与链接器
+
+### 常用嵌入式工具链
+
+嵌入式开发常用的是交叉编译工具链,即“在 PC 上编译,给目标芯片运行”。
+
+常见工具链:
+
+- `arm-none-eabi-gcc`
+- Keil ARM Compiler
+- IAR EWARM
+- RISC-V GCC Toolchain
+
+工具链通常包含:
+
+- 编译器
+- 汇编器
+- 链接器
+- 头文件和运行时库
+- 调试工具
+
+### 链接脚本(ld)示例
+
+链接脚本用于告诉链接器:程序的代码段、数据段、栈和堆应放到哪段存储器里。
+
+典型片段:
+
```ld
MEMORY
{
- FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K // 只读,可执行
- RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K // 可读可写可执行
+ FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
+ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
```
-- **属性说明**:
- - `r`:可读,`w`:可写,`x`:可执行。
- - `ORIGIN`:起始地址,`LENGTH`:大小。
-#### 3. **SECTIONS 区块解析**
-```ld
-SECTIONS
-{
- .text : { *(.text) } > FLASH // 代码段放入Flash
- .data : { *(.data) } > RAM AT > FLASH // 已初始化数据运行时在RAM,加载时在Flash
- .bss : { *(.bss) } > RAM // 未初始化数据放入RAM
- .stack : { . = . + 0x1000; } > RAM // 栈空间(1KB)
- .heap : { . = . + 0x2000; } > RAM AT > RAM // 堆空间(2KB)
-}
-```
-- **关键段说明**:
- - `.text`:存储程序代码(如函数体)。
- - `.data`:存储已初始化的全局变量(如`int a = 10;`)。
- - `.bss`:存储未初始化的全局变量(如`int b;`),运行前自动清零。
- - `.stack`:栈空间,用于局部变量和函数调用。
- - `.heap`:堆空间,用于动态内存分配(如`malloc`)。
-
-#### 4. **特殊用法**
-- **自定义段**:
- ```ld
- .mysection : { KEEP(*(.mysection)) } > RAM // 保留特定段,不被链接器优化
- ```
-- **指定中断向量表位置**:
- ```ld
- .isr_vector : {
- . = ALIGN(4);
- KEEP(*(.isr_vector)) // 中断向量表必须位于Flash起始地址
- . = ALIGN(4);
- } > FLASH
- ```
-
-### STM32存储器布局详解
-#### 1. **物理内存映射(以STM32F4为例)**
-```
-地址范围 大小 描述
-0x00000000-0x1FFFFFFF 512MB 代码区(可映射到Flash/SRAM/系统内存)
-0x20000000-0x2001FFFF 128KB SRAM(运行时数据)
-0x40000000-0x5FFFFFFF 512MB 外设寄存器(APB/AHB总线)
-0xE0000000-0xE00FFFFF 1MB 系统控制空间(NVIC、SysTick等)
-```
+它直接决定了:
-#### 2. **Flash区域详解**
-- **起始地址**:0x08000000(实际代码从此处开始)。
-- **典型布局**:
- ```
- 0x08000000-0x08000100 中断向量表
- 0x08000100-0x08080000 程序代码(.text)
- 0x08080000-0x08088000 已初始化数据(.data加载时位置)
- ```
-
-#### 3. **RAM区域详解**
-- **起始地址**:0x20000000。
-- **典型布局**:
- ```
- 0x20000000-0x20000100 已初始化数据(.data运行时位置)
- 0x20000100-0x20000200 未初始化数据(.bss)
- 0x20000200-0x20001200 栈空间(向下增长)
- 0x20001200-0x20010000 堆空间(向上增长)
- ```
-
-#### 4. **外设映射区(0x40000000起)**
-- **GPIO控制器**:0x40020000-0x40025000(如GPIOA基址0x40020000)。
-- **USART1**:0x40011000-0x40011400。
-- **定时器(TIM2)**:0x40000000-0x40000400。
-- **访问示例**:
- ```c
- #define GPIOA_BASE 0x40020000
- #define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00)) // 模式寄存器
-
- GPIOA_MODER |= 0x01; // PA0设为输出模式
- ```
-
-### 编译与链接流程
-#### 1. **编译阶段**
-```
-源代码(.c/.cpp) → 预处理器 → 编译器 → 汇编代码(.s) → 汇编器 → 目标文件(.o)
-```
-- **关键步骤**:
- - 预处理器处理`#include`、`#define`等指令。
- - 编译器将代码转换为汇编语言。
- - 汇编器生成机器码(目标文件)。
+- 程序是否能放进 Flash
+- `.data` / `.bss` 是否落在正确 RAM
+- 栈和堆是否有足够空间
-#### 2. **链接阶段**
-```
-多个目标文件(.o) + 库文件(.a/.lib) → 链接器 → 可执行文件(.elf) → 格式转换器 → 固件文件(.bin/.hex)
+### 存储器布局图(STM32 示例)
+
+一个常见 STM32 工程中的布局可以粗略理解为:
+
+```text
+Flash:
+ [向量表][代码段 .text][只读数据 .rodata]
+
+RAM:
+ [.data][.bss][heap][stack]
```
-- **链接器工作**:
- 1. 合并所有目标文件的段(如.text、.data)。
- 2. 解析符号引用(如函数调用、全局变量)。
- 3. 根据链接脚本分配地址。
- 4. 生成最终可执行文件。
-
-#### 3. **烧录阶段**
-- 工具:ST-Link、J-Link、OpenOCD等。
-- 流程:将.bin/.hex文件写入MCU的Flash起始地址(如0x08000000)。
-
-### 常见问题与调试技巧
-#### 1. **链接错误**
-- **符号未定义**:
- - 原因:调用未实现的函数或使用未定义的变量。
- - 解决:检查函数名拼写,确保目标文件包含该符号。
-- **内存溢出**:
- - 原因:代码或数据量超过Flash/RAM大小。
- - 解决:优化代码,减少全局变量,或更换更大容量的MCU。
-
-#### 2. **调试工具**
-- **反汇编工具**:
- ```bash
- arm-none-eabi-objdump -d main.elf # 生成反汇编代码
- ```
-- **查看内存分布**:
- ```bash
- arm-none-eabi-size main.elf # 显示各段大小
- ```
- 输出示例:
- ```
- text data bss dec hex filename
- 1234 56 789 2079 81F main.elf
- ```
-
-#### 3. **链接脚本调试技巧**
-- 添加自定义段:
- ```ld
- .debug_info : { *(.debug_info) } > RAM // 将调试信息放入RAM
- ```
-- 使用`KEEP`防止符号被优化:
- ```ld
- .vectors : { KEEP(*(.vectors)) } > FLASH // 保留中断向量表
- ```
-
-### 面试高频问题
-1. **.data和.bss的区别**:
- - `.data`存储已初始化的全局变量,占用Flash和RAM;
- - `.bss`存储未初始化的全局变量,仅占用RAM(运行前自动清零)。
-
-2. **如何减小代码体积**:
- - 使用IAR等优化能力更强的编译器。
- - 移除无用代码(如未使用的函数)。
- - 压缩常量数据(如图片、字体)。
-
-3. **链接脚本中AT关键字的作用**:
- - `AT`指定段的加载地址,如`.data > RAM AT > FLASH`表示:
- - 运行时在RAM(0x20000000),但加载时从Flash(0x08080000)复制到RAM。
+
+开发时经常遇到的错误:
+
+- Flash 溢出
+- RAM 溢出
+- 栈过小导致 HardFault
+- 链接地址与实际启动地址不匹配
---
-### 芯片数据手册阅读方法
+## 芯片数据手册阅读方法
-#### 为什么重要?
+### 典型结构(以 STM32 为例)
-芯片数据手册(Datasheet)和参考手册(Reference Manual)是开发嵌入式系统时的核心参考材料,了解外设功能、寄存器地址、时钟结构、中断号、引脚复用等关键信息。
+一套完整芯片资料通常包括:
-#### 典型结构(以 STM32 为例):
+- Datasheet:电气参数、封装、引脚定义、工作范围
+- Reference Manual:寄存器、外设详细说明
+- Programming Manual:内核和编程模型
+- Errata:已知问题
+- Application Note:应用建议
-| 部分 | 说明 |
-| --------------------------- | -------------------------------- |
-| Features | 简要功能描述,例如内核型号、Flash/RAM 容量、外设数量等 |
-| Block Diagram | 芯片整体结构图 |
-| Pinout / Alternate Function | 引脚定义和复用功能说明 |
-| Electrical Characteristics | 电源、电压、电流、温度范围等参数 |
-| Memory Map | 内存地址映射(Flash、SRAM、外设地址区间) |
-| Peripherals | 每个外设的寄存器结构与配置方法 |
+### 阅读技巧
-#### 阅读技巧:
+建议按问题来读,而不是从头到尾顺序翻。
-* **先看 Block Diagram 和内存映射图**,了解系统架构。
-* **按功能模块查阅**:比如用 UART,就查看 USART 章节。
-* **关注寄存器描述表格**:查看寄存器地址、每个位的含义、读写属性(R/W)和复位值。
-* **善用搜索关键词**:如 `RCC_APB2ENR`,快速定位外设时钟控制相关信息。
+例如:
----
+- 查引脚复用:看 Datasheet 的 Pinout / Alternate Function 表
+- 查 UART 如何初始化:看 Reference Manual 的 USART 章节
+- 查中断号:看向量表或 NVIC 对应章节
+- 查低功耗模式:看电源管理与功耗章节
-### 向量表的定义与重定向
+高效阅读习惯:
-#### 什么是向量表?
+- 先定位章节结构
+- 再查寄存器位定义
+- 最后对照库函数或初始化代码验证
-向量表是处理器在启动时用来获取异常/中断服务函数入口地址的数组,通常放在 Flash 起始地址(如 `0x0800 0000`)或 RAM 中。
+---
-每个项为一个 **函数指针**,比如:
+## 向量表的定义与重定向
-```c
-typedef void(*ISR_Handler)(void);
-const ISR_Handler vector_table[] __attribute__((section(".isr_vector"))) = {
- (ISR_Handler)&_estack, // 初始栈顶指针
- Reset_Handler, // Reset
- NMI_Handler, // NMI
- HardFault_Handler, // HardFault
- ...
-};
-```
+### 什么是向量表
-#### 重定向方法(常用于 Bootloader 或自定义中断):
+向量表本质上是一个函数地址表。它记录了复位处理函数和各类异常、中断服务函数的入口地址。
-**1. 修改中断处理函数指针:**
+在 Cortex-M 中,向量表开头通常包括:
-```c
-__attribute__((section(".vector_table")))
-void (*my_vector_table[])(void) = {
- ... // 自定义中断处理函数
-};
-```
+1. 初始栈顶地址
+2. Reset_Handler
+3. NMI_Handler
+4. HardFault_Handler
+5. 各种外设中断入口
+
+### 重定向方法(常用于 Bootloader 或自定义中断)
+
+当使用 Bootloader 或多应用布局时,应用程序的向量表往往不在默认地址,需要通过 `SCB->VTOR` 重定向。
-**2. 使用 `SCB->VTOR`(Vector Table Offset Register)更改向量表地址:**
+示例:
```c
-#include "core_cm4.h"
-SCB->VTOR = (uint32_t)my_vector_table;
+SCB->VTOR = 0x08010000;
```
-> 注意:新表地址必须对齐 0x100(最低 8 位为 0)
+常见场景:
-#### 应用场景:
-
-* Bootloader 跳转到 App 时的向量表切换
-* 定制中断处理逻辑
-* 启动阶段将向量表从 Flash 重定向到 RAM(以支持运行时修改)
+- Bootloader 跳转到 App
+- 多固件分区
+- RAM 启动调试
---
-### ROM 启动 vs RAM 启动的差异
+## ROM 启动 vs RAM 启动的差异
+
+### ROM 启动(Flash 启动)
-#### 启动方式的定义:
+这是量产环境下最常见的启动方式。
-启动方式决定系统复位后,**从哪块内存的地址开始执行程序**,通常与 Boot Mode 管脚或 Boot 配置位有关。
+特点:
-#### ROM 启动(Flash 启动):
+- 程序固化在 Flash 中
+- 上电即可执行
+- 可靠性高
+- 适合正式发布版本
-* CPU 从 Flash 起始地址(如 `0x0800 0000`)加载向量表与指令
-* 适用于生产烧录版本
-* 启动速度快,代码执行稳定
+### RAM 启动
-#### RAM 启动:
+RAM 启动常用于调试、下载器或特定快速加载场景。
-* CPU 从 SRAM 地址(如 `0x2000 0000`)启动
-* 适用于调试、自定义 Bootloader 或通过 JTAG 加载代码场景
-* 启动前通常需要拷贝一段程序到 RAM(由 Boot ROM、调试器或引导代码完成)
+特点:
-#### 使用差异:
+- 程序先被加载到 RAM
+- 执行速度可能更高
+- 断电即丢失
+- 常用于调试或特定自举流程
-| 项目 | ROM 启动 | RAM 启动 |
-| ----- | -------------- | --------------------- |
-| 程序位置 | 编译链接到 Flash 区域 | 编译链接到 RAM 区域 |
-| 向量表位置 | 默认在 Flash | 需手动配置向量表并设置 SCB->VTOR |
-| 应用场景 | 量产版本、正常运行 | Bootloader、自举加载、调试 |
+### 使用差异
-#### 链接脚本修改示例(GCC):
+| 项目 | ROM 启动 | RAM 启动 |
+| --- | --- | --- |
+| 存放位置 | Flash | RAM |
+| 掉电保留 | 是 | 否 |
+| 常见用途 | 正式固件 | 调试、Bootloader、特殊加载 |
+| 向量表位置 | 默认在 Flash | 常需手动重定向 |
-* ROM 启动:
+链接脚本示例:
```ld
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
+RAM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
```
-* RAM 启动:
+---
+
+## 本章小结
-```ld
-RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
-```
+这一章的重点不是背概念,而是建立“系统是如何被组织起来的”这条主线。只要把系统构成、启动流程、向量表、链接脚本和工具链之间的关系理清,后续做驱动、RTOS 和 Bootloader 时会更顺畅。
diff --git "a/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md" "b/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
index 8666236..52fb35c 100644
--- "a/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
+++ "b/03-\351\251\261\345\212\250\345\274\200\345\217\221\344\270\216\345\244\226\350\256\276\347\274\226\347\250\213/README.md"
@@ -1,236 +1,334 @@
+# 第三层:驱动开发与外设编程
+驱动开发是嵌入式工程里最靠近硬件的一层。本章的重点是学会从寄存器、时钟、引脚配置、通信时序和中断/DMA 等角度去理解外设,而不是只会调用库函数。
-# 第三层:驱动开发与外设编程
+建议学习目标:
+
+- 掌握寄存器地址、位域和掩码操作的基本方法。
+- 理解 GPIO、UART、SPI、I2C、ADC、DMA、CAN 等常见外设的工作原理。
+- 能区分寄存器级开发、HAL、LL 三种开发方式的差异。
+- 具备排查通信异常、时序错误和配置错误的基本思路。
+
+阅读建议:从 GPIO、UART 这类简单外设切入,再过渡到 DMA、CAN 等更复杂的模块。
-嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
-以下从底层原理到实践应用进行深度扩展:
+---
## 寄存器级开发
-#### 地址映射与寄存器偏移
-- **总线架构**:
- - AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
- - 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
-- **寄存器偏移**:
- - 每个外设包含多个寄存器,通过基地址+偏移量访问。
- - 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。
-
-#### 位操作技巧
-- **原子操作宏**:
- ```c
- #define SET_BIT(REG, BIT) ((REG) |= (BIT))
- #define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
- #define READ_BIT(REG, BIT) ((REG) & (BIT))
- #define TOGGLE_BIT(REG, BIT) ((REG) ^= (BIT))
- ```
-- **多位置位/清零**:
- ```c
- // 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
- GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
- ```
-
-### 通用外设驱动
-#### GPIO(通用输入输出)
-- **模式配置**:
- - 输入模式:浮空输入、上拉输入、下拉输入、模拟输入。
- - 输出模式:推挽输出、开漏输出(需外部上拉)。
- - 复用模式:用于SPI、I2C等外设功能。
-- **中断配置步骤**:
- 1. 配置GPIO为输入模式。
- 2. 配置SYSCFG_EXTICR寄存器选择中断源。
- 3. 配置EXTI_IMR(中断屏蔽)、EXTI_RTSR(上升沿触发)/FTSR(下降沿触发)。
- 4. 在NVIC中使能并设置中断优先级。
- ```c
- // 示例:配置PA0为上升沿触发中断
- SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0; // 选择PA0
- EXTI->IMR |= EXTI_IMR_IM0; // 使能中断线0
- EXTI->RTSR |= EXTI_RTSR_TR0; // 上升沿触发
- HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); // 设置中断优先级
- HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能NVIC中断
- ```
-
-#### UART/USART
-- **波特率计算**:
- - 公式:`波特率 = 系统时钟 / (16 * USARTDIV)`
- - 示例:系统时钟72MHz,波特率115200,则USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625。
-- **中断接收实现**:
- ```c
- // 接收完成回调函数
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
- if (huart->Instance == USART1) {
- // 处理接收到的数据
- process_data(rx_buffer, rx_length);
- // 重新开启接收中断
- HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
- }
- }
- ```
-
-#### SPI(串行外设接口)
-- **模式配置**:
- - 时钟极性(CPOL):0(空闲时SCLK为低)或1(空闲时SCLK为高)。
- - 时钟相位(CPHA):0(第一个边沿采样)或1(第二个边沿采样)。
- - 数据位宽:8位或16位。
-- **主从模式区别**:
- - 主模式:控制SCK时钟,负责发起通信。
- - 从模式:接收SCK时钟,响应主设备请求。
-
-#### I2C(集成电路间总线)
-- **寻址方式**:
- - 7位地址:0x00~0x7F,其中0x00为广播地址。
- - 10位地址:扩展寻址,用于特殊设备。
-- **多主竞争解决**:
- - 通过SDA线的电平检测实现总线仲裁,先检测到SDA线被拉低的主设备退出竞争。
-
-#### ADC(模拟-to-数字转换器)
-- **采样时间配置**:
- - 采样时间越长,转换结果越精确,但转换速度越慢。
- - 示例:STM32F4的ADC采样时间可配置为3、15、28、56、84、112、144、480周期。
-- **多通道扫描模式**:
- ```c
- // 配置ADC1扫描模式,采样通道0、1、2
- hadc1.Instance = ADC1;
- hadc1.Init.ScanConvMode = ENABLE;
- hadc1.Init.ContinuousConvMode = DISABLE;
- hadc1.Init.NbrOfConversion = 3; // 3个转换通道
-
- sConfig.Channel = ADC_CHANNEL_0;
- sConfig.Rank = 1;
- HAL_ADC_ConfigChannel(&hadc1, &sConfig);
-
- sConfig.Channel = ADC_CHANNEL_1;
- sConfig.Rank = 2;
- HAL_ADC_ConfigChannel(&hadc1, &sConfig);
-
- sConfig.Channel = ADC_CHANNEL_2;
- sConfig.Rank = 3;
- HAL_ADC_ConfigChannel(&hadc1, &sConfig);
- ```
-
-### 复杂外设支持
-#### DMA 控制器
-- **通道选择**:
- - 每个DMA控制器包含多个通道,不同外设对应不同通道。
- - 示例:USART1_RX对应DMA2通道5,USART1_TX对应DMA2通道4。
-- **双缓冲区模式**:
- - 适合大数据量传输,一个缓冲区用于当前传输,另一个准备下一次传输。
- ```c
- // 配置DMA双缓冲区模式
- hdma_adc.Instance = DMA2_Stream0;
- hdma_adc.Init.BufferSize = 2; // 双缓冲区
- hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
- hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
- hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
- // ...其他配置
- ```
-
-#### 看门狗(Watchdog)
-- **独立看门狗(IWDG)**:
- - 由专用低速时钟(LSI,约32kHz)驱动,即使主时钟故障仍能工作。
- - 喂狗时间范围:典型值10ms~16s。
-- **窗口看门狗(WWDG)**:
- - 喂狗时间必须在窗口范围内(上限值~下限值),防止程序在异常状态下喂狗。
-
-#### CAN(控制器局域网)
-- **位时序配置**:
- - 由同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)组成。
- - 示例:波特率500kbps,系统时钟42MHz,位时序配置为:
- ```c
- sFilterConfig.FilterBank = 0;
- sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
- sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
- sFilterConfig.FilterIdHigh = 0x0000;
- sFilterConfig.FilterIdLow = 0x0000;
- sFilterConfig.FilterMaskIdHigh = 0x0000;
- sFilterConfig.FilterMaskIdLow = 0x0000;
- sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
- sFilterConfig.FilterActivation = ENABLE;
- HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
- ```
-
-### 开发库 & 工具链
-#### STM32 HAL(硬件抽象层)
-- **HAL库架构**:
- - 核心层:提供外设初始化、控制和状态检查函数。
- - 回调函数:通过弱函数(weak)实现,用户可重写。
- - 示例:
- ```c
- // HAL_UART_Transmit()函数原型
- HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
-
- // 重写回调函数
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
- if (huart->Instance == USART1) {
- // 发送完成后的处理
- }
- }
- ```
-
-#### STM32 LL(低层驱动)
-- **优势**:
- - 代码体积更小,执行效率更高。
- - 更接近寄存器操作,适合性能敏感场景。
-- **与HAL对比**:
- | **特性** | **HAL** | **LL** |
- |----------------|--------------------------|--------------------------|
- | 抽象程度 | 高 | 低 |
- | 代码体积 | 大 | 小 |
- | 执行效率 | 低 | 高 |
- | 学习难度 | 低 | 高 |
-
-#### STM32CubeMX
-- **时钟树配置**:
- - 基于PLL(锁相环)生成系统时钟,需合理配置倍频系数和分频系数。
- - 示例:配置系统时钟为180MHz:
- ```
- HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
- ```
-- **中间件集成**:
- - 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。
-
-### 实战技巧与常见问题
-#### 1. **外设初始化流程**
-1. 使能外设时钟。
-2. 配置GPIO复用功能(如需要)。
-3. 配置外设参数(如波特率、采样时间)。
-4. 使能外设。
-
-#### 2. **中断处理优化**
-- 中断服务函数(ISR)应尽量简短,避免耗时操作。
-- 关键数据传递使用原子操作或关中断保护。
+
+驱动开发的本质,就是让软件按照芯片手册规定的方式操作寄存器,从而控制硬件行为。
+
+### 地址映射与寄存器偏移
+
+芯片会把不同外设映射到固定地址范围。例如:
+
+- `GPIOA` 可能映射在某个总线地址区间
+- `USART1` 可能映射在另一个地址区间
+
+访问寄存器时,本质上是在读写“基地址 + 偏移地址”的某个内存单元。
+
+例如:
+
```c
-// 示例:使用原子操作传递数据
-volatile uint32_t g_flag __attribute__((aligned(4)));
-
-void EXTI0_IRQHandler(void) {
- __disable_irq();
- g_flag = 1; // 原子写操作
- __enable_irq();
- HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
-}
+#define GPIOA_BASE 0x40020000U
+#define GPIOA_MODER (*(volatile unsigned int *)(GPIOA_BASE + 0x00U))
+#define GPIOA_ODR (*(volatile unsigned int *)(GPIOA_BASE + 0x14U))
```
-#### 3. **调试技巧**
-- **寄存器查看**:
- ```c
- // 查看GPIOA_MODER寄存器值
- uint32_t moder_value = GPIOA->MODER;
- printf("GPIOA_MODER = 0x%08X\n", moder_value);
- ```
-- **示波器检测**:
- - 检测SPI/I2C总线波形,验证通信时序。
- - 检测PWM波形,验证占空比和频率。
-
-### 六、面试高频问题
-1. **HAL与LL库的选择标准**:
- - 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
-
-2. **I2C通信中ACK/NACK的作用**:
- - ACK(应答):接收方正确接收到数据,发送低电平。
- - NACK(非应答):接收方无法继续接收,发送高电平。
-
-3. **ADC采样时间对精度的影响**:
- - 采样时间越长,对信号的积分效果越好,抗干扰能力越强,精度越高。
-
-4. **DMA与CPU直接传输的优缺点**:
- - 优点:释放CPU资源,实现高速数据传输。
- - 缺点:配置复杂,占用总线带宽。
+理解驱动代码时,要优先问自己:
+
+- 这个寄存器控制什么功能
+- 哪几位有效
+- 写入和读取时机是什么
+
+### 位操作技巧
+
+大多数寄存器并不是整块重写,而是修改其中若干位,所以位操作非常常见。
+
+```c
+#define SET_BIT(REG, BIT) ((REG) |= (BIT))
+#define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT))
+#define READ_BIT(REG, BIT) ((REG) & (BIT))
+#define TOGGLE_BIT(REG, BIT) ((REG) ^= (BIT))
+```
+
+常见做法:
+
+- 用掩码清除旧配置
+- 用移位写入新配置
+- 用按位与检测状态位
+
+```c
+GPIOA_MODER = (GPIOA_MODER & ~(0x3U << 10)) | (0x1U << 10);
+```
+
+工程建议:
+
+- 位定义不要硬编码,尽量用宏或枚举统一管理。
+- 写寄存器前先确认“读改写”是否安全,特别是在中断或 DMA 参与的场景。
+
+---
+
+## 通用外设驱动
+
+### GPIO(通用输入输出)
+
+GPIO 是最基础的外设,也是理解引脚复用和中断配置的起点。
+
+常见模式:
+
+- 输入
+- 输出
+- 复用功能
+- 模拟输入
+
+典型应用:
+
+- 读取按键
+- 控制 LED
+- 检测中断源
+- 给外设让出引脚复用功能
+
+外部中断配置的一般流程:
+
+1. 配置引脚输入模式
+2. 选择中断线来源
+3. 配置触发边沿
+4. 打开 NVIC 中断
+
+### UART / USART
+
+UART 是最常用的调试和模块通信接口之一。
+
+要点:
+
+- 波特率要匹配
+- 起始位、数据位、停止位要一致
+- 常见问题包括丢字节、乱码、中断接收不连续
+
+典型场景:
+
+- 调试日志输出
+- 与 GPS、蓝牙、4G 模块通信
+- Bootloader 下载协议
+
+### SPI
+
+SPI 是高速同步串行接口,适合连接显示屏、Flash、高速 ADC 和各类外设。
+
+重点关注:
+
+- 主从模式
+- 时钟极性和相位(CPOL / CPHA)
+- 片选控制
+- 收发时序
+
+SPI 的典型问题往往不是代码逻辑,而是模式配置或时序不匹配。
+
+### I2C
+
+I2C 适合挂接多个中低速外设,如温湿度传感器、IMU、RTC 等。
+
+要点:
+
+- 只有两根线:SCL、SDA
+- 使用地址区分设备
+- 支持 ACK / NACK
+- 需要关注上拉电阻和总线速度
+
+常见问题:
+
+- 地址写错
+- 上拉不合适
+- 总线被设备拉死
+- 主从收发顺序错误
+
+### ADC
+
+ADC 用于采集模拟信号,是传感器接入的关键模块。
+
+重点关注:
+
+- 分辨率
+- 参考电压
+- 采样时间
+- 转换速度
+- 校准与滤波
+
+工程上要意识到:ADC 读数不稳定往往不是代码错,而是供电、参考源、采样时序或模拟前端问题。
+
+### RTC 实时时钟
+
+RTC 用于记录时间、唤醒系统或做低功耗定时。
+
+常见用途:
+
+- 时间戳
+- 定时唤醒
+- 数据记录
+- 断电后保持时间
+
+RTC 常与低功耗设计一起出现,尤其是在电池设备中。
+
+---
+
+## 复杂外设支持
+
+### DMA 控制器
+
+DMA 的价值是让数据搬运绕开 CPU,降低中断负担。
+
+典型用途:
+
+- UART 连续接收
+- ADC 扫描采样
+- SPI 大块传输
+
+要理解 DMA,至少要看清:
+
+- 谁是源
+- 谁是目的
+- 传输方向是什么
+- 是一次传输还是循环模式
+
+### 看门狗
+
+看门狗用于在系统异常时自动复位。
+
+常见类型:
+
+- 独立看门狗(IWDG)
+- 窗口看门狗(WWDG)
+
+设计看门狗时要避免两个极端:
+
+- 根本不喂狗,系统频繁误复位
+- 随便在任意地方喂狗,导致程序异常时也无法复位
+
+### CAN
+
+CAN 常见于汽车和工业场景,优势是抗干扰强、总线结构清晰。
+
+使用时应重点关注:
+
+- 波特率和位时序
+- ID 过滤
+- 帧格式
+- 错误帧和总线恢复
+
+---
+
+## 开发库 & 工具链
+
+### STM32 HAL
+
+HAL 封装程度高,适合快速上手和中小型项目。
+
+优点:
+
+- 接口统一
+- 文档与示例丰富
+- 配合 CubeMX 使用方便
+
+缺点:
+
+- 抽象较厚
+- 某些场景效率和可控性不足
+
+### STM32 LL
+
+LL(Low Layer)比 HAL 更贴近寄存器。
+
+优点:
+
+- 代码更轻
+- 性能更可控
+- 更适合对时序和效率敏感的驱动
+
+缺点:
+
+- 学习门槛更高
+- 代码可读性依赖开发者水平
+
+### STM32CubeMX
+
+CubeMX 的核心价值不是“自动生成代码”,而是帮助你快速完成:
+
+- 时钟树配置
+- 引脚复用配置
+- 外设参数初始化
+- 中间件启用
+
+使用时应注意:
+
+- 不要完全依赖自动生成结果
+- 生成后仍要对照手册理解关键配置
+- 用户代码区要和自动生成区分开
+
+---
+
+## 实战技巧与常见问题
+
+### 外设初始化流程
+
+大部分外设初始化都可以套同一个思路:
+
+1. 打开时钟
+2. 配置 GPIO
+3. 配置外设寄存器或库参数
+4. 配置中断 / DMA
+5. 使能外设
+6. 做最小功能验证
+
+只要这个流程清楚,换 UART、SPI、ADC 或定时器都能迁移。
+
+### 中断处理优化
+
+中断服务函数应尽量短,只做必要动作:
+
+- 读取状态
+- 清中断标志
+- 把数据或事件交给主循环 / RTOS 任务处理
+
+不要在 ISR 中做的事情:
+
+- 大量打印日志
+- 长时间循环
+- 阻塞等待
+
+### 调试技巧
+
+驱动问题的排查顺序建议固定下来:
+
+1. 先确认时钟和引脚配置
+2. 再看寄存器值
+3. 再看通信波形
+4. 最后再怀疑上层逻辑
+
+工具组合建议:
+
+- 寄存器查看:调试器
+- 波形检查:示波器 / 逻辑分析仪
+- 数据链路验证:串口日志 / 协议解析
+
+---
+
+## 面试高频问题
+
+1. HAL 与 LL 库如何选择?
+ 一般快速开发优先 HAL,性能敏感、代码体积敏感或需要细粒度控制时考虑 LL 或寄存器级开发。
+
+2. I2C 中 ACK / NACK 的作用是什么?
+ ACK 表示正确接收并愿意继续,NACK 表示当前不接受数据或传输结束。
+
+3. ADC 采样时间为什么会影响精度?
+ 采样时间过短时,采样电容可能尚未充满,导致转换结果偏差更大。
+
+4. DMA 相比 CPU 直接搬运的优缺点是什么?
+ DMA 能降低 CPU 占用、提高吞吐,但配置更复杂,也会占用总线资源。
+
+---
+
+## 本章小结
+
+学驱动开发时,最重要的是形成“外设初始化 -> 参数配置 -> 数据传输 -> 中断/错误处理 -> 调试验证”的固定思路。只要这个框架稳定下来,换不同芯片和不同库都能迁移。
+
diff --git "a/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md" "b/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
index d512241..fddb984 100644
--- "a/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
+++ "b/04-\345\256\236\346\227\266\346\223\215\344\275\234\347\263\273\347\273\237/README.md"
@@ -1,450 +1,338 @@
-
# 第四层:实时操作系统(RTOS)
-本模块介绍嵌入式 RTOS(如 FreeRTOS)的基础知识、任务调度机制、资源管理方式以及在实际项目中的使用模式。
+RTOS 章节的核心不是记 API,而是理解任务调度、时间管理、线程通信和资源竞争背后的系统行为。只要调度模型清楚,才能在项目里真正把多任务系统写稳定。
+
+建议学习目标:
+
+- 理解裸机系统与 RTOS 的边界和适用场景。
+- 掌握任务、优先级、时间片、阻塞与唤醒等核心概念。
+- 能区分队列、信号量、互斥锁、事件组等机制的使用场景。
+- 具备分析死锁、优先级反转、栈溢出和实时性问题的能力。
+
+阅读建议:先建立调度模型,再学习线程通信与资源保护,最后结合实际应用场景理解 RTOS 的工程价值。
---
## RTOS 基础概念
### 什么是 RTOS?
-**RTOS**(Real-Time Operating System)是用于嵌入式设备中的轻量级操作系统,能提供任务调度、时间管理、资源管理等功能。
-**特点:**
-- 确定性(Determinism):
- - 任务执行时间可预测,如中断响应时间 ≤100μs。
- - 对比:通用操作系统(如 Linux)强调吞吐量,不保证实时性。
+RTOS(Real-Time Operating System)是面向实时场景设计的操作系统。它的目标不是追求“平均性能最高”,而是追求“关键任务在预期时间内完成”。
-- 可抢占内核(Preemptive Kernel):
- - 高优先级任务可立即抢占低优先级任务。
- - 示例:飞行控制系统中,传感器数据采集任务优先级高于显示任务。
+RTOS 关心的核心问题包括:
+
+- 任务什么时候执行
+- 哪个任务优先执行
+- 多个任务如何共享资源
+- 时间触发行为如何保证稳定
+
+很多嵌入式项目不是必须上 RTOS,但一旦系统出现以下特点,就应认真考虑:
+
+- 有多个独立功能并发运行
+- 存在固定周期任务
+- 存在通信、采集、显示、控制并行需求
+- 需要更清晰的任务边界和资源管理
### 常见 RTOS
-- FreeRTOS(开源、广泛使用)
-- RT-Thread(国产开源,图形化支持强)
-- CMSIS-RTOS(ARM 标准接口)
-- Zephyr(Linux 基金会支持,适合物联网)
-
-**RTOS vs 裸机系统**
-| 特性 | 裸机系统 | RTOS(实时操作系统) |
-|--------------|-----------------------------|---------------------------------|
-| 任务管理 | 单任务 / 前后台系统 | 多任务并发,支持任务优先级 |
-| 资源分配 | 手动管理 | 自动调度和资源管理 |
-| 实时响应 | 依赖主循环结构 | 确定性调度,响应更稳定 |
-| 开发难度 | 低(适合简单系统) | 高(需理解调度机制、堆栈管理) |
-
-**主流 RTOS 对比**
-| RTOS | 开源 | 应用领域 | 特点 |
-|------------|----------|--------------------------|----------------------------------------------|
-| FreeRTOS | | 工业控制、消费电子 | 轻量级、广泛支持、文档完善 |
-| RT-Thread | | 物联网、智能家居 | 国产、组件丰富(如文件系统、GUI) |
-| μC/OS | 商用需授权 | 航空航天、医疗设备 | 支持安全认证(如 DO-178C)、稳定可靠 |
-| VxWorks | | 国防、通信、航天 | 商业闭源、高可靠性、实时性能强 |
+
+常见 RTOS 包括:
+
+- FreeRTOS:最常见,生态成熟
+- RT-Thread:国内使用广泛,组件丰富
+- CMSIS-RTOS:ARM 提供统一接口标准
+- Zephyr:适合 IoT 和较现代的软件栈
+
+理解不同 RTOS 时,不要先看 API 差异,而要先看它们在以下方面的设计:
+
+- 调度模型
+- 任务栈管理
+- IPC 机制
+- 内存管理方式
+- 中断与任务协作方式
---
## 任务管理
### 任务创建与内存布局
+
+RTOS 中每个任务通常都有:
+
+- 独立栈空间
+- 任务控制块(TCB)
+- 优先级
+- 当前状态
+
+示例:
+
```c
-// 创建任务示例
void vTaskFunction(void *pvParameters) {
for (;;) {
- // 任务代码
- vTaskDelay(pdMS_TO_TICKS(100)); // 释放CPU
+ vTaskDelay(pdMS_TO_TICKS(100));
}
}
-// 任务创建
xTaskCreate(vTaskFunction, "Task1", 256, NULL, 2, NULL);
```
-- 栈空间分配:
- - 每个任务独立栈空间,需避免溢出(通过configCHECK_FOR_STACK_OVERFLOW检测)。
- - 计算方法:任务局部变量大小 + 函数调用深度 × 最大寄存器保存数。
+
+实际开发中,任务栈大小往往比创建 API 更重要。栈设太小会导致诡异崩溃,设太大又会浪费宝贵 RAM。
### 任务状态转换
-```plaintext
- 调度器选择 超时/事件发生
-就绪 ───────────→ 运行 ←─────────── 阻塞
- ↑ │ │
- │ └─── 调用vTaskDelay │
- │ │
- └─────── 调用vTaskSuspend ┘
- 或挂起API
-```
+
+典型任务状态包括:
+
+- Running:正在运行
+- Ready:已就绪,等待 CPU
+- Blocked:等待事件或超时
+- Suspended:被挂起
+- Deleted:已删除
+
+可以把调度理解成一个状态机:任务不断在就绪、运行、阻塞之间切换。
### 任务优先级与调度算法
-- 抢占式调度:
- - 基于任务优先级,高优先级任务可立即抢占当前运行任务。
- - 实现:FreeRTOS 通过pxCurrentTCB指针指向当前任务控制块(TCB)。
-- 时间片轮转:
- - 同优先级任务按时间片轮流执行(由configTICK_RATE_HZ决定)。
- - 示例:两个优先级相同的任务各执行 10ms。
+RTOS 常见调度原则:
+
+- 高优先级任务优先执行
+- 同优先级任务可采用时间片轮转
+- 阻塞任务不会占用 CPU
+
+调度设计的常见误区:
+
+- 把大量任务都设成高优先级
+- 用高优先级掩盖设计问题
+- 在高优先级任务里放耗时逻辑
+
+实际经验:
+
+- 实时采样和关键控制任务优先级更高
+- 通信、日志、显示通常可以更低
+- 系统稳定比“所有任务都快”更重要
---
## 时间管理
### 任务延时实现
+
+RTOS 中最常见的时间管理方式是任务延时。
+
```c
-// 相对延时(从调用开始计算)
vTaskDelay(pdMS_TO_TICKS(100));
+```
+
+这表示当前任务主动让出 CPU,并在指定时间后重新变为就绪态。
+
+对于周期任务,更推荐使用绝对周期方式:
-// 绝对延时(固定周期执行)
+```c
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(100);
+
for (;;) {
vTaskDelayUntil(&xLastWakeTime, xFrequency);
- // 周期性任务代码
}
```
+原因是它能减少累计误差,更适合定周期控制任务。
+
### 软件定时器
-- 单次触发:执行一次后停止。
-- 周期触发:按固定周期重复执行。
+
+软件定时器适合做:
+
+- 周期检测
+- 延时触发
+- 超时回调
+
+不适合做:
+
+- 高实时性控制
+- 长时间阻塞操作
+- 复杂业务逻辑堆积
+
+示例:
+
```c
-// 创建并启动定时器
TimerHandle_t xTimer = xTimerCreate(
- "Timer", // 定时器名称
- pdMS_TO_TICKS(1000), // 周期1秒
- pdTRUE, // 周期模式
- (void *)0, // 定时器ID
- vTimerCallback // 回调函数
+ "Timer",
+ pdMS_TO_TICKS(1000),
+ pdTRUE,
+ (void *)0,
+ vTimerCallback
);
-xTimerStart(xTimer, 0);
-
-// 定时器回调函数
-void vTimerCallback(TimerHandle_t xTimer) {
- // 定时任务代码
-}
```
+理解软件定时器时要意识到:它本质上依赖系统节拍和后台处理任务,不等价于硬件定时器。
+
---
## 线程间通信
### 队列(Queue)
-- 特性:
- - 线程安全的 FIFO 缓冲区,支持阻塞读写。
- - 最大长度和消息大小在创建时指定。
-```c
-// 创建队列
-QueueHandle_t xQueue = xQueueCreate(5, sizeof(int)); // 5个int元素
+队列适合在任务之间传递有顺序的数据。
+
+典型用途:
-// 发送消息(阻塞100ms)
-int value = 100;
-xQueueSend(xQueue, &value, pdMS_TO_TICKS(100));
+- 按键事件传递
+- 采样值上报
+- 命令和状态消息流转
-// 接收消息(永久等待)
-int received_value;
-xQueueReceive(xQueue, &received_value, portMAX_DELAY);
+```c
+QueueHandle_t xQueue = xQueueCreate(5, sizeof(int));
```
+队列的优势是:
+
+- 线程安全
+- 可阻塞等待
+- 数据传递语义清晰
+
### 信号量(Semaphore)
-#### 二值信号量:
-- 用于任务同步(如中断与任务通信)。
-```c
-// 创建二值信号量
-SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
-// 任务中获取信号量
-if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
- // 获得信号量,执行临界区代码
-}
+信号量本质上是“同步和计数工具”。
-// 中断中释放信号量
-BaseType_t xHigherPriorityTaskWoken = pdFALSE;
-xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
-portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
-```
+常见两类:
-#### 计数信号量(共享资源数量)
-- 核心概念
- - 资源计数器:初始值为可用资源数量,用于控制对有限资源的访问。
- - 操作规则:
- - xSemaphoreTake():获取信号量时计数器减 1,若计数器为 0 则阻塞。
- - xSemaphoreGive():释放信号量时计数器加 1,唤醒等待任务。
-- 典型应用场景
- - 多资源管理:如打印机池(假设有 3 台打印机)
-```c
-// 创建计数信号量(初始值=3,最大值=3)
-SemaphoreHandle_t xPrinterSemaphore = xSemaphoreCreateCounting(3, 3);
-
-// 任务中请求打印机
-if (xSemaphoreTake(xPrinterSemaphore, portMAX_DELAY) == pdTRUE) {
- // 获得打印机,执行打印任务
- vPrintTask();
- // 释放打印机
- xSemaphoreGive(xPrinterSemaphore);
-}
-```
- - 生产者——消费者缓冲区:用信号量跟踪缓冲区空 / 满状态。
-#### 互斥信号量(用于资源保护)
-- 核心特性
- - 二值信号量的特例:初始值为 1,表示资源可用。
- - 优先级继承:解决优先级反转问题(低优先级任务持有锁时临时提升其优先级)。
-- 优先级反转示例
-```c
-// 创建互斥锁
-SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
+- 二值信号量:做事件通知
+- 计数信号量:做资源计数
-// 高优先级任务H
-void vTaskHigh(void *pvParameters) {
- for (;;) {
- xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
- // 临界区代码
- xSemaphoreGive(xMutex); // 释放锁
- }
-}
+典型场景:
-// 低优先级任务L
-void vTaskLow(void *pvParameters) {
- for (;;) {
- xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
- // 执行长时间操作(此时被中优先级任务M抢占)
- xSemaphoreGive(xMutex); // 释放锁
- }
-}
-```
-- 问题:任务 L 持有锁时被任务 M 抢占,导致任务 H 无法执行(优先级反转)。
-- 解决:启用优先级继承后,任务 L 持有锁时临时提升至任务 H 的优先级,避免被 M 抢占。
+- 中断通知任务
+- 多个资源实例的访问控制
+
+需要区分一个关键点:
+
+- 信号量更偏“同步”
+- 互斥锁更偏“资源保护”
### 消息队列(Message Queue)
-#### 与普通队列的区别
-- 结构化数据传递:支持传递复杂数据类型(如结构体)。
-- 指针传递优化:可传递数据指针而非数据本身,减少内存拷贝。
-#### 使用示例
+很多项目会把结构体封装后通过队列传递,本质上就是“消息化”的任务通信。
+
```c
-// 定义消息结构体
typedef struct {
uint8_t command;
uint32_t data;
- void (*callback)(void);
} Message_t;
-
-// 创建消息队列(最多5个消息)
-QueueHandle_t xMessageQueue = xQueueCreate(5, sizeof(Message_t));
-
-// 发送消息
-Message_t xMessage = {
- .command = 0x01,
- .data = 100,
- .callback = vProcessCallback
-};
-xQueueSend(xMessageQueue, &xMessage, portMAX_DELAY);
-
-// 接收消息
-Message_t xReceivedMessage;
-if (xQueueReceive(xMessageQueue, &xReceivedMessage, portMAX_DELAY) == pdTRUE) {
- // 处理消息
- vProcessMessage(&xReceivedMessage);
-}
```
-#### 消息队列 vs 普通队列
-| 特性 | 普通队列 | 消息队列 |
-|--------------|----------------------------------|--------------------------------------------|
-| 数据类型 | 固定大小字节块 | 支持结构体、指针等复杂数据类型 |
-| 适用场景 | 简单数据传输(如 ADC 值) | 复杂命令传递(如协议解析、任务通信) |
-| 内存效率 | 每次传输都需拷贝数据 | 可传递指针,减少内存拷贝,效率更高 |
+
+这种方式的优势是:
+
+- 接口清晰
+- 易扩展
+- 适合做事件驱动系统
### 事件组(Event Group)
-- 类似标志位,可用于多任务同步
-```c
-// 创建事件组
-EventGroupHandle_t xEventGroup = xEventGroupCreate();
-
-// 任务1:设置事件位0
-xEventGroupSetBits(xEventGroup, 0x01);
-
-// 任务2:等待事件位0和1都置位
-EventBits_t uxBits = xEventGroupWaitBits(
- xEventGroup, // 事件组句柄
- 0x03, // 等待位0和1
- pdTRUE, // 等待后清除位
- pdTRUE, // 等待所有位
- portMAX_DELAY // 永久等待
-);
-```
+
+事件组适合管理多个布尔条件或状态位。
+
+例如:
+
+- 网络已连上
+- 传感器初始化完成
+- 存储器挂载成功
+
+当多个条件都满足时,再让某个任务继续执行。
+
+这类机制非常适合启动流程和多模块协作。
---
## 资源管理
-### 内存管理方式
-#### 静态分配(推荐)
-```c
-// 使用静态内存创建任务
-StaticTask_t xTaskBuffer;
-StackType_t xStack[256];
-
-xTaskCreateStatic(
- vTaskFunction, // 任务函数
- "Task1", // 任务名称
- 256, // 栈大小
- NULL, // 参数
- 2, // 优先级
- xStack, // 静态栈
- &xTaskBuffer // 静态任务控制块
-);
-```
-#### 动态分配(需要注意碎片与失败处理)
-- 原因:频繁分配 / 释放不同大小的内存块,导致空闲内存分散。
-- 示例:
-```c
-// 可能导致碎片的错误模式
-void vTask(void *pvParameters) {
- for (;;) {
- char *pcBuffer = (char *)pvPortMalloc(100);
- // 使用缓冲区...
- vPortFree(pcBuffer); // 释放后可能产生碎片
- vTaskDelay(pdMS_TO_TICKS(10));
- }
-}
-```
+### 互斥锁与优先级继承
-#### 安全使用动态内存的原则
-- 预分配固定大小块:
-```c
-// 预先分配对象池
-static uint8_t xObjectPool[10][100]; // 10个100字节的对象
-static BaseType_t xObjectAvailable[10] = {1}; // 标记可用状态
-
-uint8_t *pvGetObject(void) {
- for (int i = 0; i < 10; i++) {
- if (xObjectAvailable[i]) {
- xObjectAvailable[i] = 0;
- return &xObjectPool[i][0];
- }
- }
- return NULL;
-}
-```
-- 检查分配结果:
-```c
-void *pvBuffer = pvPortMalloc(100);
-if (pvBuffer == NULL) {
- // 内存分配失败处理
- vHandleMemoryError();
-}
-```
+互斥锁用于保护共享资源,例如:
-### 临界区保护
-- 关中断:
-```c
-void vCriticalFunction(void) {
- taskENTER_CRITICAL();
- // 临界区代码(禁止中断)
- taskEXIT_CRITICAL();
-}
-```
-- 互斥锁:
-```c
-// 创建互斥锁
-SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
+- 全局结构体
+- 共享总线
+- 文件系统接口
+- 显示缓冲区
-// 获取锁
-xSemaphoreTake(xMutex, portMAX_DELAY);
-// 临界区代码
-xSemaphoreGive(xMutex); // 释放锁
-```
+它和普通信号量最大的区别之一,是通常带有优先级继承机制,可以缓解优先级反转问题。
+
+优先级反转的典型场景:
+
+1. 低优先级任务占有锁
+2. 高优先级任务想拿锁被阻塞
+3. 中优先级任务不断抢占 CPU
+4. 高优先级任务反而迟迟得不到运行机会
+
+### 内存管理
+
+RTOS 中常见几种内存分配策略:
+
+- 完全静态分配
+- 简单动态分配
+- 固定块内存池
+
+在资源受限项目中,动态内存并非不能用,但必须明确:
+
+- 谁申请
+- 谁释放
+- 生命周期多长
+- 是否会碎片化
+
+如果系统稳定性要求高,静态分配通常更容易控制风险。
---
## FreeRTOS 配置与移植
-### 配置项(FreeRTOSConfig.h)
-| 参数 | 描述 | 示例值 |
-|-------------------------------|-----------------------------------|--------------------|
-| `configUSE_PREEMPTION` | 是否使用抢占式调度 | `1`(启用) |
-| `configTICK_RATE_HZ` | 系统滴答频率(Hz) | `1000`(1ms) |
-| `configMAX_PRIORITIES` | 最大任务优先级数 | `5 ~ 32` |
-| `configMINIMAL_STACK_SIZE` | 最小任务栈大小(以字为单位) | `128`(STM32) |
-| `configSUPPORT_DYNAMIC_ALLOCATION` | 是否支持动态内存分配 | `1`(支持) |
-
-### 移植步骤
-1. 提供 SysTick 定时器实现
-2. 提供上下文切换代码(汇编)
-3. 编写启动任务入口函数 `vTaskStartScheduler()`
-
-### 移植关键点
-- 上下文切换实现(汇编):
-```assembly
-; Cortex-M3/M4 上下文切换示例(PendSV处理函数)
-PendSV_Handler:
- CPSID I ; 关中断
- MRS R0, PSP ; 获取进程栈指针
- CBZ R0, PendSV_NoSave ; 首次调用直接切换
-
- ; 保存寄存器到当前任务栈
- SUBS R0, R0, #0x20 ; 调整栈指针
- STM R0, {R4-R11} ; 保存R4-R11
- LDR R1, =pxCurrentTCB ; 获取当前任务指针
- LDR R1, [R1] ; 加载任务控制块地址
- STR R0, [R1] ; 保存新的栈指针
-
-PendSV_NoSave:
- LDR R0, =pxCurrentTCB ; 获取当前任务指针
- LDR R1, [R0] ; 加载当前任务控制块
- LDR R0, [R1, #4] ; 加载下一个任务控制块
- STR R0, [R0] ; 更新当前任务指针
- LDR R0, [R0] ; 加载新任务栈指针
- LDM R0, {R4-R11} ; 恢复寄存器
- MSR PSP, R0 ; 更新进程栈指针
- ORR LR, LR, #0x04 ; 设置返回标志
- CPSIE I ; 开中断
- BX LR ; 返回
-```
----
+### 常用配置项
-## RTOS 调试与性能分析
+FreeRTOS 的很多行为由 `FreeRTOSConfig.h` 控制。
-### 调试工具与技术
-- 任务状态查看:
-```c
-// 获取任务运行时信息
-void vTaskList(char *pcWriteBuffer);
+常见配置项包括:
-// 示例输出:
-// TaskName State Priority Stack Num
-// Task1 Running 2 128 1
-// Task2 Blocked 1 256 2
-```
+- `configCPU_CLOCK_HZ`
+- `configTICK_RATE_HZ`
+- `configMAX_PRIORITIES`
+- `configMINIMAL_STACK_SIZE`
+- `configTOTAL_HEAP_SIZE`
+- `configCHECK_FOR_STACK_OVERFLOW`
-### 性能指标分析
-- CPU 使用率:
-```c
-// 计算CPU使用率(需配置configGENERATE_RUN_TIME_STATS=1)
-uint32_t ulHighFrequencyTimerTicks;
-vTaskGetRunTimeStats(&ulHighFrequencyTimerTicks);
-```
-- 任务堆栈深度:
-```c
-// 检查任务栈剩余空间
-UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
-```
+理解这些配置时,要关注它们影响的是:
+
+- 调度频率
+- 栈和堆大小
+- 调试能力
+- 系统资源上限
+
+### 移植关注点
+
+移植一个 RTOS 到新平台时,重点通常不在应用层,而在以下部分:
+
+- 时钟节拍来源
+- 中断上下文切换
+- 栈初始化
+- 临界区管理
+- 编译器与架构适配
+
+如果这些基础没对齐,后面再多的任务代码也很难稳定运行。
---
-## 面试高频问题
+## 实践应用场景
-#### RTOS 中任务与线程的区别:
-- 任务是 RTOS 调度的基本单位,线程是操作系统调度的基本单位;RTOS 任务通常更轻量级。
+RTOS 常见的实践场景包括:
-#### 信号量与互斥锁的区别:
-- 信号量可用于同步和资源计数,互斥锁专用于资源保护,支持优先级继承避免死锁。
+- 传感器采集任务 + 通信任务 + 显示任务并行
+- 定时控制与外部中断协作
+- 网络接入与业务逻辑分层
+- 低功耗系统中的周期唤醒和事件响应
-#### 如何避免 RTOS 中的死锁:
-- 按相同顺序获取锁,使用带超时的锁获取函数,避免嵌套锁。
+一个典型思路是:
-#### FreeRTOS 任务优先级设置原则:
-- 关键任务(如传感器采样)设高优先级,非关键任务(如显示更新)设低优先级。
+1. 把系统拆成若干稳定职责的任务
+2. 明确任务优先级
+3. 用合适的通信机制解耦
+4. 用日志、监控和栈检查保证稳定性
---
-## 实践应用场景
-- 多任务协同:传感器数据采集 + 通信模块处理
-- 响应式控制:定时器 + 外部中断 + 优先级控制
-- 任务调度机制优化(任务嵌套/抢占/时间片轮转)
+## 本章小结
+
+RTOS 的本质是帮助你在有限资源下有组织地安排任务执行。真正需要掌握的是调度规则、同步机制和错误模式,而不是单纯背诵某个 RTOS 的函数名。
diff --git a/05-EmbeddedLinux/README.md b/05-EmbeddedLinux/README.md
index 0c5714f..ef2c6e5 100644
--- a/05-EmbeddedLinux/README.md
+++ b/05-EmbeddedLinux/README.md
@@ -1,8 +1,15 @@
+# 第五层:嵌入式 Linux 开发基础
+这一章帮助你从 MCU 开发视角跨到 Embedded Linux 体系。重点不在“会用命令”,而在理解从 Bootloader、内核、设备树到根文件系统和用户空间之间的完整关系。
-# 第五层:嵌入式 Linux 开发基础
+建议学习目标:
+
+- 搞清楚 Bootloader、Kernel、RootFS、应用程序的职责边界。
+- 理解设备树为什么存在,以及它如何描述硬件。
+- 掌握常见 Linux 开发工具、交叉编译流程和调试思路。
+- 对系统安全、权限隔离和 Bootloader 设计有基础认知。
-嵌入式 Linux 是物联网、智能设备、工业控制等领域的核心技术之一。本层重点掌握从 Bootloader 到驱动的开发过程,理解 Linux 系统构成及其移植方法。
+阅读建议:先看系统启动链路,再看设备树和驱动模型,最后再学习工具链、文件系统和安全相关内容。
---
@@ -10,20 +17,38 @@
### 嵌入式 Linux 特点
-- 可裁剪、可定制、模块化强
-- 支持多种架构(ARM、MIPS、RISC-V 等)
-- 社区支持强大(开源内核、驱动丰富)
+嵌入式 Linux 不是简单把 Linux 装到板子上,而是围绕特定硬件和产品目标裁剪出来的 Linux 系统。
+
+它的典型特点包括:
+
+- 可裁剪:内核、驱动、文件系统、服务都可按需精简
+- 可移植:支持 ARM、RISC-V、MIPS、x86 等多种架构
+- 生态强:网络、文件系统、多进程、图形界面、中间件非常丰富
+- 资源要求更高:相比裸机和 RTOS,需要更多存储与内存
+
+适合使用 Embedded Linux 的常见场景:
+
+- 复杂网络设备
+- 带显示和多媒体能力的终端
+- 网关与边缘计算设备
+- 需要文件系统、用户态应用和远程运维能力的产品
### 系统组成
+一个典型 Embedded Linux 系统可以抽象成如下结构:
+
```text
-[Bootloader] → [Kernel] → [Root File System] → [User Application]
+[Bootloader] -> [Linux Kernel] -> [Root File System] -> [User Application]
```
-- **Bootloader**:负责上电后硬件初始化、加载内核(如 U-Boot)
-- **Kernel**:Linux 内核,管理硬件与系统资源
-- **RootFS**:根文件系统,包含用户空间程序
-- **应用层**:运行用户程序、脚本、服务等
+各层职责:
+
+- Bootloader:完成最早期硬件初始化并加载内核
+- Kernel:管理 CPU、内存、驱动、网络、进程和系统调用
+- Root File System:提供用户空间目录结构、库和命令
+- User Application:承载具体业务逻辑
+
+如果把 MCU 开发比作“一个程序控制一整块硬件”,Embedded Linux 更像是“一个完整的软件平台运行在硬件之上”。
---
@@ -31,27 +56,36 @@
### 通用启动流程
-```text
-Power On →
- BootROM →
- Bootloader (SPL/U-Boot) →
- Load & Decompress Kernel →
- Kernel 初始化 →
- 挂载 RootFS →
- 启动 init →
- Shell / App
-```
+Embedded Linux 的启动链路通常比 MCU 复杂得多,但核心思路是一致的:逐级初始化、逐级交接。
+
+典型顺序:
+
+1. ROM Code:芯片内部固化启动逻辑
+2. 第一阶段 Bootloader:初始化最小运行环境
+3. 第二阶段 Bootloader:加载内核与设备树
+4. Linux Kernel:初始化驱动、挂载根文件系统
+5. init / systemd:启动用户空间服务和应用
+
+学习这一部分时,最重要的是理解每个阶段分别负责什么,而不是死记每个平台的细节。
### U-Boot(主流 Bootloader)
-- 二阶段:SPL(初始化内存)+ U-Boot 本体
-- 功能:串口输出、TFTP 下载、引导内核、环境变量配置等
-- 命令示例:
-```bash
-setenv bootargs console=ttyS0 root=/dev/mmcblk0p2
-tftp 0x80008000 zImage
-bootz 0x80008000 - 0x83000000
-```
+U-Boot 是最常见的嵌入式 Linux Bootloader。
+
+它常见的职责包括:
+
+- 初始化 DDR、串口、时钟、存储设备
+- 提供命令行和下载功能
+- 选择启动介质(eMMC、SPI Flash、SD 卡、网络)
+- 加载内核镜像、设备树和 initramfs
+- 在升级、回滚和恢复场景中承担控制逻辑
+
+Bootloader 在实际项目中的价值远不止“启动内核”,它还常常承担:
+
+- 工厂烧录
+- OTA 升级
+- 双分区切换
+- 故障恢复
---
@@ -59,388 +93,313 @@ bootz 0x80008000 - 0x83000000
### 基本概念
-- 描述硬件资源的结构化信息
-- 独立于内核源码,提高可移植性
-- 文件类型:`.dts`(源文件)、`.dtsi`(包含文件)、`.dtb`(二进制)
+设备树的作用,是用数据结构描述硬件,而不是把硬件信息硬编码在内核里。
+
+它主要解决的问题是:
+
+- 同一套内核如何适配不同板卡
+- 哪个外设启用、哪个引脚复用、哪个中断号和地址空间有效
+
+设备树让“内核代码”和“板级硬件描述”分离,这对嵌入式 Linux 非常重要。
### 示例结构
+设备树通常以树形节点表达硬件:
+
```dts
-uart1: serial@40011000 {
- compatible = "vendor,uart";
- reg = <0x40011000 0x400>;
- interrupts = <5>;
- status = "okay";
+/ {
+ model = "my-board";
+
+ memory@80000000 {
+ device_type = "memory";
+ reg = <0x80000000 0x20000000>;
+ };
+
+ uart1: serial@40011000 {
+ compatible = "vendor,uart";
+ reg = <0x40011000 0x400>;
+ interrupts = <5>;
+ status = "okay";
+ };
};
```
+阅读设备树时,建议先看这些字段:
+
+- `compatible`
+- `reg`
+- `interrupts`
+- `clocks`
+- `pinctrl`
+- `status`
+
### 编译设备树
+设备树源文件通常是 `.dts` / `.dtsi`,最终会编译成 `.dtb`。
+
+常见命令:
+
```bash
-make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
+dtc -I dts -O dtb -o board.dtb board.dts
```
+实际工程里一般由内核构建系统或 Buildroot / Yocto 自动完成编译。
+
---
## 常用 Linux 命令与开发工具
### 文件与目录管理
-| 命令 | 功能说明 |
-|-------------------|------------------------------|
-| `ls -l` | 列出文件详细信息 |
-| `cd /path` | 进入目录 |
-| `cp source dest` | 拷贝文件/目录 |
-| `mv old new` | 移动/重命名文件 |
-| `rm -rf dir` | 删除文件或目录 |
-| `mkdir name` | 创建新目录 |
-| `find` / `grep` | 搜索文件/内容 |
+
+最常用的基础命令包括:
+
+- `ls`:查看文件
+- `cp` / `mv` / `rm`:复制、移动、删除
+- `mkdir`:创建目录
+- `cat` / `less` / `tail`:查看文件内容
+
+嵌入式 Linux 中最常见的实际用途是:
+
+- 检查挂载点
+- 查看日志
+- 检查配置文件
+- 操作脚本和升级包
### 权限与用户管理
-| 命令 | 功能说明 |
-|--------------------|-----------------------------|
-| `chmod 755 file` | 修改权限(rwx) |
-| `chown user:group` | 更改文件拥有者 |
-| `sudo` | 以管理员身份执行命令 |
-| `whoami` / `id` | 查看当前用户信息 |
+
+需要重点理解:
+
+- 用户与用户组
+- 读写执行权限
+- `chmod` / `chown`
+- root 权限与最小权限原则
+
+嵌入式产品中,很多安全问题都源于“所有服务都用 root 运行”。
### 进程管理
-| 命令 | 功能说明 |
-|------------------|-----------------------------|
-| `ps` / `top` | 查看运行进程 |
-| `kill PID` | 杀死某个进程 |
-| `htop` | 进阶图形化进程管理工具 |
-| `nice`, `renice` | 修改进程优先级 |
+
+常用命令:
+
+- `ps`
+- `top`
+- `kill`
+- `htop`(如果系统有)
+
+重点关注:
+
+- 哪些进程在启动
+- 谁占用 CPU / 内存
+- 某个服务是否异常退出
### 网络调试
-| 命令 | 功能说明 |
-|------------------------|----------------------------|
-| `ping` | 测试网络连通性 |
-| `ifconfig` / `ip` | 配置 IP、MAC |
-| `netstat -anp` | 查看网络连接状态 |
-| `scp`, `rsync` | 文件远程复制 |
-| `ssh user@host` | SSH 登录远程系统 |
+
+常见命令包括:
+
+- `ifconfig` / `ip addr`
+- `ping`
+- `netstat` / `ss`
+- `route`
+- `tcpdump`
+
+这些工具对定位“设备连不上网”“服务端口没起来”“丢包严重”等问题非常关键。
### 设备与文件系统
-| 命令 | 功能说明 |
-|----------------------|-----------------------------|
-| `mount` / `umount` | 挂载 / 卸载设备 |
-| `df -h` | 查看磁盘空间使用情况 |
-| `lsblk`, `blkid` | 查看块设备信息 |
-| `dmesg | tail` | 查看内核设备日志 |
-
-### 软件包管理(针对开发板所用 Linux)
-| 工具 | 说明 |
-|--------------------|---------------------------------|
-| `apt`, `opkg`, `yum` | 安装 / 卸载软件包 |
-| `dpkg -i pkg.deb` | 安装本地 deb 包 |
-### Shell 脚本与自动化
-- `#!/bin/sh` 或 `#!/bin/bash`:脚本头部声明
-- 脚本权限设置:`chmod +x script.sh`
-- 示例:
-
-```sh
-#!/bin/bash
-for i in {1..5}
-do
- echo "Test $i"
-done
-```
+常见命令:
-### 交叉编译相关命令(Makefile 环境)
+- `mount`
+- `umount`
+- `df`
+- `du`
+- `lsblk`
+- `dmesg`
-| 命令/工具 | 说明 |
-| --------------- | -------------- |
-| `make` | 使用 Makefile 构建 |
-| `arm-linux-gcc` | 使用交叉编译器编译 |
-| `file a.out` | 查看可执行文件平台架构 |
+学习要点:
----
+- 块设备与字符设备的区别
+- `/dev`、`/proc`、`/sys` 的作用
+- 文件系统挂载点和启动脚本之间的关系
-## Linux 驱动开发模型
+### 软件包管理
-### 驱动分层模型
+在桌面 Linux 中软件包管理是安装软件,在 Embedded Linux 中更重要的是理解“系统内容来自哪里”。
-```text
-[硬件设备] ←→ [总线] ←→ [Device] ←→ [Driver] ←→ [内核]
-```
+常见方式:
-- **总线(bus)**:如 platform、i2c、spi 总线
-- **设备(device)**:描述具体外设
-- **驱动(driver)**:实现对设备的控制逻辑
+- 发行版包管理(如 `apt`、`opkg`)
+- Buildroot 打包
+- Yocto 镜像构建
-### 字符设备驱动框架
+对嵌入式开发者来说,重点不是记包命令,而是清楚:
-```c
-struct file_operations fops = {
- .open = my_open,
- .read = my_read,
- .write = my_write,
- .release = my_release,
-};
+- 软件如何进入镜像
+- 依赖如何管理
+- 升级如何控制
-int major = register_chrdev(0, "mydev", &fops);
-```
+### Shell 脚本与自动化
-### 平台驱动开发流程
+Shell 脚本常用于:
-1. 定义 `platform_device`
-2. 编写并注册 `platform_driver`
-3. 通过 `of_match_table` 匹配设备树节点
-4. 实现 `probe/remove` 等接口
+- 启动业务服务
+- 网络初始化
+- 系统自检
+- 产测脚本
+- 升级脚本
----
+常见建议:
-## 根文件系统构建
+- 关键步骤输出清晰日志
+- 带返回值检查
+- 对文件路径、权限和挂载状态做显式判断
-### 常见文件系统类型
+### 交叉编译相关命令
-- ext3/ext4:标准 Linux 文件系统
-- squashfs:只读压缩文件系统,适合嵌入式
-- initramfs:内存文件系统
+嵌入式 Linux 开发中最重要的不是“在板子上编译”,而是“在主机上交叉编译后部署到板子”。
-### 文件系统布局(典型)
+常见命令和环境变量:
-```
-/
-├── bin/ → 常用命令
-├── sbin/ → 系统工具
-├── etc/ → 配置文件
-├── dev/ → 设备节点
-├── lib/ → 库文件
-├── proc/ → 内核虚拟文件系统
-├── sys/ → 设备/驱动信息
-├── usr/ → 用户软件
-├── tmp/ → 临时目录
-└── home/ → 用户主目录
+```bash
+export ARCH=arm
+export CROSS_COMPILE=arm-linux-gnueabihf-
+make menuconfig
+make -j8
```
-### 构建方式
+理解交叉编译时要特别关注:
-- BusyBox + 自制文件结构
-- Buildroot:快速构建定制系统
-- Yocto:更灵活、工业级构建方案
+- 主机架构与目标架构不同
+- sysroot 的作用
+- 链接的是目标平台库,而不是主机库
---
-## 工具链与调试手段
+## Linux 驱动开发模型
-### 交叉编译工具链
+Linux 驱动不是简单“写寄存器”,它需要适配内核框架和总线模型。
-- gcc-arm-linux-gnueabi
-- arm-none-eabi-gcc
-- 使用环境变量指定:
-```bash
-export CROSS_COMPILE=arm-linux-
-```
+常见模型包括:
-### GDB 调试
+- platform driver
+- I2C driver
+- SPI driver
+- character device
+- network driver
-- GDB Server + Remote Debug
-```bash
-gdb-multiarch vmlinux
-target remote :1234
-```
+驱动开发时通常要理解:
-### 常用调试工具
+- 设备如何被匹配到驱动
+- probe / remove 生命周期
+- 中断怎么注册
+- 资源如何申请与释放
-| 工具 | 用途 |
-|-------------|----------------------------|
-| GDB | 程序级调试 |
-| strace | 跟踪系统调用 |
-| dmesg | 内核日志查看 |
-| ldd | 查看依赖的库文件 |
-| top / htop | 查看系统资源使用情况 |
-| lsmod/insmod| 加载/查看内核模块 |
+如果这部分和 MCU 驱动开发相比感觉更复杂,原因就在于 Linux 驱动不仅要操作硬件,还要服从内核的统一模型。
---
-## 常见开发平台
+## 根文件系统构建
-| 平台 | 特点 |
-|-------------|------------------------------|
-| Raspberry Pi | 社区活跃,支持 Linux 全栈 |
-| Allwinner / Rockchip | 国产主控,适配良好 |
-| BeagleBone | 支持 PRU、实时协处理器 |
-| STM32MP1 | 支持 Linux + Cortex-M 协同 |
+根文件系统(RootFS)是用户空间的基础运行环境。
----
+它通常包括:
+
+- `/bin`、`/sbin`、`/usr`
+- 动态库
+- 启动脚本
+- 配置文件
+- 业务程序
-### 嵌入式系统安全基础
-1. 威胁模型分析
-- 物理攻击:
- - 探针访问调试接口(JTAG/SWD)读取 Flash 内容。
- - 电压 / 时钟干扰导致程序异常(故障注入攻击)。
-- 网络攻击:
- - 中间人攻击(MITM)篡改通信数据。
- - 恶意固件注入(利用未加密 OTA 通道)。
-- 软件攻击:
- - 缓冲区溢出执行恶意代码。
- - 逆向工程获取算法逻辑(如加密密钥)。
+常见构建方式:
-2. 安全设计原则
-- 最小权限原则:
+- BusyBox:提供轻量命令集合
+- Buildroot:适合快速构建中小型系统
+- Yocto:适合大型产品和复杂供应链管理
-每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
+选择哪种方式,取决于:
-- 防御纵深:
+- 项目规模
+- 长期维护成本
+- 第三方包复杂度
-多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
+---
+
+## 工具链与调试手段
-- 故障安全:
+常见调试手段包括:
-系统在异常情况下自动进入安全状态(如看门狗复位)。
+- 串口日志
+- `dmesg`
+- GDB / gdbserver
+- `strace`
+- `perf`
+- `tcpdump`
+
+调试 Embedded Linux 时,最好同时具备三种视角:
+
+1. 启动阶段视角:Bootloader、内核日志、挂载过程
+2. 用户态视角:进程、服务、文件系统、网络
+3. 驱动视角:中断、寄存器、probe 过程、内核日志
---
-### 安全启动(Secure Boot)
+## 常见开发平台
-> 保证启动时加载的固件是可信的
+典型平台包括:
-1. 基本原理
-```plaintext
-BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
-```
-- 信任链传递:
-
-每个阶段只信任经过上一阶段验证的代码。
-
-2. 数字签名验证流程
-```c
-// 简化的签名验证伪代码
-bool VerifyFirmwareSignature(uint8_t *firmware, uint32_t size, uint8_t *signature) {
- // 1. 从OTP读取可信根公钥
- const uint8_t *trusted_public_key = GetTrustedPublicKey();
-
- // 2. 计算固件哈希值
- uint8_t calculated_hash[32];
- SHA256(firmware, size, calculated_hash);
-
- // 3. 使用公钥解密签名获取原始哈希
- uint8_t decrypted_hash[32];
- RSA_PKCS1_Verify(trusted_public_key, signature, decrypted_hash);
-
- // 4. 比较哈希值
- return (memcmp(calculated_hash, decrypted_hash, 32) == 0);
-}
-```
-3. STM32 Secure Boot 实现
-- 选项字节配置:
-```c
-// 启用读保护(RDP)
-HAL_FLASH_OB_Unlock();
-FLASH_OBProgramInitTypeDef obInit = {0};
-obInit.OptionType = OPTIONBYTE_RDP;
-obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
-HAL_FLASHEx_OBProgram(&obInit);
-HAL_FLASH_OB_Lock();
-```
-- TrustZone 配置(适用于 STM32L5 等支持型号):
-```c
-// 配置安全/非安全区域
-MPU_Region_InitTypeDef MPU_InitStruct = {0};
-
-// 配置SRAM为安全区域
-MPU_InitStruct.Number = MPU_REGION_0;
-MPU_InitStruct.BaseAddress = 0x20000000;
-MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
-MPU_InitStruct.SubRegionDisable = 0x00;
-MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
-MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
-MPU_InitStruct.DisableExec = DISABLE;
-MPU_InitStruct.IsShareable = ENABLE;
-MPU_InitStruct.IsCacheable = DISABLE;
-MPU_InitStruct.IsBufferable = DISABLE;
-HAL_MPU_ConfigRegion(&MPU_InitStruct);
-```
+- STM32MP1:MCU + MPU 结合,适合工业和 HMI
+- NXP i.MX 系列:常见于图形与多媒体终端
+- Rockchip:适合高性能嵌入式和显示场景
+- Allwinner:消费类和轻量终端较常见
+- Raspberry Pi:学习和原型验证方便
----
+学习时不必急着覆盖所有平台,先选一个成熟开发板打通完整流程更重要。
-### 固件加密与防逆向
+---
-1. **AES 加密固件**,防止泄露源码逻辑
+## 嵌入式系统安全基础
-- 加密流程:
- - 开发阶段:使用工具链(如 GCC 插件)加密固件。
- - 部署阶段:Bootloader 解密后加载到 RAM 执行。
-- 密钥管理:
- - 主密钥存储在 OTP(一次性可编程)区域。
- - 会话密钥通过主密钥派生(如 AES-KDF)
+### 安全启动(Secure Boot)
-2. Flash 读保护(RDP)
+安全启动的核心是:只允许运行可信的固件。
-| RDP 级别 | 保护效果 | 可逆性 |
-|------------|------------------------------------------|-------------------------|
-| Level 0 | 无保护(默认) | 是 |
-| Level 1 | 禁止调试接口,Flash 只能运行不能读取 | 降级会擦除所有 Flash |
-| Level 2 | 永久禁止调试接口和 Flash 读取 | 不可逆 |
+基本思路:
-3. 代码混淆技术
-- 控制流平坦化:
+1. 建立硬件信任根
+2. 验证 Bootloader
+3. 验证内核和系统镜像
+4. 验证应用或升级包
-将线性代码转换为基于状态机的结构,增加逆向难度。
+它解决的是“设备启动的第一步就被篡改”的风险。
-- 指令替换:
+### 固件加密与防逆向
-用等效指令序列替换关键操作(如a+b替换为a-(-b))。
+常见做法:
----
+- 固件加密存储
+- 读保护和调试口限制
+- 敏感算法放在安全区域
+- 对升级包做签名和校验
### 权限隔离与防护
-1. MPU(内存保护单元)配置
-```c
-// 配置MPU保护关键数据区
-void ConfigureMPU(void) {
- // 使能MPU
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
-
- // 配置区域0保护关键代码区
- MPU_Region_InitTypeDef MPU_InitStruct = {0};
- MPU_InitStruct.Number = MPU_REGION_0;
- MPU_InitStruct.BaseAddress = 0x08000000; // Flash起始地址
- MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
- MPU_InitStruct.SubRegionDisable = 0x00;
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
- MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO; // 特权可读写,用户只读
- MPU_InitStruct.DisableExec = DISABLE;
- MPU_InitStruct.IsShareable = DISABLE;
- MPU_InitStruct.IsCacheable = DISABLE;
- MPU_InitStruct.IsBufferable = DISABLE;
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
-}
-```
-2. TrustZone 安全域隔离
-- 安全资产分类:
-
-| 类别 | 示例 | 存储位置 |
-|------------|------------------------------|------------------|
-| 密钥 | TLS 私钥、加密密钥 | 安全 SRAM |
-| 敏感算法 | 密码验证、加密函数 | 安全代码区 |
-| 安全服务 | OTA 签名验证、证书管理 | 安全任务 |
-
-- 安全 / 非安全通信:
-```c
-// 从非安全代码调用安全服务
-__attribute__((section(".nonsecure_call")))
-uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
- // 通过SVC指令切换到安全模式
- __asm("SVC #0");
- // 返回值通过R0传递
-}
-```
+Embedded Linux 的优势之一是可以利用成熟的权限体系:
----
+- 用户权限隔离
+- 文件权限限制
+- 容器或沙箱
+- SELinux / AppArmor(在较复杂系统中)
### Bootloader 开发建议
-- 通用功能:下载、校验、重启、回滚
-- 支持双分区升级(Slot A / Slot B)
-- 防止电量中断、写失败后的砖机风险
-- 可设置升级标志位(Upgrade Flag)
+Bootloader 设计上建议尽量考虑:
+
+- 下载失败后的恢复能力
+- 双分区升级
+- 升级标志位和回滚逻辑
+- 日志与错误码
---
-## 小结
+## 本章小结
+
+嵌入式 Linux 的难点在于系统层次更多、边界更复杂。把启动流程、设备树、驱动模型和根文件系统这几条主线串起来之后,后续学习 BSP、Yocto、内核移植会容易很多。
-嵌入式 Linux 是从单片机迈向高性能系统开发的核心门槛,掌握其启动流程、设备树结构与驱动框架是后续学习内核裁剪、系统移植与 IoT 平台开发的基础。
diff --git a/06-NetworkIot/README.md b/06-NetworkIot/README.md
index 363d316..499f1f6 100644
--- a/06-NetworkIot/README.md
+++ b/06-NetworkIot/README.md
@@ -1,653 +1,347 @@
+# 第六层:网络通信与物联网协议(Network & IoT)
+网络与物联网章节的重点是把“本地通信、无线接入、网络协议、云端接入和 OTA 运维”串成一条完整链路。阅读时要始终带着“数据从设备如何流到云端”这个视角。
-# 第六层:网络通信与物联网协议(Network & IoT)
+建议学习目标:
+
+- 区分 UART、Socket、BLE、Wi-Fi、LoRa、ZigBee 等通信方式的边界。
+- 理解 MQTT、HTTP/HTTPS、CoAP 等协议适合什么场景。
+- 建立 TCP/IP、设备接入、云平台对接和 OTA 的整体认知。
+- 能对连接稳定性、安全性和远程升级风险有基本判断。
-本模块聚焦于嵌入式系统中的通信机制和物联网协议栈,涵盖串口通信、无线模块、MQTT 等协议到云平台对接,适用于 IoT 产品开发全流程。
+阅读建议:先学本地通信和网络基础,再看物联网协议和云接入,最后理解 OTA 与安全通信。
---
## 串口通信与Socket通信
### 串口通信
-#### 1. 什么是串口通信?
-
-**串口通信**是一种历史悠久且广泛使用的**点对点、串行、异步**通信方式。它指的是数据在通信线上**一位一位地**顺序传输。最常见的串口标准是 **RS-232**,但在嵌入式系统中,更多使用 **TTL 电平的 UART** (Universal Asynchronous Receiver/Transmitter) 接口。
-
-它的特点是**简单、直接**,常用于设备间的短距离连接,比如单片机与电脑、两个单片机之间、或者连接一些外部模块(GPS、蓝牙模块、传感器等)。
-
-#### 2. 核心特点
-* **串行传输:** 数据一位接一位地在线上传输,而不是并行传输多位。这减少了传输线数量,降低了成本,但在相同频率下速度比并行慢。
-* **异步通信:** 发送方和接收方不需要共用同一个时钟信号。双方通过约定好的**波特率**(每秒传输的位数)来同步数据。每个数据帧通常包含起始位、数据位、校验位和停止位。
-* **点对点:** 通常只连接两个设备进行通信。
-* **硬件接口:** 涉及到物理引脚连接,如 RX (接收数据)、TX (发送数据)、GND (地线) 等。
-* **传输距离:** 相对较短,RS-232 理论上是 15 米,但通过 RS-485 等差分信号标准可以达到更远距离。
+串口通信是嵌入式开发最基础的通信方式之一,常见于:
-#### 3. 工作原理
+- 调试日志输出
+- 模块通信
+- 下载协议
+- 上位机与设备交互
-在异步串口通信中,数据以**帧**的形式传输:
+它的核心特点是:
-1. **空闲状态:** 数据线通常保持在高电平。
-2. **起始位:** 发送方发送一个低电平信号,表示一个数据帧的开始。
-3. **数据位:** 紧接着是 5 到 9 位的数据,通常是 8 位。先发低位。
-4. **校验位(可选):** 用于检测数据传输中是否出错(奇校验或偶校验)。
-5. **停止位:** 发送一个高电平信号,表示一个数据帧的结束。可以是一位、一位半或两位。
-6. **空闲状态:** 信号线回到高电平,等待下一个数据帧。
+- 点对点
+- 串行传输
+- 通常为异步通信
+- 实现简单、成本低
-#### 4. 常见应用
+串口调试时最常见的关注点是:
-* **嵌入式设备调试和数据传输:** 单片机与上位机(PC)之间传输调试信息、配置参数、传感器数据等。
-* **工业控制:** 连接 PLC、仪器仪表、HMI (人机界面) 等。
-* **GPS、蓝牙、WiFi 模块:** 许多这些模块通过串口与主控器进行数据交互。
-* **传统外设连接:** 早期鼠标、调制解调器等。
-
-#### 5. 优点与缺点
-
-* **优点:** 简单、成本低、实现容易,对于资源有限的设备非常适用。
-* **缺点:** 传输速度相对较慢(特别是与以太网相比)、传输距离有限、点对点通信不适合多设备组网。
+- 波特率是否一致
+- 数据位、停止位、校验位是否一致
+- 电平标准是否匹配(TTL / RS-232 / RS-485)
### Socket网络通信
-#### 1. 什么是 Socket 通信?
-
-**Socket (套接字)** 是**网络通信的抽象层**,是**应用层与传输层之间**的接口,它使得应用程序能够通过网络进行数据传输。你可以把 Socket 理解为**连接网络两端进程的“插座”**。通过 Socket,两个独立的进程(可以在同一台机器上,也可以在不同机器上)可以在网络上相互发送和接收数据。
-Socket 通信是构建大多数网络应用(如网页浏览、文件传输、即时通讯、网络游戏)的基础。它通常基于 **TCP/IP 协议栈**。
+Socket 是应用层访问网络协议栈的接口。它不是某个单独协议,而是“让程序能通过 TCP / UDP 进行通信”的抽象。
-#### 2. 核心特点
+典型模式:
-* **网络化:** 基于网络协议(如 TCP/IP),可以在全球范围内进行通信。
-* **全双工:** 通常情况下,两端可以同时发送和接收数据。
-* **面向连接或无连接:**
- * **TCP Socket (流套接字):** **面向连接、可靠、基于字节流**。在数据传输前需要建立连接(三次握手),数据传输有顺序、无丢失、无重复保证。
- * **UDP Socket (数据报套接字):** **无连接、不可靠、基于数据报**。无需建立连接即可发送数据,数据可能丢失、乱序、重复。
-* **IP 地址和端口号:** Socket 通信通过 **IP 地址** 确定目标主机,通过 **端口号** 确定目标主机上的具体应用程序进程。
-* **客户端/服务器模式:** 大多数 Socket 通信遵循客户端/服务器模式。服务器程序在特定端口监听连接请求,客户端程序连接到服务器的特定端口发送请求。
+- TCP Socket:面向连接,可靠,适合命令、控制、上传等场景
+- UDP Socket:无连接,时延小,适合广播、音视频、轻量消息
-#### 3. 工作原理 (以 TCP Socket 为例)
+对嵌入式开发者来说,Socket 重点不是背函数名,而是理解:
-1. **服务器端:**
- * **`socket()`:** 创建一个 Socket。
- * **`bind()`:** 将 Socket 绑定到一个本地 IP 地址和端口号上。
- * **`listen()`:** 使 Socket 进入监听状态,等待客户端连接。
- * **`accept()`:** 阻塞等待客户端连接。当有客户端连接时,接受连接并返回一个新的 Socket,用于与该客户端通信。
- * **`recv()/send()`:** 通过接受的 Socket 与客户端收发数据。
- * **`close()`:** 关闭 Socket。
-2. **客户端:**
- * **`socket()`:** 创建一个 Socket。
- * **`connect()`:** 连接到服务器的 IP 地址和端口号。
- * **`send()/recv()`:** 与服务器收发数据。
- * **`close()`:** 关闭 Socket。
+- 谁是客户端,谁是服务端
+- 使用 TCP 还是 UDP
+- 连接建立后如何处理超时、断线和重连
-#### 4. 常见应用
-
-* **Web 服务器:** HTTP 协议就是基于 TCP Socket 实现的。
-* **文件传输:** FTP (文件传输协议) 也是基于 TCP Socket。
-* **即时通讯:** QQ、微信等聊天软件底层通常使用 Socket 进行通信。
-* **网络游戏:** 大多数在线游戏使用 Socket 进行数据交互。
-* **物联网云平台通信:** 许多物联网设备(如智能家居网关、工业控制器)通过 Socket 连接到云平台进行数据上传和指令接收(如 MQTT 协议底层就运行在 TCP Socket 上)。
-
-#### 5. 优点与缺点
+### 串口通信 vs Socket 通信:对比总结
-* **优点:** 跨网络通信能力强、支持复杂的网络应用、可靠性高(TCP)、适合大数据量传输。
-* **缺点:** 相对复杂,需要理解网络协议栈;资源开销相对较大(TCP 连接维护开销)。
+| 维度 | 串口通信 | Socket 通信 |
+| --- | --- | --- |
+| 连接范围 | 本地或短距离 | 局域网 / 广域网 |
+| 协议复杂度 | 低 | 高 |
+| 实现成本 | 低 | 相对更高 |
+| 典型用途 | 调试、模块直连 | 联网、远程服务、云端接入 |
-### 串口通信 vs Socket 通信:对比总结
+可以简单理解为:
-| 特性 | 串口通信 (UART/RS-232) | Socket 通信 (TCP/UDP) |
-| :----------- | :------------------------------------- | :------------------------------------------------ |
-| **通信范围** | **局域性** (通常是设备内部或短距离有线连接) | **广域性** (可通过网络在不同设备、服务器之间通信) |
-| **传输方式** | **物理层/数据链路层** (硬件直接控制) | **网络层/传输层之上** (通过操作系统提供的接口) |
-| **协议栈** | 通常无复杂协议栈,仅定义物理电平和数据帧格式 | 基于 **TCP/IP 协议栈** (IP, TCP, UDP等) |
-| **连接对象** | **物理设备** (点对点) | **网络进程** (通过 IP 地址和端口号识别) |
-| **抽象程度** | **低** (直接操作硬件寄存器或库) | **高** (抽象为文件描述符,通过系统调用操作) |
-| **复杂性** | **相对简单** | **相对复杂** (需理解网络编程和协议) |
-| **应用场景** | 调试、嵌入式设备间通信、简单传感器连接 | 互联网应用、物联网云连接、复杂网络数据传输 |
+- 串口更像“设备内或设备旁边”的通信
+- Socket 更像“设备与网络世界”的通信
---
## 无线通信协议
-### 1. Wi-Fi
-**Wi-Fi** 是一种基于 IEEE 802.11 标准的无线局域网技术,广泛应用于家庭、办公室和公共场所,为设备提供高速、可靠的无线网络连接。在嵌入式领域,Wi-Fi 模块使得设备能够直接连接互联网或作为局部热点。
-
-#### **ESP32 支持 STA / AP 模式,常用于联网或热点传输**
-
-* **STA (Station) 模式:** 在这种模式下,ESP32 模块就像我们家里的手机或电脑一样,作为一个**客户端**连接到现有的 Wi-Fi 路由器(AP,Access Point),从而接入互联网。这让嵌入式设备能够上传数据到云端、接收远程指令,实现物联网功能。
-* **AP (Access Point) 模式:** 在这种模式下,ESP32 模块会**创建自己的 Wi-Fi 网络**,充当一个**热点**。其他设备(如手机、电脑)可以连接到这个热点,与 ESP32 进行点对点通信。这常用于设备配网(让手机连接 ESP32 设置其连接家里的 Wi-Fi)、设备调试、或者构建一个不依赖外部路由器的本地控制网络。
-* **STA + AP 共存模式:** ESP32 还可以同时运行 STA 和 AP 模式。例如,它既可以连接到家里的路由器联网,同时又提供一个临时的热点供手机连接进行本地控制。
-
-#### **Wi-Fi 应用场景**
-
-* **SmartConfig:** 这是一种**智能配网技术**,允许用户通过手机 App 快速配置 ESP32 连接到家里的 Wi-Fi 网络,而无需在设备上输入复杂的 Wi-Fi 密码。手机 App 将 Wi-Fi 凭证通过特定方式(如广播或多播)发送,ESP32 侦听到并解析这些凭证完成配网。
-* **ESP-NOW:** 这是乐鑫(Espressif)推出的一种**无连接**的 Wi-Fi 通信协议。它允许 ESP32 设备在**没有路由器的情况下**,直接与其他 ESP32 设备进行短距离、快速、低功耗的数据交换。非常适合需要设备间直接通信,且对网络基础设施依赖度低的场景,如智能家居中的灯控、传感器网络等。
-* **HTTP Server 应用:** ESP32 可以搭建一个**轻量级的 HTTP 服务器**。当 ESP32 处于 AP 模式时,其他设备连接到它,就可以通过浏览器访问 ESP32 提供的网页界面,进行设备配置、状态监控或远程控制。当 ESP32 处于 STA 模式时,也可以作为 HTTP 客户端与远程服务器进行数据交互(如上传传感器数据到服务器,或从服务器获取控制指令)。
-
-### 2. BLE(蓝牙低功耗)
-**BLE (Bluetooth Low Energy)** 是蓝牙技术联盟在蓝牙 4.0 版本中推出的一种**超低功耗无线技术**。它专为物联网设备设计,特点是功耗极低、成本低廉,适用于电池供电、数据传输量小且不频繁的场景。
+### Wi-Fi
-#### **GATT 协议模型:服务、特征值、通知机制**
+Wi-Fi 适合需要较高带宽或直连局域网 / 互联网的场景。
-BLE 的核心是 **GATT (Generic Attribute Profile) 协议**,它定义了数据组织和交互的方式:
+优点:
-* **Service (服务):** 服务是**相关数据的集合**。例如,一个心率监测器可能提供一个“心率服务”,一个温度传感器可能提供一个“环境传感服务”。每个服务都有一个唯一的 **UUID (Universally Unique Identifier)**。
-* **Characteristic (特征值):** 特征值是**服务中的具体数据项**。它是设备可读写的最小数据单元。例如,在“心率服务”中,可能有一个“心率测量值”的特征值;在“环境传感服务”中,可能有一个“温度”特征值。每个特征值也拥有唯一的 UUID。特征值有不同的属性,如可读 (Read)、可写 (Write)、可通知 (Notify) 等。
-* **Descriptor (描述符):** 描述符是对特征值的进一步描述,例如单位、范围等。
-* **通知机制 (Notification):** 订阅方(通常是手机或网关)可以订阅特征值。当设备端(如传感器)的某个特征值发生变化时,它会自动将最新的值**推送**给所有订阅了该特征值的客户端,而无需客户端主动轮询。这大大降低了功耗。
+- 带宽高
+- 网络生态成熟
+- 易于接入现有路由器和云服务
-#### **使用 nRF52、ESP32 等平台进行广播、连接和数据交互**
+缺点:
-* **广播 (Advertising):** BLE 设备可以通过广播发送少量数据包,而无需建立连接。这常用于设备发现、发送传感器快照数据、或作为信标(Beacon)。
-* **连接 (Connection):** 当需要进行更频繁或双向的数据交换时,BLE 设备会建立一个连接。一个设备作为**主机 (Master)**,另一个作为**从机 (Slave)**。连接建立后,双方可以根据 GATT 协议模型进行数据交互。
-* **数据交互:** 通过读写特征值,或者通过订阅通知/指示 (Indication) 来实现数据的双向传输。例如,手机 App 读取温度传感器的温度特征值,或者订阅其温度变化的通知。
-* **平台选择:**
- * **Nordic nRF52 系列:** 在低功耗 BLE 领域非常流行,功耗表现和 RF 性能优异,SDK 和开发工具链成熟。
- * **ESP32 系列:** 除了 Wi-Fi,ESP32 也集成了强大的 BLE 功能,可以同时支持 Wi-Fi 和 BLE,在物联网应用中具有成本和集成度优势。
+- 功耗较高
+- 配网和稳定性处理更复杂
-### 3. LoRa / ZigBee
-这两种协议都属于低功耗广域网(LPWAN)或低功耗短距离无线网络,主要用于传感器网络和远程数据采集。
+典型用途:
-#### **LoRa (Long Range) - 长距离通信:适用于室外传感器**
+- 智能家居
+- 摄像头
+- 网关设备
-* **特点:** LoRa 是一种**物理层**的调制技术,它允许数据在极低的功耗下实现**超长距离(数公里到数十公里)**的传输,通常用于室外环境。LoRaWAN 是基于 LoRa 物理层之上的一套完整网络协议,定义了设备、网关和网络服务器之间的通信规则。
-* **优势:** 功耗极低、传输距离远、抗干扰能力强。
-* **劣势:** 传输速率低,不适合传输大量数据。
-* **应用:** 智慧农业(土壤温湿度监测)、智慧城市(停车位监测、垃圾桶液位监测)、工业监测(偏远区域设备状态监控)等。
-* **使用 Semtech SX1278 / LoRa 模块通信:** Semtech 是 LoRa 技术的核心提供商,其 SX127x 系列芯片是 LoRa 模块中常用的收发器。在嵌入式开发中,通常使用集成这些芯片的 LoRa 模块(如基于 SX1278 的模块)通过 SPI 或 UART 接口与主控器(如 STM32、ESP32)通信,进行数据的发送和接收。需要关注 LoRaWAN 协议栈的实现。
+### BLE(蓝牙低功耗)
-#### **ZigBee - 短距离、低功耗、网状网络:适用于室内传感器**
+BLE 适合低功耗、短距离、人机交互型场景。
-* **特点:** ZigBee 是一种基于 IEEE 802.15.4 标准的**低功耗、短距离、自组织、网状网络**协议。它适合在室内或有限区域内构建大量的节点网络。
-* **优势:**
- * **自组织、自愈合的网状网络:** 节点之间可以相互转发数据,扩大通信范围,即使部分节点故障,网络也能自我修复。
- * **超低功耗:** 适合电池供电的传感器,电池寿命可达数年。
- * **设备容量大:** 一个 ZigBee 网络可以支持大量的设备。
-* **劣势:** 传输距离相对有限(几十到几百米)、传输速率低。
-* **应用:** 智能家居(灯控、门锁、窗帘)、工业无线传感器网络、医疗监测等。
-* **使用 ZigBee 模块通信:** 通常使用集成了 ZigBee 协议栈的模块(如 TI 的 CC2530 系列、NXP 的 JN5169 系列)。这些模块通常通过串口(UART)与主控器通信。开发者需要了解 ZigBee 网络的**组网方式(协调器、路由器、终端设备)、地址分配、数据传输(点对点、广播)和通信帧格式解析**等。例如,发送或接收一个数据帧时,需要构建或解析其头部信息(目标地址、源地址、命令类型等)和有效载荷。
+典型用途:
----
-
-## 物联网协议栈
+- 可穿戴设备
+- 手机配网
+- 传感器广播
+- 近距离控制
-#### MQTT 协议详解
+重点关注:
-**MQTT (Message Queuing Telemetry Transport)** 是一种**轻量级、发布/订阅模式**的消息传输协议。它专为**资源受限的设备**(如物联网 IoT 设备)以及**低带宽、高延迟或不稳定网络**环境而设计。因其高效、可靠地传输少量数据的能力,MQTT 在物联网领域得到了广泛应用。
+- 广播与连接模式
+- 功耗预算
+- 手机兼容性
-#### 1. 核心概念与架构
+### LoRa / ZigBee
-MQTT 协议由以下三个主要组件构成:
+这类协议更强调低功耗和广覆盖,适合分布式传感器和工业 / 农业场景。
-* **发布者 (Publisher)**:产生并发送消息的客户端设备或应用程序。发布者只负责将消息发送到**主题(Topic)**,不关心谁会接收消息。
-* **订阅者 (Subscriber)**:接收消息的客户端设备或应用程序。订阅者通过**订阅一个或多个主题**来表明对哪些消息感兴趣。
-* **消息代理/服务器 (Broker)**:MQTT 协议的核心。它负责接收发布者发送的消息,并根据消息的**主题**将消息路由到所有订阅了该主题的客户端。Broker 还处理客户端的连接、断开、订阅、取消订阅等请求,并管理会话状态。
+LoRa 特点:
-**工作流程概览:**
+- 覆盖远
+- 带宽低
+- 适合稀疏上报
-* 客户端(发布者或订阅者)与 MQTT Broker 建立 **TCP 连接**。
-* 发布者将消息发送给 Broker,消息包含一个**主题**和一个**有效载荷(Payload)**。
-* Broker 接收到消息后,根据消息的**主题**,将其转发给所有订阅了该主题的订阅者。
-* 订阅者接收到 Broker 转发的消息。
+ZigBee 特点:
-#### 2. 发布/订阅 (Publish/Subscribe) 模式
+- 适合组网
+- 节点间可形成 Mesh
+- 常见于智能家居和工业网络
-这是 MQTT 与传统请求/响应(Request/Response,如 HTTP)模式最显著的区别,也是其优势所在:
+---
-* **解耦性:** 发布者和订阅者之间无需直接知道对方的存在,它们只与 Broker 通信。这使得系统更加灵活、可伸缩、易于维护。
-* **异步通信:** 消息是异步发送和接收的,发布者无需等待订阅者的响应。这对于实时性要求不高但需要持续传输数据的场景非常有效。
-* **一对多通信:** 一个发布者发送的消息可以同时被多个订阅者接收,非常适合广播和通知场景。
-* **主题 (Topic):**
- * MQTT 使用主题来分类和路由消息。主题是层级的字符串,例如 `home/livingroom/temperature` 或 `factory/line1/machine/status`。
- * 主题支持**通配符**:
- * `+`:**单层通配符**,表示一个层级。例如 `home/+/temperature` 可以匹配 `home/livingroom/temperature` 和 `home/bedroom/temperature`。
- * `#`:**多层通配符**,表示零或多层。例如 `home/#` 可以匹配 `home/livingroom/temperature`、`home/bedroom/light` 以及 `home` 下的所有子主题。
+## 物联网协议栈
-#### 3. 服务质量等级 (Quality of Service - QoS)
+### MQTT
-MQTT 提供三种 QoS 等级,以满足不同场景下对消息可靠性的要求,实现发布者与 Broker、Broker 与订阅者之间的消息传递保证:
+MQTT 是典型的轻量级发布 / 订阅协议,广泛用于 IoT。
-* **QoS 0: At Most Once (最多一次)**
- * 消息发送后即“即发即弃”,不保证消息一定能到达,也不会重试。
- * **优点:** 传输速度最快,开销最小。
- * **适用场景:** 对实时性要求高、偶尔丢失消息可以接受的场景,如环境传感器数据、非关键性日志等。
+核心概念:
-* **QoS 1: At Least Once (至少一次)**
- * 消息至少会被送达一次,但可能会重复送达。
- * Broker 在收到消息后会发送确认包(PUBACK)。如果发布者在规定时间内未收到 PUBACK,会重发消息。
- * **优点:** 保证消息不丢失,适用于重要但不介意重复的消息。
- * **适用场景:** 命令控制、重要警报,但上层应用需要处理消息重复。
+- Broker
+- Topic
+- Publisher
+- Subscriber
-* **QoS 2: Exactly Once (只交付一次)**
- * 消息只会被送达一次,保证不丢失也不重复。这是最高级的 QoS。
- * 通过四次握手(PUBLISH, PUBREC, PUBREL, PUBCOMP)实现。
- * **优点:** 最高可靠性,保证消息的唯一性和完整性。
- * **适用场景:** 金融交易、计费数据、关键控制命令等对可靠性有严格要求的场景。
- * **缺点:** 传输开销最大,效率最低。
+优点:
-#### 4. 会话管理与持久会话 (Session Management & Persistent Sessions)
+- 协议轻
+- 适合弱网络
+- 易做云端消息分发
-* **Clean Session (清洁会话):**
- * 当客户端连接 Broker 时,可以设置 `Clean Session` 标志为 `true` 或 `false`。
- * **`Clean Session = true` (非持久会话/瞬时会话):** 客户端每次连接都是一个新的会话。断开连接后,Broker 会清除该客户端的所有订阅和未发送的消息。当客户端重新连接时,会话是全新的,需要重新订阅。
- * **`Clean Session = false` (持久会话):** 客户端断开连接后,Broker 会保存其会话状态,包括订阅列表、QoS 1 和 QoS 2 未发送/未确认的消息。当客户端以相同的 `Client ID` 重新连接时,会话会恢复,Broker 会发送离线期间的消息。
-* **应用:** 持久会话对于网络不稳定、设备可能频繁掉线的物联网场景非常有用,可以确保设备即使离线也能收到重要的离线消息。
+适合场景:
-#### 5. 遗嘱消息 (Last Will and Testament - LWT)
+- 传感器上报
+- 远程控制
+- 云端状态同步
-* 客户端在连接 Broker 时,可以注册一条“遗嘱消息”(Will Message)。
-* 如果客户端在非正常断开连接(如设备故障、网络中断)而没有主动发送 DISCONNECT 报文,Broker 就会自动发布这条预先注册的遗嘱消息到指定的主题。
-* **作用:** 允许其他客户端(通过订阅该遗嘱主题)感知到某个设备意外离线,从而可以采取相应措施(如状态更新、报警)。
+### HTTP / HTTPS
-#### 6. 安全性 (Security)
+HTTP 更适合请求 / 响应式交互,例如:
-MQTT 本身运行在 TCP/IP 协议之上,其安全性主要通过以下层级来保障:
+- REST API
+- 配置下发
+- 固件下载
+- 日志上传
-* **传输层安全 (TLS/SSL):** 这是最常见的安全方式。MQTT 可以通过 TCP/TLS(即 MQTT over SSL/TLS)进行加密通信,确保数据在传输过程中的机密性和完整性,防止窃听和篡改。
-* **应用层认证与授权:**
- * **用户名/密码认证:** 客户端在连接 Broker 时提供用户名和密码进行身份验证。
- * **客户端证书认证:** 更高级别的认证方式,通过 X.509 客户端证书进行双向身份验证。
- * **ACL (Access Control List) 授权:** Broker 根据配置的 ACL 规则,限制客户端只能发布或订阅特定的主题,防止未经授权的访问和操作。
-* **MQTT 5.0 的增强安全特性:** MQTT 5.0 引入了更多安全功能,如增强认证机制、会话过期、用户属性等,进一步提升了协议的安全性。
+HTTPS 在 HTTP 之上增加 TLS 加密,适合对安全性有要求的联网设备。
-#### 7. MQTT 协议包结构 (Control Packet Structure)
+实际项目中,HTTP / HTTPS 经常与 MQTT 共存:
-MQTT 控制报文由三部分组成:
+- MQTT 负责长连接消息流
+- HTTP / HTTPS 负责配置、查询和文件下载
-* **固定报头 (Fixed Header)**:所有 MQTT 控制报文都包含,占 1-5 个字节。包含:
- * **报文类型 (Message Type)**:4 位,表示报文的类型(如 CONNECT, PUBLISH, SUBSCRIBE 等)。
- * **标志位 (Flags)**:4 位,根据报文类型有不同含义(如 QoS 等级、DUP 标志等)。
- * **剩余长度 (Remaining Length)**:可变长度编码,表示可变报头和有效载荷的总长度。
-* **可变报头 (Variable Header)**:部分报文包含,根据报文类型不同而不同。例如,PUBLISH 报文的可变报头包含主题名和报文标识符。
-* **有效载荷 (Payload)**:部分报文包含,承载实际数据。例如,PUBLISH 报文的有效载荷就是实际要传输的应用数据。
+### CoAP / LwM2M
-由于其紧凑的报文结构和二进制有效载荷,MQTT 在数据传输效率上远优于 HTTP 等协议,尤其适用于数据量小、通信频繁的物联网场景。
+CoAP 面向资源受限设备,基于 UDP,适合低功耗环境。
-#### 8. MQTT vs HTTP (在 IoT 领域)
+LwM2M 则在 CoAP 之上定义了更完整的设备管理模型,常用于:
-| 特性 | MQTT | HTTP |
-| :----------- | :------------------------------------------- | :---------------------------------------------- |
-| **通信模式** | **发布/订阅 (Pub/Sub)**,异步,双向 | **请求/响应 (Request/Response)**,同步,单向 |
-| **连接状态** | **长连接 (Stateful)**,连接建立后可发送多条消息 | **短连接 (Stateless)**,每次请求通常建立新连接 |
-| **消息开销** | **轻量级**,最小报文头 2 字节,效率高 | **相对重**,报文头较大,多为文本格式 |
-| **实时性** | **推送 (Push)**,消息实时送达 | **轮询 (Pull)**,客户端定时请求获取更新,非实时 |
-| **功耗** | 低(长连接减少频繁建连开销) | 高(频繁建连、断连) |
-| **适用场景** | 资源受限设备、低带宽、频繁数据传输、消息推送 | Web 应用、大数据传输、文件下载、一次性请求 |
+- 远程配置
+- 生命周期管理
+- OTA 管理
-**总结:** MQTT 因其轻量、高效、发布/订阅模式和灵活的 QoS,成为物联网设备间通信的理想选择。它很好地解决了 HTTP 在资源受限和网络不稳定环境下的痛点。
+如果项目强调“低功耗 + 远程设备管理”,这组协议很值得了解。
---
-### HTTP / HTTPS
-
-#### HTTP 请求方法(Methods)
+## 安全通信实践
-* **GET**:请求指定资源。常用于获取数据。
-* **POST**:提交数据到服务器,如表单、上传。
-* **PUT**:上传数据,通常是更新资源。
-* **DELETE**:删除指定资源。
-* **HEAD**:与 GET 类似,但不返回响应体。
-* **OPTIONS**:返回服务器支持的请求方法。
-* **TRACE**:诊断请求响应路径,回显请求报文。
-* **CONNECT**:用于建立隧道(如 HTTPS 代理)。
+联网设备不仅要能通信,还要能安全通信。
-#### HTTP 状态码
+常见措施:
-| 分类 | 范围 | 含义 |
-| --- | -------- | -------- |
-| 1xx | 100\~199 | 接收中,继续处理 |
-| 2xx | 200\~299 | 请求成功 |
-| 3xx | 300\~399 | 重定向或更多操作 |
-| 4xx | 400\~499 | 客户端错误 |
-| 5xx | 500\~599 | 服务器错误 |
+- TLS / DTLS
+- 证书或预共享密钥
+- 设备身份认证
+- 防止重放攻击
+- 接口权限控制
-**部分状态码示例:**
+工程中经常出现的误区:
-* 200 OK
-* 201 Created
-* 204 No Content
-* 301 Moved Permanently
-* 302 Found
-* 304 Not Modified
-* 400 Bad Request
-* 401 Unauthorized
-* 403 Forbidden
-* 404 Not Found
-* 500 Internal Server Error
-* 502 Bad Gateway
+- 只顾连通,不顾身份校验
+- 明文传输敏感数据
+- 固件下载不做签名验证
-#### HTTP 长连接与短连接
-
-* **HTTP/1.0** 默认使用短连接(每次请求后断开)
-* **HTTP/1.1** 默认使用长连接(`Connection: keep-alive`)
+---
-#### HTTP 请求报文格式
+## 安全测试
-```
-GET /hello.htm HTTP/1.1
-Host: www.example.com
-User-Agent: Mozilla/5.0
-Accept: */*
-Connection: Keep-Alive
-... 其他头部字段
-```
+安全测试可以从这几个方向入手:
-#### HTTP 响应报文格式
+- 弱口令与默认凭据
+- 未加密通信
+- OTA 包篡改
+- 端口暴露与异常服务
+- 重放与伪造数据包
-```
-HTTP/1.1 200 OK
-Content-Type: text/html
-Content-Length: 158
-Server: Apache
-Date: Sun, 14 Jun 2025 10:00:00 GMT
+对嵌入式联网设备来说,安全测试不必一开始就追求完整渗透测试,但至少要覆盖最基本的身份、传输和升级安全。
-...
-```
+---
-### HTTPS 通信过程
+## TCP/IP 协议栈基础与嵌入式实现
-HTTPS = HTTP + TLS/SSL 加密
+### TCP/IP 协议栈分层结构(四层模型)
-#### 通信过程:
+常见四层模型如下:
-1. 客户端发起 HTTPS 请求(握手开始)
-2. 服务器返回证书(含公钥)
-3. 客户端验证证书合法性
-4. 客户端生成随机对称密钥(用公钥加密传给服务器)
-5. 双方使用对称密钥开始加密通信
+1. 应用层
+2. 传输层
+3. 网络层
+4. 网络接口层
-#### 对称加密(加解密使用同一密钥)
+理解这个分层有助于区分:
-* **DES**(56 位)
-* **AES**(128/192/256 位)
+- MQTT / HTTP 在哪一层
+- TCP / UDP 在哪一层
+- IP / ICMP 在哪一层
-#### 非对称加密(加密/解密使用公钥/私钥)
+### TCP 与 UDP 区别
-* **RSA**:支持加密、签名
-* **DSA**:只支持签名(效率高,但不用于加解密)
+| 特性 | TCP | UDP |
+| --- | --- | --- |
+| 连接方式 | 面向连接 | 无连接 |
+| 可靠性 | 高 | 低 |
+| 时延 | 相对更高 | 更低 |
+| 典型用途 | 控制、文件、云服务 | 广播、音视频、轻量报文 |
-#### 哈希算法(不可逆)
+没有哪一个绝对更好,关键是看场景。
-* **MD5**(128 位)
-* **SHA-1**(160 位)
-* **SHA-256**(256 位)
+### 嵌入式 TCP/IP 协议栈组件
-### CoAP / LwM2M
-- 适合低功耗终端的简化协议,UDP 传输,可压缩
-- 用于 NB-IoT、LwIP 等网络栈中
+嵌入式设备常见协议栈包括:
----
+- lwIP
+- uIP
+- 厂商 SDK 自带网络栈
-### 安全通信实践
-1. TLS 握手优化
-- 预共享密钥(PSK)模式:
-减少证书验证开销,适合资源受限设备。
-```c
-// mbed TLS配置PSK
-mbedtls_ssl_config_set_psk(&ssl_conf,
- psk, // 预共享密钥
- psk_length,
- identity, // 身份标识
- strlen(identity));
-```
-2. 证书管理方案
-- 证书存储:
- - 根证书存储在安全 Flash 区域。
- - 设备证书通过安全通道动态更新。
-- 证书验证:
-```c
-// 验证服务器证书链
-int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
- // 检查证书有效期
- if (mbedtls_x509_crt_check_validity(crt, time(NULL)) != 0) {
- return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
- }
-
- // 检查证书颁发者
- if (!mbedtls_x509_crt_verify(crt, trusted_certs, NULL, NULL, flags, NULL, NULL)) {
- return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
- }
-
- return 0;
-}
-```
+从工程角度看,协议栈最关键的不是“支持多少协议”,而是:
----
+- 是否稳定
+- 内存占用是否可控
+- 是否易于调试
+- 与网卡 / Wi-Fi 模块驱动配合是否成熟
-### 安全测试
-
-1. 固件逆向分析
-- 工具链:
- - Ghidra:反编译二进制文件,生成 C 语言伪代码。
- - IDA Pro:专业逆向工程工具,支持 ARM 架构。
-- 防御措施:
- - 固件加密:使用 AES-256 加密整个固件。
- - 反调试机制:检测调试接口是否被连接。
-```c
-// 检测SWD/JTAG调试接口
-bool IsDebuggerAttached(void) {
- // 读取DBGMCU_IDCODE寄存器
- uint32_t idcode = DBGMCU->IDCODE;
- // 检查调试使能位
- return ((DBGMCU->CR & (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY)) != 0);
-}
-```
-
-2. 侧信道攻击防护
-- 电源分析攻击:
-通过测量设备功耗分析加密密钥。
-- 防护措施:
-常量时间实现:避免条件分支依赖密钥值。
-```c
-// 常量时间比较(防止时序攻击)
-bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
- uint8_t result = 0;
- for (size_t i = 0; i < len; i++) {
- result |= a[i] ^ b[i];
- }
- return (result == 0);
-}
-```
-
----
+### 嵌入式 TCP/IP 通信流程(以 lwIP 为例)
-## TCP/IP 协议栈基础与嵌入式实现
+典型流程:
-#### TCP/IP 协议栈分层结构(四层模型)
+1. 初始化网络接口
+2. 获取 IP(静态或 DHCP)
+3. 建立 Socket 或上层协议连接
+4. 收发数据
+5. 处理断线、超时和重连
-| 层级 | 协议/组件 | 功能说明 |
-|----------------|----------------------------|----------------------------------|
-| 应用层 | HTTP, MQTT, CoAP, DNS | 面向用户的协议 |
-| 传输层 | TCP, UDP | 数据传输可靠性与端口管理 |
-| 网络层 | IP, ICMP, ARP | 地址与路由 |
-| 链路层 | Ethernet, Wi-Fi, BLE | 硬件通信和数据帧传输 |
+### 常用 API 示例(lwIP / BSD Socket)
-#### TCP 与 UDP 区别
+很多嵌入式协议栈会尽量兼容 BSD Socket 风格接口,例如:
-| 特性 | TCP | UDP |
-|----------------|----------------------------|----------------------------|
-| 是否连接 | 是(面向连接) | 否(无连接) |
-| 是否可靠 | 是(有重传、确认) | 否(可能丢包) |
-| 适用场景 | Web、文件传输、SSH | 视频流、语音、广播 |
-| 开销 | 较大(握手、窗口等) | 较小(直接发送) |
+- `socket()`
+- `connect()`
+- `send()`
+- `recv()`
+- `close()`
-#### 嵌入式 TCP/IP 协议栈组件
+这让你从 PC 端网络编程过渡到嵌入式更容易。
-- **LwIP(Lightweight IP)**
- - 开源轻量级 TCP/IP 协议栈
- - 支持 TCP/UDP/IP/DNS/DHCP 等
- - 常用于 STM32、ESP32、RT-Thread 中
-- **uIP(micro IP)**
- - 更轻量,适合资源极小的 MCU
-- **FreeRTOS+TCP**
- - 与 FreeRTOS 配套的 TCP/IP 协议栈
-- **Nut/Net、CycloneTCP**:其他常用协议栈
+### DHCP / DNS / ICMP 说明
-#### 嵌入式 TCP/IP 通信流程(以 LwIP 为例)
+- DHCP:自动获取 IP 地址
+- DNS:域名解析
+- ICMP:如 `ping` 使用的协议
-1. **初始化网络接口**:配置 IP、MAC、网关
-2. **创建 socket 套接字**:TCP 或 UDP
-3. **建立连接 / 绑定端口**
-4. **接收/发送数据**:`recv()`, `send()`
-5. **关闭连接**:`close()`
+它们看似基础,但实际排查联网问题时非常常用。
-#### 常用 API 示例(LwIP BSD socket)
+---
-```c
-// TCP 客户端示例
-int sock = socket(AF_INET, SOCK_STREAM, 0);
-struct sockaddr_in server;
-server.sin_family = AF_INET;
-server.sin_port = htons(12345);
-server.sin_addr.s_addr = inet_addr("192.168.1.10");
+## 云平台接入 & OTA 实现
-connect(sock, (struct sockaddr*)&server, sizeof(server));
-send(sock, "Hello", strlen("Hello"), 0);
-recv(sock, buffer, sizeof(buffer), 0);
-close(sock);
-```
+### 云平台对接
-#### DHCP / DNS / ICMP 说明
+设备上云通常至少要考虑:
-* **DHCP**:动态分配 IP(LwIP 可配置)
-* **DNS**:域名解析,调用 `gethostbyname()` 等
-* **ICMP**:如 `ping` 实现通信测试
+- 身份认证
+- 数据上报格式
+- 指令下发方式
+- 连接断开后的恢复
+- 本地缓存策略
-#### 推荐阅读资料
+云平台的核心价值,不只是“把数据传上去”,而是提供:
-* [LwIP 官方文档](https://savannah.nongnu.org/projects/lwip/)
-* [FreeRTOS+TCP 文档](https://freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_TCP/index.html)
-* [TCP/IP Illustrated (Vol 1)](https://book.douban.com/subject/1088054/)
+- 设备管理
+- 状态监控
+- 远程配置
+- 升级能力
----
+### OTA 升级机制
-## 云平台接入 & OTA 实现
+OTA 的关键不是“下载新固件”,而是“失败时还能活着回来”。
-### 云平台对接
-- 主流平台:阿里云 IoT、腾讯连连、OneNet、ThingsBoard
-- 认证方式:三元组 / MQTT 密钥 / TLS 证书
+设计时应至少考虑:
-### OTA 升级机制
-> 支持远程更新嵌入式系统的固件版本
-1. 双分区升级架构
-
-```plaintext
-Flash布局:
-+-------------------+ 0x08000000
-| Bootloader |
-+-------------------+ 0x08010000
-| Application Slot A|
-+-------------------+ 0x08040000
-| Application Slot B|
-+-------------------+ 0x08070000
-| Configuration Area|
-+-------------------+
-```
-2. 升级状态机实现
-
-```c
-typedef enum {
- OTA_IDLE, // 空闲状态
- OTA_CHECKING, // 检查更新
- OTA_DOWNLOADING, // 下载中
- OTA_VERIFYING, // 校验中
- OTA_READY, // 准备重启
- OTA_UPGRADING, // 升级中
- OTA_FAILED // 升级失败
-} OTA_State_t;
-
-// OTA状态机处理函数
-void OTA_Process(void) {
- switch (ota_state) {
- case OTA_IDLE:
- if (check_update_flag) {
- ota_state = OTA_CHECKING;
- vCheckForUpdate();
- }
- break;
-
- case OTA_DOWNLOADING:
- if (download_complete) {
- ota_state = OTA_VERIFYING;
- vVerifyFirmware();
- } else if (download_error) {
- ota_state = OTA_FAILED;
- vHandleError(DOWNLOAD_ERROR);
- }
- break;
-
- // 其他状态处理...
- }
-}
-```
-3. 失败回滚机制
-
-```c
-// 启动时验证应用完整性
-bool ValidateApplication(uint32_t start_address) {
- // 检查向量表签名
- uint32_t *vector_table = (uint32_t *)start_address;
- if (vector_table[0] == 0xFFFFFFFF) { // 检查栈顶指针是否有效
- return false;
- }
-
- // 计算应用哈希并验证
- uint8_t calculated_hash[32];
- SHA256((uint8_t *)start_address, APPLICATION_SIZE, calculated_hash);
-
- // 从配置区获取预期哈希
- uint8_t *expected_hash = GetExpectedHash();
- return (memcmp(calculated_hash, expected_hash, 32) == 0);
-}
-
-// 主程序
-int main(void) {
- // 初始化硬件
- HAL_Init();
- SystemClock_Config();
-
- // 检查主应用是否有效
- if (ValidateApplication(APPLICATION_SLOT_A_ADDRESS)) {
- // 跳转到主应用
- JumpToApplication(APPLICATION_SLOT_A_ADDRESS);
- } else if (ValidateApplication(APPLICATION_SLOT_B_ADDRESS)) {
- // 主应用无效,尝试从备份应用启动
- JumpToApplication(APPLICATION_SLOT_B_ADDRESS);
- } else {
- // 两个应用都无效,进入恢复模式
- EnterRecoveryMode();
- }
-}
-```
-#### OTA 流程核心步骤
-
-1. 检查版本更新(HTTP/MQTT 下载 manifest)
-2. 下载固件(二进制)
-3. 存储到备份区(Backup Slot)
-4. 校验 CRC/Hash / 签名
-5. 设置 Bootloader 标志位并重启
-6. Bootloader 引导进入新固件
-7. 若失败则回滚(Fail-safe 机制)
-
-#### 常用升级协议
-
-- HTTP / HTTPS
-- MQTT + Base64 二进制块传输
-- CoAP(轻量级)
+- 完整性校验
+- 版本控制
+- 双分区或回滚机制
+- 升级中断恢复
+- 升级状态上报
---
## 推荐学习顺序
-1. 学习 UART 通信与基本网络 socket 原理
-2. 掌握 Wi-Fi / BLE 开发流程(推荐 ESP32/nRF52)
-3. 理解 MQTT 协议与平台接入逻辑
-4. 实践 OTA 升级流程,构建远程维护能力
+1. 先理解 UART 与 Socket 的差异
+2. 再理解 TCP / UDP 与协议栈分层
+3. 然后学习 MQTT、HTTP / HTTPS、CoAP 的适用场景
+4. 最后再看云接入和 OTA
---
## 常见问题 FAQ
| 问题 | 解答 |
-|------|------|
-| MQTT 断线如何重连? | 设置心跳机制与 reconnect 回调逻辑 |
-| OTA 更新失败怎么办? | 回退机制 + 双镜像分区设计 |
-| CoAP 和 MQTT 有何区别? | CoAP 基于 UDP,适合低功耗设备;MQTT 基于 TCP,稳定性好 |
+| --- | --- |
+| MQTT 断线如何重连? | 需要心跳、重连策略和状态机配合 |
+| OTA 更新失败怎么办? | 需要校验、回滚和双镜像设计 |
+| CoAP 和 MQTT 有何区别? | CoAP 更轻量、偏资源访问;MQTT 更偏消息分发 |
+
+---
+
+## 本章小结
+
+做 IoT 设备时,真正重要的是把通信链路拆清楚:本地总线、设备联网、协议封装、云端接入、运维升级,每一层都有不同的问题模型。掌握这一章,后续做联网产品会更有全局感。
diff --git a/07-Debug_Optimization/README.md b/07-Debug_Optimization/README.md
index 007d6f0..553ad42 100644
--- a/07-Debug_Optimization/README.md
+++ b/07-Debug_Optimization/README.md
@@ -1,122 +1,114 @@
-
# 第七层:调试与性能优化
+调试与优化章节面向真实项目中的问题定位。重点不是罗列工具,而是建立一套从现象、日志、波形、寄存器到性能指标的排查顺序。
+
+建议学习目标:
+
+- 掌握 SWD/JTAG、GDB、OpenOCD、逻辑分析仪和示波器的分工。
+- 能把“软件调试”和“硬件信号观察”结合起来。
+- 理解性能优化与低功耗优化的常见切入点。
+- 建立从复现问题到定位根因的系统化调试习惯。
+
+阅读建议:先看调试工具,再看性能与功耗优化,最后结合实战案例理解如何选择手段。
+
---
## 常用调试工具
### JTAG / SWD 接口
-- **JTAG**(Joint Test Action Group)标准调试接口,支持多设备级联。
-- **SWD**(Serial Wire Debug)是 ARM Cortex 系列的简化调试协议,仅使用两根线(SWDIO, SWCLK),适用于资源受限设备。
-
-**JTAG 与 SWD 接口对比**
-| 特性 | JTAG | SWD |
-|------------|----------------------------------------------|--------------------------------|
-| 引脚数 | 5 线(TMS、TCK、TDI、TDO、TRST) | 2 线(SWDIO、SWCLK) |
-| 速度 | 中低速(典型 1-10MHz) | 高速(可达 50MHz 以上) |
-| 占用资源 | 高(需多个 GPIO) | 低(仅 2 个 GPIO) |
-| 级联能力 | 支持多设备(通过 TAP 控制器) | 不支持级联 |
-| 适用场景 | 复杂芯片调试(如 FPGA) | 嵌入式 MCU(如 STM32) |
-
-- SWD 调试配置示例(STM32CubeMX):
-```c
-// 使能SWD接口(禁用JTAG以释放GPIO)
-__HAL_RCC_GPIOA_CLK_ENABLE();
-GPIO_InitTypeDef GPIO_InitStruct = {0};
-GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14; // SWDIO, SWCLK
-GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
-GPIO_InitStruct.Pull = GPIO_NOPULL;
-GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
-GPIO_InitStruct.Alternate = GPIO_AF0_SWJ;
-HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
-__HAL_AFIO_REMAP_SWJ_NOJTAG(); // 禁用JTAG,保留SWD
-```
+JTAG 和 SWD 都是常见的芯片调试接口。
+
+- JTAG:功能完整,适合复杂芯片和链式调试
+- SWD:引脚更少,特别适合 ARM Cortex-M MCU
+
+对大多数 MCU 开发者来说,SWD 更常用,因为它:
+
+- 占用引脚少
+- 调试效率高
+- 生态支持好
### GDB + OpenOCD 调试
-- **GDB**:GNU 调试器,支持断点、单步、查看变量等操作。
-- **OpenOCD**:Open On-Chip Debugger,用于连接 GDB 和硬件调试接口(如 ST-Link)。
+这组组合是开源调试工具链中的核心。
+
+- GDB:负责断点、单步、查看变量、栈回溯
+- OpenOCD:负责把 GDB 连接到具体调试器和目标芯片
+
+常见使用流程:
-关键命令详解:
```bash
-# 1. 启动OpenOCD(连接ST-Link与目标MCU)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
+arm-none-eabi-gdb firmware.elf
+(gdb) target remote :3333
+(gdb) load
+(gdb) monitor reset halt
+(gdb) break main
+(gdb) continue
+```
-# 2. 启动GDB并加载ELF文件
-arm-none-eabi-gdb path/to/firmware.elf
+实际调试中最常用的不是“把所有命令背下来”,而是掌握这些能力:
-# 3. 连接到OpenOCD服务器
-(gdb) target remote :3333 # 默认端口3333
+- 断点和条件断点
+- 单步与跳过函数
+- 查看局部变量与全局变量
+- 查看调用栈
+- 查看寄存器和内存
-# 4. 下载程序到Flash
-(gdb) load
+### 逻辑分析仪 / 示波器
-# 5. 复位并暂停CPU
-(gdb) monitor reset halt
+软件调试只能看到“程序认为发生了什么”,波形工具能看到“硬件实际上发生了什么”。
-# 6. 设置断点
-(gdb) break main # 在main()函数入口设置断点
-(gdb) break MyFunction # 在自定义函数设置断点
-(gdb) break file.c:123 # 在文件file.c的第123行设置断点
-
-# 7. 执行控制
-(gdb) continue # 继续执行
-(gdb) next # 单步执行(不进入函数)
-(gdb) step # 单步执行(进入函数)
-(gdb) finish # 运行到当前函数结束
-
-# 8. 查看变量
-(gdb) print myVariable # 打印变量值
-(gdb) p &myArray[0] # 打印数组地址
-(gdb) x/10i $pc # 查看当前执行的10条汇编指令
-
-# 9. 查看寄存器
-(gdb) info registers # 查看所有寄存器
-(gdb) p $r0 # 查看特定寄存器(如R0)
-```
+逻辑分析仪更适合:
-### 逻辑分析仪 / 示波器
+- SPI / I2C / UART 协议时序
+- 触发条件分析
+- ACK / NACK、片选和时钟关系确认
-- **逻辑分析仪**:用于捕捉数字信号波形,分析通信协议(如 I2C, SPI)。
-- 逻辑分析仪典型场景:
- - SPI 通信时序分析(验证 CPOL/CPHA 设置)。
- - I2C 总线竞争检测(查看 ACK/NACK 信号)。
- - UART 波特率校准(测量位宽计算实际波特率)。
-
-- **示波器**:查看模拟信号、电压、电流变化。对调试电源问题、干扰、PWM波形等极为重要。
-- 示波器关键参数:
- - 带宽:至少为信号最高频率的 3-5 倍(如测量 1MHz PWM 需 5MHz 带宽)。
- - 采样率:至少为信号最高频率的 10 倍(如 1MHz 信号需 10MSa/s 采样率)。
-
-#### 调试 PWM 信号示例:
-```c
-// 配置TIM3输出PWM(频率1kHz,占空比50%)
-TIM_HandleTypeDef htim3;
-htim3.Instance = TIM3;
-htim3.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
-htim3.Init.Period = 1000 - 1; // 1MHz / 1000 = 1kHz
-htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
-HAL_TIM_PWM_Init(&htim3);
-
-TIM_OC_InitTypeDef sConfigOC;
-sConfigOC.OCMode = TIM_OCMODE_PWM1;
-sConfigOC.Pulse = 500; // 占空比50%
-sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
-HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
-HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
-```
-使用示波器测量:频率应为 1kHz,高电平时间 500μs(占空比 50%)。
+示波器更适合:
+
+- 模拟信号观察
+- 电源纹波和掉压
+- PWM 波形
+- 传感器模拟输出
+
+如果通信不稳定、外设无响应、偶发复位,先看波形通常比先改代码更有效。
### printf / 串口调试
-- 常用 `printf()` 输出信息到串口查看程序执行流程。
-- 可与 RTT(Real Time Transfer)配合实现非阻塞调试输出。
+串口日志仍然是嵌入式最常用的调试方式之一。
+
+优点:
+
+- 成本低
+- 上手快
+- 对业务流观察直观
+
+局限:
+
+- 打印过多会影响实时性
+- 中断中打印可能导致更大问题
+- 时间顺序不一定等同于硬件真实时序
+
+建议:
+
+- 日志要有等级
+- 关键路径避免大量打印
+- 对性能敏感路径使用事件计数或采样日志
### 断点调试
-- 在 IDE(如 STM32CubeIDE)中设置断点暂停程序运行,查看寄存器、内存、变量。
-- 适合调试初始化流程、外设配置错误等问题。
+断点调试适合定位:
+
+- 某个分支是否进入
+- 某个变量何时被改坏
+- 某个异常发生前的调用路径
+
+但也要注意:
+
+- 断点会改变时序
+- 对实时问题和通信问题有时会掩盖现象
+- 某些外设在暂停 CPU 后会进入异常状态
---
@@ -124,171 +116,88 @@ HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
### FreeRTOS Trace 与分析工具
-- 使用 FreeRTOS+Trace 工具(Percepio)记录任务切换、上下文切换、CPU 占用率。
-- 可通过 `vTraceEnable()` 开启追踪。
-- 跟踪点(Trace Point):
-在关键代码位置插入记录函数(如任务切换、中断处理)。
-
-```c
-// 自定义跟踪点示例
-#define TRACE_TASK_SWITCH() do { \
- uint32_t current_task = (uint32_t)pxCurrentTCB; \
- uint32_t timestamp = xTaskGetTickCount(); \
- vTraceStoreEvent(EVENT_TASK_SWITCH, timestamp, current_task); \
-} while(0)
-```
-- 数据存储:
- - 环形缓冲区:存储跟踪事件,避免内存溢出。
- - 示例配置:
-```c
-#define configUSE_TRACE_FACILITY 1 // 启用跟踪功能
-#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用统计功能
-#define TRACE_BUFFER_SIZE 1024 // 跟踪缓冲区大小(事件数)
-```
+RTOS 系统里,性能问题经常来自:
+
+- 任务优先级不合理
+- 阻塞时间过长
+- 中断频率过高
+- 任务之间相互抢占
+
+这类问题靠普通日志很难看清,需要借助可视化工具观察调度行为。
### SystemView 分析工具
-- SEGGER 提供的实时系统分析工具。
-- 与 FreeRTOS 集成,通过 SWO 接口获取任务执行时间、事件追踪等信息。
-- 关键指标解读:
- - 任务执行时间:各任务 CPU 占用百分比。
- - 上下文切换频率:过高表示任务调度不合理。
- - 中断响应时间:从中断触发到 ISR 执行的时间差。
+SystemView 这类工具的优势是:
+
+- 能看到任务切换顺序
+- 能看到中断触发和耗时
+- 能分析 CPU 时间花在了哪里
+
+适合排查:
+
+- 系统卡顿
+- 任务饥饿
+- 周期任务抖动
+- 中断打断过多
### STM32CubeMonitor
-- ST 官方提供的可视化变量监控与数据图示工具。
-- 可用于实时观察寄存器值、ADC 曲线、温度、电压等参数。
+这类图形化监视工具适合做在线观测:
+
+- 电流
+- 电压
+- 采样值变化趋势
+- 控制参数变化
+
+当系统“能跑但表现不稳定”时,图形化趋势往往比看单点日志更有价值。
### 低功耗模式优化
-#### Cortex-M 支持三种主要低功耗模式:
+低功耗优化并不只是“进入休眠”,而是整套策略设计:
-| 模式 | 唤醒时间 | 功耗 | 保留内容 |
-|----------|-----------|----------|--------------------------------|
-| Sleep | 数 μs | 几 mA | CPU 寄存器、SRAM 内容 |
-| Stop | 几十 μs | 几 μA | SRAM 内容、部分寄存器 |
-| Standby | 几 ms | 几十 nA | 仅备份寄存器(如 RTC) |
+- 哪些模块常开
+- 哪些模块可按需唤醒
+- 时钟是否需要动态降频
+- 通信窗口如何收敛
-#### 优化技巧:
+常见切入点:
-- 外设时钟管理:
-```c
-// 禁用未使用的外设时钟
-__HAL_RCC_GPIOA_CLK_DISABLE(); // 禁用GPIOA时钟
-__HAL_RCC_SPI1_CLK_DISABLE(); // 禁用SPI1时钟
+- 降低采样频率
+- 减少无意义轮询
+- 用中断替代忙等
+- 关闭不用的外设时钟
-// 仅在需要时启用外设
-void vReadSensor(void) {
- __HAL_RCC_I2C1_CLK_ENABLE(); // 启用I2C时钟
- // 读取传感器数据
- __HAL_RCC_I2C1_CLK_DISABLE(); // 读取完成后禁用时钟
-}
-```
+---
-- RTC 唤醒配置:
-```c
-// 配置RTC闹钟唤醒(每10秒唤醒一次)
-RTC_TimeTypeDef sTime = {0};
-RTC_DateTypeDef sDate = {0};
-RTC_AlarmTypeDef sAlarm = {0};
-
-sTime.Hours = 0;
-sTime.Minutes = 0;
-sTime.Seconds = 0;
-HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
-
-sDate.WeekDay = RTC_WEEKDAY_MONDAY;
-sDate.Month = RTC_MONTH_JANUARY;
-sDate.Date = 1;
-sDate.Year = 0;
-HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
-
-sAlarm.AlarmTime = sTime;
-sAlarm.Alarm = RTC_ALARM_A;
-sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES;
-sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
-HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
-
-// 进入Standby模式
-HAL_PWR_EnterSTANDBYMode();
-```
+## 调试与优化实战案例
-### 调试与优化实战案例
-1. 内存泄漏检测
-- 静态检测工具:
- - CppCheck:检查内存分配与释放是否匹配。
- - Valgrind(需模拟器环境):检测动态内存问题。
-- 自定义内存管理钩子:
-```c
-// 记录内存分配/释放情况
-void *pvPortMalloc( size_t xWantedSize ) {
- void *pvReturn = NULL;
- vTaskSuspendAll();
- {
- // 记录分配信息(如分配地址、大小、时间)
- pvReturn = prvHeapAllocateMemory( xWantedSize );
- vRecordMemoryAllocation(pvReturn, xWantedSize);
- }
- xTaskResumeAll();
- return pvReturn;
-}
-```
-2. 中断风暴处理
-- 问题现象:CPU 占用率 100%,系统无响应。
-- 排查步骤:
- - 使用调试器暂停 CPU,查看当前执行的代码(通常是某个 ISR)。
- - 检查中断触发条件(如 GPIO 引脚是否抖动)。
- - 添加中断计数统计:
-- 解决方案:
- - 添加软件消抖:
-```c
-static uint32_t ulLastInterruptTime = 0;
-#define DEBOUNCE_TIME 50 // 50ms
-
-void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
- uint32_t ulCurrentTime = xTaskGetTickCount();
- if (ulCurrentTime - ulLastInterruptTime > DEBOUNCE_TIME) {
- // 处理有效中断
- vProcessButtonPress();
- ulLastInterruptTime = ulCurrentTime;
- }
-}
-```
+常见问题可以按下面思路处理:
-### 面试高频问题
-1. JTAG 与 SWD 的优缺点:
+1. 先复现问题
+2. 明确是功能错误、时序错误还是性能问题
+3. 选对证据来源:日志、寄存器、波形、调度图
+4. 缩小范围
+5. 修改后重新验证
-- JTAG:兼容性强,支持多设备级联,但占用引脚多;SWD:引脚少,速度快,适合嵌入式设备。
+三个典型案例:
-2. 如何优化 RTOS 系统的 CPU 使用率:
-- 减少空闲任务 CPU 占用(通过configIDLE_SHOULD_YIELD配置)。
-- 优化中断处理时间,避免长中断服务程序。
-- 使用低功耗模式,在空闲时进入 Sleep/Stop 状态。
+- UART 偶发丢包:先查波特率、FIFO、DMA、缓存边界
+- 系统偶发复位:先看看门狗、电源和 HardFault
+- 电池设备待机不达标:先测量电流,再查唤醒源与外设时钟
-3. 调试时发现程序跑飞,如何定位问题:
+---
-- 设置看门狗定时器,捕获异常复位。
-- 使用硬件断点,检查关键函数是否被正确调用。
-- 添加断言(assert),验证关键条件。
+## 学习资源推荐
-4. 如何测量代码执行时间:
-- 使用高精度定时器(如 STM32 的 DWT_CYCCNT)。
-- SystemView 等工具通过 SWO 接口获取精确时间。
+- GDB 官方文档
+- OpenOCD 文档
+- FreeRTOS Trace 文档
+- SEGGER SystemView 文档
+- 芯片厂商低功耗应用手册
-### 学习资源推荐
-1. 调试工具文档:
-- [GDB 官方文档](https://sourceware.org/gdb/documentation/)
-- [OpenOCD 用户手册](https://link.wtturl.cn/?target=http%3A%2F%2Fopenocd.org%2Fdoc%2Fpdf%2Fopenocd.pdf&scene=im&aid=497858&lang=zh)
+---
-2. 性能分析教程:
-- [FreeRTOS Trace 可视化指南](https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_Trace/trace_introduction.html)
-- [SEGGER SystemView 应用笔记](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.segger.com%2Fproducts%2Fdevelopment-tools%2Fsystemview%2F&scene=im&aid=497858&lang=zh)
+## 本章小结
-3. 低功耗设计指南:
-- [STM32 低功耗应用手册](https://link.wtturl.cn/?target=https%3A%2F%2Fwww.st.com%2Fresource%2Fen%2Fapplication_note%2Fdm00071990-stm32-microcontroller-lowpower-modes-stmicroelectronics.pdf&scene=im&aid=497858&lang=zh)
-- [Cortex-M 低功耗技术白皮书](https://developer.arm.com/documentation/100166/latest/)
+调试能力本质上是证据收集能力。好的排查顺序通常是:先复现,再观察,再缩小范围,最后验证修复。优化也是同理,先测量,再分析,最后决定是否值得改。
-4. 实践项目:
-- 在 STM32 上实现功耗测量(使用外部电流表或内部 ADC 监测 VDD 电流)。
-- 使用 SystemView 分析 FreeRTOS 任务调度行为。
diff --git "a/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md" "b/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
index ede9b75..a70b75b 100644
--- "a/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
+++ "b/08-\351\241\271\347\233\256\345\256\236\346\210\230\344\270\216\345\267\245\345\205\267\351\223\276/README.md"
@@ -1,830 +1,215 @@
# 第八层:项目实战与工具链
+这一章把零散技术点带回工程实践。真正决定项目是否可维护的,往往不是单个函数写得多巧,而是工程结构、构建系统、版本管理、CI 和测试策略是否成熟。
+
+建议学习目标:
+
+- 理解 Git 分支、提交规范和版本标签在团队协作中的作用。
+- 掌握 Makefile、CMake、CI 流水线的基本组织方式。
+- 建立 BSP、模块化驱动和应用框架的工程化思维。
+- 对常见 IDE、调试工具、静态分析和测试工具形成选型认知。
+
+阅读建议:先看工程管理,再看项目结构设计,最后按需要查工具链安装与资源汇总。
+
+---
+
## 工程管理
### Git 版本控制
-#### 1. **Git 分支策略**
-- **主干分支(main/master)**:
- 永远代表可发布的稳定版本,仅接受通过CI/CD验证的代码。
-- **开发分支(develop)**:
- 集成所有新功能的开发,是日常开发的基础分支。
-- **特性分支(feature/*)**:
- 从develop分支创建,用于开发单个新功能或修复问题,完成后合并回develop。
-- **发布分支(release/*)**:
- 从develop分支创建,用于准备发布版本,进行最后的测试和Bug修复。
-- **热修复分支(hotfix/*)**:
- 从main分支创建,用于紧急修复生产环境问题,修复后合并回main和develop。
-
-#### 2. **提交规范**
-采用Conventional Commits规范:
-```
-<类型>[可选范围]: <描述>
+Git 在嵌入式项目中不仅用于保存代码,还直接影响:
-[可选正文]
+- 团队协作效率
+- 发布节奏
+- 问题回溯能力
+- 版本可追踪性
+
+常见分支角色:
+
+- `main`:稳定可发布版本
+- `develop`:集成开发分支
+- `feature/*`:新功能开发
+- `release/*`:发布准备
+- `hotfix/*`:线上问题修复
+
+提交建议:
+
+- 一次提交只做一类变更
+- 提交信息说明“为什么改”
+- 发布节点配合标签管理
+
+常见标签命令:
-[可选脚注]
-```
-- **常见类型**:
- - `feat`:新功能
- - `fix`:修复Bug
- - `docs`:文档更新
- - `style`:代码格式调整(不影响功能)
- - `refactor`:代码重构
- - `test`:添加或修改测试
- - `chore`:构建或辅助工具的变动
-
-#### 3. **标签管理**
-使用语义化版本(SemVer)打标签:
```bash
-# 创建标签
git tag v1.0.0
-
-# 推送标签到远程
git push origin v1.0.0
-
-# 查看所有标签
git tag -l
```
### Makefile、CMake 构建工具
-#### 1. **Makefile 基础**
-- **简单示例**:
- ```makefile
- CC = arm-none-eabi-gcc
- CFLAGS = -Wall -O2 -mcpu=cortex-m4 -mthumb
- LDFLAGS = -Tstm32f4.ld
-
- SRCS = $(wildcard *.c)
- OBJS = $(SRCS:.c=.o)
- TARGET = firmware.elf
-
- all: $(TARGET)
-
- $(TARGET): $(OBJS)
- $(CC) $(LDFLAGS) $(OBJS) -o $@
-
- %.o: %.c
- $(CC) $(CFLAGS) -c $< -o $@
-
- clean:
- rm -f $(OBJS) $(TARGET)
- ```
-
-#### 2. **CMake 高级应用**
-- **跨平台配置**:
- ```cmake
- cmake_minimum_required(VERSION 3.10)
- project(EmbeddedProject C)
-
- # 设置交叉编译工具链
- set(CMAKE_SYSTEM_NAME Generic)
- set(CMAKE_C_COMPILER arm-none-eabi-gcc)
- set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
- set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
- set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
-
- # 添加编译选项
- add_compile_options(
- -mcpu=cortex-m4
- -mthumb
- -mfloat-abi=hard
- -mfpu=fpv4-sp-d16
- -Wall
- -Wextra
- -Os
- )
-
- # 添加链接选项
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -T${CMAKE_SOURCE_DIR}/STM32F407VGTx_FLASH.ld")
-
- # 添加源文件
- file(GLOB_RECURSE SOURCES "src/*.c" "drivers/*.c")
-
- # 添加可执行文件
- add_executable(${PROJECT_NAME}.elf ${SOURCES})
-
- # 添加目标文件
- add_custom_target(${PROJECT_NAME}.bin
- COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
- DEPENDS ${PROJECT_NAME}.elf
- )
- ```
-
-### Jenkins/GitHub Actions CI 流水线
-
-#### 1. **GitHub Actions 配置**
-- **编译与测试工作流**:
- ```yaml
- name: Build and Test
-
- on:
- push:
- branches: [ main, develop ]
- pull_request:
- branches: [ main, develop ]
-
- jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: 3.9
-
- - name: Install dependencies
- run: |
- sudo apt-get update
- sudo apt-get install -y gcc-arm-none-eabi cmake ninja-build
-
- - name: Configure CMake
- run: cmake -B build -G Ninja
-
- - name: Build
- run: cmake --build build
-
- - name: Run tests
- run: |
- cd build
- ctest --output-on-failure
- ```
-
-#### 2. **Jenkins 集成**
-- **构建脚本示例**:
- ```groovy
- pipeline {
- agent any
-
- stages {
- stage('Checkout') {
- steps {
- checkout scm
- }
- }
-
- stage('Build') {
- steps {
- sh 'make clean all'
- }
- }
-
- stage('Test') {
- steps {
- sh 'make test'
- }
- }
-
- stage('Code Coverage') {
- steps {
- sh 'make coverage'
- }
- post {
- always {
- junit 'build/test-results/*.xml'
- publishCoverage adapters: [coberturaAdapter('build/coverage/coverage.xml')]
- }
- }
- }
-
- stage('Deploy') {
- when {
- branch 'main'
- }
- steps {
- sh 'make deploy'
- }
- }
- }
- }
- ```
+构建系统的核心价值是把“如何编译这个工程”变成可重复执行的规则。
-## 项目实践
+Makefile 更偏直接和轻量,适合:
-### 嵌入式应用框架设计
+- 小型项目
+- 直接控制编译流程
+- 快速搭建交叉编译规则
-#### 1. **分层架构**
-```
-+----------------------+
-| 应用层 |
-| (业务逻辑、算法) |
-+----------------------+
-| 服务层 |
-| (任务管理、事件) |
-+----------------------+
-| 驱动层 |
-| (硬件抽象、BSP) |
-+----------------------+
-| 硬件层 |
-| (MCU、外设) |
-+----------------------+
-```
+CMake 更适合:
-#### 2. **组件化设计**
-- **核心组件**:
- - 任务管理器:负责任务创建、调度和通信。
- - 事件系统:处理异步事件和回调。
- - 配置管理:加载和保存系统配置。
- - 日志系统:分级日志记录和输出。
+- 多目录工程
+- 跨平台构建
+- 配合 IDE 和测试系统
-#### 3. **代码结构示例**
-```
-project/
-├── app/ # 应用层
-│ ├── main.c # 主程序入口
-│ ├── modules/ # 功能模块
-│ │ ├── sensor/ # 传感器处理
-│ │ ├── comm/ # 通信处理
-│ │ └── control/ # 控制逻辑
-│ └── config/ # 配置文件
-├── services/ # 服务层
-│ ├── task_mgr/ # 任务管理器
-│ ├── event/ # 事件系统
-│ └── utils/ # 工具函数
-├── drivers/ # 驱动层
-│ ├── bsp/ # 板级支持包
-│ ├── hal/ # 硬件抽象层
-│ └── periph/ # 外设驱动
-└── build/ # 构建系统
- ├── cmake/ # CMake配置
- └── Makefile # Makefile
-```
+选择重点不在于“谁更高级”,而在于:
-### 通用 BSP 构建
+- 团队是否容易维护
+- 工程规模是否匹配
+- 工具链是否易接入
-#### 1. **设计原则**
-- **硬件无关性**:上层代码不直接访问硬件寄存器。
-- **可移植性**:相同功能代码可在不同硬件平台复用。
-- **配置化**:通过配置文件而非修改代码适配不同硬件。
-
-#### 2. **BSP 实现示例**
-```c
-// bsp_led.h
-#ifndef BSP_LED_H
-#define BSP_LED_H
-
-#include
-
-typedef enum {
- LED_RED,
- LED_GREEN,
- LED_BLUE
-} led_t;
-
-typedef enum {
- LED_OFF,
- LED_ON,
- LED_TOGGLE
-} led_state_t;
-
-// 初始化LED
-void bsp_led_init(void);
-
-// 设置LED状态
-void bsp_led_set(led_t led, led_state_t state);
-
-#endif
-
-// bsp_led.c (STM32实现)
-#include "bsp_led.h"
-#include "stm32f4xx_hal.h"
-
-// LED GPIO定义
-#define LED_RED_PIN GPIO_PIN_14
-#define LED_RED_PORT GPIOG
-#define LED_GREEN_PIN GPIO_PIN_13
-#define LED_GREEN_PORT GPIOG
-#define LED_BLUE_PIN GPIO_PIN_15
-#define LED_BLUE_PORT GPIOG
-
-void bsp_led_init(void) {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
-
- // 使能GPIO时钟
- __HAL_RCC_GPIOG_CLK_ENABLE();
-
- // 配置GPIO引脚
- GPIO_InitStruct.Pin = LED_RED_PIN | LED_GREEN_PIN | LED_BLUE_PIN;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
-
- // 默认关闭所有LED
- HAL_GPIO_WritePin(LED_RED_PORT, LED_RED_PIN, GPIO_PIN_RESET);
- HAL_GPIO_WritePin(LED_GREEN_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
- HAL_GPIO_WritePin(LED_BLUE_PORT, LED_BLUE_PIN, GPIO_PIN_RESET);
-}
-
-void bsp_led_set(led_t led, led_state_t state) {
- GPIO_TypeDef *port;
- uint16_t pin;
-
- // 根据LED类型选择GPIO
- switch (led) {
- case LED_RED:
- port = LED_RED_PORT;
- pin = LED_RED_PIN;
- break;
- case LED_GREEN:
- port = LED_GREEN_PORT;
- pin = LED_GREEN_PIN;
- break;
- case LED_BLUE:
- port = LED_BLUE_PORT;
- pin = LED_BLUE_PIN;
- break;
- default:
- return;
- }
-
- // 设置LED状态
- switch (state) {
- case LED_OFF:
- HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
- break;
- case LED_ON:
- HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
- break;
- case LED_TOGGLE:
- HAL_GPIO_TogglePin(port, pin);
- break;
- }
-}
-```
+### Jenkins / GitHub Actions CI 流水线
-### 模块化驱动结构
+CI 的价值在于让“构建、测试、检查”自动化。
-#### 1. **驱动分层**
-- **硬件层**:直接操作寄存器的低级驱动。
-- **抽象层**:提供统一接口的高级驱动。
-- **适配层**:连接抽象层和硬件层的中间层。
-
-#### 2. **SPI驱动示例**
-```c
-// spi_interface.h (抽象接口)
-#ifndef SPI_INTERFACE_H
-#define SPI_INTERFACE_H
-
-#include
-
-typedef struct {
- // 初始化SPI
- void (*init)(uint32_t baudrate);
-
- // 发送数据
- void (*send)(const uint8_t *data, uint32_t length);
-
- // 接收数据
- void (*receive)(uint8_t *data, uint32_t length);
-
- // 发送并接收数据
- void (*transfer)(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length);
-} spi_interface_t;
-
-// 获取SPI接口实例
-const spi_interface_t* spi_get_interface(void);
-
-#endif
-
-// spi_stm32.c (STM32实现)
-#include "spi_interface.h"
-#include "stm32f4xx_hal.h"
-
-static SPI_HandleTypeDef hspi1;
-
-static void spi_init(uint32_t baudrate) {
- // 配置SPI参数
- hspi1.Instance = SPI1;
- hspi1.Init.Mode = SPI_MODE_MASTER;
- hspi1.Init.Direction = SPI_DIRECTION_2LINES;
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
- hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
- hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
- hspi1.Init.NSS = SPI_NSS_SOFT;
-
- // 根据波特率计算分频系数
- uint32_t prescaler = SPI_BAUDRATEPRESCALER_2;
- if (baudrate < 1000000) prescaler = SPI_BAUDRATEPRESCALER_128;
- else if (baudrate < 2000000) prescaler = SPI_BAUDRATEPRESCALER_64;
- else if (baudrate < 4000000) prescaler = SPI_BAUDRATEPRESCALER_32;
- else if (baudrate < 8000000) prescaler = SPI_BAUDRATEPRESCALER_16;
- else if (baudrate < 16000000) prescaler = SPI_BAUDRATEPRESCALER_8;
- else if (baudrate < 32000000) prescaler = SPI_BAUDRATEPRESCALER_4;
-
- hspi1.Init.BaudRatePrescaler = prescaler;
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
- hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- hspi1.Init.CRCPolynomial = 10;
-
- // 初始化SPI
- HAL_SPI_Init(&hspi1);
-}
-
-static void spi_send(const uint8_t *data, uint32_t length) {
- HAL_SPI_Transmit(&hspi1, (uint8_t*)data, length, 1000);
-}
-
-static void spi_receive(uint8_t *data, uint32_t length) {
- HAL_SPI_Receive(&hspi1, data, length, 1000);
-}
-
-static void spi_transfer(const uint8_t *tx_data, uint8_t *rx_data, uint32_t length) {
- HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)tx_data, rx_data, length, 1000);
-}
-
-// SPI接口实现
-static const spi_interface_t spi_impl = {
- .init = spi_init,
- .send = spi_send,
- .receive = spi_receive,
- .transfer = spi_transfer
-};
-
-// 获取SPI接口实例
-const spi_interface_t* spi_get_interface(void) {
- return &spi_impl;
-}
-```
+嵌入式项目中常见 CI 任务:
-### OTA 升级方案设计
+- 自动编译固件
+- 运行单元测试
+- 静态检查
+- 打包发布物
+- 生成版本信息
-#### 1. **双分区架构**
-```
-Flash布局:
-+-------------------+ 0x08000000
-| Bootloader | (80KB)
-+-------------------+ 0x08014000
-| Application A | (448KB)
-+-------------------+ 0x08084000
-| Application B | (448KB)
-+-------------------+ 0x08104000
-| Configuration | (16KB)
-+-------------------+
-```
+即便没有硬件在 CI 上,也至少应保证:
-#### 2. **OTA状态机**
-```c
-typedef enum {
- OTA_IDLE, // 空闲状态
- OTA_CHECKING, // 检查更新
- OTA_DOWNLOADING, // 下载中
- OTA_DOWNLOAD_PAUSED, // 下载暂停
- OTA_VERIFYING, // 校验中
- OTA_READY, // 准备重启
- OTA_UPGRADING, // 升级中
- OTA_FAILED // 升级失败
-} ota_state_t;
-
-typedef struct {
- ota_state_t state;
- uint32_t total_size;
- uint32_t downloaded_size;
- uint8_t progress;
- char error_msg[64];
- uint8_t firmware_hash[32];
-} ota_context_t;
-```
+- 代码能稳定构建
+- 关键测试能自动执行
+- 风格和静态问题能被及时发现
+
+---
-#### 3. **OTA流程**
-1. **检查更新**:
- ```c
- bool ota_check_update(void) {
- // 从服务器获取版本信息
- http_response_t response = http_get(UPDATE_SERVER_URL "/version");
- if (response.status != 200) {
- return false;
- }
-
- // 解析服务器版本
- uint32_t server_version = parse_version(response.body);
- uint32_t current_version = get_current_version();
-
- // 比较版本
- return (server_version > current_version);
- }
- ```
-
-2. **下载固件**:
- ```c
- void ota_download_firmware(void) {
- // 打开固件下载URL
- http_client_t client = http_open(UPDATE_SERVER_URL "/firmware.bin");
- if (!client) {
- ota_set_state(OTA_FAILED, "Failed to open URL");
- return;
- }
-
- // 获取文件大小
- uint32_t file_size = http_get_content_length(client);
- ota_set_total_size(file_size);
-
- // 开始下载
- uint8_t buffer[512];
- uint32_t bytes_received = 0;
- uint32_t bytes_written = 0;
-
- while ((bytes_received = http_read(client, buffer, 512)) > 0) {
- // 写入到备份区
- if (!flash_write(APPLICATION_B_ADDRESS + bytes_written, buffer, bytes_received)) {
- ota_set_state(OTA_FAILED, "Flash write failed");
- http_close(client);
- return;
- }
-
- bytes_written += bytes_received;
- ota_update_progress(bytes_written * 100 / file_size);
-
- // 检查是否需要暂停
- if (ota_should_pause()) {
- http_close(client);
- ota_set_state(OTA_DOWNLOAD_PAUSED, "Download paused");
- return;
- }
- }
-
- http_close(client);
- ota_set_state(OTA_VERIFYING, "Verifying firmware");
- }
- ```
-
-3. **验证与应用**:
- ```c
- bool ota_verify_firmware(void) {
- // 计算下载固件的哈希值
- uint8_t calculated_hash[32];
- calculate_firmware_hash(APPLICATION_B_ADDRESS, APPLICATION_SIZE, calculated_hash);
-
- // 与服务器提供的哈希值比较
- if (memcmp(calculated_hash, ota_get_expected_hash(), 32) != 0) {
- return false;
- }
-
- // 验证向量表
- uint32_t *vector_table = (uint32_t*)APPLICATION_B_ADDRESS;
- if (vector_table[0] == 0 || vector_table[1] == 0) {
- return false;
- }
-
- return true;
- }
-
- void ota_apply_update(void) {
- // 设置升级标志
- set_update_flag(1);
-
- // 保存新固件版本
- save_new_version(get_server_version());
-
- // 重启系统
- NVIC_SystemReset();
- }
- ```
-
-# 开发工具链安装指南
-## 1. **IDE推荐**
-
-### VS Code + PlatformIO
-
-**官网链接**:
-- [VS Code](https://code.visualstudio.com/)
-- [PlatformIO](https://platformio.org/)
-
-**安装步骤**:
-1. 下载并安装 [VS Code](https://code.visualstudio.com/Download)
-2. 打开VS Code,点击左侧扩展图标(或按 `Ctrl+Shift+X`)
-3. 搜索并安装 **PlatformIO IDE** 扩展
-4. 安装完成后,重启VS Code
-5. PlatformIO会自动安装所需的工具链和依赖
-
-**验证安装**:
-打开VS Code,点击左下角的 **PlatformIO Home** 图标,若能正常打开则安装成功。
-
-### STM32CubeIDE
-
-**官网链接**:
-- [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html)
-
-**安装步骤**:
-1. 访问官网,点击 **Get Software** 下载对应操作系统的安装包
-2. 运行安装程序,按照向导完成安装
-3. 安装过程中会自动下载并配置STM32CubeMX
-
-**验证安装**:
-启动STM32CubeIDE,创建一个新的STM32项目,若能正常编译则安装成功。
-
-### CLion
-
-**官网链接**:
-- [CLion](https://www.jetbrains.com/clion/)
-
-**安装步骤**:
-1. 下载并安装 [CLion](https://www.jetbrains.com/clion/download/)
-2. 安装CMake和MinGW(Windows用户需要):
- - CMake:从 [官网](https://cmake.org/download/) 下载并安装
- - MinGW:推荐使用 [MSYS2](https://www.msys2.org/) 安装
-
-**验证安装**:
-启动CLion,创建一个新的C/C++项目,选择CMake工具链,若能正常编译则安装成功。
-
-## 2. **调试工具**
-
-### OpenOCD
-
-**官网链接**:
-- [OpenOCD](http://openocd.org/)
-
-**安装步骤**:
-- **Windows**:
- 1. 从 [GNU MCU Eclipse](https://github.com/gnu-mcu-eclipse/openocd/releases) 下载预编译二进制包
- 2. 解压到指定目录(如 `C:\openocd`)
- 3. 将 `bin` 目录添加到系统环境变量
-
-- **Linux**:
- ```bash
- sudo apt-get install openocd # Ubuntu/Debian
- sudo yum install openocd # CentOS/RHEL
- ```
-
-- **macOS**:
- ```bash
- brew install open-ocd
- ```
-
-**验证安装**:
-在终端中运行 `openocd --version`,若显示版本信息则安装成功。
-
-### GDB
-
-**官网链接**:
-- [GDB](https://www.gnu.org/software/gdb/)
-- [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm)
+## 项目实践
-**安装步骤**:
-1. 下载并安装 [ARM GCC Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
-2. 将 `bin` 目录添加到系统环境变量
+### 嵌入式应用框架设计
-**验证安装**:
-在终端中运行 `arm-none-eabi-gdb --version`,若显示版本信息则安装成功。
+一个可维护的项目应尽量分层清晰,例如:
-### ST-Link/V2
+```text
+app/ 业务逻辑
+bsp/ 板级支持包
+driver/ 外设驱动
+middleware/ 中间件
+common/ 公共工具
+```
-**官网链接**:
-- [ST-Link](https://www.st.com/en/development-tools/st-link-v2.html)
+这样做的意义在于:
-**安装步骤**:
-- **Windows**:
- 1. 从 [ST官网](https://www.st.com/en/development-tools/stsw-link004.html) 下载并安装ST-Link驱动
- 2. 安装完成后,将ST-Link/V2调试器连接到电脑
+- 更容易定位问题
+- 更容易替换硬件
+- 更容易做单元测试
-- **Linux**:
- ```bash
- sudo apt-get install stlink-tools # Ubuntu/Debian
- ```
+### 通用 BSP 构建
-**验证安装**:
-在终端中运行 `st-info --version`,若显示版本信息则安装成功。
+BSP(Board Support Package)负责把板级差异隔离出来。
-## 3. **静态代码分析**
+通常应包括:
-### CppCheck
+- 时钟初始化
+- 引脚初始化
+- 基础外设初始化
+- 板级资源映射
-**官网链接**:
-- [CppCheck](https://cppcheck.sourceforge.io/)
+好的 BSP 应做到:
-**安装步骤**:
-- **Windows**:
- 1. 从 [官网](https://cppcheck.sourceforge.io/) 下载安装包
- 2. 运行安装程序,按照向导完成安装
+- 对上层暴露稳定接口
+- 板级差异集中管理
+- 减少业务代码直接碰硬件细节
-- **Linux**:
- ```bash
- sudo apt-get install cppcheck # Ubuntu/Debian
- sudo yum install cppcheck # CentOS/RHEL
- ```
+### 模块化驱动结构
-- **macOS**:
- ```bash
- brew install cppcheck
- ```
+驱动模块化的核心目标是“边界清晰”。
-**验证安装**:
-在终端中运行 `cppcheck --version`,若显示版本信息则安装成功。
+一个驱动模块通常应包含:
-### Clang-Tidy
+- 初始化接口
+- 读写接口
+- 中断或回调处理
+- 错误状态和异常恢复
-**官网链接**:
-- [Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/)
+不要让业务代码遍地直接改寄存器,否则后续维护成本会很高。
-**安装步骤**:
-- **Windows**:
- 1. 安装 [LLVM](https://releases.llvm.org/download.html)
- 2. Clang-Tidy会随LLVM一起安装
+---
-- **Linux**:
- ```bash
- sudo apt-get install clang-tidy # Ubuntu/Debian
- ```
+## 开发工具链安装指南
-- **macOS**:
- ```bash
- brew install llvm
- ```
+### IDE 推荐
-**验证安装**:
-在终端中运行 `clang-tidy --version`,若显示版本信息则安装成功。
+常见选择:
-### SonarQube
+- VS Code + PlatformIO:轻量、插件丰富
+- STM32CubeIDE:对 STM32 生态友好
+- CLion:适合较大 CMake 工程
-**官网链接**:
-- [SonarQube](https://www.sonarqube.org/)
+选择 IDE 时重点考虑:
-**安装步骤**:
-1. 下载并安装 [Docker](https://www.docker.com/get-started)
-2. 运行SonarQube容器:
- ```bash
- docker run -d --name sonarqube -p 9000:9000 sonarqube
- ```
-3. 访问 [http://localhost:9000](http://localhost:9000),使用默认账号(admin/admin)登录
+- 与工具链整合是否顺手
+- 调试体验如何
+- 团队能否统一
-**验证安装**:
-在浏览器中打开 [http://localhost:9000](http://localhost:9000),若能看到SonarQube界面则安装成功。
+### OpenOCD / GDB
-## 4. **单元测试**
+这类工具更偏底层调试链路,适合:
-### Unity
+- 下载与调试 MCU
+- 配合脚本自动化
+- 与 CI 或命令行构建流程集成
-**官网链接**:
-- [Unity](https://github.com/ThrowTheSwitch/Unity)
+### 静态分析工具
-**安装步骤**:
-1. 从GitHub下载Unity源码:
- ```bash
- git clone https://github.com/ThrowTheSwitch/Unity.git
- ```
-2. 将 `src` 目录添加到项目的头文件搜索路径
+常见工具:
-**验证安装**:
-创建一个简单的测试文件,包含Unity头文件,若能正常编译则安装成功。
+- Cppcheck
+- Clang-Tidy
+- SonarQube
-### CMock
+它们的作用不是替代测试,而是更早发现:
-**官网链接**:
-- [CMock](https://github.com/ThrowTheSwitch/CMock)
+- 空指针风险
+- 越界访问
+- 风格不一致
+- 可维护性问题
-**安装步骤**:
-1. 从GitHub下载CMock源码:
- ```bash
- git clone https://github.com/ThrowTheSwitch/CMock.git
- ```
-2. 将 `src` 目录添加到项目的头文件搜索路径
+### 测试工具
-**验证安装**:
-创建一个简单的测试文件,包含CMock头文件,若能正常编译则安装成功。
+常见组合:
-### Google Test
+- Unity / CMock:偏嵌入式 C 测试
+- Google Test:适合 C++ 或宿主机侧测试
-**官网链接**:
-- [Google Test](https://github.com/google/googletest)
+测试体系建立的最低门槛可以是:
-**安装步骤**:
-1. 从GitHub下载Google Test源码:
- ```bash
- git clone https://github.com/google/googletest.git
- ```
-2. 使用CMake构建并安装:
- ```bash
- cd googletest
- mkdir build
- cd build
- cmake ..
- make
- sudo make install
- ```
+- 关键算法可测
+- 协议解析可测
+- 状态机可测
-**验证安装**:
-创建一个简单的测试文件,包含Google Test头文件,若能正常编译则安装成功。
+---
## 资源汇总
-| **工具** | **官网链接** | **安装指南** |
-|------------------|---------------------------------------------|-------------------------------------------|
-| VS Code | https://code.visualstudio.com/ | 直接下载安装包 |
-| PlatformIO | https://platformio.org/ | VS Code扩展市场安装 |
-| STM32CubeIDE | https://www.st.com/en/development-tools/stm32cubeide.html | 官网下载安装包 |
-| CLion | https://www.jetbrains.com/clion/ | 官网下载安装包 |
-| OpenOCD | http://openocd.org/ | 包管理器或预编译二进制包 |
-| GDB | https://www.gnu.org/software/gdb/ | 随ARM GCC Toolchain安装 |
-| ST-Link/V2 | https://www.st.com/en/development-tools/st-link-v2.html | 官网下载驱动 |
-| CppCheck | https://cppcheck.sourceforge.io/ | 包管理器或安装包 |
-| Clang-Tidy | https://clang.llvm.org/extra/clang-tidy/ | 随LLVM安装 |
-| SonarQube | https://www.sonarqube.org/ | Docker容器或独立安装 |
-| Unity | https://github.com/ThrowTheSwitch/Unity | 从GitHub下载源码 |
-| CMock | https://github.com/ThrowTheSwitch/CMock | 从GitHub下载源码 |
-| Google Test | https://github.com/google/googletest | CMake构建并安装 |
+| 工具 | 用途 |
+| --- | --- |
+| VS Code | 轻量编辑与插件生态 |
+| PlatformIO | 嵌入式项目管理和构建 |
+| STM32CubeIDE | STM32 官方 IDE |
+| CLion | 大型 C/C++ 工程开发 |
+| OpenOCD | 下载与调试桥接 |
+| GDB | 断点和调试分析 |
+| CppCheck / Clang-Tidy | 静态检查 |
+| Unity / CMock / Google Test | 测试框架 |
+
+---
+
+## 本章小结
+
+从个人写代码走向团队交付,关键差异就在工程化。版本控制、构建系统、模块边界、CI、静态分析和测试体系共同决定了项目是否能长期演进。
+
diff --git a/09-2025_AI_on_MCU/README.md b/09-2025_AI_on_MCU/README.md
index 154c52d..3849666 100644
--- a/09-2025_AI_on_MCU/README.md
+++ b/09-2025_AI_on_MCU/README.md
@@ -1,282 +1,158 @@
# 第九层:2025 新趋势
+这一章属于趋势专题,重点是帮助你建立对 Edge AI、TinyML、安全启动和可信设备的现实认知。目标不是立刻掌握全部工具链,而是知道这些技术何时有价值、何时成本过高。
+
+建议学习目标:
+
+- 理解 AI on MCU 与传统嵌入式算法的差别。
+- 掌握 TinyML 的基本开发流程:训练、量化、部署、推理。
+- 对 STM32 AI 工具链、模型压缩和部署限制有初步认知。
+- 理解安全启动、TPM 与设备可信链的基本作用。
+
+阅读建议:先看 AI on MCU 的基本流程,再看量化和案例,最后再理解安全性扩展部分。
+
+---
+
## AI on MCU / Edge AI
### TinyML / TensorFlow Lite Micro
-#### 1. **概念与优势**
-- **TinyML**:将机器学习模型部署到资源受限的微控制器(MCU)上,实现边缘智能。
-- **优势**:
- - **低延迟**:本地处理数据,无需云端交互。
- - **低功耗**:适合电池供电的物联网设备。
- - **隐私保护**:敏感数据无需上传。
- - **离线运行**:在网络中断时仍能工作。
-
-#### 2. **开发流程**
-1. **模型训练**:
- 使用TensorFlow/Keras等工具在PC上训练模型。
- ```python
- # 简单MNIST模型示例
- model = tf.keras.Sequential([
- tf.keras.layers.Flatten(input_shape=(28, 28)),
- tf.keras.layers.Dense(128, activation='relu'),
- tf.keras.layers.Dense(10, activation='softmax')
- ])
- model.compile(optimizer='adam',
- loss='sparse_categorical_crossentropy',
- metrics=['accuracy'])
- model.fit(x_train, y_train, epochs=5)
- ```
-
-2. **模型量化**:
- 将浮点模型转换为整数模型,减少内存占用和计算量。
- ```python
- converter = tf.lite.TFLiteConverter.from_keras_model(model)
- converter.optimizations = [tf.lite.Optimize.DEFAULT]
- tflite_model = converter.convert()
- ```
-
-3. **模型部署**:
- 将量化后的模型转换为C数组,集成到MCU项目中。
- ```bash
- xxd -i model.tflite > model_data.cc
- ```
-
-4. **MCU推理**:
- 使用TensorFlow Lite Micro框架在MCU上运行模型。
- ```c
- // 初始化解释器
- tflite::MicroErrorReporter micro_error_reporter;
- const tflite::ErrorReporter* error_reporter = µ_error_reporter;
-
- const tflite::MicroOpResolver& op_resolver = MicroOpsResolver();
- const tflite::SimpleTensorAllocator tensor_allocator(tensor_arena, kTensorArenaSize);
-
- tflite::MicroInterpreter interpreter(model_data, model_data_len, op_resolver,
- tensor_allocator, error_reporter);
-
- // 运行推理
- TfLiteStatus invoke_status = interpreter.Invoke();
- if (invoke_status != kTfLiteOk) {
- error_reporter->Report("Invoke failed\n");
- }
- ```
-
-#### 3. **性能指标**
-| **模型** | **参数量** | **激活内存** | **准确率** | **推理时间(STM32H7)** |
-|----------------|------------|--------------|------------|-------------------------|
-| MobileNetV1 | 4.2M | 16MB | 70.6% | 800ms |
-| TinyMLNet | 0.02M | 0.2MB | 68.2% | 5ms |
-| EfficientNet-Lite0 | 4M | 12MB | 75.0% | 600ms |
+TinyML 的核心思想是:把轻量模型部署到 MCU 或资源受限设备上,在本地完成推理。
+
+相比传统“把数据传到云端再推理”的方式,它的优势通常在于:
+
+- 时延低
+- 功耗可控
+- 隐私性更好
+- 离线可运行
+
+典型开发流程:
+
+1. 在 PC 上训练模型
+2. 做量化和裁剪
+3. 转换为嵌入式可集成格式
+4. 在 MCU 上运行推理
+5. 验证精度、时延、内存占用和功耗
+
+TensorFlow Lite Micro 是当前最常见的 MCU 侧推理框架之一。
### STM32 AI 开发套件
-#### 1. **硬件平台**
-- **STM32H7系列**:高性能MCU,支持DSP和FPU,适合运行复杂AI模型。
-- **STM32L4+系列**:低功耗MCU,集成AI加速器,适合电池供电设备。
-- **X-CUBE-AI扩展包**:提供模型转换工具和优化库。
-
-#### 2. **开发工具链**
-1. **STM32CubeMX**:配置硬件和生成初始化代码。
-2. **STM32Cube.AI**:将TensorFlow/PyTorch模型转换为STM32优化代码。
- ```bash
- # 使用x-cube-ai命令行工具转换模型
- stm32ai generate -m model.h5 -o stm32ai_output
- ```
-3. **STM32CubeIDE**:集成开发环境,调试和优化AI应用。
-
-#### 3. **性能优化**
-- **硬件加速**:利用STM32的DSP、FPU和专用AI加速器(如STM32H7的Chrom-ART加速器)。
-- **模型优化**:使用STM32Cube.AI的量化工具将模型压缩至8位或更少。
-- **内存管理**:优化模型和中间数据的内存布局,减少RAM占用。
+STM32 生态中较常见的相关工具包括:
+
+- STM32CubeMX
+- STM32Cube.AI
+- STM32CubeIDE
+
+它们的价值主要在于:
+
+- 降低模型转换门槛
+- 提供针对 STM32 的推理优化
+- 帮助评估 RAM / Flash 占用和运行时间
+
+但要注意,工具链方便并不代表项目一定适合上 AI。模型大小、功耗、数据质量和维护成本必须一起评估。
### 模型量化与部署
-#### 1. **量化技术**
-- **权重量化**:将浮点权重转换为整数(通常8位或更少)。
-- **激活量化**:运行时将输入/输出数据转换为整数。
-- **混合精度**:对关键层使用更高精度,平衡准确率和性能。
+模型量化常用于把浮点模型压缩成 8bit 或更低精度形式,以减少:
-#### 2. **部署挑战与解决方案**
-| **挑战** | **解决方案** |
-|------------------------|----------------------------------------------|
-| 内存受限 | 使用内存映射技术,模型分段加载 |
-| 计算能力有限 | 优化算子实现,利用硬件加速指令 |
-| 功耗敏感 | 采用低功耗模式,推理过程中动态调整频率 |
-| 模型更新 | 设计OTA机制,支持模型动态更新 |
+- Flash 占用
+- RAM 占用
+- 计算负担
+
+部署时常见难点:
+
+- 模型太大
+- 中间激活内存不足
+- 推理耗时超预算
+- 输入数据预处理和训练时不一致
+
+实际落地时,要优先评估的不是“模型在 PC 上多准”,而是“在目标 MCU 上是否跑得动、耗得起、维护得住”。
### AI + 外设驱动融合案例
-#### 1. **智能传感器处理**
-- **场景**:基于加速度计数据的活动识别。
-- **实现**:
- ```c
- // 从加速度计读取数据
- void read_accelerometer_data(float *data, size_t length) {
- // 读取加速度计原始数据
- int16_t raw_data[3];
- accelerometer_read(raw_data);
-
- // 转换为浮点数并归一化
- for (int i = 0; i < 3; i++) {
- data[i] = (float)raw_data[i] / 32768.0f;
- }
- }
-
- // 运行AI模型进行活动识别
- activity_t recognize_activity(float *sensor_data) {
- // 准备模型输入
- TfLiteTensor* input = interpreter->input(0);
- memcpy(input->data.f, sensor_data, input->bytes);
-
- // 执行推理
- if (interpreter->Invoke() != kTfLiteOk) {
- return ACTIVITY_UNKNOWN;
- }
-
- // 获取输出结果
- TfLiteTensor* output = interpreter->output(0);
- int activity_index = argmax(output->data.f, output->dims->data[0]);
-
- return (activity_t)activity_index;
- }
- ```
-
-#### 2. **预测性维护**
-- **场景**:基于振动传感器的电机故障预测。
-- **实现**:
- 1. 采集振动数据并进行FFT变换。
- 2. 使用AI模型分析频谱特征,识别潜在故障。
- 3. 通过BLE将结果发送至云端。
+AI 不会单独存在,它往往只是嵌入式系统中的一个能力模块。
+
+典型场景:
+
+- 振动信号故障检测
+- 语音唤醒
+- 姿态识别
+- 简易图像分类
+
+真正落地时,AI 模块通常要和:
+
+- 传感器采样
+- DMA 传输
+- 缓冲区管理
+- 通信上报
+
+一起设计。
+
+---
## 安全性
### 安全启动(Secure Boot)
-#### 1. **原理与流程**
-1. **硬件信任根**:
- - 设备内置不可更改的私钥(存储在OTP中)。
- - 用于验证第一个加载的软件组件(通常是Bootloader)。
-
-2. **验证流程**:
- ```
- ROM → 验证Bootloader签名 → 验证应用固件签名 → 启动应用
- ```
-
-#### 2. **STM32实现**
-- **选项字节配置**:
- ```c
- // 启用读保护(RDP)
- HAL_FLASH_OB_Unlock();
- FLASH_OBProgramInitTypeDef obInit;
- obInit.OptionType = OPTIONBYTE_RDP;
- obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
- HAL_FLASHEx_OBProgram(&obInit);
- HAL_FLASH_OB_Lock();
- ```
-
-- **签名验证**:
- ```c
- // 验证固件签名
- bool verify_firmware_signature(const uint8_t *firmware, size_t size, const uint8_t *signature) {
- // 从OTP读取公钥
- const uint8_t *public_key = get_public_key_from_otp();
-
- // 使用ECDSA验证签名
- return ecdsa_verify(public_key, firmware, size, signature);
- }
- ```
+安全启动的目的是保证设备只运行可信固件。
+
+常见思路:
+
+1. 从硬件信任根出发
+2. 校验 Bootloader
+3. 校验固件镜像
+4. 校验升级包
+
+对于联网设备和可升级设备,这一能力非常关键。
### TPM 安全芯片接入
-#### 1. **TPM 2.0 概述**
-- **功能**:
- - 安全存储密钥
- - 硬件级加密
- - 平台身份验证
- - 远程证明
-
-#### 2. **STM32与TPM集成**
-- **硬件连接**:
- STM32通过I2C/SPI与TPM芯片(如Infineon OPTIGA™ TPM SLB 9670)通信。
-
-- **软件实现**:
- ```c
- // TPM初始化
- tpm_error_t tpm_init(void) {
- // 初始化I2C接口
- i2c_init(TPM_I2C_ADDRESS);
-
- // 发送TPM启动命令
- uint8_t startup_cmd[10] = {0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44};
- uint8_t response[20];
-
- if (i2c_write(TPM_I2C_ADDRESS, startup_cmd, 10) != 0) {
- return TPM_ERROR_COMMUNICATION;
- }
-
- // 读取响应
- if (i2c_read(TPM_I2C_ADDRESS, response, 20) != 0) {
- return TPM_ERROR_COMMUNICATION;
- }
-
- // 验证响应
- if (response[6] == 0x00 && response[7] == 0x00) {
- return TPM_SUCCESS;
- } else {
- return TPM_ERROR_INITIALIZATION;
- }
- }
-
- // 生成密钥
- tpm_error_t tpm_generate_key(uint8_t *key_handle, uint8_t *public_key) {
- // 发送生成密钥命令
- // ...
-
- // 处理响应
- // ...
-
- return TPM_SUCCESS;
- }
- ```
-
-#### 3. **应用场景**
-- **安全启动增强**:使用TPM验证固件完整性。
-- **安全通信**:TPM生成和存储TLS密钥,保护通信数据。
-- **设备身份认证**:基于TPM的唯一密钥实现设备身份识别。 `
+TPM 或其他安全芯片常用于:
+
+- 密钥安全存储
+- 身份认证
+- 签名校验
+- 平台可信证明
+
+它的价值不在于“让设备更复杂”,而在于把关键安全材料从普通 Flash / 文件系统中隔离出来。
+
+---
## 实战案例
-### 1. **工业设备预测性维护**
-- **需求**:基于振动传感器数据预测设备故障。
-- **实现**:
- - 使用STM32H7采集振动数据。
- - 部署TinyML模型进行实时分析。
- - 通过TLS加密将结果发送至云端。
- - 使用TPM确保数据完整性和设备身份安全。
-
-### 2. **智能家居安全监控**
-- **需求**:基于摄像头的人体检测与异常行为识别。
-- **实现**:
- - 使用STM32MP1微处理器运行轻量级CNN模型。
- - 仅在检测到异常时唤醒系统并发送警报。
- - 通过安全启动确保固件未被篡改。
- - 使用TPM存储用户认证密钥。
+### 工业设备预测性维护
+
+典型链路:
+
+1. 采集振动 / 电流等信号
+2. 做特征提取或轻量模型推理
+3. 本地判断风险等级
+4. 通过安全通信上传结果
+
+### 智能家居安全监控
+
+典型链路:
+
+1. 本地传感器或图像输入
+2. 做事件检测
+3. 只在必要时上传数据或报警
+4. 配合安全启动和身份认证保证设备可信
+
+---
## 参考资源
-1. **AI on MCU**:
- - [TensorFlow Lite Micro](https://www.tensorflow.org/lite/microcontrollers)
- - [STM32Cube.AI](https://www.st.com/en/embedded-software/x-cube-ai.html)
- - [Edge Impulse](https://www.edgeimpulse.com/)
+- TensorFlow Lite Micro
+- STM32Cube.AI
+- Edge Impulse
+- PSA Certified
+- mbed TLS
+- TPM 2.0 规范文档
+
+---
-2. **安全性**:
- - [PSA Certified](https://www.psacertified.org/)
- - [mbed TLS](https://tls.mbed.org/)
- - [TPM 2.0 Specification](https://trustedcomputinggroup.org/resource/tpm-library-specification/)
+## 本章小结
-3. **实战案例**:
- - [STMicroelectronics AI Demo](https://www.st.com/en/evaluation-tools/stm32ai-discovery.html)
- - [ESP32 TinyML Examples](https://github.com/tensorflow/tflite-micro-arduino-examples)
+AI on MCU 和安全可信链是嵌入式领域的重要新方向,但落地时必须同时考虑资源、功耗、延迟、成本和可维护性。理解取舍,比盲目追新更重要。
-AI与安全是2025年嵌入式领域的两大核心趋势。通过将AI算法部署到边缘设备,可实现实时智能决策,同时降低网络带宽和云端计算成本。而安全性则是保障设备和数据可信的基础,从安全启动到加密通信,再到TPM硬件级保护,构建多层次安全防护体系。在实际项目中,需根据具体需求选择合适的AI模型和安全方案,平衡性能、功耗和安全性。
diff --git a/README.md b/README.md
index 1cac092..d8b27b3 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,16 @@
### 概述
-欢迎来到本项目,这里拥有**系统、全面**且贴近实战的2025年嵌入式软件开发学习路线和知识点总结。
-涵盖包括**C语言、驱动开发、RTOS、嵌入式 Linux、网络通信与物联网、常用工具链**等嵌入式软件开发所需知识点
-以及嵌入式开发相关的**必读书籍、面试题以及面经**。
+本项目整理了一套面向嵌入式软件开发的系统化学习笔记,覆盖 **C 语言基础、驱动开发、RTOS、Embedded Linux、网络通信与物联网、调试优化、项目工具链以及 AI on MCU 等专题**。
-> 跟随目录点击选择想学习的部分即可跳转至相应知识点。
-> 爆肝两周只为给大家呈现最新开源免费的嵌入式软件学习资料!无任何套路!
-> 能够帮助到你的话请点个star收藏一下推给更多有需要的人就是最大鼓励,不胜感激!
+这些内容更适合作为长期查阅的知识库,而不是零散的速记文档。阅读时建议优先沿主线章节推进,再根据需要进入面试题、书单和专题资料。
+
+### 如何使用本仓库
+
+- 初学者建议按目录顺序学习,从 C 语言基础一路读到工程化与专题章节。
+- 已有基础的读者可以按主题跳读,例如只看 RTOS、驱动、Linux 或 IoT 相关部分。
+- 面试准备可直接进入“面试题与面经”页,但建议回到主线章节补背景知识。
+- 书单、PDF 和网盘资源更适合作为补充阅读,不建议替代主线笔记本身。
### **目录**
* C语言基础
@@ -4730,7 +4733,7 @@ typedef struct {
---
---
-# 免责声明
+## 免责声明
本项目内容均来源于互联网公开资料,仅供学习交流使用,版权归原作者所有。
diff --git a/books/README.md b/books/README.md
index bd60be7..b996ca3 100644
--- a/books/README.md
+++ b/books/README.md
@@ -1,53 +1,46 @@
-**推荐读物:**
+# 参考书与资料
-《C和指针第二版》
+这一页用于集中整理嵌入式学习过程中常见的书籍、资料和补充下载入口。建议把它作为“延伸阅读索引”,而不是主线学习入口。
-《LinuxC编程一站式学习》
+## 使用建议
-《STC89C52数据手册》
+- 如果你正在系统学习,优先沿着主章节内容阅读。
+- 如果你希望补背景、查资料或找电子书,再回到本页按主题选择。
+- 对 PDF、网盘资料应以检索和补充阅读为主,不建议替代主线笔记本身。
-《STM32不完全手册库函数版》
+## 推荐读物
-《cortexM3权威指南》
+- 《C 和指针》
+- 《Linux C 编程一站式学习》
+- 《STC89C52 数据手册》
+- 《STM32 不完全手册(库函数版)》
+- 《Cortex-M3 权威指南》
+- 《STM32F10 中文参考手册》
+- 《FreeRTOS 源码详解与应用开发:基于 STM32》
+- 《跟我一起写 Makefile》
+- 《UNIX 环境高级编程》
+- 《深入理解 Linux 内核》
+- 《TCP/IP 详解》
-《STM32F10中文参考手册》
+## 仓库内 PDF 资料
-《FreeRTOS源码详解与应用开发 基于STM32》
+- [Arm64 指令集快速查找表](./Arm64%20%E6%8C%87%E4%BB%A4%E9%9B%86%E5%BF%AB%E9%80%9F%E6%9F%A5%E6%89%BE%E8%A1%A8%28ARMv8%20A64%20Quick%20Reference%29.pdf)
+- [Linux 初学者入门优秀教程](./Linux%E5%88%9D%E5%AD%A6%E8%80%85%E5%85%A5%E9%97%A8%E4%BC%98%E7%A7%80%E6%95%99%E7%A8%8B.pdf)
+- [Shell 命令行操作](./Shell%E5%91%BD%E4%BB%A4%E8%A1%8C%E6%93%8D%E4%BD%9C.pdf)
-《跟我一起写Makefile》
+## 百度网盘资源
-《UNIX环境高级编程》
+更多电子书和整理资料可通过以下网盘入口获取:
-《深入理解LINUX内核_第3版》
+- [百度网盘下载入口](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)
+- 提取码:`tex9`
-《UNIX环境高级编程》
+如网页扫码更方便,可使用下方二维码:
-《TCPIP详解卷》
+
-#### 因仓库容量限制,更多电子书资源在百度网盘:[点击这里下载](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)提取码: tex9
----
-
-或扫描二维码
-
----
-6.19 补充
-通过网盘分享的文件:电子书汇总.rar
-链接: https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9 提取码: tex9
-
-> 资源全面无套路,只求star一下本项目,让更多需要的人可以学习!
-> 如果资源失效请第一时间 issue 或通过邮箱联系我!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+## 补充说明
+- 电子书和资料适合在遇到具体知识点时配合查阅。
+- 芯片手册、编程指南、协议文档等一手资料优先级通常高于二手总结。
+- 若资料失效,可通过 issue 或仓库联系方式反馈。
diff --git "a/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md" "b/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
index 96a5782..a9a877a 100644
--- "a/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
+++ "b/\345\265\214\345\205\245\345\274\217\345\233\276\345\275\242 Qt \345\274\200\345\217\221/README.md"
@@ -1,221 +1,209 @@
# 嵌入式平台 Qt 开发知识体系
+Qt 专题面向图形界面和人机交互场景,适合工业 HMI、车载终端、仪表盘和智能设备面板开发。阅读时建议始终把“图形界面只是上层表现,底下仍然是嵌入式系统”这条线放在心里。
+
+建议学习目标:
+
+- 了解 Qt Widgets、Qt Quick / QML 在嵌入式中的差异。
+- 理解交叉编译、部署、图形后端和硬件适配的基本流程。
+- 掌握 Qt 的信号槽、多线程、定时器和控件体系在嵌入式中的典型用法。
+- 建立对 GUI 性能、资源占用和平台适配的工程认知。
+
+阅读建议:先看 Qt 在嵌入式中的定位,再看交叉编译与部署,最后理解控件、线程和性能优化。
+
+---
+
## 一、Qt 嵌入式开发基础认知
+
### (一)Qt 框架适配嵌入式的核心价值
-1. **跨平台兼容性**
-支持多类嵌入式系统(Linux、RTOS如FreeRTOS/RT-Thread、QNX、VxWorks等),一套代码可部署至ARM、RISC - V、x86等架构硬件,降低多平台适配成本。
-2. **轻量级与可裁剪性**
-通过`qmake`/CMake配置,可按需裁剪Qt模块(如关闭`QtNetwork`减少网络模块依赖),适配资源受限的嵌入式设备(如MCU级硬件)。
-3. **高效图形渲染能力**
-- **传统方案**:`Qt Widgets`基于`QPainter`提供基础控件(按钮、标签等),满足简单GUI需求。
-- **现代方案**:`Qt Quick/QML`采用声明式语法,结合`Scene Graph`实现硬件加速渲染,适配高动态交互场景(如车载中控、智能仪表)。
+
+Qt 在嵌入式场景里的最大价值,不只是“能做界面”,而是它把 GUI、事件系统、资源管理和跨平台能力整合成了一套相对完整的开发框架。
+
+核心优势:
+
+- 跨平台:同一套 UI 逻辑可迁移到不同硬件与系统
+- 图形能力完整:既能做简单控件,也能做复杂动画与交互
+- 工程体系成熟:工具链、构建、资源管理都较完善
### (二)嵌入式开发典型场景
-覆盖工业控制(人机界面HMI)、医疗设备(便携式诊断仪界面)、汽车电子(车载信息娱乐系统IVI)、智能家居(智能家电控制面板)、手持终端(工业PDA、POS机)等领域,成为嵌入式GUI开发首选框架。
+
+Qt 在嵌入式中的常见应用包括:
+
+- 工业控制 HMI
+- 医疗设备界面
+- 车载中控或仪表
+- 智能家居控制面板
+- 手持终端
+
+在这些场景中,界面并不是独立存在的,它需要和驱动、通信、状态机和系统资源协同工作。
+
+---
## 二、环境搭建与工具链
+
### (一)开发环境搭建
-1. **宿主环境**
-主流选择Linux(如Ubuntu)作为开发主机,便于交叉编译工具链配置;Windows可通过WSL2或虚拟机模拟Linux环境。
-2. **Qt 安装与配置**
-- 下载[Qt Online Installer](https://www.qt.io/download),选择对应版本(建议LTS版,如Qt 6.6),按需安装`Qt Widgets`、`Qt Quick`、`Qt for Device Creation`等组件。
-- 配置交叉编译工具链(如ARM的`arm - linux - gnueabihf - gcc`、RISC - V的`riscv64 - linux - gnu - gcc`),在Qt Creator中关联工具链(`工具>选项>设备>编译器/ kits`)。
+
+通常需要准备:
+
+- 主机开发环境(常见为 Linux,也可能是 Windows + WSL)
+- Qt 安装环境
+- 目标板 sysroot
+- 交叉编译工具链
+
+重点不只是把 Qt 装好,而是确保:
+
+- 编译器正确
+- 目标库正确
+- 依赖库版本匹配
### (二)交叉编译流程
-以ARM嵌入式平台为例:
-```bash
-# 配置Qt交叉编译
-./configure -prefix /opt/qt5 -opensource -confirm -license \
--platform linux - gcc -device linux - arm - gnueabihf - g++ \
--device - option CROSS_COMPILE = /path/to/arm - linux - gnueabihf - \
--sysroot /path/to/sysroot - opengl es2 - eglfs
-
-# 编译与安装
-make - j$(nproc)
-make install
-```
-编译后,生成适配目标平台的Qt库,用于嵌入式应用开发。
+
+Qt for Embedded 的核心工作之一,就是交叉编译。
+
+通用流程:
+
+1. 准备工具链和 sysroot
+2. 配置 Qt 构建选项
+3. 编译并安装 Qt 库
+4. 使用相同工具链编译应用
+5. 部署到目标板验证运行
+
+如果这一步不清楚,后续很多“程序编译过了但板子上跑不起来”的问题都会反复出现。
+
+---
## 三、核心机制与基础开发
+
### (一)信号与槽机制
-1. **异步事件驱动**
-实现界面交互(如按钮`clicked`信号触发LED控制槽函数)、硬件事件响应(串口数据接收信号触发解析逻辑),解耦嵌入式系统中UI、外设、业务逻辑。
-2. **跨线程/跨模块通信**
-支持线程间安全通信(如传感器采集线程发送`dataReady`信号,UI线程更新显示),或不同模块(硬件驱动与应用逻辑)间解耦。
-
-示例(控制嵌入式LED):
-```cpp
-class LedControl : public QObject {
- Q_OBJECT
-public slots:
- void toggleLed() { /* 操作GPIO控制LED */ }
-};
-
-// 主窗口绑定
-QPushButton *btn = new QPushButton("Toggle LED");
-LedControl *led = new LedControl();
-connect(btn, &QPushButton::clicked, led, &LedControl::toggleLed);
-```
+
+信号与槽是 Qt 的核心机制,适合处理:
+
+- 控件交互
+- 模块解耦
+- 线程间通知
+- 外设事件响应
+
+它的价值在于:
+
+- 让 UI 逻辑和业务逻辑更容易分离
+- 便于做异步事件驱动
### (二)嵌入式控件开发与适配
-1. **基础控件优化**
-- `QPushButton`:适配触摸交互,设置`setFlat(true)`简化样式,通过`setStyleSheet`自定义按压反馈(如改变背景色)。
-- `QLabel`:显示传感器数据、状态图标,支持`setPixmap`加载硬件加速的SVG/PNG图像,减少内存占用。
-- `QSlider`:调节设备参数(音量、亮度),通过`valueChanged`信号实时同步硬件状态。
-2. **自定义控件**
-针对嵌入式外设(如旋钮、仪表盘),继承`QWidget`/`QQuickItem`开发自定义控件,复用Qt绘图系统(`QPainter`/`QSGNode`)实现硬件状态可视化。
+
+嵌入式 Qt 界面常见的关注点包括:
+
+- 触摸友好
+- 字体与缩放
+- 分辨率适配
+- 刷新性能
+- 内存占用
+
+设计控件时要记住:嵌入式界面不是桌面软件的缩小版,很多设备没有鼠标、没有大内存,也不允许复杂动画随意堆叠。
+
+---
## 四、嵌入式功能开发模块
+
### (一)定时器(QTimer)
-1. **硬件轮询**
-定时读取传感器数据(如每100ms读取温湿度传感器),通过`timeout`信号触发采集逻辑,适配嵌入式低功耗需求(减少无效轮询)。
-2. **动画与状态刷新**
-配合QML实现LED呼吸灯、UI状态轮询(如网络连接状态),或在`Qt Widgets`中驱动自定义动画(进度条、波形图刷新)。
-
-示例(传感器数据采集):
-```cpp
-QTimer *sensorTimer = new QTimer(this);
-sensorTimer->setInterval(100);
-connect(sensorTimer, &QTimer::timeout, this, &SensorWidget::readSensorData);
-sensorTimer->start();
-```
+
+QTimer 适合做:
+
+- 周期刷新
+- 状态轮询
+- UI 更新节奏控制
+
+但如果任务本身耗时较长,不应把复杂逻辑直接堆进定时器回调。
### (二)文本与文件操作
-1. **配置文件读写**
-利用`QSettings`读写嵌入式设备配置(如串口波特率、LED亮度),支持INI、JSON格式,存储路径可指定为`/etc`等系统分区。
-2. **日志系统**
-通过`qInstallMessageHandler`自定义日志输出,将调试信息写入串口、本地文件或远程服务器,便于嵌入式设备离线调试。
-
-示例(配置文件读写):
-```cpp
-QSettings settings("/etc/app_config.ini", QSettings::IniFormat);
-settings.setValue("serial/baudrate", 115200);
-int baudrate = settings.value("serial/baudrate").toInt();
-```
+
+Qt 在文件操作和配置管理方面非常方便,例如:
+
+- `QFile`
+- `QTextStream`
+- `QSettings`
+
+嵌入式项目里,这些能力常用来:
+
+- 保存配置
+- 输出日志
+- 读取资源文件
### (三)绘图与数据可视化
-1. **QPainter 绘图**
-用于自定义控件(如仪表盘、波形图),直接操作硬件加速的绘图上下文,适配嵌入式GPU渲染(如ARM Mali、全志VPU)。
-2. **QChart 图表**
-展示传感器历史数据(温度曲线、压力变化),需优化内存(限制数据点缓存数量),通过`QChartView`嵌入界面,支持触控交互(缩放、平移)。
-
-示例(简单波形绘制):
-```cpp
-void CustomPlot::paintEvent(QPaintEvent *event) {
- QPainter painter(this);
- painter.drawLine(0, height()/2, width(), height()/2); // 基线
- // 绘制传感器数据点...
-}
-```
+
+Qt Widgets 和 Qt Quick 都可用于图形绘制,但取舍不同:
+
+- Widgets 更传统,适合管理型界面
+- QML / Qt Quick 更适合动画和复杂交互
### (四)多线程开发
-1. **硬件交互线程**
-独立线程处理耗时操作(串口数据解析、传感器采集),避免阻塞UI线程,保证嵌入式界面响应流畅。
-2. **线程同步**
-通过`QMutex`、`QWaitCondition`保证多线程访问硬件资源(GPIO、I2C、SPI)的安全性,防止竞争条件。
-3. **轻量化线程池**
-优先使用`QRunnable` + `QThreadPool`实现任务池(如批量传感器数据处理),减少线程创建销毁开销,适配嵌入式资源限制。
-
-示例(串口数据采集线程):
-```cpp
-class SerialWorker : public QObject, public QRunnable {
- Q_OBJECT
-public:
- void run() override { /* 串口数据读取与解析逻辑 */ }
-signals:
- void dataReady(QByteArray data);
-};
-
-// 主线程调用
-QThreadPool::globalInstance()->start(new SerialWorker());
-```
-
-## 五、嵌入式外设交互开发
-### (一)多媒体应用开发
-1. **音频播放**
-使用`QMediaPlayer` + `QAudioOutput`播放提示音、语音播报,依赖嵌入式平台音频驱动(如ALSA、ASoC),需在系统中配置音频设备。
-2. **视频渲染**
-通过`QVideoWidget`(`Qt Widgets`)或QML `Video`组件播放摄像头画面、本地视频,结合硬件解码(如全志VPU、NXP i.MX VPU)降低CPU负载,需在Qt配置中启用对应编解码器。
-
-示例(简单音频播放):
-```cpp
-QMediaPlayer *player = new QMediaPlayer(this);
-player->setMedia(QUrl::fromLocalFile("/usr/share/sounds/alert.wav"));
-player->setAudioOutput(new QAudioOutput(this));
-player->play();
-```
-
-### (二)硬件控制(LED、按键等)
-1. **LED控制**
-- **sysfs方式**:操作`/sys/class/leds`路径下的LED节点(亮度、触发模式),封装`QLedControl`类实现控制。
-- **直接硬件操作**:调用嵌入式平台SDK接口(如`stm32_gpio_set`、`sunxi_gpio_set_value`),直接控制GPIO电平。
-
-示例(sysfs控制LED):
-```cpp
-class QLedControl : public QObject {
- Q_OBJECT
-public:
- void setBrightness(int value) {
- QFile file("/sys/class/leds/led0/brightness");
- if (file.open(QIODevice::WriteOnly)) {
- file.write(QByteArray::number(value));
- file.close();
- }
- }
-};
-```
-
-2. **按键交互**
-- **输入子系统**:通过`QSocketNotifier`监听`/dev/input/eventX`设备节点,捕获按键事件(按下、松开、长按)。
-- **触摸模拟**:在带触摸屏的嵌入式设备中,利用Qt触摸事件模拟按键交互,简化硬件设计。
-
-示例(监听输入事件):
-```cpp
-QSocketNotifier *notifier = new QSocketNotifier(keyEventFd, QSocketNotifier::Read, this);
-connect(notifier, &QSocketNotifier::activated, this, &KeyWidget::onKeyEvent);
-```
-
-### (三)串口通信(Serial)
-1. **基础配置与数据收发**
-使用`QSerialPort`配置串口参数(波特率、数据位、校验位等),适配嵌入式平台UART外设,注意运行时设备权限(需`sudo`或配置`udev`规则)。
-2. **协议解析与多线程处理**
-在独立线程中处理串口数据接收、协议解析(Modbus、自定义二进制协议),通过信号与槽同步到UI线程,避免阻塞界面。
-
-示例(串口通信):
-```cpp
-QSerialPort *serial = new QSerialPort(this);
-serial->setPortName("/dev/ttyS0");
-serial->setBaudRate(115200);
-if (serial->open(QIODevice::ReadWrite)) {
- connect(serial, &QSerialPort::readyRead, this, &SerialWidget::onSerialDataReceived);
-}
-```
-
-## 六、进阶优化与平台适配
-### (一)性能优化策略
-1. **资源裁剪**
-通过`qmake`/CMake关闭不必要的Qt模块(如`QT -= network`),减小应用体积;使用`strip`工具去除调试符号,进一步压缩可执行文件。
-2. **渲染优化**
-- 优先采用QML硬件加速渲染,避免复杂`QWidget`层级嵌套。
-- 配置`QSG_RHI_BACKEND`指定嵌入式平台GPU渲染后端(如`vulkan`、`opengl`、`metal`),利用硬件加速提升图形性能。
-3. **内存管理**
-- 嵌入式场景禁用Qt调试内存分配器(定义`QT_NO_DEBUG`宏),减少内存开销。
-- 使用`QScopedPointer`、`QSharedPointer`等智能指针管理硬件资源,防止内存泄漏。
-
-### (二)多平台适配与BSP集成
-1. **设备树与硬件抽象**
-在嵌入式Linux中,通过设备树配置Qt依赖的硬件资源(帧缓冲、GPU节点、外设引脚),确保Qt EGLFS/Wayland后端正确识别硬件。
-2. **BSP定制与编译**
-基于嵌入式平台BSP(如Yocto Project、Buildroot)编译Qt库,启用平台特定优化(NEON指令集加速、硬件编解码器支持),并集成到系统镜像。
-
-### (三)调试与部署
-1. **远程调试**
-利用Qt Creator的远程调试功能,通过GDB Server连接嵌入式设备,实时调试程序、查看变量与调用栈,定位硬件交互、逻辑错误。
-2. **应用部署**
-- 使用`linuxdeployqt`工具打包Qt应用及依赖库,生成独立可执行包,适配不同嵌入式系统。
-- 通过Yocto Project、Buildroot将Qt应用集成到系统镜像,实现出厂预装。
+
+Qt 多线程通常用来解决:
+
+- 后台通信
+- 传感器采集
+- 文件处理
+- 图像处理
+
+核心目标是避免阻塞 UI 线程。
+
+---
+
+## 五、性能优化与资源管理
+
+嵌入式 Qt 项目最常见的问题包括:
+
+- 首屏慢
+- 滑动卡顿
+- 占内存过高
+- CPU 占用高
+
+常见优化手段:
+
+- 减少不必要重绘
+- 控制图片和字体资源大小
+- 避免在 UI 线程做耗时操作
+- 根据硬件能力选择合适的渲染后端
+
+---
+
+## 六、平台适配与部署
+
+### (一)多平台适配与 BSP 集成
+
+Qt 在嵌入式里的适配往往不止是“编译通过”,还包括:
+
+- 图形后端匹配
+- 输入设备适配
+- GPU / Framebuffer / Wayland 等后端选择
+- BSP 中库和依赖的集成
+
+### (二)调试与部署
+
+部署时常见方式包括:
+
+- 直接复制可执行文件和依赖库
+- 用打包工具整理运行环境
+- 集成进 Buildroot / Yocto 镜像
+
+调试时则常结合:
+
+- Qt Creator 远程调试
+- gdbserver
+- 串口日志
+
+---
## 七、生态与学习资源
-### 官方资源
-- [Qt 嵌入式开发文档](https://doc.qt.io/qt - for - embedded - linux/index.html):涵盖框架架构、平台适配、性能优化等内容。
-- [Qt for Device Creation](https://www.qt.io/product/qt - for - device - creation):专为嵌入式设计的商业解决方案,提供工具链、部署管理支持。
+
+推荐关注:
+
+- Qt 官方文档
+- Qt for Device Creation
+- Qt Quick / QML 文档
+- 目标平台 BSP 和 GPU 驱动文档
+
+---
+
+## 本章小结
+
+Qt 在嵌入式里的价值在于帮助你快速搭建高质量 GUI,但真正的难点仍然是平台适配、性能约束和与底层硬件的集成。把这些边界处理好,Qt 才能真正服务于产品交付。
diff --git "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md" "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
index 0da492b..3bb3914 100644
--- "a/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
+++ "b/\351\235\242\350\257\225\351\242\230\344\270\216\351\235\242\347\273\217/README.md"
@@ -1,12 +1,36 @@
-# 更多资源尽在百度网盘:[点击这里下载](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)提取码: tex9
+# 面试题与面经
-
+这一页作为面试资料入口,集中整理仓库中的面试题、复习资料和额外下载资源。建议把它作为“查漏补缺”区域,而不是替代主线知识学习。
----
+## 使用建议
-或扫描二维码
+- 如果你正在准备校招或社招,可以先从本页进入,再回到对应主章节补齐背景知识。
+- 如果你已经在读主线笔记,本页更适合作为复盘和高频题检查清单。
----
-6.19 补充
-通过网盘分享的文件:电子书汇总.rar
-链接: https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9 提取码: tex9
+## 仓库内面试资料
+
+- [操作系统面试题](./%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E9%9D%A2%E8%AF%95%E9%A2%98.md)
+- [计算机网络原理面试题](./%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86%E9%9D%A2%E8%AF%95%E9%A2%98.md)
+- [Linux 面试题 1](./Linux%E9%9D%A2%E8%AF%95%E9%A2%981.md)
+- [Linux 面试题 2](./Linux%E9%9D%A2%E8%AF%95%E9%A2%982.md)
+
+## 附加资料
+
+- [嵌入式资料整合第二辑 PDF](./%E5%B5%8C%E5%85%A5%E5%BC%8F%E8%B5%84%E6%96%99%E6%95%B4%E5%90%88%E7%AC%AC%E4%BA%8C%E8%BE%91.pdf)
+- [ARM 嵌入式系统基础教程 PDF](./ARM%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B.PDF)
+- [Linux BSP 工程师面试常问问题汇集 PDF](./Linux%20BSP%E5%B7%A5%E7%A8%8B%E5%B8%88%E9%9D%A2%E8%AF%95%E5%B8%B8%E9%97%AE%E9%97%AE%E9%A2%98%E6%B1%87%E9%9B%86..pdf)
+
+## 百度网盘资源
+
+- [百度网盘下载入口](https://pan.baidu.com/s/1ZNIpG7bVxzgVxD_ySkxZ0g?pwd=tex9)
+- 提取码:`tex9`
+
+二维码入口:
+
+
+
+## 复习建议
+
+- 操作系统题适合配合 RTOS、Linux 内核基础一起复习。
+- 网络题建议结合 TCP/IP、Socket、MQTT、HTTP/HTTPS 一起理解。
+- Linux 面试题建议与文件系统、进程、驱动模型和交叉编译知识一起回顾。
From 84ead92fedbc097aa7333b827e37a602536b6831 Mon Sep 17 00:00:00 2001
From: liueggy <3157487230@qq.com>
Date: Wed, 22 Apr 2026 10:30:30 +0800
Subject: [PATCH 09/13] =?UTF-8?q?=E4=BC=98=E5=8C=96pages=E5=B1=95=E7=A4=BA?=
=?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8vue=E7=BB=84=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.vitepress/config.ts | 5 +
.vitepress/theme/components/Breadcrumb.vue | 98 +++++++++
.vitepress/theme/components/CourseCard.vue | 207 +++++++++++++++++
.vitepress/theme/components/StudyRoadmap.vue | 89 ++++++++
.vitepress/theme/components/TagFilter.vue | 113 ++++++++++
.vitepress/theme/custom.css | 130 +++--------
.vitepress/theme/env.d.ts | 5 +
.vitepress/theme/index.ts | 27 ++-
index.md | 85 ++++---
package-lock.json | 220 +++++++++++++++++--
package.json | 5 +-
scripts/docs-tools.mjs | 29 ++-
study-map.md | 78 ++++---
13 files changed, 881 insertions(+), 210 deletions(-)
create mode 100644 .vitepress/theme/components/Breadcrumb.vue
create mode 100644 .vitepress/theme/components/CourseCard.vue
create mode 100644 .vitepress/theme/components/StudyRoadmap.vue
create mode 100644 .vitepress/theme/components/TagFilter.vue
create mode 100644 .vitepress/theme/env.d.ts
diff --git a/.vitepress/config.ts b/.vitepress/config.ts
index bc3eaad..0ba405c 100644
--- a/.vitepress/config.ts
+++ b/.vitepress/config.ts
@@ -2,6 +2,11 @@ import { defineConfig } from 'vitepress';
import { buildNav, buildRewrites, buildSidebar } from '../scripts/docs-tools.mjs';
export default defineConfig({
+ vite: {
+ ssr: {
+ noExternal: ['element-plus']
+ }
+ },
lang: 'zh-CN',
title: 'Embed Book',
description: '嵌入式软件开发全景知识库,覆盖 C 语言、驱动开发、RTOS、Embedded Linux、IoT 与 AI on MCU。',
diff --git a/.vitepress/theme/components/Breadcrumb.vue b/.vitepress/theme/components/Breadcrumb.vue
new file mode 100644
index 0000000..f16e36c
--- /dev/null
+++ b/.vitepress/theme/components/Breadcrumb.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+ {{ crumb.text }}
+ {{ crumb.text }}
+
+
+
+
+
diff --git a/.vitepress/theme/components/CourseCard.vue b/.vitepress/theme/components/CourseCard.vue
new file mode 100644
index 0000000..cae995b
--- /dev/null
+++ b/.vitepress/theme/components/CourseCard.vue
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
diff --git a/.vitepress/theme/components/StudyRoadmap.vue b/.vitepress/theme/components/StudyRoadmap.vue
new file mode 100644
index 0000000..7dbe65c
--- /dev/null
+++ b/.vitepress/theme/components/StudyRoadmap.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+ {{ stage.title }}
+ {{ stage.description }}
+
+
+
+
+
+
+
+
diff --git a/.vitepress/theme/components/TagFilter.vue b/.vitepress/theme/components/TagFilter.vue
new file mode 100644
index 0000000..e22f278
--- /dev/null
+++ b/.vitepress/theme/components/TagFilter.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+ 全部
+
+
+ {{ tag }}
+
+
+
+
+
+
diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css
index b1df1e3..0bf3966 100644
--- a/.vitepress/theme/custom.css
+++ b/.vitepress/theme/custom.css
@@ -7,6 +7,12 @@
--vp-home-hero-name-background: none;
--vp-home-hero-image-background-image: none;
--vp-home-hero-image-filter: none;
+ --el-color-primary: #1f4b6e;
+ --el-color-primary-light-3: #3d7bad;
+ --el-color-primary-light-5: #8fafc7;
+ --el-color-primary-light-7: #bfd4e3;
+ --el-color-primary-light-9: #e8f0f6;
+ --el-color-primary-dark-2: #173a57;
}
.VPHero.has-image .name,
@@ -47,111 +53,43 @@
background: transparent;
}
-.portal-strip {
- margin: 24px 0 20px;
- padding: 12px 14px;
- border: 1px solid rgba(148, 163, 184, 0.18);
- border-radius: 12px;
- background: #f8fafc;
- color: var(--vp-c-text-2);
- font-size: 14px;
-}
-
-.dark .portal-strip {
- background: rgba(15, 23, 42, 0.55);
-}
-
-.portal-index {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 20px;
- margin: 20px 0 28px;
-}
-
-.portal-block {
- padding: 18px 20px;
- border: 1px solid rgba(148, 163, 184, 0.18);
- border-radius: 14px;
- background: var(--vp-c-bg-soft);
-}
-
-.portal-block h3 {
- margin: 0 0 12px;
- font-size: 17px;
-}
-
-.portal-block ol,
-.portal-block ul {
- margin: 0;
- padding-left: 18px;
-}
-
-.portal-block li {
- margin: 8px 0;
- line-height: 1.6;
-}
-
-.portal-table {
- margin: 16px 0 28px;
- border: 1px solid rgba(148, 163, 184, 0.18);
- border-radius: 14px;
- overflow: hidden;
- background: var(--vp-c-bg-soft);
-}
-
-.portal-row {
- display: grid;
- grid-template-columns: 240px minmax(0, 1fr);
- gap: 18px;
- padding: 16px 18px;
- color: inherit;
- text-decoration: none;
- border-top: 1px solid rgba(148, 163, 184, 0.14);
- transition: background-color 0.18s ease;
+.vp-doc h2 {
+ border-top: none;
+ padding-top: 24px;
}
-.portal-row:first-child {
- border-top: 0;
+.VPHome .vp-doc h2 {
+ text-align: center;
+ font-size: 22px;
+ margin-top: 32px;
}
-.portal-row:hover {
- background: rgba(148, 163, 184, 0.08);
-}
-
-.portal-row strong {
- font-size: 15px;
- font-weight: 600;
-}
-
-.portal-row span {
- color: var(--vp-c-text-2);
- line-height: 1.6;
-}
+@media (max-width: 768px) {
+ .VPHome .VPHero .container {
+ padding-top: 32px;
+ padding-bottom: 28px;
+ }
-.portal-image {
- margin: 18px 0 10px;
- overflow: hidden;
- border: 1px solid rgba(148, 163, 184, 0.18);
- border-radius: 16px;
- background: #fff;
-}
+ .VPHome .VPHero .name {
+ font-size: 28px !important;
+ }
-.dark .portal-image {
- background: #0f172a;
-}
+ .VPHome .VPHero .text {
+ font-size: 16px;
+ }
-.portal-image img {
- display: block;
- width: 100%;
-}
+ .VPHome .VPHero .tagline {
+ font-size: 14px;
+ }
-@media (max-width: 768px) {
- .portal-index {
- grid-template-columns: 1fr;
+ .vp-doc {
+ font-size: 15px;
+ line-height: 1.75;
}
+}
- .portal-row {
- grid-template-columns: 1fr;
- gap: 6px;
+@media (min-width: 1280px) {
+ .VPDoc .aside {
+ display: block;
}
}
diff --git a/.vitepress/theme/env.d.ts b/.vitepress/theme/env.d.ts
new file mode 100644
index 0000000..64c3fd9
--- /dev/null
+++ b/.vitepress/theme/env.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue';
+ const component: DefineComponent<{}, {}, any>;
+ export default component;
+}
diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts
index bc392a2..d1db5a7 100644
--- a/.vitepress/theme/index.ts
+++ b/.vitepress/theme/index.ts
@@ -1,7 +1,30 @@
import DefaultTheme from 'vitepress/theme';
+import type { Theme } from 'vitepress';
+import { h } from 'vue';
+import ElementPlus from 'element-plus';
+import 'element-plus/dist/index.css';
+import 'element-plus/theme-chalk/dark/css-vars.css';
import './custom.css';
-export default {
- extends: DefaultTheme
+import CourseCard from './components/CourseCard.vue';
+import StudyRoadmap from './components/StudyRoadmap.vue';
+import TagFilter from './components/TagFilter.vue';
+import Breadcrumb from './components/Breadcrumb.vue';
+
+const theme: Theme = {
+ extends: DefaultTheme,
+ enhanceApp({ app }) {
+ app.use(ElementPlus);
+ app.component('CourseCard', CourseCard);
+ app.component('StudyRoadmap', StudyRoadmap);
+ app.component('TagFilter', TagFilter);
+ app.component('Breadcrumb', Breadcrumb);
+ },
+ Layout() {
+ return h(DefaultTheme.Layout, null, {
+ 'doc-before': () => h(Breadcrumb)
+ });
+ }
};
+export default theme;
diff --git a/index.md b/index.md
index e4dc90c..e21b4d6 100644
--- a/index.md
+++ b/index.md
@@ -5,6 +5,7 @@ titleTemplate: false
hero:
name: 嵌入式软件开发图谱
text: 系统化学习路径与技术参考
+ tagline: 覆盖 C 语言、驱动开发、RTOS、Embedded Linux、IoT 与 AI on MCU 的全景知识库
actions:
- theme: brand
text: 开始学习
@@ -14,48 +15,42 @@ hero:
link: /study-map
---
-## 核心课程
-
-
-
-## 专题方向
-
-
+
+
+## 全部专题
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 441ee7a..80eee94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,10 @@
"packages": {
"": {
"name": "embed-book-site",
+ "dependencies": {
+ "@element-plus/icons-vue": "^2.3.2",
+ "element-plus": "^2.13.7"
+ },
"devDependencies": {
"vitepress": "^1.6.4"
}
@@ -272,7 +276,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -282,7 +285,6 @@
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -292,7 +294,6 @@
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
@@ -308,7 +309,6 @@
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -318,6 +318,15 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@ctrl/tinycolor": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
+ "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/@docsearch/css": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz",
@@ -369,6 +378,15 @@
}
}
},
+ "node_modules/@element-plus/icons-vue": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
+ "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -760,6 +778,31 @@
"node": ">=12"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
"node_modules/@iconify-json/simple-icons": {
"version": "1.2.76",
"resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.76.tgz",
@@ -781,9 +824,19 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/@popperjs/core": {
+ "name": "@sxzz/popperjs-es",
+ "version": "2.11.8",
+ "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
+ "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@@ -1245,6 +1298,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.24",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz",
+ "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
@@ -1312,7 +1381,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz",
"integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.2",
@@ -1326,7 +1394,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz",
"integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.32",
@@ -1337,7 +1404,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz",
"integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.2",
@@ -1355,7 +1421,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz",
"integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.32",
@@ -1402,7 +1467,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz",
"integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.32"
@@ -1412,7 +1476,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz",
"integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.32",
@@ -1423,7 +1486,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz",
"integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.32",
@@ -1436,7 +1498,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz",
"integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.32",
@@ -1450,7 +1511,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz",
"integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
- "dev": true,
"license": "MIT"
},
"node_modules/@vueuse/core": {
@@ -1586,6 +1646,12 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/async-validator": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+ "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+ "license": "MIT"
+ },
"node_modules/birpc": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
@@ -1660,7 +1726,12 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
"license": "MIT"
},
"node_modules/dequal": {
@@ -1687,6 +1758,74 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/element-plus": {
+ "version": "2.13.7",
+ "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.7.tgz",
+ "integrity": "sha512-XdHATFZOyzVFL1DaHQ90IOJQSg9UnSAV+bhDW+YB5UoZ0Hxs50mwqjqfwXkuwpSag+VXXizVcErBR6Movo5daw==",
+ "license": "MIT",
+ "dependencies": {
+ "@ctrl/tinycolor": "^4.2.0",
+ "@element-plus/icons-vue": "^2.3.2",
+ "@floating-ui/dom": "^1.0.1",
+ "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+ "@types/lodash": "^4.17.20",
+ "@types/lodash-es": "^4.17.12",
+ "@vueuse/core": "12.0.0",
+ "async-validator": "^4.2.5",
+ "dayjs": "^1.11.19",
+ "lodash": "^4.17.23",
+ "lodash-es": "^4.17.23",
+ "lodash-unified": "^1.0.3",
+ "memoize-one": "^6.0.0",
+ "normalize-wheel-es": "^1.2.0",
+ "vue-component-type-helpers": "^3.2.4"
+ },
+ "peerDependencies": {
+ "vue": "^3.3.0"
+ }
+ },
+ "node_modules/element-plus/node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
+ "license": "MIT"
+ },
+ "node_modules/element-plus/node_modules/@vueuse/core": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz",
+ "integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.20",
+ "@vueuse/metadata": "12.0.0",
+ "@vueuse/shared": "12.0.0",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/element-plus/node_modules/@vueuse/metadata": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.0.0.tgz",
+ "integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/element-plus/node_modules/@vueuse/shared": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.0.0.tgz",
+ "integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==",
+ "license": "MIT",
+ "dependencies": {
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/emoji-regex-xs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
@@ -1698,7 +1837,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
@@ -1750,7 +1888,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true,
"license": "MIT"
},
"node_modules/focus-trap": {
@@ -1848,11 +1985,35 @@
"url": "https://github.com/sponsors/mesqueeb"
}
},
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash-unified": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+ "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/lodash-es": "*",
+ "lodash": "*",
+ "lodash-es": "*"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -1887,6 +2048,12 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
"node_modules/micromark-util-character": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
@@ -1999,7 +2166,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2014,6 +2180,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/normalize-wheel-es": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+ "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/oniguruma-to-es": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz",
@@ -2037,14 +2209,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -2199,7 +2369,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -2482,7 +2651,6 @@
"version": "3.5.32",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz",
"integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
- "dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -2501,6 +2669,12 @@
}
}
},
+ "node_modules/vue-component-type-helpers": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.2.7.tgz",
+ "integrity": "sha512-+gPp5YGmhfsj1IN+xUo7y0fb4clfnOiiUA39y07yW1VzCRjzVgwLbtmdWlghh7mXrPsEaYc7rrIir/HT6C8vYQ==",
+ "license": "MIT"
+ },
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
diff --git a/package.json b/package.json
index 65f86e2..5d26c3d 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,9 @@
},
"devDependencies": {
"vitepress": "^1.6.4"
+ },
+ "dependencies": {
+ "@element-plus/icons-vue": "^2.3.2",
+ "element-plus": "^2.13.7"
}
}
-
diff --git a/scripts/docs-tools.mjs b/scripts/docs-tools.mjs
index 319124c..de11a5f 100644
--- a/scripts/docs-tools.mjs
+++ b/scripts/docs-tools.mjs
@@ -404,15 +404,28 @@ function listSectionPages(section) {
}
export function buildNav() {
- const studyItems = DOC_SECTIONS.slice(0, 10).map((section) => ({
- text: section.text,
- link: getEntryRoute(section)
- }));
+ const coreSections = DOC_SECTIONS.slice(0, 8);
+ const topicSections = DOC_SECTIONS.filter((s) =>
+ ['09-2025_AI_on_MCU', '嵌入式图形 Qt 开发'].includes(s.dir)
+ );
return [
{ text: '首页', link: '/' },
{ text: '学习地图', link: '/study-map' },
- { text: '专题文档', items: studyItems },
+ {
+ text: '核心课程',
+ items: coreSections.map((section) => ({
+ text: section.text,
+ link: getEntryRoute(section)
+ }))
+ },
+ {
+ text: '专题方向',
+ items: topicSections.map((section) => ({
+ text: section.text,
+ link: getEntryRoute(section)
+ }))
+ },
{
text: '面试与资源',
items: [
@@ -458,8 +471,9 @@ export function buildSidebar() {
const entryRoute = getEntryRoute(section);
const items = [{ text: '章节导读', link: entryRoute }];
- if (section.dir === '面试题与面经') {
- items.push(...listSectionPages(section));
+ const subPages = listSectionPages(section);
+ if (subPages.length > 0) {
+ items.push(...subPages);
}
if (section.dir === 'books') {
@@ -469,6 +483,7 @@ export function buildSidebar() {
sidebar[entryRoute] = [
{
text: section.text,
+ collapsed: subPages.length > 6,
items
}
];
diff --git a/study-map.md b/study-map.md
index 9558563..cb3681b 100644
--- a/study-map.md
+++ b/study-map.md
@@ -2,39 +2,45 @@
这份学习地图把仓库里的 Markdown 内容按“先基础、再系统、后工程与专题”的顺序整理出来,适合第一次系统阅读时参考。
-## 第一阶段:基础打底
-
-1. [C 语言基础与进阶](/01-C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B8%8E%E8%BF%9B%E9%98%B6/)
-2. [嵌入式系统基础知识](/02-%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/)
-3. [驱动开发与外设编程](/03-%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91%E4%B8%8E%E5%A4%96%E8%AE%BE%E7%BC%96%E7%A8%8B/)
-
-这一阶段的目标是把语言、硬件抽象、启动链路和常见外设串起来,形成最基本的嵌入式开发心智模型。
-
-## 第二阶段:系统能力
-
-1. [实时操作系统](/04-%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/)
-2. [Embedded Linux](/05-EmbeddedLinux/)
-3. [网络通信与物联网](/06-NetworkIot/)
-
-这一阶段开始从“写代码”提升到“组织系统”。重点关注任务调度、驱动模型、设备树、协议栈和云边通信流程。
-
-## 第三阶段:工程与优化
-
-1. [调试与性能优化](/07-Debug_Optimization/)
-2. [项目实战与工具链](/08-%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98%E4%B8%8E%E5%B7%A5%E5%85%B7%E9%93%BE/)
-
-这部分更偏工程交付,适合在做项目时随查随用,包括调试链路、构建工具、CI、静态分析和团队协作习惯。
-
-## 扩展专题
-
-- [2025 AI on MCU](/09-2025_AI_on_MCU/)
-- [嵌入式图形 Qt 开发](/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%9B%BE%E5%BD%A2%20Qt%20%E5%BC%80%E5%8F%91/)
-
-这两个专题更偏趋势与方向扩展。前者面向边缘 AI,后者面向图形界面和工业终端。
-
-## 面试与复习材料
-
-- [面试题与面经](/%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E9%9D%A2%E7%BB%8F/)
-- [资源中心](/resources.md)
-
-如果你已经有项目经验,想快速梳理高频考点,可以直接从这两个入口开始,再反向回到主线章节查漏补缺。
+
+
+
From af396b07ee693774cddf6f050324f474f8621270 Mon Sep 17 00:00:00 2001
From: liueggy <3157487230@qq.com>
Date: Wed, 22 Apr 2026 10:38:07 +0800
Subject: [PATCH 10/13] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=BB=E9=A1=B5?=
=?UTF-8?q?=E5=B1=95=E7=A4=BA=EF=BC=8C=E7=BC=A9=E5=B0=8F=E5=AD=97=E4=BD=93?=
=?UTF-8?q?=EF=BC=8C=E5=B1=85=E4=B8=AD=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.vitepress/theme/custom.css | 50 +++++++++++++++++++++++++++++++------
index.md | 30 +++++++++++-----------
2 files changed, 57 insertions(+), 23 deletions(-)
diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css
index 0bf3966..93e5027 100644
--- a/.vitepress/theme/custom.css
+++ b/.vitepress/theme/custom.css
@@ -3,8 +3,8 @@
--vp-c-brand-2: #173a57;
--vp-c-brand-3: #102a40;
--vp-c-brand-soft: rgba(31, 75, 110, 0.1);
- --vp-home-hero-name-color: #0f172a;
- --vp-home-hero-name-background: none;
+ --vp-home-hero-name-color: transparent;
+ --vp-home-hero-name-background: linear-gradient(135deg, #1f4b6e 0%, #3d7bad 50%, #1f4b6e 100%);
--vp-home-hero-image-background-image: none;
--vp-home-hero-image-filter: none;
--el-color-primary: #1f4b6e;
@@ -18,6 +18,24 @@
.VPHero.has-image .name,
.VPHero .name {
letter-spacing: -0.02em;
+ font-size: 36px !important;
+ line-height: 1.2 !important;
+}
+
+.VPHome .VPHero .main .name {
+ max-width: 100%;
+}
+
+.VPHome .VPHero .main .text {
+ font-size: 18px !important;
+ font-weight: 500;
+ max-width: 560px;
+}
+
+.VPHome .VPHero .main .tagline {
+ font-size: 15px !important;
+ max-width: 520px;
+ line-height: 1.7;
}
.VPHome .VPHero {
@@ -40,13 +58,29 @@
.VPHome .VPHero .container {
padding-top: 52px;
padding-bottom: 40px;
+ text-align: center;
+ max-width: 640px;
+ margin: 0 auto;
+}
+
+.VPHome .VPHero .main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.VPHome .VPHero .actions {
+ justify-content: center;
}
-.VPHome .VPHero .text,
.VPHome .VPHero .tagline {
color: var(--vp-c-text-2);
}
+.VPHome .VPHero .text {
+ color: var(--vp-c-text-1);
+}
+
.VPHome .VPHero .action.alt {
color: var(--vp-c-text-1);
border-color: rgba(148, 163, 184, 0.3);
@@ -71,15 +105,15 @@
}
.VPHome .VPHero .name {
- font-size: 28px !important;
+ font-size: 26px !important;
}
- .VPHome .VPHero .text {
- font-size: 16px;
+ .VPHome .VPHero .main .text {
+ font-size: 15px !important;
}
- .VPHome .VPHero .tagline {
- font-size: 14px;
+ .VPHome .VPHero .main .tagline {
+ font-size: 13px !important;
}
.vp-doc {
diff --git a/index.md b/index.md
index e21b4d6..0f0c176 100644
--- a/index.md
+++ b/index.md
@@ -3,9 +3,9 @@ layout: home
title: 嵌入式软件开发图谱
titleTemplate: false
hero:
- name: 嵌入式软件开发图谱
- text: 系统化学习路径与技术参考
- tagline: 覆盖 C 语言、驱动开发、RTOS、Embedded Linux、IoT 与 AI on MCU 的全景知识库
+ name: Embed Book
+ text: 嵌入式开发全景知识库
+ tagline: C · 驱动 · RTOS · Linux · IoT · AI on MCU
actions:
- theme: brand
text: 开始学习
@@ -19,21 +19,21 @@ hero:
import { ref, computed } from 'vue';
const coreSections = [
- { dir: '01-C语言基础与进阶', text: 'C 语言基础与进阶', summary: '变量、指针、内存管理、排序算法与编译调试基础。', accent: '从语法、内存与调试入手,打牢嵌入式软件基本功。', link: '/01-C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B8%8E%E8%BF%9B%E9%98%B6/', tag: '基础' },
- { dir: '02-嵌入式系统基础知识', text: '嵌入式系统基础知识', summary: '系统构成、启动流程、链接脚本、芯片手册与工具链。', accent: '理解 MCU、存储器、启动链路和开发调试全景。', link: '/02-%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/', tag: '基础' },
- { dir: '03-驱动开发与外设编程', text: '驱动开发与外设编程', summary: 'GPIO、UART、SPI、I2C、ADC、DMA、CAN 与 STM32 工具链。', accent: '从寄存器级开发到 HAL/LL 抽象,覆盖常见外设驱动。', link: '/03-%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91%E4%B8%8E%E5%A4%96%E8%AE%BE%E7%BC%96%E7%A8%8B/', tag: '驱动' },
- { dir: '04-实时操作系统', text: '实时操作系统', summary: '任务管理、时间管理、线程通信、资源管理与 FreeRTOS。', accent: '把 RTOS 的概念、调度与工程化配置串起来。', link: '/04-%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/', tag: 'RTOS' },
- { dir: '05-EmbeddedLinux', text: 'Embedded Linux', summary: '启动流程、设备树、驱动模型、根文件系统与安全基础。', accent: '从命令行到系统启动链,覆盖嵌入式 Linux 开发核心。', link: '/05-EmbeddedLinux/', tag: 'Linux' },
- { dir: '06-NetworkIot', text: '网络通信与物联网', summary: '串口、Socket、无线协议、MQTT、TCP/IP、云接入与 OTA。', accent: '面向 IoT 产品开发的通信栈与云边协同知识。', link: '/06-NetworkIot/', tag: 'IoT' },
- { dir: '07-Debug_Optimization', text: '调试与性能优化', summary: 'JTAG/SWD、GDB、OpenOCD、功耗优化与性能分析。', accent: '聚焦问题定位、性能瓶颈与低功耗调优手段。', link: '/07-Debug_Optimization/', tag: '调试' },
- { dir: '08-项目实战与工具链', text: '项目实战与工具链', summary: '工程管理、构建系统、CI 流水线、常用开发工具。', accent: '把知识点落地到工程组织、构建和团队协作层面。', link: '/08-%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98%E4%B8%8E%E5%B7%A5%E5%85%B7%E9%93%BE/', tag: '工程' }
+ { dir: '01-C语言基础与进阶', text: 'C 语言基础与进阶', summary: '指针、内存管理、编译调试', accent: '打牢嵌入式软件基本功', link: '/01-C%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E4%B8%8E%E8%BF%9B%E9%98%B6/', tag: '基础' },
+ { dir: '02-嵌入式系统基础知识', text: '嵌入式系统基础', summary: '启动流程、链接脚本、芯片手册', accent: 'MCU 与工具链全景', link: '/02-%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/', tag: '基础' },
+ { dir: '03-驱动开发与外设编程', text: '驱动与外设', summary: 'GPIO / UART / SPI / I2C / DMA', accent: '寄存器到 HAL 抽象', link: '/03-%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91%E4%B8%8E%E5%A4%96%E8%AE%BE%E7%BC%96%E7%A8%8B/', tag: '驱动' },
+ { dir: '04-实时操作系统', text: '实时操作系统', summary: '任务调度、线程通信、FreeRTOS', accent: 'RTOS 概念与工程配置', link: '/04-%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/', tag: 'RTOS' },
+ { dir: '05-EmbeddedLinux', text: 'Embedded Linux', summary: '设备树、驱动模型、根文件系统', accent: '嵌入式 Linux 开发核心', link: '/05-EmbeddedLinux/', tag: 'Linux' },
+ { dir: '06-NetworkIot', text: '网络与物联网', summary: 'MQTT / TCP/IP / OTA', accent: '通信栈与云边协同', link: '/06-NetworkIot/', tag: 'IoT' },
+ { dir: '07-Debug_Optimization', text: '调试与优化', summary: 'GDB、OpenOCD、功耗分析', accent: '定位瓶颈与低功耗调优', link: '/07-Debug_Optimization/', tag: '调试' },
+ { dir: '08-项目实战与工具链', text: '工具链与实战', summary: '构建系统、CI、工程管理', accent: '知识落地到团队协作', link: '/08-%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98%E4%B8%8E%E5%B7%A5%E5%85%B7%E9%93%BE/', tag: '工程' }
];
const topicSections = [
- { dir: '09-2025_AI_on_MCU', text: '2025 AI on MCU', summary: 'TinyML、TensorFlow Lite Micro、STM32 AI 与模型量化部署。', accent: '跟进 2025 年 MCU 端 AI 的新趋势与落地方案。', link: '/09-2025_AI_on_MCU/', tag: 'AI' },
- { dir: '嵌入式图形 Qt 开发', text: '嵌入式图形 Qt 开发', summary: 'Qt for Embedded、交叉编译、图形界面、线程与外设集成。', accent: '为 HMI、工业终端和图形化设备补齐 GUI 技术栈。', link: '/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%9B%BE%E5%BD%A2%20Qt%20%E5%BC%80%E5%8F%91/', tag: 'GUI' },
- { dir: '面试题与面经', text: '面试题与面经', summary: '操作系统、Linux、网络原理与嵌入式岗位面试资料。', accent: '把知识库转换成面试场景下的高频问答与复习材料。', link: '/%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E9%9D%A2%E7%BB%8F/', tag: '面试' },
- { dir: 'books', text: '资源中心', summary: '电子书、PDF 资料与外部资源入口。', accent: '集中收口阅读材料、网盘资源与补充资料。', link: '/resources', tag: '资源' }
+ { dir: '09-2025_AI_on_MCU', text: 'AI on MCU', summary: 'TinyML / TFLite Micro / 量化部署', accent: 'MCU 端 AI 落地', link: '/09-2025_AI_on_MCU/', tag: 'AI' },
+ { dir: '嵌入式图形 Qt 开发', text: 'Qt 图形开发', summary: '交叉编译、GUI、外设集成', accent: 'HMI 与工业终端', link: '/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%9B%BE%E5%BD%A2%20Qt%20%E5%BC%80%E5%8F%91/', tag: 'GUI' },
+ { dir: '面试题与面经', text: '面试题与面经', summary: 'OS / Linux / 网络高频题', accent: '嵌入式岗位面试速查', link: '/%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E9%9D%A2%E7%BB%8F/', tag: '面试' },
+ { dir: 'books', text: '资源中心', summary: '电子书、PDF、网盘', accent: '阅读材料集中入口', link: '/resources', tag: '资源' }
];
const allSections = [...coreSections, ...topicSections];
From e06b73208431afa79d53a9fde5ebf89198e46e39 Mon Sep 17 00:00:00 2001
From: liueggy <3157487230@qq.com>
Date: Wed, 22 Apr 2026 10:54:42 +0800
Subject: [PATCH 11/13] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BB=E9=A1=B5?=
=?UTF-8?q?=E8=83=8C=E6=99=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.vitepress/theme/custom.css | 10 ++++++++--
public/hero-bg.png | Bin 0 -> 1153176 bytes
2 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 public/hero-bg.png
diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css
index 93e5027..6595df5 100644
--- a/.vitepress/theme/custom.css
+++ b/.vitepress/theme/custom.css
@@ -42,12 +42,18 @@
margin-top: 20px;
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 18px;
- background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.82) 0%, rgba(248, 250, 252, 0.88) 100%),
+ url('/hero-bg.png') center / cover no-repeat;
box-shadow: none;
+ position: relative;
+ overflow: hidden;
}
.dark .VPHome .VPHero {
- background: linear-gradient(180deg, #111827 0%, #0f172a 100%);
+ background:
+ linear-gradient(180deg, rgba(17, 24, 39, 0.85) 0%, rgba(15, 23, 42, 0.9) 100%),
+ url('/hero-bg.png') center / cover no-repeat;
border-color: rgba(148, 163, 184, 0.18);
}
diff --git a/public/hero-bg.png b/public/hero-bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..962ed4fd584c2ebbb1cb2d6cf26d5cb566eb148a
GIT binary patch
literal 1153176
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