浏览代码

fix: 按审计结果重写笔记07~20代码段——从实际源文件复制而非虚构示意代码

OpenCode 1 天之前
父节点
当前提交
7095b111ef
共有 14 个文件被更改,包括 3361 次插入1063 次删除
  1. 71 103
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/07-I2C通信与EEPROM 24C02.md
  2. 99 59
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/08-SysTick与通用定时器TIM.md
  3. 342 118
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/09-TIM高级应用:PWM与输入捕获.md
  4. 195 110
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/10-DMA数据传输.md
  5. 215 99
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/11-ADC模数转换.md
  6. 421 78
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/12-SPI通信与FSMC总线.md
  7. 236 93
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/13-CAN通信协议与bxCAN外设.md
  8. 360 67
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/14-以太网通信与W5500.md
  9. 205 67
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/15-WiFi通信与ESP32-C3.md
  10. 123 44
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/16-BLE低功耗蓝牙.md
  11. 285 63
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/17-LoRa远距离通信.md
  12. 247 32
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/18-电源管理与低功耗模式.md
  13. 417 100
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/19-BKP备份寄存器与RTC实时时钟.md
  14. 145 30
      X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/20-看门狗IWDG与WWDG.md

+ 71 - 103
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/07-I2C通信与EEPROM 24C02.md

@@ -172,21 +172,19 @@ I2C 总线需要外部上拉电阻(典型值 4.7KΩ),因为 I2C 引脚使
 #define __I2C_H
 
 #include "stm32f10x.h"
+#include "usart.h"
+#include "delay.h"
 
-// GPIO 引脚: PB10=SCL, PB11=SDA
-// 使用宏定义简化位操作,提升代码可读性
-#define SCL_HIGH  (GPIOB->ODR |=  GPIO_ODR_ODR10)   // |= 将ODR10置1
-#define SCL_LOW   (GPIOB->ODR &= ~GPIO_ODR_ODR10)   // &=~ 将ODR10清0
-#define SDA_HIGH  (GPIOB->ODR |=  GPIO_ODR_ODR11)   // |= 将ODR11置1
-#define SDA_LOW   (GPIOB->ODR &= ~GPIO_ODR_ODR11)   // &=~ 将ODR11清0
-#define READ_SDA  (GPIOB->IDR &   GPIO_IDR_IDR11)   // & 读取IDR11位
+// PB10=SCL, PB11=SDA
+#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 ACK  0
 #define NACK 1
-
-// I2C 延时: 100KHz 模式下 10us ≈ 半个时钟周期
-// 调整此值可改变 I2C 通信速率
-#define I2C_DELAY  Delay_us(10)
+#define I2C_DELAY Delay_us(10)
 
 void I2C_Init(void);
 void I2C_Start(void);
@@ -205,177 +203,147 @@ uint8_t I2C_ReadByte(void);
 ```c
 #include "i2c.h"
 
-// I2C 初始化: PB10(SCL) + PB11(SDA) 开漏输出
 void I2C_Init(void)
 {
-    // 1. 开启 GPIOB 时钟
-    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;       // |=: IOPBEN位置1
-
-    // 2. PB10, PB11 开漏输出 50MHz
-    // CNF=01(开漏输出), MODE=11(50MHz)
-    // MODE10[1:0] 在 CRH 第 8~9 位, CNF10[1:0] 在第 10~11 位
-    // MODE11[1:0] 在 CRH 第 12~13 位, CNF11[1:0] 在第 14~15 位
-    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);       // MODE=11
-    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);    // CNFx[1]=0
-    GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);     // CNFx[0]=1
-    // CNF[1:0]=01 → 开漏输出
+    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);
 }
 
-// 起始条件: SCL=1 时 SDA 下降沿
 void I2C_Start(void)
 {
-    SCL_HIGH;   // 先确保 SCL 高电平(准备总线)
-    SDA_HIGH;   // SDA 先高
+    SCL_HIGH;
+    SDA_HIGH;
     I2C_DELAY;
-    SDA_LOW;    // SDA 从高变低 → 起始条件
+    SDA_LOW;
     I2C_DELAY;
-    // 起始后 SCL 可拉低准备传输数据
-    SCL_LOW;
 }
 
-// 停止条件: SCL=1 时 SDA 上升沿
 void I2C_Stop(void)
 {
-    SCL_LOW;    // 先确保 SCL 低
-    SDA_LOW;    // SDA 拉低
+    SCL_HIGH;
+    SDA_LOW;
     I2C_DELAY;
-    SCL_HIGH;   // SCL 先高
-    I2C_DELAY;
-    SDA_HIGH;   // SDA 从低变高 → 停止条件
+    SDA_HIGH;
     I2C_DELAY;
 }
 
-// 发送应答: 在第 9 个时钟拉低 SDA
 void I2C_Ack(void)
 {
-    SDA_LOW;    // SDA 拉低 = 应答
+    SDA_HIGH;
+    SCL_LOW;
     I2C_DELAY;
-    SCL_HIGH;   // SCL 脉冲(从机采样)
+    SDA_LOW;
+    I2C_DELAY;
+    SCL_HIGH;
     I2C_DELAY;
     SCL_LOW;
     I2C_DELAY;
-    SDA_HIGH;   // 释放 SDA
+    SDA_HIGH;
+    I2C_DELAY;
 }
 
-// 发送非应答: 在第 9 个时钟释放 SDA
 void I2C_NAck(void)
 {
-    SDA_HIGH;   // SDA 高 = 非应答
+    SDA_HIGH;
+    SCL_LOW;
     I2C_DELAY;
-    SCL_HIGH;   // SCL 脉冲
+    SCL_HIGH;
     I2C_DELAY;
     SCL_LOW;
     I2C_DELAY;
 }
 
-// 等待从机应答: 返回 0=ACK, 1=NACK
 uint8_t I2C_Wait4Ack(void)
 {
-    SDA_HIGH;   // 释放 SDA(让从机控制)
+    SDA_HIGH;
+    SCL_LOW;
     I2C_DELAY;
-    SCL_HIGH;   // SCL 高 = 从机可应答
+    SCL_HIGH;
     I2C_DELAY;
-
-    uint8_t ack = READ_SDA;   // & 测试: 读取 SDA 电平
-    // SDA=0 → 从机应答(ACK); SDA=1 → 从机非应答(NACK)
-
-    SCL_LOW;    // 完成应答位
+    uint8_t ack = READ_SDA;
+    SCL_LOW;
     I2C_DELAY;
-
     return ack ? NACK : ACK;
 }
 
-// 发送 1 字节(MSB first)
 void I2C_SendByte(uint8_t byte)
 {
     for (uint8_t i = 0; i < 8; i++)
     {
-        // SCL 低电平时改变 SDA
-        if (byte & 0x80)     // &测试: 最高位=1?
+        SCL_LOW;
+        I2C_DELAY;
+        if (byte & 0x80)
             SDA_HIGH;
         else
             SDA_LOW;
-
-        byte <<= 1;           // 左移, 准备下一位
-
-        // SCL 高电平期间 SDA 保持稳定(从机采样)
+        I2C_DELAY;
         SCL_HIGH;
         I2C_DELAY;
         SCL_LOW;
         I2C_DELAY;
+        byte <<= 1;
     }
 }
 
-// 读取 1 字节(MSB first)
 uint8_t I2C_ReadByte(void)
 {
-    uint8_t byte = 0;
-
-    SDA_HIGH;   // 释放 SDA(让从机控制)
-
+    uint8_t data = 0;
     for (uint8_t i = 0; i < 8; i++)
     {
-        byte <<= 1;           // 左移留出低位
-
-        SCL_HIGH;             // SCL 高 → 从机将数据放到 SDA
+        SDA_HIGH;
+        SCL_LOW;
         I2C_DELAY;
-
-        if (READ_SDA)         // & 测试: 读取 SDA 电平
-            byte |= 1;        // 如果 SDA=1, 置当前位为 1
-
-        SCL_LOW;              // SCL 低 → 从机准备下一位
+        SCL_HIGH;
+        I2C_DELAY;
+        data <<= 1;
+        if (READ_SDA)
+            data |= 0x01;
+        SCL_LOW;
         I2C_DELAY;
     }
-
-    return byte;
+    return data;
 }
 ```
 
 **文件:`stm32_base/13_i2c_software_register/User/main.c`**
 
 ```c
-#include "i2c.h"
+#include "usart.h"
+#include "m24c02.h"
+#include <string.h>
 
 int main(void)
 {
-    USART1_Init();     // 串口初始化(调试输出用)
-    I2C_Init();        // I2C GPIO 初始化
+    USART_Init();
+    M24C02_Init();
 
-    /* ========== 写操作: 向 24C02 地址 0x00 写入 0x55 ========== */
-    I2C_Start();
-    I2C_SendByte(0xA0);      // 从机地址 + 写标志 (0xA0 = 1010 0000)
-    I2C_Wait4Ack();          // 等待 24C02 应答
+    printf("尚硅谷 I2C 软件模拟实验...\n");
 
-    I2C_SendByte(0x00);      // 要写入的 24C02 内部字节地址
-    I2C_Wait4Ack();
+    M24C02_WriteByte(0x00, 'a');
+    M24C02_WriteByte(0x01, 'b');
+    M24C02_WriteByte(0x02, 'c');
 
-    I2C_SendByte(0x55);      // 要写入的数据
-    I2C_Wait4Ack();
-    I2C_Stop();
+    uint8_t byte1 = M24C02_ReadByte(0x00);
+    uint8_t byte2 = M24C02_ReadByte(0x01);
+    uint8_t byte3 = M24C02_ReadByte(0x02);
 
-    // 等待 24C02 内部写周期完成(约 5ms)
-    Delay_ms(10);
+    printf("%c\n%c\n%c\n", byte1, byte2, byte3);
 
-    /* ========== 读操作: 从 24C02 地址 0x00 读取数据 ========== */
-    // 组合格式: START + 写地址 + 寄存器地址 + RESTART + 读地址 + 数据
-    I2C_Start();
-    I2C_SendByte(0xA0);      // 写地址(先写寄存器地址)
-    I2C_Wait4Ack();
-    I2C_SendByte(0x00);      // 要读取的字节地址
-    I2C_Wait4Ack();
+    M24C02_WriteBytes(0x00, "123456", 6);
 
-    I2C_Start();              // RESTART: 不发送 STOP 直接发第二个 START
-    I2C_SendByte(0xA1);      // 读地址
-    I2C_Wait4Ack();
+    uint8_t buff[50] = {0};
+    M24C02_ReadBytes(0x00, buff, 6);
+    printf("%s\n", buff);
 
-    uint8_t data = I2C_ReadByte();  // 读取数据
-    I2C_NAck();               // 最后一字节主机发 NACK
-    I2C_Stop();
+    memset(buff, 0, sizeof(buff));
 
-    printf("写入 0x55, 读出 0x%02X\r\n", data);
-    // 预期输出: "写入 0x55, 读出 0x55"
+    M24C02_WriteBytes(0x05, "1234567890abcdefghijk", 21);
+    M24C02_ReadBytes(0x00, buff, 21);
+    printf("%s\n", buff);
 
-    while (1);
+    while (1) {}
 }
 ```
 

+ 99 - 59
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/08-SysTick与通用定时器TIM.md

@@ -174,36 +174,52 @@ CK_PSC = TIM 时钟频率(通常是 72MHz)
 
 **项目路径**:`stm32_base/16_systick_led_twinkle_register`
 
-**文件:`stm32_base/16_systick_led_twinkle_register/Hardware/SysTick/systick.c`**
+**文件:`stm32_base/16_systick_led_twinkle_register/User/systick.h`**
+
+```c
+#ifndef __SYSTICK_H
+#define __SYSTICK_H
+
+#include "stm32f10x.h"
+#include "led.h"
+
+void Systick_Init(void);
+
+#endif
+```
+
+**文件:`stm32_base/16_systick_led_twinkle_register/User/systick.c`**
 
 ```c
 #include "systick.h"
 
-// SysTick 初始化:产生 1ms 周期中断
 void Systick_Init(void)
 {
-    // 选择 AHB/8 = 9MHz(更准确,且与 FreeRTOS 常用配置兼容)
-    // 1ms 中断: LOAD = 9MHz / 1000 - 1 = 8999
-    // 使用 AHB/8 可避免 SysTick 频率过高导致频繁中断
-
-    // CLKSOURCE=0 (AHB/8=9MHz), TICKINT=1, ENABLE=1
-    SysTick->LOAD = 9000 - 1;             // 9MHz/1000 = 9K, 9000-1
-    SysTick->VAL  = 0;                    // 清当前值
-    SysTick->CTRL = SysTick_CTRL_TICKINT_Msk  // |= TICKINT=1
-                  | SysTick_CTRL_ENABLE_Msk;  // |= ENABLE=1
-    // 注意: CLKSOURCE 默认为 0(上电复位值)
+    // 1. 设置初始装载的值,1ms 产生一次中断,72000 时钟周期
+    SysTick->LOAD = 72000 - 1;
+
+    // 2. 设置时钟源
+    SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;
+
+    // 3. 使能中断
+    SysTick->CTRL |= SysTick_CTRL_TICKINT;
+
+    // 4. 启动定时器
+    SysTick->CTRL |= SysTick_CTRL_ENABLE;
 }
 
-// SysTick 中断服务函数(由启动文件的中断向量表引用)
+// 全局变量作为中断计数
+uint16_t count = 0;
+
 void SysTick_Handler(void)
 {
-    static uint16_t tick_count = 0;
+    count++;
 
-    tick_count++;
-    if (tick_count >= 1000)               // 1000 × 1ms = 1s
+    // 计数到 1000 次中断,即是1s,翻转LED1
+    if (count == 1000)
     {
-        LED_Toggle(LED_1);               // 翻转 LED(每秒闪烁一次)
-        tick_count = 0;
+        LED_Toggle(LED_1);
+        count = 0;
     }
 }
 ```
@@ -212,55 +228,79 @@ void SysTick_Handler(void)
 
 **项目路径**:`stm32_base/18_tim_led_twinkle_register`
 
-**文件:`stm32_base/18_tim_led_twinkle_register/Hardware/TIM5/tim5.c`**
+**文件:`stm32_base/18_tim_led_twinkle_register/Hardware/TIM/tim6.h`**
+
+```c
+#ifndef __TIM6_H
+#define __TIM6_H
+
+#include "stm32f10x.h"
+#include "led.h"
+
+void TIM6_Init(void);
+
+#endif
+```
+
+**文件:`stm32_base/18_tim_led_twinkle_register/Hardware/TIM/tim6.c`**
 
 ```c
-#include "tim5.h"
+#include "tim6.h"
 
-// TIM5 初始化:1s 溢出一次,产生更新中断
-void TIM5_Init(void)
+void TIM6_Init(void)
 {
-    /* ========== 1. 开启 TIM5 时钟 ========== */
-    // TIM5 挂载在 APB1 总线上(APB1 时钟 = 36MHz)
-    // 但若 APB1 分频≠1,则定时器时钟 = APB1 × 2 = 72MHz
-    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;     // |=: TIM5时钟使能
-
-    /* ========== 2. 配置 PSC 和 ARR ========== */
-    // 目标: Tout = 1s
-    // CK_PSC = 72MHz, PSC = 7200 - 1 → CK_CNT = 72M/7200 = 10KHz
-    // ARR = 10000 - 1 → Tout = 10000 / 10KHz = 1s
-    TIM5->PSC = 7200 - 1;                   // 预分频: 72MHz → 10KHz
-    TIM5->ARR = 10000 - 1;                  // 自动重装载值
-
-    /* ========== 3. 清计数器 ========== */
-    TIM5->CNT = 0;                           // 从 0 开始计数
-
-    /* ========== 4. 使能更新中断 ========== */
-    // UIE = 1: 允许更新事件产生中断
-    TIM5->DIER |= TIM_DIER_UIE;             // |=: UIE位置1
-
-    /* ========== 5. 使能 NVIC 中断 ========== */
-    // TIM5 的中断号 = TIM5_IRQn
-    NVIC_EnableIRQ(TIM5_IRQn);              // 在 NVIC 中使能 TIM5 中断
-
-    /* ========== 6. 启动定时器 ========== */
-    // CEN=1: 计数器开始工作
-    TIM5->CR1 |= TIM_CR1_CEN;               // |=: CEN位置1
+    // 1. 使能定时器
+    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
+
+    // 2. 设置预分频值 7199,表示7200分频得到10000Hz(100us)
+    TIM6->PSC = 7199;
+
+    // 3. 设置自动装载值 99,表示 100 次计数得到 1s
+    TIM6->ARR = 99;
+
+    // 4. 打开更新中断使能
+    TIM6->DIER |= TIM_DIER_UIE;
+
+    // 5. NVIC配置
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(TIM6_IRQn, 2);
+    NVIC_EnableIRQ(TIM6_IRQn);
+
+    // 6. 使能计数器
+    TIM6->CR1 = TIM_CR1_CEN;
 }
 
-// TIM5 中断服务函数
-void TIM5_IRQHandler(void)
+// 中断服务函数
+void TIM6_IRQHandler(void)
 {
-    // 检查 SR.UIF(更新中断标志位)
-    // UIF=1 表示计数器从 ARR 溢出回 0,产生了更新事件
-    if (TIM5->SR & TIM_SR_UIF)              // &测试: UIF位=1?
-    {
-        LED_Toggle(LED_1);                  // 翻转 LED
+    // 清除中断标志位
+    TIM6->SR &= ~TIM_SR_UIF;
 
-        // 清除 UIF 标志(必须软件清除,否则持续进中断)
-        // 注意: SR 寄存器是"写 0 清除"
-        TIM5->SR &= ~TIM_SR_UIF;            // &=~: UIF位清0
-    }
+    // 每1s翻转一次LED2
+    LED_Toggle(LED_2);
+}
+```
+
+**文件:`stm32_base/18_tim_led_twinkle_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "delay.h"
+#include "led.h"
+#include "tim6.h"
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	LED_Init();
+	TIM6_Init();
+
+	printf("Hello world!\n");
+
+	while (1)
+	{
+	}
 }
 ```
 

+ 342 - 118
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/09-TIM高级应用:PWM与输入捕获.md

@@ -92,11 +92,11 @@ TIM 的 ARR 和 CCRx 都有**影子寄存器**机制:
 
 **项目路径**:`stm32_base/20_tim_led_breathe_register`
 
-**需求**:使用 TIM5_CH1 输出 PWM 到 PA0(LED1),占空比 0~99% 循环渐变。
+**需求**:使用 TIM5_CH2 输出 PWM 到 PA1(LED1),占空比 0~99% 循环渐变。
 
-**硬件电路**:LED1 = PA0,TIM5_CH1 的输出通道可通过重映射连接到 PA0
+**硬件电路**:LED1 = PA1,TIM5_CH2 默认映射到 PA1(无需重映射)
 
-**文件:`stm32_base/20_tim_led_breathe_register/Hardware/TIM5/tim5.h`**
+**文件:`stm32_base/20_tim_led_breathe_register/Hardware/TIM/tim5.h`**
 
 ```c
 #ifndef __TIM5_H
@@ -104,100 +104,132 @@ TIM 的 ARR 和 CCRx 都有**影子寄存器**机制:
 
 #include "stm32f10x.h"
 
+// 初始化
 void TIM5_Init(void);
+
+// 定时器的启动和关闭
 void TIM5_Start(void);
-void TIM5_SetDutyCycle(uint8_t duty);  // duty: 0~99
+void TIM5_Stop(void);
+
+// 设置占空比,传入百分比值
+void TIM5_SetDutyCycle(uint8_t duty);
 
 #endif
 ```
 
