소스 검색

raw: 补充STM32笔记完整代码——标准库+对应HAL库

OpenCode 1 주 전
부모
커밋
7e535272d0
14개의 변경된 파일2752개의 추가작업 그리고 51개의 파일을 삭제
  1. 102 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/02-GPIO详解.md
  2. 394 10
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/03-OLED显示与调试.md
  3. 240 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/04-UART串口通信详解.md
  4. 309 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/05-I2C通信详解.md
  5. 166 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/06-SPI通信详解.md
  6. 144 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/07-中断系统(NVIC+EXTI).md
  7. 474 6
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/08-时钟系统(RCC).md
  8. 266 21
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/09-定时器详解.md
  9. 249 4
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/10-ADC模数转换.md
  10. 92 1
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/11-DMA数据传输.md
  11. 53 1
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/12-PWR电源管理.md
  12. 121 2
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/13-BKP与RTC实时时钟.md
  13. 53 4
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/14-WDG看门狗.md
  14. 89 2
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/15-FLASH内部存储器.md

+ 102 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/02-GPIO详解.md

@@ -392,6 +392,108 @@ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1 | GPIO_PIN_2, GPIO_PIN_RESET);
 | 读取输出 | `GPIO_ReadOutputDataBit(GPIOx, Pin)` | (无对应函数,直接读 ODR)                        |
 | 翻转     | 手动读输出后取反                       | `HAL_GPIO_TogglePin(GPIOx, Pin)`                |
 
+### 4.6 GPIO 模式初始化与速度配置(HAL 库)
+
+HAL 库的 `GPIO_InitTypeDef` 包含 4 个成员:`Pin`、`Mode`、`Pull`、`Speed`,通过 `HAL_GPIO_Init()` 一次性配置。
+
+```c
+// HAL 库 GPIO 初始化结构体
+GPIO_InitTypeDef GPIO_InitStruct = {0};
+
+// ===== 推挽输出(对应标准库 GPIO_Mode_Out_PP)=====
+GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2;
+GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // 推挽输出
+GPIO_InitStruct.Pull = GPIO_NOPULL;             // 无需上拉/下拉
+GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;   // 高速(约 50MHz)
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+// ===== 开漏输出(对应标准库 GPIO_Mode_Out_OD)=====
+GPIO_InitStruct.Pin = GPIO_PIN_6;               // I2C SCL
+GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;     // 开漏输出
+GPIO_InitStruct.Pull = GPIO_NOPULL;
+GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; // 中速(约 10MHz)
+HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+
+// ===== 浮空输入(对应标准库 GPIO_Mode_IN_FLOATING)=====
+GPIO_InitStruct.Pin = GPIO_PIN_6;               // SPI MISO
+GPIO_InitStruct.Mode = GPIO_MODE_INPUT;         // 输入模式
+GPIO_InitStruct.Pull = GPIO_NOPULL;             // 无上下拉 → 浮空
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+// ===== 上拉输入(对应标准库 GPIO_Mode_IPU)=====
+GPIO_InitStruct.Pin = GPIO_PIN_1;
+GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+GPIO_InitStruct.Pull = GPIO_PULLUP;             // 内部上拉
+HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+
+// ===== 下拉输入(对应标准库 GPIO_Mode_IPD)=====
+GPIO_InitStruct.Pin = GPIO_PIN_11;
+GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+GPIO_InitStruct.Pull = GPIO_PULLDOWN;           // 内部下拉
+HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+
+// ===== 复用推挽输出(对应标准库 GPIO_Mode_AF_PP)=====
+GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;  // SPI SCK + MOSI
+GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;         // 复用推挽
+GPIO_InitStruct.Pull = GPIO_NOPULL;
+GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+// ===== 模拟输入(对应标准库 GPIO_Mode_AIN)=====
+GPIO_InitStruct.Pin = GPIO_PIN_0;               // ADC 通道
+GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;        // 模拟模式
+GPIO_InitStruct.Pull = GPIO_NOPULL;
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+```
+
+**速度选项(HAL 库)**:
+
+| HAL 宏                        | 对应标准库       | 说明   |
+|------------------------------|----------------|--------|
+| `GPIO_SPEED_FREQ_LOW`        | GPIO_Speed_2MHz  | 低速   |
+| `GPIO_SPEED_FREQ_MEDIUM`     | GPIO_Speed_10MHz | 中速   |
+| `GPIO_SPEED_FREQ_HIGH`       | GPIO_Speed_50MHz | 高速   |
+
+**AFIO / EXTI 引脚映射(HAL 库)**:
+
+HAL 库中 AFIO 和 EXTI 的配置通过 `HAL_GPIO_Init()` 的 `Mode` 参数完成,无需单独调用 AFIO 函数:
+
+```c
+// GPIO 外部中断(上升沿触发,使用内部上拉)
+GPIO_InitStruct.Pin = GPIO_PIN_0;
+GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;    // 上升沿外部中断
+GPIO_InitStruct.Pull = GPIO_PULLUP;
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+// 使能 AFIO 时钟(HAL 库需手动开启以支持 EXTI 和 remap)
+__HAL_RCC_AFIO_CLK_ENABLE();
+
+// 复用功能重映射示例:USART1 TX/RX 从 PA9/PA10 重映射到 PB6/PB7
+__HAL_RCC_AFIO_CLK_ENABLE();
+GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
+GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+```
+
+### HAL 库 vs 标准库模式对照表
+
+| HAL 库 Mode                 | 标准库 Mode              | 说明             |
+|-----------------------------|--------------------------|------------------|
+| `GPIO_MODE_INPUT`           | `GPIO_Mode_IN_FLOATING`  | 浮空输入         |
+| `GPIO_MODE_INPUT` + `PULLUP` | `GPIO_Mode_IPU`        | 上拉输入         |
+| `GPIO_MODE_INPUT` + `PULLDOWN` | `GPIO_Mode_IPD`     | 下拉输入         |
+| `GPIO_MODE_OUTPUT_PP`       | `GPIO_Mode_Out_PP`       | 推挽输出         |
+| `GPIO_MODE_OUTPUT_OD`       | `GPIO_Mode_Out_OD`       | 开漏输出         |
+| `GPIO_MODE_AF_PP`           | `GPIO_Mode_AF_PP`        | 复用推挽输出     |
+| `GPIO_MODE_AF_OD`           | `GPIO_Mode_AF_OD`        | 复用开漏输出     |
+| `GPIO_MODE_ANALOG`          | `GPIO_Mode_AIN`          | 模拟输入         |
+| `GPIO_MODE_IT_RISING`       | EXTI + `GPIO_Mode_IPU`   | 上升沿外部中断   |
+| `GPIO_MODE_IT_FALLING`      | EXTI + `GPIO_Mode_IPU`   | 下降沿外部中断   |
+| `GPIO_MODE_IT_RISING_FALLING` | EXTI + `GPIO_Mode_IPU` | 双边沿外部中断 |
+
+**关键区别**:HAL 库将输入方向与上下拉分离为 `Mode` 和 `Pull` 两个字段;标准库则合并为单一 `GPIO_Mode` 枚举。速度配置在 HAL 库中为独立字段 `Speed`,标准库在输出模式下也通过 `GPIO_Speed` 设置,但输入模式下速度可选任意值。
+
 ---
 
 ## 5. 蜂鸣器模块

+ 394 - 10
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/03-OLED显示与调试.md

@@ -32,9 +32,12 @@ I2C 时序关键函数:
 
 | 函数 | 说明 |
 |------|------|
+| `OLED_I2C_Init()` | 初始化 GPIO(开漏输出),SCL/SDA 初始拉高 |
 | `OLED_I2C_Start()` | SDA 在高电平期间拉低,SCL 保持高 |
 | `OLED_I2C_Stop()` | SDA 在低电平期间拉高,SCL 保持高 |
 | `OLED_I2C_SendByte()` | 高位先行,逐位输出,每字节后额外一个时钟不处理应答 |
+| `OLED_WriteCommand()` | 封装 Start + 地址 + 0x00 + Command + Stop |
+| `OLED_WriteData()` | 封装 Start + 地址 + 0x40 + Data + Stop |
 
 **写命令/写数据流程**:
 
@@ -43,6 +46,106 @@ I2C 时序关键函数:
 
 SSD1306 的 I2C 从机地址固定为 `0x78`(7 位地址 0x3C 左移一位)。
 
+#### 标准库(GPIO 模拟 I2C)
+
+```c
+/* 引脚初始化:开漏输出,50MHz */
+void OLED_I2C_Init(void)
+{
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
+
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
+    GPIO_Init(GPIOB, &GPIO_InitStructure);
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
+    GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    OLED_W_SCL(1);
+    OLED_W_SDA(1);
+}
+
+void OLED_I2C_Start(void)
+{
+    OLED_W_SDA(1);
+    OLED_W_SCL(1);
+    OLED_W_SDA(0);
+    OLED_W_SCL(0);
+}
+
+void OLED_I2C_Stop(void)
+{
+    OLED_W_SDA(0);
+    OLED_W_SCL(1);
+    OLED_W_SDA(1);
+}
+
+void OLED_I2C_SendByte(uint8_t Byte)
+{
+    uint8_t i;
+    for (i = 0; i < 8; i++)
+    {
+        OLED_W_SDA(!!(Byte & (0x80 >> i)));
+        OLED_W_SCL(1);
+        OLED_W_SCL(0);
+    }
+    OLED_W_SCL(1);  // 额外时钟,不处理应答
+    OLED_W_SCL(0);
+}
+
+void OLED_WriteCommand(uint8_t Command)
+{
+    OLED_I2C_Start();
+    OLED_I2C_SendByte(0x78);   // 从机地址
+    OLED_I2C_SendByte(0x00);   // 写命令
+    OLED_I2C_SendByte(Command);
+    OLED_I2C_Stop();
+}
+
+void OLED_WriteData(uint8_t Data)
+{
+    OLED_I2C_Start();
+    OLED_I2C_SendByte(0x78);   // 从机地址
+    OLED_I2C_SendByte(0x40);   // 写数据
+    OLED_I2C_SendByte(Data);
+    OLED_I2C_Stop();
+}
+```
+
+#### HAL库(硬件 I2C 外设)
+
+```c
+/* 前提:使用 CubeMX 配置 I2C1,SCL=PB8, SDA=PB9 */
+/* 在 i2c.c 中自动生成 MX_I2C1_Init(),此处仅封装 OLED 接口 */
+
+#include "i2c.h"
+
+#define OLED_I2C_ADDR     0x78    // 7位地址0x3C左移1位
+#define OLED_I2C_TIMEOUT  100
+
+void OLED_WriteCommand(uint8_t Command)
+{
+    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00,
+                      I2C_MEMADD_SIZE_8BIT, &Command, 1, OLED_I2C_TIMEOUT);
+}
+
+void OLED_WriteData(uint8_t Data)
+{
+    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
+                      I2C_MEMADD_SIZE_8BIT, &Data, 1, OLED_I2C_TIMEOUT);
+}
+
+/* 批量写数据(效率更高) */
+void OLED_WriteData_Bulk(uint8_t *pData, uint16_t Length)
+{
+    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
+                      I2C_MEMADD_SIZE_8BIT, pData, Length, OLED_I2C_TIMEOUT);
+}
+```
+
+> HAL 库下无需软件模拟 I2C,直接复用 STM32 的 I2C 外设。需在 CubeMX 中配置 I2C1(速度 400kHz,7 位地址),并在工程中包含 `i2c.h`。
+
 ## OLED 初始化序列
 
 `OLED_Init()` 在上电延时后按顺序发送以下命令序列:
@@ -64,18 +167,108 @@ SSD1306 的 I2C 从机地址固定为 `0x78`(7 位地址 0x3C 左移一位)
 15. `0xAF` — 开启显示
 16. `OLED_Clear()` — 清屏
 
+#### 标准库
+
+```c
+void OLED_Init(void)
+{
+    uint32_t i, j;
+    for (i = 0; i < 1000; i++)      // 上电延时
+        for (j = 0; j < 1000; j++);
+
+    OLED_I2C_Init();                // GPIO 初始化
+
+    OLED_WriteCommand(0xAE);        // 关闭显示
+    OLED_WriteCommand(0xD5);        // 设置时钟分频/振荡器频率
+    OLED_WriteCommand(0x80);
+    OLED_WriteCommand(0xA8);        // 设置多路复用率
+    OLED_WriteCommand(0x3F);
+    OLED_WriteCommand(0xD3);        // 设置显示偏移
+    OLED_WriteCommand(0x00);
+    OLED_WriteCommand(0x40);        // 设置显示开始行
+    OLED_WriteCommand(0xA1);        // 左右方向(正常)
+    OLED_WriteCommand(0xC8);        // 上下方向(正常)
+    OLED_WriteCommand(0xDA);        // COM 引脚硬件配置
+    OLED_WriteCommand(0x12);
+    OLED_WriteCommand(0x81);        // 设置对比度
+    OLED_WriteCommand(0xCF);
+    OLED_WriteCommand(0xD9);        // 预充电周期
+    OLED_WriteCommand(0xF1);
+    OLED_WriteCommand(0xDB);        // VCOMH 取消选择级别
+    OLED_WriteCommand(0x30);
+    OLED_WriteCommand(0xA4);        // 整个显示打开
+    OLED_WriteCommand(0xA6);        // 正常显示(非反色)
+    OLED_WriteCommand(0x8D);        // 使能充电泵
+    OLED_WriteCommand(0x14);
+    OLED_WriteCommand(0xAF);        // 开启显示
+
+    OLED_Clear();
+}
+```
+
+#### HAL库
+
+```c
+void OLED_Init(void)
+{
+    HAL_Delay(100);                 // 上电延时 100ms
+
+    OLED_WriteCommand(0xAE);
+    OLED_WriteCommand(0xD5);
+    OLED_WriteCommand(0x80);
+    OLED_WriteCommand(0xA8);
+    OLED_WriteCommand(0x3F);
+    OLED_WriteCommand(0xD3);
+    OLED_WriteCommand(0x00);
+    OLED_WriteCommand(0x40);
+    OLED_WriteCommand(0xA1);
+    OLED_WriteCommand(0xC8);
+    OLED_WriteCommand(0xDA);
+    OLED_WriteCommand(0x12);
+    OLED_WriteCommand(0x81);
+    OLED_WriteCommand(0xCF);
+    OLED_WriteCommand(0xD9);
+    OLED_WriteCommand(0xF1);
+    OLED_WriteCommand(0xDB);
+    OLED_WriteCommand(0x30);
+    OLED_WriteCommand(0xA4);
+    OLED_WriteCommand(0xA6);
+    OLED_WriteCommand(0x8D);
+    OLED_WriteCommand(0x14);
+    OLED_WriteCommand(0xAF);
+
+    OLED_Clear();
+}
+```
+
 ## 核心函数详解
 
 ### 光标设置 `OLED_SetCursor(Y, X)`
 
 SSD1306 内部将 128×64 像素划分为 **8 页(Page)**,每页 8 个像素高。Y 范围 0~7 对应页号,X 范围 0~127。
 
