소스 검색

raw: 融合尚硅谷内容——新增寄存器代码+CAN+FSMC,17篇笔记全部三层覆盖(寄存器+标准库+HAL)

OpenCode 1 주 전
부모
커밋
87c82ad8a9
17개의 변경된 파일2267개의 추가작업 그리고 160개의 파일을 삭제
  1. 23 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/01-环境搭建与工程模板.md
  2. 105 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/02-GPIO详解.md
  3. 20 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/03-OLED显示与调试.md
  4. 55 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/04-UART串口通信详解.md
  5. 183 77
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/05-I2C通信详解.md
  6. 132 69
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/06-SPI通信详解.md
  7. 38 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/07-中断系统(NVIC+EXTI).md
  8. 28 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/08-时钟系统(RCC).md
  9. 135 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/09-定时器详解.md
  10. 60 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/10-ADC模数转换.md
  11. 81 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/11-DMA数据传输.md
  12. 26 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/12-PWR电源管理.md
  13. 78 14
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/13-BKP与RTC实时时钟.md
  14. 18 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/14-WDG看门狗.md
  15. 18 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/15-FLASH内部存储器.md
  16. 623 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/16-CAN通信.md
  17. 644 0
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/17-FSMC与LCD显示.md

+ 23 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/01-环境搭建与工程模板.md

@@ -413,3 +413,26 @@ GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1); // 高电平
 - 本文基于:铁头山羊 HAL 库教程、江协科技标准库教程、STM32 HAL库入门教程
 - 相关 wiki:[[STM32-GPIO]]、[[STM32-环境搭建]]、[[STM32-HAL库笔记]]、[[STM32-标准库笔记]]
 - 下一步:[[STM32-时钟系统]] → GPIO 实战 → OLED 显示 → 中断系统
+
+---
+
+### 尚硅谷工程模板结构
+
+尚硅谷课程采用三目录结构(与江协的五目录相比更精简):
+
+```
+Project/
+├── Hardware/       # 硬件驱动:LED、Key、LCD 等
+├── Start/          # 启动文件、CMSIS 内核文件、system_stm32f10x
+└── User/           # main.c、stm32f10x_it.c、stm32f10x_conf.h
+```
+
+**三种开发方法对比**:
+
+| 方法 | 描述 | 特点 |
+|------|------|------|
+| 寄存器 | 直接操作 `GPIOx->ODR`、`ADC1->DR` 等 | 代码最小,需对照手册 |
+| 标准库 | 调用 `GPIO_Init()`、`ADC_RegularChannelConfig()` 等 | 函数封装,易读 |
+| HAL库 | 使用 CubeMX 生成 + `HAL_ADC_Start()` 等 | 开发最快,代码最大 |
+
+尚硅谷课程以 **寄存器操作** 为主线(深入理解底层),辅以标准库对比。

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

@@ -220,6 +220,111 @@ CNF[1:0] 配置模式类别,MODE[1:0] 配置速度:
 
 ---
 
+### 尚硅谷风格:寄存器代码实战
+
+尚硅谷教程采用"三步进化法"从底层寄存器逐步过渡到可读性强的宏定义写法:
+
+~~~c title="寄存器 - 尚硅谷风格"
+// 第1步:直接地址访问(最原始)
+// *(uint32_t *)(0x40021000 + 0x18) = 4;   // RCC->APB2ENR
+
+// 第2步:结构体封装(读性好一点)
+// RCC->APB2ENR = 4;
+
+// 第3步:位操作 + 宏定义(最终采用)
+RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+
+// GPIO 配置
+GPIOA->CRL &= ~GPIO_CRL_CNF0;   // 清除 CNF 位
+GPIOA->CRL |= GPIO_CRL_MODE0;   // 设置 MODE 位(输出 50MHz)
+
+// GPIO 输出
+GPIOA->ODR &= ~GPIO_ODR_ODR0;   // 点亮 LED(PA0 低电平)
+GPIOA->ODR |= GPIO_ODR_ODR0;    // 熄灭 LED(PA0 高电平)
+
+// SysTick 延时
+void Delay_us(uint16_t us) {
+    SysTick->LOAD = 72 * us;
+    SysTick->CTRL |= 0x05;
+    while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG) == 0) {}
+    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
+}
+~~~
+
+完整 LED 流水灯寄存器代码:
+
+~~~c title="寄存器 - 尚硅谷风格"
+#include "stm32f10x.h"
+
+void Delay_us(uint16_t us) {
+    SysTick->LOAD = 72 * us;
+    SysTick->CTRL |= 0x05;
+    while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG) == 0) {}
+    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
+}
+
+void Delay_ms(uint16_t ms) {
+    while (ms--) Delay_us(1000);
+}
+
+int main(void)
+{
+    // RCC 时钟使能 — GPIOA
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+
+    // PA0~PA7 推挽输出 50MHz
+    GPIOA->CRL = 0x33333333;
+
+    // 流水灯
+    while (1) {
+        GPIOA->ODR = ~0x01;  Delay_ms(100);
+        GPIOA->ODR = ~0x02;  Delay_ms(100);
+        GPIOA->ODR = ~0x04;  Delay_ms(100);
+        GPIOA->ODR = ~0x08;  Delay_ms(100);
+        GPIOA->ODR = ~0x10;  Delay_ms(100);
+        GPIOA->ODR = ~0x20;  Delay_ms(100);
+        GPIOA->ODR = ~0x40;  Delay_ms(100);
+        GPIOA->ODR = ~0x80;  Delay_ms(100);
+    }
+}
+~~~
+
+带按键中断的完整寄存器代码:
+
+~~~c title="寄存器 - 尚硅谷风格"
+#include "stm32f10x.h"
+
+void Key_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
+    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
+
+    GPIOF->CRH &= ~GPIO_CRH_MODE10;
+    GPIOF->CRH |= GPIO_CRH_CNF10_1;
+    GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
+    GPIOF->ODR &= ~GPIO_ODR_ODR10;
+
+    // EXTI 配置
+    AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
+    EXTI->RTSR |= EXTI_RTSR_TR10;
+    EXTI->IMR |= EXTI_IMR_MR10;
+
+    // NVIC 配置
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(EXTI15_10_IRQn, 3);
+    NVIC_EnableIRQ(EXTI15_10_IRQn);
+}
+
+void EXTI15_10_IRQHandler(void) {
+    EXTI->PR |= EXTI_PR_PR10;
+    Delay_ms(10);
+    if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0) {
+        GPIOA->ODR ^= GPIO_ODR_ODR1;
+    }
+}
+~~~
+
+---
+
 ## 4. LED 驱动详解
 
 ### 4.1 硬件连接

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

@@ -561,3 +561,23 @@ int main(void)
 ```
 
 屏幕坐标系:左上角为原点 (0,0),向右 X 轴 0~127,向下 Y 轴 0~63。驱动将屏幕划分为 4 行 16 列的逻辑网格,便于定位字符显示位置。
+
+---
+
+### 尚硅谷:软件I2C驱动参考
+
+尚硅谷课程不使用 OLED(使用 LCD 1602/12864),但其 **软件 I2C 驱动** 实现与 OLED 的 GPIO 模拟 I2C 方式完全一致。核心结构:
+
+```c
+// I2C 位操作宏(尚硅谷风格)
+#define I2C_SCL_H()    GPIOB->BSRR = GPIO_BSRR_BS8
+#define I2C_SCL_L()    GPIOB->BRR  = GPIO_BRR_BR8
+#define I2C_SDA_H()    GPIOB->BSRR = GPIO_BSRR_BS9
+#define I2C_SDA_L()    GPIOB->BRR  = GPIO_BRR_BR9
+#define I2C_SDA_R()    (GPIOB->IDR & GPIO_IDR_IDR9)
+
+// 起始 / 停止 / 发送字节 / 接收字节
+// 时序与 OLED 中 OLED_I2C_Start/Stop/SendByte 完全相同
+```
+
+详见 I2C 章节的寄存器级位操作实现。

+ 55 - 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);
 ```
 
+### 寄存器:USART驱动(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void USART_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
+
+    // PA9: TX - 复用推挽输出 CNF-10, MODE-11
+    GPIOA->CRH |= GPIO_CRH_MODE9;
+    GPIOA->CRH |= GPIO_CRH_CNF9_1;  GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
+    // PA10: RX - 浮空输入 CNF-01, MODE-00
+    GPIOA->CRH &= ~GPIO_CRH_MODE10;
+    GPIOA->CRH &= ~GPIO_CRH_CNF10_1;  GPIOA->CRH |= GPIO_CRH_CNF10_0;
+
+    USART1->BRR = 0x271;  // 72MHz/62500=115200
+    USART1->CR1 |= USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
+    USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PCE);  // 8N1
+    USART1->CR2 &= ~USART_CR2_STOP;  // 1 stop bit
+}
+
+void USART_SendChar(uint8_t ch) {
+    while ((USART1->SR & USART_SR_TXE) == 0) {}
+    USART1->DR = ch;
+}
+
+uint8_t USART_ReceiveChar(void) {
+    while ((USART1->SR & USART_SR_RXNE) == 0) {
+        if (USART1->SR & USART_SR_IDLE) return 0;
+    }
+    return USART1->DR;
+}
+~~~
+
+~~~c title="寄存器 - 尚硅谷风格(中断接收 + IDLE帧尾检测)"
+uint8_t buffer[256];
+uint16_t size = 0;
+uint8_t isOver = 0;
+
+void USART1_IRQHandler(void) {
+    if (USART1->SR & USART_SR_RXNE) {
+        buffer[size++] = USART1->DR;
+    } else if (USART1->SR & USART_SR_IDLE) {
+        USART1->DR;  // 读DR清除IDLE标志
+        isOver = 1;
+    }
+}
+~~~
+
+~~~c title="寄存器 - printf重定向"
+int fputc(int ch, FILE *f) {
+    USART_SendChar((uint8_t)ch);
+    return ch;
+}
+~~~
+
 ### Serial_Init 串口初始化(发送模式)
 
 **标准库:**

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

@@ -24,6 +24,7 @@ I2C 的 SCL 和 SDA 都采用**开漏输出**(Open-Drain),必须外接**
 *上图:I2C 总线硬件连接(开漏输出 + 上拉电阻)(来源:STM32入门教程 PPT 第128页)*
 
 对比 UART:
+
 - **UART**:点对点通信,推挽输出,一对一连接
 - **I2C**:总线式通信,开漏输出,一对多连接(一主多从)
 
@@ -72,12 +73,12 @@ SDA  ────┘
 
 ### 速度模式
 
-| 模式 | 最大波特率 |
-|------|-----------|
-| 标准模式 (SM) | 100 kbit/s |
-| 快速模式 (FM) | 400 kbit/s |
-| 快速增强模式 (FM+) | 1 Mbit/s |
-| 高速模式 (HS) | 3.4 Mbit/s |
+| 模式               | 最大波特率 |
+| ------------------ | ---------- |
+| 标准模式 (SM)      | 100 kbit/s |
+| 快速模式 (FM)      | 400 kbit/s |
+| 快速增强模式 (FM+) | 1 Mbit/s   |
+| 高速模式 (HS)      | 3.4 Mbit/s |
 
 STM32F103 的 I2C 外设支持标准模式和快速模式。
 
@@ -97,14 +98,15 @@ STM32F103 的 I2C 外设支持标准模式和快速模式。
 
 以 I2C1 为例:
 
-| 默认引脚 | 重映射后 |
-|---------|---------|
+| 默认引脚  | 重映射后  |
+| --------- | --------- |
 | PB6 (SCL) | PB8 (SCL) |
 | PB7 (SDA) | PB9 (SDA) |
 
 引脚模式:**复用开漏输出**(GPIO_Mode_AF_OD)
 
 为什么用复用 + 开漏?
+
 - **复用**:因为由 I2C 模块内部硬件控制引脚,不是软件 GPIO 控制
 - **开漏**:为了实现线与逻辑
 
@@ -159,27 +161,27 @@ I2C_Cmd(I2C1, ENABLE);   // 闭合总开关
 void MyI2C_Init(void) {
     // 1. 开启 GPIOB 时钟
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
-    
+  
     // 2. 开启 AFIO 时钟(重映射需要)
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
-    
+  
     // 3. 重映射 I2C1 到 PB8/PB9
     GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
-    
+  
     // 4. 初始化 PB8(SCL) 和 PB9(SDA) 为复用开漏输出
     GPIO_InitTypeDef GPIO_InitStructure;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
     GPIO_Init(GPIOB, &GPIO_InitStructure);
-    
+  
     // 5. 开启 I2C1 时钟
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
-    
+  
     // 6. 复位 I2C1
     RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, ENABLE);
     RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1, DISABLE);
-    
+  
     // 7. 初始化 I2C1
     I2C_InitTypeDef I2C_InitStructure;
     I2C_InitStructure.I2C_ClockSpeed = 400000;
@@ -189,7 +191,7 @@ void MyI2C_Init(void) {
     I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
     I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
     I2C_Init(I2C1, &I2C_InitStructure);
-    
+  
     // 8. 使能 I2C1
     I2C_Cmd(I2C1, ENABLE);
 }
@@ -201,7 +203,7 @@ void MyI2C_Init(void) {
 
 使用 STM32CubeMX 生成的 HAL 库代码,I2C 初始化通过 `I2C_HandleTypeDef` 结构体完成:
 
-~~~c
+```c
 /* HAL库 — I2C 句柄定义与初始化 */
 I2C_HandleTypeDef hi2c1;
 
@@ -234,7 +236,7 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
         HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
     }
 }
-~~~
+```
 
 关键区别:
 
@@ -260,15 +262,15 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
 
 ### 关键标志位
 
-| 标志位 | 含义 |
-|--------|------|
-| **BUSY** (SR2) | 总线忙标志,=1 时总线正在通信 |
-| **SB** (SR1) | 起始位已发送 |
-| **ADDR** (SR1) | 地址已发送且收到 ACK(寻址成功) |
-| **AF** (SR1) | 应答失败(NACK received) |
-| **TXE** (SR1) | 发送数据寄存器空,可写入下一个数据 |
-| **BTF** (SR1) | Byte Transfer Finished,字节传输完成(移位寄存器和 DR 均空) |
-| **RXNE** (SR1) | 接收数据寄存器非空,有数据可读 |
+| 标志位               | 含义                                                         |
+| -------------------- | ------------------------------------------------------------ |
+| **BUSY** (SR2) | 总线忙标志,=1 时总线正在通信                                |
+| **SB** (SR1)   | 起始位已发送                                                 |
+| **ADDR** (SR1) | 地址已发送且收到 ACK(寻址成功)                             |
+| **AF** (SR1)   | 应答失败(NACK received)                                    |
+| **TXE** (SR1)  | 发送数据寄存器空,可写入下一个数据                           |
+| **BTF** (SR1)  | Byte Transfer Finished,字节传输完成(移位寄存器和 DR 均空) |
+| **RXNE** (SR1) | 接收数据寄存器非空,有数据可读                               |
 
 ### 写数据完整流程
 
@@ -284,15 +286,15 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
 int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Size) {
     // === 阶段一:等待总线空闲 ===
     while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
-    
+  
     // === 阶段二:发送起始位 ===
     I2C_GenerateSTART(I2Cx, ENABLE);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));  // 等待起始位发送完成