-**文件:`stm32_base/20_tim_led_breathe_register/Hardware/TIM5/tim5.c`**
+**文件:`stm32_base/20_tim_led_breathe_register/Hardware/TIM/tim5.c`**
 
 ```c
 #include "tim5.h"
 
+// 初始化
 void TIM5_Init(void)
 {
-    /* ========== 1. 开启时钟 ========== */
-    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;      // TIM5 时钟使能(APB1)
-    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;       // GPIOA 时钟使能
-
-    /* ========== 2. 配置 PA0 为复用功能推挽输出 ========== */
-    // TIM5_CH1 输出到 PA0(需要 AFIO 重映射)
-    // CNF=10(复用推挽), MODE=11(50MHz)
-    GPIOA->CRL &= ~GPIO_CRL_CNF0;             // &=~: CNF0清0
-    GPIOA->CRL |= GPIO_CRL_CNF0_1;            // |=: CNF0[1]=1
-    // CNF[1:0] = 10 → 复用功能推挽
-    GPIOA->CRL |= GPIO_CRL_MODE0;             // |=: MODE0[1:0]=11
-
-    /* ========== 3. 配置 TIM5 时基 ========== */
-    // PWM 频率 = 72MHz / 72 / 1000 = 1KHz
-    TIM5->PSC = 72 - 1;                       // 预分频: 72MHz → 1MHz
-    TIM5->ARR = 1000 - 1;                     // 重装载: 1MHz/1000 = 1KHz
-
-    /* ========== 4. 配置通道1输出比较模式 ========== */
-    // CCMR1 控制 CH1 和 CH2
-    // OC1M[2:0] = 110 → PWM 模式1
-    TIM5->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;  // 设置 OC1M bit2=1, bit1=1
-    // OC1PE = 1 → 预装载使能(CCR1 修改后等待 UEV 才更新)
-    TIM5->CCMR1 |= TIM_CCMR1_OC1PE;           // |=: OC1PE位置1
-
-    // CCER: CC1E = 1 → CH1 输出使能
-    // CC1P = 0 → 高电平有效(CNT<CCR 输出高)
-    TIM5->CCER |= TIM_CCER_CC1E;              // |=: CC1E位置1
-
-    /* ========== 5. 初始占空比 ========== */
-    TIM5->CCR1 = 0;                           // 初始占空比 0%
-
-    /* ========== 6. 使能自动重装载预装载 ========== */
-    TIM5->CR1 |= TIM_CR1_ARPE;               // |=: ARPE=1
+    // 1. 开启时钟
+    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+
+    // 2. GPIO配置,复用推挽输出CNF = 10, MODE = 11
+    GPIOA->CRL |= GPIO_CRL_MODE1;
+    GPIOA->CRL |= GPIO_CRL_CNF1_1;
+    GPIOA->CRL &= ~GPIO_CRL_CNF1_0;
+
+    // 3. 定时器时基单元
+    // 3.1 预分频值 7199,得到 10000Hz,计数周期 0.1ms
+    TIM5->PSC = 7199;
+
+    // 3.2 自动重装值 99,每100次计数溢出一次,10ms
+    TIM5->ARR = 99;
+
+    // 3.3 计数方向默认递增
+    TIM5->CR1 &= ~TIM_CR1_DIR;
+
+    // 4. 定时器输出比较部分
+    // 4.1 通道2设置为输出模式,CC2S = 00
+    TIM5->CCMR1 &= ~TIM_CCMR1_CC2S;
+
+    // 4.2 配置通道2输出比较模式,OC2M = 110,PWM模式1
+    TIM5->CCMR1 |= TIM_CCMR1_OC2M_2;
+    TIM5->CCMR1 |= TIM_CCMR1_OC2M_1;
+    TIM5->CCMR1 &= ~TIM_CCMR1_OC2M_0; 
+
+    // 4.3 配置通道极性
+    TIM5->CCER &= ~TIM_CCER_CC2P;
+
+    // 4.4 使能输出通道
+    TIM5->CCER |= TIM_CCER_CC2E;
 }
 
-// 启动 TIM5 计数器
+// 定时器的启动和关闭
 void TIM5_Start(void)
 {
-    TIM5->CR1 |= TIM_CR1_CEN;                 // |=: CEN=1, 开始计数
+    TIM5->CR1 |= TIM_CR1_CEN;
+}
+
+void TIM5_Stop(void)
+{
+    TIM5->CR1 &= ~TIM_CR1_CEN;
 }
 
-// 设置占空比
-// duty: 0~99 对应占空比 0%~99%
-// CCR1 = duty × 10(因为 ARR=999, 1% 对应 10 个计数)
+// 设置占空比,传入百分比值
 void TIM5_SetDutyCycle(uint8_t duty)
 {
-    if (duty > 99) duty = 99;                 // 限制范围
-    TIM5->CCR1 = duty * 10;                  // CCR1 = 0, 10, 20, ..., 990
+    TIM5->CCR2 = duty;
 }
 ```
 
 **文件:`stm32_base/20_tim_led_breathe_register/User/main.c`**
 
 ```c
-#include "tim5.h"
+#include "usart.h"
 #include "delay.h"
+#include "tim5.h"
 
 int main(void)
 {
-    TIM5_Init();
-    TIM5_Start();
-
-    uint8_t duty = 0;     // 当前占空比 (0~99)
-    uint8_t dir  = 0;     // 方向: 0=增加占空比, 1=减小
-    uint8_t step = 1;     // 每次改变量
-
-    while (1)
-    {
-        // 修改占空比
-        if (dir == 0) {
-            duty += step;
-            if (duty >= 99) dir = 1;   // 到 99% 转向减小
-        } else {
-            duty -= step;
-            if (duty <= 0)  dir = 0;   // 到 0% 转向增加
-        }
-
-        TIM5_SetDutyCycle(duty);        // 更新 CCR1
-        Delay_ms(10);                   // 每 10ms 改变一次,呼吸周期 ≈ 2s
-    }
+	// 初始化
+	USART_Init();
+	TIM5_Init();
+
+	printf("Hello world!\n");
+
+	// 启动定时器
+	TIM5_Start();
+
+	// 定义一个变量保存当前的占空比
+	uint8_t duty = 0;
+	// 定义占空比变化方向和步长
+	uint8_t dir = 0;	// 0 - 增加 1 - 减小
+	uint8_t step = 1;
+
+	while (1)
+	{
+		// 判断方向,计算新的占空比
+		if (dir == 0)
+		{
+			// 占空比增加
+			duty += step;
+			// 增加到99就反转方向
+			if (duty >= 99)
+			{
+				dir = 1;
+			}
+		}
+		else
+		{
+			// 占空比减小
+			duty -= step;
+			// 减小到0就反转方向
+			if (duty <= 0)
+			{
+				dir = 0;
+			}
+		}
+		
+		TIM5_SetDutyCycle(duty);
+
+		Delay_ms(10);
+	}
 }
 ```
 
@@ -205,26 +237,73 @@ int main(void)
 
 ## 实验:PWM 周期/占空比独立控制
 
-**项目路径**:`stm32_base/22_tim_pwm_cycle_register`(周期控制)
-**项目路径**:`stm32_base/24_tim_pwm_duty_register`(占空比控制)
+**项目路径**:`stm32_base/22_tim_pwm_cycle_register`(周期捕获)
+**项目路径**:`stm32_base/24_tim_pwm_duty_register`(占空比捕获)
+
+两个项目共用同一份 TIM5 输出 + TIM4 输入捕获代码。区别仅在 main.c 的测量逻辑。
+
+**文件:`stm32_base/22_tim_pwm_cycle_register/User/main.c`**
 
 ```c
-// 周期控制:固定占空比 50%,改变频率
-// 通过修改 PSC 或 ARR 改变频率
-void PWM_SetFreq(uint16_t freq)
+#include "usart.h"
+#include "delay.h"
+#include "tim5.h"
+#include "tim4.h"
+
+int main(void)
 {
-    // Freq = 72MHz / (PSC+1) / (ARR+1)
-    // 固定 PSC=72-1, 调整 ARR
-    TIM5->ARR = (72000000 / 72 / freq) - 1;
-    // 保持占空比 50%: CCR1 = ARR/2
-    TIM5->CCR1 = TIM5->ARR / 2;
+	// 初始化
+	USART_Init();
+	TIM5_Init();
+	TIM4_Init();
+
+	printf("Hello world!\n");
+
+	// 启动定时器
+	TIM5_Start();
+	TIM4_Start();
+
+	TIM5_SetDutyCycle(50);
+
+	while (1)
+	{
+		// 测量当前周期和频率
+		printf("T = %.2f ms, f = %.2f Hz\n", TIM4_GetPWMCycle(), TIM4_GetPWMFreq());
+		Delay_ms(1000);
+	}
 }
+```
+
+**文件:`stm32_base/24_tim_pwm_duty_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "delay.h"
+#include "tim5.h"
+#include "tim4.h"
 
-// 占空比控制:固定频率 1KHz,改变占空比
-void PWM_SetDuty(uint8_t percent)
+int main(void)
 {
-    // ARR 固定 999, 改变 CCR1
-    TIM5->CCR1 = (uint16_t)((uint32_t)999 * percent / 100);
+	// 初始化
+	USART_Init();
+	TIM5_Init();
+	TIM4_Init();
+
+	printf("Hello world!\n");
+
+	// 启动定时器
+	TIM5_Start();
+	TIM4_Start();
+
+	TIM5_SetDutyCycle(32);
+
+	while (1)
+	{
+		// 测量当前周期、频率和占空比
+		printf("T = %.2f ms, f = %.2f Hz\n, duty = %.2f %%\n", 
+			TIM4_GetPWMCycle(), TIM4_GetPWMFreq(), TIM4_GetPWMDuty() * 100);
+		Delay_ms(1000);
+	}
 }
 ```
 
@@ -244,61 +323,206 @@ void PWM_SetDuty(uint8_t percent)
 5. 在 ISR 中读取 CCRx,计算差值
 ```
 
+### 输入捕获实现(TIM4 单通道测周期/频率)
+
+**项目路径**:`stm32_base/22_tim_pwm_cycle_register`
+
+TIM4_CH1(PB6)捕获 PWM 输入上升沿,在中断中复位 CNT,直接读取 CCR1 获得周期。
+
+**文件:`stm32_base/22_tim_pwm_cycle_register/Hardware/TIM/tim4.h`**
+
 ```c
-// 初始化 TIM2_CH1(PA0) 为输入捕获模式
-void TIM2_Capture_Init(void)
+#ifndef __TIM4_H
+#define __TIM4_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void TIM4_Init(void);
+
+// 控制
+void TIM4_Start(void);
+void TIM4_Stop(void);
+
+// 获取PWM周期和频率
+double TIM4_GetPWMCycle(void);
+double TIM4_GetPWMFreq(void);
+
+#endif
+```
+
+**文件:`stm32_base/22_tim_pwm_cycle_register/Hardware/TIM/tim4.c`**
+
+```c
+#include "tim4.h"
+
+// 初始化
+void TIM4_Init(void)
 {
-    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
-    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    // 1. 开启时钟
+    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
 
-    // PA0 浮空输入
-    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
+    // 2. GPIO配置,浮空输入,CNF = 01,MODE = 00
+    GPIOB->CRL &= ~GPIO_CRL_MODE6;
+    GPIOB->CRL &= ~GPIO_CRL_CNF6_1;
+    GPIOB->CRL |= GPIO_CRL_CNF6_0;
 
-    // TIM2_CH1 映射到 PA0(默认)
-    TIM2->PSC = 72 - 1;             // CK_CNT = 1MHz (分辨率 1us)
-    TIM2->ARR = 0xFFFF;             // 最大 16 位
+    // 3. 定时器时基单元配置
+    // 3.1 预分频值 71,得到 1MHz,计数周期 1us
+    TIM4->PSC = 71;
 
-    // CCMR1: CH1 输入捕获模式(IC1=1 映射到 TI1)
-    TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;  // CC1S=01: CH1→TI1
+    // 3.2 自动重装值 65535,保证最大范围
+    TIM4->ARR = 65535;
 
-    // 捕获边沿: 上升沿
-    TIM2->CCER &= ~TIM_CCER_CC1P;      // CC1P=0: 上升沿
+    // 3.3 计数方向默认递增
+    TIM4->CR1 &= ~TIM_CR1_DIR;
 
-    // 使能捕获中断
-    TIM2->DIER |= TIM_DIER_CC1IE;
+    // 4. 定时器输入捕获部分
+    // 4.1 TI1 选择 CH1 通道的直接输入(默认)
+    TIM4->CR2 &= ~TIM_CR2_TI1S;
 
-    // NVIC
-    NVIC_EnableIRQ(TIM2_IRQn);
+    // 4.2 不使用输入滤波器(默认)
+    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;
 
-    // 启动
-    TIM2->CR1 |= TIM_CR1_CEN;
+    // 4.3 捕获极性,默认高电平(上升沿)
+    TIM4->CCER &= ~TIM_CCER_CC1P;
+
+    // 4.4 通道1设置为输入,映射为直通信号(TI1 -> IC1),CC1S = 01
+    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1;
+    TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;
+
+    // 4.5 输入预分频器(默认)
+    TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;
+
+    // 4.6 使能通道1
+    TIM4->CCER |= TIM_CCER_CC1E;
+
+    // 4.7 使能输入捕获中断
+    TIM4->DIER |= TIM_DIER_CC1IE;
+
+    // 5. NVIC配置
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(TIM4_IRQn, 3);
+    NVIC_EnableIRQ(TIM4_IRQn);
 }
 
-// 捕获中断:记录两次上升沿的 CNT 差值
-volatile uint16_t cap1, cap2;
-volatile uint8_t cap_count = 0;
+// 控制
+void TIM4_Start(void)
+{
+    TIM4->CR1 |= TIM_CR1_CEN;
+}
+void TIM4_Stop(void)
+{
+    TIM4->CR1 &= ~TIM_CR1_CEN;
+}
 
-void TIM2_IRQHandler(void)
+// 改进版中断服务函数:捕获到上升沿直接复位CNT
+void TIM4_IRQHandler(void)
 {
-    if (TIM2->SR & TIM_SR_CC1IF)      // 捕获事件发生
+    // 判断是否是通道1的捕获中断
+    if (TIM4->SR & TIM_SR_CC1IF)
     {
-        if (cap_count == 0) {
-            cap1 = TIM2->CCR1;         // 第一次捕获值
-            cap_count = 1;
-        } else {
-            cap2 = TIM2->CCR1;         // 第二次捕获值
-            cap_count = 2;
-
-            // 频率 = CK_CNT / (cap2 - cap1)
-            // = 1MHz / 差值
-            uint16_t diff = (cap2 > cap1) ? (cap2 - cap1) : (0xFFFF - cap1 + cap2);
-            uint32_t freq = 1000000 / diff;
-        }
-        TIM2->SR &= ~TIM_SR_CC1IF;     // 清标志
+        // 清除中断标志位
+        TIM4->SR &= ~TIM_SR_CC1IF;
+
+        // 直接复位CNT
+        TIM4->CNT = 0;
     }
 }
+
+// 获取PWM周期,单位为ms
+double TIM4_GetPWMCycle(void)
+{
+    return (TIM4->CCR1 + 1) / 1000.0;
+}
+
+// 获取PWM频率,单位为Hz
+double TIM4_GetPWMFreq(void)
+{
+    return 1000000.0 / (TIM4->CCR1 + 1);
+}
 ```
 
+### 输入捕获增强版(TIM4 双通道测周期+占空比)
+
+**项目路径**:`stm32_base/24_tim_pwm_duty_register`
+
+使用 TIM4 的 CH1(TI1 -> IC1)捕获周期,CH2(TI1 -> IC2)捕获占空比。通过**从模式复位**(SMS=100, TS=101)自动复位 CNT,无需中断。
+
+**文件:`stm32_base/24_tim_pwm_duty_register/Hardware/TIM/tim4.h`**
+
+```c
+#ifndef __TIM4_H
+#define __TIM4_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void TIM4_Init(void);
+
+// 控制
+void TIM4_Start(void);
+void TIM4_Stop(void);
+
+// 获取PWM周期和频率
+double TIM4_GetPWMCycle(void);
+double TIM4_GetPWMFreq(void);
+
+// 获取PWM占空比
+double TIM4_GetPWMDuty(void);
+
+#endif
+```
+
+**文件:`stm32_base/24_tim_pwm_duty_register/Hardware/TIM/tim4.c`**(初始化部分差异)
+
+```c
+void TIM4_Init(void)
+{
+    // 1-3. 时钟、GPIO、时基配置同 22 项目 ...
+
+    // 4. 定时器输入捕获部分(双通道)
+    // 4.1-4.2 TI1、滤波器配置同 22 项目 ...
+
+    // 4.3 捕获极性:CH1上升沿,CH2下降沿
+    TIM4->CCER &= ~TIM_CCER_CC1P;
+    TIM4->CCER |= TIM_CCER_CC2P;
+
+    // 4.4 CH1直通(TI1 -> IC1),CH2间接(TI1 -> IC2)
+    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1;
+    TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;         // CC1S = 01
+    TIM4->CCMR1 |= TIM_CCMR1_CC2S_1;
+    TIM4->CCMR1 &= ~TIM_CCMR1_CC2S_0;        // CC2S = 10
+
+    // 4.5 输入预分频器(默认)
+    TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;
+    TIM4->CCMR1 &= ~TIM_CCMR1_IC2PSC;
+
+    // 4.6 配置从模式触发源:TS = 101(TI1FP1)
+    TIM4->SMCR |= TIM_SMCR_TS_2;
+    TIM4->SMCR &= ~TIM_SMCR_TS_1;
+    TIM4->SMCR |= TIM_SMCR_TS_0;
+
+    // 4.7 从模式:复位模式 SMS = 100
+    TIM4->SMCR |= TIM_SMCR_SMS_2;
+    TIM4->SMCR &= ~TIM_SMCR_SMS_1;
+    TIM4->SMCR &= ~TIM_SMCR_SMS_0;
+
+    // 4.8 使能通道1和通道2
+    TIM4->CCER |= TIM_CCER_CC1E;
+    TIM4->CCER |= TIM_CCER_CC2E;
+}
+
+// 获取PWM占空比
+double TIM4_GetPWMDuty(void)
+{
+    return (TIM4->CCR2 + 1) * 1.0 / (TIM4->CCR1 + 1);
+}
+```
+
+> **原理**:CH1(TI1 -> IC1)捕获上升沿,此时 CNT 值存入 CCR1(周期);CH2(TI1 -> IC2)捕获下降沿,CNT 值存入 CCR2(高电平宽度)。从模式复位在 TI1FP1 上升沿自动清零 CNT,无需中断干预。占空比 = (CCR2 + 1) / (CCR1 + 1)。
+
 ---
 
 ## HAL 库版 PWM

+ 195 - 110
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/10-DMA数据传输.md

@@ -129,9 +129,11 @@ for (i = 0; i < 100; i++) {
 
 #include "stm32f10x.h"
 
-void DMA_Init(void);
-void DMA_Start(void);
-uint8_t DMA_IsComplete(void);
+// 初始化
+void DMA1_Init(void);
+
+// DMA1通道1传输数据,需要指定源地址、目的地址、数据长度
+void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen);
 
 #endif
 ```
@@ -141,95 +143,111 @@ uint8_t DMA_IsComplete(void);
 ```c
 #include "dma.h"
 
-// 定义源和目的缓冲区(放在 SRAM 中)
-#define BUFFER_SIZE  10
-uint32_t src_buffer[BUFFER_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-uint32_t dst_buffer[BUFFER_SIZE] = {0};
+// 初始化
+void DMA1_Init(void)
+{
+    // 1. 开启时钟
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+
+    // 2. DMA参数配置
+    // 2.1 传输方向DIR = 0,从外设到存储器(实际是ROM到RAM,存储器到存储器)
+    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
+    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;
+
+    // 2.2 设置数据宽度,均为00 - 8位
+    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;
+    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;
+
+    // 2.3 外设和存储器地址增量
+    DMA1_Channel1->CCR |= DMA_CCR1_PINC;
+    DMA1_Channel1->CCR |= DMA_CCR1_MINC;
+
+    // 2.4 使能传输完成中断
+    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
+
+    // 3. NVIC配置
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(DMA1_Channel1_IRQn, 3);
+    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
+}
 
-void DMA_Init(void)
+// DMA1通道1传输数据,需要指定源地址、目的地址、数据长度
+void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen)
 {
-    // 1. 开启 DMA1 时钟
-    // DMA 挂载在 AHB 总线上,时钟由 AHBENR 控制
-    RCC->AHBENR |= RCC_AHBENR_DMA1EN;       // |=:DMA1时钟使能
-
-    // 2. 清除通道1的所有中断标志
-    // IFCR 写 1 清除对应标志位
-    // CGIF1 = 清除通道1全局标志(包含 TCIF/HTIF/TEIF)
-    DMA1->IFCR |= DMA_IFCR_CGIF1;            // |=:写1清除所有标志
-
-    // 3. 配置 DMA1 通道1
-    // 内存到内存传输:从 src_buffer 复制到 dst_buffer
-    // 注意:必须先配置 CPAR/CMAR/CNDTR,再配 CCR
-
-    // 外设地址寄存器 ← 源地址(在内存到内存模式下,"外设"端就是源)
-    DMA1_Channel1->CPAR = (uint32_t)src_buffer;
-
-    // 存储器地址寄存器 ← 目的地址
-    DMA1_Channel1->CMAR = (uint32_t)dst_buffer;
-
-    // 传输数量寄存器 ← 传输次数(一次传输的宽度由 MSIZE 决定)
-    // CNDTR 是 16 位,最大 65535
-    DMA1_Channel1->CNDTR = BUFFER_SIZE;
-
-    // 4. 配置 CCR
-    // MEM2MEM=1: 内存到内存模式(不需要外设请求信号,配置完立即开始)
-    // PL[1:0]=11: 最高优先级
-    // MSIZE[1:0]=10: 存储器数据宽度 = 32 位
-    // PSIZE[1:0]=10: 外设数据宽度 = 32 位
-    // MINC=1: 存储器地址递增(每传一次 +4 字节)
-    // PINC=1: 外设地址递增(每传一次 +4 字节)
-    // CIRC=1: 循环模式(传输完成后自动重新开始,适用于持续采集)
-    // TCIE=1: 传输完成中断使能
-    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM   // MEM2MEM 位置 1
-                       | DMA_CCR1_PL          // 优先级 11
-                       | DMA_CCR1_MSIZE       // MSIZE = 32位
-                       | DMA_CCR1_PSIZE       // PSIZE = 32位
-                       | DMA_CCR1_MINC        // 存储器地址递增
-                       | DMA_CCR1_PINC        // 外设地址递增
-                       | DMA_CCR1_CIRC        // 循环模式
-                       | DMA_CCR1_TCIE;       // 传输完成中断使能
-
-    // 5. 使能通道1
-    // EN=1 表示通道启动。配置完所有参数后最后才置 EN
-    // 对于 MEM2MEM 模式,EN=1 后 DMA 立即开始传输
-    DMA1_Channel1->CCR |= DMA_CCR1_EN;        // |=: EN位置1
+    // 1. 将源地址写入外设地址寄存器
+    DMA1_Channel1->CPAR = srcAddr;
+
+    // 2. 将目的地址写入存储器地址寄存器
+    DMA1_Channel1->CMAR = destAddr;
+
+    // 3. 将数据长度写入 CNDTR
+    DMA1_Channel1->CNDTR = dataLen;
+
+    // 4. 使能通道,开始数据传输
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
 }
 
-// 检查传输是否完成(查询方式)
-uint8_t DMA_IsComplete(void)
+extern uint8_t isOver;
+
+// 中断服务函数
+void DMA1_Channel1_IRQHandler(void)
 {
-    // ISR.TCIF1 = 1 表示通道1传输完成
-    if (DMA1->ISR & DMA_ISR_TCIF1)    // &测试: TCIF1位=1?
+    // 判断是否是传输完成中断(TCIF)
+    if ( DMA1->ISR & DMA_ISR_TCIF1 )
     {
-        DMA1->IFCR |= DMA_IFCR_CTCIF1; // 清除完成标志(写1)
-        return 1;
+        // 清除中断标志位
+        DMA1->IFCR |= DMA_IFCR_CTCIF1;
+
+        // 关闭通道
+        DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
+
+        isOver = 1;
     }
-    return 0;
 }
 ```
 
 **文件:`stm32_base/28_dma_mem2mem_register/User/main.c`**
 
 ```c
+#include "usart.h"
 #include "dma.h"
-#include "led.h"
+
+// 常量全局数组,存储在 ROM 区
+const uint8_t src[] = {10, 20, 30, 40};
+
+// 变量全局数组,存储在 RAM 区
+uint8_t dest[4];
+
+// 全局变量,表示数据传输完毕
+uint8_t isOver;
 
 int main(void)
 {
-    LED_Init();
-    DMA_Init();   // DMA 配置完立即开始传输
+	// 初始化
+	USART_Init();
+	DMA1_Init();
 
-    while (1)
-    {
-        if (DMA_IsComplete())
-        {
-            // 传输完成:dst_buffer 数组已包含 src_buffer 的副本
-            // 验证第一个元素是否复制正确
-            if (dst_buffer[0] == 0x00) {
-                LED_On(LED_1);     // 正确则点亮 LED1
-            }
-        }
-    }
+	printf("Hello world!\n");
+
+	// 打印源和目的地址
+	printf("src = %p, dest = %p\n", src, dest);
+
+	// 调用函数启动传输
+	DMA1_Transmit((uint32_t)src, (uint32_t)dest, 4);
+
+	// 等待传输完毕,打印结果
+	while (!isOver)
+	{
+	}
+
+	for (uint8_t i = 0; i < 4; i++)
+	{
+		printf("%d\t", dest[i]);
+	}
+
+	while (1)
+	{
+	}
 }
 ```
 