+#### 标准库
+
+```c
+void OLED_SetCursor(uint8_t Y, uint8_t X)
+{
+    OLED_WriteCommand(0xB0 | Y);                     // 设置页地址(Y)
+    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));     // 列地址高4位
+    OLED_WriteCommand(0x00 | (X & 0x0F));            // 列地址低4位
+}
+```
+
+#### HAL库
+
 ```c
 void OLED_SetCursor(uint8_t Y, uint8_t X)
 {
-    OLED_WriteCommand(0xB0 | Y);                     // 设置页地址(Y 位置)
-    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));     // 设置列地址高 4 位
-    OLED_WriteCommand(0x00 | (X & 0x0F));            // 设置列地址低 4 位
+    uint8_t cmds[3];
+    cmds[0] = 0xB0 | Y;
+    cmds[1] = 0x10 | ((X & 0xF0) >> 4);
+    cmds[2] = 0x00 | (X & 0x0F);
+    HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00,
+                      I2C_MEMADD_SIZE_8BIT, cmds, 3, OLED_I2C_TIMEOUT);
 }
 ```
 
@@ -83,31 +276,72 @@ void OLED_SetCursor(uint8_t Y, uint8_t X)
 
 遍历 8 页 × 128 列,每字节写入 `0x00`。
 
+#### 标准库
+
 ```c
 void OLED_Clear(void)
 {
-    for (j = 0; j < 8; j++) {
+    uint8_t i, j;
+    for (j = 0; j < 8; j++)
+    {
         OLED_SetCursor(j, 0);
-        for (i = 0; i < 128; i++) {
+        for (i = 0; i < 128; i++)
+        {
             OLED_WriteData(0x00);
         }
     }
 }
 ```
 
+#### HAL库
+
+```c
+void OLED_Clear(void)
+{
+    uint8_t i, j;
+    uint8_t zeroBuf[128];
+    for (i = 0; i < 128; i++) zeroBuf[i] = 0x00;
+
+    for (j = 0; j < 8; j++)
+    {
+        OLED_SetCursor(j, 0);
+        HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40,
+                          I2C_MEMADD_SIZE_8BIT, zeroBuf, 128, OLED_I2C_TIMEOUT);
+    }
+}
+```
+
 ### 显示字符 `OLED_ShowChar(Line, Column, Char)`
 
 - **Line**: 1~4(每行对应 2 页 = 16 像素高)
 - **Column**: 1~16(每列对应 8 像素宽)
 - 一个字符占 8×16 像素,上半页 8 字节 + 下半页 8 字节,从字模表 `OLED_F8x16` 取数据
 
+#### 标准库
+
 ```c
 void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
 {
-    OLED_SetCursor((Line-1)*2, (Column-1)*8);            // 上半部分
+    uint8_t i;
+    OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);        // 上半部分
     for (i = 0; i < 8; i++)
         OLED_WriteData(OLED_F8x16[Char - ' '][i]);
-    OLED_SetCursor((Line-1)*2+1, (Column-1)*8);          // 下半部分
+    OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);    // 下半部分
+    for (i = 0; i < 8; i++)
+        OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);
+}
+```
+
+#### HAL库
+
+```c
+void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
+{
+    uint8_t i;
+    OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);
+    for (i = 0; i < 8; i++)
+        OLED_WriteData(OLED_F8x16[Char - ' '][i]);
+    OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);
     for (i = 0; i < 8; i++)
         OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);
 }
@@ -117,13 +351,75 @@ void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
 
 逐字符循环调用 `OLED_ShowChar`,遇 `\0` 终止。
 
+#### 标准库
+
+```c
+void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
+{
+    uint8_t i;
+    for (i = 0; String[i] != '\0'; i++)
+    {
+        OLED_ShowChar(Line, Column + i, String[i]);
+    }
+}
+```
+
+#### HAL库
+
+```c
+void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
+{
+    uint8_t i;
+    for (i = 0; String[i] != '\0'; i++)
+    {
+        OLED_ShowChar(Line, Column + i, String[i]);
+    }
+}
+```
+
 ### 显示数字 `OLED_ShowNum(Line, Column, Number, Length)`
 
 显示无符号十进制整数,逐位提取后调用 `OLED_ShowChar`:
 
+#### 标准库
+
 ```c
