STM32F103驱动AD7124实战:从SPI时序到多通道高精度采集

简介:这份资源是面向STM32F103平台、针对AD7124高精度ADC芯片的完整驱动代码工程,适合正在调试该芯片却苦于缺少参考的嵌入式开发者与新手。作者基于Keil5环境,采用模拟SPI方式实现通信,测试代码聚焦6通道、双极性、全功率采样并启用外部参考电压,未堆砌冗余功能,工程已全部调通,可直接作为可靠起点。压缩包共215个文件,约6.18MB,包含35个c源文件、38个h头文件及大量编译中间产物(d、o、crf等),另有uvprojx工程文件、map映射文件、hex烧录文件与sct分散加载配置,覆盖从源码到可执行输出的完整链路。目前已有6215人学习下载,热度较高。读者可借此省去从零摸索寄存器时序与采样配置的时间,快速理解AD7124在STM32F103上的驱动框架与排错思路,降低调试门槛。

1. 拿到一份「已全部调通」的 AD7124 驱动,先别急着烧录

手上这份 AD7124 驱动代码,基于 STM32F103,标题写着「已全部调通」。做过高精度采集的人都知道,这句话含金量不低——AD7124 这颗 24 位 Σ-Δ ADC,寄存器多、模式多、时序讲究,SPI 读回来全是 0xFFFFFF 或者 0x000000 是常态。它解决的是小信号、高分辨率、多通道差分采集的场景,比如称重、热电偶、RTD、压力桥路。适合谁?适合已经能跑通 STM32 基础 SPI、但被 AD7124 的配置寄存器和校准流程卡住的嵌入式工程师。但「调通」是别人的板子调通,换到你的硬件上,第一件事不是烧录,而是先搞清楚这份代码的配置假设和你的电路差在哪。

2. AD7124 驱动骨架拆解:SPI 时序、寄存器映射与状态机

2.1 为什么 AD7124 不能当普通 SPI 从机用

很多人第一反应是拿 STM32 的硬件 SPI 直接怼上去,结果读 ID 都读不对。AD7124 的 SPI 有几个硬性约束:CPOL=1、CPHA=1,也就是空闲时钟高电平、第二个边沿采样。STM32F103 的 SPI1 配成SPI_CPOL_High | SPI_CPHA_2Edge才对得上。另外它的通信寄存器是 8 位,但数据寄存器是 24 位,读写要按「先写通信寄存器选目标寄存器,再连续读/写 N 字节」的节奏走。如果你用 HAL 的HAL_SPI_TransmitReceive一次性发 4 字节,大概率错位。

常见做法是封装两个底层函数:一个写通信寄存器,一个读写数据。我一般会把 CS 拉低、发通信字、再操作数据、最后拉高 CS 这一整套放在一个函数里,避免中间被中断打断导致帧错位。

// AD7124 底层读写:通信寄存器 + 数据寄存器 // hspi: 已配置为 CPOL=1, CPHA=1, 软件 NSS // addr: 寄存器地址(0x00~0x3F), rw: 0写 1读, len: 数据字节数(1~3) uint8_t AD7124_ReadReg(SPI_HandleTypeDef *hspi, uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t comm = (addr << 2) | 0x40; // bit7=0 写通信寄存器, bit6=1 读操作 uint8_t dummy = 0xFF; HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi, &comm, 1, 100); // 先写通信寄存器 for (uint8_t i = 0; i < len; i++) { HAL_SPI_TransmitReceive(hspi, &dummy, &buf[i], 1, 100); // 逐字节读 } HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_SET); return 0; }

逻辑说明:通信寄存器 bit7 固定为 0(写通信寄存器),bit6 是读写标志,bit5~0 是寄存器地址。读操作时 bit6 置 1。参数len根据目标寄存器决定——状态寄存器 1 字节,配置寄存器 2 字节,数据寄存器 3 字节。注意每次操作都要完整拉 CS,不能中途松手。

2.2 寄存器初始化顺序:别跳步,跳了就等着读全 0