@@ -241,53 +259,120 @@ int main(void)
 
 **项目路径**:`stm32_base/30_dma_mem2usart_register`
 
+DMA + USART 使用 **DMA1 通道4**(USART1_TX 映射到此通道),配置为内存→外设方向:
+
+**文件:`stm32_base/30_dma_mem2usart_register/Hardware/DMA/dma.h`**
+
 ```c
-// 通过 DMA 自动将缓冲区数据发送到 USART1
-// CPU 仅需配置一次,DMA 自动搬完所有数据
+#ifndef __DMA_H
+#define __DMA_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void DMA1_Init(void);
+
+// DMA1通道4传输数据,需要指定源地址、目的地址、数据长度
+void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen);
 
-#define TX_BUFFER_SIZE  100
-uint8_t tx_buffer[TX_BUFFER_SIZE] = "Hello from DMA! This is sent without CPU intervention!\r\n";
+#endif
+```
+
+**文件:`stm32_base/30_dma_mem2usart_register/Hardware/DMA/dma.c`**
 
-void DMA_USART_Init(void)
+```c
+#include "dma.h"
+
+// 初始化
+void DMA1_Init(void)
 {
-    // 1. DMA 时钟
+    // 1. 开启时钟
     RCC->AHBENR |= RCC_AHBENR_DMA1EN;
 
-    // 2. 配置 DMA 通道2(USART1_TX 使用 DMA1 通道2)
-    // 外设地址 = USART1 的数据寄存器地址(0x40013804)
-    DMA1_Channel2->CPAR = (uint32_t)&(USART1->DR);
-    // 存储器地址 = 待发送的缓冲区
-    DMA1_Channel2->CMAR = (uint32_t)tx_buffer;
-    // 传输次数 = 缓冲区大小
-    DMA1_Channel2->CNDTR = TX_BUFFER_SIZE;
-
-    // 3. 配置 CCR
-    // DIR = 1: 内存→外设方向
-    // MSIZE=8位, PSIZE=8位(USART1->DR 是 8 位寄存器)
-    // MINC=1: 地址递增(从 buffer 中逐字节取数据)
-    // PINC=0: 外设地址不递增(始终写入同一个 DR 寄存器)
-    DMA1_Channel2->CCR |= DMA_CCR2_DIR;      // DIR=1: 内存→外设
-    DMA1_Channel2->CCR |= DMA_CCR2_MINC;     // MINC: 内存地址递增
-    DMA1_Channel2->CCR |= DMA_CCR2_TCIE;     // TCIE: 完成后中断
-
-    // 4. 注意:此时不使能 DMA 通道
-    // DMA 传输由 USART 的发送请求触发
-    // USART 配置中使能 DMA 发送(USART_CR3_DMAT)后,DMA 才开始工作
+    // 2. DMA参数配置
+    // 2.1 传输方向DIR = 1,从存储器到外设
+    DMA1_Channel4->CCR |= DMA_CCR4_DIR;
+
+    // 2.2 设置数据宽度,均为00 - 8位
+    DMA1_Channel4->CCR &= ~DMA_CCR4_PSIZE;
+    DMA1_Channel4->CCR &= ~DMA_CCR4_MSIZE;
+
+    // 2.3 存储器地址递增,外设地址不递增
+    DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;
+    DMA1_Channel4->CCR |= DMA_CCR4_MINC;
+
+    // 2.4 使能传输完成中断
+    DMA1_Channel4->CCR |= DMA_CCR4_TCIE;
+
+    // 2.5 使能USART发送DMA请求
+    USART1->CR3 |= USART_CR3_DMAT;
+
+    // 循环模式
+    DMA1_Channel4->CCR |= DMA_CCR1_CIRC;
+
+    // 3. NVIC配置
+    NVIC_SetPriorityGrouping(3);
+    NVIC_SetPriority(DMA1_Channel4_IRQn, 3);
+    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
 }
 
-void USART_Init_With_DMA(void)
+// DMA1通道4传输数据,需要指定源地址、目的地址、数据长度
+void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t dataLen)
 {
-    // USART 常规初始化 ...(同之前的 USART 配置)
+    // 1. 将源地址写入存储器地址寄存器
+    DMA1_Channel4->CMAR = srcAddr;
+
+    // 2. 将目的地址写入外设地址寄存器
+    DMA1_Channel4->CPAR = destAddr;
+
+    // 3. 将数据长度写入 CNDTR
+    DMA1_Channel4->CNDTR = dataLen;
+
+    // 4. 使能通道,开始数据传输
+    DMA1_Channel4->CCR |= DMA_CCR4_EN;
+}
+
+// 中断服务函数
+void DMA1_Channel4_IRQHandler(void)
+{
+    // 判断是否是传输完成中断(TCIF)
+    if ( DMA1->ISR & DMA_ISR_TCIF4 )
+    {
+        // 清除中断标志位
+        DMA1->IFCR |= DMA_IFCR_CTCIF4;
+
+        // 关闭通道(此处注释掉,循环模式下保持使能)
+        // DMA1_Channel4->CCR &= ~DMA_CCR4_EN;
+    }
+}
+```
+
+**文件:`stm32_base/30_dma_mem2usart_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "dma.h"
+#include "delay.h"
+
+// 全局变量,需要发送的数据
+uint8_t src[] = {'a', 'b', 'c', 'd', 'e'};
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	DMA1_Init();
+
+	printf("Hello world!\n");
+
+	Delay_ms(1);
 
-    // 关键是 USART CR3 寄存器的 DMAT 位
-    // DMAT=1: 使能 USART 发送的 DMA 请求
-    // 当 USART 的 TXE 置 1 时,自动向 DMA 发送请求
-    // DMA 收到请求后,从内存搬 1 字节到 USART->DR
-    USART1->CR3 |= USART_CR3_DMAT;           // |=: DMAT位置1
+	// 调用函数启动传输
+	DMA1_Transmit((uint32_t)src, (uint32_t)&(USART1->DR), 5);
 
-    // 使能 DMA 通道(必须在 USART 使能 DMA 之后)
-    DMA1_Channel2->CCR |= DMA_CCR2_EN;       // |=: EN位置1
-    // 此后,DMA 自动将 tx_buffer 中的数据逐字节发送到串口
+	while (1)
+	{
+	}
 }
 ```
 

+ 215 - 99
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/11-ADC模数转换.md

@@ -173,103 +173,104 @@ while (ADC1->CR2 & ADC_CR2_CAL)       // 等待校准完成(CAL 位硬件自
 
 **项目路径**:`stm32_base/32_adc_single_register`
 
-**需求**:使用 ADC1 通道 0(PA0)采集模拟电压,通过串口打印原始值和电压值。
+**需求**:使用 ADC1 通道 10(PC0)采集模拟电压,通过串口打印原始值和电压值。
 
 **文件:`stm32_base/32_adc_single_register/Hardware/ADC/adc.c`**
 
 ```c
 #include "adc.h"
 
-// ADC1 初始化:PA0(通道0),软件触发,单次转换
+// 初始化
 void ADC1_Init(void)
 {
-    /* ========== 1. 开启时钟 ========== */
-    // ADC1 挂载在 APB2 总线上
-    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;     // |=: ADC1时钟使能
-    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;      // |=: GPIOA时钟使能
-
-    /* ========== 2. 配置 PA0 为模拟输入 ========== */
-    // 模拟输入模式: MODE=00, CNF=00
-    // 注意: 模拟输入下施密特触发器关闭以降低功耗
-    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);  // &=~: 同时清除MODE0和CNF0
-
-    /* ========== 3. 配置 ADC 时钟分频 ========== */
-    // APB2=72MHz, ADCPRE=Div6 → ADC_CLK=12MHz(≤14MHz ✅)
-    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;      // |=: ADCPRE[1:0]=10
-
-    /* ========== 4. 复位 ADC 配置 ========== */
-    // 通过 RSTR 寄存器复位(可选但推荐,确保从干净状态开始)
-    RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;   // |=: 复位ADC1
-    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;  // &=~: 解除复位
-
-    /* ========== 5. 开启 ADC + 校准 ========== */
-    ADC1->CR2 |= ADC_CR2_ADON;               // |=: ADON=1 开启ADC
-    Delay_us(10);                             // 等待稳定
-
-    // 校准
-    ADC1->CR2 |= ADC_CR2_CAL;               // |=: 开始校准
-    while (ADC1->CR2 & ADC_CR2_CAL);         // 等待CAL硬件清0(校准结束)
-
-    /* ========== 6. 配置规则组序列 ========== */
-    // SQ1[4:0] = 通道号 0(PA0)
-    ADC1->SQR3 &= ~ADC_SQR3_SQ1;             // &=~: 清除SQ1位域
-    ADC1->SQR3 |= (0 << 0);                   // |=: SQ1 = 通道 0(0 是默认值,此行示意)
-    // SEQ_LEN[3:0] = 0(表示序列长度为 1)
-    ADC1->SQR1 &= ~ADC_SQR1_L;               // &=~: L清0(1个转换)
-
-    /* ========== 7. 配置采样时间 ========== */
-    // 通道0的采样时间: SMP0[2:0]=111 → 239.5周期(最准)
-    ADC1->SMPR2 |= ADC_SMPR2_SMP0;           // |=: SMP0位全部置1
-}
+    // 1. 时钟配置
+    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    // ADC的时钟频率不能超过14MHz,需预分频 10 - 6分频
+    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
 
-// 读取 ADC 转换结果(软件触发单次转换)
-uint16_t ADC1_Read(void)
-{
-    // 1. 启动转换:再次写 ADON(当 CR2 已有 ADON=1 时,再次写 1 触发转换)
-    ADC1->CR2 |= ADC_CR2_ADON;               // |=: 软件触发
+    // RCC->APB2ENR | RCC_APB2ENR_IOPCEN;
+
+    // 2. GPIO配置模式:PC0 - 模拟输入,CNF = 00, MODE = 00
+    GPIOC->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
+
+    // 3. ADC配置
+    // 3.1 关闭扫描模式,只有1个转换通道
+    ADC1->CR1 &= ~ADC_CR1_SCAN;
+
+    // 3.2 开启连续转换模式
+    ADC1->CR2 |= ADC_CR2_CONT;
+
+    // 3.3 数据对齐方式:0 - 右对齐
+    ADC1->CR2 &= ~ADC_CR2_ALIGN;
+
+    // 3.4 配置采样时间,010 - 13.5个ADC时钟周期
+    ADC1->SMPR1 &= ~ADC_SMPR1_SMP10;
+    ADC1->SMPR1 |= ADC_SMPR1_SMP10_1;
 
-    // 2. 等待转换结束
-    // SR.EOC = 1 表示转换完成
-    while (!(ADC1->SR & ADC_SR_EOC))          // &测试: EOC位=1?
-    {
-    }
+    // 3.5 配置通道序列
+    // 3.5.1 设置规则组通道数目L = 0000,1个通道
+    ADC1->SQR1 &= ~ADC_SQR1_L;
 
-    // 3. 读取 DR 寄存器(读取后 EOC 自动清 0)
-    return ADC1->DR;                          // 返回 12 位结果(0~4095)
+    // 3.5.2 将通道号10写入 SQ1(在SQR3里)
+    ADC1->SQR3 &= ~ADC_SQR3_SQ1;
+    ADC1->SQR3 |= 10 << 0;      // 低5位,第一个通道号,设为10
+
+    // 3.6 选择外部触发方式(暂时注释)
+    // ADC1->CR2 |= ADC_CR2_EXTTRIG;
+    // ADC1->CR2 |= ADC_CR2_EXTSEL;
+}
+
+// 启动ADC开始转换
+void ADC1_StartConvert(void)
+{
+    // 1. 上电,将ADC从断电模式唤醒
+    ADC1->CR2 |= ADC_CR2_ADON;
+
+    // 2. 执行校准,等待结束
+    ADC1->CR2 |= ADC_CR2_CAL;
+    while (ADC1->CR2 & ADC_CR2_CAL)
+    {}
+
+    // 3. 连续方式启动AD转换
+    // ADC1->CR2 |= ADC_CR2_SWSTART;
+    ADC1->CR2 |= ADC_CR2_ADON;
+    
+    // 4. 等待转换结束
+    while ((ADC1->SR & ADC_SR_EOC) == 0)
+    {}
 }
 
-// 将 ADC 原始值转换为电压(mV)
-uint16_t ADC_GetVoltage(uint16_t adc_value)
+// 获取转换后的电压值
+double ADC1_ReadV(void)
 {
-    // V = adc_value × 3300mV / 4095
-    return (uint16_t)((uint32_t)adc_value * 3300 / 4095);
+    return ADC1->DR * 3.3 / 4095;
 }
 ```
 
 **文件:`stm32_base/32_adc_single_register/User/main.c`**
 
 ```c
-#include "adc.h"
 #include "usart.h"
-#include <stdio.h>
+#include "adc.h"
+#include "delay.h"
 
 int main(void)
 {
-    USART_Init();
-    ADC1_Init();
+	// 初始化
+	USART_Init();
+	ADC1_Init();
 
-    printf("ADC 单通道采集实验\r\n");
+	printf("Hello world!\n");
 
-    while (1)
-    {
-        uint16_t adc_val = ADC1_Read();
-        uint16_t volt_mv = ADC_GetVoltage(adc_val);
+	// 启动AD转换
+	ADC1_StartConvert();
 
-        printf("ADC: %4d  (0~4095)  |  %.3fV\r\n",
-               adc_val, volt_mv / 1000.0f);
+	while (1)
+	{
+		printf("V = %.2f\n", ADC1_ReadV());
 
-        Delay_ms(500);  // 每 500ms 采集一次
-    }
+		Delay_ms(1000);
+	}
 }
 ```
 
@@ -279,52 +280,167 @@ int main(void)
 
 **项目路径**:`stm32_base/34_adc_double_register`
 
-多通道 + DMA 是 ADC 的典型高效用法:
+多通道 + DMA 是 ADC 的典型高效用法。实际项目中使用 ADC1 的**通道10(PC0)**和**通道12(PC2)**,通过 DMA1 通道1自动搬运转换结果:
+
+**文件:`stm32_base/34_adc_double_register/Hardware/ADC/adc.h`**
 
 ```c
-// 配置 ADC1 通道 0 和通道 1(PA0, PA1)
-// 使用 DMA 自动搬运转换结果,无需 CPU 轮询
+#ifndef __ADC_H
+#define __ADC_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void ADC1_Init(void);
+
+// DMA相关的初始化
+void ADC1_DMA_Init(void);
+
+// 启动ADC开始转换,DMA配置完成(源地址固定为ADC1->DR)
+void ADC1_StartConvert_DMA(uint32_t destAddr, uint16_t len);
+
+#endif
+```
 
-#define ADC_CHANNELS  2
-uint16_t adc_values[ADC_CHANNELS];  // DMA 自动写入此数组
+**文件:`stm32_base/34_adc_double_register/Hardware/ADC/adc.c`**
 
-void ADC_DMA_Init(void)
+```c
+#include "adc.h"
+
+// 初始化
+void ADC1_Init(void)
 {
-    // ADC 配置(同单通道,但做以下修改)
+    // 1. 时钟配置
+    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    // ADC的时钟频率不能超过14MHz,需预分频 10 - 6分频
+    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
+
+    // RCC->APB2ENR | RCC_APB2ENR_IOPCEN;
+
+    // 2. GPIO配置模式:PC0 - 模拟输入,CNF = 00, MODE = 00
+    GPIOC->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
 
-    // 1. 规则序列: 通道0 → 通道1 依次转换
-    ADC1->SQR3 = (1 << 0) | (0 << 5);  // SQ1=通道1, SQ2=通道0(此处仅为示例)
-    // SQR1.L = 1 → 序列长度为 2
-    ADC1->SQR1 |= (1 << 20);            // L[3:0]=1: 共 2 个转换
+    // PC2 - 模拟输入,CNF = 00, MODE = 00
+    GPIOC->CRL &= ~(GPIO_CRL_MODE2 | GPIO_CRL_CNF2);
 
-    // 2. 开启 DMA 模式
-    ADC1->CR2 |= ADC_CR2_DMA;          // DMA=1: 使能DMA传输
+    // 3. ADC配置
+    // 3.1 开启扫描模式
+    ADC1->CR1 |= ADC_CR1_SCAN;
 
-    // 3. 开启扫描模式(多通道必须开启)
-    ADC1->CR1 |= ADC_CR1_SCAN;         // SCAN=1: 扫描模式
+    // 3.2 开启连续转换模式
+    ADC1->CR2 |= ADC_CR2_CONT;
 
-    // 4. 开启连续转换
-    ADC1->CR2 |= ADC_CR2_CONT;         // CONT=1: 连续转换
+    // 3.3 数据对齐方式:0 - 右对齐
+    ADC1->CR2 &= ~ADC_CR2_ALIGN;
+
+    // 3.4 配置采样时间,010 - 13.5个ADC时钟周期
+    ADC1->SMPR1 &= ~ADC_SMPR1_SMP10;
+    ADC1->SMPR1 |= ADC_SMPR1_SMP10_1;
+    ADC1->SMPR1 &= ~ADC_SMPR1_SMP12;
+
+    // 3.5 配置通道序列
+    // 3.5.1 设置规则组通道数目L = 0001,2个通道
+    ADC1->SQR1 &= ~ADC_SQR1_L;
+    ADC1->SQR1 |= ADC_SQR1_L_0;
+
+    // 3.5.2 将通道号10写入 SQ1(在SQR3里)
+    ADC1->SQR3 &= ~ADC_SQR3_SQ1;
+    ADC1->SQR3 |= 10 << 0;      // 低5位,第一个通道号,设为10
+
+    // 将通道号12写入 SQ2(在SQR3里)
+    ADC1->SQR3 &= ~ADC_SQR3_SQ2;
+    ADC1->SQR3 |= 12 << 5;      // 中间5位,第二个通道号,设为12
+
+    // 3.6 选择外部触发方式(暂时注释)
+    // ADC1->CR2 |= ADC_CR2_EXTTRIG;
+    // ADC1->CR2 |= ADC_CR2_EXTSEL;
 }
 
-// DMA 配置
-void DMA_ADC_Init(void)
+// DMA相关的初始化
+void ADC1_DMA_Init(void)
 {
+    // 1. 开启时钟
     RCC->AHBENR |= RCC_AHBENR_DMA1EN;
 
-    // ADC1 使用 DMA1 通道1
-    DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);      // 外设: ADC数据寄存器
-    DMA1_Channel1->CMAR = (uint32_t)adc_values;         // 内存: 存储数组
-    DMA1_Channel1->CNDTR = ADC_CHANNELS;                // 传输次数 = 通道数
-    DMA1_Channel1->CCR |= DMA_CCR1_MINC                 // 内存地址递增
-                        | DMA_CCR1_CIRC                 // 循环模式
-                        | DMA_CCR1_TCIE;                // 完成中断
+    // 2. DMA传输方向DIR = 0,从外设读取
+    DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
+
+    // 3. 数据宽度:01 - 16位
+    DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE_1;
+    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
+
+    DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE_1;
+    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;
+
+    // 4. 地址是否增量:外设不增、存储器增
+    DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;
+    DMA1_Channel1->CCR |= DMA_CCR1_MINC;
+
+    // 5. 开启循环模式
+    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
+
+    // 6. ADC1使能DMA请求
+    ADC1->CR2 |= ADC_CR2_DMA;
+}
+
+// 启动ADC开始转换,DMA配置完成(源地址固定为ADC1->DR)
+void ADC1_StartConvert_DMA(uint32_t destAddr, uint16_t len)
+{
+    // 0. DMA配置参数:通道1
+    DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);
+    DMA1_Channel1->CMAR = destAddr;
+    DMA1_Channel1->CNDTR = len;
+
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
+
+    // 1. 上电,将ADC从断电模式唤醒
+    ADC1->CR2 |= ADC_CR2_ADON;
+
+    // 2. 执行校准,等待结束
+    ADC1->CR2 |= ADC_CR2_CAL;
+    while (ADC1->CR2 & ADC_CR2_CAL)
+    {}
+
+    // 3. 连续方式启动AD转换
+    // ADC1->CR2 |= ADC_CR2_SWSTART;
+    ADC1->CR2 |= ADC_CR2_ADON;
+    
+    // 4. 等待转换结束
+    while ((ADC1->SR & ADC_SR_EOC) == 0)
+    {}
+}
+```
+
+**文件:`stm32_base/34_adc_double_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "adc.h"
+#include "delay.h"
+
+// 定义一个大小为2的数组,保存转换结果
+uint16_t data[2];
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	ADC1_Init();
+	ADC1_DMA_Init();
+
+	printf("Hello world!\n");
+
+	// 启动AD转换
+	ADC1_StartConvert_DMA((uint32_t)data, 2);
 
-    DMA1_Channel1->CCR |= DMA_CCR1_EN;                  // 使能
+	while (1)
+	{
+		double v1 = data[0] * 3.3 / 4095;
+		double v2 = data[1] * 3.3 / 4095;
+		printf("V_PC0 = %.2f, V_PC2 = %.2f\n", v1, v2);
 
-    // 注意: 每次转换完 ADC 会自动发 DMA 请求
-    // DMA 将 ADC1->DR 的值搬到 adc_values[x] 然后地址+1
-    // 所有通道转换完一轮 → DMA 中断
+		Delay_ms(1000);
+	}
 }
 ```
 

+ 421 - 78
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/12-SPI通信与FSMC总线.md

@@ -108,19 +108,36 @@ MISO 逐位输入 ← 移位寄存器(8位) ← RX 缓冲区 → 读 DR
 #define __SPI_H
 
 #include "stm32f10x.h"
+#include "delay.h"
 
