Эх сурвалжийг харах

ingest: 尚硅谷STM32 进阶篇笔记导入(07~12)

OpenCode 2 өдөр өмнө
parent
commit
dacf24170f

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

@@ -0,0 +1,277 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(基础篇)V1.0.2 — 第9章:I2C通信 + 配套代码13~15"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# I2C通信与EEPROM 24C02
+
+> **用生活理解**:I2C 就像教室里的传纸条规则——SCL 是老师拍手的节奏(时钟),SDA 是传的纸条(数据)。每次老师拍一下手(时钟跳变),纸条就传递一位信息。每个同学有唯一的学号(设备地址),只有学号对得上的人才能接收纸条。
+
+---
+
+## I2C协议基础
+
+I2C(Inter-Integrated Circuit)是一种同步、半双工、多主从的串行通信总线,仅需 **2 根线**:SCL(时钟线)和 SDA(数据线)。
+
+| 特性 | 说明 |
+|------|------|
+| 线数 | SCL + SDA 两根 |
+| 模式 | 半双工 |
+| 速率 | 标准 100Kbps,快速 400Kbps |
+| 主从 | 多主多从(通过仲裁解决冲突) |
+| 地址 | 7位地址(最多 127 个设备)或 10位地址 |
+| 上拉 | 两根线都需要外部上拉电阻(4.7KΩ 典型) |
+
+### I2C时序协议详解
+
+I2C 协议由几个基本操作组成:
+
+#### 起始条件(Start Condition)
+```
+SCL 在高电平时,SDA 从高电平切换到低电平
+```
+```
+SCL  ────┐  ┌────
+          │  │
+SDA  ────┘──┘────
+```
+
+#### 终止条件(Stop Condition)
+```
+SCL 在高电平时,SDA 从低电平切换到高电平
+```
+```
+SCL  ────┐  ┌────
+          │  │
+SDA  ────┘──┘────
+```
+
+#### 数据字节传输
+每个字节 8 位,MSB 先发。
+SCL 低电平时 SDA 改变数据,SCL 高电平时 SDA 保持稳定。
+
+#### 应答(ACK/NACK)
+每个字节后,接收方在第 9 个 SCL 周期拉低 SDA 表示应答(ACK),释放 SDA 表示非应答(NACK)。
+
+### I2C完整通信流程
+
+**主机写数据到从机**:
+```
+START + 从机地址(W) + ACK + 数据1 + ACK + 数据2 + ACK + ... + STOP
+```
+
+**主机从从机读数据**:
+```
+START + 从机地址(R) + ACK + 数据1 + ACK + 数据2 + NACK + STOP
+```
+
+---
+
+## I2C硬件电路设计
+
+I2C 总线需要外部上拉电阻(RPU):
+```
+VCC(3.3V)
+  ├── RPU(4.7KΩ)
+  ├──── SCL
+  ├── RPU(4.7KΩ)  
+  └──── SDA
+```
+
+软件模拟 I2C 使用 GPIO 的**开漏输出**模式(CNF=01, MODE=11):
+- 开漏输出允许"线与"(多个设备可同时拉低总线)
+- 高电平靠外部上拉电阻实现
+
+> **参考**:原理图 I2C 接口部分(24C02 电路),参考手册 §16
+
+---
+
+## 24C02 EEPROM
+
+| 参数 | 值 |
+|------|-----|
+| 容量 | 256 字节(2Kbit) |
+| 页大小 | 8 字节(页写最多 8 字节) |
+| 地址 | 0xA0(写)/ 0xA1(读) |
+| 接口 | I2C |
+| 工作电压 | 1.8V~5.5V |
+
+**页写限制**:24C02 每页 8 字节,跨页写时需要手动分页(超过页边界会自动卷回页开头)。
+
+**写周期**:每个写操作后需要等待 5ms(内部擦写时间),在此期间不响应任何 I2C 命令。
+
+---
+
+## 实验:软件模拟 I2C 读写 24C02
+
+### 软件设计(寄存器版 — 软件模拟 I2C)
+
+**项目路径**:`stm32_base/13_i2c_software_register`
+
+**文件:`stm32_base/13_i2c_software_register/Hardware/I2C/i2c.h`**
+
+```c
+#ifndef __I2C_H
+#define __I2C_H
+
+#include "stm32f10x.h"
+
+// 引脚宏定义:PB10=SCL, PB11=SDA
+#define SCL_HIGH  (GPIOB->ODR |= GPIO_ODR_ODR10)   // |= SCL置1
+#define SCL_LOW   (GPIOB->ODR &= ~GPIO_ODR_ODR10)  // &=~ SCL清0
+#define SDA_HIGH  (GPIOB->ODR |= GPIO_ODR_ODR11)   // |= SDA置1
+#define SDA_LOW   (GPIOB->ODR &= ~GPIO_ODR_ODR11)  // &=~ SDA清0
+
+#define READ_SDA  (GPIOB->IDR & GPIO_IDR_IDR11)    // & 读取SDA电平
+
+#define ACK  0
+#define NACK 1
+
+#define I2C_DELAY  Delay_us(10)  // 100KHz:半周期延时10us
+
+void I2C_Init(void);
+void I2C_Start(void);
+void I2C_Stop(void);
+void I2C_Ack(void);
+void I2C_NAck(void);
+uint8_t I2C_Wait4Ack(void);
+void I2C_SendByte(uint8_t byte);
+uint8_t I2C_ReadByte(void);
+
+#endif
+```
+
+**文件:`stm32_base/13_i2c_software_register/Hardware/I2C/i2c.c`**(核心函数)
+
+```c
+#include "i2c.h"
+
+void I2C_Init(void)
+{
+    // 开启GPIOB时钟
+    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
+    // PB10(PB11) 开漏输出:MODE=11(50MHz), CNF=01(开漏)
+    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);   // MODE位置1
+    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1); // CNF[1]清0
+    GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);  // CNF[0]置1
+}
+
+// 起始:SCL高电平时,SDA高→低
+void I2C_Start(void)
+{
+    SCL_HIGH;  // 先确保SCL高
+    SDA_HIGH;  // SDA先高
+    I2C_DELAY;
+    SDA_LOW;   // SDA从高变低 → 起始条件
+    I2C_DELAY;
+}
+
+// 停止:SCL高电平时,SDA低→高
+void I2C_Stop(void)
+{
+    SCL_HIGH;  // SCL拉高
+    SDA_LOW;   // SDA先低
+    I2C_DELAY;
+    SDA_HIGH;  // SDA从低变高 → 停止条件
+    I2C_DELAY;
+}
+
+// 发送ACK:在第9个时钟将SDA拉低
+void I2C_Ack(void)
+{
+    SDA_LOW;   // SDA拉低表示应答
+    SCL_HIGH;  // SCL产生脉冲
+    I2C_DELAY;
+    SCL_LOW;
+    I2C_DELAY;
+    SDA_HIGH;  // 释放SDA
+}
+
+// 发送NACK:在第9个时钟释放SDA
+void I2C_NAck(void)
+{
+    SDA_HIGH;  // SDA高表示非应答
+    SCL_HIGH;  // SCL产生脉冲
+    I2C_DELAY;
+    SCL_LOW;
+}
+
+// 等待从机ACK:返回0=有ACK, 1=无ACK
+uint8_t I2C_Wait4Ack(void)
+{
+    SDA_HIGH;  // 释放SDA(让从机控制)
+    SCL_HIGH;  // SCL高电平采样
+    I2C_DELAY;
+    uint8_t ack = READ_SDA;  // &测试: 读取SDA电平
+    SCL_LOW;
+    I2C_DELAY;
+    return ack ? NACK : ACK;  // SDA=0: 从机应答; SDA=1: 无应答
+}
+```
+
+**文件:`stm32_base/13_i2c_software_register/User/main.c`**
+
+```c
+#include "i2c.h"
+
+int main(void)
+{
+    usart1_init();         // 串口初始化(用于打印调试)
+    I2C_Init();            // I2C GPIO初始化
+
+    // 测试:向24C02地址0x00写入0x55
+    I2C_Start();
+    I2C_SendByte(0xA0);    // 从机地址+写 (0xA0)
+    I2C_Wait4Ack();        // 等待24C02应答
+    I2C_SendByte(0x00);    // 写入地址(24C02的字节地址)
+    I2C_Wait4Ack();
+    I2C_SendByte(0x55);    // 写入数据
+    I2C_Wait4Ack();
+    I2C_Stop();
+
+    Delay_ms(10);          // 等待内部写周期完成
+
+    // 读回验证:从地址0x00读取
+    I2C_Start();
+    I2C_SendByte(0xA0);
+    I2C_Wait4Ack();
+    I2C_SendByte(0x00);    // 再次发出地址
+    I2C_Wait4Ack();
+    I2C_Start();           // 重启(组合格式)
+    I2C_SendByte(0xA1);    // 从机地址+读 (0xA1)
+    I2C_Wait4Ack();
+    uint8_t data = I2C_ReadByte();  // 读取数据
+    I2C_NAck();            // 主机发NACK表示读取结束
+    I2C_Stop();
+
+    printf("Read data: 0x%02X\r\n", data);  // 应输出0x55
+    while (1);
+}
+```
+
+---
+
+## 核心函数速查表
+
+| 函数 | 说明 |
+|------|------|
+| `I2C_Init()` | PB10(SCL)+PB11(SDA) 开漏输出初始化 |
+| `I2C_Start()` | SCL高→SDA高→低切换,产生起始条件 |
+| `I2C_Stop()` | SCL高→SDA低→高切换,产生停止条件 |
+| `I2C_SendByte(byte)` | 逐位发送8位数据(MSB first) |
+| `I2C_ReadByte()` | 逐位读取8位数据 |
+| `I2C_Wait4Ack()` | 等待从机ACK(返回0=应答) |
+| `I2C_Ack()` | 主机发送ACK |
+| `I2C_NAck()` | 主机发送NACK(读最后一个字节后) |
+
+## 常见问题与避坑
+
+1. **I2C 总线卡死(SDA 一直为低)** → 从机异常,发 9 个以上的 SCL 脉冲让从机释放 SDA
+2. **24C02 写入后读回不对** → 页写溢出(跨页写会自动卷回),需分页写入
+3. **NACK 持续** → 设备地址错误、上拉电阻缺失、SCL 频率太高
+4. **Start 条件后无 ACK** → 检查从机电源、I2C 地址(7位+读写位 = 8位)
+5. **软件 I2C 有时钟干扰** → I2C_DELAY 时间不够,增加至 20~50us

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

