Răsfoiți Sursa

docs: 补充gitignore、source索引页及extract工具

OpenCode 1 săptămână în urmă
părinte
comite
2dd75224a5
3 a modificat fișierele cu 231 adăugiri și 4 ștergeri
  1. 3 0
      .gitignore
  2. 120 0
      X-Knowledge-Base/wiki/source-stm32-three-tutorials.md
  3. 108 4
      tools/extract.py

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+tmp/
+tools/
+docs/superpowers/

+ 120 - 0
X-Knowledge-Base/wiki/source-stm32-three-tutorials.md

@@ -0,0 +1,120 @@
+---
+tags: [source-summary]
+type: source
+source: "STM32学习笔记(三教程综合) — 环境搭建与GPIO详解"
+author: "铁头山羊 / 江协科技 / STM32 HAL库入门"
+date: 2026-07-09
+url: ""
+created: 2026-07-09
+updated: 2026-07-09
+---
+
+# 三教程综合 STM32 入门笔记
+
+## 来源
+
+基于三个教程系列的 ASR 转录文本与配套源码整理:
+
+- **铁头山羊 HAL 库教程**(B站):CubeMX + HAL 库为主,偏原理讲解,GPIO 输出速度与 EMI 讲解深入
+- **江协科技标准库教程**(B站):标准库(StdPeriph)为主,工程模板、Delay 模块(SysTick 寄存器操作)、LED/Key/Buzzer/LightSensor 模块化代码
+- **STM32 HAL库入门**(B站):CubeMX 图形化配置演示,闪灯/按钮实验
+
+## 新增原始笔记
+
+位于 `raw/Joplin/嵌入式+Linux/STM32学习笔记(三教程综合)/`:
+
+### Note 1:`STM32-环境搭建与工程模板.md`
+
+完整覆盖:
+- STM32 vs 51 差异对比(铁头山羊第一课核心观点:零基础可直接学 STM32)
+- Keil MDK 安装(含 Device Pack、注册授权、ST-Link 驱动)
+- CubeMX + HAL 库方案定位
+- 项目五目录结构(Start/User/Hardware/Library/System)
+- `stm32f10x_conf.h` 外设开关机制
+- 启动文件选择(MD/LD/HD)
+- Delay 模块(SysTick 寄存器操作原理:LOAD/VAL/CTRL/COUNTFLAG)
+- main.c 模板(标准库八字诀:时钟→结构体→配置→初始化)
+- 3 种 GPIO 电平操作方法
+- 标准库 vs HAL 库对比表格
+- 烧录方法(ST-Link SWD、FlyMcu 串口 ISP、bootloader 擦除恢复)
+- GPIO 初始化寄存器级原理(CRL/CRH 分析)
+- 常见问题排查表
+
+### Note 2:`STM32-GPIO详解.md`
+
+完整覆盖:
+- GPIO 内部结构框图(P-MOS/N-MOS/ODR/IDR/I/O Pad)
+- 8 种工作模式详解(附 P-MOS/N-MOS 状态表)
+  - 4 种输出:推挽、开漏、复用推挽、复用开漏
+  - 4 种输入:浮空、上拉、下拉、模拟
+- 推挽 vs 开漏对比(含线与能力、电平兼容)
+- 输出速度详解(2/10/50MHz、上升/下降时间、EMI 关系、选型建议)
+- CRL/CRH 寄存器 MODE 位含义
+- LED 驱动代码(低电平点亮电路原理、模块化 LED.c、LED 闪烁、LED 流水灯)
+- HAL 库等效 API 对照表
+- 蜂鸣器模块代码
+- Key 模块(上拉输入 + 延时消抖 + 等待松手)
+- 光敏传感器控制蜂鸣器
+- 8 种输入模式选择指南
+- 常见误区纠正(速度、上拉下拉、开漏、浮空等)
+
+### Note 3:`STM32学习笔记(上)-UART串口通信.md`
+
+完整覆盖:
+- 串行 vs 并行、同步 vs 异步通信概念
+- UART 数据帧结构(起始位/数据位/校验位/停止位)
+- 波特率与位时序
+- TX/RX 交叉连接
+- STM32 USART 模块结构、GPIO 引脚配置(PA9/PA10)
+- CubeMX 配置与标准外设库初始化
+- 轮询发送(TXE/TC 标志位)
+- 发送字节/数组/字符串/数字
+- printf 重定向(fputc + MicroLIB)与自封装 Serial_Printf
+- 中断接收(RXNE 中断 + NVIC 配置)
+- 接收标志位与数据变量、回传实验
+- 中断进阶:命令解析控制 LED 可变间隔闪烁
+- 常用问题:波特率不一致、TX 接 TX、MicroLIB、接收丢数据、电平转换
+
+### Note 4:`STM32学习笔记(中)-I2C通信.md`
+
+完整覆盖:
+- I2C 总线结构(SCL/SDA/上拉电阻)
+- 开漏输出与线与逻辑、I2C vs UART 对比
+- 起始/停止条件、数据帧格式、ACK/NACK
+- 速度模式(SM 100kHz / FM 400kHz)
+- STM32 I2C 外设配置(引脚/重映射/复用开漏/时钟)
+- 内部结构框图与关键标志位(SB/ADDR/AF/TXE/BTF/RXNE/BUSY)
+- 硬件 I2C 写数据完整流程(等待空闲→起始位→寻址→发数据→停止位)
+- ADDR 清零规范(先读 SR1 再读 SR2)
+- 硬件 I2C 读数据流程(重复起始位 + NACK 结束)
+- 软件 I2C GPIO 模拟实现(Start/Stop/SendByte/RecvByte/Ack)
+- MPU6050 六轴传感器驱动(寄存器读写、初始化、数据读取)
+- 硬件 vs 软件 I2C 对比表格
+- 常见问题:总线锁死、上拉电阻、地址冲突
+
+### Note 5:`STM32学习笔记(下)-SPI通信.md`
+
+完整覆盖:
+- SPI 四线制(SCLK/MOSI/MISO/NSS)与一主多从结构
+- 全双工数据交换机制(16 位环形移位寄存器)
+- CPOL/CPHA 定义的 4 种时序模式详解
+- MSB First vs LSB First
+- STM32 SPI 外设配置(CubeMX 全双工主机模式/软件 NSS/波特率计算)
+- 标准外设库初始化代码
+- HAL 库 API:HAL_SPI_Transmit / Receive / TransmitReceive
+- NSS 手动控制与接收缓冲区初值 0xFF 要点
+- W25Q64 Flash 存储结构(块/扇区/页)
+- W25Q64 指令集:写使能(0x06)、扇区擦除(0x20)、页编程(0x02)、读数据(0x03)、读 ID(0x9F)
+- 等待忙(状态寄存器 BUSY 位查询 + 超时保护)
+- 完整实验:LED 状态保存与恢复(按钮控制 + Flash 存储,断电不丢失)
+- 软件 SPI GPIO 模拟实现(Start/Stop/SwapByte 模式 0)
+- 硬件 vs 软件 SPI 对比表格
+- 常见问题:NSS 未拉低、模式不匹配、忘记写使能、跨页写入、擦除时间长
+
+## 与现有 wiki 的关系
+
+- 现有 [[STM32-环境搭建]]、[[STM32-GPIO]] 是精炼的 wiki 摘要层
+- 现有 [[STM32-中断系统]]、[[STM32-时钟系统]]、[[STM32-定时器]]、[[STM32-I2C]]、[[STM32-ADC]]、[[STM32-DMA]]、[[STM32-PWR]]、[[STM32-BKP与RTC]]、[[STM32-WDG]]、[[STM32-FLASH]] 是 wiki 综合笔记
+- 这 11 篇原始笔记是更详细的课程级笔记,保留了教师的讲解顺序和示例代码
+- 现有 [[source-jiangxie-stm32]]、[[source-hal-library-best]]、[[source-tietoushanyang]] 是单教程来源摘要
+- 本文是三个教程的综合型原始笔记集合,可直接作为 wiki 的补充参考来源