AD7124 上电后必须按固定顺序配置,顺序错了它不报错,只是默默给你垃圾数据。这份驱动的初始化流程大致是:复位 → 读 ID 确认通信 → 配置各通道 → 配置 ADC 控制寄存器 → 配置滤波器 → 使能通道 → 启动转换。

复位有两种:硬件复位(拉 RESET 引脚)和软件复位(连续写 64 个 1)。代码里一般用软件复位,省一个 IO。复位后所有寄存器回默认值,此时读 ID 寄存器应该得到 0x14(AD7124-8)或 0x16(AD7124-4),这个值对不上,后面全白搭。

// 软件复位 + ID 校验 void AD7124_Reset(SPI_HandleTypeDef *hspi) { uint8_t tx[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_RESET); for (int i = 0; i < 8; i++) { HAL_SPI_Transmit(hspi, tx, 8, 100); // 连续 64 个 1 } HAL_GPIO_WritePin(ADC_CS_PORT, ADC_CS_PIN, GPIO_PIN_SET); HAL_Delay(1); // 等内部振荡器稳定 } uint8_t AD7124_CheckID(SPI_HandleTypeDef *hspi) { uint8_t id = 0; AD7124_ReadReg(hspi, 0x05, &id, 1); // ID 寄存器地址 0x05 return id; // 期望 0x14 或 0x16 }

参数说明:复位后至少延时 1ms 再通信,内部振荡器起振需要时间。ID 寄存器低 4 位是版本,高 4 位是型号,判断时用(id & 0xF0)更稳。

2.3 通道与滤波器配置:决定你读到的是不是有效值

AD7124 有 8 个配置寄存器(AD7124-8),每个通道可以绑定一个配置。配置寄存器里最关键的是增益、基准源选择、输入缓冲。滤波器寄存器决定输出数据率和建立时间。很多人读出来的值跳得厉害,八成是滤波器没配对——Sinc4 滤波在 50Hz/60Hz 工频抑制上表现好,但建立时间长;单周期滤波快,但噪声大。

参数典型值影响
增益1~128小信号必须放大,但放大后满量程缩小
基准源内部 2.5V / 外部 REFIN内部基准温漂大,精密场合用外部
滤波类型Sinc4 / 单周期Sinc4 抑工频,单周期快
输出速率10SPS~19200SPS越低越稳,越高越跳
建立时间与滤波和速率相关切换通道后要等建立完再读

配置通道时,先写配置寄存器,再写通道寄存器把通道映射到配置,最后使能通道。顺序反了,通道使能时配置还没生效,读出来就是默认增益 1、内部基准,小信号直接淹在噪声里。

3. 从零跑通采集:STM32F103 工程搭建与实测步骤

3.1 工程文件组织与 CubeMX 关键配置

拿到代码包,先别一股脑塞进现有工程。我一般会新建一个ad7124_driver分组,把驱动文件(ad7124.c/ad7124.h)和板级支持(ad7124_port.c)分开。驱动层不碰 HAL 具体外设,只依赖几个函数指针或宏,这样换 MCU 只改 port 层。

CubeMX 里 SPI1 配置:Mode 选 Full-Duplex Master,Data Size 8 Bits,CPOL High,CPHA 2Edge,NSS 选 Software,Prescaler 先设 64(约 1.1MHz),AD7124 最高支持 5MHz,但 F103 走线长时降速更稳。GPIO 里把 CS 和 RESET 配成推挽输出,初始电平都拉高。

// ad7124_port.h 里做硬件抽象 #define ADC_CS_PORT GPIOA #define ADC_CS_PIN GPIO_PIN_4 #define ADC_RESET_PORT GPIOA #define ADC_RESET_PIN GPIO_PIN_3 #define ADC_SPI_HANDLE hspi1

这样驱动里只调AD7124_ReadReg(&ADC_SPI_HANDLE, ...),换板子只改这个头文件。

3.2 单通道连续采集的完整调用链

跑通的最小闭环是:初始化 → 配通道 0 → 启动连续转换 → 等 RDY 引脚或查状态寄存器 → 读数据 → 转电压。RDY 引脚接法看你的板子,没接就用状态寄存器的 RDY 位轮询。