@@ -0,0 +1,193 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — 第1章:定时器 + 配套代码16~19"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# SysTick与通用定时器TIM
+
+> **用生活理解**:SysTick 就像秒表倒计时——设定一个时间(LOAD值),它倒数到 0 就提醒你(中断)。TIM通用定时器就像高级秒表,可以预分频(把太快的时间变成慢的)、自动重装(倒计时到0自动重新开始),适合精确控制 LED 闪烁频率。
+
+---
+
+## SysTick 系统定时器
+
+SysTick 是 Cortex-M3 内核自带的**24 位递减计数器**,属于内核外设(不依赖具体芯片厂商)。
+
+### SysTick 寄存器
+
+| 寄存器 | 地址偏移 | 功能 |
+|--------|---------|------|
+| CTRL | 0x00 | 控制与状态寄存器 |
+| LOAD | 0x04 | 重装载数值寄存器 |
+| VAL | 0x08 | 当前数值寄存器 |
+| CALIB | 0x0C | 校准数值寄存器(很少用) |
+
+**CTRL 寄存器位说明**:
+| 位 | 名称 | 说明 |
+|----|------|------|
+| 0 | ENABLE | 1=开启定时器,0=关闭 |
+| 1 | TICKINT | 1=计数到0时产生SysTick异常 |
+| 2 | CLKSOURCE | 1=AHB时钟(72MHz),0=AHB/8(9MHz) |
+| 16 | COUNTFLAG | 计数到0时硬件置1,读后自动清0 |
+
+**LOAD 寄存器**:24 位有效(最大值 16,777,215)。
+`VAL 从 LOAD 值开始递减 → 到 0 时产生 COUNTFLAG → 自动重新装载 LOAD 值`
+
+### SysTick 延时实现(查询方式)
+
+```c
+// 微秒延时(AHB=72MHz)
+void Delay_us(uint32_t us)
+{
+    SysTick->LOAD = 72 * us;        // 72MHz下,1us需72次计数
+    SysTick->VAL  = 0;              // 清当前值
+    SysTick->CTRL = 0x05;           // ENABLE=1, CLKSOURCE=1(AHB)
+
+    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG))  // 等待计数到0
+    {
+    }
+    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;  // 关闭定时器
+}
+```
+
+> **注意**:LOAD 最大 24 位(16,777,215),最大单次延时 = 16,777,215 / 72 ≈ 233ms
+
+---
+
+## 通用定时器 TIM
+
+STM32F103ZET6 有多个定时器:
+
+| 定时器 | 类型 | 位数 | 主要特性 |
+|--------|------|------|---------|
+| TIM1 | 高级 | 16 | 带互补输出、死区插入、刹车功能 |
+| TIM2~5 | 通用 | 16 | 时基、PWM、捕获/比较 |
+| TIM6~7 | 基本 | 16 | 仅时基(无PWM/捕获) |
+
+### TIM 时基单元结构
+
+```
+预分频器(PSC) → 计数器(CNT) → 自动重装载(ARR) → 更新事件(UEV)
+```
+
+**计数器时钟频率**:`CK_CNT = CK_PSC / (PSC + 1)`
+
+**溢出时间**:`Tout = (ARR + 1) × (PSC + 1) / CK_PSC`
+
+**示例**:CK_PSC=72MHz, PSC=7199, ARR=9999
+`CK_CNT = 72MHz / 7200 = 10KHz`
+`Tout = 10000 × 10KHz = 1s`(1秒溢出一次)
+
+### TIM 关键寄存器
+
+| 寄存器 | 功能 |
+|--------|------|
+| TIMx_PSC | 预分频器(16位,0~65535) |
+| TIMx_CNT | 当前计数值 |
+| TIMx_ARR | 自动重装载值 |
+| TIMx_CR1 | 控制寄存器1(CEN使能、DIR方向、ARPE自动重装载预装载) |
+| TIMx_DIER | 中断使能寄存器(UIE更新中断、CCxIE捕获比较中断) |
+| TIMx_SR | 状态寄存器(UIF更新标志、CCxIF捕获比较标志) |
+
+> **参考**:参考手册 §14(通用定时器寄存器描述)
+
+---
+
+## 实验:SysTick/TIM 定时控制 LED 闪烁
+
+### 软件设计(SysTick 中断版)
+
+**文件:`stm32_base/16_systick_led_twinkle_register/Hardware/SysTick/systick.c`**
+
+```c
+#include "systick.h"
+
+// SysTick 初始化:配置1ms中断一次
+void Systick_Init(void)
+{
+    // 72MHz / 72000 = 1KHz = 1ms中断一次
+    // LOAD = 72000 - 1 = 71999
+    SysTick->LOAD = 72000 - 1;
+    SysTick->VAL  = 0;
+    // CTRL: ENABLE=1, TICKINT=1(中断使能), CLKSOURCE=1(AHB)
+    SysTick->CTRL |= (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_CLKSOURCE_Msk);
+}
+
+// SysTick 中断服务函数(在启动文件中注册)
+void SysTick_Handler(void)
+{
+    static uint16_t tick = 0;
+    tick++;
+    if (tick >= 1000)  // 1000ms = 1s
+    {
+        LED_Toggle(LED_1);  // 翻转LED
+        tick = 0;
+    }
+}
+```
+
+### 软件设计(TIM 中断版)
+
+**文件:`stm32_base/18_tim_led_twinkle_register/Hardware/TIM5/tim5.c`**
+
+```c
+#include "tim5.h"
+
+void TIM5_Init(void)
+{
+    // 1. 开启TIM5时钟(APB1总线)
+    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+
+    // 2. 配置预分频器:72MHz / (7199+1) = 10KHz
+    TIM5->PSC = 7200 - 1;
+
+    // 3. 配置自动重装载值:10KHz / (10000+1) ≈ 1秒溢出一次
+    TIM5->ARR = 10000 - 1;
+
+    // 4. 清计数器
+    TIM5->CNT = 0;
+
+    // 5. 使能更新中断
+    TIM5->DIER |= TIM_DIER_UIE;
+
+    // 6. 使能TIM5中断(NVIC)
+    NVIC_EnableIRQ(TIM5_IRQn);
+
+    // 7. 使能计数器(CEN=1)
+    TIM5->CR1 |= TIM_CR1_CEN;
+}
+
+// TIM5 中断服务函数
+void TIM5_IRQHandler(void)
+{
+    if (TIM5->SR & TIM_SR_UIF)  // 检查更新中断标志
+    {
+        LED_Toggle(LED_1);      // 翻转LED
+        TIM5->SR &= ~TIM_SR_UIF;  // 清标志(写0清除)
+    }
+}
+```
+
+---
+
+## 核心速查表
+
+| 操作 | SysTick | TIM通用定时器 |
+|------|---------|--------------|
+| 初始化 | `SysTick->CTRL = 0x07` | `TIMx->CR1 |= TIM_CR1_CEN` |
+| 预分频 | 不可配(固定AHB或AHB/8) | `TIMx->PSC = value` |
+| 重装载 | `SysTick->LOAD = value` | `TIMx->ARR = value` |
+| 中断使能 | CTRL.TICKINT=1 | DIER.UIE=1 + NVIC |
+| 清标志 | 读CTRL自动清 | `SR &= ~UIF` |
+| 停止 | `CTRL &= ~ENABLE` | `CR1 &= ~CEN` |
+
+## 常见问题与避坑
+
+1. **SysTick 中断不触发** → 检查 CTRL.TICKINT 是否置 1、NVIC 优先级是否设置
+2. **TIM 溢出时间不对** → 检查 PSC/ARR 计算、确认时钟源频率(APB1=36MHz 还是 72MHz?)
+3. **AHB/APB1 时钟差异** → TIM2~5 挂载 APB1,若 APB1 分频≠1 则定时器时钟 = APB1×2
+4. **更新标志不清导致只中断一次** → ISR 中必须手动清除 SR.UIF

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