-// GPIO 引脚宏定义
-// PA5=SCK, PA6=MISO, PA7=MOSI, PA4=CS(片选)
-#define SPI_SCK_HIGH  (GPIOA->ODR |= GPIO_ODR_ODR5)   // |= SCK置1
-#define SPI_SCK_LOW   (GPIOA->ODR &= ~GPIO_ODR_ODR5)  // &=~ SCK清0
-#define SPI_MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)   // |= MOSI置1
-#define SPI_MOSI_LOW  (GPIOA->ODR &= ~GPIO_ODR_ODR7)  // &=~ MOSI清0
-#define SPI_MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)     // & 读取MISO电平
-#define SPI_CS_HIGH   (GPIOA->ODR |= GPIO_ODR_ODR4)   // |= CS置1
-#define SPI_CS_LOW    (GPIOA->ODR &= ~GPIO_ODR_ODR4)  // &=~ CS清0
+// 宏定义:SPI总线的操作
+// CS - PC13
+#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
 
+// SCK - PA5
+#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
+#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
+
+// MOSI - PA7
+#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
+#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
+
+// MISO - PA6,读取输入
+#define MISO_READ ( GPIOA->IDR & GPIO_IDR_IDR6 )
+
+// 产生标准的延迟时间
+#define SPI_DELAY Delay_us(5)
+
+// 初始化
 void SPI_Init(void);
-uint8_t SPI_SendByte(uint8_t byte);
+
+// SPI通信的开启和关闭
+void SPI_Start(void);
+void SPI_Stop(void);
+
+// 一个时钟周期内,主机交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte);
 
 #endif
 ```
@@ -130,102 +147,256 @@ uint8_t SPI_SendByte(uint8_t byte);
 ```c
 #include "spi.h"
 
-// SPI 初始化(模式0: CPOL=0, CPHA=0)
+// 初始化
 void SPI_Init(void)
 {
-    // 1. 开启 GPIOA 时钟
+    // 1. 开启时钟
     RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
 
-    // 2. 配置 PA4(CS), PA5(SCK), PA7(MOSI) 为推挽输出 50MHz
-    // PA4: CRL 控制,Pin4 的 MODE 在 CRL 的第 18~19 位
-    GPIOA->CRL |= GPIO_CRL_MODE4;      // MODE4=11: 50MHz输出
-    GPIOA->CRL &= ~GPIO_CRL_CNF4;      // CNF4=00: 通用推挽
+    // 2. GPIO配置模式
+    // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
+    GPIOC->CRH |= GPIO_CRH_MODE13;
+    GPIOC->CRH &= ~GPIO_CRH_CNF13;
 
-    // PA5(SCK)
+    // 2.2 SCK - PA5,通用推挽输出,CNF = 00,MODE = 11
     GPIOA->CRL |= GPIO_CRL_MODE5;
     GPIOA->CRL &= ~GPIO_CRL_CNF5;
 
-    // PA7(MOSI)
+    // 2.3 MOSI - PA7,通用推挽输出,CNF = 00,MODE = 11
     GPIOA->CRL |= GPIO_CRL_MODE7;
     GPIOA->CRL &= ~GPIO_CRL_CNF7;
 
-    // 3. 配置 PA6(MISO) 为浮空输入
-    GPIOA->CRL &= ~GPIO_CRL_MODE6;     // MODE6=00: 输入
-    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;    // CNF6[1]=0
-    GPIOA->CRL |= GPIO_CRL_CNF6_0;     // CNF6[0]=1 → 浮空输入
+    // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
+    GPIOA->CRL &= ~GPIO_CRL_MODE6;
+    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
+    GPIOA->CRL |= GPIO_CRL_CNF6_0;
+
+    // 3. SCK 保持空闲状态(模式0 - 低电平空闲)
+    SCK_LOW;
 
-    // 4. 初始状态: SCK=低(CPOL=0), CS=高(不选中)
-    SPI_SCK_LOW;
-    SPI_CS_HIGH;
+    // 4. 片选初始为未选中
+    CS_HIGH;
+
+    // 5. 延时
+    SPI_DELAY;
+}
+
+// SPI通信的开启和关闭(通过片选信号控制)
+void SPI_Start(void)
+{
+    CS_LOW;
+}
+void SPI_Stop(void)
+{
+    CS_HIGH;
 }
 
-// SPI 模式0: CPOL=0(空闲SCK低), CPHA=0(上升沿采集)
-// 发送 1 字节的同时接收 1 字节
-uint8_t SPI_SendByte(uint8_t byte)
+// 一个时钟周期内,主机交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte)
 {
-    uint8_t i;
+    // 交换字节并返回接收数据
+    uint8_t rByte = 0x00;
 
-    // 循环 8 次,每次处理 1 位
-    for (i = 0; i < 8; i++)
+    // 通过循环每次读写一
+    for (uint8_t i = 0; i < 8; i++)
     {
-        // 第 1 步: 在 SCK 低电平时改变 MOSI 数据
-        // MSB first (先发最高位)
-        if (byte & 0x80)              // &测试: 最高位=1?
-            SPI_MOSI_HIGH;            // 输出高
+        // 1. 判断当前最高位,向MOSI输出相应电平
+        if ( byte & 0x80 )
+        {
+            MOSI_HIGH;
+        }
         else
-            SPI_MOSI_LOW;             // 输出低
+        {
+            MOSI_LOW;
+        }
+
+        // 2. 移位
+        byte <<= 1;
+        
+        // 3. 输出时钟,在第一个时钟沿产生上升沿
+        SCK_HIGH;
+        SPI_DELAY;
+
+        // 4. 移位,腾出最低位用来接收数据
+        rByte <<= 1;
+
+        // 5. 根据 MISO 电平来接收
+        if ( MISO_READ )
+        {
+            rByte |= 0x01;  // 高电平则最低位置1
+        }
+
+        // 6. 下降沿,为下次传输准备
+        SCK_LOW;
+        SPI_DELAY;
+    }
+    
+    return rByte;
+}
+```
+
+**文件:`stm32_base/36_spi_software_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "w25q32.h"
+
+int main(void)
+{
+	// 1. 初始化
+	USART_Init();
+	W25Q32_Init();
 
-        byte <<= 1;                   // 左移1位,准备下一位
+	printf("中国芯SPI通信模块实验...\n");
 
-        // 第 2 步: SCK 上升沿 → 从机采集 MOSI
-        SPI_SCK_HIGH;                 // SCK上升沿
+	// 2. 获取JEDEC ID
+	uint8_t mID = 0;
+	uint16_t dID = 0;
 
-        // 同时,主机在上升沿采集 MISO(从机的数据)
-        if (SPI_MISO_READ)            // 读取MISO电平
-            byte |= 1;                // 如果MISO=高,将当前位(已左移后的bit0)置1
+	W25Q32_ReadID(&mID, &dID);
+	printf("mid = %#x, did = %#x\n", mID, dID);
 
-        // 第 3 步: SCK 下降沿 → 从机改变 MISO 数据
-        SPI_SCK_LOW;                  // SCK下降沿
-    }
+	// 3. 写数据前需要先擦除一个扇区
+	W25Q32_EraseSector(0, 0);
+
+	// 4. 页写入一页数据
+	W25Q32_WritePage(0, 0, 0, "12345678", 8);
+
+	// 5. 读取
+	uint8_t buff[10] = {0};
+	W25Q32_Read(0x0, buff, 8);
+
+	printf("buff: %s\n", buff);
 
-    return byte;  // 返回接收到的数据
+	while (1)
+	{
+	}
 }
 ```
 
+> **注意**:W25Q32 是 4MB(32Mbit)Flash,Page=256B,Sector=4KB,Block=64KB。与 W25Q64(8MB)指令集完全兼容,仅容量减半。
+
 ---
 
 ## 硬件 SPI(stm32_base/37_spi_hardware_register)
 
-硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位):
+硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位)。实际项目中 CS 片选仍用 GPIO(PC13)独立控制,SCK/MOSI 配置为复用推挽输出:
+
+**文件:`stm32_base/37_spi_hardware_register/Hardware/SPI/spi.h`**
+
+```c
+#ifndef __SPI_H
+#define __SPI_H
+
+#include "stm32f10x.h"
+
+// 宏定义:SPI总线的操作
+// CS - PC13
+#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
+#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
+
+// 初始化
+void SPI_Init(void);
+
+// SPI通信的开启和关闭
+void SPI_Start(void);
+void SPI_Stop(void);
+
+// 一个时钟周期内,主机交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte);
+
+#endif
+```
+
+**文件:`stm32_base/37_spi_hardware_register/Hardware/SPI/spi.c`**
 
 ```c
-void SPI1_Init(void)
+#include "spi.h"
+
+// 初始化
+void SPI_Init(void)
 {
-    // 1. 开启 SPI1 + GPIOA 时钟
-    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;      // SPI1在APB2
+    // 1. 开启时钟
     RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
+    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+
+    // 2. GPIO配置模式
+    // 2.1 CS - PC13,通用推挽输出,CNF = 00,MODE = 11
+    GPIOC->CRH |= GPIO_CRH_MODE13;
+    GPIOC->CRH &= ~GPIO_CRH_CNF13;
+
+    // 2.2 SCK - PA5,复用推挽输出,CNF = 10,MODE = 11
+    GPIOA->CRL |= GPIO_CRL_MODE5;
+    GPIOA->CRL |= GPIO_CRL_CNF5_1;
+    GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
+
+    // 2.3 MOSI - PA7,复用推挽输出,CNF = 10,MODE = 11
+    GPIOA->CRL |= GPIO_CRL_MODE7;
+    GPIOA->CRL |= GPIO_CRL_CNF7_1;
+    GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
+
+    // 2.4 MISO - PA6,浮空输入,CNF = 01,MODE = 00
+    GPIOA->CRL &= ~GPIO_CRL_MODE6;
+    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
+    GPIOA->CRL |= GPIO_CRL_CNF6_0;
+
+    // 3. SPI模块参数配置
+    // 3.1 通信模式配置
+    // 3.1.1 设为主机模式
+    SPI1->CR1 |= SPI_CR1_MSTR;
+
+    // 3.1.2 选择软件控制片选,NSS电平为高电平
+    SPI1->CR1 |= SPI_CR1_SSM;
+    SPI1->CR1 |= SPI_CR1_SSI;
 
-    // 2. PA5(SCK) 复用推挽, PA6(MISO) 浮空输入, PA7(MOSI) 复用推挽
-
-    // 3. 配置 SPI1
-    // CR1: CPOL=0, CPHA=0, BR=011(72/16=4.5MHz), MSTR=1(主机), SPE=1(使能)
-    SPI1->CR1 = 0;                              // 先清0
-    SPI1->CR1 |= SPI_CR1_CPOL & 0;             // CPOL=0
-    SPI1->CR1 |= SPI_CR1_CPHA & 0;             // CPHA=0
-    SPI1->CR1 |= SPI_CR1_BR_1 | SPI_CR1_BR_0; // BR=011: fPCLK/16
-    SPI1->CR1 |= SPI_CR1_MSTR;                  // MSTR=1: 主机模式
-    SPI1->CR1 |= SPI_CR1_SSI;                   // SSI=1: 软件NSS高电平
-    SPI1->CR1 |= SPI_CR1_SSM;                   // SSM=1: 软件NSS管理
-    SPI1->CR1 |= SPI_CR1_SPE;                   // SPE=1: SPI使能
+    // 3.1.2 SPI通信模式设为模式0:CPOL = 0,CPHA = 0
+    SPI1->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
+
+    // 3.2 设置波特率,时钟分频系数:001 - 4分频,18MHz
+    SPI1->CR1 &= ~SPI_CR1_BR;
+    SPI1->CR1 |= SPI_CR1_BR_0;
+
+    // 3.3 帧格式配置
+    // 3.3.1 帧长度:0 - 8位
+    SPI1->CR1 &= ~SPI_CR1_DFF;
+
+    // 3.3.2 数据传输顺序:0 - MSB在前
+    SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
+
+    // 3.4 使能SPI
+    SPI1->CR1 |= SPI_CR1_SPE;
+}
+
+// SPI通信的开启和关闭(通过片选信号控制)
+void SPI_Start(void)
+{
+    CS_LOW;
+}
+void SPI_Stop(void)
+{
+    CS_HIGH;
 }
 
-// 硬件 SPI 收发
-uint8_t SPI1_SendByte(uint8_t byte)
+// 一个时钟周期内,主机交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte)
 {
-    while (!(SPI1->SR & SPI_SR_TXE)) {}   // 等待 TX 缓冲区空
-    SPI1->DR = byte;                       // 写入数据 → 开始发送
-    while (!(SPI1->SR & SPI_SR_RXNE)) {}  // 等待 RX 缓冲区非空
-    return SPI1->DR;                       // 读取接收数据
+    // 1. 将要发送的数据写入发送缓冲区
+    // 1.1 等待发送缓冲区为空(TXE = 1)
+    while ((SPI1->SR & SPI_SR_TXE) == 0 )
+    {}
+    
+    // 1.2 将数据写入DR
+    SPI1->DR = byte;
+
+    // 2. 获取接收到的数据并返回
+    // 2.1 等待接收缓冲区非空(RXNE = 1)
+    while ( (SPI1->SR & SPI_SR_RXNE) == 0 )
+    {}
+    
+    // 2.2 将DR中的数据返回
+    return (uint8_t)(SPI1->DR & 0xff);
 }
 ```
 
@@ -258,7 +429,9 @@ uint8_t SPI1_SendByte(uint8_t byte)
 | CHIP_ERASE | 0xC7 | 全片擦除 | — |
 | RDID | 0x9F | 读芯片 ID | 3字节返回 |
 
-### W25Q64 擦写(读写前必须擦除)
+### W25Q64/W25Q32 擦写(读写前必须擦除)
+
+> **注意**:实际项目中使用 **W25Q32**(4MB/32Mbit),与 W25Q64 指令集完全兼容,容量减半。以下代码使用 `SPI_SwapByte` 函数(软件/硬件 SPI 驱动均为此函数名)。
 
 ```c
 // W25Q64 写入流程:
@@ -272,8 +445,8 @@ uint8_t W25Q64_ReadSR(void)
 {
     uint8_t sr;
     CS_LOW;
-    SPI_SendByte(RDSR);           // 发指令 0x05
-    sr = SPI_SendByte(0xFF);      // 发占住位,收状态
+    SPI_SwapByte(RDSR);           // 发指令 0x05
+    sr = SPI_SwapByte(0xFF);      // 发占位字节,收状态
     CS_HIGH;
     return sr;
 }
@@ -287,23 +460,23 @@ void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len)
 {
     W25Q64_WaitBusy();          // 等上次完成
     CS_LOW;
-    SPI_SendByte(WREN);         // 写使能
+    SPI_SwapByte(WREN);         // 写使能
     CS_HIGH;
 
     CS_LOW;
-    SPI_SendByte(PAGE_PROG);    // 页编程指令
-    SPI_SendByte(addr >> 16);   // 地址高8位
-    SPI_SendByte(addr >> 8);    // 地址中8位
-    SPI_SendByte(addr);         // 地址低8位
+    SPI_SwapByte(PAGE_PROG);    // 页编程指令
+    SPI_SwapByte(addr >> 16);   // 地址高8位
+    SPI_SwapByte(addr >> 8);    // 地址中8位
+    SPI_SwapByte(addr);         // 地址低8位
     for (uint16_t i = 0; i < len; i++) {
-        SPI_SendByte(data[i]);  // 发送数据
+        SPI_SwapByte(data[i]);  // 发送数据
     }
     CS_HIGH;
     W25Q64_WaitBusy();          // 等写入完成
 }
 ```
 
-> **注意**:W25Q64 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
+> **注意**:W25Q64/W25Q32 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
 > 页编程不能跨页(256 字节边界)。如果数据跨页,需要分多次写入。
 
 ---
@@ -364,6 +537,176 @@ Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
 | 8:15 | DATAST | 数据保持时间(1~255个HCLK周期) |
 | 16:19 | BUSTURN | 总线周转时间 |
 
+### 实验:FSMC 扩展 SRAM(寄存器版)
+
+**项目路径**:`stm32_base/39_fsmc_sram_register`
+
+使用 FSMC 的 Bank1 子区3(NE3,地址范围 0x68000000~0x6BFFFFFF),16 位数据总线连接外部 SRAM。
+
+**文件:`stm32_base/39_fsmc_sram_register/Hardware/FSMC/fsmc.h`**
+
+```c
+#ifndef __FSMC_H
+#define __FSMC_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void FSMC_Init(void);
+
+#endif
+```
+
+**文件:`stm32_base/39_fsmc_sram_register/Hardware/FSMC/fsmc.c`**
+
+```c
+#include "fsmc.h"
+
+void FSMC_GPIO_Init(void);
+
+// 初始化
+void FSMC_Init(void)
+{
+    // 1. 开启时钟
+    RCC->AHBENR |= RCC_AHBENR_FSMCEN;
+
+    RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPEEN |
+                     RCC_APB2ENR_IOPFEN | RCC_APB2ENR_IOPGEN);
+
+    // 2. GPIO模式配置
+    FSMC_GPIO_Init();
+
+    // 3. FSMC寄存器配置
+    // 3.1 BCR3 - BTCR[4]
+    // 3.1.1 存储区使能
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MBKEN;
+
+    // 3.1.2 设置存储器类型:MTYP = 00,SRAM/ROM
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MTYP;
+
+    // 3.1.3 禁止Flash访问
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_FACCEN;
+
+    // 3.1.4 地址数据复用功能,不使能
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MUXEN;
+
+    // 3.1.5 设置总线宽度:MWID = 01,16位
+    FSMC_Bank1->BTCR[4] &= ~FSMC_BCR3_MWID_1;
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR3_MWID_0;
+
+    // 3.1.6 使能写操作
+    FSMC_Bank1->BTCR[4] |= FSMC_BCR3_WREN;
+
+    // 3.2 BTR - BTCR[5]
+    // 3.2.1 地址建立时间 ADDSET
+    FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_ADDSET;
+
+    // 3.2.2 数据建立时间 DATAST
+    FSMC_Bank1->BTCR[5] &= ~FSMC_BTR3_DATAST;
+    FSMC_Bank1->BTCR[5] |= (71 << 8);
+}
+
+// 配置GPIO引脚,均为复用推挽输出(CNF = 10,MODE = 11)
+void FSMC_GPIO_Init(void)
+{
+    // 1. 地址线 A0 ~ A18
+    // MODE = 11
+    GPIOF->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
+                   GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
+    GPIOF->CRH |= (GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
+                   GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
+    GPIOG->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1 | GPIO_CRL_MODE2 |
+                   GPIO_CRL_MODE3 | GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
+    GPIOD->CRH |= (GPIO_CRH_MODE11 | GPIO_CRH_MODE12 | GPIO_CRH_MODE13);
+
+    // CNF = 10(复用推挽输出)
+    GPIOF->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1 | GPIO_CRL_CNF2_1 |
+                   GPIO_CRL_CNF3_1 | GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
+    GPIOF->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0 | GPIO_CRL_CNF2_0 |
+                    GPIO_CRL_CNF3_0 | GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
+    // ...(其余地址线引脚配置类似)
+
+    // 2. 数据线 D0 ~ D15(MODE = 11,CNF = 10)
+    GPIOD->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
+    GPIOD->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
+                   GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
+    GPIOE->CRL |= GPIO_CRL_MODE7;
+    GPIOE->CRH |= (GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_MODE10 |
+                   GPIO_CRH_MODE11 | GPIO_CRH_MODE12 | GPIO_CRH_MODE13 |
+                   GPIO_CRH_MODE14 | GPIO_CRH_MODE15);
+    // CNF = 10
+    GPIOD->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
+    GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
+    // ...
+
+    // 3. 控制线
+    // PD4 - NOE(读使能),PD5 - NWE(写使能)
+    GPIOD->CRL |= (GPIO_CRL_MODE4 | GPIO_CRL_MODE5);
+    GPIOD->CRL |= (GPIO_CRL_CNF4_1 | GPIO_CRL_CNF5_1);
+    GPIOD->CRL &= ~(GPIO_CRL_CNF4_0 | GPIO_CRL_CNF5_0);
+
+    // PG10 - NE3(片选)
+    GPIOG->CRH |= GPIO_CRH_MODE10;
+    GPIOG->CRH |= GPIO_CRH_CNF10_1;
+    GPIOG->CRH &= ~GPIO_CRH_CNF10_0;
+
+    // PE0, PE1 - NBL(字节掩码,16位SRAM的高低位使能)
+    GPIOE->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
+    GPIOE->CRL |= (GPIO_CRL_CNF0_1 | GPIO_CRL_CNF1_1);
+    GPIOE->CRL &= ~(GPIO_CRL_CNF0_0 | GPIO_CRL_CNF1_0);
+}
+
+// 配置完成后,FSMC 区域3的内存映射地址为 0x68000000
+// 写: *(uint16_t *)0x68000000 = data;
+// 读: data = *(uint16_t *)0x68000000;
+```
+
+**文件:`stm32_base/39_fsmc_sram_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "fsmc.h"
+
+// 方法1:使用关键字attribute指定全局变量的地址
+uint8_t v1 __attribute__((at(0x68000000)));
+uint8_t v2 __attribute__((at(0x68000004)));
+
+uint16_t v3 = 30;
+
+int main(void)
+{
+	// 1. 初始化
+	USART_Init();
+	FSMC_Init();
+
+	printf("中国芯FSMC扩展SRAM实验...\n");
+
+	v1 = 10;
+	v2 = 20;
+
+	// 测试局部变量指定地址
+	uint8_t v4 __attribute__((at(0x68000008)));
+	v4 = 40;
+	uint8_t v5 = 50;
+
+	// 打印地址验证
+	printf("v1 = %d, @%p\n", v1, &v1);
+	printf("v2 = %d, @%p\n", v2, &v2);
+	printf("v3 = %d, @%p\n", v3, &v3);
+	printf("v4 = %d, @%p\n", v4, &v4);
+	printf("v5 = %d, @%p\n", v5, &v5);
+
+	// 方法2:指针直接访问
+	uint8_t *p = (uint8_t *)0x68000FFF;
+	*p = 100;
+	printf("*p = %d, @%p\n", *p, p);
+
+	while (1)
+	{
+	}
+}
+```
+
 ### FSMC + LCD 应用
 
 将 LCD 连接到 FSMC,利用地址线 A0 区分命令和数据:

+ 236 - 93
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/13-CAN通信协议与bxCAN外设.md

@@ -188,21 +188,34 @@ bxCAN = Basic Extended CAN,支持 CAN 2.0A(标准帧)和 2.0B Active(标
 **文件:`stm32/stm32/01_can_test_register/Hardware/CAN/can.h`**
 
 ```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-21 16:52:20
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
 #ifndef __CAN_H
 #define __CAN_H
 
 #include "stm32f10x.h"
 
-// 接收数据结构
-typedef struct {
-    uint16_t stdID;       // 标准 ID
-    uint8_t  data[8];     // 数据
-    uint8_t  len;         // 数据长度
+// 接收数据结构,保存接收到的报文信息
+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);
+
+// 发送报文
+void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len);
+
+// 接收报文
+void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount);
 
 #endif
 ```
@@ -210,132 +223,262 @@ void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t *msgCount);
 **文件:`stm32/stm32/01_can_test_register/Hardware/CAN/can.c`**
 
 ```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-21 16:52:16
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
 #include "can.h"
 