+/* 次方函数:X 的 Y 次方 */
+uint32_t OLED_Pow(uint32_t X, uint32_t Y)
+{
+    uint32_t Result = 1;
+    while (Y--)
+    {
+        Result *= X;
+    }
+    return Result;
+}
+
+void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
+{
+    uint8_t i;
+    for (i = 0; i < Length; i++)
+        OLED_ShowChar(Line, Column + i,
+            Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
+}
+```
+
+#### HAL库
+
+```c
+uint32_t OLED_Pow(uint32_t X, uint32_t Y)
+{
+    uint32_t Result = 1;
+    while (Y--)
+    {
+        Result *= X;
+    }
+    return Result;
+}
+
 void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
 {
+    uint8_t i;
     for (i = 0; i < Length; i++)
         OLED_ShowChar(Line, Column + i,
             Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
@@ -134,12 +430,100 @@ void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
 
 | 函数 | 说明 |
 |------|------|
-| `OLED_ShowSignedNum` | 有符号十进制,带 `+`/`-` 前缀 |
-| `OLED_ShowHexNum` | 十六进制,0~F |
-| `OLED_ShowBinNum` | 二进制,仅每位 0/1 |
+| `OLED_ShowSignedNum(Line, Column, Number, Length)` | 有符号十进制,带 `+`/`-` 前缀 |
+| `OLED_ShowHexNum(Line, Column, Number, Length)` | 十六进制,0~F |
+| `OLED_ShowBinNum(Line, Column, Number, Length)` | 二进制,仅每位 0/1 |
 
 所有数字显示函数均通过 `OLED_ShowChar` 实现,核心是一个通用的 `OLED_Pow(X, Y)` 次方函数。
 
+#### 标准库
+
+```c
+void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
+{
+    uint8_t i;
+    uint32_t Number1;
+    if (Number >= 0)
+    {
+        OLED_ShowChar(Line, Column, '+');
+        Number1 = Number;
+    }
+    else
+    {
+        OLED_ShowChar(Line, Column, '-');
+        Number1 = -Number;
+    }
+    for (i = 0; i < Length; i++)
+        OLED_ShowChar(Line, Column + i + 1,
+            Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
+}
+
+void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
+{
+    uint8_t i, SingleNumber;
+    for (i = 0; i < Length; i++)
+    {
+        SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
+        if (SingleNumber < 10)
+            OLED_ShowChar(Line, Column + i, SingleNumber + '0');
+        else
+            OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
+    }
+}
+
+void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
+{
+    uint8_t i;
+    for (i = 0; i < Length; i++)
+        OLED_ShowChar(Line, Column + i,
+            Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
+}
+```
+
+#### HAL库(与标准库完全相同,此处仅列一次)
+
+```c
+void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
+{
+    uint8_t i;
+    uint32_t Number1;
+    if (Number >= 0)
+    {
+        OLED_ShowChar(Line, Column, '+');
+        Number1 = Number;
+    }
+    else
+    {
+        OLED_ShowChar(Line, Column, '-');
+        Number1 = -Number;
+    }
+    for (i = 0; i < Length; i++)
+        OLED_ShowChar(Line, Column + i + 1,
+            Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
+}
+
+void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
+{
+    uint8_t i, SingleNumber;
+    for (i = 0; i < Length; i++)
+    {
+        SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
+        if (SingleNumber < 10)
+            OLED_ShowChar(Line, Column + i, SingleNumber + '0');
+        else
+            OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
+    }
+}
+
+void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
+{
+    uint8_t i;
+    for (i = 0; i < Length; i++)
+        OLED_ShowChar(Line, Column + i,
+            Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
+}
+```
+
 ## 字模表 `OLED_F8x16`
 
 位于 `OLED_Font.h`,定义为 `const uint8_t OLED_F8x16[][16]`,共 95 个 ASCII 可见字符(从空格 `' '` 到 `'~'`),每个字符 16 字节(上半 8 字节 + 下半 8 字节)。字模数据为列行式:每字节的 8 位对应一列中 8 个像素的亮灭。

+ 240 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/04-UART串口通信详解.md

@@ -128,6 +128,61 @@ while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);  // 等待发送
 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
 ```
 
+### Serial_Init 串口初始化(发送模式)
+
+**标准库:**
+~~~c
+void Serial_Init(void)
+{
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+    USART_InitTypeDef USART_InitStructure;
+    USART_InitStructure.USART_BaudRate = 9600;
+    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
+    USART_InitStructure.USART_Mode = USART_Mode_Tx;
+    USART_InitStructure.USART_Parity = USART_Parity_No;
+    USART_InitStructure.USART_StopBits = USART_StopBits_1;
+    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
+    USART_Init(USART1, &USART_InitStructure);
+
+    USART_Cmd(USART1, ENABLE);
+}
+~~~
+
+**HAL库:**
+~~~c
+UART_HandleTypeDef huart1;
+
+void Serial_Init(void)
+{
+    __HAL_RCC_USART1_CLK_ENABLE();
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    GPIO_InitStruct.Pin = GPIO_PIN_9;
+    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    huart1.Instance = USART1;
+    huart1.Init.BaudRate = 9600;
+    huart1.Init.WordLength = UART_WORDLENGTH_8B;
+    huart1.Init.StopBits = UART_STOPBITS_1;
+    huart1.Init.Parity = UART_PARITY_NONE;
+    huart1.Init.Mode = UART_MODE_TX;
+    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
+    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
+    HAL_UART_Init(&huart1);
+}
+~~~
+
 ### 发送字节、数组、字符串
 
 ```c
@@ -150,6 +205,27 @@ void Serial_SendString(char *String) {
 }
 ```
 
+**HAL库 发送等效:**
+~~~c
+// 发送单字节(阻塞超时模式)
+void Serial_SendByte(uint8_t Byte)
+{
+    HAL_UART_Transmit(&huart1, &Byte, 1, HAL_MAX_DELAY);
+}
+
+// 发送数组
+void Serial_SendArray(uint8_t *Array, uint16_t Length)
+{
+    HAL_UART_Transmit(&huart1, Array, Length, HAL_MAX_DELAY);
+}
+
+// 发送字符串
+void Serial_SendString(char *String)
+{
+    HAL_UART_Transmit(&huart1, (uint8_t *)String, strlen(String), HAL_MAX_DELAY);
+}
+~~~
+
 ### 发送数字
 
 数字需要转换为 ASCII 码再发送。例如数字 123 要分别发送 '1'、'2'、'3' 三个字符。
@@ -274,6 +350,92 @@ NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 NVIC_Init(&NVIC_InitStructure);
 ```
 
+完整代码(包含 GPIO、USART 和中断配置):
+
+**标准库:**
+~~~c
+uint8_t Serial_RxData;
+uint8_t Serial_RxFlag;
+
+void Serial_Init(void)
+{
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+    USART_InitTypeDef USART_InitStructure;
+    USART_InitStructure.USART_BaudRate = 9600;
+    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
+    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
+    USART_InitStructure.USART_Parity = USART_Parity_No;
+    USART_InitStructure.USART_StopBits = USART_StopBits_1;
+    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
+    USART_Init(USART1, &USART_InitStructure);
+
+    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
+
+    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
+
+    NVIC_InitTypeDef NVIC_InitStructure;
+    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
+    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
+    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
+    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
+    NVIC_Init(&NVIC_InitStructure);
+
+    USART_Cmd(USART1, ENABLE);
+}
+~~~
+
+**HAL库:**
+~~~c
+UART_HandleTypeDef huart1;
+uint8_t Serial_RxData;
+uint8_t Serial_RxFlag;
+
+void Serial_Init(void)
+{
+    __HAL_RCC_USART1_CLK_ENABLE();
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    GPIO_InitStruct.Pin = GPIO_PIN_9;
+    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    GPIO_InitStruct.Pin = GPIO_PIN_10;
+    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+    GPIO_InitStruct.Pull = GPIO_PULLUP;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    huart1.Instance = USART1;
+    huart1.Init.BaudRate = 9600;
+    huart1.Init.WordLength = UART_WORDLENGTH_8B;
+    huart1.Init.StopBits = UART_STOPBITS_1;
+    huart1.Init.Parity = UART_PARITY_NONE;
+    huart1.Init.Mode = UART_MODE_TX_RX;
+    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
+    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
+    HAL_UART_Init(&huart1);
+
+    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
+
+    HAL_NVIC_SetPriority(USART1_IRQn, 1, 1);
+    HAL_NVIC_EnableIRQ(USART1_IRQn);
+}
+~~~
+
 ### 中断服务函数
 
 ```c
@@ -303,6 +465,24 @@ uint8_t Serial_GetRxData(void) {
 }
 ```
 
+**HAL库 中断接收等效:**
+~~~c
+uint8_t rx_buffer;                         // 接收缓冲区
+
+// 启动中断接收(在 main 中调用一次即可)
+HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);
+
+// 中断回调函数(HAL 自动调用)
+void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
+{
+    if (huart->Instance == USART1) {
+        Serial_RxData = rx_buffer;
+        Serial_RxFlag = 1;
+        HAL_UART_Receive_IT(&huart1, &rx_buffer, 1);  // 重新启动接收
+    }
+}
+~~~
+
 ### 回传实验
 
 ```c
@@ -351,6 +531,66 @@ void USART1_IRQHandler(void) {
 
 ---
 
+## 5.5 DMA 模式收发
+
+在大量数据收发场景下,使用 DMA 可以释放 CPU 资源。HAL 库提供了 DMA 版本的收发函数,配合 USART 的硬件 DMA 请求使用。
+
+### CubeMX 配置
+
+1. USART1 的 `DMA Settings` 标签页中添加:
+   - USART1_TX — DMA1 Channel4 (或相应通道),Direction `Memory to Peripheral`
+   - USART1_RX — DMA1 Channel5 (或相应通道),Direction `Peripheral to Memory`
+2. DMA 参数:Mode `Normal`(或 `Circular` 用于连续接收), Increment Address 勾选
+
+### HAL 库 DMA 发送
+
+```c
+// 发送数组(DMA 方式,非阻塞)
+HAL_UART_Transmit_DMA(&huart1, TxBuffer, Size);
+
+// 查询发送是否完成
+while (HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);
+```
+
+### HAL 库 DMA 接收
+
+```c
+// 启动 DMA 接收
+HAL_UART_Receive_DMA(&huart1, RxBuffer, BufferSize);
+
+// 停止 DMA 接收
+HAL_UART_DMAStop(&huart1);
+```
+
+### 轮询接收(HAL 库)
+
+```c
+// 阻塞式接收一个字节(超时等待)
+uint8_t Serial_ReceiveByte(void)
+{
+    uint8_t data;
+    HAL_UART_Receive(&huart1, &data, 1, HAL_MAX_DELAY);
+    return data;
+}
+```
+
+### 标准库与 HAL 库 API 对照
+
+| 功能 | 标准库 | HAL 库 |
+|------|--------|--------|
+| 发送字节 | `USART_SendData()` | `HAL_UART_Transmit()` |
+| 接收字节 | `USART_ReceiveData()` | `HAL_UART_Receive()` |
+| 中断发送 | `USART_ITConfig(USART_IT_TXE)` | `HAL_UART_Transmit_IT()` |
+| 中断接收 | `USART_ITConfig(USART_IT_RXNE)` | `HAL_UART_Receive_IT()` |
+| DMA 发送 | — | `HAL_UART_Transmit_DMA()` |
+| DMA 接收 | — | `HAL_UART_Receive_DMA()` |
+| 查询 TXE | `USART_GetFlagStatus(USART_FLAG_TXE)` | `__HAL_UART_GET_FLAG(UART_FLAG_TXE)` |
+| 查询 RXNE | `USART_GetFlagStatus(USART_FLAG_RXNE)` | `__HAL_UART_GET_FLAG(UART_FLAG_RXNE)` |
+| 使能 USART | `USART_Cmd(ENABLE)` | `HAL_UART_Init()` 自动使能 |
+| 中断使能 | `USART_ITConfig()` | `__HAL_UART_ENABLE_IT()` |
+
+---
+
 ## 6. 常见问题与注意事项
 
 ### 波特率不一致

+ 309 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/05-I2C通信详解.md

@@ -85,6 +85,8 @@ STM32F103 的 I2C 外设支持标准模式和快速模式。
 
 ## 2. STM32 I2C 外设配置
 
+### 标准库
+
 ![STM32 I2C 模块框图](assets/i2c_block.png)
 *上图:STM32 I2C 模块框图(来源:STM32入门教程 PPT 第141页)*
 
@@ -195,6 +197,53 @@ void MyI2C_Init(void) {
 
 ---
 
+### HAL库
+
+使用 STM32CubeMX 生成的 HAL 库代码,I2C 初始化通过 `I2C_HandleTypeDef` 结构体完成:
+
+~~~c
+/* HAL库 — I2C 句柄定义与初始化 */
+I2C_HandleTypeDef hi2c1;
+
+void MX_I2C1_Init(void)
+{
+    hi2c1.Instance = I2C1;
+    hi2c1.Init.ClockSpeed = 400000;          // 400kHz
+    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;  // 占空比 2:1
+    hi2c1.Init.OwnAddress1 = 0;              // 本机地址(主机模式不用)
+    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
+    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
+    hi2c1.Init.OwnAddress2 = 0;
+    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
+    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
+    HAL_I2C_Init(&hi2c1);
+}
+
+/* HAL 库的 MSP 初始化(引脚与时钟)由 HAL_I2C_MspInit 回调完成 */
+void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (hi2c->Instance == I2C1) {
+        __HAL_RCC_GPIOB_CLK_ENABLE();
+        __HAL_RCC_I2C1_CLK_ENABLE();
+
+        GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;           // PB6=SCL, PB7=SDA
+        GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
+        GPIO_InitStruct.Pull = GPIO_NOPULL;
+        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+    }
+}
+~~~
+
+关键区别:
+
+- **结构体**:标准库用 `I2C_InitTypeDef` + `I2C_Init()`;HAL 库用 `I2C_HandleTypeDef` + `HAL_I2C_Init()`
+- **MSP 回调**:HAL 库通过 `HAL_I2C_MspInit()` 统一管理引脚时钟和 GPIO 初始化
+- **地址处理**:HAL 库的 `HAL_I2C_Mem_Write/Read` 要求传入**7 位原始地址**(不含 R/W 位),如 MPU6050 传入 `0x68`;而标准库中传的是左对齐的 `0xD0`
+
+---
+
 ## 3. I2C 写数据
 
 ### I2C 模块内部结构
@@ -312,6 +361,55 @@ int main(void) {
 
 OLED 地址是 0x78,这里传的是已经左对齐的地址(最低位为 R/W 位,函数内做了 `& 0xFE` 处理)。
 
+### HAL库写操作
+
+HAL 库将 I2C 写操作封装为两个高级函数,无需手动操作 EV5/EV6/EV7 事件标志位:
+
+~~~c
+/* HAL库 — I2C 主机发送(直接发送数据,无寄存器地址) */
+// 原型:HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout)
+// DevAddress:7位从机地址左移1位(即含R/W位的8位地址)
+// pData:要发送的数据缓冲区
+// Size:字节数
+// Timeout:超时时间(ms)
+HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(
+    &hi2c1,
+    0xD0,           // MPU6050 地址(左对齐,含R/W位)
+    Command,        // 数据缓冲区
+    5,              // 字节数
+    100             // 超时100ms
+);
+if (status != HAL_OK) {
+    Error_Handler();  // 处理错误
+}
+
+/* HAL库 — I2C 写寄存器(Mem Write,带寄存器地址) */
+// 原型:HAL_I2C_Mem_Write(&hi2c1, DevAddress, MemAddress, MemAddSize, pData, Size, Timeout)
+// DevAddress:7位从机地址左移1位
+// MemAddress:寄存器地址
+// MemAddSize:寄存器地址长度(8位或16位)
+HAL_StatusTypeDef ret = HAL_I2C_Mem_Write(
+    &hi2c1,
+    0xD0,           // 从机地址
+    0x6B,           // 寄存器地址(PWR_MGMT_1)
+    I2C_MEMADD_SIZE_8BIT,  // 寄存器地址为8位
+    (uint8_t[]){0x01},     // 写入的数据
+    1,              // 1个字节
+    100
+);
+
+/* HAL库 — 同时写多个寄存器 */
+uint8_t configData[] = {0x6B, 0x01, 0x6C, 0x00, 0x19, 0x09};
+HAL_I2C_Master_Transmit(&hi2c1, 0xD0, configData, 6, 100);
+~~~
+
+HAL 库内部自动处理了:
+1. 生成起始条件(EV5:SB 标志位)
+2. 发送地址 + 等待 ADDR 标志(EV6)
+3. 逐个字节发送数据 + 等待 TXE/BTF
+4. 生成停止条件
+5. 超时重试与错误状态返回
+
 ---
 
 ## 4. I2C 读数据
@@ -369,6 +467,50 @@ uint8_t MyI2C_ReadByte(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t RegAddr) {
 }
 ```
 
+### HAL库读操作
+
+~~~c
+/* HAL库 — I2C 主机接收(直接读取从机数据,无寄存器地址) */
+// 原型:HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout)
+uint8_t rxBuffer[6];
+HAL_StatusTypeDef status = HAL_I2C_Master_Receive(
+    &hi2c1,
+    0xD0,           // 从机地址(左对齐)
+    rxBuffer,       // 接收缓冲区
+    6,              // 接收6个字节
+    100
+);
+
+/* HAL库 — I2C 读寄存器(Mem Read,先写寄存器地址,再读数据) */
+// 原型:HAL_I2C_Mem_Read(&hi2c1, DevAddress, MemAddress, MemAddSize, pData, Size, Timeout)
+// 内部自动完成:Write(RegAddr) → Repeated Start → Read
+uint8_t data;
+HAL_StatusTypeDef ret = HAL_I2C_Mem_Read(
+    &hi2c1,
+    0xD0,                       // 从机地址
+    0x3B,                       // 起始寄存器地址(ACCEL_XOUT_H)
+    I2C_MEMADD_SIZE_8BIT,       // 8位寄存器地址
+    &data,                      // 接收缓冲区
+    1,                          // 读取1个字节
+    100
+);
+
+/* HAL库 — 连续读取多个寄存器(MPU6050 获取全部6轴数据) */
+uint8_t mpuBuffer[14];  // ACCEL(6) + TEMP(2) + GYRO(6) = 14 bytes
+HAL_I2C_Mem_Read(&hi2c1, 0xD0, 0x3B, I2C_MEMADD_SIZE_8BIT,
+                 mpuBuffer, 14, 100);
+
+// 解析数据
+int16_t AccX = (mpuBuffer[0] << 8) | mpuBuffer[1];
+int16_t AccY = (mpuBuffer[2] << 8) | mpuBuffer[3];
+int16_t AccZ = (mpuBuffer[4] << 8) | mpuBuffer[5];
+int16_t GyroX = (mpuBuffer[8] << 8) | mpuBuffer[9];
+int16_t GyroY = (mpuBuffer[10] << 8) | mpuBuffer[11];
+int16_t GyroZ = (mpuBuffer[12] << 8) | mpuBuffer[13];
+~~~
+
+HAL 库的 `HAL_I2C_Mem_Read` 内部自动执行:Start → 地址+W → 寄存器地址 → Repeated Start → 地址+R → NACK → Stop,完全封装了重复起始位和 NACK 发送逻辑。
+
 ---
 
 ## 5. 软件 I2C(GPIO 模拟)
@@ -544,6 +686,173 @@ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
 }
 ```
 
+### 硬件 I2C(标准库 — EV5/EV6/EV7 事件驱动)
+
+如果使用 STM32 硬件 I2C 外设 + 标准库,MPU6050 驱动通过 I2C 事件标志位控制时序(EV5 = SB起始已发送,EV6 = ADDR寻址成功,EV7 = RXNE数据可读):
+
+~~~c
+/* 标准库 — 硬件 I2C MPU6050 驱动(基于 I2C2,PB10/PB11) */
+
+#define MPU6050_ADDRESS     0xD0
+
+/* 等待 I2C 事件,带超时 */
+void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
+{
+    uint32_t Timeout = 10000;
+    while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
+    {
+        Timeout--;
+        if (Timeout == 0) break;
+    }
+}
+
+/* 硬件 I2C 写寄存器:发送起始位 → EV5 → 地址+W → EV6 → 数据 → EV8 → EV8_2 → 停止 */
+void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
+{
+    I2C_GenerateSTART(I2C2, ENABLE);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);          // EV5
+
+    I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6
+
+    I2C_SendData(I2C2, RegAddress);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);    // EV8
+
+    I2C_SendData(I2C2, Data);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);     // EV8_2
+
+    I2C_GenerateSTOP(I2C2, ENABLE);
+}
+
+/* 硬件 I2C 读寄存器:起始 → EV5 → 地址+W → EV6 → 寄存器地址 → EV8_2 → 重复起始 → EV5 → 地址+R → EV6 → 数据 → EV7 → 停止 */
+uint8_t MPU6050_ReadReg(uint8_t RegAddress)
+{
+    uint8_t Data;
+
+    I2C_GenerateSTART(I2C2, ENABLE);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);          // EV5
+
+    I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6
+
+    I2C_SendData(I2C2, RegAddress);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);     // EV8_2
+
+    I2C_GenerateSTART(I2C2, ENABLE);                                // 重复起始位
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);          // EV5
+
+    I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // EV6
+
+    I2C_AcknowledgeConfig(I2C2, DISABLE);                            // 最后一字节前失能ACK
+    I2C_GenerateSTOP(I2C2, ENABLE);                                   // 提前请求停止
+
+    MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);         // EV7
+    Data = I2C_ReceiveData(I2C2);
+
+    I2C_AcknowledgeConfig(I2C2, ENABLE);                              // 恢复ACK
+
+    return Data;
+}
+
+void MPU6050_Init(void)
+{
+    /* GPIO 初始化 */
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    /* I2C2 初始化 */
+    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
+    I2C_InitTypeDef I2C_InitStructure;
+    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
+    I2C_InitStructure.I2C_ClockSpeed = 50000;
+    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
+    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
+    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
+    I2C_InitStructure.I2C_OwnAddress1 = 0x00;
+    I2C_Init(I2C2, &I2C_InitStructure);
+    I2C_Cmd(I2C2, ENABLE);
+
+    /* MPU6050 配置 */
+    MPU6050_WriteReg(0x6B, 0x01);
+    MPU6050_WriteReg(0x6C, 0x00);
+    MPU6050_WriteReg(0x19, 0x09);
+    MPU6050_WriteReg(0x1A, 0x06);
+    MPU6050_WriteReg(0x1B, 0x18);
+    MPU6050_WriteReg(0x1C, 0x18);
+}
+~~~
+
+事件与标志位对应关系:
+
+| I2C 事件 | 标志位 | 说明 |
+|----------|--------|------|
+| `I2C_EVENT_MASTER_MODE_SELECT` | SB | EV5:起始位已发送 |
+| `I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(发送模式) |
+| `I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED` | ADDR | EV6:地址已发送且收到 ACK(接收模式) |
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTING` | TXE | EV8:数据寄存器空,可发送下一字节 |
+| `I2C_EVENT_MASTER_BYTE_TRANSMITTED` | BTF | EV8_2:字节传输完成(DR + 移位寄存器均空) |
+| `I2C_EVENT_MASTER_BYTE_RECEIVED` | RXNE | EV7:收到一个字节,数据可读 |
+
+### HAL库下的 MPU6050 驱动
+
+如果使用 HAL 库,MPU6050 驱动可简化为直接调用 `HAL_I2C_Mem_Write/Read`,不需要自己实现软件 I2C 时序:
+
+~~~c
+/* HAL库 — MPU6050 驱动(基于 HAL_I2C_Mem_Write/Read) */
+
+#define MPU6050_ADDR_7BIT   0x68      // 7位原始地址(不含R/W位)
+#define MPU6050_ADDR       0xD0      // 左对齐地址(含R/W位),供 HAL 库函数使用
+
+void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
+{
+    HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, RegAddress,
+                      I2C_MEMADD_SIZE_8BIT, &Data, 1, 100);
+}
+
+uint8_t MPU6050_ReadReg(uint8_t RegAddress)
+{
+    uint8_t Data;
+    HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, RegAddress,
+                     I2C_MEMADD_SIZE_8BIT, &Data, 1, 100);
+    return Data;
+}
+
+void MPU6050_Init(void)
+{
+    MX_I2C1_Init();                              // 初始化硬件 I2C
+    MPU6050_WriteReg(0x6B, 0x01);               // PWR_MGMT_1
+    MPU6050_WriteReg(0x6C, 0x00);               // PWR_MGMT_2
+    MPU6050_WriteReg(0x19, 0x09);               // SMPLRT_DIV
+    MPU6050_WriteReg(0x1A, 0x06);               // CONFIG
+    MPU6050_WriteReg(0x1B, 0x18);               // GYRO_CONFIG (±2000°/s)
+    MPU6050_WriteReg(0x1C, 0x18);               // ACCEL_CONFIG (±16g)
+}
+
+void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
+                     int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
+{
+    uint8_t buf[14];
+    HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B,
+                       I2C_MEMADD_SIZE_8BIT, buf, 14, 100);
+    *AccX = (buf[0]  << 8) | buf[1];
+    *AccY = (buf[2]  << 8) | buf[3];
+    *AccZ = (buf[4]  << 8) | buf[5];
+    *GyroX = (buf[8] << 8) | buf[9];
+    *GyroY = (buf[10] << 8) | buf[11];
+    *GyroZ = (buf[12] << 8) | buf[13];
+}
+~~~
+
+对比标准库方案,HAL 库方案的优点:
+- **无需手动操作 EV5/EV6/EV7 事件**——HAL 库内部封装
+- **`HAL_I2C_Mem_Read` 一次性完成写寄存器地址 + 重复起始 + 读数据**
+- **地址传入方式不同**:`HAL_I2C_Mem_Write/Read` 的 DevAddress 参数传左对齐地址(含 R/W 位,如 0xD0)
+
 ---
 
 ## 6. 硬件 I2C vs 软件 I2C

+ 166 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/06-SPI通信详解.md

@@ -194,6 +194,87 @@ void SPI_Init(void) {
 }
 ```
 