@@ -0,0 +1,165 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — 第1章:PWM + 配套代码20~27"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# TIM高级应用:PWM与输入捕获
+
+> **用生活理解**:PWM 就像快速开关灯——如果你每秒开关 1000 次,眼睛就感觉灯光变暗了。开的时间比例(占空比)越大就越亮。呼吸灯就是占空比从 0% 逐渐变到 100% 再回来,像人呼吸一样平滑。
+
+---
+
+## PWM 原理
+
+PWM = Pulse Width Modulation(脉冲宽度调制),通过调节脉冲的宽度(占空比)来控制平均电压。
+
+### 基本参数
+
+| 参数 | 公式 | 示例(50%占空比, 1KHz) |
+|------|------|------------------------|
+| 频率 | `Freq = CK_PSC / (PSC+1) / (ARR+1)` | 72MHz/72/1000 = 1KHz |
+| 占空比 | `Duty = CCR / (ARR+1) × 100%` | 500/1000 = 50% |
+| 分辨率 | `Resolution = 1 / (ARR+1) × 100%` | 1/1000 = 0.1% |
+
+- **PSC**(预分频器):决定计数时钟频率
+- **ARR**(自动重装载值):决定 PWM 周期
+- **CCR**(捕获/比较值):决定 PWM 占空比
+
+### 输出比较模式
+
+| 模式 | 说明 | 用途 |
+|------|------|------|
+| PWM 模式 1 | 向上计数时 CNT<CCR 输出有效电平 | 常用 |
+| PWM 模式 2 | 向上计数时 CNT<CCR 输出无效电平 | 互补 |
+
+通常有效电平 = 高电平(由 CCERP 决定极性)。
+
+**PWM 波形生成过程**:
+```
+CNT 计数: 0__1__2__...__CCR__...__ARR__0__1__...
+输出(模1): 高          低             高
+              ← 占空比 → ← 剩余 →
+              ←—————— 周期(ARR+1) ——————→
+```
+
+---
+
+## 实验:呼吸灯
+
+### 软件设计(寄存器版)
+
+**文件:`stm32_base/20_tim_led_breathe_register/Hardware/TIM5/tim5.c`**
+
+```c
+#include "tim5.h"
+
+// TIM5 初始化:配置PWM输出到PA0(LED1)
+void TIM5_Init(void)
+{
+    // 1. 时钟
+    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;   // TIM5时钟
+
+    // 2. 配置PA0为复用功能推挽输出
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    GPIOA->CRL &= ~GPIO_CRL_CNF0;         // CNF=10(复用推挽)
+    GPIOA->CRL |= GPIO_CRL_CNF0_1;
+    GPIOA->CRL |= GPIO_CRL_MODE0;          // MODE=11(50MHz)
+    // TIM5_CH1 输出到 PA0(通过AFIO重映射)
+
+    // 3. 配置TIM5时基
+    TIM5->PSC = 72 - 1;                    // 72MHz/72 = 1MHz
+    TIM5->ARR = 1000 - 1;                  // 1MHz/1000 = 1KHz PWM
+
+    // 4. 配置输出比较模式
+    TIM5->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2;  // PWM模式1
+    TIM5->CCMR1 |= TIM_CCMR1_OC1PE;        // 预装载使能
+    TIM5->CCER |= TIM_CCER_CC1E;           // CH1输出使能
+
+    // 5. 初始占空比0
+    TIM5->CCR1 = 0;
+}
+
+void TIM5_Start(void)
+{
+    TIM5->CR1 |= TIM_CR1_CEN;              // 启动定时器
+}
+
+// 设置占空比(0~99)
+void TIM5_SetDutyCycle(uint8_t duty)
+{
+    TIM5->CCR1 = duty * 10;                // CCR1 = 0~990
+}
+```
+
+**文件:`stm32_base/20_tim_led_breathe_register/User/main.c`**
+
+```c
+#include "tim5.h"
+
+int main(void)
+{
+    TIM5_Init();
+    TIM5_Start();
+
+    uint8_t duty = 0;
+    uint8_t dir = 0;    // 0=增加, 1=减小
+    uint8_t step = 1;
+
+    while (1)
+    {
+        if (dir == 0) {
+            duty += step;
+            if (duty >= 99) dir = 1;   // 到最大转向减小
+        } else {
+            duty -= step;
+            if (duty <= 0)  dir = 0;   // 到最小转向增加
+        }
+        TIM5_SetDutyCycle(duty);        // 设置新占空比
+        Delay_ms(10);                   // 每10ms改变一次
+    }
+}
+```
+
+---
+
+## 实验:输入捕获测频率
+
+TIM 的输入捕获可以测量外部信号的脉宽或频率。
+
+**配置流程**:
+```
+GPIO输入 → TIM_CHx引脚 → 输入滤波器 → 边沿检测 → 捕获CCR
+```
+
+**测频率原理**:
+1. 配置 CHx 捕获上升沿
+2. 第一次捕获到上升沿 → 记录 CCR1 值
+3. 第二次捕获到上升沿 → 记录 CCR2 值
+4. 频率 = CK_CNT / (CCR2 - CCR1)
+
+> **参考**:参考手册 §14.3(输入捕获模式)
+
+---
+
+## 核心速查表
+
+| 功能 | 配置方法 |
+|------|---------|
+| PWM模式1 | `CCMR1.OC1M = 110`(PWM模式1) |
+| PWM使能输出 | `CCER.CC1E = 1` |
+| 预装载使能 | `CCMR1.OC1PE = 1` |
+| ARPE使能 | `CR1.ARPE = 1`(ARR影子寄存器) |
+| 极性反转 | `CCER.CC1P = 1`(低电平有效) |
+| 更新占空比 | 写 `CCR1` 值(0~ARR) |
+
+## 常见问题与避坑
+
+1. **PWM 无输出** → 检查 GPIO 模式(复用推挽)、TIM CH 输出是否使能(CCER.CCxE)
+2. **占空比 0 时仍有微弱电平** → 检查 CCR1=0 是否真为低电平(取决于极性配置)
+3. **PWM 频率不对** → APB1 定时器时钟 = APB1×2(如果 APB1 预分频≠1)
+4. **输入捕获值不准确** → 检查输入滤波器和边沿极性配置
+5. **CCR 更新不及时** → 检查 ARPE 影子寄存器是否使能(建议使能,防止中途改变ARR/CCR引起波形异常)

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