-    
+  
     // === 阶段三:寻址阶段 ===
     I2C_ClearFlag(I2Cx, I2C_FLAG_AF);               // 先清除 AF 标志位
     I2C_SendData(I2Cx, Addr & 0xFE);                 // 地址 + R/W=0(写)
-    
+  
     // 等待寻址结果
     while (1) {
         if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR))     // 寻址成功
@@ -302,11 +304,11 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
             return 1;
         }
     }
-    
+  
     // 清除 ADDR 标志位(必须:先读 SR1,再读 SR2)
     I2C_ReadRegister(I2Cx, I2C_Register_SR1);
     I2C_ReadRegister(I2Cx, I2C_Register_SR2);
-    
+  
     // === 阶段四:发送数据 ===
     for (uint16_t i = 0; i < Size; i++) {
         // 等待可以发送下一个字节
@@ -320,7 +322,7 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
         }
         I2C_SendData(I2Cx, pData[i]);                // 写入发送数据寄存器
     }
-    
+  
     // 等待最后一个字节发送完成
     while (1) {
         if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_AF)) {
@@ -330,7 +332,7 @@ int MyI2C_SendBytes(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t *pData, uint16_t Si
         if (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF))   // 整个字节传输完成
             break;
     }
-    
+  
     // === 阶段五:发送停止位 ===
     I2C_GenerateSTOP(I2Cx, ENABLE);
     return 0;   // 成功
@@ -351,9 +353,9 @@ uint8_t Command[] = {0x00, 0x8D, 0x14, 0xAF, 0xA5};  // OLED 开启命令
 
 int main(void) {
     OLED_Init();    // 内部调用 MyI2C_Init()
-    
+  
     MyI2C_SendBytes(I2C1, 0x78, Command, 5);
-    
+  
     // OLED 屏幕点亮
     while (1);
 }
@@ -365,7 +367,7 @@ OLED 地址是 0x78,这里传的是已经左对齐的地址(最低位为 R/W
 
 HAL 库将 I2C 写操作封装为两个高级函数,无需手动操作 EV5/EV6/EV7 事件标志位:
 
-~~~c
+```c
 /* HAL库 — I2C 主机发送(直接发送数据,无寄存器地址) */
 // 原型:HAL_I2C_Master_Transmit(&hi2c1, DevAddress, pData, Size, Timeout)
 // DevAddress:7位从机地址左移1位(即含R/W位的8位地址)
@@ -401,9 +403,10 @@ HAL_StatusTypeDef ret = HAL_I2C_Mem_Write(
 /* 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
@@ -425,51 +428,51 @@ HAL 库内部自动处理了:
 ```c
 uint8_t MyI2C_ReadByte(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t RegAddr) {
     uint8_t Data;
-    
+  
     // 阶段 1-2:起始位
     while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY));
     I2C_GenerateSTART(I2Cx, ENABLE);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
-    
+  
     // 阶段 3:写从机地址(写方向)—— 先写寄存器地址
     I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
     I2C_SendData(I2Cx, Addr & 0xFE);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
     I2C_ReadRegister(I2Cx, I2C_Register_SR1);
     I2C_ReadRegister(I2Cx, I2C_Register_SR2);
-    
+  
     // 发送寄存器地址
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_TXE));
     I2C_SendData(I2Cx, RegAddr);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_BTF));
-    
+  
     // 阶段 4:重复起始位 —— 切换方向
     I2C_GenerateSTART(I2Cx, ENABLE);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_SB));
-    
+  
     // 阶段 5:写从机地址(读方向)
     I2C_ClearFlag(I2Cx, I2C_FLAG_AF);
     I2C_SendData(I2Cx, Addr | 0x01);
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_ADDR));
     I2C_ReadRegister(I2Cx, I2C_Register_SR1);
     I2C_ReadRegister(I2Cx, I2C_Register_SR2);
-    
+  
     // 阶段 6:接收数据 + 发 NACK
     I2C_AcknowledgeConfig(I2Cx, DISABLE);   // 不发 ACK
     while (!I2C_GetFlagStatus(I2Cx, I2C_FLAG_RXNE));
     Data = I2C_ReceiveData(I2Cx);
-    
+  
     // 阶段 7:停止位
     I2C_GenerateSTOP(I2Cx, ENABLE);
     I2C_AcknowledgeConfig(I2Cx, ENABLE);    // 恢复 ACK
-    
+  
     return Data;
 }
 ```
 
 ### HAL库读操作
 
-~~~c
+```c
 /* HAL库 — I2C 主机接收(直接读取从机数据,无寄存器地址) */
 // 原型:HAL_I2C_Master_Receive(&hi2c1, DevAddress, pData, Size, Timeout)
 uint8_t rxBuffer[6];
@@ -507,7 +510,7 @@ 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 发送逻辑。
 
@@ -621,20 +624,20 @@ void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data) {
 // 读寄存器
 uint8_t MPU6050_ReadReg(uint8_t RegAddress) {
     uint8_t Data;
-    
+  
     MyI2C_Start();
     MyI2C_SendByte(MPU6050_ADDRESS);   // 写方向
     MyI2C_ReceiveAck();
     MyI2C_SendByte(RegAddress);        // 指定寄存器地址
     MyI2C_ReceiveAck();
-    
+  
     MyI2C_Start();                     // 重复起始位
     MyI2C_SendByte(MPU6050_ADDRESS | 0x01);  // 读方向
     MyI2C_ReceiveAck();
     Data = MyI2C_ReceiveByte();
     MyI2C_SendAck(1);                  // 发 NACK
     MyI2C_Stop();
-    
+  
     return Data;
 }
 
@@ -659,38 +662,138 @@ void MPU6050_Init(void) {
 void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
                      int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) {
     uint8_t DataH, DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x3B);   // ACCEL_XOUT_H
     DataL = MPU6050_ReadReg(0x3C);
     *AccX = (DataH << 8) | DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x3D);   // ACCEL_YOUT_H
     DataL = MPU6050_ReadReg(0x3E);
     *AccY = (DataH << 8) | DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x3F);   // ACCEL_ZOUT_H
     DataL = MPU6050_ReadReg(0x40);
     *AccZ = (DataH << 8) | DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x43);   // GYRO_XOUT_H
     DataL = MPU6050_ReadReg(0x44);
     *GyroX = (DataH << 8) | DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x45);   // GYRO_YOUT_H
     DataL = MPU6050_ReadReg(0x46);
     *GyroY = (DataH << 8) | DataL;
-    
+  
     DataH = MPU6050_ReadReg(0x47);   // GYRO_ZOUT_H
     DataL = MPU6050_ReadReg(0x48);
     *GyroZ = (DataH << 8) | DataL;
 }
-```
+ ```
+
+### 寄存器:软件模拟I2C(尚硅谷风格)
+
+~~~c
+#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
+#define SCL_LOW  (GPIOB->ODR &= ~GPIO_ODR_ODR10)
+#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
+#define SDA_LOW  (GPIOB->ODR &= ~GPIO_ODR_ODR11)
+#define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
+#define I2C_DELAY Delay_us(10)
+
+void I2C_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    GPIOB->CRH |= GPIO_CRH_MODE10 | GPIO_CRH_MODE11;
+    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
+    GPIOB->CRH |= GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0;
+}
+
+void I2C_Start(void) {
+    SCL_HIGH; SDA_HIGH; I2C_DELAY;
+    SDA_LOW;  I2C_DELAY;
+}
+
+void I2C_Stop(void) {
+    SCL_HIGH; SDA_LOW;  I2C_DELAY;
+    SDA_HIGH; I2C_DELAY;
+}
+
+uint8_t I2C_Wait4Ack(void) {
+    SDA_HIGH; SCL_LOW;  I2C_DELAY;
+    SCL_HIGH; I2C_DELAY;
+    uint8_t ack = READ_SDA;
+    SCL_LOW;  I2C_DELAY;
+    return ack ? NACK : ACK;
+}
+
+void I2C_SendByte(uint8_t byte) {
+    for (uint8_t i = 0; i < 8; i++) {
+        SCL_LOW; I2C_DELAY;
+        if (byte & 0x80) SDA_HIGH; else SDA_LOW;
+        I2C_DELAY;
+        SCL_HIGH; I2C_DELAY;
+        SCL_LOW;  I2C_DELAY;
+        byte <<= 1;
+    }
+}
+
+uint8_t I2C_ReadByte(void) {
+    uint8_t data = 0;
+    for (uint8_t i = 0; i < 8; i++) {
+        SDA_HIGH; SCL_LOW;  I2C_DELAY;
+        SCL_HIGH; I2C_DELAY;
+        data <<= 1;
+        if (READ_SDA) data |= 0x01;
+        SCL_LOW;  I2C_DELAY;
+    }
+    return data;
+}
+~~~
+
+### 寄存器:硬件I2C外设(尚硅谷风格)
+
+~~~c
+void I2C_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
+    GPIOB->CRH |= GPIO_CRH_MODE10 | GPIO_CRH_MODE11 | GPIO_CRH_CNF10 | GPIO_CRH_CNF11;
+
+    I2C2->CR1 &= ~I2C_CR1_SMBUS;
+    I2C2->CCR &= ~I2C_CCR_FS;
+    I2C2->CR2 |= 36;
+    I2C2->CCR |= 180;
+    I2C2->TRISE |= 37;
+    I2C2->CR1 |= I2C_CR1_PE;
+}
+
+uint8_t I2C_Start(void) {
+    I2C2->CR1 |= I2C_CR1_START;
+    uint16_t timeout = 0xffff;
+    while ((I2C2->SR1 & I2C_SR1_SB) == 0 && timeout--) {}
+    return timeout ? OK : FAIL;
+}
+
+uint8_t I2C_SendAddr(uint8_t addr) {
+    I2C2->DR = addr;
+    uint16_t timeout = 0xffff;
+    while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout--) {}
+    if (timeout > 0) I2C2->SR2;
+    return timeout ? OK : FAIL;
+}
+
+uint8_t I2C_SendByte(uint8_t byte) {
+    uint16_t timeout = 0xffff;
+    while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout--) {}
+    I2C2->DR = byte;
+    timeout = 0xffff;
+    while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout--) {}
+    return timeout ? OK : FAIL;
+}
+~~~
 
 ### 硬件 I2C(标准库 — EV5/EV6/EV7 事件驱动)
 
 如果使用 STM32 硬件 I2C 外设 + 标准库,MPU6050 驱动通过 I2C 事件标志位控制时序(EV5 = SB起始已发送,EV6 = ADDR寻址成功,EV7 = RXNE数据可读):
 
-~~~c
+```c
 /* 标准库 — 硬件 I2C MPU6050 驱动(基于 I2C2,PB10/PB11) */
 
 #define MPU6050_ADDRESS     0xD0
@@ -785,24 +888,24 @@ void MPU6050_Init(void)
     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:收到一个字节,数据可读 |
+| 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
+```c
 /* HAL库 — MPU6050 驱动(基于 HAL_I2C_Mem_Write/Read) */
 
 #define MPU6050_ADDR_7BIT   0x68      // 7位原始地址(不含R/W位)
@@ -846,9 +949,10 @@ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
     *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)
@@ -857,14 +961,14 @@ void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
 
 ## 6. 硬件 I2C vs 软件 I2C
 
-| 对比 | 硬件 I2C | 软件 I2C |
-|------|---------|---------|
-| CPU 占用 | 低(硬件控制时序) | 高(CPU 逐位模拟) |
-| 引脚灵活性 | 需指定引脚 | 任意 GPIO |
-| 速度 | 可达 400kHz | 取决于延时精度 |
-| 抗干扰 | 好(硬件滤波) | 差 |
-| 编程复杂度 | 较高(寄存器/标志位多) | 简单直观 |
-| 适用场景 | 高速、复杂系统 | 低速、原型验证、引脚不足 |
+| 对比       | 硬件 I2C                | 软件 I2C                 |
+| ---------- | ----------------------- | ------------------------ |
+| CPU 占用   | 低(硬件控制时序)      | 高(CPU 逐位模拟)       |
+| 引脚灵活性 | 需指定引脚              | 任意 GPIO                |
+| 速度       | 可达 400kHz             | 取决于延时精度           |
+| 抗干扰     | 好(硬件滤波)          | 差                       |
+| 编程复杂度 | 较高(寄存器/标志位多) | 简单直观                 |
+| 适用场景   | 高速、复杂系统          | 低速、原型验证、引脚不足 |
 
 ---
 
@@ -886,6 +990,7 @@ I2C 总线上必须接上拉电阻。如果未接或阻值过大,SCL/SDA 波
 ### 地址冲突
 
 多个 I2C 设备使用相同地址会导致冲突。MPU6050 的 AD0 引脚可改变地址:
+
 - AD0 = 0:地址 0x68
 - AD0 = 1:地址 0x69
 
@@ -903,6 +1008,7 @@ I2C 总线上必须接上拉电阻。如果未接或阻值过大,SCL/SDA 波
 ---
 
 > 本笔记综合整理自三套 STM32 教程:
+>
 > 1. [STM32 HAL库] I2C 基础知识 / 数据收发实验
 > 2. 铁头山羊 I2C 系列(4.1~4.8)
 > 3. 江协科技 I2C 系列(10-1~10-5)

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

@@ -6,21 +6,21 @@
 
 SPI(Serial Peripheral Interface)是一种**同步、全双工**的串行通信总线,使用 4 根线:
 
-| 信号 | 全称 | 方向(主机→从机) |
-|------|------|------------------|
-| **SCLK** | Serial Clock | 主机→所有从机 |
-| **MOSI** | Master Out Slave In | 主机→从机 |
-| **MISO** | Master In Slave Out | 从机→主机 |
-| **NSS** | Negative Slave Select | 主机→指定从机(低电平有效) |
+| 信号     | 全称                  | 方向(主机→从机)           |
+| -------- | --------------------- | --------------------------- |
+| **SCLK** | Serial Clock          | 主机→所有从机               |
+| **MOSI** | Master Out Slave In   | 主机→从机                   |
+| **MISO** | Master In Slave Out   | 从机→主机                   |
+| **NSS**  | Negative Slave Select | 主机→指定从机(低电平有效) |
 
 注意不同芯片的引脚命名可能不同。例如 W25Q64 Flash 模块的标注:
 
-| Flash 模块引脚 | 对应 SPI 信号 |
-|---------------|-------------|
-| DI (Data Input) | MOSI |
-| DO (Data Output) | MISO |
-| CLK (Clock) | SCLK |
-| CS (Chip Select) | NSS |
+| Flash 模块引脚   | 对应 SPI 信号 |
+| ---------------- | ------------- |
+| DI (Data Input)  | MOSI          |
+| DO (Data Output) | MISO          |
+| CLK (Clock)      | SCLK          |
+| CS (Chip Select) | NSS           |
 
 ### 一主多从结构
 
@@ -62,12 +62,12 @@ SPI 的**全双工**特性:每发送一个字节的同时也会收到一个字
 
 SPI 有 4 种模式,由 **CPOL**(Clock Polarity,时钟极性)和 **CPHA**(Clock Phase,时钟相位)决定:
 
