news 2026/10/5 2:18:51

VSCode搭建STM32开发环境:GCC+OpenOCD全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VSCode搭建STM32开发环境:GCC+OpenOCD全流程指南

1. 为什么要在 VSCode 里折腾 STM32 开发

嵌入式开发这个圈子有个很有意思的现象:很多人学 STM32 的第一套工具链是 Keil MDK 或者 IAR,用着用着就离不开了,哪怕编辑器再难用、代码补全再拉胯、跨平台再差,也忍着。但最近两三年,我身边越来越多的工程师开始把主力开发环境往 VSCode 上迁移,尤其是做 STM32 项目的朋友。原因其实不复杂——VSCode 的编辑体验、插件生态、Git 集成、终端一体化,这些东西一旦用过就回不去了。

但问题也随之而来:VSCode 本身只是一个编辑器,它不像 Keil 那样开箱即用。你要在 VSCode 里写 STM32 代码、编译、下载、调试,需要自己把工具链串起来。这中间涉及编译器选型、构建系统配置、调试器对接、芯片支持包安装等一系列环节,任何一个环节出问题都可能导致编译报错或者下载失败。我见过不少新手在这一步卡了好几天,最后又灰溜溜地回到 Keil 的怀抱。

这篇文章就是来解决这个问题的。我会从零开始,把 VSCode 中 STM32 开发环境的搭建过程完整走一遍,包括工具链的选择逻辑、每一步操作背后的原因、实际配置中容易踩的坑,以及最终跑通一个完整项目的基本流程。不管你是刚接触 STM32 的新手,还是想从 Keil 迁移过来的老手,都能按照这个流程直接复现。整个方案基于STM32CubeMX + HAL 库 + arm-none-eabi-gcc + OpenOCD这套组合,全部使用免费开源工具,跨平台可用,Windows、Linux、macOS 都能跑。

2. 工具链选型与整体方案设计

2.1 为什么选 GCC 而不是 Keil 的 ARMCC

在 VSCode 里做 STM32 开发,第一个要做的决策就是编译器用什么。Keil 用的是 ARMCC(现在叫 Arm Compiler),IAR 用的是自己的编译器,这两个都是商业编译器,不单独售卖,绑定在 IDE 里。VSCode 要用的话,最自然的选择就是arm-none-eabi-gcc,也就是 ARM 官方的 GNU 工具链。

选 GCC 的理由很实在:第一,完全免费,没有版权风险,公司里用也不用担心律师函;第二,跨平台,Windows 上装个 MSYS2 或者直接下 ARM 官方的安装包就能用,Linux 和 macOS 更简单,包管理器一行命令搞定;第三,和 CMake、Make 这些构建系统配合得天衣无缝,而 VSCode 的插件生态对 CMake 的支持非常成熟;第四,社区资源丰富,遇到问题搜索到的答案基本都是 GCC 相关的。

当然 GCC 也有它的短板。编译出来的代码体积通常比 ARMCC 大一些,优化等级需要调得更激进才能达到相近的尺寸。但对于大多数项目来说,STM32 的 Flash 空间足够宽裕,这点差异完全可以接受。而且 GCC 的编译速度在某些场景下反而更快,特别是配合 ccache 之后。

2.2 构建系统:Make 还是 CMake

确定了编译器,接下来要选构建系统。STM32CubeMX 默认生成的是 Makefile 工程,直接用 make 就能编译。但如果你想让 VSCode 的智能提示、跳转定义、调试配置更顺畅,CMake 是更好的选择。CMake 的优势在于它能生成 compile_commands.json,这个文件是 VSCode 的 C/C++ 插件理解你项目结构的关键。有了它,代码补全、函数跳转、头文件索引都能准确工作,不会出现满屏红色波浪线的情况。

我的建议是:如果你只是临时跑个 demo,用 Makefile 就够了;但如果是正经项目,强烈建议用 CMake。STM32CubeMX 从某个版本开始已经支持直接生成 CMake 工程了,虽然生成的 CMakeLists.txt 比较简单,但作为起点完全够用,后续可以根据需要自己扩展。