@@ -0,0 +1,130 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — DMA章节 + 配套代码28~31"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# DMA数据传输
+
+> **用生活理解**:DMA 就像公司里的快递员——以前你要自己去取文件(CPU 轮询/中断读外设),现在快递员帮你送了(DMA 控制器自动搬运),你就可以专心做其他重要工作。快递送到后喊你一声(完成中断)即可。
+
+---
+
+## DMA 概述
+
+DMA(Direct Memory Access)允许外设与存储器之间直接传输数据,**不需要 CPU 参与**。
+
+**传统方式(无 DMA)**:
+```
+CPU 读取外设数据寄存器 → CPU 写入内存 → CPU 继续下一字节
+```
+
+**DMA 方式**:
+```
+CPU 配置 DMA 通道后 → DMA 控制器自动搬运 → 搬运完成通知 CPU
+```
+
+### STM32F103 DMA 特性
+
+- 2 个 DMA 控制器:DMA1(7 通道)、DMA2(5 通道)
+- 支持:外设到内存、内存到外设、内存到内存
+- 存储器增量模式(地址自动递增)
+- 循环模式(自动重传)
+- 优先级可配(4 级:低/中/高/最高)
+
+### 通道映射
+
+| DMA1 通道 | 外设请求 |
+|-----------|---------|
+| CH1 | ADC1、TIM2_CH3、TIM4_CH1 |
+| CH2 | USART1_TX、SPI1_RX、TIM1_CH1 |
+| CH3 | USART1_RX、SPI1_TX、TIM1_CH2 |
+| CH4 | SPI2_RX、SPI1_RX、USART1_TX |
+| CH5 | SPI2_TX、USART2_TX、TIM1_CH4 |
+| CH6 | USART2_RX、ADC3、TIM1_CH1 |
+| CH7 | USART3_TX、ADC3、TIM1_CH2 |
+
+---
+
+## 实验:DMA 内存到内存传输
+
+### 软件设计(寄存器版)
+
+**文件:`stm32_base/28_dma_mem2mem_register/Hardware/DMA/dma.c`**
+
+```c
+#include "dma.h"
+
+#define SRC_ADDR  ((uint32_t)0x20000000)   // 源地址(SRAM)
+#define DST_ADDR  ((uint32_t)0x20000010)   // 目的地址(SRAM偏移)
+
+void DMA_Init(void)
+{
+    // 1. 开启DMA1时钟
+    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
+
+    // 2. 清除通道1的所有标志
+    DMA1->IFCR |= DMA_IFCR_CGIF1;           // |= 写1清除所有标志
+
+    // 3. 配置DMA通道1
+    DMA1_Channel1->CPAR  = SRC_ADDR;         // 外设地址 = 源地址
+    DMA1_Channel1->CMAR  = DST_ADDR;         // 存储器地址 = 目的地址
+    DMA1_Channel1->CNDTR = 10;               // 传输次数 = 10(10个32位字)
+
+    // 4. 配置控制寄存器CCR
+    DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;  // MEM2MEM=1: 内存到内存模式
+    DMA1_Channel1->CCR |= DMA_CCR1_PL;        // PL[1:0]=11: 最高优先级
+    DMA1_Channel1->CCR |= DMA_CCR1_MSIZE;     // MSIZE=10: 存储器数据宽度32位
+    DMA1_Channel1->CCR |= DMA_CCR1_PSIZE;     // PSIZE=10: 外设数据宽度32位
+    DMA1_Channel1->CCR |= DMA_CCR1_MINC;      // MINC=1: 存储器地址递增
+    DMA1_Channel1->CCR |= DMA_CCR1_PINC;      // PINC=1: 外设地址递增
+    DMA1_Channel1->CCR |= DMA_CCR1_CIRC;      // CIRC=1: 循环模式
+    DMA1_Channel1->CCR |= DMA_CCR1_TCIE;      // TCIE=1: 传输完成中断使能
+
+    // 5. 使能通道
+    DMA1_Channel1->CCR |= DMA_CCR1_EN;
+
+    // 6. NVIC使能DMA中断
+    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
+}
+
+// DMA传输完成中断
+void DMA1_Channel1_IRQHandler(void)
+{
+    if (DMA1->ISR & DMA_ISR_TCIF1)           // 检查传输完成标志
+    {
+        DMA1->IFCR |= DMA_IFCR_CTCIF1;       // 清除完成标志
+        // 传输完成:目的地址已包含源数据的副本
+    }
+}
+```
+
+---
+
+## 核心速查表
+
+| DMA寄存器 | 功能 |
+|-----------|------|
+| CCR.EN | 通道使能 |
+| CCR.PL[1:0] | 通道优先级 |
+| CCR.MSIZE[1:0] / PSIZE[1:0] | 数据宽度(8/16/32位) |
+| CCR.MINC / PINC | 地址递增使能 |
+| CCR.CIRC | 循环模式 |
+| CCR.DIR | 传输方向(外设→内存 / 内存→外设) |
+| CCR.MEM2MEM | 内存到内存模式(无视外设请求) |
+| CNDTR | 传输次数(每次传输后自减) |
+| CPAR | 外设地址 |
+| CMAR | 存储器地址 |
+| ISR | 中断状态寄存器(TCIF/HTIF/TEIF等) |
+| IFCR | 中断标志清除寄存器(写1清除) |
+
+## 常见问题与避坑
+
+1. **DMA 传输没开始** → 检查 CCR.EN 是否使能、CNDTR 是否设置、外设请求是否使能
+2. **传输完没有中断** → 检查 CCR.TCIE
+3. **CNDTR 一直为 0** → 传输完成后的值;循环模式下复位后重新装载
+4. **DMA 与外设配合错误** → 注意 MSIZE/PSIZE 要与外设寄存器宽度匹配
+5. **内存到内存模式启动失败** → 先配置好所有寄存器再 EN=1;注意通道启动后 CPAR/CMAR 不可修改

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