-| 模式 | CPOL | CPHA | 空闲时 SCLK | 数据采样边沿 |
-|------|------|------|------------|-------------|
-| 模式 0 | 0 | 0 | 低电平 | **上升沿**(第一边沿) |
-| 模式 1 | 0 | 1 | 低电平 | **下降沿**(第二边沿) |
-| 模式 2 | 1 | 0 | 高电平 | **下降沿**(第一边沿) |
-| 模式 3 | 1 | 1 | 高电平 | **上升沿**(第二边沿) |
+| 模式   | CPOL | CPHA | 空闲时 SCLK | 数据采样边沿           |
+| ------ | ---- | ---- | ----------- | ---------------------- |
+| 模式 0 | 0    | 0    | 低电平      | **上升沿**(第一边沿) |
+| 模式 1 | 0    | 1    | 低电平      | **下降沿**(第二边沿) |
+| 模式 2 | 1    | 0    | 高电平      | **下降沿**(第一边沿) |
+| 模式 3 | 1    | 1    | 高电平      | **上升沿**(第二边沿) |
 
 **CPOL = 0**:空闲时 SCLK 为低电平
 **CPOL = 1**:空闲时 SCLK 为高电平
@@ -83,6 +83,7 @@ SPI 有 4 种模式,由 **CPOL**(Clock Polarity,时钟极性)和 **CPHA*
 ### MSB First 与 LSB First
 
 数据位传输顺序有两种:
+
 - **MSB First**:先传最高位(D7→D0),最常用
 - **LSB First**:先传最低位(D0→D7)
 
@@ -124,6 +125,7 @@ SPI1 挂在 **APB2** 总线上(最高 72MHz,默认 8MHz):
 ```
 
 默认 PCLK2 = 8MHz:
+
 - 2 分频 → 4MHz
 - 4 分频 → 2MHz
 - **8 分频 → 1MHz**(推荐,稳定)
@@ -156,26 +158,26 @@ void SPI_Init(void) {
     // 1. 开启时钟
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
-    
+  
     // 2. 初始化 MOSI (PA7)、SCK (PA5) 为复用推挽输出
     GPIO_InitTypeDef GPIO_InitStructure;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStructure);
-    
+  
     // 3. 初始化 MISO (PA6) 为上拉输入
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
     GPIO_Init(GPIOA, &GPIO_InitStructure);
-    
+  
     // 4. 初始化 NSS (PA4) 为推挽输出
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_Init(GPIOA, &GPIO_InitStructure);
     NSS_HIGH();     // 初始为高电平(未选中)
-    
+  
     // 5. SPI 参数配置
     SPI_InitTypeDef SPI_InitStructure;
     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
@@ -188,7 +190,7 @@ void SPI_Init(void) {
     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
     SPI_InitStructure.SPI_CRCPolynomial = 7;
     SPI_Init(SPI1, &SPI_InitStructure);
-    
+  
     // 6. 使能 SPI
     SPI_Cmd(SPI1, ENABLE);
 }
@@ -262,18 +264,18 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
 
 **标准库 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 内自动调用) |
+| 参数       | 标准库                            | 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 内自动调用) |
 
 ---
 
@@ -354,6 +356,7 @@ W25Q64 是一款 SPI 接口的 Flash 存储芯片,容量 64 Mbit(8 MByte)
 ```
 
 **关键限制**:
+
 - Flash 写入前必须先擦除
 - 擦除的最小单位:**扇区**(4 KB)
 - 写入的最小单位:**页**(256 字节)
@@ -361,14 +364,14 @@ W25Q64 是一款 SPI 接口的 Flash 存储芯片,容量 64 Mbit(8 MByte)
 
 ### 常用指令
 
-| 指令 | 码值 | 说明 |
-|------|------|------|
-| **WRITE_ENABLE** | 0x06 | 写使能(每次擦除/写前必须) |
-| **SECTOR_ERASE_4KB** | 0x20 | 扇区擦除(后跟 24 位地址) |
-| **PAGE_PROGRAM** | 0x02 | 页编程(后跟 24 位地址 + 数据) |
-| **READ_DATA** | 0x03 | 读取数据(后跟 24 位地址) |
-| **READ_STATUS_REGISTER_1** | 0x05 | 读状态寄存器 1(查询 BUSY 位) |
-| **JEDEC_ID** | 0x9F | 读取芯片 ID(返回 3 字节:制造商 ID + 设备 ID) |
+| 指令                       | 码值 | 说明                                            |
+| -------------------------- | ---- | ----------------------------------------------- |
+| **WRITE_ENABLE**           | 0x06 | 写使能(每次擦除/写前必须)                     |
+| **SECTOR_ERASE_4KB**       | 0x20 | 扇区擦除(后跟 24 位地址)                      |
+| **PAGE_PROGRAM**           | 0x02 | 页编程(后跟 24 位地址 + 数据)                 |
+| **READ_DATA**              | 0x03 | 读取数据(后跟 24 位地址)                      |
+| **READ_STATUS_REGISTER_1** | 0x05 | 读状态寄存器 1(查询 BUSY 位)                  |
+| **JEDEC_ID**               | 0x9F | 读取芯片 ID(返回 3 字节:制造商 ID + 设备 ID) |
 
 ### 写使能
 