-// 过滤器配置(内部函数)
 static void CAN_FilterConfig(void);
 
-// CAN 初始化(环回静默模式,125Kbps)
+// 初始化
 void CAN_Init(void)
 {
-    /* ========== 1. 开启时钟 ========== */
-    // CAN1 挂载在 APB1 总线上(36MHz)
-    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;      // |=: CAN1时钟使能
-    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;       // |=: GPIOB时钟
-    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;       // |=: AFIO时钟(重映射用)
-
-    /* ========== 2. 引脚重映射(PB8=RX, PB9=TX) ========== */
-    // CAN 默认引脚是 PA11(RX)/PA12(TX)
-    // 开发板使用重映射到 PB8(RX)/PB9(TX)
-    // MAPR[25:24] = 10 → CAN 重映射到 PB8/PB9
-    AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;      // |=: 置位REMAP[1]
-    AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;     // &=~: 清零REMAP[0]
-
-    /* ========== 3. GPIO 模式 ========== */
-    // PB8(CAN_RX): 浮空输入 MODE=00, CNF=01
-    GPIOB->CRH &= ~GPIO_CRH_MODE8;            // MODE8=00(输入)
-    GPIOB->CRH &= ~GPIO_CRH_CNF8_1;           // CNF8[1]=0
-    GPIOB->CRH |= GPIO_CRH_CNF8_0;            // CNF8[0]=1 → 浮空输入
-
-    // PB9(CAN_TX): 复用推挽输出 MODE=11, CNF=10
-    GPIOB->CRH |= GPIO_CRH_MODE9;             // MODE9=11(50MHz输出)
-    GPIOB->CRH |= GPIO_CRH_CNF9_1;            // CNF9[1]=1
-    GPIOB->CRH &= ~GPIO_CRH_CNF9_0;           // CNF9[0]=0 → 复用推挽
-
-    /* ========== 4. CAN 初始化 ========== */
-    // 4.1 进入初始化模式(INRQ=1, 等待 INAK=1)
-    CAN1->MCR |= CAN_MCR_INRQ;               // |=: INRQ位置1
-    while ((CAN1->MSR & CAN_MSR_INAK) == 0)  // 等待INAK=1确认
+    // 1. 开启时钟:CAN、GPIO和AFIO
+    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
+
+    // 2. 重映射PB8和PB9
+    AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;
+    AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;
+
+    // 3. GPIO配置模式:
+    // PB8 - 浮空输入,MODE 00,CNF 01
+    GPIOB->CRH &= ~GPIO_CRH_MODE8;
+    GPIOB->CRH &= ~GPIO_CRH_CNF8_1;
+    GPIOB->CRH |= GPIO_CRH_CNF8_0;
+    
+    // PB9 - 复用推挽输出,MODE 11,CNF 10
+    GPIOB->CRH |= GPIO_CRH_MODE9;
+    GPIOB->CRH |= GPIO_CRH_CNF9_1;
+    GPIOB->CRH &= ~GPIO_CRH_CNF9_0;
+
+    // 4. CAN 初始化配置
+    // 4.1 进入初始化模式
+    CAN1->MCR |= CAN_MCR_INRQ;
+    while ((CAN1->MSR & CAN_MSR_INAK) == 0)
     {}
 
-    // 4.2 退出睡眠模式(SLEEP=0, 等待 SLAK=0)
-    CAN1->MCR &= ~CAN_MCR_SLEEP;             // &=~: SLEEP位清0
-    while ((CAN1->MSR & CAN_MSR_SLAK) != 0)  // 等待SLAK=0
+    // 4.2 退出睡眠模式
+    CAN1->MCR &= ~CAN_MCR_SLEEP;
+    while ((CAN1->MSR & CAN_MSR_SLAK) != 0)
     {}
 
     // 4.3 自动离线管理
-    CAN1->MCR |= CAN_MCR_ABOM;               // ABOM=1: 错误节点自动恢复
+    CAN1->MCR |= CAN_MCR_ABOM;
 
     // 4.4 自动唤醒管理
-    CAN1->MCR |= CAN_MCR_AWUM;               // AWUM=1: 检测总线活动自动唤醒
-
-    /* ========== 5. 配置位时序 ========== */
-    // 5.1 测试模式:环回 + 静默
-    CAN1->BTR |= CAN_BTR_SILM;               // SILM=1: 静默模式
-    CAN1->BTR |= CAN_BTR_LBKM;               // LBKM=1: 环回模式
-
-    // 5.2 波特率: APB1=36MHz, BRP=35 → Tq=1us
-    // BS1=3, BS2=5 → 位时间=1+3+5=9us → Baud≈111Kbps
-    CAN1->BTR &= ~CAN_BTR_BRP;               // 清除BRP位域
-    CAN1->BTR |= (35 << 0);                   // BRP[9:0]=35
-    CAN1->BTR &= ~CAN_BTR_TS1;               // 清除TS1位域
-    CAN1->BTR |= (3 << 16);                   // TS1[3:0]=3 (BS1=4Tq)
-    CAN1->BTR &= ~CAN_BTR_TS2;               // 清除TS2位域
-    CAN1->BTR |= (5 << 20);                   // TS2[2:0]=5 (BS2=6Tq)
-    CAN1->BTR &= ~CAN_BTR_SJW;               // 清除SJW位域
-    CAN1->BTR |= (1 << 24);                   // SJW[1:0]=1
-
-    // 5.3 退出初始化模式,进入正常模式
-    CAN1->MCR &= ~CAN_MCR_INRQ;              // &=~: INRQ位清0
-    while ((CAN1->MSR & CAN_MSR_INAK) != 0)  // 等待INAK=0确认
+    CAN1->MCR |= CAN_MCR_AWUM;
+
+    // 4.5 设置环回静默模式
+    CAN1->BTR |= CAN_BTR_SILM;
+    CAN1->BTR |= CAN_BTR_LBKM;
+
+    // 4.6 设置位时序
+    // 4.6.1 设置波特率分频系数:35,Tq = 1us
+    CAN1->BTR &= ~CAN_BTR_BRP;
+    CAN1->BTR |= (35 << 0);
+
+    // 4.6.2 设置BS1和BS2时间长度
+    CAN1->BTR &= ~CAN_BTR_TS1;
+    CAN1->BTR |= (2 << 16);
+
+    CAN1->BTR &= ~CAN_BTR_TS2;
+    CAN1->BTR |= (5 << 20);
+
+    // 4.6.3 配置同步跳转宽度
+    CAN1->BTR &= ~CAN_BTR_SJW;
+    CAN1->BTR |= (1 << 24);
+
+    // 4.7 退出初始化模式
+    CAN1->MCR &= ~CAN_MCR_INRQ;
+    while ((CAN1->MSR & CAN_MSR_INAK) != 0)
     {}
 
-    /* ========== 6. 过滤器配置 ========== */
+    // 5. CAN过滤器配置
     CAN_FilterConfig();
 }
 
+// 定义静态过滤器配置函数
 static void CAN_FilterConfig(void)
 {
-    // 1. 进入过滤器初始化模式
-    CAN1->FMR |= CAN_FMR_FINIT;              // FINIT=1: 允许配置过滤器
+    // 1. 进入初始化模式
+    CAN1->FMR |= CAN_FMR_FINIT;
 
-    // 2. 过滤器0: 掩码模式(32位)
-    CAN1->FM1R &= ~CAN_FM1R_FBM0;            // FBM0=0: 掩码模式
+    // 2. 设置过滤器组工作模式:0 - 掩码位模式
+    CAN1->FM1R &= ~CAN_FM1R_FBM0;
 
-    // 3. 过滤器位宽: 32位
-    CAN1->FS1R |= CAN_FS1R_FSC0;             // FSC0=1: 32位宽
+    // 3. 设置位宽:1 - 32位
+    CAN1->FS1R |= CAN_FS1R_FSC0;
 
-    // 4. 关联 FIFO: FIFO0
-    CAN1->FFA1R &= ~CAN_FFA1R_FFA0;          // FFA0=0: 匹配帧→FIFO0
+    // 4. 设置过滤器关联FIFO:FIFO0
+    CAN1->FFA1R &= ~CAN_FFA1R_FFA0;
 
-    // 5. 过滤器ID (FR1) = 0x06e ← 只接收 ID=0x06e 的帧
+    // 5. 设置过滤器组0的ID寄存器:FR1
     CAN1->sFilterRegister[0].FR1 = 0x06e << 21;
 
-    // 6. 过滤器掩码 (FR2) = 0x7f1 ← 高11位全部必须匹配
+    // 6. 设置过滤器组0的掩码寄存器:FR2 = 0,所有位都匹配
     CAN1->sFilterRegister[0].FR2 = 0x7f1 << 21;
 
-    // 7. 激活过滤器0
-    CAN1->FA1R |= CAN_FA1R_FACT0;            // FACT0=1: 激活
+    // 7. 激活过滤器0
+    CAN1->FA1R |= CAN_FA1R_FACT0;
 
-    // 8. 退出过滤器初始化模式
+    // 8. 退出初始化模式
     CAN1->FMR &= ~CAN_FMR_FINIT;
 }
 
-// 发送报文(使用发送邮箱0)
-void CAN_SendMsg(uint16_t stdID, uint8_t *data, uint8_t len)
+// 发送报文:标准ID、数据、长度,使用发送邮箱0
+void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len)
 {
-    // 1. 等待邮箱0为空
-    while ((CAN1->TSR & CAN_TSR_TME0) == 0)  // TME0=1 表示空
-    {}
+    // 1. 等待发送邮箱0为空
+    while ( (CAN1->TSR & CAN_TSR_TME0) == 0)
+    {
+    }
+
+    // 2. 组装要发送的数据帧
+    // 2.1 设置标准ID
+    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
+    CAN1->sTxMailBox[0].TIR |= stdID << 21;
+
+    // 2.2 设为标准帧
+    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;
 
-    // 2. 写入 ID
-    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;       // 清除STID位域
-    CAN1->sTxMailBox[0].TIR |= (uint32_t)stdID << 21; // 写入11位标准ID
+    // 2.3 设为数据帧
+    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;
 
-    // 3. 写入 DLC(数据长度)
-    CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;       // 清除DLC
-    CAN1->sTxMailBox[0].TDTR |= len;                   // 写入长度
+    // 2.4 设置数据长度
+    CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;
+    CAN1->sTxMailBox[0].TDTR |= len << 0;
 
-    // 4. 写入数据(最多8字节)
-    for (uint8_t i = 0; i < len && i < 8; i++)
+    // 2.5 写入数据
+    // 清空寄存器
+    CAN1->sTxMailBox[0].TDLR = 0;
+    CAN1->sTxMailBox[0].TDHR = 0;
+
+    // 循环写入每一个字节
+    for (uint8_t i = 0; i < len; i++)
     {
-        CAN1->sTxMailBox[0].TDLR = data[0];   // 低32位寄存器存data[0~3]
-        if (len > 4)                           // 超过4字节的存入高32位
-            CAN1->sTxMailBox[0].TDHR = *(uint32_t *)&data[4];
+        // 判断前4个字节,放到TDLR
+        if (i < 4)
+        {
+            CAN1->sTxMailBox[0].TDLR |= data[i] << (i * 8);
+        } 
+        // 后4个字节,放到TDHR
+        else
+        {
+            CAN1->sTxMailBox[0].TDHR |= data[i] << ((i - 4) * 8);
+        }
     }
 
-    // 5. 请求发送(TXRQ=1)
+    // 3. 请求发送该帧
     CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
+
+    // 4. 等待发送完成
+    while ((CAN1->TSR & CAN_TSR_TXOK0) == 0)
+    {
+    }
+}
+
+// 接收报文:结构体数组,数组长度,从FIFO0读取
+void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount)
+{
+    // 1. 获取FIFO0的报文个数,通过指针返回
+    * msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0;
+
+    // 2. 循环取出每一个报文
+    for (uint8_t i = 0; i < *msgCount; i++)
+    {
+        // 定义指针,指向当前保存报文的数据对象
+        RxMsg * msg = &rxMsg[i];
+
+        // 2.1 获取ID
+        msg->stdID = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7ff;
+
+        // 2.2 获取数据长度
+        msg->len = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0f;
+        
+        // 2.3 获取数据
+        uint32_t low = CAN1->sFIFOMailBox[0].RDLR;
+        uint32_t high = CAN1->sFIFOMailBox[0].RDHR;
+
+        for (uint8_t j = 0; j < msg->len; j++)
+        {
+            // 如果是前4个字节,就从RDLR读取
+            if (j < 4)
+            {
+                msg->data[j] = (low >> (8 * j)) & 0xff;
+            }      
+            // 如果是后4个字节,就从RDHR读取
+            else
+            {
+                msg->data[j] = (high >> (8 * (j - 4))) & 0xff;
+            }
+        }
+
+        // 2.4 释放FIFO0的邮箱,以便读取下一个报文
+        CAN1->RF0R |= CAN_RF0R_RFOM0;
+    }
+}
+```
+
+**文件:`stm32/stm32/01_can_test_register/User/main.c`**
+
+```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-09-13 16:29:39
+ * @Description:
+ *
+ * Copyright (c) 2024 by atguigu, All Rights Reserved.
+ */
+
+#include "usart.h"
+#include "can.h"
+#include <string.h>
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	CAN_Init();
+
+	printf("尚硅谷CAN通讯实验:环回静默模式测试,寄存器版...\n");
+
+	// 1. 发送三帧数据
+	uint16_t stdID = 0x066;
+	uint8_t * data = "abcdefg";
+	CAN_SendMsg(stdID, data, strlen((char *)data));
+
+	stdID = 0x068;
+	data = "123";
+	CAN_SendMsg(stdID, data, strlen((char *)data));
+
+	stdID = 0x067;
+	data = "xyz";
+	CAN_SendMsg(stdID, data, strlen((char *)data));
+
+	// 2. 开始接收
+	RxMsg rxMsg[3];
+	uint8_t msgCount;
+
+	CAN_ReceiveMsg(rxMsg, &msgCount);
+
+	printf("报文接收完毕,count = %d\n", msgCount);
+
+	// 3. 打印接收到的数据
+	for (uint8_t i = 0; i < msgCount; i++)
+	{
+		printf("stdID = %#X, len = %d, data = %.*s\n",
+			 rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data);
+	}
+
+	while (1)
+	{
+	}
 }
 ```
 

+ 360 - 67
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/14-以太网通信与W5500.md

@@ -126,104 +126,397 @@ W5500 的 SPI 数据帧与普通 SPI Flash 不同,分为**控制字节 + 数
 
 **项目路径**:`stm32/05_ethernet_test_register` ~ `stm32/09_ethernet_webserver_register`
 
-**文件:`stm32/05_ethernet_test_register/User/spi.c`**(SPI 初始化)
+**文件:`stm32/05_ethernet_test_register/Hardware/SPI/spi.h`**
 
 ```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-10-09 16:17:42
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
+#ifndef __SPI_H
+#define __SPI_H
+
+#include "stm32f10x.h"
+
+// 宏定义操作总线的引脚
+// CS - PD3
+#define CS_HIGH (GPIOD->ODR |= GPIO_ODR_ODR3)
+#define CS_LOW (GPIOD->ODR &= ~GPIO_ODR_ODR3)
+
+// 初始化
+void SPI_Init(void);
+
+// SPI通信的开启和关闭
+void SPI_Start(void);
+void SPI_Stop(void);
+
+// 一个时钟周期内,交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte);
+
+#endif
+```
+
+**文件:`stm32/05_ethernet_test_register/Hardware/SPI/spi.c`**
+
+```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-10-09 16:17:30
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
 #include "spi.h"
 
-// W5500 使用 STM32 SPI2:PB13(SCK), PB14(MISO), PB15(MOSI)
+// 初始化
 void SPI_Init(void)
 {
-    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;       // SPI2 在 APB1
+    // 1. 开启时钟
     RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
+    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
 
-    // PB13(SCK) 复用推挽, PB14(MISO) 浮空输入, PB15(MOSI) 复用推挽
-    GPIOB->CRH |= GPIO_CRH_CNF13_1;            // PB13 复用: CNF=10
-    GPIOB->CRH |= GPIO_CRH_MODE13;             // MODE=11(50MHz)
-    GPIOB->CRH &= ~GPIO_CRH_CNF14;             // PB14 浮空: CNF=01
-    GPIOB->CRH |= GPIO_CRH_CNF14_0;
-    GPIOB->CRH |= GPIO_CRH_CNF15_1;            // PB15 复用: CNF=10
+    // 2. GPIO配置模式
+    // 2.1 CS - PD3:通用推挽输出,CNF = 00,MODE = 11
+    GPIOD->CRL |= GPIO_CRL_MODE3;
+    GPIOD->CRL &= ~GPIO_CRL_CNF3;
+
+    // 2.2 SCK - PB13:复用推挽输出,CNF = 10,MODE = 11
+    GPIOB->CRH |= GPIO_CRH_MODE13;
+    GPIOB->CRH |= GPIO_CRH_CNF13_1;
+    GPIOB->CRH &= ~GPIO_CRH_CNF13_0;
+
+    // 2.3 MOSI - PB15:复用推挽输出,CNF = 10,MODE = 11
     GPIOB->CRH |= GPIO_CRH_MODE15;
+    GPIOB->CRH |= GPIO_CRH_CNF15_1;
+    GPIOB->CRH &= ~GPIO_CRH_CNF15_0;
+
+    // 2.4 MISO - PB14:浮空输入,CNF = 01,MODE = 00
+    GPIOB->CRH &= ~GPIO_CRH_MODE14;
+    GPIOB->CRH &= ~GPIO_CRH_CNF14_1;
+    GPIOB->CRH |= GPIO_CRH_CNF14_0;
+
+    // 3. SPI模块配置
+    // 3.1 配置模式参数
+    // 3.1.1 设为主模式
+    SPI2->CR1 |= SPI_CR1_MSTR;
 
-    // SPI2 配置:模式0(CPOL=0,CPHA=0), 主机, 分频=8 → 4.5MHz
-    SPI2->CR1 = 0;                              // 复位配置
-    SPI2->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;   // 软件 NSS
-    SPI2->CR1 |= SPI_CR1_MSTR;                 // 主机模式
-    SPI2->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1; // BR=011: PCLK/16=2.25MHz
-    SPI2->CR1 |= SPI_CR1_SPE;                  // SPI 使能
+    // 3.1.2 选择软件控制片选,SSI置为高电平
+    SPI2->CR1 |= SPI_CR1_SSM;
+    SPI2->CR1 |= SPI_CR1_SSI;
+
+    // 3.1.2 SPI通信模式设为模式0:CPOL = 0,CPHA = 0
+    SPI2->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
+
+    // 3.2 设置波特率:时钟分频系数,000 - 2分频,18MHz
+    SPI2->CR1 &= ~SPI_CR1_BR;
+    // SPI2->CR1 |= SPI_CR1_BR_0;
+
+    // 3.3 配置帧格式参数
+    // 3.3.1 数据帧长度:0 - 8位
+    SPI2->CR1 &= ~SPI_CR1_DFF;
+
+    // 3.3.2 数据传输顺序:0 - MSB先发
+    SPI2->CR1 &= ~SPI_CR1_LSBFIRST;
+
+    // 3.4 使能SPI
+    SPI2->CR1 |= SPI_CR1_SPE;
 }
 
-uint8_t SPI_ReadWrite(uint8_t byte)
+// SPI通信的开启和关闭,通过片选信号控制
+void SPI_Start(void)
 {
-    while (!(SPI2->SR & SPI_SR_TXE)) {}
+    CS_LOW;
+}
+void SPI_Stop(void)
+{
+    CS_HIGH;
+}
+
+// 一个时钟周期内,交换一个字节数据
+uint8_t SPI_SwapByte(uint8_t byte)
+{
+    // 1. 将要发送的数据写入发送缓冲区
+    // 1.1 等待发送缓冲区为空(TXE = 1)
+    while ((SPI2->SR & SPI_SR_TXE) == 0 )
+    {}
+    
+    // 1.2 将数据写入DR
     SPI2->DR = byte;
-    while (!(SPI2->SR & SPI_SR_RXNE)) {}
-    return SPI2->DR;
+
+    // 2. 获取接收到的数据并返回
+    // 2.1 等待接收缓冲区非空(RXNE = 1)
+    while ( (SPI2->SR & SPI_SR_RXNE) == 0 )
+    {}
+    
+    // 2.2 将DR中数据返回
+    return (uint8_t)(SPI2->DR & 0xff);
 }
 ```
 
-**文件:`stm32/09_ethernet_webserver_register/User/App_ETH.c`**(W5500 初始化与应用)
+**文件:`stm32/09_ethernet_webserver_register/Interface/Ethernet/eth.h`**
 
 ```c