void AD7124_SetupChannel0(void) { uint8_t cfg[2] = {0x00, 0x00}; // 配置寄存器: 增益1, 内部基准, 缓冲开 AD7124_WriteReg(&ADC_SPI_HANDLE, 0x19, cfg, 2); // CFG0 地址 0x19 uint8_t ch[2] = {0x01, 0x80}; // 通道0: 使能, 接 AIN0/AIN1 AD7124_WriteReg(&ADC_SPI_HANDLE, 0x09, ch, 2); // CH0 地址 0x09 uint8_t filt[3] = {0x00, 0x00, 0x60}; // 滤波器: Sinc4, 输出速率约 10SPS AD7124_WriteReg(&ADC_SPI_HANDLE, 0x21, filt, 3); // FILT0 地址 0x21 uint8_t adc_ctrl[2] = {0x00, 0x0C}; // 连续转换模式, 内部基准使能 AD7124_WriteReg(&ADC_SPI_HANDLE, 0x01, adc_ctrl, 2); // ADC_CTRL 地址 0x01 } int32_t AD7124_ReadData(void) { uint8_t raw[3] = {0}; uint8_t status = 0; do { AD7124_ReadReg(&ADC_SPI_HANDLE, 0x00, &status, 1); // 状态寄存器 } while (status & 0x80); // 等 RDY 位变低 AD7124_ReadReg(&ADC_SPI_HANDLE, 0x02, raw, 3); // 数据寄存器 0x02 int32_t code = ((int32_t)raw[0] << 16) | ((int32_t)raw[1] << 8) | raw[2]; if (code & 0x800000) code |= 0xFF000000; // 24 位补码转 32 位 return code; }

逻辑说明:状态寄存器 bit7 是 RDY,低电平有效,所以while (status & 0x80)是等它变 0。数据寄存器是 24 位,读回来要拼成 int32 并做符号扩展。电压换算:V = code * VREF / (增益 * 2^23),内部基准 2.5V、增益 1 时,满量程约 ±2.5V。

3.3 用已知电压验证:别信「能读数」,要信「读得准」

跑通不代表读准。我一般会拿一个基准电压源或者分压后的电池电压做验证。比如用两节干电池分压到 1.0V 左右,接 AIN0/AIN1,读回来换算成电压,和万用表对比。偏差在几个 LSB 以内算正常,偏差几百 mV 就是配置错了。

验证步骤:先短接 AIN0 和 AIN1,读出来应该接近 0(有几十个 LSB 的失调正常);再接入已知电压,看线性度。如果短接都读几百 mV,检查增益和基准配置;如果读数乱跳,检查滤波器和 SPI 速率。

提示:AD7124 的模拟输入范围是±VREF/增益,差分输入时 AIN0 和 AIN1 都要在允许的共模范围内,超了读出来是满量程或负满量程,不是真实值。

4. 避坑与排查:调 AD7124 最容易翻车的五个地方

4.1 读回来全是 0xFFFFFF 或 0x000000

现象:初始化后读数据寄存器,永远是满量程或零。原因:SPI 模式不对,或者通信寄存器写错导致根本没选中数据寄存器。解决:先用示波器看 CS、SCLK、MOSI 波形,确认 CPOL/CPHA;再单独读 ID 寄存器,ID 不对就先修通信,别往下走。

4.2 读数跳得厉害,像随机数

现象:电压值上下跳几百个 LSB。原因:滤波器输出速率设太高,或者基准源用了内部基准且没使能缓冲。解决:把输出速率降到 10SPS 或更低,滤波器选 Sinc4;基准改外部 REFIN,并在配置寄存器里打开基准缓冲。

4.3 切换通道后第一笔数据是错的

现象:多通道轮询时,刚切过去的通道读出来是上一个通道的值。原因:Σ-Δ ADC 切换通道后有建立时间,没等建立完就读。解决:切换通道后丢弃第一笔或前两笔数据,或者查状态寄存器的 RDY 位等它真正更新。

4.4 校准后反而更不准

现象:跑了内部零刻度校准和满刻度校准,结果偏差更大。原因:校准时的输入条件不对,零刻度校准要求输入短接,满刻度校准要求输入接近满量程。解决:校准前先把输入接到对应电平,校准完再切回正常测量;或者干脆用系统校准,在已知信号下算修正系数。

4.5 长时间运行后数据漂移

现象:跑几小时后读数慢慢偏。原因:内部基准温漂,或者芯片自热。解决:换外部基准,降低激励电流,PCB 上模拟部分远离发热源。AD7124 本身功耗不高,但配置成高增益、高速度时功耗上去了,自热会影响精度。

5. 进阶:把这份驱动改成多通道轮询 + 软件滤波的稳定采集

单通道跑通只是起点,实际项目多半是多通道轮询。AD7124 支持序列器模式,可以自动按顺序扫多个通道,但配置起来寄存器操作多,调试时容易乱。我一般先用软件轮询:手动切通道、等建立、读数据,逻辑清晰,出问题好定位。

多通道的关键是维护一个通道-配置映射表,每个通道有自己的增益和滤波需求。比如通道 0 接称重传感器要增益 128,通道 1 接 RTD 要增益 16,那就配两个配置寄存器,通道寄存器分别指向它们。切换时先写通道寄存器,再等建立时间,再读。

typedef struct { uint8_t ch_reg_val; // 通道寄存器值 uint8_t cfg_idx; // 绑定的配置索引 uint8_t settle_cnt; // 需要丢弃的样本数 } AD7124_ChannelMap; AD7124_ChannelMap ch_map[2] = { {0x01, 0, 2}, // 通道0, 配置0, 丢2笔 {0x02, 1, 2}, // 通道1, 配置1, 丢2笔 }; int32_t AD7124_ScanChannel(uint8_t idx) { AD7124_WriteReg(&ADC_SPI_HANDLE, 0x09, &ch_map[idx].ch_reg_val, 2); for (int i = 0; i < ch_map[idx].settle_cnt; i++) { AD7124_ReadData(); // 丢弃建立期样本 } return AD7124_ReadData(); }

软件滤波方面,我习惯在驱动上层加一个滑动平均或中值滤波。AD7124 本身输出速率低的时候噪声已经很小,但工频干扰残留还是有的。滑动平均窗口取 8 或 16,对缓变信号效果好;如果信号有突变,中值滤波更合适。别在驱动层做滤波,放应用层,方便调参。

验证方法:拿一个标准信号源,或者用高精度万用表对比,跑 24 小时看漂移。我一般会记录每小时的平均值和标准差,标准差突然变大就是有干扰进来了。从那以后我每次改滤波器参数,都强制先跑一遍短接和满量程两点验证,确认没翻车再上真实信号。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

调出最佳回答:PIIP-LLaVA生成参数temperature与top_p调优技巧清单

调出最佳回答:PIIP-LLaVA生成参数temperature与top_p调优技巧清单

【免费下载链接】PIIP-LLaVA_ConvNeXt-B_CLIP-L_1024-336_7B 项目地址&#xff1a; https://ai.gitcode.com/OpenGVLab/PIIP-LLaVA_ConvNeXt-B_CLIP-L_1024-336_7B 点击查看 免费下载 PIIP-LLaVA_ConvNeXt-B_CLIP-L_1024-336_7B 是一款多模态视觉指令模型&#xff1a;它用 Con…

2026/10/20 10:47:40 阅读更多 →
3 步搞定 emuMMC 备份与恢复:Atmosphere 保姆级指南

3 步搞定 emuMMC 备份与恢复:Atmosphere 保姆级指南

3 步搞定 emuMMC 备份与恢复&#xff1a;Atmosphere 保姆级指南 【免费下载链接】Atmosphere Atmosphre is a work-in-progress customized firmware for the Nintendo Switch. 项目地址: https://gitcode.com/GitHub_Trending/at/Atmosphere 有没有遇到过误断电或系统更…

2026/10/20 10:47:40 阅读更多 →
MQTT实战 - QoS 0-1-2 到底怎么选?一张图看懂(四)

MQTT实战 - QoS 0-1-2 到底怎么选?一张图看懂(四)

我是做物联网后端的老兵&#xff0c;8 年 .NET 开发&#xff0c;现在带一支研发团队做 .NET AIoT 架构。这个系列讲我真实项目里踩过的坑——不是抄来的入门教程。 QoS 这三个字母&#xff0c;可能是 MQTT 里被误解最深的东西。新手要么一句「当然全选 QoS 2&#xff0c;最可靠…

2026/10/20 10:47:40 阅读更多 →

最新新闻

VS2019快速集成wxWidgets二进制包实战指南

VS2019快速集成wxWidgets二进制包实战指南

1. 项目概述&#xff1a;为什么“直接使用二进制文件”是wxWidgets开发中最常被卡住的第一关 在Visual Studio 2019环境下配置wxWidgets&#xff0c;绝大多数人踩进的第一个坑&#xff0c;不是代码写错&#xff0c;也不是逻辑出问题&#xff0c;而是——连编译都过不去。你下载…

2026/10/20 11:33:30 阅读更多 →
AI芯片设计黑盒困局:从不可解释到可控黑盒的工程实践

AI芯片设计黑盒困局:从不可解释到可控黑盒的工程实践

“AI 在芯片领域依然是个黑盒。”这句话不是我从某篇报告里抄来的&#xff0c;是我在上一个流片项目的复盘会上&#xff0c;被自己的算法同事一句一句追问出来的。当时我们上线了一个辅助布局优化的AI模型&#xff0c;指标提升很漂亮&#xff0c;可一旦被问到“为什么这几百个标…

2026/10/20 11:33:30 阅读更多 →
HTC Vive OpenVR 配置避坑指南:SDK 1.25.4 + SteamVR 1.26.4 黄金组合

HTC Vive OpenVR 配置避坑指南:SDK 1.25.4 + SteamVR 1.26.4 黄金组合

简介&#xff1a;本资源是面向VR开发初学者与进阶者的OpenVR实战入门项目&#xff0c;聚焦HTC Vive设备的API调用与Tracker扩展应用&#xff0c;解决开发者在跨平台VR交互开发中接口适配难、追踪数据解析复杂等实际问题。压缩包共9个文件&#xff0c;含4个核心Python脚本&#…

2026/10/20 11:33:30 阅读更多 →
Llama 3 精读:FP8 量化与长上下文实测经验分享

Llama 3 精读:FP8 量化与长上下文实测经验分享

1. 为什么值得花时间精读 Llama 3Llama 3 发布之后&#xff0c;我身边不少做推理部署和模型微调的朋友都在第一时间拉取了权重做实测。大家最关心的其实不是榜单上的分数&#xff0c;而是两个非常实际的问题&#xff1a;第一&#xff0c;这个模型在FP8 量化之后精度到底掉多少&…

2026/10/20 11:33:30 阅读更多 →
腾讯广告回收业务推广类目梳理:可投品类、禁投品类及资质要点

腾讯广告回收业务推广类目梳理:可投品类、禁投品类及资质要点

前言很多再生资源、回收行业的从业者&#xff0c;会选择腾讯广告&#xff08;朋友圈、视频号、腾讯新闻等流量位&#xff09;来收集 B 端企业或 C 端散户的业务咨询信息。但回收类目细分多、监管差异大&#xff0c;不少商家开户后遇到广告拒审、计划下架、账户限流等问题。多数…

2026/10/20 11:33:30 阅读更多 →
专科生论文降AI率实战指南:九款降AIGC工具测评与避坑要点

专科生论文降AI率实战指南:九款降AIGC工具测评与避坑要点

最近后台问得最多的不是论文怎么写&#xff0c;而是“论文写完了AI率太高怎么办”。很多专科生现在交课程论文、毕业设计、实习报告之前&#xff0c;都要先跑一遍AIGC检测&#xff0c;结果一出来就慌了&#xff1a;明明是自己查阅资料写的初稿&#xff0c;只是用AI帮忙润了润色…

2026/10/20 11:32:30 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/20 9:50:45 阅读更多 →