@@ -389,12 +392,12 @@ void W25Q64_WriteEnable(void) {
 ```c
 void W25Q64_SectorErase(uint32_t Address) {
     W25Q64_WriteEnable();   // 先写使能
-    
+  
     uint8_t Cmd[4] = {0x20, Address >> 16, Address >> 8, Address};
     NSS_LOW();
     HAL_SPI_Transmit(&hspi1, Cmd, 4, HAL_MAX_DELAY);
     NSS_HIGH();
-    
+  
     W25Q64_WaitBusy();      // 等待擦除完成
 }
 ```
@@ -404,13 +407,13 @@ void W25Q64_SectorErase(uint32_t Address) {
 ```c
 void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) {
     W25Q64_WriteEnable();
-    
+  
     uint8_t Cmd[4] = {0x02, Address >> 16, Address >> 8, Address};
     NSS_LOW();
     HAL_SPI_Transmit(&hspi1, Cmd, 4, HAL_MAX_DELAY);           // 发送指令 + 地址
     HAL_SPI_Transmit(&hspi1, DataArray, Count, HAL_MAX_DELAY); // 发送数据
     NSS_HIGH();
-    
+  
     W25Q64_WaitBusy();
 }
 ```
@@ -435,7 +438,7 @@ Flash 在执行擦除或编程时需要时间,通过查询状态寄存器的 B
 void W25Q64_WaitBusy(void) {
     uint32_t Timeout = 100000;
     uint8_t Status;
-    
+  
     NSS_LOW();
     HAL_SPI_Transmit(&hspi1, (uint8_t*)"\x05", 1, HAL_MAX_DELAY);  // 读状态寄存器命令
     do {
@@ -573,25 +576,25 @@ int main(void) {
     // 初始化
     OLED_Init();
     W25Q64_Init();
-    
+  
     // 显示 ID
     uint8_t MID;
     uint16_t DID;
     W25Q64_ReadID(&MID, &DID);
     OLED_ShowHexNum(1, 1, MID, 2);
     OLED_ShowHexNum(1, 8, DID, 4);
-    
+  
     // 上电恢复 LED 状态
     LED_State = LoadLEDState();
     if (LED_State == 1) LED_ON();
     else                LED_OFF();
-    
+  
     // 按钮控制
     uint8_t Previous = 1, Current = 1;
     while (1) {
         Previous = Current;
         Current = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
-        
+      
         if (Previous != Current) {              // 检测到变化
             Delay_ms(10);                       // 消抖
             if (Current == 1) {                 // 按钮抬起
@@ -645,14 +648,14 @@ uint8_t MySPI_SwapByte(uint8_t Byte) {
         if (Byte & 0x80) SPI_MOSI_HIGH();
         else             SPI_MOSI_LOW();
         Byte <<= 1;
-        
+      
         // SCK 上升沿:发送数据被从机采样
         SPI_SCK_HIGH();
         Delay_us(1);
-        
+      
         // MISO 采样:在 SCK 上升沿读取
         if (SPI_MISO_READ()) Byte |= 0x01;
-        
+      
         // SCK 下降沿
         SPI_SCK_LOW();
         Delay_us(1);
@@ -735,42 +738,101 @@ void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) {
 int main(void) {
     OLED_Init();
     W25Q64_Init();
-    
+  
     uint8_t MID;
     uint16_t DID;
     W25Q64_ReadID(&MID, &DID);
     OLED_ShowHexNum(1, 1, MID, 2);
     OLED_ShowHexNum(1, 8, DID, 4);
-    
+  
     uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
     uint8_t ArrayRead[4];
-    
+  
     W25Q64_SectorErase(0x000000);
     W25Q64_PageProgram(0x000000, ArrayWrite, 4);
     W25Q64_ReadData(0x000000, ArrayRead, 4);
-    
+  
     // 显示写入和读出的数据(应一致)
     OLED_ShowHexNum(2, 1, ArrayWrite[0], 2);
     OLED_ShowHexNum(2, 4, ArrayWrite[1], 2);
     OLED_ShowHexNum(3, 1, ArrayRead[0], 2);
     OLED_ShowHexNum(3, 4, ArrayRead[1], 2);
-    
+  
     while (1);
 }
-```
+ ```
+
+### 寄存器:软件模拟SPI(尚硅谷风格)
+
+~~~c
+#define CS_LOW    (GPIOC->ODR &= ~GPIO_ODR_ODR13)
+#define CS_HIGH   (GPIOC->ODR |= GPIO_ODR_ODR13)
+#define SCK_HIGH  (GPIOA->ODR |= GPIO_ODR_ODR5)
+#define SCK_LOW   (GPIOA->ODR &= ~GPIO_ODR_ODR5)
+#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
+#define MOSI_LOW  (GPIOA->ODR &= ~GPIO_ODR_ODR7)
+#define MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)
+
+void SPI_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;
+    GPIOC->CRH |= GPIO_CRH_MODE13;  GPIOC->CRH &= ~GPIO_CRH_CNF13;
+    GPIOA->CRL |= GPIO_CRL_MODE5;   GPIOA->CRL &= ~GPIO_CRL_CNF5;
+    GPIOA->CRL |= GPIO_CRL_MODE7;   GPIOA->CRL &= ~GPIO_CRL_CNF7;
+    GPIOA->CRL &= ~GPIO_CRL_MODE6;
+    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;  GPIOA->CRL |= GPIO_CRL_CNF6_0;
+    SCK_LOW; CS_HIGH;
+}
+
+uint8_t SPI_SwapByte(uint8_t byte) {
+    uint8_t rByte = 0;
+    for (uint8_t i = 0; i < 8; i++) {
+        if (byte & 0x80) MOSI_HIGH; else MOSI_LOW;
+        byte <<= 1;
+        SCK_HIGH; SPI_DELAY;
+        rByte <<= 1;
+        if (MISO_READ) rByte |= 0x01;
+        SCK_LOW; SPI_DELAY;
+    }
+    return rByte;
+}
+~~~
+
+### 寄存器:硬件SPI外设(尚硅谷风格)
+
+~~~c
+void SPI_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+    GPIOA->CRL |= GPIO_CRL_MODE5 | GPIO_CRL_CNF5_1;  GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
+    GPIOA->CRL |= GPIO_CRL_MODE7 | GPIO_CRL_CNF7_1;  GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
+
+    SPI1->CR1 |= SPI_CR1_MSTR;
+    SPI1->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;
+    SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
+    SPI1->CR1 &= ~SPI_CR1_BR;  SPI1->CR1 |= SPI_CR1_BR_0;
+    SPI1->CR1 &= ~(SPI_CR1_DFF | SPI_CR1_LSBFIRST);
+    SPI1->CR1 |= SPI_CR1_SPE;
+}
+
+uint8_t SPI_SwapByte(uint8_t byte) {
+    while ((SPI1->SR & SPI_SR_TXE) == 0) {}
+    SPI1->DR = byte;
+    while ((SPI1->SR & SPI_SR_RXNE) == 0) {}
+    return (uint8_t)(SPI1->DR & 0xff);
+}
+~~~
 
 ---
 
 ## 7. 硬件 SPI vs 软件 SPI
 
-| 对比 | 硬件 SPI | 软件 SPI |
-|------|---------|---------|
-| CPU 占用 | 低 | 高 |
-| 引脚 | 需固定复用引脚 | 任意 GPIO |
-| 速度 | 可达 18MHz(APB2/2) | 受延时精度限制 |
-| 模式切换 | 配置寄存器即可 | 需重写时序 |
-| 编程复杂度 | 中等 | 简单 |
-| 适用场景 | 高速、大数据量 | 低速、引脚受限 |
+| 对比       | 硬件 SPI             | 软件 SPI       |
+| ---------- | -------------------- | -------------- |
+| CPU 占用   | 低                   | 高             |
+| 引脚       | 需固定复用引脚       | 任意 GPIO      |
+| 速度       | 可达 18MHz(APB2/2) | 受延时精度限制 |
+| 模式切换   | 配置寄存器即可       | 需重写时序     |
+| 编程复杂度 | 中等                 | 简单           |
+| 适用场景   | 高速、大数据量       | 低速、引脚受限 |
 
 ---
 
@@ -814,6 +876,7 @@ W25Q64 页编程时写入地址不能跨 256 字节边界。如果需要写入
 ---
 
 > 本笔记综合整理自三套 STM32 教程:
+>
 > 1. [STM32 HAL库] SPI 总线结构 / 5 个参数 / 外部 flash 实验
 > 2. 铁头山羊 SPI 系列(5.1~5.8)
 > 3. 江协科技 SPI 系列(11-1~11-5)

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

@@ -157,6 +157,44 @@ GPIO 引脚电平变化
     → CPU(暂停主程序,跳转到中断服务函数)
 ```
 
+### 寄存器:EXTI配置(尚硅谷风格)
+
+尚硅谷风格直接操作寄存器配置 EXTI,省去标准库的封装层:
+
+~~~c title="寄存器"
+void Key_Init(void) {
+    // GPIO 时钟 + AFIO 时钟
+    RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;
+    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
+
+    // PF10 输入模式(上拉/下拉)
+    GPIOF->CRH &= ~GPIO_CRH_MODE10;
+    GPIOF->CRH |= GPIO_CRH_CNF10_1;
+    GPIOF->CRH &= ~GPIO_CRH_CNF10_0;
+    GPIOF->ODR &= ~GPIO_ODR_ODR10;   // 下拉
+
+    // AFIO:PF10 → EXTI10
+    AFIO->EXTICR[2] |= AFIO_EXTICR3_EXTI10_PF;
+
+    // EXTI:上升沿触发 + 中断使能
+    EXTI->RTSR |= EXTI_RTSR_TR10;     // 上升沿触发
+    EXTI->IMR |= EXTI_IMR_MR10;       // 中断屏蔽使能
+
+    // NVIC 配置(寄存器风格)
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(EXTI15_10_IRQn, 3);
+    NVIC_EnableIRQ(EXTI15_10_IRQn);
+}
+
+void EXTI15_10_IRQHandler(void) {
+    EXTI->PR |= EXTI_PR_PR10;          // 清除中断标志
+    Delay_ms(10);                      // 消抖
+    if ((GPIOF->IDR & GPIO_IDR_IDR10) != 0) {
+        LED_Toggle(LED1);
+    }
+}
+~~~
+
 ---
 
 ## 4. 完整编程流程

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

@@ -701,3 +701,31 @@ OLED 上会显示当前的 SYSCLK 值(如 72000000)。注意修改主频后
 | 8 | 9 MHz | 18 MHz(×2)|
 
 > APB1 定时器(TIM2~TIM7)时钟 = APB1 ×2 仅当 APB1 分频系数 ≠ 1 时成立。调试定时器频率时要注意这一点。
+
+---
+
+### 尚硅谷:时钟使能宏定义速查
+
+| 总线 | 寄存器 | 外设 | 宏定义 |
+|------|--------|------|--------|
+| APB2 | `RCC->APB2ENR` | ADC1 | `RCC_APB2ENR_ADC1EN` |
+| APB2 | `RCC->APB2ENR` | USART1 | `RCC_APB2ENR_USART1EN` |
+| APB2 | `RCC->APB2ENR` | GPIOA~GPIOC | `RCC_APB2ENR_IOPAEN` ~ `RCC_APB2ENR_IOPCEN` |
+| APB2 | `RCC->APB2ENR` | AFIO | `RCC_APB2ENR_AFIOEN` |
+| APB2 | `RCC->APB2ENR` | TIM1 | `RCC_APB2ENR_TIM1EN` |
+| APB2 | `RCC->APB2ENR` | SPI1 | `RCC_APB2ENR_SPI1EN` |
+| APB1 | `RCC->APB1ENR` | TIM2~TIM4 | `RCC_APB1ENR_TIM2EN` ~ `RCC_APB1ENR_TIM4EN` |
+| APB1 | `RCC->APB1ENR` | USART2 | `RCC_APB1ENR_USART2EN` |
+| APB1 | `RCC->APB1ENR` | I2C1/I2C2 | `RCC_APB1ENR_I2C1EN` / `RCC_APB1ENR_I2C2EN` |
+| AHB | `RCC->AHBENR` | DMA1 | `RCC_AHBENR_DMA1EN` |
+
+### 寄存器:RCC外设时钟使能(尚硅谷风格)
+
+```c
+// 标准外设库函数封装的是寄存器操作,等效于:
+RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;    // GPIOA 时钟使能
+RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;    // ADC1 时钟使能
+RCC->APB2ENR |= RCC_APB2ENR_USART1EN;  // USART1 时钟使能
+RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;    // TIM2 时钟使能
+RCC->AHBENR  |= RCC_AHBENR_DMA1EN;     // DMA1 时钟使能
+```

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

@@ -168,6 +168,63 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
 - `AutoReloadPreload` 使能后,ARR 在更新事件时更新,防止写入途中产生异常波形。
 - 中断回调使用弱函数 `HAL_TIM_PeriodElapsedCallback`,在 HAL 库里有一个默认 `__weak` 实现,用户需在同文件或主文件中覆盖。
 
+### 寄存器:SysTick定时器(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void Systick_Init(void) {
+    SysTick->LOAD = 71999;         // 1ms @72MHz
+    SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;   // AHB时钟
+    SysTick->CTRL |= SysTick_CTRL_TICKINT;     // 使能中断
+    SysTick->CTRL |= SysTick_CTRL_ENABLE;      // 开启
+}
+
+uint16_t count = 0;
+void SysTick_Handler(void) {
+    count++;
+    if (count == 1000) { LED_Toggle(LED1); count = 0; }
+}
+~~~
+
+### 尚硅谷:SysTick延时函数
+
+~~~c title="寄存器 - 尚硅谷风格"
+void Delay_us(uint32_t us) {
+    SysTick->LOAD = us * 72 - 1;    // 72 ticks/us @72MHz AHB
+    SysTick->VAL = 0;
+    SysTick->CTRL = SysTick_CTRL_CLKSOURCE | SysTick_CTRL_ENABLE;
+    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG));
+    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
+}
+
+void Delay_ms(uint32_t ms) {
+    while (ms--) Delay_us(1000);
+}
+
+void Delay_s(uint32_t s) {
+    while (s--) Delay_ms(1000);
+}
+~~~
+
+### 寄存器:基本定时器TIM6(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void TIM6_Init(void) {
+    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
+    TIM6->PSC = 7199;           // 72MHz/7200 = 10kHz
+    TIM6->ARR = 9999;           // 10kHz/10000 = 1Hz
+    TIM6->DIER |= TIM_DIER_UIE; // 更新中断使能
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(TIM6_IRQn, 2);
+    NVIC_EnableIRQ(TIM6_IRQn);
+    TIM6->CR1 |= TIM_CR1_CEN;   // 开启
+}
+
+void TIM6_IRQHandler(void) {
+    TIM6->SR &= ~TIM_SR_UIF;    // 清除标志
+    LED_Toggle(LED2);
+}
+~~~
+
 ---
 
 ## 3. 输出比较与 PWM
@@ -350,6 +407,29 @@ GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);  // 部分重映射
 // 或完全重映射参考手册确定映射关系
 ```
 
+### 寄存器:PWM输出(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void TIM5_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+    // PA1: 复用推挽 CNF-10 MODE-11
+    GPIOA->CRL |= GPIO_CRL_MODE1 | GPIO_CRL_CNF1_1;  GPIOA->CRL &= ~GPIO_CRL_CNF1_0;
+
+    TIM5->PSC = 7199;            // 72MHz/7200 = 10kHz
+    TIM5->ARR = 99;              // 10kHz/100 = 100Hz PWM
+    TIM5->CR1 &= ~TIM_CR1_DIR;   // 向上计数
+    TIM5->CCR2 = 50;             // 占空比50%
+    TIM5->CCMR1 &= ~TIM_CCMR1_CC2S;  // 输出模式
+    TIM5->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1;  // PWM1模式
+    TIM5->CCMR1 &= ~TIM_CCMR1_OC2M_0;
+    TIM5->CCER |= TIM_CCER_CC2E; // 使能通道2输出
+}
+
+void TIM5_SetDutyCycle(uint8_t dutyCycle) { TIM5->CCR2 = dutyCycle; }
+void TIM5_Start(void) { TIM5->CR1 |= TIM_CR1_CEN; }
+~~~
+
 ---
 
 ## 4. 输入捕获
@@ -543,6 +623,48 @@ HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
 //   占空比 = DutyPulse / Period
 ```
 
+### 寄存器:输入捕获测周期(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void TIM4_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
+    // PB6: 浮空输入 CNF-01 MODE-00
+    GPIOB->CRL &= ~GPIO_CRL_MODE6;
+    GPIOB->CRL &= ~GPIO_CRL_CNF6_1;  GPIOB->CRL |= GPIO_CRL_CNF6_0;
+
+    TIM4->PSC = 71;              // 72MHz/72 = 1MHz (1us精度)
+    TIM4->ARR = 65535;           // 最大周期
+    TIM4->CR1 &= ~TIM_CR1_DIR;
+    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;   // 无滤波
+    TIM4->CCER &= ~TIM_CCER_CC1P;     // 上升沿捕获
+    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1; TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;  // TI1映射
+    TIM4->CCER |= TIM_CCER_CC1E;      // 使能捕获
+    TIM4->DIER |= TIM_DIER_CC1IE;     // 捕获中断
+}
+
+void TIM4_IRQHandler(void) {
+    if (TIM4->SR & TIM_SR_CC1IF) {
+        TIM4->SR &= ~TIM_SR_CC1IF;
+        TIM4->CNT = 0;           // 清零CNT, 直接读取CCR1得到周期
+    }
+}
+double TIM4_GetPWMFreq(void) { return 1000000.0 / TIM4->CCR1; }
+~~~
+
+**PWMI 模式(周期+占空比同时测量):**
+
+~~~c title="寄存器 - 尚硅谷风格"
+// 通道1: 上升沿捕获 (TI1) -> CCR1 = 周期
+// 通道2: 下降沿捕获 (TI1间接) -> CCR2 = 脉宽
+TIM4->CCER |= TIM_CCER_CC2P;               // 通道2下降沿
+TIM4->CCMR1 |= TIM_CCMR1_CC2S_1;           // 通道2映射到TI1 (间接)
+TIM4->CCMR1 &= ~TIM_CCMR1_CC2S_0;
+TIM4->SMCR |= TIM_SMCR_TS_2 | TIM_SMCR_TS_0;  // 触发源TI1FP1
+TIM4->SMCR |= TIM_SMCR_SMS_2;              // 从模式: 复位
+double TIM4_GetPWMDuty(void) { return TIM4->CCR2 * 1.0 / TIM4->CCR1; }
+~~~
+
 ---
 
 ## 5. 编码器接口
@@ -717,6 +839,19 @@ int16_t Encoder_Get(void)
 - 编码器模式下 `AutoReloadPreload` 通常设为 `DISABLE`,因为需要 CNT 对 ARR 立即响应。
 - `__HAL_TIM_GET_COUNTER` / `__HAL_TIM_SET_COUNTER` 是宏,代替标准库的 `TIM_GetCounter` / `TIM_SetCounter`。
 
+### 寄存器:高级定时器PWM(尚硅谷风格)
+
+~~~c title="寄存器 - 尚硅谷风格"
+void TIM1_Init(void) {
+    TIM1->PSC = 7199;  TIM1->ARR = 4999;  // 5s周期
+    TIM1->RCR = 4;     // 重复计数: 每5次溢出才产生一次UEV
+    TIM1->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;  // PWM1
+    TIM1->CCER |= TIM_CCER_CC1E;          // CH1输出
+    TIM1->BDTR |= TIM_BDTR_MOE;           // 刹车使能 (高级定时器必须)
+    TIM1->DIER |= TIM_DIER_UIE;           // 更新中断
+}
+~~~
+
 ---
 
 ## 6. 定时器关键函数速查

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

@@ -127,6 +127,33 @@ uint16_t AD_GetValue(void)
 }
 ```
 
+### 寄存器:单通道ADC(尚硅谷风格)
+
+```c
+void ADC1_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    RCC->CFGR |= RCC_CFGR_ADCPRE_1; RCC->CFGR &= ~RCC_CFGR_ADCPRE_0;  // 6分频=12MHz
+
+    GPIOC->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);  // PC0: 模拟输入
+
+    ADC1->CR1 &= ~ADC_CR1_SCAN;    // 非扫描模式
+    ADC1->CR2 |= ADC_CR2_CONT;     // 连续转换
+    ADC1->CR2 &= ~ADC_CR2_ALIGN;   // 右对齐
+    ADC1->SMPR1 |= ADC_SMPR1_SMP10_0;  // 采样时间7.5周期
+    ADC1->SQR3 |= 10 << 0;         // 规则序列1: 通道10(PC0)
+}
+
+void ADC1_StartConvert(void) {
+    ADC1->CR2 |= ADC_CR2_ADON;     // 上电
+    ADC1->CR2 |= ADC_CR2_CAL;      // 校准
+    while (ADC1->CR2 & ADC_CR2_CAL) {}
+    ADC1->CR2 |= ADC_CR2_ADON;     // 再次置位ADON开始转换
+    while ((ADC1->SR & ADC_SR_EOC) == 0) {}
+}
+
+double ADC1_ReadV(void) { return ADC1->DR * 3.3 / 4095; }
+```
+
 ### 主函数(单通道)
 ```c
 int main(void)
@@ -407,4 +434,37 @@ int main(void)
         Delay_ms(100);
     }
 }
+
+### 寄存器:双通道+DMA(尚硅谷风格)
+
+```c
+void ADC1_Init(void) {
+    ADC1->CR1 |= ADC_CR1_SCAN;     // 扫描模式
+    ADC1->SQR1 |= ADC_SQR1_L_0;    // 序列长度=2
+    ADC1->SQR3 |= 10 << 0;         // 第1个: 通道10
+    ADC1->SQR3 |= 12 << 5;         // 第2个: 通道12
+}
+
+void ADC1_DMA_Init(void) {
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;        // 外设到存储器
+    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;     // 16位
+    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
+    DMA1_Channel1->CCR |= DMA_CCR1_MINC;        // 存储器递增
+    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;        // 循环模式
+    ADC1->CR2 |= ADC_CR2_DMA;                   // ADC DMA使能
+}
+
+void ADC1_DMA_StartConvert(uint32_t destAddr, uint8_t len) {
+    DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);
+    DMA1_Channel1->CMAR = destAddr;
+    DMA1_Channel1->CNDTR = len;
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
+    ADC1->CR2 |= ADC_CR2_ADON;    // 上电
+    ADC1->CR2 |= ADC_CR2_CAL;     // 校准
+    while (ADC1->CR2 & ADC_CR2_CAL) {}
+    ADC1->CR2 |= ADC_CR2_ADON;    // 开始转换
+    while ((ADC1->SR & ADC_SR_EOC) == 0) {}
+}
+```
 ```

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

@@ -30,6 +30,87 @@ DMA 优先级分为 4 级:`Low` < `Medium` < `High` < `VeryHigh`。当多个
 
 将源内存区域的数据搬运到目标内存区域。需要使能 `DMA_M2M`(存储器到存储器模式),此时 DMA 从软件请求触发转为连续工作。
 
+### 寄存器:DMA配置(尚硅谷风格)
+
+~~~c title="寄存器"
+// M2M模式 - DMA1 Channel1
+void DMA1_Init(void) {
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+
+    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;    // M2M模式
+    DMA1_Channel1->CCR |= DMA_CCR1_DIR;         // 存储器到存储器
+    DMA1_Channel1->CCR &= ~(DMA_CCR1_PSIZE | DMA_CCR1_MSIZE);  // 8位
+    DMA1_Channel1->CCR |= DMA_CCR1_PINC | DMA_CCR1_MINC;  // 地址递增
+    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;        // 传输完成中断
+}
+
+void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen) {
+    DMA1_Channel1->CPAR = destAddr;
+    DMA1_Channel1->CMAR = srcAddr;
+    DMA1_Channel1->CNDTR = dataLen;
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
+}
+
+void DMA1_Channel1_IRQHandler(void) {
+    if (DMA1->ISR & DMA_ISR_TCIF1) {
+        DMA1->IFCR |= DMA_IFCR_CTCIF1;
+        DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
+        isFinished = 1;
+    }
+}
+~~~
+
+~~~c title="寄存器 - M2P to USART (Channel4)"
+// DMA到UART通道配置(channel4)
+void DMA1_USART_Init(void) {
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+
+    DMA1_Channel4->CCR |= DMA_CCR4_DIR;          // 存储器到外设
+    DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;        // 外设地址不递增
+    DMA1_Channel4->CCR |= DMA_CCR4_MINC;         // 存储器地址递增
+    DMA1_Channel4->CCR |= DMA_CCR4_CIRC;         // 循环模式
+    DMA1_Channel4->CCR |= DMA_CCR4_TCIE;         // 传输完成中断
+
+    USART1->CR3 |= USART_CR3_DMAT;               // USART发送DMA使能
+}
+
+void DMA1_USART_Transmit(uint32_t bufAddr, uint16_t dataLen) {
+    DMA1_Channel4->CPAR = (uint32_t)&USART1->DR;
+    DMA1_Channel4->CMAR = bufAddr;
+    DMA1_Channel4->CNDTR = dataLen;
+    DMA1_Channel4->CCR |= DMA_CCR4_EN;
+}
+~~~
+
+~~~c title="寄存器 - ADC + DMA 循环采集"
+void ADC1_DMA_Init(void) {
+    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+
+    // DMA1 Channel1: ADC1->DR -> memory
+    DMA1_Channel1->CCR &= ~(DMA_CCR1_MEM2MEM | DMA_CCR1_DIR);  // 外设到存储器
+    DMA1_Channel1->CCR &= ~(DMA_CCR1_PSIZE | DMA_CCR1_MSIZE);
+    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0 | DMA_CCR1_MSIZE_0; // 16位
+    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;     // 外设地址不递增
+    DMA1_Channel1->CCR |= DMA_CCR1_MINC;      // 存储器地址递增
+    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;      // 循环模式
+    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;      // 传输完成中断
+
+    // ADC1连续扫描模式 + DMA
+    ADC1->CR2 |= ADC_CR2_DMA;                 // DMA模式
+    ADC1->CR2 |= ADC_CR2_CONT;                // 连续转换
+    ADC1->CR2 |= ADC_CR2_ADON;                // 使能ADC
+}
+
+void ADC1_DMA_StartConvert(uint32_t bufAddr, uint16_t len) {
+    DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
+    DMA1_Channel1->CMAR = bufAddr;
+    DMA1_Channel1->CNDTR = len;
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
+    ADC1->CR2 |= ADC_CR2_SWSTART;             // 软件触发连续转换
+}
+~~~
+
 ### 驱动代码 `MyDMA.c`(标准库)
 
 ```c

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

@@ -210,6 +210,32 @@ HAL_PWR_ConfigPVD(&sConfigPVD);
 HAL_PWR_EnablePVD();
 ```
 
+### 寄存器:三种低功耗模式(尚硅谷风格)
+
+尚硅谷课程中直接操作寄存器实现三种低功耗模式,不依赖 HAL 或标准库:
+
+```c
+void enter_sleep_mode(void) {
+    SCB->SCR &= ~SCB_SCR_SLEEPDEEP;  // 睡眠模式
+    __WFI();
+}
+
+void enter_stop_mode(void) {
+    SCB->SCR |= SCB_SCR_SLEEPDEEP;
+    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+    PWR->CR &= ~PWR_CR_PDDS;         // 停止模式
+    PWR->CR |= PWR_CR_LPDS;          // 低功耗调节器
+    __WFI();
+}
+
+void enter_standby_mode(void) {
+    SCB->SCR |= SCB_SCR_SLEEPDEEP;
+    PWR->CR |= PWR_CR_PDDS;          // 待机模式
+    PWR->CSR |= PWR_CSR_EWUP;        // PA0唤醒使能
+    __WFI();
+}
+```
+
 ### 关键注意事项
 
 1. **停止模式和待机模式必须开启 PWR 时钟**:`RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);`

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

@@ -6,7 +6,6 @@ lecture_material: "12-1 Unix时间戳, 12-3 读写备份寄存器&实时时钟"
 project_code: "12-1 读写备份寄存器, 12-2 实时时钟"
 tags: [stm32, bkp, rtc, unix-timestamp, backup-domain]
 ---
-
 # STM32-BKP 备份寄存器 & RTC 实时时钟
 
 ## 1 Unix 时间戳
@@ -28,13 +27,13 @@ tags: [stm32, bkp, rtc, unix-timestamp, backup-domain]
 ![BKP 内部结构](assets/bkp_structure.png)
 *上图:BKP 备份寄存器内部结构(来源:STM32入门教程 PPT 第172页)*
 
-| 特性             | 值                            |
-|------------------|-------------------------------|
-| 容量             | 10 × 16 位备份寄存器(BKP_DR1~DR10) |
-| 供电             | 由 **备份域**(VBAT 引脚)供电        |
-| 主电源掉电       | 数据不丢失(纽扣电池维持)         |
-| 额外功能         | 侵入检测(TAMPER 引脚)          |
-| 时钟             | 位于 APB1 总线                    |
+| 特性       | 值                                    |
+| ---------- | ------------------------------------- |
+| 容量       | 10 × 16 位备份寄存器(BKP_DR1~DR10) |
+| 供电       | 由**备份域**(VBAT 引脚)供电   |
+| 主电源掉电 | 数据不丢失(纽扣电池维持)            |
+| 额外功能   | 侵入检测(TAMPER 引脚)               |
+| 时钟       | 位于 APB1 总线                        |
 
 ### 2.2 访问流程(标准库)
 
@@ -95,6 +94,19 @@ void BKP_Test_HAL(void)
 }
 ```
 
+### 寄存器:BKP(尚硅谷风格)
+
+尚硅谷直接操作寄存器读写 BKP:
+
+```c
+void BKP_Init(void) {
+    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+    PWR->CR |= PWR_CR_DBP;           // 使能后备域访问
+    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
+}
+// 读写:BKP->DR1 = value;  value = BKP->DR1;
+```
+
 ## 3 RTC(Real-Time Clock)——实时时钟
 
 ![RTC 模块框图](assets/rtc_block.png)
@@ -391,6 +403,58 @@ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
 }
 ```
 
+### 寄存器:RTC日历(尚硅谷风格)
+
+尚硅谷直接操作寄存器实现 RTC 日历:
+
+```c
+typedef struct {
+    uint16_t year;
+    uint16_t month;
+    uint16_t day;
+    uint16_t hour;
+    uint16_t minute;
+    uint16_t second;
+} DateTime;
+
+void RTC_Init(void) {
+    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+    PWR->CR |= PWR_CR_DBP;           // 使能后备域
+
+    RCC->BDCR |= RCC_BDCR_RTCEN;     // RTC使能
+    RCC->BDCR |= RCC_BDCR_LSEON;     // LSE使能
+    while (!(RCC->BDCR & RCC_BDCR_LSERDY)) {}
+    RCC->BDCR &= ~RCC_BDCR_RTCSEL;   // 选择LSE
+    RCC->BDCR |= RCC_BDCR_RTCSEL_0;
+
+    while (!(RTC->CRL & RTC_CRL_RTOFF)) {}
+    RTC->CRL |= RTC_CRL_CNF;         // 进入配置模式
+    RTC->PRLH = 0;                   // 32768分频
+    RTC->PRLL = 0x7fff;
+    RTC->CRL &= ~RTC_CRL_CNF;
+    while (!(RTC->CRL & RTC_CRL_RTOFF)) {}
+}
+
+void RTC_SetTimestamp(uint32_t ts) {
+    while (!(RTC->CRL & RTC_CRL_RTOFF)) {}
+    RTC->CRL |= RTC_CRL_CNF;
+    RTC->CNTH = (ts >> 16) & 0xffff;
+    RTC->CNTL = (ts >> 0) & 0xffff;
+    RTC->CRL &= ~RTC_CRL_CNF;
+}
+
+void RTC_GetDateTime(DateTime *dt) {
+    uint32_t second = (RTC->CNTH << 16) | RTC->CNTL;
+    struct tm *ptm = localtime(&second);
+    dt->year = ptm->tm_year + 1900;
+    dt->month = ptm->tm_mon + 1;
+    dt->day = ptm->tm_mday;
+    dt->hour = ptm->tm_hour;
+    dt->minute = ptm->tm_min;
+    dt->second = ptm->tm_sec;
+}
+```
+
 ## 4 备份域(Backup Domain)总览
 
 ```
@@ -412,9 +476,9 @@ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
 
 ## 5 应用场景
 
-| 场景               | 使用                                              |
-|--------------------|---------------------------------------------------|
-| 设备累计运行时间    | RTC 作 timestamp,BKP 记录累计值                     |
-| 校准参数掉电保存   | BKP 寄存器存校验标志和参数                               |
-| 闹钟唤醒           | RTC_AL 闹钟匹配时产生中断,配合待机模式              |
-| 防盗检测           | BKP TAMPER 引脚触发侵入事件 → 清除 BKP 数据          |
+| 场景             | 使用                                         |
+| ---------------- | -------------------------------------------- |
+| 设备累计运行时间 | RTC 作 timestamp,BKP 记录累计值             |
+| 校准参数掉电保存 | BKP 寄存器存校验标志和参数                   |
+| 闹钟唤醒         | RTC_AL 闹钟匹配时产生中断,配合待机模式      |
+| 防盗检测         | BKP TAMPER 引脚触发侵入事件 → 清除 BKP 数据 |

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

@@ -153,6 +153,24 @@ if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET)
 
 > 教师特别提醒:有些同学把喂狗放在 SysTick 中断里,这是致命的!如果主循环死锁但中断仍然正常触发,看门狗永远不会复位——掩盖了真正的故障。
 
+### 寄存器:IWDG配置(尚硅谷风格)
+
+尚硅谷直接操作 IWDG 寄存器(KR/PR/RLR):
+
+```c
+void IWDG_Init(void) {
+    IWDG->KR = 0xCCCC;      // 启用寄存器访问
+    IWDG->KR = 0x5555;      // 取消写保护
+    IWDG->PR = 4;           // 预分频 64 (4*4=16 -> 2^6=64)
+    IWDG->RLR = 2499;       // 重装值: (2499+1)*1/625Hz = 4s
+    IWDG_Refresh();
+}
+
+void IWDG_Refresh(void) {
+    IWDG->KR = 0xAAAA;      // 喂狗
+}
+```
+
 ## 3 窗口看门狗(WWDG)
 
 ![WWDG 模块框图](assets/wwdg_block.png)

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

@@ -438,6 +438,24 @@ void GenerateMAC(uint8_t *mac)
 }
 ```
 
+### 寄存器:FLASH操作注意(尚硅谷参考)
+
+尚硅谷课程中 FLASH 部分使用外部 SPI Flash(W25Q32)而非内部 FLASH 进行多字节写入,因为内部 FLASH 必须以页擦除再写入,不适合频繁小量写入。
+
+内部 FLASH 解锁密钥寄存器参考:
+
+| 寄存器 | 密钥值 |
+|--------|--------|
+| `FLASH->KEYR` | 0x45670123 → 0xCDEF89AB(依次写入) |
+| `FLASH->OPTKEYR` | 0x45670123 → 0xCDEF89AB(选项字节解锁) |
+
+```c
+FLASH->KEYR = 0x45670123;   // 密钥1
+FLASH->KEYR = 0xCDEF89AB;   // 密钥2 → 解锁
+```
+
+完整操作序列参考标准库或 HAL 库封装。
+
 ## 6 总结
 
 ```

+ 623 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/16-CAN通信.md

@@ -0,0 +1,623 @@
+# STM32 CAN 通信
+
+> 基于尚硅谷(高级篇·第1章)、STM32参考手册综合整理
+
+---
+
+## 1. CAN 协议基础
+
+### 1.1 CAN 总线概述
+
+CAN(Controller Area Network)是一种**多主总线式**串行通信协议,由 Bosch 公司为汽车电子开发,已成为 ISO 国际标准(ISO 11898)。
+
+#### 差分信号
+
+CAN 使用两根线——**CAN_HIGH** 和 **CAN_LOW**——传输差分信号:
+
+```
+CAN_HIGH ──┬──────┬──────┬──────
+           │      │      │
+CAN_LOW  ──┴──────┴──────┴──────
+```
+
+- 显性(Dominant):CAN_H ≈ 3.5V,CAN_L ≈ 1.5V,逻辑 **0**
+- 隐性(Recessive):CAN_H ≈ 2.5V,CAN_L ≈ 2.5V,逻辑 **1**
+- 显性电平会覆盖隐性电平(线与逻辑:0 覆盖 1)
+
+差分传输带来极强的抗共模干扰能力,适合汽车、工业等噪声环境。
+
+#### 多主控制
+
+总线上任何节点都可以主动发送,没有主从之分。当多个节点同时发送时,通过**逐位仲裁**(非破坏性位仲裁)解决冲突。
+
+### 1.2 帧格式
+
+#### 数据帧(标准格式)
+
+```
+SOF | 12-bit ID | RTR | IDE | r0 | DLC | 0~8 Byte Data | CRC | ACK | EOF
+```
+
+标准格式共 **108 位**(数据段为 0 时)。逐段说明:
+
+| 段         | 位数   | 说明                            |
+|------------|--------|---------------------------------|
+| SOF        | 1      | Start of Frame,显性            |
+| 仲裁域     | 12     | 11 位标识符 + RTR 位            |
+| 控制域     | 6      | IDE + r0 + DLC(4 位数据长度)  |
+| 数据域     | 0~64   | 0~8 字节数据                    |
+| CRC 域     | 16     | 15 位 CRC + CRC 分隔符          |
+| ACK 域     | 2      | ACK 槽 + ACK 分隔符             |
+| EOF        | 7      | End of Frame,7 个隐性位        |
+
+#### 扩展帧
+
+```
+SOF | 29-bit ID | SRR | IDE | RTR | r1 | r0 | DLC | Data | CRC | ACK | EOF
+```
+
+扩展帧的标识符为 29 位(11 位基本 ID + 18 位扩展 ID),仲裁域使用 32 位。
+
+#### 帧类型
+
+| 帧类型   | 用途                             |
+|----------|----------------------------------|
+| 数据帧   | 发送节点向接收节点传输数据       |
+| 远程帧   | 发送节点请求接收节点发送数据     |
+| 错误帧   | 检测到总线错误时主动报告         |
+| 过载帧   | 接收节点未准备好时延迟下一帧     |
+
+### 1.3 仲裁机制
+
+CAN 总线的仲裁在**仲裁域**完成,基于**线与逻辑**(显性位 0 覆盖隐性位 1):
+
+- 发送节点在发送仲裁域的同时也在监听总线
+- 如果发送了隐性位(1)却读到显性位(0),说明有更高优先级(ID 更小)的节点正在发送
+- 该节点立即退出仲裁,转为接收
+
+**结论:ID 值越小,优先级越高。**
+
+### 1.4 错误处理
+
+CAN 协议提供 5 种错误检测机制:
+
+| 错误类型     | 说明                               |
+|--------------|------------------------------------|
+| 位错误       | 发送位与总线电平不一致             |
+| 填充错误     | 连续 6 个相同电平(破坏位填充规则)|
+| CRC 错误     | CRC 校验不通过                     |
+| 格式错误     | 固定格式域出现非法电平             |
+| ACK 错误     | 发送节点未收到应答                 |
+
+每个节点有两个错误计数器(TEC/REC),根据错误严重程度进入 **主动错误 → 被动错误 → 总线关闭** 状态。
+
+---
+
+## 2. STM32 bxCAN 外设
+
+### 2.1 特性概览
+
+STM32F103 集成了 **bxCAN**(Basic Extended CAN)外设:
+
+| 特性              | 说明                               |
+|-------------------|------------------------------------|
+| 双 CAN            | CAN1 和 CAN2(互联型才有 CAN2)    |
+| 发送邮箱          | 3 个(优先级可配置)               |
+| 接收 FIFO         | 2 个(FIFO0 / FIFO1),深度 3      |
+| 过滤器组          | 14 个(互联型 28 个)              |
+| 工作模式          | 正常 / 静默 / 回环 / 回环静默      |
+| 位速率            | 最高 1 Mbps                        |
+| 自动功能          | 自动离线恢复、自动唤醒             |
+| 中断              | 发送/接收/错误/唤醒中断            |
+
+### 2.2 工作模式
+
+| 模式         | 说明                                     |
+|--------------|------------------------------------------|
+| 正常模式     | 正常收发,总线需要至少两个节点           |
+| 静默模式     | 只接收不发送(发送为隐性),用于监听     |
+| 回环模式     | 只发送不接收(内部回环),用于自测       |
+| 回环静默模式 | 内部回环 + 不发显性位,彻底与外部隔离    |
+
+### 2.3 过滤器
+
+bxCAN 的过滤器支持两种工作模式:
+
+- **标识符列表模式**:精确匹配,只接收指定 ID
+- **掩码模式**:指定 ID + 掩码,匹配 ID 范围
+
+每个过滤器组可以配置为:
+
+- 32 位模式:匹配完整 ID(标准/扩展)
+- 16 位模式:匹配两个短 ID
+
+过滤器关联到 FIFO0 或 FIFO1,匹配成功的帧进入对应的 FIFO。
+
+### 2.4 位时序与波特率
+
+CAN 总线的位时间由三段组成:
+
+```
+位时间 = 1 tq (Sync_Seg) + BS1 tq + BS2 tq
+```
+
+| 段          | 可配置范围 | 说明                        |
+|-------------|------------|-----------------------------|
+| Sync_Seg    | 1 tq(固定)| 同步段,总线电平跳变在此段 |
+| BS1 (TSEG1) | 1~16 tq    | 传播段 + 相位缓冲段 1       |
+| BS2 (TSEG2) | 1~8 tq     | 相位缓冲段 2                |
+| SJW         | 1~4 tq     | 同步跳转宽度                |
+
+**采样点位置** = (1 + BS1) / (1 + BS1 + BS2)
+
+**波特率计算**:
+
+```
+tq = (BRP + 1) / fAPB1
+波特率 = 1 / (tq × (1 + BS1 + BS2))
+```
+
+举例:fAPB1 = 36MHz,目标 500kbps:
+
+- BRP = 35 → tq = 36MHz / 36 = 1MHz
+- BS1 = 2, BS2 = 5 → 位时间 = 1 + 2 + 5 = 8 tq
+- 波特率 = 1MHz / 8 = **125kbps**
+
+如果要达到 500kbps:BRP = 3 → tq = 36/4 = 9MHz;BS1 = 7, BS2 = 8 → 9MHz / (1+7+8) = 9/16 = 562.5kHz。或者 BRP = 8 → tq = 36/9 = 4MHz;BS1 = 5, BS2 = 2 → 4/(1+5+2) = 4/8 = **500kHz**。
+
+### 2.5 引脚映射
+
+CAN1 的引脚映射(通过 AFIO 配置):
+
+| 配置        | CAN_RX  | CAN_TX  |
+|-------------|---------|---------|
+| 默认        | PB8     | PB9     |
+| 重映射 0    | PD0     | PD1     |
+| 重映射 1    | PB8     | PB9     |
+
+---
+
+## 3. 寄存器:CAN 驱动(尚硅谷风格)
+
+以下代码基于寄存器直接操作 bxCAN,实现初始化、发送和接收。
+
+### can.h
+
+```c
+#ifndef __CAN_H
+#define __CAN_H
+
+#include "stm32f10x.h"
+#include <string.h>
+
+typedef struct {
+    uint16_t stdID;
+    uint8_t  data[8];
+    uint8_t  len;
+} RxMsg;
+
+void CAN_Init(void);
+void CAN_SendMsg(uint16_t stdID, uint8_t *data, uint8_t len);
+void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t *msgCount);
+
+#endif
+```
+
+### can.c — 初始化
+
+```c
+#include "can.h"
+
+static void CAN_FilterConfig(void);
+
+void CAN_Init(void)
+{
+    // 1. 时钟使能
+    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
+
+    // 2. 引脚重映射 — PB8(RX), PB9(TX)
+    AFIO->MAPR |=  AFIO_MAPR_CAN_REMAP_1;
+    AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;
+
+    // 3. GPIO 配置
+    // PB8 — RX (上拉输入)
+    GPIOB->CRH &= ~GPIO_CRH_MODE8;
+    GPIOB->CRH &= ~GPIO_CRH_CNF8_1;
+    GPIOB->CRH |=  GPIO_CRH_CNF8_0;
+
+    // PB9 — TX (复用推挽输出)
+    GPIOB->CRH |=  GPIO_CRH_MODE9;
+    GPIOB->CRH |=  GPIO_CRH_CNF9_1;
+    GPIOB->CRH &= ~GPIO_CRH_CNF9_0;
+
+    // 4. CAN 进入初始化模式
+    CAN1->MCR |= CAN_MCR_INRQ;
+    while ((CAN1->MSR & CAN_MSR_INAK) == 0) {}
+
+    // 5. 基础配置
+    CAN1->MCR &= ~CAN_MCR_SLEEP;          // 退出睡眠
+    while ((CAN1->MSR & CAN_MSR_SLAK) != 0) {}
+
+    CAN1->MCR |= CAN_MCR_ABOM;            // 自动离线管理
+    CAN1->MCR |= CAN_MCR_AWUM;            // 自动唤醒
+
+    // 6. 回环静默模式(自测用)
+    CAN1->BTR |= CAN_BTR_SILM;            // 静默模式
+    CAN1->BTR |= CAN_BTR_LBKM;            // 回环模式
+
+    // 7. 位时序配置(APB1 = 36MHz)
+    CAN1->BTR &= ~CAN_BTR_BRP;
+    CAN1->BTR |= (35 << 0);               // BRP=35 → tq=1MHz
+    CAN1->BTR &= ~CAN_BTR_TS1;
+    CAN1->BTR |= (2 << 16);               // BS1=2 tq
+    CAN1->BTR &= ~CAN_BTR_TS2;
+    CAN1->BTR |= (5 << 20);               // BS2=5 tq
+    CAN1->BTR &= ~CAN_BTR_SJW;
+    CAN1->BTR |= (1 << 24);               // SJW=1 tq
+    // 波特率 = 1MHz / (1+2+5) = 125kbps
+
+    // 8. 退出初始化模式
+    CAN1->MCR &= ~CAN_MCR_INRQ;
+    while ((CAN1->MSR & CAN_MSR_INAK) != 0) {}
+
+    CAN_FilterConfig();
+}
+```
+
+### can.c — 过滤器配置
+
+```c
+static void CAN_FilterConfig(void)
+{
+    CAN1->FMR |= CAN_FMR_FINIT;                   // 进入滤波初始化模式
+
+    CAN1->FM1R &= ~CAN_FM1R_FBM0;                 // 标识符列表模式(精确匹配)
+    CAN1->FS1R |= CAN_FS1R_FSC0;                  // 32 位过滤器
+    CAN1->FFA1R &= ~CAN_FFA1R_FFA0;               // 关联 FIFO0
+
+    // 过滤器 0:匹配标准 ID 0x06e 和 0x7f1
+    CAN1->sFilterRegister[0].FR1 = 0x06E << 21;   // 标准 ID 左对齐到高 11 位
+    CAN1->sFilterRegister[0].FR2 = 0x7F1 << 21;
+
+    CAN1->FA1R |= CAN_FA1R_FACT0;                 // 激活过滤器 0
+    CAN1->FMR &= ~CAN_FMR_FINIT;                  // 退出滤波初始化模式
+}
+```
+
+### can.c — 发送
+
+```c
+void CAN_SendMsg(uint16_t stdID, uint8_t *data, uint8_t len)
+{
+    // 等待发送邮箱 0 空闲
+    while ((CAN1->TSR & CAN_TSR_TME0) == 0) {}
+
+    // 配置发送标识符 — 标准 ID,左对齐
+    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
+    CAN1->sTxMailBox[0].TIR |= (stdID << 21);
+
+    // 标准数据帧(IDE=0, RTR=0)
+    CAN1->sTxMailBox[0].TIR &= ~(CAN_TI0R_IDE | CAN_TI0R_RTR);
+
+    // 设置数据长度
+    CAN1->sTxMailBox[0].TDTR = len;
+
+    // 清空数据寄存器
+    CAN1->sTxMailBox[0].TDLR = 0;
+    CAN1->sTxMailBox[0].TDHR = 0;
+
+    // 填入数据(最多 8 字节)
+    for (uint8_t i = 0; i < len && i < 8; i++) {
+        if (i < 4)
+            CAN1->sTxMailBox[0].TDLR |= data[i] << (i * 8);
+        else
+            CAN1->sTxMailBox[0].TDHR |= data[i] << ((i - 4) * 8);
+    }
+
+    // 请求发送
+    CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
+
+    // 等待发送完成
+    while ((CAN1->TSR & CAN_TSR_TXOK0) == 0) {}
+}
+```
+
+### can.c — 接收
+
+```c
+void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t *msgCount)
+{
+    // FIFO0 中待接收报文数量
+    *msgCount = (CAN1->RF0R & CAN_RF0R_FMP0);
+
+    for (uint8_t i = 0; i < *msgCount; i++) {
+        RxMsg *msg = &rxMsg[i];
+
+        // 取出标准 ID
+        msg->stdID = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7FF;
+
+        // 取出数据长度
+        msg->len = CAN1->sFIFOMailBox[0].RDTR & 0x0F;
+
+        // 读取数据
+        uint32_t low  = CAN1->sFIFOMailBox[0].RDLR;
+        uint32_t high = CAN1->sFIFOMailBox[0].RDHR;
+
+        for (uint8_t j = 0; j < msg->len; j++) {
+            msg->data[j] = (j < 4) ? ((low >> (8 * j)) & 0xFF)
+                                   : ((high >> (8 * (j - 4))) & 0xFF);
+        }
+
+        // 释放 FIFO(读取下一帧)
+        CAN1->RF0R |= CAN_RF0R_RFOM0;
+    }
+}
+```
+
+---
+
+## 4. 标准库 CAN 驱动
+
+STM32 标准外设库提供 `CAN` 相关库函数,封装了寄存器操作。
+
+### 初始化流程
+
+```c
+void CAN_Config(void)
+{
+    GPIO_InitTypeDef GPIO_InitStructure;
+    CAN_InitTypeDef  CAN_InitStructure;
+    CAN_FilterInitTypeDef CAN_FilterInitStructure;
+
+    // 1. 时钟
+    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
+    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
+
+    // 2. 重映射
+    GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
+
+    // 3. GPIO
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;         // RX 上拉输入
+    GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
+    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;       // TX 复用推挽
+    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
+    GPIO_Init(GPIOB, &GPIO_InitStructure);
+
+    // 4. CAN 初始化
+    CAN_DeInit(CAN1);
+    CAN_StructInit(&CAN_InitStructure);
+    CAN_InitStructure.CAN_TTCM = DISABLE;
+    CAN_InitStructure.CAN_ABOM = ENABLE;
+    CAN_InitStructure.CAN_AWUM = ENABLE;
+    CAN_InitStructure.CAN_NART = DISABLE;
+    CAN_InitStructure.CAN_RFLM = DISABLE;
+    CAN_InitStructure.CAN_TXFP = DISABLE;
+    CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;      // 回环模式
+    CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
+    CAN_InitStructure.CAN_BS1 = CAN_BS1_2tq;
+    CAN_InitStructure.CAN_BS2 = CAN_BS2_5tq;
+    CAN_InitStructure.CAN_Prescaler = 35;
+    CAN_Init(CAN1, &CAN_InitStructure);
+
+    // 5. 过滤器
+    CAN_FilterInitStructure.CAN_FilterNumber = 0;
+    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList;
+    CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
+    CAN_FilterInitStructure.CAN_FilterIdHigh = 0x06E << 5;
+    CAN_FilterInitStructure.CAN_FilterIdLow = 0;
+    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x7F1 << 5;
+    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0;
+    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
+    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
+    CAN_FilterInit(&CAN_FilterInitStructure);
+}
+```
+
+### 收发函数
+
+```c
+// 发送
+uint8_t canTxData[8] = "abcdefg";
+CanTxMsg TxMessage;
+TxMessage.StdId = 0x066;
+TxMessage.ExtId = 0;
+TxMessage.IDE = CAN_Id_Standard;
+TxMessage.RTR = CAN_RTR_Data;
+TxMessage.DLC = 7;
+for (uint8_t i = 0; i < 7; i++)
+    TxMessage.Data[i] = canTxData[i];
+CAN_Transmit(CAN1, &TxMessage);
+
+// 接收
+CanRxMsg RxMessage;
+if (CAN_MessagePending(CAN1, CAN_FIFO0) > 0) {
+    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
+    // RxMessage.StdId, RxMessage.Data, RxMessage.DLC
+}
+```
+
+---
+
+## 5. HAL 库 CAN 驱动
+
+STM32Cube HAL 库通过 `CAN_HandleTypeDef` 句柄管理 CAN 外设。
+
+### 初始化
+
+```c
+CAN_HandleTypeDef hcan1;
+
+void MX_CAN1_Init(void)
+{
+    hcan1.Instance = CAN1;
+    hcan1.Init.Prescaler = 35;                      // BRP
+    hcan1.Init.Mode = CAN_MODE_LOOPBACK;            // 回环模式
+    hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
+    hcan1.Init.TimeSeg1 = CAN_BS1_2TQ;              // BS1
+    hcan1.Init.TimeSeg2 = CAN_BS2_5TQ;              // BS2
+    hcan1.Init.TimeTriggeredMode = DISABLE;
+    hcan1.Init.AutoBusOff = ENABLE;                 // ABOM
+    hcan1.Init.AutoWakeUp = ENABLE;                 // AWUM
+    hcan1.Init.AutoRetransmission = DISABLE;
+    hcan1.Init.ReceiveFifoLocked = DISABLE;
+    hcan1.Init.TransmitFifoPriority = DISABLE;
+    HAL_CAN_Init(&hcan1);
+
+    // 过滤器配置
+    CAN_FilterTypeDef sFilterConfig;
+    sFilterConfig.FilterBank = 0;
+    sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST;
+    sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
+    sFilterConfig.FilterIdHigh = 0x06E << 5;
+    sFilterConfig.FilterIdLow = 0;
+    sFilterConfig.FilterMaskIdHigh = 0x7F1 << 5;
+    sFilterConfig.FilterMaskIdLow = 0;
+    sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
+    sFilterConfig.FilterActivation = ENABLE;
+    sFilterConfig.SlaveStartFilterBank = 14;
+    HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
+}
+```
+
+### MSP 初始化
+
+```c
+void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+    if (hcan->Instance == CAN1) {
+        __HAL_RCC_CAN1_CLK_ENABLE();
+        __HAL_RCC_GPIOB_CLK_ENABLE();
+        __HAL_RCC_AFIO_CLK_ENABLE();
+
+        GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
+
+        // PB8 — RX, 上拉输入
+        GPIO_InitStruct.Pin = GPIO_PIN_8;
+        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
+        GPIO_InitStruct.Pull = GPIO_PULLUP;
+        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+
+        // PB9 — TX, 复用推挽
+        GPIO_InitStruct.Pin = GPIO_PIN_9;
+        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+    }
+}
+```
+
+### 收发函数
+
+```c
+// 启动 CAN
+HAL_CAN_Start(&hcan1);
+
+// 发送
+CAN_TxHeaderTypeDef txHeader;
+uint8_t txData[8] = "abcdefg";
+uint32_t txMailbox;
+
+txHeader.StdId = 0x066;
+txHeader.ExtId = 0;
+txHeader.IDE = CAN_ID_STD;
+txHeader.RTR = CAN_RTR_DATA;
+txHeader.DLC = 7;
+txHeader.TransmitGlobalTime = DISABLE;
+
+HAL_CAN_AddTxMessage(&hcan1, &txHeader, txData, &txMailbox);
+
+// 接收(轮询方式)
+CAN_RxHeaderTypeDef rxHeader;
+uint8_t rxData[8];
+
+if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) {
+    // rxHeader.StdId, rxData, rxHeader.DLC
+}
+```
+
+### HAL vs 标准库 vs 寄存器对照
+
+| 功能         | 寄存器 (bxCAN)              | 标准库                          | HAL 库                               |
+|--------------|-----------------------------|----------------------------------|--------------------------------------|
+| 配置结构     | 直接操作 CAN1->MCR/BTR     | CAN_InitTypeDef                  | CAN_HandleTypeDef.Init               |
+| 过滤器       | CAN1->sFilterRegister[]    | CAN_FilterInitTypeDef            | CAN_FilterTypeDef                    |
+| 使能         | —                           | CAN_Cmd(CAN1, ENABLE)            | HAL_CAN_Start()                      |
+| 发送         | TIR/TDTR/TDLR/TDHR         | CAN_Transmit()                   | HAL_CAN_AddTxMessage()               |
+| 接收         | sFIFOMailBox[0].RIR/RDTR   | CAN_Receive()                    | HAL_CAN_GetRxMessage()               |
+| 中断         | CAN1->IER                   | CAN_ITConfig() + 状态寄存器      | HAL_CAN_ActivateNotification()       |
+
+---
+
+## 6. 实验:回环静默自测
+
+该实验使用回环静默模式,CAN 内部自发自收,无需外部硬件连接。
+
+### main.c
+
+```c
+#include "stm32f10x.h"
+#include "can.h"
+#include <string.h>
+
+int main(void)
+{
+    CAN_Init();
+
+    // 准备发送数据
+    uint16_t stdID = 0x066;
+    uint8_t *data = "abcdefg";
+
+    // 发送
+    CAN_SendMsg(stdID, data, strlen((char *)data));
+
+    // 接收
+    RxMsg rxMsg[3];
+    uint8_t msgCount;
+    CAN_ReceiveMsg(rxMsg, &msgCount);
+
+    // 验证
+    if (msgCount > 0) {
+        // rxMsg[0].stdID == 0x066
+        // rxMsg[0].data == "abcdefg"
+        // rxMsg[0].len == 7
+    }
+
+    while (1) {}
+}
+```
+
+### 预期结果
+
+- 发送邮箱 0 发送完成后 TXOK0 置位
+- FIFO0 中收到一帧,FMP0 = 1
+- 接收到的 ID、数据、长度与发送的完全一致
+- 可在调试器中观察 `rxMsg` 数组确认
+
+### 修改为正常模式
+
+将 `CAN_Init()` 中以下两行注释或删除即可接入真实 CAN 总线:
+
+```c
+// CAN1->BTR |= CAN_BTR_SILM;   // 注释掉静默
+// CAN1->BTR |= CAN_BTR_LBKM;   // 注释掉回环
+```
+
+同时需要修改过滤器为掩码模式(`CAN_FM1R` 对应位设为 1)以接收任意 ID,或在总线上配置对端节点发送匹配的 ID(0x06E / 0x7F1)。
+
+---
+
+## 7. 常见问题
+
+| 问题                   | 可能原因                           | 解决方法                       |
+|------------------------|------------------------------------|--------------------------------|
+| 发送一直等待 TXOK      | 总线没有其他节点应答               | 改为回环模式自测               |
+| 接收不到数据           | 过滤器未配置或未激活               | 检查 FA1R 对应位是否为 1       |
+| 初始化卡在 INAK 等待   | CAN 时钟未开启或位时序配置错误     | 检查 RCC 时钟和 BTR 寄存器     |
+| 波特率不符             | BRP/BS1/BS2 计算错误               | 用示波器测 CAN_TX 脚频率       |
+| 进入总线关闭           | 重复发送失败                       | 开启 ABOM(自动离线恢复)      |

+ 644 - 0
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/17-FSMC与LCD显示.md

@@ -0,0 +1,644 @@
+# STM32 FSMC 外部存储器控制
+
+> 基于尚硅谷(进阶篇·第6/7章)综合整理
+
+---
+
+## 1. FSMC 简介
+
+FSMC(Flexible Static Memory Controller)是 STM32F10x 的**可变静态存储控制器**,可以连接 NOR Flash、PSRAM、SRAM、NAND Flash 和 PC Card。
+
+### 1.1 关键特性
+
+- 支持 3 种存储器类型:NOR/PSRAM/SRAM、NAND Flash、PC Card
+- 4 个片选(NE1~NE4),对应 Bank1 的 4 个区域
+- 数据总线宽度:8/16 位(可配置)
+- 地址总线最高 26 位(HADDR[25:0])
+- 支持复用/非复用模式
+- 支持同步/异步传输
+
+### 1.2 存储映射
+
+FSMC 将外部存储器映射到 Cortex-M3 的 4GB 地址空间的特定区域:
+
+```
+地址范围            存储器类型        片选
+0x6000 0000 ~
+  0x6FFF FFFF      NOR/PSRAM/SRAM   NE1~NE4
+
+0x7000 0000 ~
+  0x7FFF FFFF      NAND Flash       NCE2~NCE3
+
+0x8000 0000 ~
+  0x8FFF FFFF      PC Card          NCE4_1~NCE4_2
+```
+
+Bank1 被分为 4 个区域,每个 64MB:
+
+| 区域   | 地址范围         | 片选引脚 | 使用场景     |
+|--------|------------------|----------|--------------|
+| Region1| 0x6000 0000~     | NE1      | NOR/SRAM     |
+| Region2| 0x6400 0000~     | NE2      | NOR/SRAM     |
+| Region3| 0x6800 0000~     | NE3      | SRAM / LCD   |
+| Region4| 0x6C00 0000~     | NE4      | SRAM / LCD   |
+
+### 1.3 FSMC 内部框图
+
+```
+                        ┌───────────┐
+HCLK ──────────────────→│   FSMC    │
+                        │           │
+A[25:0] / D[15:0] ←───→│  FSMC_NE  ├────→ 片选
+                        │  FSMC_NWE ├────→ 写使能
+                        │  FSMC_NOE ├────→ 读使能
+                        │  FSMC_NBL ├────→ 字节掩码
+                        └───────────┘
+```
+
+---
+
+## 2. 扩展外部 SRAM(IS62WV51216)
+
+### 2.1 IS62WV51216 简介
+
+| 参数       | 规格                       |
+|------------|----------------------------|
+| 容量       | 512K × 16bit = 1MB         |
+| 接口       | 并行异步 SRAM              |
+| 数据总线   | 16 位                      |
+| 地址总线   | 19 位(A0~A18)            |
+| 访问时间   | 45ns/55ns                  |
+| 工作电压   | 2.5V ~ 3.6V                |
+
+### 2.2 硬件连接
+
+```
+STM32 FSMC              IS62WV51216
+─────────               ───────────
+FSMC_A[18:0] ─────────── A[18:0]
+FSMC_D[15:0] ─────────── DQ[15:0]
+FSMC_NE3     ─────────── CS#           (片选, 地址 0x6800 0000)
+FSMC_NWE     ─────────── WE#           (写使能)
+FSMC_NOE     ─────────── OE#           (输出使能)
+FSMC_NBL0    ─────────── LB#           (低字节掩码)
+FSMC_NBL1    ─────────── UB#           (高字节掩码)
+```
+
+### 2.3 FSMC 寄存器配置(尚硅谷风格)
+
+FSMC Bank1 的配置通过两组寄存器完成:
+
+- **BCR** (片选控制寄存器):配置存储器类型、数据宽度、时序模式
+- **BTR** (片选时序寄存器):配置地址建立时间、数据保持时间
+
+每个 Region 对应一组 BCR/BTR,索引关系:
+
+| 区域   | BCR 寄存器 | BTR 寄存器 |
+|--------|------------|------------|
+| Region1| BTCR[0]    | BTCR[1]    |
+| Region2| BTCR[2]    | BTCR[3]    |
+| Region3| BTCR[4]    | BTCR[5]    |
+| Region4| BTCR[6]    | BTCR[7]    |
+
+#### FSMC 初始化函数
+
+```c
+#include "stm32f10x_fsmc.h"
+
+void FSMC_SRAM_Init(void)
+{
+    // 1. 时钟使能
+    RCC->AHBENR |= RCC_AHBENR_FSMCEN;       // FSMC 时钟
+    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;     // FSMC_D[0..3]
+    RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;     // FSMC_D[4..15]
+    RCC->APB2ENR |= RCC_APB2ENR_IOPFEN;     // FSMC_A[0..3], FSMC_NWE, NOE
+    RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;     // FSMC_A[4..18], FSMC_NE3
+
+    // 2. GPIO 配置 — 所有 FSMC 引脚设为复用推挽输出 50MHz
+    // 数据线 PD0~PD15
+    GPIO_SetBits(GPIOD, 0xFFFF);             // 初始电平避免毛刺
+    // ... 实际需要逐组配置 GPIO_CRL/CRH
+
+    // 地址线 PE0/PE1, PF0~PF3, PG0~PG15
+
+    // 控制信号:NWE(PD5), NOE(PD4), NE3(PG10), NBL0(PE0), NBL1(PE1)
+
+    // 3. FSMC 时序配置 — Bank1 Region3 (NE3)
+    // BCR: 片选控制
+    FSMC_Bank1->BTCR[4] = 0;                 // 先清零
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MBKEN;  // 使能存储器 bank
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MTYP;  // MTYP = 00 (SRAM)
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR1_MWID_1; // MWID = 16 位
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR1_MWID_0;
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR1_WREN;   // 写使能
+
+    // BTR: 时序
+    FSMC_Bank1->BTCR[5] = 0;
+    FSMC_Bank1->BTCR[5] |= (0 << 0);          // ADDSET = 0 (地址建立时间)
+    FSMC_Bank1->BTCR[5] |= (3 << 8);          // DATAST = 3 (数据建立时间)
+}
+```
+
+#### GPIO 初始化详细实现
+
+```c
+void FSMC_GPIO_Init(void)
+{
+    // PD0~PD7 (数据低字节) — CRL
+    GPIOD->CRL = 0xBBBBBBBB;     // 复用推挽输出 50MHz
+
+    // PD8~PD15 (数据高字节) — CRH
+    GPIOD->CRH = 0xBBBBBBBB;
+
+    // PD4 (NOE), PD5 (NWE)
+    GPIOD->CRL |= 0x00BB0000;    // 复用推挽
+
+    // PE0, PE1 (NBL0, NBL1)
+    GPIOE->CRL = 0xBBBBBBBB;
+
+    // PF0~PF3 (地址低 4 位)
+    GPIOF->CRL = 0xBBBBBBBB;
+
+    // PG0~PG3 (地址线)
+    // PG4~PG9 (地址线)
+    // PG10 (NE3)
+    // PG12~PG15 (地址线)
+    GPIOG->CRL = 0xBBBBBBBB;
+    GPIOG->CRH = 0xBBBBBBBB;     // 包含 NE3
+}
+```
+
+> **注意**:`0xBBBBBBBB` 表示每 4 位配置为 `1011`,即 MODE=11(50MHz),CNF=10(复用推挽)。
+
+#### 简化版宏配置(尚硅谷风格)
+
+```c
+/* FSMC 参数宏 */
+#define FSMC_BANK1_REGION3    ((uint32_t)0x68000000)
+
+/* 初始化封装 */
+void FSMC_Init(void)
+{
+    // 开时钟
+    RCC->AHBENR |= RCC_AHBENR_FSMCEN;
+
+    // PD 数据线
+    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
+    GPIOD->CRL = 0xBBBBBBBB;
+    GPIOD->CRH = 0xBBBBBBBB;
+
+    // ... 类似配置所有 FSMC 引脚
+
+    // FSMC 控制寄存器
+    FSMC_Bank1->BTCR[4] = (1<<0) | (1<<1) | (1<<4) | (1<<12);
+    // Bit0=MBKEN, Bit1=MTYP0=0(SRAM), Bit4=MWID=16bit, Bit12=WREN
+
+    FSMC_Bank1->BTCR[5] = (0<<0) | (3<<8);   // ADDSET=0, DATAST=3
+}
+```
+
+### 2.4 SRAM 访问
+
+配置完成后,直接通过指针访问映射地址即可读写 SRAM:
+
+```c
+// 方法 1:KEIL 扩展关键字
+uint8_t  v1  __attribute__((at(0x68000000)));    // 8 位变量
+uint16_t v16 __attribute__((at(0x68000002)));    // 16 位变量
+
+// 方法 2:指针直接访问
+uint8_t *p = (uint8_t *)0x68000001;
+*p = 100;
+
+uint16_t *pw = (uint16_t *)0x68000000;
+*pw = 0xAABB;
+
+// 方法 3:数组操作
+uint16_t sram_buf[256];   // 实际上需要指定地址
+// 或使用指针数组
+#define SRAM_BASE   ((uint16_t *)0x68000000)
+SRAM_BASE[0] = 0x1234;    // 写入第一个 16 位单元
+uint16_t val = SRAM_BASE[0];  // 读取
+
+// 内存拷贝示例
+void SRAM_Test(void)
+{
+    uint8_t src[10] = "STM32FSMC";
+    uint8_t dst[10] = {0};
+
+    // 写入 SRAM
+    for (int i = 0; i < 10; i++)
+        ((uint8_t *)0x68000000)[i] = src[i];
+
+    // 从 SRAM 读出
+    for (int i = 0; i < 10; i++)
+        dst[i] = ((uint8_t *)0x68000000)[i];
+
+    // 此时 dst[] == "STM32FSMC"
+}
+```
+
+---
+
+## 3. LCD 显示(ILI9486 + FSMC 8080 时序)
+
+### 3.1 ILI9486 简介
+
+ILI9486 是一款常见的 TFT-LCD 驱动芯片,支持 16-bit/18-bit RGB 接口和 MCU 8080 并行接口。
+
+| 参数         | 规格                    |
+|--------------|-------------------------|
+| 分辨率       | 320 × 480               |
+| 颜色深度     | 16-bit (RGB565)/18-bit  |
+| 接口         | 8080 并行 / SPI / RGB   |
+| 驱动 IC      | ILI9486                  |
+
+### 3.2 8080 并行接口时序
+
+8080 接口是一种异步并行总线,类似 SRAM 的读/写时序,因此 FSMC 可以**无缝驱动** LCD。
+
+关键信号:
+
+| 信号  | 功能                     | 连接                   |
+|-------|--------------------------|------------------------|
+| CS    | 片选(低有效)           | FSMC_NE4 (0x6C00 0000)|
+| RS    | 命令/数据选择            | FSMC_A11               |
+| RD    | 读使能(低有效)         | FSMC_NOE               |
+| WR    | 写使能(低有效)         | FSMC_NWE               |
+| D[15:0]| 16 位数据总线           | FSMC_D[15:0]           |
+
+### 3.3 地址映射
+
+RS 连接到 A11,决定访问的是命令寄存器还是数据寄存器:
+
+```
+LCD 命令地址 (RS=0):
+  0x6C00 0000  (A11 = 0)
+
+LCD 数据地址 (RS=1):
+  0x6C00 0800  (A11 = 1)
+```
+
+```c
+#define LCD_BASE      ((uint32_t)0x6C000000)
+#define LCD_CMD_ADDR  (LCD_BASE)               // RS=0: 命令
+#define LCD_DATA_ADDR (LCD_BASE + 0x00000800)  // RS=1: 数据
+
+// 命令/数据写函数
+#define LCD_WriteCmd(cmd)    (*(volatile uint16_t *)LCD_CMD_ADDR  = (cmd))
+#define LCD_WriteData(data)  (*(volatile uint16_t *)LCD_DATA_ADDR = (data))
+
+// 读数据
+#define LCD_ReadData()       (*(volatile uint16_t *)LCD_DATA_ADDR)
+```
+
+**原理**:FSMC 将外部设备视为存储器,当 CPU 访问 `0x6C000000` 时,FSMC 自动产生片选 NE4 以及 NWE/NOE 时序。A11 的状态由地址的第 12 位决定(A11 = bit 11),所以 `0x6C000800` 的 bit 11 = 1 使 RS 为高。
+
+### 3.4 FSMC 配置(LCD 方式)
+
+LCD 需要通过 FSMC 的 NOR/PSRAM 模式 A 来模拟 8080 时序:
+
+```c
+void FSMC_LCD_Init(void)
+{
+    // 使能 FSMC 时钟和 GPIO
+    RCC->AHBENR |= RCC_AHBENR_FSMCEN;
+    // ... GPIO 配置同 SRAM 部分
+
+    // 使用 Bank1 Region4 (NE4)
+    FSMC_Bank1->BTCR[6] = 0;
+    FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MBKEN;   // 使能 bank
+    FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MTYP;    // MTYP=00(SRAM)
+    FSMC_Bank1->BTCR[6] |= FSMC_BCR1_MWID_1;   // MWID=16bit
+    FSMC_Bank1->BTCR[6] &= ~FSMC_BCR1_MWID_0;
+    FSMC_Bank1->BTCR[6] |= FSMC_BCR1_WREN;     // 写使能
+
+    // 时序:LCD 通常较慢
+    FSMC_Bank1->BTCR[7] = 0;
+    FSMC_Bank1->BTCR[7] |= (1 << 0);            // ADDSET = 1
+    FSMC_Bank1->BTCR[7] |= (2 << 8);            // DATAST = 2
+}
+```
+
+### 3.5 LCD 驱动函数(尚硅谷风格)
+
+#### 基本写操作
+
+```c
+/* 命令和数据写入 */
+void LCD_WriteReg(uint16_t reg)
+{
+    *(volatile uint16_t *)LCD_CMD_ADDR = reg;
+}
+
+void LCD_WriteData(uint16_t data)
+{
+    *(volatile uint16_t *)LCD_DATA_ADDR = data;
+}
+
+/* 组合操作:先写命令,再写数据 */
+void LCD_WriteRegValue(uint16_t reg, uint16_t value)
+{
+    LCD_WriteReg(reg);
+    LCD_WriteData(value);
+}
+```
+
+#### 读操作
+
+```c
+uint16_t LCD_ReadData(void)
+{
+    // 先读一个虚拟数据(FSMC 需要先输出一个读周期)
+    volatile uint16_t dummy = *(volatile uint16_t *)LCD_DATA_ADDR;
+    (void)dummy;
+    // 再读真实数据
+    return *(volatile uint16_t *)LCD_DATA_ADDR;
+}
+
+uint16_t LCD_ReadReg(uint16_t reg)
+{
+    LCD_WriteReg(reg);
+    return LCD_ReadData();
+}
+```
+
+#### LCD 初始化序列(ILI9486)
+
+ILI9486 的初始化通常需要发送一串配置寄存器命令。以下是精简版:
+
+```c
+void LCD_Init(void)
+{
+    // 等待 LCD 上电稳定
+    Delay_ms(120);
+
+    // 软件复位
+    LCD_WriteRegValue(0x01, 0x0000);   // 软件复位
+    Delay_ms(120);
+
+    // 电源控制
+    LCD_WriteRegValue(0xC0, 0x1000);   // POWER_CONTROL_1
+    LCD_WriteRegValue(0xC1, 0x0005);   // POWER_CONTROL_2
+    LCD_WriteRegValue(0xC2, 0x01C0);   // POWER_CONTROL_3 (VCOM)
+    LCD_WriteRegValue(0xC5, 0x0086);   // VCOM_CONTROL
+
+    // 帧率控制
+    LCD_WriteRegValue(0xB0, 0x00);     // 内部帧率
+    LCD_WriteRegValue(0xB1, 0x00B0);   // 帧率 = 70Hz
+    LCD_WriteRegValue(0xB4, 0x0002);   // 显示反相控制
+
+    // Gamma 校正
+    LCD_WriteRegValue(0xE0, 0x00);     // Positive Gamma
+    // ... 完整 Gamma 设置约 15 个寄存器
+
+    // 显示控制
+    LCD_WriteRegValue(0x36, 0x0048);   // 内存访问控制 (旋转/镜像)
+    LCD_WriteRegValue(0x3A, 0x0055);   // 像素格式: 16-bit (RGB565)
+
+    // 退出睡眠
+    LCD_WriteRegValue(0x11, 0x0000);   // SLEEP_OUT
+    Delay_ms(120);
+
+    // 开启显示
+    LCD_WriteRegValue(0x29, 0x0000);   // DISPLAY_ON
+}
+```
+
+#### 绘图函数
+
+```c
+/* 设置坐标窗口 */
+void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
+{
+    LCD_WriteRegValue(0x2A, (x1 >> 8) & 0xFF);    // 列地址起始高8位
+    LCD_WriteRegValue(0x2A, x1 & 0xFF);            // 列地址起始低8位
+    LCD_WriteRegValue(0x2A, (x2 >> 8) & 0xFF);    // 列地址结束高8位
+    LCD_WriteRegValue(0x2A, x2 & 0xFF);            // 列地址结束低8位
+    LCD_WriteRegValue(0x2B, (y1 >> 8) & 0xFF);    // 行地址起始高8位
+    LCD_WriteRegValue(0x2B, y1 & 0xFF);            // 行地址起始低8位
+    LCD_WriteRegValue(0x2B, (y2 >> 8) & 0xFF);    // 行地址结束高8位
+    LCD_WriteRegValue(0x2B, y2 & 0xFF);            // 行地址结束低8位
+    LCD_WriteReg(0x2C);                            // 内存写命令
+}
+
+/* 画点 */
+void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
+{
+    LCD_SetWindow(x, y, x, y);
+    LCD_WriteData(color);
+}
+
+/* 全屏填充 */
+void LCD_Fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
+{
+    uint32_t total = (x2 - x1 + 1) * (y2 - y1 + 1);
+    LCD_SetWindow(x1, y1, x2, y2);
+    for (uint32_t i = 0; i < total; i++)
+        LCD_WriteData(color);
+}
+
+/* 清屏 */
+void LCD_Clear(uint16_t color)
+{
+    LCD_Fill(0, 0, 319, 479, color);
+}
+
+/* 画线(Bresenham 算法) */
+void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
+{
+    int16_t dx = (x2 > x1) ? (x2 - x1) : (x1 - x2);
+    int16_t dy = (y2 > y1) ? (y2 - y1) : (y1 - y2);
+    int16_t sx = (x2 > x1) ? 1 : -1;
+    int16_t sy = (y2 > y1) ? 1 : -1;
+    int16_t err = dx - dy;
+
+    while (1) {
+        LCD_DrawPoint(x1, y1, color);
+        if (x1 == x2 && y1 == y2) break;
+        int16_t e2 = 2 * err;
+        if (e2 > -dy) { err -= dy; x1 += sx; }
+        if (e2 < dx)  { err += dx; y1 += sy; }
+    }
+}
+
+/* 画圆(Bresenham 算法) */
+void LCD_DrawCircle(uint16_t cx, uint16_t cy, uint16_t r, uint16_t color)
+{
+    int16_t x = 0, y = r;
+    int16_t d = 3 - 2 * r;
+    while (x <= y) {
+        LCD_DrawPoint(cx + x, cy + y, color);
+        LCD_DrawPoint(cx - x, cy + y, color);
+        LCD_DrawPoint(cx + x, cy - y, color);
+        LCD_DrawPoint(cx - x, cy - y, color);
+        LCD_DrawPoint(cx + y, cy + x, color);
+        LCD_DrawPoint(cx - y, cy + x, color);
+        LCD_DrawPoint(cx + y, cy - x, color);
+        LCD_DrawPoint(cx - y, cy - x, color);
+        if (d < 0) d += 4 * x + 6;
+        else { d += 4 * (x - y) + 10; y--; }
+        x++;
+    }
+}
+```
+
+#### 字符显示
+
+```c
+/* 显示 ASCII 字符(8×16 点阵) */
+void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc)
+{
+    extern const uint8_t ascii_8x16[][16];  // 字库数组
+
+    if (ch < 32 || ch > 126) ch = ' ';
+    uint8_t idx = ch - 32;
+
+    for (uint8_t row = 0; row < 16; row++) {
+        uint8_t data = ascii_8x16[idx][row];
+        for (uint8_t col = 0; col < 8; col++) {
+            if (data & (0x80 >> col))
+                LCD_DrawPoint(x + col, y + row, fc);   // 前景色
+            else
+                LCD_DrawPoint(x + col, y + row, bc);   // 背景色
+        }
+    }
+}
+
+/* 显示字符串 */
+void LCD_ShowString(uint16_t x, uint16_t y, const char *str, uint16_t fc, uint16_t bc)
+{
+    while (*str) {
+        // 自动换行
+        if (x > 320 - 8) { x = 0; y += 16; }
+        if (y > 480 - 16) break;
+        LCD_ShowChar(x, y, *str, fc, bc);
+        x += 8;
+        str++;
+    }
+}
+
+/* 显示十进制数字 */
+void LCD_ShowNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint16_t fc, uint16_t bc)
+{
+    char buf[12];
+    sprintf(buf, "%*lu", len, (unsigned long)num);
+    LCD_ShowString(x, y, buf, fc, bc);
+}
+
+/* 显示十六进制数字 */
+void LCD_ShowHexNum(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint16_t fc, uint16_t bc)
+{
+    char buf[12];
+    sprintf(buf, "%0*X", len, (unsigned int)num);
+    LCD_ShowString(x, y, buf, fc, bc);
+}
+```
+
+### 3.6 完整初始化调用示例
+
+```c
+void LCD_Init(void);
+
+int main(void)
+{
+    // 初始化 FSMC (LCD 部分)
+    FSMC_LCD_Init();
+
+    // 初始化 LCD 驱动
+    LCD_Init();
+
+    // 清屏为蓝色
+    LCD_Clear(0x001F);        // RGB565: 0x001F = Blue
+
+    // 绘图
+    LCD_DrawLine(0, 0, 319, 479, 0xFFFF);    // 白色对角线
+    LCD_DrawCircle(160, 240, 100, 0xFFE0);   // 黄色圆
+    LCD_ShowString(50, 200, "Hello STM32!", 0xFFFF, 0x0000);
+
+    while (1) {}
+}
+```
+
+---
+
+## 4. 标准库 FSMC 驱动
+
+标准外设库将 FSMC 配置封装为 `FSMC_NORSRAMInit()` 等函数:
+
+```c
+void FSMC_LCD_StdPeriph_Init(void)
+{
+    FSMC_NORSRAMInitTypeDef FSMC_InitStructure;
+    FSMC_NORSRAMTimingInitTypeDef FSMC_TimingStructure;
+
+    // 时序:ADDSET=1, DATAST=2
+    FSMC_TimingStructure.FSMC_AddressSetupTime = 1;
+    FSMC_TimingStructure.FSMC_AddressHoldTime = 0;
+    FSMC_TimingStructure.FSMC_DataSetupTime = 2;
+    FSMC_TimingStructure.FSMC_BusTurnAroundDuration = 0;
+    FSMC_TimingStructure.FSMC_CLKDivision = 0;
+    FSMC_TimingStructure.FSMC_DataLatency = 0;
+    FSMC_TimingStructure.FSMC_AccessMode = FSMC_AccessMode_A;
+
+    // Bank1 Region4
+    FSMC_InitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
+    FSMC_InitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
+    FSMC_InitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
+    FSMC_InitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
+    FSMC_InitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
+    FSMC_InitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
+    FSMC_InitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
+    FSMC_InitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
+    FSMC_InitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
+    FSMC_InitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
+    FSMC_InitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
+    FSMC_InitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
+    FSMC_InitStructure.FSMC_ReadWriteTimingStruct = &FSMC_TimingStructure;
+    FSMC_InitStructure.FSMC_WriteTimingStruct = &FSMC_TimingStructure;
+
+    FSMC_NORSRAMInit(&FSMC_InitStructure);
+    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);
+}
+```
+
+---
+
+## 5. FSMC 与 8080 时序对比
+
+8080 并行接口的读写时序与 SRAM 的异步读写在 FSMC 视角下完全一致:
+
+```
+写时序(8080):
+  CS#  ──┐   ┌────
+          │   │
+  RS   ───┼───┼────    (决定命令/数据)
+          │   │
+  WR#  ───┴─┐ ┌────
+             │ │
+  D[15:0] ──┘ └────    (有效数据)
+
+读时序(8080):
+  CS#  ──┐   ┌────
+          │   │
+  RS   ───┼───┼────
+          │   │
+  RD#  ───┴─┐ ┌────
+             │ │
+  D[15:0] ──┘ └────    (LCD 输出数据)
+```
+
+FSMC 将上述时序参数化为 ADDSET(地址建立时间)和 DATAST(数据建立时间),自动产生符合 8080 协议的片选、读/写使能和数据信号。
+
+---
+
+## 6. 常见问题
+
+| 问题                   | 可能原因                         | 解决方法                             |
+|------------------------|----------------------------------|--------------------------------------|
+| SRAM 读写值全为 0xFF   | FSMC 未使能或 GPIO 未配置正确    | 检查 AHBENR 和 GPIO CRL/CRH         |
+| LCD 无显示             | RS 地址映射错误                   | 确认 A11 连接,检查 CMD/DATA 地址   |
+| 颜色显示异常           | 像素格式不匹配                    | 检查 0x3A 寄存器值(0x55 = 16-bit) |
+| 数据错位               | 数据总线宽度 / 字节序问题         | 确认 MWID 正确,检查时序 ADDSET     |
+| 访问速度慢             | DATAST 过大                       | 在保证稳定的前提下减小 DATAST       |
+| LCD 花屏               | 初始化序列不完整                  | 检查上电延时和寄存器配置序列         |