+### HAL 库初始化示例
+
+HAL 库使用 `SPI_HandleTypeDef` 句柄 + `HAL_SPI_Init()` 完成配置,底层 GPIO/RCC/NVIC 初始化在 `HAL_SPI_MspInit()` 回调中实现。
+
+```c
+// === SPI_HandleTypeDef 定义(全局)===
+SPI_HandleTypeDef hspi1;
+
+// === SPI 参数配置 ===
+void MX_SPI1_Init(void)
+{
+    hspi1.Instance = SPI1;
+    hspi1.Init.Mode = SPI_MODE_MASTER;              // 主机模式
+    hspi1.Init.Direction = SPI_DIRECTION_2LINES;    // 全双工
+    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;        // 8 位数据
+    hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;     // CPOL = 1(模式 3)
+    hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;          // CPHA = 1(模式 3)
+    hspi1.Init.NSS = SPI_NSS_SOFT;                  // 软件 NSS 管理
+    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 1MHz(APB2=8MHz/8)
+    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;         // MSB 先行
+    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;          // 标准 SPI(非 TI 模式)
+    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
+    hspi1.Init.CRCPolynomial = 0;
+    HAL_SPI_Init(&hspi1);
+}
+
+// === MSP 初始化回调:GPIO + RCC + NVIC ===
+void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (hspi->Instance == SPI1)
+    {
+        __HAL_RCC_SPI1_CLK_ENABLE();                // 开启 SPI1 时钟
+        __HAL_RCC_GPIOA_CLK_ENABLE();               // 开启 GPIOA 时钟
+
+        // PA5 (SCK)、PA7 (MOSI) → 复用推挽输出
+        GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;
+        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+        // PA6 (MISO) → 浮空输入
+        GPIO_InitStruct.Pin = GPIO_PIN_6;
+        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+        GPIO_InitStruct.Pull = GPIO_NOPULL;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+        // PA4 (NSS/CS) → 推挽输出,软件手动控制
+        GPIO_InitStruct.Pin = GPIO_PIN_4;
+        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
+        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 默认高电平
+    }
+}
+```
+
+**NSS 软件管理(`SPI_NSS_SOFT`)说明**:
+
+`SPI_NSS_SOFT` 告诉硬件:NSS 信号由软件通过寄存器(SSI 位)控制,而非硬件 NSS 引脚。此时 NSS 引脚(如 PA4)释放为普通 GPIO,由用户手动拉高/拉低来选择从机。
+
+- 设置 `SPI_NSS_SOFT` 后,CubeMX 中对应 Hardware NSS Signal 设为 Disable
+- 通信前 `HAL_GPIO_WritePin(GPIOx, NSS_PIN, GPIO_PIN_RESET)`
+- 通信后 `HAL_GPIO_WritePin(GPIOx, NSS_PIN, GPIO_PIN_SET)`
+- 与标准库 `SPI_NSS_Soft` 行为完全一致
+
+**标准库 vs HAL 库 SPI 配置对照**:
+
+| 参数          | 标准库                         | HAL 库                            |
+|---------------|-------------------------------|-----------------------------------|
+| 模式          | `SPI_Mode_Master`             | `SPI_MODE_MASTER`                |
+| 方向          | `SPI_Direction_2Lines_FullDuplex` | `SPI_DIRECTION_2LINES`        |
+| 数据宽度      | `SPI_DataSize_8b`             | `SPI_DATASIZE_8BIT`              |
+| 时钟极性      | `SPI_CPOL_High`               | `SPI_POLARITY_HIGH`              |
+| 时钟相位      | `SPI_CPHA_2Edge`              | `SPI_PHASE_2EDGE`                |
+| NSS 管理      | `SPI_NSS_Soft`                | `SPI_NSS_SOFT`                   |
+| 波特率分频    | `SPI_BaudRatePrescaler_8`     | `SPI_BAUDRATEPRESCALER_8`        |
+| 先行位        | `SPI_FirstBit_MSB`            | `SPI_FIRSTBIT_MSB`               |
+| 初始化函数    | `SPI_Init(SPIx, &init)`       | `HAL_SPI_Init(&hspi)`            |
+| 使能          | `SPI_Cmd(SPIx, ENABLE)`       | `__HAL_SPI_ENABLE(&hspi)`(Init 内自动调用) |
+
 ---
 
 ## 4. 编程接口
@@ -381,6 +462,91 @@ void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) {
 }
 ```
 
+### W25Q64 完整读写示例(使用 HAL_SPI_TransmitReceive)
+
+以下代码使用 `HAL_SPI_TransmitReceive` 同时收发,适用于 W25Q64 的读写操作。与分离调用 `HAL_SPI_Transmit` + `HAL_SPI_Receive` 不同,`TransmitReceive` 在发送指令字节的同时接收从机返回的数据,逻辑更紧凑。
+
+```c
+#define NSS_LOW()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
+#define NSS_HIGH()  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
+
+// 单字节交换(发送一个字节,接收一个字节)
+uint8_t W25Q64_SwapByte(uint8_t byte)
+{
+    uint8_t rx;
+    HAL_SPI_TransmitReceive(&hspi1, &byte, &rx, 1, HAL_MAX_DELAY);
+    return rx;
+}
+
+// 写使能
+void W25Q64_WriteEnable(void)
+{
+    uint8_t cmd = 0x06;
+    NSS_LOW();
+    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
+    NSS_HIGH();
+}
+
+// 等待 BUSY 释放
+void W25Q64_WaitBusy(void)
+{
+    uint8_t cmd = 0x05, status;
+    NSS_LOW();
+    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
+    do {
+        HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);
+    } while (status & 0x01);
+    NSS_HIGH();
+}
+
+// 扇区擦除(4KB)
+void W25Q64_SectorErase(uint32_t addr)
+{
+    uint8_t cmd[4] = {0x20, addr >> 16, addr >> 8, addr};
+    W25Q64_WriteEnable();
+    NSS_LOW();
+    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
+    NSS_HIGH();
+    W25Q64_WaitBusy();
+}
+
+// 页编程(最多 256 字节)
+void W25Q64_PageProgram(uint32_t addr, const uint8_t *data, uint16_t len)
+{
+    uint8_t cmd[4] = {0x02, addr >> 16, addr >> 8, addr};
+    W25Q64_WriteEnable();
+    NSS_LOW();
+    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
+    HAL_SPI_Transmit(&hspi1, (uint8_t *)data, len, HAL_MAX_DELAY);
+    NSS_HIGH();
+    W25Q64_WaitBusy();
+}
+
+// 读取数据(使用 TransmitReceive 实现)
+void W25Q64_ReadData(uint32_t addr, uint8_t *buf, uint32_t len)
+{
+    uint8_t cmd[4] = {0x03, addr >> 16, addr >> 8, addr};
+    NSS_LOW();
+    HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
+    HAL_SPI_Receive(&hspi1, buf, len, HAL_MAX_DELAY);
+    NSS_HIGH();
+}
+
+// 读取芯片 ID
+void W25Q64_ReadID(uint8_t *mid, uint16_t *did)
+{
+    uint8_t cmd = 0x9F, buf[3];
+    NSS_LOW();
+    HAL_SPI_TransmitReceive(&hspi1, &cmd, buf, 1, HAL_MAX_DELAY);   // 发指令,收制造商 ID
+    HAL_SPI_Receive(&hspi1, &buf[1], 2, HAL_MAX_DELAY);            // 收设备 ID 高 + 低
+    NSS_HIGH();
+    *mid = buf[0];
+    *did = ((uint16_t)buf[1] << 8) | buf[2];
+}
+```
+
+对比单独调用 `Transmit` + `Receive` 与一次性 `TransmitReceive`:前者分两步执行(底层仍是全双工),后者将收发捆绑在一个 API 调用中,代码更紧凑、中断/DMA 模式下效率更高。W25Q64 的 `ReadData` 和 `ReadID` 场景通常分步调用即可,只有单字节交换场景推荐 `TransmitReceive`。
+
 ### 完整实验:LED 状态保存
 
 ```c

+ 144 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/07-中断系统(NVIC+EXTI).md

@@ -69,8 +69,38 @@ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;              // 使能
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // 抢占优先级
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;            // 响应优先级
 NVIC_Init(&NVIC_InitStructure);
+
+### HAL库 等效代码
+
+在 HAL 库中,NVIC 配置分为三个独立步骤:
+
+```c
+// 1. 优先级分组(全工程调用一次)
+// HAL 默认使用 NVIC_PRIORITYGROUP_4(4 位抢占 + 0 位响应)
+HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
+
+// 2. 设置中断优先级
+HAL_NVIC_SetPriority(EXTI15_10_IRQn, 1, 0);   // PreemptPriority=1, SubPriority=0
+
+// 3. 使能中断
+HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
+// HAL_NVIC_DisableIRQ(EXTI15_10_IRQn);        // 禁用中断
 ```
 
+**优先级分组对比表(标准库 vs HAL)**:
+
+| 分组 | 标准库分组宏 | HAL分组宏 | 抢占位数 | 响应位数 |
+|------|-------------|----------|---------|---------|
+| 0 | `NVIC_PriorityGroup_0` | `NVIC_PRIORITYGROUP_0` | 0 bit | 4 bits |
+| 1 | `NVIC_PriorityGroup_1` | `NVIC_PRIORITYGROUP_1` | 1 bit | 3 bits |
+| 2 | `NVIC_PriorityGroup_2` | `NVIC_PRIORITYGROUP_2` | 2 bits | 2 bits |
+| 3 | `NVIC_PriorityGroup_3` | `NVIC_PRIORITYGROUP_3` | 3 bits | 1 bit |
+| 4 | `NVIC_PriorityGroup_4` | `NVIC_PRIORITYGROUP_4` | 4 bits | 0 bit |
+
+> **注意**:HAL 的 `HAL_NVIC_SetPriority()` 第三个参数 `SubPriority` 仅在分组为 1/2/3 时有效。使用 `NVIC_PRIORITYGROUP_4` 时 SubPriority 固定为 0。
+
+---
+
 **STM32 标准库中 NVIC 的 IRQ 通道枚举见 `stm32f10x.h`,命名规则:`外设名_IRQn`**,例如:
 - `EXTI0_IRQn` — EXTI 线 0
 - `EXTI1_IRQn` — EXTI 线 1
@@ -215,8 +245,58 @@ int main(void)
         OLED_ShowNum(1, 7, CountSensor_Get(), 5);
     }
 }