@@ -0,0 +1,145 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — ADC章节 + 配套代码32~35"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# ADC模数转换
+
+> **用生活理解**:ADC 就像一把电压尺子——输入一个模拟电压(0~3.3V),输出一个数字(0~4095,12位精度)。就像用尺子量高度,尺子的刻度数决定了精度(12位=4096个刻度)。
+
+---
+
+## ADC 概述
+
+ADC = Analog-to-Digital Converter(模数转换器)。
+
+STM32F103ZET6 有 **3 个 ADC**(ADC1/2/3),各有:
+
+| 特性 | 值 |
+|------|-----|
+| 分辨率 | 12 位(输出 0~4095) |
+| 通道数 | 最多 18 个(16 外部 + 2 内部) |
+| 转换模式 | 单次/连续/扫描/间断 |
+| 触发方式 | 软件触发 / 定时器触发 / 外部引脚触发 |
+| ADC 时钟 | 来自 APB2,最大 14MHz |
+| 采样时间 | 可编程 1.5~239.5 个周期 |
+
+### SAR ADC 原理
+
+STM32F103 使用**逐次逼近型**(SAR, Successive Approximation Register)ADC:
+
+```
+1. 采样:对输入电压进行采样保持
+2. 比较:从最高位(bit 11)开始,逐位比较
+   第1步:VIN > VREF/2 ? → bit11=1 : bit11=0
+   第2步:VIN > 调整后的参考值 ? → bit10=1 : bit10=0
+   ... 共12步 → 得到12位结果
+3. 转换时间 = 采样时间 + 12.5 个 ADC 时钟周期
+```
+
+### 规则组与注入组
+
+| 组 | 说明 | 通道数 | 用途 |
+|----|------|--------|------|
+| **规则组** | 常规转换序列 | 最多16通道 | 主要数据采集 |
+| **注入组** | 可打断规则组的"高优先级"转换 | 最多4通道 | 紧急数据采集 |
+
+### ADC 关键寄存器
+
+| 寄存器 | 功能 |
+|--------|------|
+| SR | 状态(EOC转换结束、STRT开始) |
+| CR1 | 控制(SCAN扫描模式、RES分辨率) |
+| CR2 | 控制(ADON开启、CONT连续、EXTTRIG外部触发) |
+| SMPR1/2 | 采样时间(每个通道单独配置) |
+| SQR1~3 | 规则序列(通道顺序与数量) |
+| DR | 转换结果(12位右对齐) |
+
+> **参考**:参考手册 §11(ADC 寄存器描述)
+
+---
+
+## 实验:单通道 ADC 采集
+
+### 软件设计(寄存器版 — 单次采集)
+
+```c
+// ADC1 配置:通道0(PA0),软件触发,单次转换
+void ADC1_Init(void)
+{
+    // 1. 开启ADC1时钟 + GPIOA时钟
+    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+
+    // 2. PA0 模拟输入模式(CNF=00, MODE=00)
+    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
+
+    // 3. 复位ADC1配置
+    RCC->APB2RSTR |= RCC_APB2RSTR_ADC1RST;  // 复位
+    RCC->APB2RSTR &= ~RCC_APB2RSTR_ADC1RST;
+
+    // 4. ADC 时钟分频(APB2=72MHz, ADC_CLK=72/6=12MHz)
+    RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6;
+
+    // 5. 配置ADC1
+    ADC1->CR2 |= ADC_CR2_ADON;               // 开启ADC1
+    Delay_us(10);
+
+    // 6. 校准
+    ADC1->CR2 |= ADC_CR2_CAL;                // 开始校准
+    while (ADC1->CR2 & ADC_CR2_CAL);         // 等待校准结束
+
+    // 7. 配置规则组序列长度=1,通道0为第1个
+    ADC1->SQR1 &= ~ADC_SQR1_L;               // L[3:0]=0: 1个转换
+    ADC1->SQR3 &= ~ADC_SQR3_SQ1;             // SQ1[4:0]=0: 通道0
+
+    // 8. 采样时间:239.5周期
+    ADC1->SMPR2 |= ADC_SMPR2_SMP0;           // SMP0[2:0]=111: 239.5周期
+}
+
+// 单次转换(软件触发)
+uint16_t ADC1_Read(void)
+{
+    ADC1->CR2 |= ADC_CR2_ADON;               // 再次写ADON启动转换
+    while (!(ADC1->SR & ADC_SR_EOC));        // 等待转换结束(EOC=1)
+    return ADC1->DR;                         // 读取结果(0~4095)
+}
+
+int main(void)
+{
+    ADC1_Init();
+    while (1)
+    {
+        uint16_t val = ADC1_Read();
+        float voltage = val * 3.3f / 4095.0f;  // 转为电压值
+        printf("ADC: %d, %.2fV\r\n", val, voltage);
+        Delay_ms(500);
+    }
+}
+```
+
+---
+
+## 核心速查表
+
+| 步骤 | 寄存器操作 |
+|------|----------|
+| 开启ADC | `CR2 |= ADON` |
+| 校准 | `CR2 |= CAL → 等待CAL=0` |
+| 配置通道 | `SQR3.SQ1 = 通道编号` |
+| 配置采样时间 | `SMPRx.SMPy = 周期数` |
+| 启动转换 | `CR2 |= ADON`(软件)或 `SWSTART` |
+| 查询EOC | `while (!(SR & EOC))` |
+| 读取结果 | `val = DR`(12位右对齐) |
+
+## 常见问题与避坑
+
+1. **ADC 值跳动大** → 电源去耦不良、采样时间太短、建议开启硬件平均
+2. **ADC 值始终为 0** → 检查 GPIO 模式(必须模拟输入)、ADC 是否校准
+3. **ADC 值始终为 4095** → 输入电压超过 VREF+ 或 ADC 输入引脚悬空
+4. **多通道采样顺序错乱** → 检查 SQR 序列配置(SQ1~SQ16 的顺序)
+5. **ADC 时钟超过 14MHz** → ADC 转换结果会不准确(ADCPRE 分频必须使 ADC_CLK ≤ 14MHz)

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