-#include "w5500/wizchip_conf.h"
-#include "w5500/socket.h"
-
-// 网络配置
-wiz_NetInfo netInfo = {
-    .mac = {0x00, 0x08, 0xDC, 0x12, 0x34, 0x56},  // MAC 地址
-    .ip  = {192, 168, 1, 100},                      // IP 地址
-    .sn  = {255, 255, 255, 0},                      // 子网掩码
-    .gw  = {192, 168, 1, 1},                        // 默认网关
-    .dns = {8, 8, 8, 8},                            // DNS 服务器
-};
-
-void W5500_Init(void)
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-24 14:38:01
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
+#ifndef __ETH_H
+#define __ETH_H
+
+#include "w5500.h"
+#include <stdio.h>
+
+// 初始化
+void ETH_Init(void);
+
+#endif
+```
+
+**文件:`stm32/09_ethernet_webserver_register/Interface/Ethernet/eth.c`**
+
+```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-24 14:37:53
+ * @Description:
+ *
+ * Copyright (c) 2024 by atguigu, All Rights Reserved.
+ */
+#include "eth.h"
+#include "delay.h"
+
+// 定义W5500的IP地址、MAC地址、子网掩码和网关地址
+uint8_t ip[4] = {192, 168, 44, 222};
+uint8_t mac[6] = {110, 120, 130, 140, 150, 160};
+uint8_t submask[4] = {255, 255, 255, 0};
+uint8_t gateway[4] = {192, 168, 44, 1};
+
+// 复位W5500
+static void ETH_Reset(void);
+// 配置 MAC 地址
+static void ETH_SetMac(void);
+// 配置 IP 地址、子网掩码和网关
+static void ETH_SetIP(void);
+
+// 初始化
+void ETH_Init(void)
+{
+    // 0. SPI 初始化
+    SPI_Init();
+
+    // 1. 注册自定义回调函数
+    user_register_function();
+
+    // 2. 复位W5500
+    ETH_Reset();
+
+    // 3. 配置 MAC 地址
+    ETH_SetMac();
+
+    // 4. 配置 IP 地址、子网掩码和网关
+    ETH_SetIP();
+}
+
+// 复位W5500
+static void ETH_Reset(void)
 {
-    // 1. 硬件复位 W5500
-    RSTn_LOW;  Delay_ms(1);
-    RSTn_HIGH; Delay_ms(10);
+    // 1. 配置RST引脚-PG7
+    // 1.1 开启时钟
+    RCC->APB2ENR |= RCC_APB2ENR_IOPGEN;
+
+    // 1.2 配置工作模式:通用推挽输出,MODE - 11,CNF - 00
+    GPIOG->CRL |= GPIO_CRL_MODE7;
+    GPIOG->CRL &= ~GPIO_CRL_CNF7;
 
-    // 2. 注册 SPI 通信函数
-    reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);
-    reg_wizchip_spi_cbfunc(SPI_ReadWrite);
+    // 2. 拉低RST引脚,等待500us以上
+    GPIOG->ODR &= ~GPIO_ODR_ODR7;
 
-    // 3. W5500 初始化
-    uint8_t tmp;
-    wizchip_init(NULL, NULL);
+    Delay_us(800);
 
-    // 4. 配置网络参数
-    wizchip_setnetinfo(&netInfo);
+    GPIOG->ODR |= GPIO_ODR_ODR7;
 
-    // 5. 配置 WOL 和 PPPoE(默认关闭)
+    printf("W5500 复位完成!\n");
 }
 