+
+### HAL库 等效代码(EXTI 配置 + 传感器计次)
+
+HAL 库中没有独立的 `EXTI_InitTypeDef`,EXTI 通过 `GPIO_Init()` 的 `Mode` 参数配置。将引脚设为 `GPIO_MODE_IT_xx` 后,HAL 自动完成 GPIO_EXTILineConfig 和 EXTI 初始化。
+
+```c
+void CountSensor_Init(void)
+{
+    // 1. 开启时钟
+    __HAL_RCC_GPIOB_CLK_ENABLE();
+    __HAL_RCC_AFIO_CLK_ENABLE();
+
+    // 2. GPIO + EXTI 初始化(一步完成)
+    GPIO_InitTypeDef GPIO_InitStructure = {0};
+    GPIO_InitStructure.Pin = GPIO_PIN_14;
+    GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发中断
+    GPIO_InitStructure.Pull = GPIO_PULLUP;
+    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    // 3. NVIC 分组 + 配置
+    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
+    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 1, 0);
+    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
+}
+```
+
+**中断处理方式差异**:HAL 的启动文件中已有弱定义的 `EXTI15_10_IRQHandler`,它调用 `HAL_GPIO_EXTI_IRQHandler()`,该函数自动检测中断标志、清除标志位、并调用 `HAL_GPIO_EXTI_Callback()`。用户只需实现回调:
+
+```c
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+{
+    if (GPIO_Pin == GPIO_PIN_14)
+    {
+        CountSensor_Count++;
+    }
+}
 ```
 
+> **对比**:标准库需自行实现 `EXTI15_10_IRQHandler`,手动判断 `EXTI_GetITStatus()`、清除 `EXTI_ClearITPendingBit()`。HAL 将样板代码封装在 `HAL_GPIO_EXTI_IRQHandler` 中,用户只需在 Callback 中写业务逻辑。
+
+**EXTI 的 GPIO_MODE_IT_xx 触发方式**:
+
+| HAL 模式宏 | 对应标准库 EXTI_Trigger | 说明 |
+|-----------|----------------------|------|
+| `GPIO_MODE_IT_FALLING` | `EXTI_Trigger_Falling` | 下降沿触发 |
+| `GPIO_MODE_IT_RISING` | `EXTI_Trigger_Rising` | 上升沿触发 |
+| `GPIO_MODE_IT_RISING_FALLING` | `EXTI_Trigger_Rising_Falling` | 双边沿触发 |
+
+**主函数与标准库完全相同,无需修改。**
+
+---
+
 ### 4.3 代码示例二:旋转编码器计次
 
 **功能**:旋转编码器(EC11)的 A 相(PB0)和 B 相(PB1),根据两相信号的先后顺序判断正反转。
@@ -317,9 +397,56 @@ int main(void)
         Num += Encoder_Get();  // 获取增量值并累加
         OLED_ShowSignedNum(1, 5, Num, 5);
     }
+    }
+}
+
+### HAL库 等效代码
+
+在 HAL 中,两条 EXTI 线共用同一个回调函数,通过 `GPIO_Pin` 参数区分:
+
+```c
+void Encoder_Init(void)
+{
+    __HAL_RCC_GPIOB_CLK_ENABLE();
+    __HAL_RCC_AFIO_CLK_ENABLE();
+
+    GPIO_InitTypeDef GPIO_InitStructure = {0};
+    GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1;
+    GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING;   // A/B 相都下降沿触发
+    GPIO_InitStructure.Pull = GPIO_PULLUP;
+    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
+
+    HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
+    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
+
+    HAL_NVIC_SetPriority(EXTI1_IRQn, 1, 1);
+    HAL_NVIC_EnableIRQ(EXTI1_IRQn);
+}
+
+// 统一回调:通过 GPIO_Pin 判断触发来源
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+{
+    if (GPIO_Pin == GPIO_PIN_0)      // A 相下降沿
+    {
+        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
+        {
+            Encoder_Count--;         // B 为低 → 反转
+        }
+    }
+    else if (GPIO_Pin == GPIO_PIN_1) // B 相下降沿
+    {
+        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
+        {
+            Encoder_Count++;         // A 为低 → 正转
+        }
+    }
 }
 ```
 
+> **注意**:与标准库不同,HAL 所有 EXTI 线共享一个回调函数。多条线时需要在 `HAL_GPIO_EXTI_Callback` 中通过 `if-else` 或 `switch(GPIO_Pin)` 分发处理。HAL 的 EXTI 中断服务函数已内置标志位清除,无需手动调用。
+
 ---
 
 ## 5. 常见问题与注意事项
@@ -387,3 +514,20 @@ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
 - 编码器正交信号 → 下降沿触发(结合另一相判断方向)
 - 需要检测高电平有效的事件 → 上升沿触发
 - 电平变化都需要响应 → 上升+下降沿触发
+
+---
+
+## 7. API 对照表:标准库 vs HAL 库
+
+| 功能 | 标准库函数 | HAL库函数 |
+|------|-----------|----------|
+| 优先级分组 | `NVIC_PriorityGroupConfig()` | `HAL_NVIC_SetPriorityGrouping()` |
+| 设置中断优先级 | `NVIC_Init()` | `HAL_NVIC_SetPriority()` |
+| 使能中断 | `NVIC_Init()` 内 `ENABLE` | `HAL_NVIC_EnableIRQ()` |
+| 禁用中断 | `NVIC_Init()` 内 `DISABLE` | `HAL_NVIC_DisableIRQ()` |
+| EXTI 初始化 | `EXTI_Init()` + `GPIO_EXTILineConfig()` | `HAL_GPIO_Init()` 配合 `GPIO_MODE_IT_xx` |
+| GPIO 时钟使能 | `RCC_APB2PeriphClockCmd()` | `__HAL_RCC_GPIOx_CLK_ENABLE()` |
+| AFIO 时钟使能 | `RCC_APB2PeriphClockCmd()` | `__HAL_RCC_AFIO_CLK_ENABLE()` |
+| 中断标志查询 | `EXTI_GetITStatus()` | 由 `HAL_GPIO_EXTI_IRQHandler()` 内部完成 |
+| 中断标志清除 | `EXTI_ClearITPendingBit()` | 由 `HAL_GPIO_EXTI_IRQHandler()` 自动清除 |
+| 用户中断处理 | 实现 `EXTIx_IRQHandler()` | 实现 `HAL_GPIO_EXTI_Callback()` |

+ 474 - 6
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/08-时钟系统(RCC).md

@@ -135,7 +135,457 @@ HSE(8 MHz 外部晶振)
 9. 切换 SYSCLK 到 PLL 输出
 10. 等待 SYSCLK 切换完成
 
-### 3.3 SystemCoreClock 变量
+### 3.3 SetSysClockTo72 完整代码(标准库)
+
+以下代码来自 `system_stm32f10x.c`,当定义 `SYSCLK_FREQ_72MHz` 宏时被 `SetSysClock()` 调用:
+
+~~~c
+static void SetSysClockTo72(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    /* 使能 HSE */
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    /* 等待 HSE 就绪,超时则退出 */
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        /* 使能预取缓冲 */
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+
+        /* Flash 2 个等待周期(72MHz 需要) */
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
+
+        /* HCLK = SYSCLK(AHB 不分频) */
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+
+        /* PCLK2 = HCLK(APB2 不分频) */
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+
+        /* PCLK1 = HCLK / 2(APB1 二分频) */
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
+
+        /* PLL 配置:PLLCLK = HSE × 9 = 72 MHz */
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC |
+                                            RCC_CFGR_PLLXTPRE |
+                                            RCC_CFGR_PLLMULL));
+        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
+
+        /* 使能 PLL */
+        RCC->CR |= RCC_CR_PLLON;
+        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }
+
+        /* 选择 PLL 作为系统时钟 */
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+
+        /* 等待 PLL 切换完成 */
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
+    }
+    else
+    {
+        /* HSE 起振失败 — 可在此添加错误处理代码 */
+    }
+}
+~~~
+
+### 3.4 SetSysClockTo72 HAL库等价代码
+
+~~~c
+static void SetSysClockTo72(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    /* HSE 振荡器配置:使能 HSE,作为 PLL 输入 */
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
+    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
+    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;       /* HSE × 9 = 72 MHz */
+    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { }
+
+    /* 时钟树配置:SYSCLK = PLL72MHz,总线分频 */
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK  |
+                                       RCC_CLOCKTYPE_SYSCLK |
+                                       RCC_CLOCKTYPE_PCLK1  |
+                                       RCC_CLOCKTYPE_PCLK2;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;    /* HCLK = 72 MHz */
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;      /* PCLK2 = 72 MHz */
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;      /* PCLK1 = 36 MHz */
+
+    /* Flash 等待周期:72MHz 需要 2 个等待周期 */
+    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { }
+}
+~~~
+
+### 3.6 SetSysClockToHSE 完整代码(标准库 — HSE 直通)
+
+```c
+static void SetSysClockToHSE(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;
+
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_HSE;
+
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x04) { }
+    }
+}
+
+/* --- HAL库等价 --- */
+static void SetSysClockToHSE(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_HCLK  |
+                                       RCC_CLOCKTYPE_SYSCLK |
+                                       RCC_CLOCKTYPE_PCLK1  |
+                                       RCC_CLOCKTYPE_PCLK2;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_HSE;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
+    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
+}
+```
+
+### 3.7 SetSysClockTo24 完整代码(标准库 — 24 MHz)
+
+```c
+static void SetSysClockTo24(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;
+
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
+        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL6);
+
+        RCC->CR |= RCC_CR_PLLON;
+        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
+    }
+}
+
+/* --- HAL库等价 --- */
+static void SetSysClockTo24(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
+    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
+    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL6;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
+    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
+}
+```
+
+### 3.8 SetSysClockTo36 完整代码(标准库 — 36 MHz)
+
+```c
+static void SetSysClockTo36(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_1;
+
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
+        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_HSE_Div2 | RCC_CFGR_PLLMULL9);
+
+        RCC->CR |= RCC_CR_PLLON;
+        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
+    }
+}
+
+/* HAL库等价版 */
+static void SetSysClockTo36(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
+    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
+    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL9;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
+    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);
+}
+```
+
+### 3.9 SetSysClockTo48 完整代码(标准库 — 48 MHz)
+
+```c
+static void SetSysClockTo48(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_1;
+
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
+        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL6);
+
+        RCC->CR |= RCC_CR_PLLON;
+        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
+    }
+}
+
+/* HAL库等价版 */
+static void SetSysClockTo48(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
+    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
+    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL6;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1);
+}
+```
+
+### 3.10 SetSysClockTo56 完整代码(标准库 — 56 MHz)
+
+```c
+static void SetSysClockTo56(void)
+{
+    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
+
+    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
+
+    do
+    {
+        HSEStatus = RCC->CR & RCC_CR_HSERDY;
+        StartUpCounter++;
+    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
+
+    if ((RCC->CR & RCC_CR_HSERDY) != RESET)
+        HSEStatus = (uint32_t)0x01;
+    else
+        HSEStatus = (uint32_t)0x00;
+
+    if (HSEStatus == (uint32_t)0x01)
+    {
+        FLASH->ACR |= FLASH_ACR_PRFTBE;
+        FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
+        FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
+
+        RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
+        RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
+        RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL7);
+
+        RCC->CR |= RCC_CR_PLLON;
+        while ((RCC->CR & RCC_CR_PLLRDY) == 0) { }
+
+        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
+        RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { }
+    }
+}
+
+/* HAL库等价版 */
+static void SetSysClockTo56(void)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+    RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;
+    RCC_OscInitStruct.PLL.PLLSource  = RCC_PLLSOURCE_HSE;
+    RCC_OscInitStruct.PLL.PLLMUL     = RCC_PLL_MUL7;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_ALL;
+    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
+    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
+    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
+}
+```
+
+### 3.11 MCO 时钟输出配置
+
+MCO(Microcontroller Clock Output)可将内部时钟从 PA8 引脚输出,供外部测量或作为其它芯片的时钟源。
+
+~~~c
+/* 标准库版:PA8 输出 PLL/2 = 36 MHz */
+GPIO_InitTypeDef GPIO_InitStructure;
+RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+
+GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
+GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
+GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+RCC_MCOConfig(RCC_MCO_PLLCLK_DIV2);   /* MCO = PLL / 2 */
+
+/* 其他可选时钟源 */
+// RCC_MCOConfig(RCC_MCO_HSI);        /* MCO = HSI 8 MHz       */
+// RCC_MCOConfig(RCC_MCO_HSE);        /* MCO = HSE 8 MHz       */
+// RCC_MCOConfig(RCC_MCO_SYSCLK);     /* MCO = SYSCLK           */
+// RCC_MCOConfig(RCC_MCO_PLLCLK_DIV2);/* MCO = PLLCLK / 2      */
+~~~
+
+~~~c
+/* HAL库等价:PA8 输出 PLL/2 = 36 MHz */
+GPIO_InitTypeDef GPIO_InitStruct = {0};
+
+__HAL_RCC_GPIOA_CLK_ENABLE();
+GPIO_InitStruct.Pin       = GPIO_PIN_8;
+GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
+GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+HAL_RCC_MCOConfig(RCC_MCO1SOURCE_PLLCLK, RCC_MCODIV_2);
+~~~
+
+### 3.12 SystemCoreClock 变量
 
 ```c
 // system_stm32f10x.c 中定义
@@ -153,11 +603,29 @@ OLED_ShowNum(1, 8, SystemCoreClock, 8);  // 显示当前系统主频
 
 RCC 通过三个寄存器组控制外设时钟的开关。使用时只需调用以下标准库函数:
 
-| 总线 | 函数 | 示例 |
-|------|------|------|
-| APB2 | `RCC_APB2PeriphClockCmd()` | `RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE)` |
-| APB1 | `RCC_APB1PeriphClockCmd()` | `RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE)` |
-| AHB | `RCC_AHBPeriphClockCmd()` | `RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)` |
+| 总线 | 标准库函数 | HAL 宏 |
+|------|-----------|--------|
+| APB2 | `RCC_APB2PeriphClockCmd()` | `__HAL_RCC_GPIOA_CLK_ENABLE()` 等 |
+| APB1 | `RCC_APB1PeriphClockCmd()` | `__HAL_RCC_TIM2_CLK_ENABLE()` 等 |
+| AHB | `RCC_AHBPeriphClockCmd()` | `__HAL_RCC_DMA1_CLK_ENABLE()` 等 |
+
+**标准库示例:**
+```c
+RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
+RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
+RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
+```
+
+**HAL库示例:**
+```c
+__HAL_RCC_GPIOB_CLK_ENABLE();     /* APB2 — GPIOB 时钟使能 */
+__HAL_RCC_TIM2_CLK_ENABLE();      /* APB1 — TIM2 时钟使能 */
+__HAL_RCC_DMA1_CLK_ENABLE();      /* AHB  — DMA1 时钟使能 */
+
+__HAL_RCC_USART1_CLK_ENABLE();    /* APB2 — USART1 时钟使能 */
+__HAL_RCC_ADC1_CLK_ENABLE();      /* APB2 — ADC1 时钟使能 */
+__HAL_RCC_SPI1_CLK_ENABLE();      /* APB2 — SPI1 时钟使能 */
+```
 
 **不能在初始化函数中关闭时钟后再开启**,否则外设寄存器会丢失配置。
 

+ 266 - 21
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/09-定时器详解.md

@@ -124,6 +124,50 @@ void TIM2_IRQHandler(void)
 }
 ```
 