@@ -0,0 +1,174 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — SPI/FSMC章节 + 配套代码36~42"
+author: "尚硅谷研究院"
+date: 2026-07-15
+created: 2026-07-15
+---
+
+# SPI通信与FSMC总线
+
+> **用生活理解**:SPI 是全双工的同步通信,就像两个人用两根线同时说话和听。四条线各司其职:SCK(节奏)、MOSI(你说)、MISO(我听)、NSS(点名)。FSMC 就是芯片的外部数据总线,把外部设备(Flash/SRAM/LCD)映射到 STM32 的地址空间,像访问内部变量一样访问外部设备。
+
+---
+
+## SPI 通信协议
+
+SPI = Serial Peripheral Interface(串行外设接口)。
+
+### 四线制
+
+| 信号 | 功能 |
+|------|------|
+| SCK | 时钟线(由主机产生) |
+| MOSI | 主机输出/从机输入 |
+| MISO | 主机输入/从机输出 |
+| NSS/CS | 从机选择(低电平有效) |
+
+### 四种模式(CPOL/CPHA)
+
+| 模式 | CPOL | CPHA | 特性 |
+|------|------|------|------|
+| 0 | 0 | 0 | SCK空闲低,第一个边沿采集 |
+| 1 | 0 | 1 | SCK空闲低,第二个边沿采集 |
+| 2 | 1 | 0 | SCK空闲高,第一个边沿采集 |
+| 3 | 1 | 1 | SCK空闲高,第二个边沿采集 |
+
+> 与 W25Q64 Flash 通信常用**模式 0**(CPOL=0, CPHA=0)或**模式 3**(CPOL=1, CPHA=3)
+
+### SPI 关键寄存器
+
+| 寄存器 | 功能 |
+|--------|------|
+| CR1 | 控制(CPOL/CPHA、BR波特率分频、MSTR主从、SPE使能、LSBFIRST) |
+| CR2 | 控制(SSOE、TXEIE/RXNEIE中断使能) |
+| SR | 状态(BSY忙、TXE发送空、RXNE接收非空) |
+| DR | 数据寄存器(发送/接收共用) |
+
+> **参考**:参考手册 §23(SPI 寄存器描述)
+
+---
+
+## W25Q64 Flash
+
+| 参数 | 值 |
+|------|-----|
+| 容量 | 8MB(64Mbit) |
+| 接口 | SPI |
+| 页 | 256 字节 |
+| 扇区 | 4KB(16 页) |
+| 擦除 | 扇区擦除(20ms)、块擦除、全擦除 |
+| 写寿命 | 100,000 次 |
+
+**常用指令**:
+| 指令 | 代码 | 功能 |
+|------|------|------|
+| WREN | 0x06 | 写使能 |
+| RDSR | 0x05 | 读状态寄存器 |
+| PAGE_PROG | 0x02 | 页编程(256字节) |
+| READ | 0x03 | 读数据 |
+| SECTOR_ERASE | 0xD8 | 扇区擦除(4KB) |
+
+---
+
+## 软件设计(软件模拟 SPI 写 Flash)
+
+**文件:`stm32_base/36_spi_software_register/Hardware/SPI/spi.h`**
+
+```c
+#define SCK_HIGH  (GPIOA->ODR |= GPIO_ODR_ODR5)
+#define SCK_LOW   (GPIOA->ODR &= ~GPIO_ODR_ODR5)
+#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
+#define MOSI_LOW  (GPIOA->ODR &= ~GPIO_ODR_ODR7)
+#define MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)
+#define CS_HIGH   (GPIOA->ODR |= GPIO_ODR_ODR4)
+#define CS_LOW    (GPIOA->ODR &= ~GPIO_ODR_ODR4)
+
+// SPI 模式0:CPOL=0, CPHA=0
+// 空闲时SCK=低, 上升沿采集数据
+
+void SPI_Init(void)
+{
+    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
+    // PA5(SCK)+PA7(MOSI): 推挽输出
+    // PA6(MISO): 浮空输入
+    // PA4(CS): 推挽输出
+}
+
+uint8_t SPI_SendByte(uint8_t byte)
+{
+    for (uint8_t i = 0; i < 8; i++)
+    {
+        if (byte & 0x80) MOSI_HIGH; else MOSI_LOW;  // MSB first
+        byte <<= 1;
+        SCK_HIGH;                      // 上升沿 → 从机采集
+        if (MISO_READ) byte |= 1;      // 读取MISO
+        SCK_LOW;                       // 下降沿 → 从机改变数据
+    }
+    return byte;
+}
+```
+
+---
+
+## FSMC 总线
+
+FSMC = Flexible Static Memory Controller(灵活的静态存储器控制器)。
+
+### 地址映射
+
+| Bank | 地址范围 | 目标设备 |
+|------|---------|---------|
+| Bank1 | 0x60000000~0x6FFFFFFF | NOR Flash / PSRAM / SRAM / LCD |
+| Bank2 | 0x70000000~0x7FFFFFFF | NAND Flash |
+| Bank3 | 0x80000000~0x8FFFFFFF | NAND Flash |
+| Bank4 | 0x90000000~0x9FFFFFFF | PC Card |
+
+Bank1 又分为 4 个子区(NE1~NE4),每个 64MB:
+```
+NE1: 0x60000000~0x63FFFFFF
+NE2: 0x64000000~0x67FFFFFF
+NE3: 0x68000000~0x6BFFFFFF
+NE4: 0x6C000000~0x6FFFFFFF
+```
+
+### FSMC 关键寄存器
+
+| 寄存器 | 功能 |
+|--------|------|
+| BC1~4 | 存储片控制寄存器(MTYP设备类型、MWID数据宽度) |
+| BT1~4 | 存储片时序寄存器(ADDSET地址建立、DATAST数据保持) |
+
+**LCD 应用**:将 LCD 的 D0~D15 数据线接 FSMC_D0~D15,RS 接 A0(地址线),
+这样:
+- 写 `*(uint16_t*)0x60000000 = data` → 写命令(RS=0)
+- 写 `*(uint16_t*)0x60020000 = data` → 写数据(RS=1,A0=1)
+
+> **参考**:参考手册 §19(FSMC 寄存器描述)
+
+---
+
+## 核心速查表
+
+| SPI操作 | 代码 |
+|---------|------|
+| 使能SPI | `CR1 |= SPE` |
+| 发送1字节 | `DR = byte; while(!(SR & TXE))` |
+| 接收1字节 | `while(!(SR & RXNE)); byte = DR` |
+| 波特率分频 | `CR1.BR[2:0]` = fPCLK/2~(fPCLK/256) |
+
+| FSMC操作 | 代码 |
+|----------|------|
+| 写SRAM | `*(uint16_t*)0x60000000 = data` |
+| 读SRAM | `data = *(uint16_t*)0x60000000` |
+| 写LCD命令 | `*(uint16_t*)0x60000000 = cmd` |
+| 写LCD数据 | `*(uint16_t*)0x60020000 = data` |
+
+## 常见问题与避坑
+
+1. **SPI 接收数据为 0xFF** → 检查 MISO 连接、从机是否选中(CS)、时钟是否匹配
+2. **W25Q64 写入失败** → 必须先发 WREN(0x06)、扇区必须先擦除(不能写覆盖)
+3. **FSMC 读写时序不对** → 参考 SRAM/LCD 数据手册的时序参数配置 ADDSET/DATAST
+4. **多个 SPI 设备冲突** → 各设备独立 CS、SCK/MOSI/MISO 共享
+5. **FSMC 地址线错位** → 16位模式下 HADDR[0] 不连接到 FSMC_A[0](内部是对齐的)