-// 创建 TCP Server,监听 80 端口
-void HTTP_Server_Init(void)
+// 配置 MAC 地址
+static void ETH_SetMac(void)
 {
-    int32_t sock = socket(0, Sn_MR_TCP, 80, 0);  // Socket0, TCP, 80端口
-    if (sock < 0) { printf("Socket create failed\r\n"); return; }
+    printf("开始配置 MAC 地址:\n");
+    setSHAR(mac);
+    printf("MAC 地址配置完成:%X-%X-%X-%X-%X-%X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+// 配置 IP 地址、子网掩码和网关
+static void ETH_SetIP(void)
+{
+    printf("开始配置 IP 地址:\n");
 
-    listen(sock);                                   // 开始监听
-    printf("HTTP Server listening on port 80\r\n");
+    // 配置IP
+    setSIPR(ip);
+    // 配置子网掩码
+    setSUBR(submask);
+    // 配置网关地址
+    setGAR(gateway);
 
-    while (1)
+    printf("IP 地址配置完成:%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
+}
+```
+
+**文件:`stm32/09_ethernet_webserver_register/App/Web/web_server.h`**
+
+```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-25 16:35:51
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
+#ifndef __WEB_SERVER_H
+#define __WEB_SERVER_H
+
+#include "httpServer.h"
+#include "led.h"
+#include <string.h>
+
+// 初始化Web服务器
+void WebServer_Init(void);
+
+// 运行Web服务器
+void WebServer_Start(void);
+
+#endif
+```
+
+**文件:`stm32/09_ethernet_webserver_register/App/Web/web_server.c`**
+
+```c
+/*
+ * @Author: wushengran
+ * @Date: 2024-12-25 16:35:46
+ * @Description: 
+ * 
+ * Copyright (c) 2024 by atguigu, All Rights Reserved. 
+ */
+#include "web_server.h"
+
+// 全局变量定义,初始化Web服务器需要的参数
+uint8_t txBuff[2048] = {0};
+uint8_t rxBuff[2048] = {0};
+uint8_t socketCount = 8;
+uint8_t socketList[] = {0,1,2,3,4,5,6,7};
+
+uint8_t *contentName = "index.html";
+
+uint8_t content[] = "<!doctype html>\n"
+                             "<html lang=\"en\">\n"
+                             "<head>\n"
+                             "    <meta charset=\"GBK\">\n"
+                             "    <meta name=\"viewport\"\n"
+                             "          content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n"
+                             "    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n"
+                             "    <title>尚硅谷嵌入式课程</title>\n"
+                             "\n"
+                             "    <style type=\"text/css\">\n"
+                             "        #open_red{\n"
+                             "            color: red;\n"
+                             "            width: 100px;\n"
+                             "            height: 40px;\n"
+                             "\n"
+                             "\n"
+                             "        }\n"
+                             "        #close_red{\n"
+                             "            color: black;\n"
+                             "            width: 100px;\n"
+                             "            height: 40px;\n"
+                             "        }\n"
+                             "    </style>\n"
+                             "</head>\n"
+                             "<body>\n"
+                             "<a href=\"/index.html?action=1\"><button id=\"open_red\" >开灯</button></a>\n"
+                             "<a href=\"/index.html?action=2\"><button id=\"close_red\" >关灯</button></a>\n"
+                             "<a href=\"/index.html?action=3\"><button id=\"close_red\" >翻转</button></a>\n"
+                             "</body>\n"
+                             "</html>";
+
+
+// 初始化Web服务器
+void WebServer_Init(void)
+{
+    // 1. 初始化LED
+    LED_Init();
+
+    // 2. 初始化http服务器
+    httpServer_init(txBuff, rxBuff, socketCount, socketList);
+
+    // 3. 注册html页面,告诉Web服务器需要响应的数据,即页面
+    reg_httpServer_webContent(contentName, content);
+}
+
+// 运行Web服务器
+void WebServer_Start(void)
+{
+    for (uint8_t i = 0; i < sizeof(socketList); i++)
+    {
+        httpServer_run(i);
+    }
+}
+
+// 内部函数
+// 解析URL中的action
+static uint8_t parse_url_action(uint8_t *url);
+
+// 根据action执行LED控制
+static void do_led_action(uint8_t action);
+
+void handler_user_function(uint8_t *url)
+{
+    // 1. 从URL解析出action的值
+    uint8_t action = parse_url_action(url);
+
+    // 2. 根据action的值,执行相应的LED控制
+    do_led_action(action);
+}
+
+// 解析URL中的action
+static uint8_t parse_url_action(uint8_t *url)
+{
+    // 查找action=字符串的位置
+    uint8_t *pAction = (uint8_t *)strstr((char *)url, "action=");
+
+    if (pAction == NULL)
+    {
+        return '0';
+    }
+    else
     {
-        int32_t client = accept(sock);              // 等待客户端连接
-        if (client >= 0)
-        {
-            printf("Client connected\r\n");
-            uint8_t buffer[1460];
-            int32_t len = recv(client, buffer, sizeof(buffer));  // 接收 HTTP 请求
-
-            // 回复 HTTP 响应
-            char *response = "HTTP/1.1 200 OK\r\n"
-                             "Content-Type: text/html\r\n\r\n"
-                             "<h1>Hello from STM32 + W5500!</h1>";
-            send(client, (uint8_t *)response, strlen(response));
-
-            close(client);                           // 关闭连接
-        }
+        return *(pAction + 7);
     }
+    
+}
+
+// 根据action执行LED控制
+static void do_led_action(uint8_t action)
+{
+    if (action == '1')
+    {
+        LED_On(LED_2);
+    }
+    else if (action == '2')
+    {
+        LED_Off(LED_2);
+    }
+    else if (action == '3')
+    {
+        LED_Toggle(LED_2);
+    } 
 }
 ```
 

+ 205 - 67
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/15-WiFi通信与ESP32-C3.md

@@ -115,88 +115,226 @@ STM32(USART) ── TX ──→ RX(ESP32-C3)
 
 ### 软件设计(HAL 库版)
 
+**基础 AT 通信测试 — 文件:`stm32/10_wifi_test_hal/Core/Src/main.c`**
+
 ```c
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
 #include "usart.h"
-#include <string.h>
-#include <stdio.h>
-
-// 串口接收缓冲区
-uint8_t rx_buffer[512];
-uint8_t rx_len = 0;
-
-// 发送 AT 指令并等待期望响应
-// cmd: AT 指令字符串(以 \r\n 结尾)
-// expect: 期望响应中的关键字(如 "OK", "CONNECT", "WIFI GOT IP")
-// timeout: 超时 ms
-uint8_t AT_SendCmd(char *cmd, char *expect, uint32_t timeout)
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "esp32.h"
+/* USER CODE END Includes */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+int main(void)
 {
-    // 清空接收缓冲区
-    memset(rx_buffer, 0, sizeof(rx_buffer));
-    rx_len = 0;
 
-    // 发送 AT 指令
-    HAL_UART_Transmit(&huart2, (uint8_t *)cmd, strlen(cmd), 1000);
+  /* USER CODE BEGIN 1 */
 
-    // 等待响应
-    uint32_t start = HAL_GetTick();
-    while (HAL_GetTick() - start < timeout)
-    {
-        uint8_t ch;
-        if (HAL_UART_Receive(&huart2, &ch, 1, 10) == HAL_OK)
-        {
-            rx_buffer[rx_len++] = ch;
-            rx_buffer[rx_len] = '\0';
-
-            // 检查是否包含期望响应
-            if (strstr((char *)rx_buffer, expect))
-            {
-                return 1;  // 成功
-            }
-        }
-    }
-    return 0;  // 超时
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
+
+  /* USER CODE END Init */
+
+  /* Configure the system clock */
+  SystemClock_Config();
+
+  /* USER CODE BEGIN SysInit */
+
+  /* USER CODE END SysInit */
+
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  MX_USART1_UART_Init();
+  // MX_USART2_UART_Init();
+  /* USER CODE BEGIN 2 */
+
+  printf("尚硅谷Wi-Fi通信实验:AT指令...\n");
+
+  // 初始化ESP32
+  ESP32_Init();
+  printf("ESP32 初始化完成!\n");
+
+  // 1. 发送AT测试
+  uint8_t *cmd = "AT\r\n";
+  ESP32_SendCmd(cmd, strlen((char *)cmd));
+
+  // 2. 查看固件版本信息
+  cmd = "AT+GMR\r\n";
+  ESP32_SendCmd(cmd, strlen((char *)cmd));
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    /* USER CODE END WHILE */
+
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
 }
+```
+
+**TCP 服务器 — 文件:`stm32/11_wifi_tcp_server_hal/Core/Src/main.c`**
+
+```c
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "usart.h"
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "wifi.h"
+/* USER CODE END Includes */
 
-// WiFi + TCP 通信完整流程
-void WiFi_TCP_Test(void)
+/* Private variables ---------------------------------------------------------*/
+
+/* USER CODE BEGIN PV */
+
+// 全局变量定义,数据缓冲区及其长度、连接id、对端IP和端口号
+uint8_t rxBuff[1024];
+uint16_t rxLen;
+uint8_t id;
+uint8_t ip[16];
+uint16_t port;
+
+/* USER CODE END PV */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+int main(void)
 {
-    /* ===== 阶段1: 测试 AT 连接 ===== */
-    if (!AT_SendCmd("AT\r\n", "OK", 2000)) {
-        printf("ESP32 not responding!\r\n");
-        return;
-    }
 
-    /* ===== 阶段2: 设置 Station 模式 ===== */
-    AT_SendCmd("AT+CWMODE=1\r\n", "OK", 1000);
+  /* USER CODE BEGIN 1 */
 
-    /* ===== 阶段3: 连接 WiFi ===== */
-    printf("Connecting to WiFi...\r\n");
-    if (!AT_SendCmd("AT+CWJAP=\"MyWiFi\",\"password123\"\r\n",
-                     "WIFI GOT IP", 15000)) {
-        printf("WiFi connect failed!\r\n");
-        return;
-    }
-    printf("WiFi connected!\r\n");
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
+
+  /* USER CODE END Init */
+
+  /* Configure the system clock */
+  SystemClock_Config();
+
+  /* USER CODE BEGIN SysInit */
 
-    /* ===== 阶段4: 查询 IP ===== */
-    AT_SendCmd("AT+CIFSR\r\n", "OK", 1000);
-    printf("IP: %s\r\n", rx_buffer);
+  /* USER CODE END SysInit */
 
-    /* ===== 阶段5: 建立 TCP 连接 ===== */
-    if (!AT_SendCmd("AT+CIPSTART=\"TCP\",\"192.168.1.100\",8080\r\n",
-                     "CONNECT", 10000)) {
-        printf("TCP connect failed!\r\n");
-        return;
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  MX_USART1_UART_Init();
+  // MX_USART2_UART_Init();
+  /* USER CODE BEGIN 2 */
+
+  printf("尚硅谷Wi-Fi通信实验:TCP服务器...\n");
+
+  // 1. WiFi初始化
+  WIFI_Init(AP);
+
+  // 2. 启动TCP服务器
+  WIFI_TCP_ServerStart();
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    // 3. 从套接字读取
+    WIFI_TCP_ReadData(rxBuff, &rxLen, &id, ip, &port);
+
+    // 4. 如果收到数据,原样发送回去
+    if (rxLen > 0)
+    {
+      printf("收到数据:连接ID = %d, 对端IP:端口号 = %s:%d, 数据长度 = %d, 内容 = %.*s\n",
+        id, ip, port, rxLen, rxLen, rxBuff);
+
+      WIFI_TCP_SendData(id, rxBuff, rxLen);
+
+      // 清空长度0
+      rxLen = 0;
     }
-    printf("TCP connected!\r\n");
+    
+    /* USER CODE END WHILE */
 
-    /* ===== 阶段6: 发送数据 ===== */
-    AT_SendCmd("AT+CIPSEND=11\r\n", ">", 3000);
-    AT_SendCmd("Hello World", "SEND OK", 3000);
-    printf("Data sent!\r\n");
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
+}
 
-    /* ===== 阶段7: 关闭连接 ===== */
-    AT_SendCmd("AT+CIPCLOSE\r\n", "CLOSED", 3000);
+void SystemClock_Config(void)
+{
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  /** Initializes the RCC Oscillators according to the specified parameters
+   * in the RCC_OscInitTypeDef structure.
+   */
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the CPU, AHB and APB buses clocks
+   */
+  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;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
+  {
+    Error_Handler();
+  }
+}
+
+void Error_Handler(void)
+{
+  /* USER CODE BEGIN Error_Handler_Debug */
+  /* User can add his own implementation to report the HAL error return state */
+  __disable_irq();
+  while (1)
+  {
+  }
+  /* USER CODE END Error_Handler_Debug */
 }
 ```
 

+ 123 - 44
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/16-BLE低功耗蓝牙.md

@@ -93,62 +93,141 @@ STM32 通过串口连接 BLE 透传模块(支持 AT 指令),实现 BLE 数
 
 ### 软件设计
 
+**文件:`stm32/12_ble_server_hal/Core/Src/main.c`**
+
 ```c
-// BLE 模块初始化(Peripheral 模式 + 广播)
-void BLE_Init(void)
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "usart.h"
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "ble.h"
+/* USER CODE END Includes */
+
+/* Private variables ---------------------------------------------------------*/
+
+/* USER CODE BEGIN PV */
+
+// 全局变量定义,数据缓冲区及其长度
+uint8_t rxBuff[1024];
+uint16_t rxLen;
+
+/* USER CODE END PV */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+int main(void)
 {
-    // 1. 设置为 Peripheral(从机)模式
-    AT_SendCmd("AT+BLEINIT=2\r\n", "OK", 2000);
-    // AT+BLEINIT: 0=关闭BLE, 1=Central, 2=Peripheral
 
-    // 2. 设置设备名称(广播时显示)
-    AT_SendCmd("AT+BLENAME=\"STM32_BLE_Device\"\r\n", "OK", 1000);
+  /* USER CODE BEGIN 1 */
+
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
 
-    // 3. 设置广播间隔(单位 0.625ms,默认 100 = 62.5ms)
-    AT_SendCmd("AT+BADVINT=100\r\n", "OK", 1000);
-    // 值越小 → 被发现越快 → 功耗越高
+  /* USER CODE END Init */
 
-    // 4. 设置广播数据(自定义数据)
-    AT_SendCmd("AT+BLEADVDATA=\"020106\"\r\n", "OK", 1000);
-    // 02=长度, 01=标志类型, 06=LE General Discoverable + BR/EDR Not Support
+  /* Configure the system clock */
+  SystemClock_Config();
 
-    // 5. 启动广播
-    AT_SendCmd("AT+BLEADVSTART\r\n", "OK", 2000);
-    printf("BLE 广播中...\r\n");
+  /* USER CODE BEGIN SysInit */
+
+  /* USER CODE END SysInit */
+
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  MX_USART1_UART_Init();
+  // MX_USART2_UART_Init();
+  /* USER CODE BEGIN 2 */
+
+  printf("尚硅谷BLE实验...\n");
+
+  // BLE初始化
+  BLE_Init();
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    // 读取数据
+    BLE_ReadData(rxBuff, &rxLen);
+
+    // 如果收到数据,原样发送回去
+    if (rxLen > 0)
+    {
+      printf("收到数据:数据长度 = %d, 内容 = %.*s\n", rxLen, rxLen, rxBuff);
+
+      BLE_SendData(rxBuff, rxLen);
+
+      // 清空长度0
+      rxLen = 0;
+    }
+    
+    /* USER CODE END WHILE */
+
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
 }
 
-// 发送数据(通过 GATT 通知)
-void BLE_SendData(uint8_t *data, uint16_t len)
+void SystemClock_Config(void)
 {
-    char cmd[64];
-    // 格式: AT+BLESEND=conn_id,len\r\n
-    // 之后发送 len 字节的数据
-    sprintf(cmd, "AT+BLESEND=0,%d\r\n", len);
-    AT_SendCmd(cmd, ">", 3000);
-
-    // 发送实际数据
-    HAL_UART_Transmit(&huart2, data, len, 1000);
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  /** Initializes the RCC Oscillators according to the specified parameters
+   * in the RCC_OscInitTypeDef structure.
+   */
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the CPU, AHB and APB buses clocks
+   */
+  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;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
+  {
+    Error_Handler();
+  }
 }
 
-// BLE 数据接收(串口主动上报)
-// 当手机发送数据给 STM32 时,BLE 模块通过串口上报:
-// +BLEDATA: conn_id,len,data
-void BLE_ProcessRxData(uint8_t *buffer, uint16_t len)
+void Error_Handler(void)
 {
-    if (strstr((char *)buffer, "+BLEDATA:"))
-    {
-        // 解析数据并处理
-        char *data_ptr = strstr((char *)buffer, ",");
-        if (data_ptr)
-        {
-            data_ptr = strstr(data_ptr + 1, ",");
-            if (data_ptr)
-            {
-                data_ptr++;  // 跳过逗号,指向实际数据
-                printf("Received BLE: %s\r\n", data_ptr);
-            }
-        }
-    }
+  /* USER CODE BEGIN Error_Handler_Debug */
+  /* User can add his own implementation to report the HAL error return state */
+  __disable_irq();
+  while (1)
+  {
+  }
+  /* USER CODE END Error_Handler_Debug */
 }
 ```
 

+ 285 - 63
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/17-LoRa远距离通信.md

@@ -79,100 +79,322 @@ LoRa 网络采用**星型拓扑**:
 
 ## 实验:LoRa 节点与网关通信
 
-### 软件设计(AT 指令模式
+### 软件设计(SPI 驱动 API 模式 — LLCC68 库
 
 **项目路径**:`stm32/13_lora_node_hal`(节点)、`stm32/14_lora_gateway_hal`(网关)
 
-节点通过串口配置 LoRa 模块参数,定时发送传感器数据
+实际代码使用 LLCC68 芯片 SPI 驱动 API,通过 SPI 配置 LoRa 射频参数并收发数据,而非 AT 指令
 
-**文件:`stm32/13_lora_node_hal`(节点发送端)**
+**文件:`stm32/13_lora_node_hal/Core/Src/main.c`(节点端)**
 
 ```c
-// LoRa 模块初始化 AT 配置
-void LoRa_Node_Init(void)
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "spi.h"
+#include "usart.h"
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "lora.h"
+/* USER CODE END Includes */
+
+/* Private variables ---------------------------------------------------------*/
+
+/* USER CODE BEGIN PV */
+
+// 定义全局变量,按键标志位,以及计数值
+uint8_t isKeyed;
+uint32_t count;
+
+/* USER CODE END PV */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+int main(void)
 {
-    // 1. 设置模块工作模式
-    AT_SendCmd("AT+MODE=0\r\n", "OK", 1000);
-    // MODE: 0=透明传输(透传), 1=定点传输(指定地址)
 
-    // 2. 设置本机地址(不同类型设备用不同地址区分)
-    AT_SendCmd("AT+ADDR=1\r\n", "OK", 1000);     // 节点地址=1
+  /* USER CODE BEGIN 1 */
+
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
+
+  /* USER CODE END Init */
+
+  /* Configure the system clock */
+  SystemClock_Config();
+
+  /* USER CODE BEGIN SysInit */
+
+  /* USER CODE END SysInit */
 
-    // 3. 设置射频参数
-    AT_SendCmd("AT+BAND=470000000\r\n", "OK", 1000);  // 频率 470MHz
-    AT_SendCmd("AT+SF=12\r\n", "OK", 1000);            // 扩频因子 12
-    AT_SendCmd("AT+BW=125\r\n", "OK", 1000);           // 带宽 125KHz
-    AT_SendCmd("AT+CR=1\r\n", "OK", 1000);              // 编码率 4/5
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  // MX_SPI1_Init();
+  MX_USART1_UART_Init();
+  /* USER CODE BEGIN 2 */
 
-    // 4. 设置发射功率(0~20dBm,默认 20dBm)
-    AT_SendCmd("AT+POWER=20\r\n", "OK", 1000);
+  printf("尚硅谷LoRa通讯实验:普通节点...\n");
+
+  LoRa_Init();
+
+  // 进入接收模式
+  LoRa_EnterRxMode();
+
+  // 定义接收缓冲区和长度
+  uint8_t rxBuff[256];
+  uint16_t rxLen;
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    LoRa_RecvData(rxBuff, &rxLen);
+
+    if (rxLen > 0)
+    {
+      printf("收到数据:data: %.*s\n", rxLen, rxBuff);
+
+      // 清空长度
+      rxLen = 0;
+    }
+    if (isKeyed)
+    {
+      printf("按键按下!\n");
+
+      // 准备发送数据,拼接上按键次数
+      uint8_t msg[100] = {0};
+      sprintf((char *)msg, "一个普通LoRa节点,准备开始发送数据... %d", ++count);
+
+      LoRa_SendData(msg, strlen((char *)msg));
+
+      isKeyed = 0;  // 清除标志位
+
+      // 再次进入接收模式
+      LoRa_EnterRxMode();
+    }
+    /* USER CODE END WHILE */
+
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
 }
 
-// 发送数据到网关(地址=0, 因为网关是透明接收模式)
-void LoRa_Node_Send(uint8_t *data, uint16_t len)
+void SystemClock_Config(void)
 {
-    char cmd[256];
-    // 透明传输模式: AT+SEND=dest_addr,len,data
-    // dest_addr=0 表示所有设备都能收
-    sprintf(cmd, "AT+SEND=0,%d,", len);
-    AT_SendCmd(cmd, "OK", 2000);
-    // 发送实际数据
-    HAL_UART_Transmit(&huart2, data, len, 1000);
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  /** Initializes the RCC Oscillators according to the specified parameters
+  * in the RCC_OscInitTypeDef structure.
+  */
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the CPU, AHB and APB buses clocks
+  */
+  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;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
+  {
+    Error_Handler();
+  }
 }
 
-// 应用程序:每 60 秒上报一次温度值
-void Node_Application(void)
+/* USER CODE BEGIN 4 */
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
 {
-    LoRa_Node_Init();
+  if (GPIO_Pin == KEY_Pin)
+  {
+    // 延时消抖
+    HAL_Delay(100);
 
-    while (1)
+    if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin))
     {
-        uint16_t adc_val = ADC1_Read();          // 读取 ADC(温度传感器)
-        float    temp    = adc_val * 3.3f / 4095 * 100;  // 温度转换
-
-        char buf[64];
-        sprintf(buf, "TEMP:%.1f\r\n", temp);
-        LoRa_Node_Send((uint8_t *)buf, strlen(buf));
-
-        HAL_Delay(60000);  // 60s 发送一次(超低功耗场景)
+      isKeyed = 1;
     }
+    
+  }
+  
+}
+/* USER CODE END 4 */
+
+void Error_Handler(void)
+{
+  /* USER CODE BEGIN Error_Handler_Debug */
+  /* User can add his own implementation to report the HAL error return state */
+  __disable_irq();
+  while (1)
+  {
+  }
+  /* USER CODE END Error_Handler_Debug */
 }
 ```
 
-**文件:`stm32/14_lora_gateway_hal`(网关接收端)**
+**文件:`stm32/14_lora_gateway_hal/Core/Src/main.c`(网关端)**
 
 ```c
-// 网关初始化(使用与节点相同的射频参数)
-void LoRa_Gateway_Init(void)
-{
-    // 与节点相同:470MHz, SF12, BW125
-    AT_SendCmd("AT+MODE=0\r\n", "OK", 1000);
-    AT_SendCmd("AT+ADDR=0\r\n", "OK", 1000);      // 网关地址=0
-    AT_SendCmd("AT+BAND=470000000\r\n", "OK", 1000);
-    AT_SendCmd("AT+SF=12\r\n", "OK", 1000);
-    AT_SendCmd("AT+BW=125\r\n", "OK", 1000);
-}
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "spi.h"
+#include "usart.h"
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "lora.h"
+/* USER CODE END Includes */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
 
-// 数据接收中断(串口主动上报)
-// LoRa 模块收到数据后通过串口打印:
-// +RCV=src_addr,len,RSSI,SNR,data
-void Process_LoRa_RxData(uint8_t *buffer)
+/* USER CODE END 0 */
+
+int main(void)
 {
-    if (strstr((char *)buffer, "+RCV="))
+
+  /* USER CODE BEGIN 1 */
+
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
+
+  /* USER CODE END Init */
+
+  /* Configure the system clock */
+  SystemClock_Config();
+
+  /* USER CODE BEGIN SysInit */
+
+  /* USER CODE END SysInit */
+
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  // MX_SPI1_Init();
+  MX_USART1_UART_Init();
+  /* USER CODE BEGIN 2 */
+
+  printf("尚硅谷LoRa通讯实验:网关节点...\n");
+
+  LoRa_Init();
+
+  // 进入接收模式
+  LoRa_EnterRxMode();
+
+  // 定义接收缓冲区和长度
+  uint8_t rxBuff[256];
+  uint16_t rxLen;
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    LoRa_RecvData(rxBuff, &rxLen);
+
+    if (rxLen > 0)
     {
-        // 解析 RSSI(接收信号强度)和 SNR(信噪比)
-        // e.g., +RCV=1,6,-115,5,HELLO
-        //         ↑地址 ↑RSSI ↑SNR
+      printf("收到数据:data: %.*s\n", rxLen, rxBuff);
 
-        int src_addr, len, rssi, snr;
-        char data[64];
+      // 清空长度
+      rxLen = 0;
 
-        sscanf((char *)buffer, "+RCV=%d,%d,%d,%d,%s",
-               &src_addr, &len, &rssi, &snr, data);
+      // 发送回复消息
+      uint8_t *msg = "网关已经收到你的数据,收到请回复...";
+      LoRa_SendData(msg, strlen((char *)msg));
 
-        printf("Node %d: %s (RSSI=%d, SNR=%d)\r\n",
-               src_addr, data, rssi, snr);
+      // 再次进入接收模式
+      LoRa_EnterRxMode();
     }
+    
+    /* USER CODE END WHILE */
+
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
+}
+
+void SystemClock_Config(void)
+{
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  /** Initializes the RCC Oscillators according to the specified parameters
+  * in the RCC_OscInitTypeDef structure.
+  */
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the CPU, AHB and APB buses clocks
+  */
+  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;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
+  {
+    Error_Handler();
+  }
+}
+
+void Error_Handler(void)
+{
+  /* USER CODE BEGIN Error_Handler_Debug */
+  /* User can add his own implementation to report the HAL error return state */
+  __disable_irq();
+  while (1)
+  {
+  }
+  /* USER CODE END Error_Handler_Debug */
 }
 ```
 

+ 247 - 32
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/18-电源管理与低功耗模式.md

@@ -134,18 +134,24 @@ CPU + 所有外设时钟都停止,但 **SRAM 和寄存器内容保留**。电
 
 **配置与进入**:
 ```c
-// 1. 设置电源控制寄存器:进入停止模式时调压器模式
-// LPDS=1: 调压器低功耗模式(更省电)
-PWR->CR |= PWR_CR_LPDS;
+// 进入停止模式的函数
+void enter_stop_mode(void)
+{
+	// 1. 设置深度睡眠模式
+	SCB->SCR |= SCB_SCR_SLEEPDEEP;
+
+	// 2. 开启PWR模块时钟
+	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
 
-// 2. 设置 SLEEPDEEP 位
-SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
+	// 3. 设置PDDS = 0,表示停止模式
+	PWR->CR &= ~PWR_CR_PDDS;
 
-// 3. 选择停止模式(PDDS=0)
-PWR->CR &= ~PWR_CR_PDDS;
+	// 4. 设置电压调节器的工作模式:低功耗模式
+	PWR->CR |= PWR_CR_LPDS;
 
-// 4. 执行 WFI 进入停止模式
-__WFI();
+	// 5. 使用WFI指令,进入停止模式
+	__WFI();
+}
 ```
 
 **唤醒后**:触发 EXTI 中断后,系统从停止模式恢复,执行 ISR,恢复运行主程序。
@@ -156,24 +162,40 @@ __WFI();
 
 **配置与进入**:
 ```c
-// 1. 设置 SLEEPDEEP
-SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
+// 进入待机模式的函数
+void enter_standby_mode(void)
+{
+	// 1. 设置深度睡眠模式
+	SCB->SCR |= SCB_SCR_SLEEPDEEP;
+
+	// 2. 设置PDDS = 1,表示待机模式
+	PWR->CR |= PWR_CR_PDDS;
 
-// 2. 选择待机模式(PDDS=1)
-PWR->CR |= PWR_CR_PDDS;
+	// 3. 使能 PA0 的唤醒功能
+	PWR->CSR |= PWR_CSR_EWUP;
 
-// 3. 执行 WFI(或 WFE)
-__WFI();
+	// 4. 使用WFI指令,进入待机模式
+	__WFI();
+}
 
 // 唤醒后相当于系统复位!从 startup 重新执行。
 ```
 
 **唤醒后检测**:
 ```c
-if (PWR->CSR & PWR_CSR_SBF)      // SBF=1: 从待机模式唤醒
+// 开启PWR模块时钟
+RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+
+// 判断标志位来判断是否从待机模式唤醒
+if (PWR->CSR & PWR_CSR_SBF)
 {
-    PWR->CSR |= PWR_CSR_WUF;     // 清除唤醒标志
-    // 执行待机后恢复程序
+    printf("从待机模式唤醒:\n");
+    PWR->CR |= PWR_CR_CSBF;
+}
+if (PWR->CSR & PWR_CSR_WUF)
+{
+    printf("产生了唤醒事件:\n");
+    PWR->CR |= PWR_CR_CWUF;
 }
 ```
 
@@ -189,29 +211,222 @@ if (PWR->CSR & PWR_CSR_SBF)      // SBF=1: 从待机模式唤醒
 | 停止模式 | `stm32/17_lowpower_stop_register` | `stm32/18_lowpower_stop_hal` |
 | 待机模式 | `stm32/19_lowpower_standby_register` | `stm32/20_lowpower_standby_hal` |
 
-**文件:`stm32/17_lowpower_stop_register/User/main.c`**(停止模式示例)
+**文件:`stm32/17_lowpower_stop_register/User/main.c`**(停止模式,进入停止模式后通过 KEY 唤醒)
+
+```c
+#include "usart.h"
+#include "delay.h"
+#include "led.h"
+#include "key.h"
+
+void enter_stop_mode(void);
+void get_clock_freq(uint32_t *sys_clock, uint32_t *AHB_clock, uint32_t *APB2_clock, uint32_t *APB1_clock);
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	LED_Init();
+	KEY_Init();
+
+	printf("尚硅谷低功耗实验:停止模式...\n");
+
+	// 1. 点亮LED灯,延时2s,模拟正常运行程序
+	LED_On(LED_2);
+	Delay_s(2);
+
+	while (1)
+	{
+		// 2. 进入停止模式
+		printf("正常运行执行完毕,3s后进入停止模式...\n");
+		Delay_s(3);
+		printf("进入停止模式...\n");
+		Delay_ms(1);	// 稍作延时确保串口信息成功发送
+		enter_stop_mode();
+
+		uint32_t sys_clock, ahb_clock, apb1_clock, apb2_clock;
+		get_clock_freq(&sys_clock, &ahb_clock, &apb2_clock, &apb1_clock);
+
+		// 3. 重新进行系统初始化,配置系统时钟
+		SystemInit();
+
+		printf("唤醒前的时钟频率:\nsys_clock = %d, ahb_clock = %d, apb1_clock = %d, apb2_clock = %d\n\n",
+				sys_clock, ahb_clock, apb1_clock, apb2_clock);
+
+		get_clock_freq(&sys_clock, &ahb_clock, &apb2_clock, &apb1_clock);
+
+		printf("唤醒后的时钟频率:\nsys_clock = %d, ahb_clock = %d, apb1_clock = %d, apb2_clock = %d\n\n",
+				sys_clock, ahb_clock, apb1_clock, apb2_clock);
+
+		// 4. 以下代码只有在唤醒之后才会执行
+		printf("从停止模式唤醒...\n");
+		Delay_s(2);
+	}
+}
+
+// 进入深度睡眠模式的函数
+void enter_stop_mode(void)
+{
+	// 1. 设置深度睡眠模式
+	SCB->SCR |= SCB_SCR_SLEEPDEEP;
+
+	// 2. 开启PWR模块时钟
+	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+
+	// 3. 设置PDDS = 0,表示停止模式
+	PWR->CR &= ~PWR_CR_PDDS;
+
+	// 4. 设置电压调节器的工作模式:低功耗模式
+	PWR->CR |= PWR_CR_LPDS;
+
+	// 5. 使用WFI指令,进入停止模式
+	__WFI();
+}
+
+// 自定义函数,查询各时钟频率(系统时钟sysclock、AHB、APB2、APB1)
+void get_clock_freq(uint32_t *sys_clock, uint32_t *AHB_clock, uint32_t *APB2_clock, uint32_t *APB1_clock)
+{
+	// 1. 获取时钟源
+	uint32_t clock_src = RCC->CFGR & RCC_CFGR_SWS;
+
+	// 2. 根据时钟源获取系统时钟频率
+	if (clock_src == RCC_CFGR_SWS_HSE)
+	{
+		*sys_clock = HSE_VALUE;
+	}
+	else if (clock_src == RCC_CFGR_SWS_HSI)
+	{
+		*sys_clock = HSI_VALUE;
+	}
+	else if (clock_src == RCC_CFGR_SWS_PLL)
+	{
+		// 取倍频系数值
+		uint32_t mul = ((RCC->CFGR & RCC_CFGR_PLLMULL) >> 18) + 2;
+		*sys_clock = HSE_VALUE * mul;
+	}
+
+	// 3. 根据分频系数确定最终时钟频率
+	// 3.1 AHB
+	// 3.1.1 获取分频系数
+	uint32_t hpre = (RCC->CFGR & RCC_CFGR_HPRE) >> 4;
+	// 3.1.2 检查最高位来判断是否有分频
+	if (hpre & 0x8)
+	{
+		// 有分频,取低三位
+		uint32_t n = hpre & 0x7;
+		// 与4比较,选则n+1位或n+2位
+		if (n < 4)
+		{
+			*AHB_clock = *sys_clock >> (n+1);
+		}
+		else
+		{
+			*AHB_clock = *sys_clock >> (n+2);
+		}
+	}
+	else
+	{
+		// 没有分频
+		*AHB_clock = *sys_clock;
+	}
+
+	// 3.2 APB1
+	// 3.2.1 获取分频系数
+	uint32_t ppre1 = (RCC->CFGR & RCC_CFGR_PPRE1) >> 8;
+	// 3.2.2 检查最高位来判断是否有分频
+	if (ppre1 & 0x4)
+	{
+		*APB1_clock = *AHB_clock >> ((ppre1 & 0x3) + 1);
+	}
+	else
+	{
+		// 没有分频
+		*APB1_clock = *AHB_clock;
+	}
+
+	// 3.3 APB2
+	// 3.3.1 获取分频系数
+	uint32_t ppre2 = (RCC->CFGR & RCC_CFGR_PPRE2) >> 11;
+	// 3.3.2 检查最高位来判断是否有分频
+	if (ppre2 & 0x4)
+	{
+		*APB2_clock = *AHB_clock >> ((ppre2 & 0x3) + 1);
+	}
+	else
+	{
+		// 没有分频
+		*APB2_clock = *AHB_clock;
+	}
+}
+```
+
+**文件:`stm32/19_lowpower_standby_register/User/main.c`**(待机模式,通过 PA0 WKUP 引脚唤醒)
 
 ```c
-#include "stm32f10x.h"
+#include "usart.h"
+#include "delay.h"
+#include "led.h"
+#include "key.h"
+
+void enter_standby_mode(void);
 
 int main(void)
 {
-    // 初始化 USART 和按键
-    USART1_Init();
-    KEY_Init();  // 配置 PF10 为 EXTI 中断(作为唤醒源)
+	// 初始化
+	USART_Init();
+	LED_Init();
+	KEY_Init();
+
+	// 开启PWR模块时钟
+	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
+
+	// 判断标志位来判断是否从待机模式唤醒
+	if (PWR->CSR & PWR_CSR_SBF)
+	{
+		printf("从待机模式唤醒:\n");
+		PWR->CR |= PWR_CR_CSBF;
+	}
+	if (PWR->CSR & PWR_CSR_WUF)
+	{
+		printf("产生了唤醒事件:\n");
+		PWR->CR |= PWR_CR_CWUF;
+	}
+
+	printf("尚硅谷低功耗实验:待机模式...\n");
+
+	// 1. 点亮LED灯,延时2s,模拟正常运行程序
+	LED_On(LED_2);
+	Delay_s(2);
+
+	while (1)
+	{
+		// 2. 进入待机模式
+		printf("正常运行执行完毕,3s后进入待机模式...\n");
+		Delay_s(3);
+		printf("进入待机模式,按下KEY唤醒...\n");
+		Delay_ms(1);	// 稍作延时确保串口信息成功发送
+		enter_standby_mode();
+
+		// 4. 以下代码不会执行
+		printf("从待机模式唤醒...\n");
+		Delay_s(2);
+	}
+}
 
-    printf("进入停止模式,按 KEY 唤醒...\r\n");
+// 进入待机模式的函数
+void enter_standby_mode(void)
+{
+	// 1. 设置深度睡眠模式
+	SCB->SCR |= SCB_SCR_SLEEPDEEP;
 
-    // 进入停止模式
-    PWR->CR |= PWR_CR_LPDS;          // 调压器低功耗
-    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  // SLEEPDEEP=1
-    PWR->CR &= ~PWR_CR_PDDS;         // PDDS=0 → 停止模式
-    __WFI();                         // 进入停止模式
+	// 2. 设置PDDS = 1,表示待机模式
+	PWR->CR |= PWR_CR_PDDS;
 
-    // 唤醒后从此继续执行
-    printf("已从停止模式唤醒\r\n");
+	// 3. 使能 PA0 的唤醒功能
+	PWR->CSR |= PWR_CSR_EWUP;
 
-    while (1);
+	// 4. 使用WFI指令,进入待机模式
+	__WFI();
 }
 ```
 

+ 417 - 100
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/19-BKP备份寄存器与RTC实时时钟.md

@@ -47,30 +47,68 @@ BKP(Backup)寄存器共 **42 个 16 位寄存器**(总容量 84 字节)
 
 ### 数据存储(写入 BKP 寄存器)
 
+**文件:`stm32/21_bkp_test_register/Hardware/BKP/bkp.h`**
+
+```c
+#ifndef __BKP_H
+#define __BKP_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void BKP_Init(void);
+
+#endif
+```
+
+**文件:`stm32/21_bkp_test_register/Hardware/BKP/bkp.c`**
+
 ```c
-// 写 BKP 前必须使能对备份域的写访问
-// PWR_CR 的 DBP 位控制备份域的写保护
-void BKP_Write(uint16_t reg_index, uint16_t data)
+#include "bkp.h"
+
+// 初始化
+void BKP_Init(void)
 {
-    // 1. 使能对备份寄存器和 RTC 的访问
-    RCC->APB1ENR |= RCC_APB1ENR_PWREN;      // 开启 PWR 时钟
-    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;      // 开启 BKP 时钟
+    // 1. 开启PWR时钟
+    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
 
     // 2. 解锁备份域写保护
-    PWR->CR |= PWR_CR_DBP;                   // DBP=1: 允许写备份域
+    PWR->CR |= PWR_CR_DBP;
+
+    // 3. 开启BKP时钟
+    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
 
-    // 3. 写入备份寄存器(BKP_DR1~BKP_DR42)
-    // BKP 寄存器地址偏移: 0x04 + (reg_index-1) * 4
-    BKP->DR[reg_index - 1] = data;           // 写入
+    // // 4. 复位备份域寄存器
+    // RCC->BDCR |= RCC_BDCR_BDRST;
 
-    // 4. 重新加锁(可选的,安全考虑)
-    // PWR->CR &= ~PWR_CR_DBP;
+    // // 5. 结束备份域复位
+    // RCC->BDCR &= ~RCC_BDCR_BDRST;
 }
+```
+
+**文件:`stm32/21_bkp_test_register/User/main.c`**
+
+```c
+#include "usart.h"
+#include "delay.h"
+#include "key.h"
+#include "bkp.h"
 
-uint16_t BKP_Read(uint16_t reg_index)
+int main(void)
 {
-    // 读取不需要 DBP 解锁
-    return BKP->DR[reg_index - 1];
+	// 初始化
+	USART_Init();
+	KEY_Init();
+	BKP_Init();
+
+	printf("尚硅谷备份寄存器实验...\n");
+
+	// 向备份数据寄存器写入数据值
+	// BKP->DR1 = 9999;
+
+	while (1)
+	{
+	}
 }
 ```
 
@@ -112,19 +150,13 @@ RTC 有三个时钟源可选:
 
 ### RTC 预分频器
 
-RTC 预分频器由两部分组成:
+RTC 预分频器由 20 位组成(7 位异步 + 13 位同步),**STM32F1 的 RTC 只使用一个 16 位 PRL 寄存器**(低 13 位有效)。
 
 ```c
-// 异步预分频(7 位,最大 128 分频)
-// 同步预分频(15 位,最大 32768 分频)
-// 总预分频 = (ASYNC_PREDIV + 1) × (SYNC_PREDIV + 1)
-
-// LSE = 32.768KHz 时:
-RTC->PRLH = 0x0000;            // 异步预分频高(很少用)
-RTC->PRLL = 127;               // 异步 = 127+1 = 128
-RTC->DIVH = 0x0000;            // 同步预分频高(很少用)
-RTC->DIVL = 255;               // 同步 = 255+1 = 256
-// 总 = 128 × 256 = 32768 → 1Hz 基准时钟
+// LSE = 32.768KHz 时,PRL = 32767 → 1Hz
+// 配置流程(封装在 RTC_Init 中):
+RTC->PRLH = 0;
+RTC->PRLL = 0x7fff;        // 32767
 ```
 
 ### RTC 寄存器
@@ -140,117 +172,402 @@ RTC->DIVL = 255;               // 同步 = 255+1 = 256
 
 > **注意**:RTC 寄存器通过 **APB1 接口**访问,但 APB1 频率可能低于 RTC 频率,因此读取前需等待 RSF 标志同步。
 
-### RTC 读写流程
+### RTC 初始化与配置
+
+**文件:`stm32/25_rtc_calendar_register/Hardware/RTC/rtc.h`**
+
+```c
+#ifndef __RTC_H
+#define __RTC_H
+
+#include "stm32f10x.h"
+#include <time.h>
+
+// 自定义日期时间结构体类型
+typedef struct
+{
+    uint16_t year;
+    uint8_t month;
+    uint8_t day;
+    uint8_t hour;
+    uint8_t minute;
+    uint8_t second;
+} DateTime;
+
+// 初始化
+void RTC_Init(void);
+
+// 设置闹钟(s秒之后报警)
+void RTC_SetAlarm(uint32_t s);
+
+// 设置当前时间(UNIX时间戳)
+void RTC_SetTimestamp(uint32_t ts);
+
+// 获取日期时间(年月日时分秒)
+void RTC_GetDateTime(DateTime * dateTime);
+
+#endif
+```
+
+**文件:`stm32/25_rtc_calendar_register/Hardware/RTC/rtc.c`**(RTC 初始化 + 日历功能)
 
 ```c
-// RTC 写入流程(RTC 寄存器属于备份域,写入需特殊时序)
-void RTC_SetCounter(uint32_t count)
+#include "rtc.h"
+
+// 初始化
+void RTC_Init(void)
 {
-    // 1. 电源和时钟使能
+    // 1. 开启PWR时钟
     RCC->APB1ENR |= RCC_APB1ENR_PWREN;
-    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
-    PWR->CR |= PWR_CR_DBP;                // 允许写备份域
 
-    // 2. RTC 配置模式
-    RTC->CRL |= RTC_CRL_CNF;              // CNF=1: 进入配置模式
+    // 1.2 解锁备份域写保护
+    PWR->CR |= PWR_CR_DBP;
+
+    // // 1.3 复位备份域寄存器
+    // RCC->BDCR |= RCC_BDCR_BDRST;
+    // // 1.4 结束备份域复位
+    // RCC->BDCR &= ~RCC_BDCR_BDRST;
+
+    // 2. 开启RTC时钟源以及使能RTC
+    // 2.1 使能RTC时钟
+    RCC->BDCR |= RCC_BDCR_RTCEN;
+
+    // 2.2 使能LSE并等待其就绪
+    RCC->BDCR |= RCC_BDCR_LSEON;
+    while (!(RCC->BDCR & RCC_BDCR_LSERDY))
+    {
+    }
+
+    // 2.3 选择LSE作为RTC的时钟源
+    RCC->BDCR &= ~RCC_BDCR_RTCSEL;
+    RCC->BDCR |= RCC_BDCR_RTCSEL_0;
+
+    // 3. RTC寄存器配置操作
+    // 3.1 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {
+    }
+
+    // 3.2 进入配置模式
+    RTC->CRL |= RTC_CRL_CNF;
+
+    // 3.3 设置预分频系数 32767,得到1秒计数
+    RTC->PRLH = 0;
+    RTC->PRLL = 0x7fff;
+
+    // 3.4 退出配置模式
+    RTC->CRL &= ~RTC_CRL_CNF;
+
+    // 3.5 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {
+    }
+}
+
+// 设置闹钟(s秒之后报警)
+void RTC_SetAlarm(uint32_t s)
+{
+    // 0. 清除闹钟标志
+    RTC->CRL &= ~RTC_CRL_ALRF;
+
+    // 1. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {
+    }
 