2.3 调试器与下载方式

调试器这块,市面上常见的 ST-Link、J-Link、DAPLink 都支持。ST-Link 是性价比最高的选择,原厂的也不贵,淘宝上几十块钱的克隆版也能用。J-Link 性能更好,但价格贵不少,而且克隆版有被 ban 的风险。DAPLink 是开源的方案,配合 OpenOCD 使用很灵活。

在 VSCode 里,调试和下载主要通过Cortex-Debug插件来完成,它底层调用 OpenOCD 或者 J-Link GDB Server。OpenOCD 是开源方案,支持 ST-Link、DAPLink 等多种调试器,配置稍微麻烦一点但胜在免费灵活。J-Link 用户可以直接用 SEGGER 的 GDB Server,配置更简单,但需要安装 J-Link 驱动软件。

2.4 整体方案架构

把上面的选择串起来,整个方案是这样的:STM32CubeMX 负责生成初始化代码和工程骨架,arm-none-eabi-gcc 负责编译,CMake 负责组织构建流程,OpenOCD 负责和调试器通信,Cortex-Debug 插件负责在 VSCode 里提供图形化调试界面,STM32 的 HAL 库提供外设驱动。这套组合全部开源免费,社区活跃,遇到问题容易找到解决方案。

组件选型作用是否必须
编辑器VSCode代码编写、插件宿主必须
编译器arm-none-eabi-gcc将 C/C++ 编译为 ARM 机器码必须
构建系统CMake + Ninja组织编译流程、生成构建文件推荐
代码生成STM32CubeMX生成初始化代码和工程骨架推荐
调试器软件OpenOCD与硬件调试器通信必须
VSCode 插件Cortex-Debug图形化调试界面必须
VSCode 插件C/C++代码补全、跳转、索引必须
硬件调试器ST-Link V2连接 PC 与 STM32 芯片必须

3. 环境搭建的完整实操步骤

3.1 安装 VSCode 与必备插件

VSCode 的安装没什么好说的,官网下载对应系统的安装包,一路下一步就行。安装完成后,有几个插件是必须装的。打开扩展面板,搜索并安装以下插件:

  • C/C++(微软官方):提供代码补全、跳转、错误检查,是 VSCode 写 C 代码的基础。
  • CMake Tools(微软官方):提供 CMake 工程的配置、构建、调试集成。
  • Cortex-Debug:专门用于 ARM Cortex-M 芯片的调试插件,支持 OpenOCD、J-Link、ST-Link GDB Server 等多种后端。
  • ARM Assembly:提供 ARM 汇编语法高亮,看启动文件的时候有用。

装完插件后,建议把 VSCode 的终端默认配置改成你常用的 shell。Windows 上如果装了 Git Bash 或者 MSYS2,可以设成对应的 bash,这样后续执行 make、cmake 命令会更顺手。

注意:C/C++ 插件和 Cortex-Debug 插件偶尔会有版本兼容问题,如果调试时出现奇怪的报错,可以先检查这两个插件是否都是最新版。

3.2 安装 arm-none-eabi-gcc 工具链

这是整个环境搭建中最关键的一步。Windows 用户有两个选择:一是去 ARM 官网下载官方的 GNU Toolchain 安装包,二是通过 MSYS2 安装。官方安装包的好处是版本稳定、安装简单,缺点是更新麻烦。MSYS2 的好处是包管理方便,一条命令就能装好,而且自带 make、cmake 等工具。

我个人的习惯是用 MSYS2,因为后续装 OpenOCD、make、cmake 都可以用 pacman 统一管理。安装 MSYS2 后,打开 MSYS2 终端,执行:

pacman -S mingw-w64-x86_64-arm-none-eabi-gcc pacman -S mingw-w64-x86_64-arm-none-eabi-binutils pacman -S mingw-w64-x86_64-arm-none-eabi-newlib pacman -S mingw-w64-x86_64-cmake pacman -S mingw-w64-x86_64-ninja pacman -S mingw-w64-x86_64-make

