流程:源材料 →
raw/学习笔记(含代码 + 通俗解释) →wiki/知识页。以 STM32 学习笔记和 FreeRTOS 笔记为例。
| 类型 | 处理方式 | 工具 |
|---|---|---|
| DOCX 教程 | pandoc 转 MD,提取图片 |
pandoc + --extract-media |
| PDF 手册 | markitdown 或直接 OCR |
markitdown skill |
| 视频教程 | 已有配套讲义则直接处理,无讲义需先转录 | — |
| 网页/博客 | webfetch 抓取 |
webfetch tool |
拿到文档后先扫描目录结构,理解其教学法:
# 提取到 temp 目录,保留所有图片
pandoc "源文档.docx" -t markdown --wrap=none `
--extract-media="C:\temp\docx_media" `
-o "C:\temp\docx_media\笔记.md"
这会生成:
.md 文件(含图片引用路径)media/ 目录(含所有 PNG、EMF 等图片)用 Read tool 逐段阅读 MD 文件,在 read 时注意:
代码块:注意代码风格和教学顺序
# 读取文档的前 200 行了解结构
Read offset=1 limit=200
# 跳读找到关键章节
Read offset=N limit=50
不要盲目全部复制到仓库! 大多数 DOCX 图片是 IDE 截图、安装步骤图等无长期参考价值的内容。
筛选策略:
用 API 视觉模型逐张验证(可调用兼容 OpenAI API 的 VLM)
# 调用视觉 API 识别图片内容
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($path))
$body = @{
model = "qwen3.5-9b-uncensored-nothink" # 非推理模型,content 直接出结果
messages = @(@{ role = "user"; content = @(
@{ type = "text"; text = "图中内容是什么?用一句话回答" }
@{ type = "image_url"; image_url = @{ url = "data:image/png;base64,$b64" } }
)})
max_tokens = 100
}
$r = Invoke-RestMethod -Uri "https://api.example.com/v1/chat/completions" ...
$r.choices[0].message.content
只保留以下类型图片:
删除以下类型:
重命名:image1.png → rcc_register_description.png
raw/{source-category}/
{topic}/ # 每个主题一个目录
01-第一篇章.md
02-第二篇章.md
assets/ # 该主题的图片资源
gpio_pin_structure.png
usart_block.png
| 粒度 | 适用场景 | 示例 |
|---|---|---|
| 单篇完整笔记 | 内容单一的文档 | 01-环境搭建与工程模板.md |
| 分篇笔记 | 内容多的文档,按外设拆分 | 02-GPIO详解.md ~ 17-FSMC与LCD显示.md |
当有多个教程源(寄存器教程 + 标准库教程 + HAL 教程)时:
# 在每个笔记中按"三层代码"结构组织
## 寄存器方式(尚硅谷风格)
~~~c
// 裸地址操作 → 结构体宏定义
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
~~~
## 标准库方式(江协科技风格)
~~~c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
~~~
## HAL 库方式
~~~c
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
~~~
寄存器操作的每行代码必须标注操作符含义:
REG |= BITMASK; // |= 按位或: 保持其他位不变,只将目标位置1
REG &= ~BITMASK; // &= ~ 按位与+取反: 保留其他位,只将目标位清0
REG ^= BITMASK; // ^= 按位异或: 翻转目标位
x << N; // << 左移N位
~x; // ~ 按位取反
REG & BITMASK // & 按位与: 测试特定位是否为1
while (条件); // 轮询等待硬件标志位
示例:
// 使能 GPIOD 时钟(APB2ENR 寄存器第 5 位置 1)
RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; // |= 按位或: 保持其他时钟位不变
// 只将 IOPDEN(IOPD 时钟使能位) 置 1
// 配置 PD13 为推挽输出 50MHz
GPIOD->CRH &= ~GPIO_CRH_CNF13; // &= ~ 按位与+取反: 清除 CNF13 位域
GPIOD->CRH |= GPIO_CRH_MODE13; // |= 按位或: 设置 MODE13=11 (50MHz)
// MODE[1:0]=11 + CNF[1:0]=00
// = 推挽输出、最高速度
# pandoc 提取
pandoc source.docx --extract-media=./media -o output.md
# 或手动复制(如果已有图片文件)
Copy-Item source/*.png assets/
问题:pandoc 提取的 PNG 经常 >200KB(最大可达 1.7MB),直接 base64 传入视觉 API 会导致超时(connection timeout 或空响应)。
解决:先压缩到宽度 800px、质量 80%,体积可缩小 10~50 倍。
# 压缩图片到 800px 宽、质量 80%,存入 temp
Add-Type -AssemblyName System.Drawing
function Compress-Image {
param($srcPath, $destPath, $maxWidth = 800, $quality = 80)
$img = [System.Drawing.Image]::FromFile($srcPath)
$ratio = [Math]::Min(1.0, $maxWidth / $img.Width)
$w = [int]($img.Width * $ratio)
$h = [int]($img.Height * $ratio)
$bmp = New-Object System.Drawing.Bitmap($w, $h)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.DrawImage($img, 0, 0, $w, $h)
$g.Dispose()
$encParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
$encParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter(
[System.Drawing.Imaging.Encoder]::Quality, [int64]$quality)
$codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
Where-Object { $_.MimeType -eq 'image/jpeg' }
$bmp.Save($destPath, $codec, $encParams)
$bmp.Dispose(); $img.Dispose()
}
# 使用示例:压缩原图后传给 API 验证
Compress-Image "原始大图.png" "C:\temp\压缩小图.jpg" 800 80
压缩后调用视觉 API 验证每张图片是否与其文件名匹配:
function Test-Img {
param($path, $question)
# 大图先压缩
if ((Get-Item $path).Length -gt 150KB) {
$compressed = Join-Path $env:TEMP "tmp_$(Get-Random).jpg"
Compress-Image $path $compressed 800 80
$sendPath = $compressed
} else {
$sendPath = $path
}
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($sendPath))
$body = @{ model = "qwen3.5-9b-uncensored-nothink"
messages = @(@{ role = "user"; content = @(
@{ type = "text"; text = $question }
@{ type = "image_url"; image_url = @{ url = "data:image/jpeg;base64,$b64" } }
)})
max_tokens = 60 } | ConvertTo-Json -Depth 10
$r = Invoke-RestMethod -Uri "https://api.1808366.xyz/v1/chat/completions" ...
if ($compressed) { Remove-Item $compressed -Force }
return $r.choices[0].message.content
}
foreach ($f in $allImages) {
$result = Test-Img $f "图中内容是什么?一句话回答"
if ($result -match "超时|空|null") { Write-Warning "$f 仍需手动检查" }
Write-Host "$f => $result"
}
| 图片宽度 | 高度 | 文件大小 | 典型内容 | 是否需要压缩 |
|---|---|---|---|---|
| 960×154 | 窄长 | ~43KB | 寄存器位域图 | 否 |
| 700×250 | 适中 | ~14KB | 小结构图 | 否 |
| 2500×1500 | 大 | ~260KB | 完整框图 | 是 |
| 1200×1700 | 大 | ~400KB | 波形图 | 是 |
| 1800×1600 | 大 | ~1.7MB | 复杂框图 | 必须压缩 |
{外设}_{内容}.pnggpio_pin_structure.png, usart_block_diagram.png, i2c_start_stop.png在笔记中使用相对路径引用:

*来源:STM32 参考手册*
| 前缀 | 场景 |
|---|---|
ingest |
导入新资料 + 同步更新 wiki |
wiki |
纯知识页整理(不伴随导入) |
raw |
仅涉及原始资料资产,不改 wiki |
lint |
知识库健康检查与修复 |
docs |
仓库说明文档与协作规则更新 |
wiki/index.mdwiki/log.mdingest: 简短摘要git add .
git commit -m "<类型>: <摘要>"
| 位置 | 用途 |
|---|---|
assets/ |
最终选定的高质量图片(精选后≤50张) |
C:\Users\<你>\AppData\Local\Temp\opencode\ |
原始提取的全部图片(按需保留) |
code/ # 第一层:完整工程归档(只存不改)
├── 09_消息队列/
│ └── Core/Src/freertos_demo.c
07-消息队列与队列集.md # 第二层:嵌入提取的核心代码
code/ — 原始工程完整复制,作为可编译参考.md 笔记 — 从 freertos_demo.c 中提取核心完整示例,嵌入文档正文中不要直接复制整个文件到笔记中。提取核心示例时:
freertos_demo.c(或 main.c)找到核心演示逻辑示例(笔记中的代码段):
// ========== 核心示例:发送消息 ==========
// 保留了完整函数体,删除了任务创建等模板部分
void task1(void *pvParameters) {
uint8_t key = 0;
while (1) {
key = Key_Detect();
if (key == KEY1_PRESS) {
xQueueSend(queue1, &key, portMAX_DELAY);
// portMAX_DELAY = 队列满了就死等
}
vTaskDelay(500);
}
}
vTaskDelay() 而不是 HAL_Delay()(主动让出 CPU)portMAX_DELAY 表示"无限等待"#include 的头文件要写清楚configUSE_XXX)要说明含义flowchart TD
A[拿到源文档 DOCX/PDF/网页] --> B[pandoc 转 MD + 提取图片]
B --> C[阅读 MD 理解教学结构与风格]
C --> D[设计笔记架构<br/>拆分外设/概念]
D --> E[通读配套代码]
E --> F[提取核心代码段<br/>删除重复模板]
F --> G[撰写讲解正文<br/>代码 + 通俗解释]
G --> H[API 视觉模型验证图片]
H --> I[精选+重命名图片到 assets/]
I --> J[整体复制代码项目到 code/]
J --> K[创建 raw/ 学习笔记]
K --> L[从 raw 笔记提炼 wiki 知识页]
L --> M[更新 wiki/index.md + log.md]
M --> N[git commit<br/>ingest/raw/wiki]
raw/ 目录只读不改|=、&=~、<< 都要解释raw/ 的对应分类,再提炼到 wiki/