+ 10 - 0
X-Knowledge-Base/wiki/log.md

@@ -60,6 +60,16 @@ type: log
    新建 Wiki 页面:
    - `wiki/source-stm32-basic.md`(基础篇资料索引)
 
+    更新 `wiki/log.md`
+
+- `ingest`: 导入尚硅谷 STM32 进阶篇笔记
+
+   原始资料归档:
+   - `raw/Joplin/嵌入式+Linux/STM32学习笔记/07~12`(I2C、定时器、PWM、DMA、ADC、SPI+FSMC)
+
+   新建 Wiki 页面:
+   - `wiki/source-stm32-medium.md`(进阶篇资料索引)
+
    更新 `wiki/log.md`
 
 - `lint`: 清理 FreeRTOS 图片资源,重写 11 篇 raw 笔记

+ 24 - 0
X-Knowledge-Base/wiki/source-stm32-medium.md

@@ -0,0 +1,24 @@
+---
+tags: [source-summary]
+type: source
+source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1"
+author: "尚硅谷研究院"
+date: 2026-07-15
+url: ""
+created: 2026-07-15
+---
+
+# 尚硅谷 STM32 进阶篇笔记
+
+来源:尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — I2C、定时器、DMA、ADC、SPI、FSMC
+
+## 笔记列表
+
+| 编号 | 文件 | 内容 |
+|------|------|------|
+| 07 | `07-I2C通信与EEPROM 24C02.md` | I2C协议时序、软件模拟、硬件I2C、24C02 EEPROM |
+| 08 | `08-SysTick与通用定时器TIM.md` | SysTick寄存器、TIM时基单元、溢出时间计算 |
+| 09 | `09-TIM高级应用:PWM与输入捕获.md` | PWM频率/占空比、呼吸灯、输入捕获测频、寄存器+HAL双版 |
+| 10 | `10-DMA数据传输.md` | DMA控制器、通道映射、内存到内存传输、中断 |
+| 11 | `11-ADC模数转换.md` | SAR ADC原理、规则/注入组、单次/连续/多通道、定时器触发 |
+| 12 | `12-SPI通信与FSMC总线.md` | SPI四模式、W25Q64 Flash驱动、FSMC地址映射与SRAM/LCD控制 |