装完后,把 MSYS2 的 mingw64/bin 目录加到系统 PATH 里。验证是否成功:

arm-none-eabi-gcc --version

如果能看到版本号输出,说明工具链安装成功。Linux 用户更简单,Ubuntu 下直接:

sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi sudo apt install cmake ninja-build make

macOS 用户用 Homebrew:

brew install arm-none-eabi-gcc cmake ninja

3.3 安装 OpenOCD 与调试器驱动

OpenOCD 的安装同样可以通过 MSYS2 完成:

pacman -S mingw-w64-x86_64-openocd

Linux 下sudo apt install openocd,macOS 下brew install openocd。

如果你用的是 ST-Link 调试器,Windows 上还需要安装 ST-Link 的 USB 驱动。这个驱动通常在 STM32CubeProgrammer 的安装包里自带,也可以单独下载。装好驱动后,把 ST-Link 插上电脑,在设备管理器里应该能看到 "STMicroelectronics STLink" 设备,没有黄色感叹号就说明驱动正常。

J-Link 用户需要安装 SEGGER 的 J-Link 软件包,里面包含 GDB Server 和驱动。DAPLink 通常是免驱的,插上就能识别为 HID 设备。

3.4 安装 STM32CubeMX 并生成工程

STM32CubeMX 是 ST 官方出的图形化配置工具,用来生成芯片初始化代码。去 ST 官网下载对应系统的安装包,安装过程中会提示安装 STM32Cube 固件包,也就是 HAL 库的源码。建议至少安装你用的芯片系列对应的固件包,比如 F1 系列、F4 系列。

安装完成后,新建工程,选择你的芯片型号。以 STM32F103C8T6 为例,在搜索框输入型号,选中后进入配置界面。这里需要配置几个关键项:

  • RCC:把 HSE 设为 Crystal/Ceramic Resonator,这样外部晶振才能工作。
  • SYS:Debug 设为 Serial Wire,否则下载一次后可能锁住芯片。
  • 时钟树:根据外部晶振频率配置 PLL,让系统时钟跑到目标频率。
  • GPIO:配置一个 LED 引脚作为输出,方便验证程序是否运行。

配置完成后,在 Project Manager 里设置工程名称、路径、工具链。Toolchain/IDE 选择CMake,这样生成的工程可以直接用 CMake 构建。Code Generator 里勾选 "Generate peripheral initialization as a pair of .c/.h files",这样每个外设的初始化代码会单独成文件,结构更清晰。

点击 GENERATE CODE,CubeMX 会生成完整的工程文件,包括 CMakeLists.txt、启动文件、链接脚本、HAL 库源码和初始化代码。

3.5 配置 VSCode 工程

用 VSCode 打开 CubeMX 生成的工程目录。第一次打开时,C/C++ 插件可能会提示找不到 include 路径,满屏红色波浪线。这是因为插件还不知道你的头文件在哪里。解决办法是让 CMake 生成 compile_commands.json。

在工程根目录下打开终端,执行:

cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug

如果一切正常,build 目录下会生成 compile_commands.json。然后在 VSCode 的 settings.json 里加上:

{ "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json" }

重启 VSCode 后,红色波浪线应该就消失了,代码补全和跳转也能正常工作。

接下来配置调试。在工程根目录下新建.vscode/launch.json,内容如下:

{ "version": "0.2.0", "configurations": [ { "name": "OpenOCD Debug", "type": "cortex-debug", "request": "launch", "servertype": "openocd", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/你的工程名.elf", "device": "STM32F103C8", "configFiles": [ "interface/stlink.cfg", "target/stm32f1x.cfg" ], "svdFile": "你的SVDFile路径/STM32F103.svd", "runToEntryPoint": "main" } ] }

这里的device填你的芯片型号,configFiles里的 interface 文件根据你的调试器选择,ST-Link 用 stlink.cfg,J-Link 用 jlink.cfg,DAPLink 用 cmsis-dap.cfg。target 文件根据芯片系列选择,F1 用 stm32f1x.cfg,F4 用 stm32f4x.cfg。svdFile 是芯片的寄存器描述文件,ST 官网可以下载,配上之后调试时能看到外设寄存器的值,非常方便。

4. 编译、下载与调试的完整流程

4.1 编译工程与常见报错处理

配置完成后,在 VSCode 终端里执行:

cmake --build build

如果一切顺利,build 目录下会生成 .elf、.hex、.bin 文件。但实际第一次编译往往会遇到各种报错,我整理了几个最常见的:

报错一:找不到 arm-none-eabi-gcc。这说明工具链没加到 PATH 里,或者 VSCode 的终端环境变量没刷新。解决办法是检查系统 PATH,重启 VSCode,或者在 CMakeLists.txt 里手动指定编译器路径。

报错二:undefined reference to_exit或_sbrk。这是 newlib 的 syscall 桩函数缺失导致的。CubeMX 生成的工程通常已经包含了 syscalls.c,如果没有,需要自己添加一个,或者链接时加上--specs=nosys.specs。

报错三:region `FLASH' overflowed。这说明代码体积超过了芯片 Flash 容量。检查是否开了 Debug 优化等级(-O0),改成 -Og 或 -Os 通常能显著减小体积。另外检查是否链接了不需要的库。

报错四:multiple definition ofxxx。通常是头文件里定义了变量而不是声明,或者源文件被重复编译。检查 CMakeLists.txt 里的源文件列表是否有重复。

4.2 使用 OpenOCD 下载程序

编译成功后,把 ST-Link 和开发板连好,SWDIO、SWCLK、GND、3.3V 四根线接对。然后在终端里启动 OpenOCD:

openocd -f interface/stlink.cfg -f target/stm32f1x.cfg

如果连接正常,会看到类似这样的输出:

Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.300000 Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

看到 "hardware has 6 breakpoints" 就说明芯片识别成功了。保持 OpenOCD 运行,另开一个终端,用 telnet 连上去执行下载:

telnet localhost 4444

在 telnet 会话里执行:

reset halt flash write_image erase build/你的工程名.elf reset run

这样程序就下载进去并开始运行了。如果板子上有 LED,应该能看到它按照代码逻辑闪烁。

当然,更优雅的方式是直接用 Cortex-Debug 插件。在 VSCode 里按 F5,插件会自动启动 OpenOCD、下载程序、进入调试模式。你可以在代码里打断点、单步执行、查看变量和外设寄存器,体验和 Keil 基本一致。

4.3 调试配置的细节优化

Cortex-Debug 的 launch.json 里有几个参数值得细说。runToEntryPoint设为 "main" 可以让程序下载后自动运行到 main 函数暂停,省去手动打断点的麻烦。svdFile配上之后,调试面板里会多出一个 "Peripherals" 视图,可以实时查看 GPIO、USART、TIM 等外设的寄存器值,排查硬件问题时特别有用。

如果你用的是 J-Link,servertype 改成 "jlink",然后指定 device 和 interface 即可。J-Link 的下载速度通常比 ST-Link 快不少,特别是大工程的时候差异明显。

还有一个实用技巧:在 launch.json 里加上"preLaunchTask": "build",并配置对应的 tasks.json,这样每次按 F5 调试前会自动编译,省去手动编译的步骤。

5. 实操心得与常见问题排查

5.1 新手最容易踩的五个坑

第一个坑:SYS Debug 没配成 Serial Wire。CubeMX 里如果 SYS 的 Debug 保持默认的 Disable,生成代码后第一次下载可能成功,但之后芯片的 SWD 引脚会被复用为普通 GPIO,导致再也连不上。解决办法是下载时按住复位键,松开瞬间点击下载,或者用 ST-Link Utility 擦除芯片。所以配置 CubeMX 时一定要记得把 SYS Debug 设为 Serial Wire。

第二个坑:时钟配置错误导致串口乱码。很多人配置完时钟树后不检查实际频率,结果串口波特率对不上,打印出来全是乱码。CubeMX 的时钟树界面会实时显示各总线的频率,配置完后一定要核对一下 HCLK、PCLK1、PCLK2 的值是否符合预期。

第三个坑:OpenOCD 配置文件选错。interface 文件和 target 文件必须和实际硬件匹配。用 ST-Link V2 却选了 stlink-v3.cfg,或者用 F4 芯片却选了 stm32f1x.cfg,都会导致连接失败。报错信息通常是 "Error: open failed" 或者 "Target not examined yet"。

第四个坑:CMake 构建类型没设对。默认不指定 CMAKE_BUILD_TYPE 的话,CMake 可能用空配置,导致优化等级和调试信息都不对。Debug 模式用 -Og -g3,Release 模式用 -Os -g0,这些都要在 CMakeLists.txt 里明确设置。

第五个坑:PATH 环境变量在 VSCode 里不生效。Windows 上改了系统 PATH 后,已经打开的 VSCode 不会自动刷新环境变量。必须完全关闭 VSCode 再重新打开,或者重启电脑,新的 PATH 才会生效。

5.2 常见问题速查表

现象可能原因排查方法解决方案
编译报错找不到 gccPATH 未配置终端执行arm-none-eabi-gcc --version添加工具链路径到 PATH,重启 VSCode
代码满屏红色波浪线缺少 compile_commands.json检查 build 目录下是否有该文件执行 cmake 配置生成,并在 settings.json 中指定路径
OpenOCD 连接失败调试器驱动问题或接线错误检查设备管理器,确认 SWD 四线连接重装驱动,检查接线,确认 target 配置正确
下载后程序不运行复位方式不对或时钟配置错误用调试器查看 PC 指针位置检查时钟树配置,确认启动文件正确
调试时断点不生效优化等级过高查看编译选项Debug 模式改用 -Og,避免 -O2 以上
串口输出乱码波特率不匹配核对系统时钟和串口分频重新配置时钟树,确认波特率计算正确
Flash 下载失败芯片读保护或写保护用 ST-Link Utility 查看选项字节解除读保护,擦除全片后重新下载

5.3 提升开发效率的几个实用技巧

技巧一:用 ccache 加速编译。在 CMakeLists.txt 里加上find_program(CCACHE ccache)并设置CMAKE_C_COMPILER_LAUNCHER,第二次编译开始速度会有明显提升,特别是大工程改一个文件重新编译的时候。

技巧二:配置 VSCode 的 tasks.json 实现一键编译下载。把 cmake build 和 openocd 下载命令串成一个 task,绑定快捷键,按一下就能完成编译加下载,比在终端里敲命令快得多。

技巧三:用 STM32CubeProgrammer 的 CLI 模式批量下载。如果你要给多块板子烧录同一个固件,可以用 STM32CubeProgrammer 的命令行版本写个脚本,插上一块烧一块,效率比图形界面高很多。

技巧四:把常用的 OpenOCD 配置封装成脚本。比如写一个flash.sh,里面包含启动 OpenOCD、telnet 下载、退出的完整流程,以后只需要执行./flash.sh就能一键下载。

技巧五:善用 SVD 文件查看外设寄存器。调试的时候,与其在代码里加一堆 printf,不如直接看外设寄存器的值。Cortex-Debug 的 Peripherals 视图可以实时刷新,配合断点使用,排查硬件初始化问题非常高效。

6. 从点亮 LED 到跑通完整项目

6.1 第一个验证程序:LED 闪烁

环境搭好后,第一件事是写一个最简单的 LED 闪烁程序验证整条链路是否通畅。在 CubeMX 里配置一个 GPIO 为输出模式,生成代码后,在 main 函数的 while 循环里加上:

HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(500);

编译下载后,如果 LED 按照 500ms 的间隔闪烁,说明编译器、构建系统、调试器、下载链路全部正常。这一步虽然简单,但它是后续所有复杂项目的基础。如果 LED 不闪,就要按照上一节的排查表逐项检查,不要急着往下走。

6.2 加入串口打印调试信息

LED 验证通过后,下一步是配置串口。在 CubeMX 里使能 USART1,模式设为 Asynchronous,配置好波特率(通常 115200)。生成代码后,重定向 printf 到串口:

#include <stdio.h> int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }

然后在主循环里用 printf 打印信息。串口调试是嵌入式开发中最重要的调试手段之一,有了它,你就能在运行时输出变量值、程序状态、错误信息,比单步调试效率高得多。

6.3 集成外设驱动与项目扩展

基础链路跑通后,就可以开始集成各种外设驱动了。比如用 HAL 库驱动 OLED 屏幕、DHT11 温湿度传感器、W25Q64 SPI Flash、HC-SR04 超声波模块等等。这些驱动的集成方式大同小异:在 CubeMX 里配置对应的外设接口(I2C、SPI、GPIO、定时器),生成初始化代码,然后把驱动源码加到工程里,在 CMakeLists.txt 里添加源文件路径和头文件路径。

以 SPI Flash 为例,CubeMX 里配置好 SPI 接口后,把 W25Q64 的驱动文件放到Drivers/BSP/W25Q64/目录下,然后在 CMakeLists.txt 里加上:

target_sources(${PROJECT_NAME} PRIVATE Drivers/BSP/W25Q64/w25q64.c ) target_include_directories(${PROJECT_NAME} PRIVATE Drivers/BSP/W25Q64 )

重新 cmake 配置后,驱动就能正常编译和调用了。这种模块化的组织方式让工程结构清晰,后续添加新外设也不会乱。

6.4 版本管理与团队协作

用 VSCode 做开发的一个额外好处是 Git 集成非常方便。建议在工程根目录初始化 Git 仓库,把 build 目录加到 .gitignore 里,只提交源码和配置文件。CubeMX 生成的 .ioc 文件也要提交,这样团队成员可以随时用 CubeMX 重新生成代码。

如果团队里有人用 Keil 有人用 VSCode,可以维护两套工程文件,但源码和 HAL 库版本要保持一致。CubeMX 的 .ioc 文件是跨工具的,只要大家用同一个版本的 CubeMX 和固件包,生成的初始化代码就是一致的。

7. 一些个人体会

这套 VSCode + GCC + OpenOCD 的方案我从几年前开始用,中间也踩过不少坑,但用顺之后确实回不去了。最直观的感受是写代码的效率提升明显,代码补全、跳转、重构这些在 Keil 里很难用的功能,在 VSCode 里都是标配。调试体验也不差,Cortex-Debug 配合 SVD 文件看寄存器,比 Keil 的界面还直观一些。

当然这套方案也不是没有缺点。初次搭建确实比装个 Keil 麻烦,涉及的工具多,任何一个环节出问题都要花时间排查。但这个过程本身也是学习的机会,搞明白编译器、构建系统、调试器之间的关系之后,对整个嵌入式开发流程的理解会更深入。

最后分享一个小技巧:如果你在 Windows 上同时装了 MSYS2 和官方 ARM 工具链,注意 PATH 里的顺序。两个工具链的 gcc 名字一样,PATH 里靠前的会优先生效。建议只保留一个,避免版本混乱。另外,OpenOCD 的配置文件路径在不同安装方式下可能不同,用openocd -f指定文件时最好用绝对路径,或者把配置文件目录加到环境变量里,省得每次都要找路径。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 2:16:27

第六章 TypeScript MCP Server:独立综合项目与能力验收

系列文章目录 第一章 TypeScript MCP Server&#xff1a;从零到一&#xff08;已更新&#xff09;第二章 TypeScript MCP Server&#xff1a;提取业务逻辑与建立自动化测试&#xff08;已更新&#xff09;第三章 TypeScript MCP Server&#xff1a;分析 package.json 与处理文…

作者头像 李华
网站建设 2026/10/5 2:16:23

WeMod-Patcher:5 分钟解除 WeMod 2 小时限制,无需订阅付费

WeMod-Patcher&#xff1a;5 分钟解除 WeMod 2 小时限制&#xff0c;无需订阅付费 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer WeMod 免费版每天…

作者头像 李华
网站建设 2026/10/5 2:10:47

网络安全应急处置流程图落地指南:从预案到实战的检查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华