+### 2.6 HAL 库版本:定时中断
+
+```c
+// HAL库 — 定时中断
+TIM_HandleTypeDef htim2;
+
+void MX_TIM2_Init(void)
+{
+    htim2.Instance = TIM2;
+    htim2.Init.Prescaler = 7200 - 1;            // 72MHz/7200 = 10kHz
+    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
+    htim2.Init.Period = 10000 - 1;              // 10kHz/10000 = 1Hz
+    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
+    HAL_TIM_Base_Init(&htim2);
+
+    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
+    HAL_NVIC_EnableIRQ(TIM2_IRQn);
+
+    HAL_TIM_Base_Start_IT(&htim2);              // 启动中断模式
+}
+
+// MSP 初始化:HAL 自动调用,负责底层 RCC/GPIO/NVIC
+void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
+{
+    if (htim->Instance == TIM2) {
+        __HAL_RCC_TIM2_CLK_ENABLE();
+    }
+}
+
+// HAL 弱回调,需在用户文件中实现
+void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
+{
+    if (htim->Instance == TIM2) {
+        // 定时中断处理,如 Num++;
+    }
+}
+```
+
+注意:
+- `HAL_TIM_Base_Init()` 内部会调用 `HAL_TIM_Base_MspInit()` 开启时钟等底层配置。
+- `AutoReloadPreload` 使能后,ARR 在更新事件时更新,防止写入途中产生异常波形。
+- 中断回调使用弱函数 `HAL_TIM_PeriodElapsedCallback`,在 HAL 库里有一个默认 `__weak` 实现,用户需在同文件或主文件中覆盖。
+
 ---
 
 ## 3. 输出比较与 PWM
@@ -243,6 +287,61 @@ int main(void)
 }
 ```
 
+### 3.6 HAL 库版本:PWM 输出
+
+```c
+// HAL库 — PWM 驱动 LED 呼吸灯
+TIM_HandleTypeDef htim2;
+
+void MX_TIM2_Init(void)
+{
+    TIM_OC_InitTypeDef sConfigOC = {0};
+
+    htim2.Instance = TIM2;
+    htim2.Init.Prescaler = 720 - 1;             // 72MHz/720 = 100kHz
+    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
+    htim2.Init.Period = 100 - 1;                 // 100kHz/100 = 1kHz
+    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
+    HAL_TIM_PWM_Init(&htim2);
+
+    sConfigOC.OCMode = TIM_OCMODE_PWM1;
+    sConfigOC.Pulse = 0;                          // 初始 CCR=0
+    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
+    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
+    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
+
+    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
+}
+
+void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (htim->Instance == TIM2) {
+        __HAL_RCC_TIM2_CLK_ENABLE();
+        __HAL_RCC_GPIOA_CLK_ENABLE();
+        GPIO_InitStruct.Pin = GPIO_PIN_0;
+        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;   // 复用推挽输出
+        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+    }
+}
+
+// 设置占空比(宏写法,与 GPIO 无关)
+void PWM_SetCompare1(uint16_t Compare)
+{
+    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, Compare);
+}
+```
+
+与标准库对照:
+| 标准库 | HAL 库 |
+|--------|--------|
+| `TIM_OC1Init(TIM2, &...)` | `HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1)` |
+| `TIM_SetCompare1(TIM2, val)` | `__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, val)` |
+| `GPIO_Mode_AF_PP` | `GPIO_MODE_AF_PP` |
+| `TIM_Cmd(TIM2, ENABLE)` | `HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1)` |
+
 **引脚重映射**:如果 PA0 被占用,可以将 TIM2_CH1 重映射到其他引脚:
 
 ```c
@@ -359,6 +458,91 @@ uint32_t IC_GetFreq(void)
 
 配置差别:将通道 2 设置为相反的极性(下降沿),选择交叉输入(`TIM_ICSelection_IndirectTI`)。
 
+### 4.6 HAL 库版本:输入捕获测频率
+
+```c
+// HAL库 — 输入捕获测频率
+TIM_HandleTypeDef htim3;
+
+void MX_TIM3_IC_Init(void)
+{
+    TIM_IC_InitTypeDef sConfigIC = {0};
+
+    htim3.Instance = TIM3;
+    htim3.Init.Prescaler = 72 - 1;               // 72MHz/72 = 1MHz
+    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
+    htim3.Init.Period = 65536 - 1;               // ARR 最大
+    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
+    HAL_TIM_IC_Init(&htim3);
+
+    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
+    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
+    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
+    sConfigIC.ICFilter = 0xF;
+    HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
+
+    // 从模式复位:TI1FP1 上升沿触发 CNT 归零(HAL 无封装,直接用寄存器)
+    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
+    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
+
+    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
+    HAL_NVIC_EnableIRQ(TIM3_IRQn);
+
+    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
+}
+
+void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (htim->Instance == TIM3) {
+        __HAL_RCC_TIM3_CLK_ENABLE();
+        __HAL_RCC_GPIOA_CLK_ENABLE();
+        GPIO_InitStruct.Pin = GPIO_PIN_6;
+        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+        GPIO_InitStruct.Pull = GPIO_PULLUP;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+    }
+}
+
+// 捕获回调:发生捕获时自动调用
+uint32_t CapturedValue = 0;
+void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
+{
+    if (htim->Instance == TIM3) {
+        CapturedValue = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
+        // 频率 f = 1000000 / (CapturedValue + 1)
+    }
+}
+```
+
+**PWMI 模式(测频率+占空比)** HAL 库配置:
+
+```c
+// 通道 1:上升沿,直通
+sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
+sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
+HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
+
+// 通道 2:下降沿,交叉输入
+sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
+sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
+HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);
+
+// 从模式复位
+TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
+TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
+
+HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
+HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
+
+// 回调中读取:
+//   uint32_t Period   = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
+//   uint32_t DutyPulse = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);
+//   频率 f = CK_CNT / Period
+//   占空比 = DutyPulse / Period
+```
+
 ---
 
 ## 5. 编码器接口
@@ -475,41 +659,102 @@ int main(void)
 }
 ```
 
+### 5.5 HAL 库版本:编码器接口
+
+```c
+// HAL库 — 编码器接口测速
+TIM_HandleTypeDef htim3;
+TIM_Encoder_InitTypeDef sEncoder = {0};
+
+void MX_TIM3_Encoder_Init(void)
+{
+    htim3.Instance = TIM3;
+    htim3.Init.Prescaler = 1 - 1;                // 不分频
+    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
+    htim3.Init.Period = 65536 - 1;               // ARR 最大,自由运行
+    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
+    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
+
+    sEncoder.EncoderMode = TIM_ENCODERMODE_TI12;  // TI1+TI2 双沿计数
+    sEncoder.IC1Polarity = TIM_ICPOLARITY_RISING;
+    sEncoder.IC1Selection = TIM_ICSELECTION_DIRECTTI;
+    sEncoder.IC1Prescaler = TIM_ICPSC_DIV1;
+    sEncoder.IC1Filter = 0xF;
+    sEncoder.IC2Polarity = TIM_ICPOLARITY_RISING;
+    sEncoder.IC2Selection = TIM_ICSELECTION_DIRECTTI;
+    sEncoder.IC2Prescaler = TIM_ICPSC_DIV1;
+    sEncoder.IC2Filter = 0xF;
+
+    HAL_TIM_Encoder_Init(&htim3, &sEncoder);
+
+    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
+}
+
+void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (htim->Instance == TIM3) {
+        __HAL_RCC_TIM3_CLK_ENABLE();
+        __HAL_RCC_GPIOA_CLK_ENABLE();
+        GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
+        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+        GPIO_InitStruct.Pull = GPIO_PULLUP;
+        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+    }
+}
+
+// 获取增量值并清零
+int16_t Encoder_Get(void)
+{
+    int16_t Temp = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
+    __HAL_TIM_SET_COUNTER(&htim3, 0);
+    return Temp;
+}
+```
+
+注意:
+- `HAL_TIM_Encoder_Init()` 内部同时完成了时基单元 + 编码器配置 + 通道滤波,无需额外调用 `TIM_ICInit`。
+- 编码器模式下 `AutoReloadPreload` 通常设为 `DISABLE`,因为需要 CNT 对 ARR 立即响应。
+- `__HAL_TIM_GET_COUNTER` / `__HAL_TIM_SET_COUNTER` 是宏,代替标准库的 `TIM_GetCounter` / `TIM_SetCounter`。
+
 ---
 
 ## 6. 定时器关键函数速查
 
 ### 基础操作
 
-| 函数 | 用途 |
-|------|------|
-| `TIM_Cmd(TIMx, ENABLE/DISABLE)` | 启动/停止定时器 |
-| `TIM_InternalClockConfig(TIMx)` | 选择内部时钟(默认可不调用) |
-| `TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE)` | 使能更新中断 |
-| `TIM_SetCounter(TIMx, Val)` | 写 CNT |
-| `TIM_GetCounter(TIMx)` | 读 CNT |
+| 标准库 | HAL 库(宏/函数) | 用途 |
+|--------|-------------------|------|
+| `TIM_Cmd(TIMx, ENABLE/DISABLE)` | `HAL_TIM_Base_Start/Stop(&htim)` | 启动/停止定时器 |
+| `TIM_InternalClockConfig(TIMx)` | HAL 初始化默认内部时钟 | 内部时钟 |
+| `TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE)` | `HAL_TIM_Base_Start_IT(&htim)` | 使能更新中断 |
+| `TIM_SetCounter(TIMx, Val)` | `__HAL_TIM_SET_COUNTER(&htim, Val)` | 写 CNT |
+| `TIM_GetCounter(TIMx)` | `__HAL_TIM_GET_COUNTER(&htim)` | 读 CNT |
+| `TIM_ClearFlag(TIMx, flag)` | `__HAL_TIM_CLEAR_FLAG(&htim, flag)` | 清除标志 |
 
-### 输出比较
+### 输出比较 / PWM
 
-| 函数 | 用途 |
-|------|------|
-| `TIM_SetCompare1(TIMx, Val)` | 设置 CCR1 |
-| `TIM_GetCompare1(TIMx)` | 读取 CCR1 |
-| `TIM_OC1PreloadConfig(TIMx, ENABLE)` | 使能预装载(防止 CCR 更新时出现不完整 PWM)|
+| 标准库 | HAL 库(宏/函数) | 用途 |
+|--------|-------------------|------|
+| `TIM_SetCompare1(TIMx, Val)` | `__HAL_TIM_SET_COMPARE(&htim, CH, Val)` | 设置 CCR |
+| `TIM_GetCompare1(TIMx)` | `__HAL_TIM_GET_COMPARE(&htim, CH)` | 读取 CCR |
+| `TIM_OC1PreloadConfig(TIMx, ENABLE)` | `sConfigOC.OCFastMode` + `AutoReloadPreload` | 预装载 |
+| `TIM_OCStructInit(&s)` | 直接给 `TIM_OC_InitTypeDef` 成员赋值 | 初始化结构体 |
 
 ### 输入捕获
 
-| 函数 | 用途 |
-|------|------|
-| `TIM_GetCapture1(TIMx)` | 读取捕获值(IC 自动锁存的 CNT)|
-| `TIM_SelectInputTrigger(TIMx, TIM_TS_TI1FP1)` | 选择触发源 |
-| `TIM_SelectSlaveMode(TIMx, TIM_SlaveMode_Reset)` | 从模式:复位/门控/触发 |
+| 标准库 | HAL 库(宏/函数 | 用途 |
+|--------|-------------------|------|
+| `TIM_GetCapture1(TIMx)` | `HAL_TIM_ReadCapturedValue(&htim, CH)` | 读取捕获值 |
+| `TIM_SelectInputTrigger(TIMx, src)` | 寄存器直接操作(HAL 无封装) | 选择触发源 |
+| `TIM_SelectSlaveMode(TIMx, mode)` | 寄存器直接操作(HAL 无封装) | 从模式配置 |
 
 ### 编码器
 
-| 函数 | 用途 |
-|------|------|
-| `TIM_EncoderInterfaceConfig(TIMx, mode, pol1, pol2)` | 配置编码器模式 |
+| 标准库 | HAL 库 | 用途 |
+|--------|--------|------|
+| `TIM_EncoderInterfaceConfig(...)` | `HAL_TIM_Encoder_Init(&htim, &sEncoder)` | 配置编码器模式 |
+| `TIM_ICInit(TIMx, &s)` → `TIM_EncoderInterfaceConfig(...)` | `HAL_TIM_Encoder_Init()` 一次完成 | 一步到位 |
 
 ### 时钟源切换
 

+ 249 - 4
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/10-ADC模数转换.md

@@ -89,8 +89,45 @@ uint16_t AD_GetValue(void)
 }
 ```
 
-### 主函数(单通道)
+~~~c HAL库
+ADC_HandleTypeDef hadc1;
+
+void AD_Init(void)
+{
+    __HAL_RCC_ADC1_CLK_ENABLE();
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
+
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    GPIO_InitStruct.Pin = GPIO_PIN_0;
+    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    hadc1.Instance = ADC1;
+    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
+    hadc1.Init.ContinuousConvMode = DISABLE;
+    hadc1.Init.DiscontinuousConvMode = DISABLE;
+    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
+    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
+    hadc1.Init.NbrOfConversion = 1;
+    HAL_ADC_Init(&hadc1);
+
+    ADC_ChannelConfTypeDef sConfig = {0};
+    sConfig.Channel = ADC_CHANNEL_0;
+    sConfig.Rank = ADC_REGULAR_RANK_1;
+    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+}
 
+uint16_t AD_GetValue(void)
+{
+    HAL_ADC_Start(&hadc1);
+    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
+    return (uint16_t)HAL_ADC_GetValue(&hadc1);
+}
+```
+
+### 主函数(单通道)
 ```c
 int main(void)
 {
@@ -115,13 +152,33 @@ int main(void)
 
 ### AD.c(多通道)
 
-```c
+```c 标准库
 void AD_Init(void)
 {
-    // 初始化 PA0~PA3 为模拟输入
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
+
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStructure);
-    // 其余初始化同单通道...
+
+    ADC_InitTypeDef ADC_InitStructure;
+    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
+    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
+    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
+    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
+    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
+    ADC_InitStructure.ADC_NbrOfChannel = 1;
+    ADC_Init(ADC1, &ADC_InitStructure);
+
+    ADC_Cmd(ADC1, ENABLE);
+    ADC_ResetCalibration(ADC1);
+    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
+    ADC_StartCalibration(ADC1);
+    while (ADC_GetCalibrationStatus(ADC1) == SET);
 }
 
 uint16_t AD_GetValue(uint8_t ADC_Channel)
@@ -133,6 +190,44 @@ uint16_t AD_GetValue(uint8_t ADC_Channel)
 }
 ```
 
+~~~c HAL库
+ADC_HandleTypeDef hadc1;
+
+void AD_Init(void)
+{
+    __HAL_RCC_ADC1_CLK_ENABLE();
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
+
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
+    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    hadc1.Instance = ADC1;
+    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
+    hadc1.Init.ContinuousConvMode = DISABLE;
+    hadc1.Init.DiscontinuousConvMode = DISABLE;
+    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
+    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
+    hadc1.Init.NbrOfConversion = 1;
+    HAL_ADC_Init(&hadc1);
+}
+
+uint16_t AD_GetValue(uint8_t ADC_Channel)
+{
+    ADC_ChannelConfTypeDef sConfig = {0};
+    sConfig.Channel = ADC_CHANNEL_0 + ADC_Channel;
+    sConfig.Rank = ADC_REGULAR_RANK_1;
+    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+
+    HAL_ADC_Start(&hadc1);
+    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
+    return (uint16_t)HAL_ADC_GetValue(&hadc1);
+}
+~~~
+
 ### 主函数(多通道)
 
 ```c
@@ -163,3 +258,153 @@ while (1) {
 ### 扫描模式
 
 使能扫描模式后,ADC 可对规则组中多个通道依次转换。配合连续模式 + DMA 可实现多通道自动连续采集。
+
+## DMA + ADC 多通道自动采集
+
+使用 DMA 自动将 ADC 多通道转换结果转运到内存数组,无需 CPU 干预。
+
+### AD.c(DMA 模式)
+
+```c 标准库
+#include "stm32f10x.h"
+
+uint16_t AD_Value[4];
+
+void AD_Init(void)
+{
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
+    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
+    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
+
+    GPIO_InitTypeDef GPIO_InitStructure;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOA, &GPIO_InitStructure);
+
+    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
+    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
+    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
+    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
+
+    ADC_InitTypeDef ADC_InitStructure;
+    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
+    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
+    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
+    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
+    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
+    ADC_InitStructure.ADC_NbrOfChannel = 4;
+    ADC_Init(ADC1, &ADC_InitStructure);
+
+    DMA_InitTypeDef DMA_InitStructure;
+    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
+    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
+    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
+    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
+    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
+    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
+    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
+    DMA_InitStructure.DMA_BufferSize = 4;
+    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
+    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
+    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
+    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
+
+    DMA_Cmd(DMA1_Channel1, ENABLE);
+    ADC_DMACmd(ADC1, ENABLE);
+    ADC_Cmd(ADC1, ENABLE);
+
+    ADC_ResetCalibration(ADC1);
+    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
+    ADC_StartCalibration(ADC1);
+    while (ADC_GetCalibrationStatus(ADC1) == SET);
+
+    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
+}
+```
+
+~~~c HAL库
+ADC_HandleTypeDef hadc1;
+DMA_HandleTypeDef hdma_adc1;
+uint16_t AD_Value[4];
+
+void AD_Init(void)
+{
+    __HAL_RCC_ADC1_CLK_ENABLE();
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+    __HAL_RCC_DMA1_CLK_ENABLE();
+    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
+
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
+    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+    hadc1.Instance = ADC1;
+    hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
+    hadc1.Init.ContinuousConvMode = ENABLE;
+    hadc1.Init.DiscontinuousConvMode = DISABLE;
+    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
+    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
+    hadc1.Init.NbrOfConversion = 4;
+    HAL_ADC_Init(&hadc1);
+
+    ADC_ChannelConfTypeDef sConfig = {0};
+    sConfig.Channel = ADC_CHANNEL_0;
+    sConfig.Rank = ADC_REGULAR_RANK_1;
+    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+    sConfig.Channel = ADC_CHANNEL_1;
+    sConfig.Rank = ADC_REGULAR_RANK_2;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+    sConfig.Channel = ADC_CHANNEL_2;
+    sConfig.Rank = ADC_REGULAR_RANK_3;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+    sConfig.Channel = ADC_CHANNEL_3;
+    sConfig.Rank = ADC_REGULAR_RANK_4;
+    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
+
+    hdma_adc1.Instance = DMA1_Channel1;
+    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
+    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
+    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
+    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
+    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
+    hdma_adc1.Init.Mode = DMA_CIRCULAR;
+    hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM;
+    HAL_DMA_Init(&hdma_adc1);
+
+    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
+
+    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)AD_Value, 4);
+}
+~~~
+
+#### 主函数(DMA 模式)
+
+```c
+#include "AD.h"
+#include "OLED.h"
+#include "Delay.h"
+
+int main(void)
+{
+    OLED_Init();
+    AD_Init();
+
+    OLED_ShowString(1, 1, "AD0:");
+    OLED_ShowString(2, 1, "AD1:");
+    OLED_ShowString(3, 1, "AD2:");
+    OLED_ShowString(4, 1, "AD3:");
+
+    while (1)
+    {
+        OLED_ShowNum(1, 5, AD_Value[0], 4);
+        OLED_ShowNum(2, 5, AD_Value[1], 4);
+        OLED_ShowNum(3, 5, AD_Value[2], 4);
+        OLED_ShowNum(4, 5, AD_Value[3], 4);
+        Delay_ms(100);
+    }
+}
+```

+ 92 - 1
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/11-DMA数据传输.md

@@ -30,7 +30,7 @@ DMA 优先级分为 4 级:`Low` < `Medium` < `High` < `VeryHigh`。当多个
 
 将源内存区域的数据搬运到目标内存区域。需要使能 `DMA_M2M`(存储器到存储器模式),此时 DMA 从软件请求触发转为连续工作。
 
-### 驱动代码 `MyDMA.c`
+### 驱动代码 `MyDMA.c`(标准库)
 
 ```c
 void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
@@ -64,6 +64,67 @@ void MyDMA_Transfer(void)
 }
 ```
 
+### MEM-to-MEM 阻塞转运 HAL
+
+```c
+DMA_HandleTypeDef hdma_m2m;
+
+void MyDMA_Init_HAL(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
+{
+    __HAL_RCC_DMA1_CLK_ENABLE();
+
+    hdma_m2m.Instance                 = DMA1_Channel1;
+    hdma_m2m.Init.Direction           = DMA_MEMORY_TO_MEMORY;
+    hdma_m2m.Init.PeriphInc           = DMA_PINC_ENABLE;   // 源地址自增
+    hdma_m2m.Init.MemInc              = DMA_MINC_ENABLE;   // 目标地址自增
+    hdma_m2m.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
+    hdma_m2m.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
+    hdma_m2m.Init.Mode                = DMA_NORMAL;
+    hdma_m2m.Init.Priority            = DMA_PRIORITY_MEDIUM;
+    HAL_DMA_Init(&hdma_m2m);
+}
+
+void MyDMA_Transfer_HAL(void)
+{
+    HAL_DMA_Start(&hdma_m2m, (uint32_t)DataA, (uint32_t)DataB, 4);
+    HAL_DMA_PollForTransfer(&hdma_m2m, HAL_DMA_FULL_TRANSFER, HAL_MAX_DELAY);
+}
+```
+
+### MEM-to-MEM 中断转运(HAL)
+
+```c
+// 全局回调
+void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma)
+{
+    // 传输完成 — 设置标志位供主循环查询
+    TransferDone = 1;
+}
+
+void MyDMA_Transfer_IT_HAL(void)
+{
+    HAL_DMA_Start_IT(&hdma_m2m, (uint32_t)DataA, (uint32_t)DataB, 4);
+}
+
+int main(void)
+{
+    HAL_Init();
+    MyDMA_Init_HAL((uint32_t)DataA, (uint32_t)DataB, 4);
+
+    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
+    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
+
+    while (1)
+    {
+        if (TransferDone)
+        {
+            TransferDone = 0;
+            // 处理转运完成后的数据
+        }
+    }
+}
+```
+
 ### 主函数
 
 ```c
@@ -97,6 +158,36 @@ int main(void)
 
 ADC 每次转换完成后自动触发 DMA 请求,DMA 将 ADC 数据寄存器(`ADC1->DR`)的值转运到内存数组。配合 ADC 的连续转换 + 扫描模式,实现多通道自动采集。
 
+### AD.c(DMA 版本,标准库)
+
+### DMA+ADC 循环采集(HAL)
+
+```c
+DMA_HandleTypeDef hdma_adc;
+
+void AD_Init_HAL(void)
+{
+    // GPIO, ADC 时钟使能 ...(省略,同标准库)
+
+    __HAL_RCC_DMA1_CLK_ENABLE();
+
+    hdma_adc.Instance                 = DMA1_Channel1;
+    hdma_adc.Init.Direction           = DMA_PERIPH_TO_MEMORY;
+    hdma_adc.Init.PeriphInc           = DMA_PINC_DISABLE;     // ADC 数据寄存器固定
+    hdma_adc.Init.MemInc              = DMA_MINC_ENABLE;      // 内存地址递增
+    hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;  // ADC 16位
+    hdma_adc.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;
+    hdma_adc.Init.Mode                = DMA_CIRCULAR;          // 循环模式
+    hdma_adc.Init.Priority            = DMA_PRIORITY_MEDIUM;
+    HAL_DMA_Init(&hdma_adc);
+
+    // 连接 DMA 到 ADC
+    __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc);
+    HAL_DMA_Start(&hdma_adc, (uint32_t)&ADC1->DR, (uint32_t)AD_Value, 4);
+    HAL_ADC_Start_DMA(&hadc, (uint32_t *)AD_Value, 4);
+}
+```
+
 ### AD.c(DMA 版本)
 
 ```c

+ 53 - 1
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/12-PWR电源管理.md

@@ -46,6 +46,13 @@ CPU 时钟关闭,外设继续运行。任何中断均可唤醒。
 __WFI();  // 执行 WFI 指令进入睡眠,中断唤醒后继续执行后续代码
 ```
 
+#### 睡眠模式 HAL
+
+```c
+HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
+// 唤醒后从此处继续执行
+```
+
 示例(睡眠模式 + 串口接收唤醒):
 
 ```c
@@ -73,6 +80,13 @@ int main(void)
 
 CPU 停止,所有外设时钟可以关闭(由寄存器配置决定),SRAM 和寄存器内容保持。唤醒后需要重新配置系统时钟(`SystemInit()`)。
 
+#### 停止模式 HAL
+
+```c
+HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
+SystemInit();  // 唤醒后重新配置时钟
+```
+
 ```c
 // 必须先开启 PWR 时钟
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
@@ -123,6 +137,31 @@ PWR_WakeUpPinCmd(ENABLE);  // 使能 WKUP 引脚
 PWR_EnterSTANDBYMode();    // 进入待机模式
 ```
 
+#### 待机模式 HAL
+
+```c
+HAL_PWR_EnterSTANDBYMode();
+```
+
+#### 唤醒配置 HAL(EXTI 上升沿唤醒)
+
+```c
+GPIO_InitTypeDef GPIO_InitStruct = {0};
+__HAL_RCC_GPIOA_CLK_ENABLE();
+
+GPIO_InitStruct.Pin   = GPIO_PIN_0;           // PA0 = WKUP
+GPIO_InitStruct.Mode  = GPIO_MODE_IT_FALLING; // 下降沿中断(或上升沿)
+GPIO_InitStruct.Pull  = GPIO_NOPULL;
+HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
+HAL_NVIC_EnableIRQ(EXTI0_IRQn);
+
+// 进入待机前清除 WU 标志
+__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
+HAL_PWR_EnterSTANDBYMode();
+```
+
 示例(待机模式 + RTC 闹钟唤醒):
 
 ```c
@@ -159,9 +198,22 @@ int main(void)
 }
 ```
 
+### PVD(可编程电压检测器)HAL
+
+PVD 监控 VDD 电压,低于阈值时可产生中断(提前保存数据)。
+
+```c
+PWR_PVDTypeDef sConfigPVD = {0};
+sConfigPVD.PVDLevel = PWR_PVDLEVEL_7;      // 2.9V 阈值
+sConfigPVD.Mode      = PWR_PVD_MODE_IT_RISING_FALLING;
+HAL_PWR_ConfigPVD(&sConfigPVD);
+HAL_PWR_EnablePVD();
+```
+
 ### 关键注意事项
 
 1. **停止模式和待机模式必须开启 PWR 时钟**:`RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);`
 2. **停止模式唤醒后必须重新配置时钟**:调用 `SystemInit()` 恢复系统时钟到 72MHz
 3. **待机模式唤醒后程序从头执行**:`main()` 中的所有初始化代码都会重新执行
-4. 进入低功耗模式前,建议关闭不必要的外设时钟以进一步降低功耗
+4. **待机模式唤醒后清除 WU 标志**:`__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);`
+5. 进入低功耗模式前,建议关闭不必要的外设时钟以进一步降低功耗

+ 121 - 2
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/13-BKP与RTC实时时钟.md

@@ -72,6 +72,29 @@ HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, data);
 data = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
 ```
 
+### 2.5 完整 HAL BKP 访问示例
+
+```c
+RTC_HandleTypeDef hrtc;      // BKP 寄存器属于 RTC 备份域,需通过 RTC 句柄访问
+
+void BKP_Test_HAL(void)
+{
+    // 1. 使能 PWR + BKP 时钟
+    __HAL_RCC_PWR_CLK_ENABLE();
+    __HAL_RCC_BKP_CLK_ENABLE();
+
+    // 2. 使能备份域访问
+    HAL_PWR_EnableBkUpAccess();
+
+    // 3. 写入 / 读取 BKP 寄存器(DR1 ~ DR10,各 16 位)
+    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A5);
+    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, 1234);
+
+    uint32_t val1 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
+    uint32_t val2 = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
+}
+```
+
 ## 3 RTC(Real-Time Clock)——实时时钟
 
 ![RTC 模块框图](assets/rtc_block.png)
@@ -211,7 +234,76 @@ while (1)
 }
 ```
 
-### 3.5 LSE 无法起振的替代方案
+### 3.5 完整 HAL RTC 初始化(含 MspInit)
+
+```c
+RTC_HandleTypeDef hrtc;
+
+void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
+{
+    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
+
+    __HAL_RCC_PWR_CLK_ENABLE();
+    __HAL_RCC_BKP_CLK_ENABLE();
+    HAL_PWR_EnableBkUpAccess();
+
+    // LSE 配置
+    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
+    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE;
+    RCC_OscInitStruct.LSEState       = RCC_LSE_ON;
+    HAL_RCC_OscConfig(&RCC_OscInitStruct);
+
+    // 选择 LSE 作为 RTC 时钟源
+    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
+    PeriphClkInitStruct.RTCClockSelection    = RCC_RTCCLKSOURCE_LSE;
+    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
+
+    __HAL_RCC_RTC_ENABLE();
+}
+
+void MyRTC_Init_HAL(void)
+{
+    // 首次初始化判断(读 BKP 标志)
+    HAL_PWR_EnableBkUpAccess();
+    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0xA5A5)
+    {
+        hrtc.Instance = RTC;
+        hrtc.Init.HourFormat     = RTC_HOURFORMAT_24;
+        hrtc.Init.AsynchPrediv   = 127;       // 异步分频
+        hrtc.Init.SynchPrediv    = 255;       // 同步分频 → 32768/(127+1)/(255+1) = 1Hz
+        hrtc.Init.OutPut         = RTC_OUTPUT_DISABLE;
+        hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
+        hrtc.Init.OutPutType     = RTC_OUTPUT_TYPE_OPENDRAIN;
+        HAL_RTC_Init(&hrtc);
+
+        // 设置初始时间
+        RTC_TimeTypeDef sTime = {0};
+        sTime.Hours   = 0;
+        sTime.Minutes = 0;
+        sTime.Seconds = 0;
+        HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
+
+        RTC_DateTypeDef sDate = {0};
+        sDate.Year  = 25;        // 2025
+        sDate.Month = 1;
+        sDate.Date  = 1;
+        HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
+
+        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0xA5A5);  // 标记已初始化
+    }
+    else
+    {
+        HAL_RTC_Init(&hrtc);    // 非首次仅 Init(MspInit 中跳过 LSE 重配)
+    }
+}
+
+// 每秒中断使能
+HAL_RTCEx_SetSecond_IT(&hrtc);
+// 回调:HAL_RTCEx_RTCIRQHandler(&hrtc); → HAL_RTCEx_RTCEventCallback(&hrtc);
+```
+
+### 3.6 LSE 无法起振的替代方案
 
 LSE 晶振可能因 PCB 布局或晶振质量问题无法起振。代码中提供了 LSI 替代方案:
 
@@ -251,7 +343,7 @@ void MyRTC_Init(void)   // LSI 版本
 
 > ⚠ LSI 精度远逊于 LSE(温漂大),且断电后 RTC 暂停(LSI 不由 VBAT 供电)。
 
-### 3.6 HAL 库 RTC 操作
+### 3.7 HAL 库 RTC 操作
 
 ```c
 RTC_TimeTypeDef sTime;
@@ -272,6 +364,33 @@ HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
 HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
 ```
 
+### 3.8 RTC 闹钟(HAL)
+
+```c
+RTC_AlarmTypeDef sAlarm = {0};
+
+sAlarm.AlarmTime.Hours     = 8;
+sAlarm.AlarmTime.Minutes   = 0;
+sAlarm.AlarmTime.Seconds   = 0;
+sAlarm.AlarmTime.SubSeconds = 0;
+sAlarm.AlarmTime.TimeFormat = RTC_HOURFORMAT_24;
+sAlarm.Alarm         = RTC_ALARM_A;
+sAlarm.AlarmMask     = RTC_ALARMMASK_NONE;     // 精确匹配
+sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
+sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
+sAlarm.AlarmDateWeekDay    = 1;
+HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
+
+// 查询闹钟
+HAL_RTC_GetAlarm(&hrtc, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN);
+
+// 回调
+void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
+{
+    // 闹钟触发
+}
+```
+
 ## 4 备份域(Backup Domain)总览
 
 ```

+ 53 - 4
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/14-WDG看门狗.md

@@ -115,6 +115,29 @@ int main(void)
 }
 ```
 
+### 2.6 IWDG HAL 库实现
+
+```c
+IWDG_HandleTypeDef hiwdg;
+
+void IWDG_Init_HAL(void)
+{
+    hiwdg.Instance       = IWDG;
+    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // 40kHz / 64 = 625Hz
+    hiwdg.Init.Reload    = 625;                  // 625 / 625Hz ≈ 1s 超时
+    HAL_IWDG_Init(&hiwdg);
+}
+
+// 喂狗
+// HAL_IWDG_Refresh(&hiwdg);
+
+// 复位来源判断(HAL 中通过 RCC 寄存器读取)
+if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET)
+{
+    __HAL_RCC_CLEAR_RESET_FLAGS();
+}
+```
+
 > 实验现象:正常运行时 "FEED" 每秒闪烁一次;按住按键不放(卡死 2s)→ 系统超时复位 → OLED 显示 "IWDGRST"。
 
 **判定复位来源**:
@@ -122,7 +145,7 @@ int main(void)
 - `RCC_GetFlagStatus(RCC_FLAG_WWDGRST)` → WWDG 复位
 - `RCC_ClearFlag()` → 清除所有复位标志
 
-### 2.6 IWDG 注意事项
+### 2.7 IWDG 注意事项
 
 - 使能后无法关闭(由硬件决定)
 - LSI 精度差(30~60kHz),超时时间要留 50% 余量
@@ -231,7 +254,33 @@ int main(void)
 
 > 喂狗间隔 40ms。窗口值 84 ÷ 超时时间约 50us → 必须在 30~50us 内喂狗,否则复位。这就是 WWDG 的"窗口"约束。
 
-### 3.6 提前唤醒中断(EWI)
+### 3.6 WWDG HAL 库实现
+
+```c
+WWDG_HandleTypeDef hwwdg;
+
+void WWDG_Init_HAL(void)
+{
+    hwwdg.Instance     = WWDG;
+    hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
+    hwwdg.Init.Window    = 0x50;             // 上窗口值
+    hwwdg.Init.Counter   = 0x7F;             // 计数器初始值
+    hwwdg.Init.EWIMode   = WWDG_EWI_ENABLE;  // 使能提前唤醒中断
+    HAL_WWDG_Init(&hwwdg);
+}
+
+// 喂狗
+// HAL_WWDG_Refresh(&hwwdg);
+
+// 提前唤醒中断回调
+void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
+{
+    // 复位前保存关键数据
+    Store_Save();
+}
+```
+
+### 3.7 提前唤醒中断(EWI)
 
 EWI 可在计数器到 0x40 时产生中断,在复位前执行抢救操作(如保存数据、记录日志)。
 
@@ -247,7 +296,7 @@ void WWDG_IRQHandler(void)
 }
 ```
 
-### 3.7 WWDG 与 IWDG 对比
+### 3.8 WWDG 与 IWDG 对比
 
 | 对比项           | IWDG                    | WWDG                         |
 |------------------|-------------------------|------------------------------|
@@ -261,7 +310,7 @@ void WWDG_IRQHandler(void)
 
 > 教师总结:IWDG 适合"慢速保护"——主循环 1 秒内一定要跑完一圈;WWDG 适合"快速精准保护"——某个操作必须在 1 毫秒内完成,否则异常。实际项目中,两个看门狗可以同时使用(IWDG 兜底大循环、WWDG 监控关键任务)。
 
-### 3.8 应用建议
+### 3.9 应用建议
 
 | 场景               | 推荐             | 原因                                        |
 |--------------------|------------------|---------------------------------------------|

+ 89 - 2
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/15-FLASH内部存储器.md

@@ -121,6 +121,30 @@ FLASH_EraseAllPages();          // 擦除所有用户区(包括程序自身!
 FLASH_Lock();
 ```
 
+#### 擦除 HAL 库
+
+```c
+FLASH_EraseInitTypeDef EraseInitStruct = {0};
+uint32_t PageError = 0;
+
+HAL_FLASH_Unlock();
+
+EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;     // 页擦除
+EraseInitStruct.PageAddress = 0x0800FC00;                // 起始地址
+EraseInitStruct.NbPages     = 1;                         // 擦除 1 页
+if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)
+{
+    // 擦除失败,通过 HAL_FLASH_GetError() 获取详细错误
+    uint32_t err = HAL_FLASH_GetError();
+}
+
+// 全擦除
+EraseInitStruct.TypeErase = FLASH_TYPEERASE_MASSERASE;
+HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
+
+HAL_FLASH_Lock();
+```
+
 ### 3.3 写入
 
 擦除后,Flash 全页为 `0xFF`,然后写入数据:
@@ -137,6 +161,23 @@ FLASH_ProgramHalfWord(Address, Data);
 FLASH_Lock();
 ```
 
+#### 写入 HAL 库
+
+```c
+HAL_FLASH_Unlock();
+
+// 按字节写入
+HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE,      Address, Data8);
+
+// 按半字写入
+HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,  Address, Data16);
+
+// 按字写入
+HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,      Address, Data32);
+
+HAL_FLASH_Lock();
+```
+
 > ⚠ 写入前必须保证该地址所在页已擦除(全 `0xFF`),否则写入不可靠。
 
 ### 3.4 完整写入流程
@@ -224,7 +265,53 @@ void Store_Clear(void)
 }
 ```
 
-### 4.3 main.c 主逻辑
+### 4.3 参数存储 HAL 库实现
+
+```c
+// Store.c — HAL 版,使用 HAL_FLASH 函数
+#define STORE_START_ADDRESS   0x0800FC00
+#define STORE_COUNT          512
+
+uint16_t Store_Data[STORE_COUNT];
+
+static void Store_ErasePage_HAL(void)
+{
+    FLASH_EraseInitTypeDef erase = {0};
+    uint32_t PageError = 0;
+    erase.TypeErase   = FLASH_TYPEERASE_PAGES;
+    erase.PageAddress = STORE_START_ADDRESS;
+    erase.NbPages     = 1;
+    HAL_FLASHEx_Erase(&erase, &PageError);
+}
+
+void Store_Init_HAL(void)
+{
+    HAL_FLASH_Unlock();
+    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, STORE_START_ADDRESS, 0xA5A5) != HAL_OK)
+    {
+        // 第一次初始化:擦除后写标志
+        Store_ErasePage_HAL();
+        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, STORE_START_ADDRESS, 0xA5A5);
+        for (uint16_t i = 1; i < STORE_COUNT; i++)
+            HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, STORE_START_ADDRESS + i * 2, 0x0000);
+    }
+    HAL_FLASH_Lock();
+    // 上电加载
+    for (uint16_t i = 0; i < STORE_COUNT; i++)
+        Store_Data[i] = *((__IO uint16_t *)(STORE_START_ADDRESS + i * 2));
+}
+
+void Store_Save_HAL(void)
+{
+    HAL_FLASH_Unlock();
+    Store_ErasePage_HAL();
+    for (uint16_t i = 0; i < STORE_COUNT; i++)
+        HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, STORE_START_ADDRESS + i * 2, Store_Data[i]);
+    HAL_FLASH_Lock();
+}
+```
+
+### 4.4 main.c 主逻辑
 
 ```c
 int main(void)
@@ -266,7 +353,7 @@ int main(void)
 > - 用 **0xA5A5 标志位** 判断是否首次上电——如果首次则格式化。
 > - 使用 **SRAM 缓冲 + Flash 备份** 策略——运行中只操作 SRAM,保存时才写 Flash(Flash 写入很慢且有擦写寿命限制)。
 
-### 4.3 设计要点
+### 4.5 设计要点
 
 | 问题                     | 解决方案                               |
 |--------------------------|----------------------------------------|