-    // 3. 写入新值
-    RTC->CNTH = count >> 16;              // 高 16 位
-    RTC->CNTL = count & 0xFFFF;           // 低 16 位
+    // 2. 进入配置模式
+    RTC->CRL |= RTC_CRL_CNF;
+
+    // 3. 设置寄存器
+    // 3.1 设置计数器 CNT = 0
+    RTC->CNTH = 0;
+    RTC->CNTL = 0;
+
+    // 3.2 设置 ALR = s - 1
+    s -= 1;
+    RTC->ALRH = (s >> 16) & 0xffff;
+    RTC->ALRL = (s >> 0) & 0xffff;
 
     // 4. 退出配置模式
-    RTC->CRL &= ~RTC_CRL_CNF;             // CNF=0
+    RTC->CRL &= ~RTC_CRL_CNF;
 
-    // 5. 等待写入完成
-    while ((RTC->CRL & RTC_CRL_RTOFF) == 0) {}  // RTOFF=1 才完成
+    // 5. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {
+    }
 }
 
-uint32_t RTC_GetCounter(void)
+// 设置当前时间(UNIX时间戳)
+void RTC_SetTimestamp(uint32_t ts)
 {
-    // 读取 RTC 不必进配置模式,但需要等待同步
-    // 等待 RSF 标志
-    RTC->CRL |= RTC_CRL_RSF;
-    while ((RTC->CRL & RTC_CRL_RSF) == 0) {}
+    // 1. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {
+    }
 
-    // 读取计数器
-    uint16_t high = RTC->CNTH;
-    uint16_t low  = RTC->CNTL;
+    // 2. 进入配置模式
+    RTC->CRL |= RTC_CRL_CNF;
 
-    // 如果高 16 位在读低 16 位时改变了(进位),重新读
-    if (high != RTC->CNTH)
+    // 3. 设置CNT寄存器
+    RTC->CNTH = (ts >> 16) & 0xffff;
+    RTC->CNTL = (ts >> 0) & 0xffff;
+
+    // 4. 退出配置模式
+    RTC->CRL &= ~RTC_CRL_CNF;
+
+    // 5. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
     {
-        high = RTC->CNTH;
-        low  = RTC->CNTL;
     }
+}
 
-    return ((uint32_t)high << 16) | low;
+// 获取日期时间(年月日时分秒)
+void RTC_GetDateTime(DateTime *dateTime)
+{
+    // 1. 等待寄存器同步
+    while ( !(RTC->CRL & RTC_CRL_RSF) )
+    {}
+
+    // 2. 获取当前秒数并组合
+    uint32_t second = RTC->CNTH << 16 | RTC->CNTL;
+
+    // 3. 将时间戳转换为tm结构体变量
+    struct tm* ptm = localtime(&second);
+
+    // 4. 将tm数据赋给自定义的结构体变量
+    dateTime->year = ptm->tm_year + 1900;
+    dateTime->month = ptm->tm_mon + 1;
+    dateTime->day = ptm->tm_mday;
+    dateTime->hour = ptm->tm_hour;
+    dateTime->minute = ptm->tm_min;
+    dateTime->second = ptm->tm_sec;
 }
 ```
 
-### Unix 时间戳转换
+---
+
+## 实验:BKP 数据存储 + RTC 日历
+
+### BKP 实验
+
+**项目路径**:`stm32/21_bkp_test_register`
 
-RTC 计数器存储的是从指定基准时间(如 1970-01-01)开始的**秒数**(Unix 时间戳)。
+**文件:`stm32/21_bkp_test_register/User/main.c`**(向 BKP 寄存器写入数据)
 
 ```c
-// Unix 时间戳 → 年月日时分秒(标准库 time.h 处理)
-#include <time.h>
+#include "usart.h"
+#include "delay.h"
+#include "key.h"
+#include "bkp.h"
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	KEY_Init();
+	BKP_Init();
 
-time_t raw_time = RTC_GetCounter();     // 读取 RTC 秒数
-struct tm *info = localtime(&raw_time);  // 转换为日期时间
+	printf("尚硅谷备份寄存器实验...\n");
 
-printf("%04d-%02d-%02d %02d:%02d:%02d\r\n",
-    info->tm_year + 1900,                // 年份
-    info->tm_mon + 1,                    // 月份 (0~11)
-    info->tm_mday,                       // 日
-    info->tm_hour,                       // 时
-    info->tm_min,                        // 分
-    info->tm_sec);                       // 秒
-
-// 反向:年月日→Unix 时间戳
-// 使用 mktime() 函数
+	// 向备份数据寄存器写入数据值
+	// BKP->DR1 = 9999;
+
+	while (1)
+	{
+	}
+}
 ```
 
----
+### RTC 日历实验
 
-## 实验:BKP 数据存储 + RTC 日历
+**项目路径**:`stm32/25_rtc_calendar_register`
 
-**项目路径**:`stm32/21_bkp_test_register` ~ `stm32/26_rtc_calendar_hal`
+**文件:`stm32/25_rtc_calendar_register/User/main.c`**
 
 ```c
-// RTC + BKP 初始化
-void RTC_BKP_Init(void)
+#include "usart.h"
+#include "delay.h"
+#include "rtc.h"
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	RTC_Init();
+
+	printf("尚硅谷RTC实验:RTC实时时钟...\n");
+
+	// 设定一次当前的时间戳
+	// RTC_SetTimestamp(1736160789);
+
+	DateTime dateTime;
+
+	while (1)
+	{
+		// 每隔1s获取当前时间并打印一次
+		RTC_GetDateTime(&dateTime);
+
+		printf("%04d年%02d月%02d日 %02d:%02d:%02d\n",
+			dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second);
+
+		Delay_ms(1000);
+	}
+}
+```
+
+### RTC 闹钟唤醒待机实验
+
+**项目路径**:`stm32/23_rtc_alarm_standby_register`
+
+**文件:`stm32/23_rtc_alarm_standby_register/Hardware/RTC/rtc.c`**(闹钟功能)
+
+```c
+#include "rtc.h"
+
+// 初始化
+void RTC_Init(void)
 {
-    // 1. 开启 PWR 和 BKP 时钟
+    // 1. 开启PWR时钟
     RCC->APB1ENR |= RCC_APB1ENR_PWREN;
-    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
-    PWR->CR |= PWR_CR_DBP;                 // 解锁备份域
 
-    // 2. 如果 LSE 未启动,初始化 RTC
-    if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0)
-    {
-        // 复位备份域
-        RCC->BDCR |= RCC_BDCR_BDRST;
-        RCC->BDCR &= ~RCC_BDCR_BDRST;
+    // 1.2 解锁备份域写保护
+    PWR->CR |= PWR_CR_DBP;
 
-        // 配置 LSE 并等待就绪
-        RCC->BDCR |= RCC_BDCR_LSEON;
-        while (!(RCC->BDCR & RCC_BDCR_LSERDY));
+    // 1.3 复位备份域寄存器
+    RCC->BDCR |= RCC_BDCR_BDRST;
 
-        // 选择 LSE 为 RTC 时钟源
-        RCC->BDCR |= RCC_BDCR_RTCSEL_0;    // RTCSEL=01: LSE
+    // 1.4 结束备份域复位
+    RCC->BDCR &= ~RCC_BDCR_BDRST;
 
-        // 配置 RTC 预分频器
-        RTC->CRL |= RTC_CRL_CNF;            // 进入配置
-        RTC->PRLL = 32768 - 1;              // 32.768KHz → 1Hz
-        RTC->CRL &= ~RTC_CRL_CNF;           // 退出配置
-        while (!(RTC->CRL & RTC_CRL_RTOFF));
+    // 2. 开启RTC时钟源以及使能RTC
+    // 2.1 使能RTC时钟
+    RCC->BDCR |= RCC_BDCR_RTCEN;
 
-        // 使能 RTC
-        RCC->BDCR |= RCC_BDCR_RTCEN;
+    // 2.2 使能LSE并等待其就绪
+    RCC->BDCR |= RCC_BDCR_LSEON;
+    while (!(RCC->BDCR & RCC_BDCR_LSERDY))
+    {}
 
-        // 设置初始时间(示例:2026-01-01 00:00:00)
-        RTC_SetCounter(1767225600);          // Unix 时间戳
-    }
+    // 2.3 选择LSE作为RTC的时钟源
+    RCC->BDCR &= ~RCC_BDCR_RTCSEL;
+    RCC->BDCR |= RCC_BDCR_RTCSEL_0;
+
+    // 3. RTC寄存器配置操作
+    // 3.1 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {}
+
+    // 3.2 进入配置模式
+    RTC->CRL |= RTC_CRL_CNF;
+
+    // 3.3 设置预分频系数 32767,得到1秒计数
+    RTC->PRLH = 0;
+    RTC->PRLL = 0x7fff;
+
+    // 3.4 退出配置模式
+    RTC->CRL &= ~RTC_CRL_CNF;
+
+    // 3.5 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {}
+}
+
+// 设置闹钟(s秒之后报警)
+void RTC_SetAlarm(uint32_t s)
+{
+    // 0. 清除闹钟标志
+    RTC->CRL &= ~RTC_CRL_ALRF;
+
+    // 1. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {}
+
+    // 2. 进入配置模式
+    RTC->CRL |= RTC_CRL_CNF;
+
+    // 3. 设置寄存器
+    // 3.1 设置计数器 CNT = 0
+    RTC->CNTH = 0;
+    RTC->CNTL = 0;
+
+    // 3.2 设置 ALR = s - 1
+    s -= 1;
+    RTC->ALRH = (s >> 16) & 0xffff;
+    RTC->ALRL = (s >> 0) & 0xffff;
+
+    // 4. 退出配置模式
+    RTC->CRL &= ~RTC_CRL_CNF;
+
+    // 5. 查询RTOFF位直到为1
+    while (!(RTC->CRL & RTC_CRL_RTOFF))
+    {}
+}
+```
+
+**文件:`stm32/23_rtc_alarm_standby_register/User/main.c`**(闹钟唤醒待机)
+
+```c
+#include "usart.h"
+#include "delay.h"
+#include "led.h"
+#include "rtc.h"
+
+void enter_standby_mode(void);
+
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	LED_Init();
+	RTC_Init();
+
+	// 判断标志位来判断是否从待机唤醒
+	if (PWR->CSR & PWR_CSR_SBF)
+	{
+		printf("从待机模式唤醒:\n");
+		PWR->CR |= PWR_CR_CSBF;
+	}
+	if (PWR->CSR & PWR_CSR_WUF)
+	{
+		printf("产生了唤醒事件:\n");
+		PWR->CR |= PWR_CR_CWUF;
+	}
+
+	printf("尚硅谷RTC实验:闹钟唤醒待机模式...\n");
+
+	// 1. 点亮LED灯,延时2s,模拟正常运行程序
+	LED_On(LED_2);
+	Delay_s(2);
+
+	while (1)
+	{
+		// 2. 进入待机模式
+		printf("正常运行执行完毕,3s后进入待机模式...\n");
+		Delay_s(3);
+		printf("进入待机模式,5s后闹钟唤醒...\n");
+		Delay_ms(1);	// 稍作延时确保串口信息成功发送
+
+		// 3. 在进入待机模式前设置闹钟
+		RTC_SetAlarm(5);
+		enter_standby_mode();
+
+		// 4. 以下代码不会执行
+		printf("从待机模式唤醒...\n");
+		Delay_s(2);
+	}
+}
+
+// 进入待机模式的函数
+void enter_standby_mode(void)
+{
+	// 1. 设置深度睡眠模式
+	SCB->SCR |= SCB_SCR_SLEEPDEEP;
+
+	// 2. 设置PDDS = 1,表示待机模式
+	PWR->CR |= PWR_CR_PDDS;
+
+	// 3. 使用WFI指令,进入待机模式
+	__WFI();
 }
 ```
 
@@ -304,8 +621,8 @@ HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x55AA);
 | 解锁备份域 | `PWR->CR |= PWR_CR_DBP` | CubeMX 自动处理 |
 | 写 BKP 寄存器 | `BKP->DR[i] = data` | `HAL_RTCEx_BKUPWrite()` |
 | 读 BKP 寄存器 | `data = BKP->DR[i]` | `HAL_RTCEx_BKUPRead()` |
-| 设置时间 | `RTC_SetCounter(秒数)` | `HAL_RTC_SetTime()` + `SetDate()` |
-| 读取时间 | `RTC_GetCounter()` → Unix 转换 | `HAL_RTC_GetTime()` + `GetDate()` |
+| 设置时间 | `RTC_SetTimestamp(UNIX时间戳)` | `HAL_RTC_SetTime()` + `SetDate()` |
+| 读取时间 | `RTC_GetDateTime(&dt)` → DateTime 结构体 | `HAL_RTC_GetTime()` + `GetDate()` |
 | 配置 LSE | `RCC->BDCR` 逐位操作 | CubeMX 自动配置 |
 | 写注意事项 | 需 CNF 模式 + 等待 RTOFF | HAL 库自动处理 |
 

+ 145 - 30
X-Knowledge-Base/raw/Joplin/嵌入式+Linux/STM32学习笔记/20-看门狗IWDG与WWDG.md

@@ -66,46 +66,95 @@ Tout = 1001 × 32 × 4 / 40000 = 3.2s
 
 ### IWDG 配置示例
 
+**文件:`stm32/27_iwdg_test_register/Hardware/IWDG/iwdg.h`**
+
+```c
+#ifndef __IWDG_H
+#define __IWDG_H
+
+#include "stm32f10x.h"
+
+// 初始化
+void IWDG_Init(void);
+
+// 喂狗:刷新计数器值
+void IWDG_Refresh(void);
+
+#endif
+```
+
+**文件:`stm32/27_iwdg_test_register/Hardware/IWDG/iwdg.c`**(实际代码:先 KR=0xCCCC 启动,再 0x5555 配置)
+
 ```c
+#include "iwdg.h"
+
+// 初始化
 void IWDG_Init(void)
 {
-    // 1. 写 0x5555 到 KR,允许修改 PR 和 RLR
+    // 1. 启动看门狗
+    IWDG->KR = 0xCCCC;
+
+    // 2. 解除寄存器保护
     IWDG->KR = 0x5555;
 
-    // 2. 设置预分频系数 = 32 (PR=011)
-    IWDG->PR = 3;        // PR[2:0]=011
+    // 3. 设置预分频系数 64 - PR = 100
+    IWDG->PR = 4;
 
-    // 3. 设置重装值 = 1000 → Tout ≈ 3.2s (40KHz)
-    IWDG->RLR = 1000;
+    // 4. 设置重装载值:2499
+    IWDG->RLR = 2499;
 
-    // 4. 写 0xCCCC 启动 IWDG
-    IWDG->KR = 0xCCCC;
-
-    // 5. 喂狗(必须在 3.2s 内执行一次,否则复位)
-    // 通常在 main 循环中调用
+    // 5. 重新加载计数值:喂一次狗
+    IWDG_Refresh();
 }
 
-void IWDG_Feed(void)
+// 喂狗:刷新计数器值
+void IWDG_Refresh(void)
 {
-    IWDG->KR = 0xAAAA;   // 写 0xAAAA 重装计数器
+    IWDG->KR = 0xAAAA;
 }
+```
 
-int main(void)
-{
-    IWDG_Init();          // 启动看门狗
+**文件:`stm32/27_iwdg_test_register/User/main.c`**
 
-    while (1)
-    {
-        // 主循环逻辑...
+```c
+#include "usart.h"
+#include "delay.h"
+#include "key.h"
+#include "iwdg.h"
 
-        IWDG_Feed();      // 每 3s 内喂一次狗(Tout=3.2s)
+// 全局变量:按键标志位
+uint8_t flag;
 
-        // 如果程序卡死在此循环后面,3.2s 后系统复位
-    }
+int main(void)
+{
+	// 初始化
+	USART_Init();
+	KEY_Init();
+	IWDG_Init();
+
+	printf("尚硅谷独立看门狗实验...\n");
+
+	while (1)
+	{
+		// 1. 模拟正常运行程序
+		printf("正常运行程序...\n");
+		Delay_ms(3000);
+
+		// 按下按键中断,在中断服务中,增加程序的执行时间
+		if (flag)
+		{
+			Delay_ms(3000);
+			flag = 0;
+		}
+
+		// 2. 正常程序运行结束后喂狗
+		IWDG_Refresh();
+		printf("程序运行结束,喂狗成功!\n");
+	}
 }
 ```
 
-> 注意:IWDG 一旦启动**无法停止**,只能通过系统复位关闭。
+> **注意**实际代码先写 `KR=0xCCCC` **启动**看门狗,再写 `KR=0x5555` 解除保护配置 PR/RLR,最后 `IWDG_Refresh()` 喂狗。IWDG 一旦启动**无法停止**,只能通过系统复位关闭。
 
 ---
 
@@ -204,17 +253,83 @@ void WWDG_IRQHandler(void)
 
 ### HAL IWDG
 
+**文件:`stm32/28_iwdg_test_hal/Core/Src/main.c`**(HAL 版看门狗,实际项目代码)
+
 ```c
-// HAL 库使用 IWDG_HandleTypeDef
-IWDG_HandleTypeDef hiwdg;
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "iwdg.h"
+#include "usart.h"
+#include "gpio.h"
+
+uint8_t flag;
+
+int main(void)
+{
+  HAL_Init();
+  SystemClock_Config();
+
+  MX_GPIO_Init();
+  MX_IWDG_Init();
+  MX_USART1_UART_Init();
 
-hiwdg.Instance = IWDG;
-hiwdg.Init.Prescaler = IWDG_PRESCALER_32;    // PR=011: 分频32
-hiwdg.Init.Reload = 1000;                     // RLR=1000
-HAL_IWDG_Init(&hiwdg);                        // 相当于 KR=0xCCCC 启动
+  printf("尚硅谷独立看门狗实验...\n");
 
-// HAL 库喂狗
-HAL_IWDG_Refresh(&hiwdg);                     // 相当于 KR=0xAAAA
+  while (1)
+  {
+    // 1. 模拟正常运行程序
+    printf("正常运行程序...\n");
+    HAL_Delay(3000);
+
+    // 按下按键中断,在中断服务中,增加程序的执行时间
+    if (flag)
+    {
+      HAL_Delay(3000);
+      flag = 0;
+    }
+
+    // 2. 正常程序运行结束后喂狗
+    HAL_IWDG_Refresh(&hiwdg);
+    printf("程序运行结束,喂狗成功!\n");
+  }
+}
+
+void SystemClock_Config(void)
+{
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
+  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
+  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  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;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
+  {
+    Error_Handler();
+  }
+}
+
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+{
+  flag = 1;
+  printf("按键按下...\n");
+}
 ```
 
 ### HAL WWDG