+ 108 - 4
tools/extract.py

@@ -1,11 +1,12 @@
 # /// script
-# dependencies = []
+# dependencies = ["requests", "silero-vad", "torch"]
 # ///
-"""Extract timed text from video subtitles; grab single frames.
+"""Extract timed text from video subtitles; transcribe via ASR; grab frames.
 
 Commands:
-  parse  <input> [--outdir <dir>]  extract timed text from subtitles
-  frame  <video> <timestamp> --outpath <path>  extract single frame
+  parse       <input> [--outdir <dir>]           extract timed text from subtitles
+  transcribe  <video> [--outpath <path>]          transcribe speech via VAD+ASR
+  frame       <video> <timestamp> --outpath <p>   extract single frame
 """
 
 import argparse
@@ -13,6 +14,7 @@ import os
 import re
 import subprocess
 import sys
+import tempfile
 from pathlib import Path
 
 SRT_PATTERN = re.compile(
@@ -164,6 +166,101 @@ def cmd_frame(args: argparse.Namespace) -> None:
     print(f"[OK] Frame saved -> {out}")
 
 
+def extract_audio(video_path: Path, out_wav: Path) -> None:
+    """Extract 16kHz mono WAV from video."""
+    cmd = [
+        "ffmpeg", "-y", "-i", str(video_path),
+        "-vn", "-ar", "16000", "-ac", "1",
+        str(out_wav),
+    ]
+    subprocess.run(cmd, capture_output=True, check=True)
+
+
+def read_audio_tensor(wav_path: Path) -> "torch.Tensor":
+    """Read WAV file as mono float32 tensor (compat with silero-vad)."""
+    import numpy as np
+    import torch
+    # Use ffmpeg to decode to raw PCM float32
+    result = subprocess.run(
+        ["ffmpeg", "-y", "-i", str(wav_path),
+         "-f", "f32le", "-ac", "1", "-ar", "16000", "-"],
+        capture_output=True, check=True,
+    )
+    arr = np.frombuffer(result.stdout, dtype=np.float32).copy()
+    return torch.from_numpy(arr).squeeze()
+
+
+def _find_model_dir(model_size: str) -> str | None:
+    """Find cached faster-whisper model directory."""
+    repo = f"Systran/faster-whisper-{model_size}"
+    cache = Path(os.path.expanduser("~/.cache/huggingface/hub"))
+    repo_dir = cache / f"models--{repo.replace('/', '--')}"
+    if not repo_dir.exists():
+        return None
+    snapshots = repo_dir / "snapshots"
+    if not snapshots.exists():
+        return None
+    snaps = list(snapshots.iterdir())
+    return str(snaps[0]) if snaps else None
+
+
+def cmd_transcribe(args: argparse.Namespace) -> None:
+    video = Path(args.video)
+    if not video.exists():
+        print(f"[ERR] Video not found: {video}", file=sys.stderr)
+        sys.exit(1)
+
+    outpath = Path(args.outpath) if args.outpath else None
+    model_size = args.model
+
+    model_dir = _find_model_dir(model_size)
+    if model_dir is None:
+        print(f"[ERR] Model '{model_size}' not cached locally", file=sys.stderr)
+        sys.exit(1)
+
+    print(f"[...] Extracting audio from {video.name} ...", file=sys.stderr)
+    wav = Path(tempfile.mktemp(suffix=".wav"))
+    try:
+        extract_audio(video, wav)
+
+        print(f"[...] Loading faster-whisper ({model_size}) ...", file=sys.stderr)
+        import os as _os
+        _os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
+        from faster_whisper import WhisperModel
+        model = WhisperModel(model_dir, device="cpu", compute_type="int8")
+
+        print(f"[...] Transcribing. This may take a few minutes ...", file=sys.stderr)
+        segments, _info = model.transcribe(
+            str(wav), beam_size=5, vad_filter=True,
+            vad_parameters=dict(min_silence_duration_ms=300),
+        )
+
+        segs: list[tuple[float, str]] = []
+        for seg in segments:
+            text = seg.text.strip()
+            if text:
+                segs.append((seg.start, text))
+
+        print(f"[...] {len(segs)} segments", file=sys.stderr)
+        for s, t in segs:
+            print(f"  [{sec_to_ts(s)}] {t[:80]}", file=sys.stderr)
+
+    finally:
+        wav.unlink(missing_ok=True)
+
+    if not segs:
+        print(f"[WARN] No speech transcribed", file=sys.stderr)
+        return
+
+    lines = [f"[{sec_to_ts(s)}] {t}" for s, t in segs]
+    if outpath:
+        outpath.parent.mkdir(parents=True, exist_ok=True)
+        outpath.write_text("\n".join(lines), encoding="utf-8")
+        print(f"[OK] Transcribed -> {outpath}  ({len(segs)} segments)")
+    else:
+        print("\n".join(lines))
+
+
 def main() -> None:
     parser = argparse.ArgumentParser(description="Video subtitle extractor & frame grabber")
     sub = parser.add_subparsers(dest="command")
@@ -172,6 +269,11 @@ def main() -> None:
     p.add_argument("input", help="Video file or directory")
     p.add_argument("--outdir", default="./transcripts", help="Output directory")
 
+    p = sub.add_parser("transcribe", help="Transcribe speech via local faster-whisper")
+    p.add_argument("video", help="Path to video file")
+    p.add_argument("--outpath", help="Output transcript path (default: stdout)")
+    p.add_argument("--model", default="base", help="Model size: tiny/base/small/medium/large")
+
     p = sub.add_parser("frame", help="Extract single frame as JPEG")
     p.add_argument("video", help="Path to video file")
     p.add_argument("timestamp", help="Timestamp (HH:MM:SS or seconds)")
@@ -180,6 +282,8 @@ def main() -> None:
     args = parser.parse_args()
     if args.command == "parse":
         cmd_parse(args)
+    elif args.command == "transcribe":
+        cmd_transcribe(args)
     elif args.command == "frame":
         cmd_frame(args)
     else: