news 2026/11/3 1:07:27

基于MAX14431BAEE+与PIC18LF45K42的SPI隔离通信设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于MAX14431BAEE+与PIC18LF45K42的SPI隔离通信设计

如果你干过工业控制类的嵌入式项目,大概率碰到过这种局面:一块控制板上的 MCU 要跟另一类电源域里的外设通信,两边既不是同一套电源,也不是同一个地,直接拉线过去要么偶尔丢数据,要么整板复位,严重的时候直接把引脚甚至主控烧掉。这次要聊的项目就是围绕这个痛点做的——用 MAX14431BAEE+ 数字隔离器和 PIC18LF45K42 单片机,把工业和嵌入式应用里的 SPI 通信链路完整地隔离出来。翻译成大白话就是:一块 PIC18LF45K42 做主控,通过 MAX14431BAEE+ 把 SPI 的四根信号线安全地搬到隔离另一侧,另一侧再接一片 PIC18LF45K42 或其它 SPI 外设,两边电源独立、地分开,通信照常跑。

这篇文章不会只贴芯片手册里能搜到的东西,而是按我实际画板、写固件、上电调试的顺序,把为什么选这个方案、通道方向怎么分配、时序余量怎么算、PCB 上怎么落隔离带、固件初始化怎么写、最后又踩了哪些坑,一条线讲清楚。适合做 PLC 模块、电机驱动器、传感器采集板、电池包监控这类场景的人参考,也适合刚接触数字隔离器、想知道 SPI 隔离到底怎么做的嵌入式工程师。哪怕你现在还没定芯片,这套分析思路也能帮你少走不少弯路。

1. 先把需求拆开看:为什么要给 SPI 做隔离

1.1 工业现场里的真实冲突

在工业设备里,SPI 大概是板内用得最频繁的串行总线。ADC、DAC、Flash、编码器接口、数字量 I/O 扩展芯片,全都能用 SPI 挂起来。问题是,这些外设不一定都待在同一个电源区域里。

举个例子。变频器主控板在低压侧,IGBT 驱动板和电流采样板却在功率侧;PLC 的 CPU 板跑 3.3V,现场端子那边却是 24V 继电器回路;电池管理系统里电芯采集板直接挂在几十伏甚至更高的电池串上。这些区域之间唯一能接受的关联,是数据信号,而不是电流路径。如果直接拉 SPI 线跨过去,两侧地电位稍有差异,信号线上就会有不希望的电流流动,轻则波形畸变,重则把 SPI 引脚打坏。

传统做法用光耦,一路信号一颗光耦,四线 SPI 至少四颗,还要考虑电流传输比、CTR 衰减、速度上不去的问题。后来出现了基于电容隔离或磁隔离的数字隔离器,把“隔离”这件事从分立元件整合成一颗芯片,速度和可靠性都改善很多。MAX14431BAEE+ 就是这类数字隔离器里的一个典型选型。

1.2 光耦、容耦、磁耦:方案选型到底怎么比

隔离方案不是越贵越好,得先看使用场景。光耦适合低速、低频、几百 kHz 以内的信号,胜在便宜、电路简单,但一致性和随温度漂移的问题是硬伤,而且同一颗料用久了 CTR 会下降,维护性不好。脉冲变压器体积大、只能过交流信号,不能直接传直流电平,需要额外调制电路,一般不做首选。

数字隔离器内部用的是电容耦合或者磁耦合,本质是把输入侧的电气信号先调制再穿过隔离屏障,输出侧解调恢复。对使用者来说,它看起来就是一个逻辑门电路,输入高电平,输出高电平;输入低电平,输出低电平,不需要像光耦那样计算驱动电流和输出上拉。更重要的是传播延迟稳定、通道间偏差小、CMTI(共模瞬态抑制)高,特别适合 SPI 这种需要边沿采样的同步串行总线。

MAX14431BAEE+ 这个系列的特点,是做成多通道集成封装,一个片子就把 SPI 四根线全包进去,不需要再搭外围调理电路。三个通道从 A 侧跑到 B 侧,一个通道从 B 侧跑回 A 侧,正好对应 SPI 的 SCLK、MOSI、CS 是主到从方向,MISO 是回到主方向。这个 3+1 的通道结构,基本就是冲着 SPI 隔离去设计的。

1.3 为什么是 MAX14431BAEE+,而不是普通四通道数字隔离器

普通四通道隔离器也能做 SPI,但很多四通道隔离器是 4 个同向通道,比如四个 A→B。你要是接了 MISO,方向就反了,根本不能用。所以选型时第一步不是看隔离电压多高,而是数清楚通道方向。

MAX14431BAEE+ 的优势在于,它把 SPI 里最别扭的那条回程通道(MISO)也做进同一个封装了,整体走线短,信号完整性好控制。再加上它本身是数字隔离器,不是 SPI 专用协议芯片,意味着 SPI 的帧格式、命令字、速率完全由两边 MCU 的 SPI 外设决定,隔离器只负责“无损过墙”,不受协议层限制。这个特性比某些带协议解读的专用隔离方案更灵活,也更容易换颗主控就复用。

2. 系统架构与关键参数设计

2.1 信号链路和通道方向

整个系统的信号链路是这样一个流向:控制侧的信号由主控 MCU 的 SPI 外设产生,经过隔离器隔离后到达现场侧外设,现场侧外设的 MISO 输出再经过隔离器反向通道回到主控的 SDI 引脚。

以 MAX14431BAEE+ 的 3+1 通道构型为例,三个正向通道分别分配给:

  • SCLK:主控发送时钟到现场侧。
  • MOSI/SDO:主控发送数据到现场侧。
  • CS:主控的片选信号,低有效。

一个反向通道分配给:

  • MISO/SDI:现场侧外设的数据返回主控。

如果现场侧挂的不是 MCU,而是 W25Q64 Flash 或者 ADC 等纯外设,信号结构完全一样,只需要把隔离器输出侧接到外设的对应引脚。如果挂的是另一颗 PIC18LF45K42,且配置成 SPI 从模式,那么这张表照抄就行:现场侧 MCU 的 SDO 接到隔离器反向通道的输入端,隔离器反向通道的输出端回到主控的 SDI。

这里有一个容易踩的坑:有的隔离器输出默认是三态,需要使能脚 EN 拉高才输出。如果你把 EN 悬空或通过默认低电平的 GPIO 控制,调试时会发现从机完全收不到波形,而主机侧的信号又是正常的。这问题极有迷惑性,第一反应通常是怀疑 SPI 寄存器配置,但真相是隔离器根本没把信号放出来。

2.2 三块电源与两套地

隔离系统里至少有三套电源概念,真正要分开的是两套地。主控侧电源 VДД1 和地 GND1,现场侧电源 VДД2 和地 GND2,隔离器自身还需要在两侧分别供电。

以 MAX14431BAEE+ 这类数字隔离器为例,A 侧供电从上侧电源取,B 侧供电从现场侧电源取。两侧电源电压可以不同,比如主控侧 3.3V,现场侧也是 3.3V,或现场侧是 5V 系统,只要电平在隔离器允许范围内就行。这个特性非常关键,因为很多隔离应用不只隔离“地”,还顺手做了电平转换,省掉一级电平转换芯片。

实际电路里,隔离器两侧的电源去耦必须分开,不能两个 VCC 引脚共用一个电容网络。每个电源引脚附近至少放一个 100nF 和一个 1uF 电容,电容要尽量靠近电源引脚。地也一样,GND1 和 GND2 在 PCB 上要明确分开,不要在一个区域里混着铺铜。

2.3 把 SPI 时序余量算明白

SPI 是同步串行协议,主机在 SCLK 的某个边沿发送数据,在另一个边沿采样数据。引入隔离器后,所有的信号都被延迟了几十纳秒,虽然看起来不多,但 SPI 时钟高或低电平的半周期可能才几百纳秒,延迟会造成采样点偏移,甚至导致数据位错。

以系统时钟 32MHz、SPI 时钟目标 4MHz 为例:

SPI_CLK = Fosc / (2 * (SPixBAUD + 1)) SPixBAUD = (32MHz / (2 * 4MHz)) - 1 = 3

也就是说,把 SPIxBAUD 寄存器设为 3,主控输出约 4MHz 的 SCLK,周期约为 250ns,半周期约为 125ns。如果隔离器传播延迟典型值是几十 ns,加上 PCB 走线延迟,125ns 的半周期依然够用。但如果把速度提到 10MHz,半周期只剩 50ns,此时隔离器的延迟接近半个采样窗口,再考虑通道间 skew,很容易出现采样边缘落在数据跳变附近的情况。

所以我的建议是:在隔离场景里,SPI 时钟先按 1MHz 到 4MHz 之间调试,确认波形稳定后再慢慢往上提。千万不要一上来就按非隔离系统惯用的 8MHz 甚至 12MHz 设计,那样大概率会怀疑人生。

3. 硬件实操:连接、布局与 PCB 细节

3.1 MAX14431BAEE+ 的典型接法

硬件上的连接表可以做成这样,照着画就不会乱:

信号主控 PIC18LF45K42MAX14431BAEE+ 通道现场侧外设/PIC18LF45K42
SCLKSPI 时钟输出引脚A侧输入到B侧输出SCLK 输入
MOSISPI 数据输出A侧输入到B侧输出SDI/MOSI 输入
CSGPIO 片选输出A侧输入到B侧输出CS 输入
MISOSPI 数据输入B侧输入到A侧输出SDO/MISO 输出

这个接法的核心是所有信号都单向通过隔离器,没有任何“双向 IO”的烦恼。数字隔离器不像电平转换器那样能自动判断方向,它内部每个通道方向是固定的,所以接反了不会自适应,只能靠人工确认通道方向。

在具体 PCB 上,尽量让信号线从隔离器两侧的引出脚自然延展。A 侧信号线从主控那边走过来,B 侧信号线走向现场侧外设,中间不要绕圈,不要在隔离器正下方布置走线,那样既影响爬电距离,也容易在不注意时形成天线效应。

3.2 让 PIC18LF45K42 跑起 SPI:引脚和初始化

PIC18LF45K42 是 Microchip 的 8 位低功耗 MCU,最大特色是把不少常用外设做成了 CIP(Core Independent Peripherals,外设独立于内核运行)。SPI 模块在这里可以独立工作,主模式、从模式、FIFO、中断标志位都全。它的工作电压范围比较宽,低电压版本(带 LF 的料号)适合电池或现场电源供电的场景。

用这颗芯片最省事的办法是借助 MPLAB Code Configurator(MCC)。新建工程后在 Device Resources 里找到 SPI1,配置为主模式,数据宽度 8 位,时钟极性 CPOL=0,相位 CPHA=0,波特率 4MHz,之后 MCC 会自动配置引脚方向和寄存器。生成的初始化代码大致是:

void SPI1_Initialize(void) { // 注意:不同版本的 MCC 生成的寄存器名可能略有差异, // 以实际工程里的头文件为准 SPI1CON0 = 0x00; SPI1CON1 = 0x00; SPI1BAUD = 0x03; // 32MHz系统时钟下约4MHz SPI时钟 SPI1CON0bits.SPIEN = 1; // 使能 SPI 模块 }

如果不想用 MCC,手写寄存器也完全可行,先关 SPI 使能,把模式配置好,再写波特率寄存器,最后再把 SPIEN 拉高。手动配置时最容易漏掉的是时钟极性相位那几位,建议对照数据手册的 SPI 模式表逐位核对。

CS 片选一般不用 SPI 外设自带的自动片选,而是用普通 GPIO 手动控制,这样在调试时最灵活。PIC18LF45K42 的端口每个引脚都能独立配置方向,初始化时把片选引脚设成输出并默认拉高,通信时先拉低,等所有字节传完再拉高。

3.3 隔离区的 PCB 落地技巧

PCB 上真正决定隔离方案是否合格的关键,不是画几根线好看,而是爬电距离够不够。数字隔离器通过封装内部的绝缘层实现电气隔离,但封装外面、PCB 表面还是有空气路径。如果两边的引脚走线靠得太近,高压瞬态情况下会在印制板表面发生爬电甚至打火。

常规做法是先在隔离器下方投影区域里清空底层铺铜,再沿着隔离器长边方向开一条槽。槽的宽度取决于隔离等级,常见的增强型隔离要求爬电距离在 8mm 级别,普通基本绝缘和加强绝缘的要求会低一些,但保守起见把开槽宽度做到 2mm 以上,隔离带两侧至少留出 4mm 净空,是很多工业板卡的默认做法。具体的数值最终要以产品要过的安规标准为准。

去耦电容的位置也要讲究。隔离器两侧电源引脚各放一个 100nF 陶瓷电容,电容的地端分别接到自己侧的地,不要跨接。如果两侧地之间因为布线被意外连接,隔离就白做了。

4. 固件配置与通信验证

4.1 主控侧 SPI 初始化要点

主控侧 PIC18LF45K42 的初始化分三步:系统时钟、引脚方向、SPI 外设。

系统时钟频率先定下来,如果是用内部振荡器,通常是 HFINTOSC 倍频后得到 32MHz 或 64MHz。K42 系列内部振荡器精度对 SPI 来说够用,但如果现场侧外设有严格时钟要求,建议用外部晶振或者检查两边是否有偏差。SPI 是同步协议,时钟由主机给出,从机不需要提供时钟,所以主从两颗芯片用不同振荡器源问题不大,关键是主机输出频率要稳定。

引脚方向这块说一个实践经验:K42 的引脚复用功能很多,同一引脚可能既是 SPI 时钟,又是其它外设的输入。如果用 MCC,它会自动在 PPS(Peripheral Pin Select,外设引脚选择)里配好映射,但手动配置时你必须在 PPS 寄存器里把 SPI 功能绑定到具体引脚,否则外设不会输出到物理引脚。别问我是怎么知道的,问就是第一次上电测了半小时发现 SCLK 引脚一直是高电平。

SPI 初始化本身不复杂,代码量很少。关键是把模式 0 的时钟极性和相位确认对,因为现场侧器件如果也是 PIC18LF45K42,它从模式读取数据默认就是在主机发送边沿采样。只要两边都配成模式 0,数据就能对齐。

4.2 从机侧怎么配合

现场侧如果用的是一颗 PIC18LF45K42,那就把它配置成 SPI 从模式。从模式不需要配置波特率,因为时钟完全由主机提供,但必须确保 SPI 模块在主机片选信号拉低之前已经使能。

从机初始化代码的骨架是这样:

void SPI1_Slave_Initialize(void) { SPI1CON0 = 0x00; SPI1CON1 = 0x00; // SPI 从模式,模式0 SPI1CON0bits.SPIEN = 1; }

从机收到数据的标志位可以轮询,也可以开中断。工业现场一般建议用中断,这样 MCU 不用一直忙等。需要注意的是,SPI 从模式下如果主机发了数据但从机没来得及读走,FIFO 满之后再来数据就会丢,调试压力测试时偶尔出现的个别字节错误,往往就是这个原因。

4.3 通信自测方法

做完整系统前,最推荐的自测是“回环测试”。让现场侧从机把接收到的字节原样返回,主控发一组数据再读回,比较是否一致。这样测一遍下来,链路里的所有方向都覆盖了。

主控侧测试逻辑类似这样:

uint8_t tx_data[6] = {0xA5, 0x5A, 0x01, 0x02, 0x80, 0x00}; uint8_t rx_data[6] = {0}; for (uint8_t i = 0; i < 6; i++) { SPI1BUF = tx_data[i]; // 发送 while (!PIR2bits.SPI1RXIF); // 等待接收完成 rx_data[i] = SPI1BUF; // 读回 }

把读回数组和发送数组逐字节比较,完全一致说明隔离器的正向通道和反向通道都正常。如果读回全 0,优先查 MISO 方向;如果读回的数据是错位,比如发 0xA5 读回 0x4B,大概率是 SPI 模式 0 和模式 3 接混了。

再进一步,可以在主控发完一帧后,看现场侧从机是否确实收到了正确字节。用调试器去读从机 SRAM 里的接收缓冲区,或者放一串 LED 指示,都能快速判断整条隔离链路是否真的工作,而不只是主控自己跟自己玩。

5. 调试实录与问题速查

5.1 我踩过的坑:按症状排查

实际调试过程中遇到的问题,比预想的多得多。整理一个速查表,方便直接排查:

症状可能原因排查手段
读回全 0MISO 方向接反,隔离器 B 到 A 通道没通用示波器点测 MISO 隔离两侧
通信时好时坏SPI 采样点太靠近数据跳变沿降低速率,或调整 CPOL/CPHA
上电瞬间从机误动作CS 在电源建立前被拉低加 RC 延时或用隔离器 EN 脚控制
低速正常高速乱码隔离器传播延迟导致建立时间不足检查数据手册 tPD,降低目标速率
电机一启动就丢包共模瞬变通过隔离器耦合到输出增强电源去耦,检查 CMTI 指标
同一块板子不同批次表现不一隔离器通道间 skew 偏大换成 skew 指标更好的批次或降速

其中读回全 0 是新手最容易掉进去的坑。原因在于,SPI 主机的 SCLK 和 MOSI 都有输出波形,哪怕方向接反,示波器上看也像有信号,但卡在 MISO 上就不对。所以我每次新板回来,第一件事是在主控初始化后直接点测 SPI 四个引脚:SCLK 有没有脉冲、MOSI 有没有数据、CS 有没有拉低动作、MISO 在从机回环时有没有数据变化。四根线逐个过一遍,链路问题基本都是秒定位。

5.2 上电瞬间乱发数据的真相

隔离系统最容易被忽略的是上电时序。主控侧电源先建立,隔离器开始输出,而现场侧外设还没复位完成,这时候如果 CS 处于不确定状态,从机可能接收到一坨随机字节,做出错误动作,比如继电器误吸合。

解决办法通常有三招。第一,把现场侧从机的 CS 引脚用外部 100kΩ 电阻上拉到高电平,确保默认无效。第二,在隔离器输出使能脚上做一个延迟控制,让输出使能晚于两侧电源稳定。第三,在从机固件里加一个“先收到特定握手字节才启用输出”的逻辑,这样即使有随机数据进来,从机也不会执行具体动作。

这三种方法我建议至少同时用前两种。硬件上用上拉电阻是最便宜可靠的,不可省;如果主控侧允许,用一根 GPIO 单独控制隔离器 EN 脚,软件里延时几十毫秒再拉高,时序就完全受控了。

5.3 用示波器把时序看明白

数字隔离器调试的必备仪器是示波器,最好是四通道以上的,能把 SCLK、MOSI、CS、MISO 四线同时抓出来。没有四通道的话,至少也要两通道:一路测 SCLK,另一路测数据线,然后通过光标读取建立时间。

测量时分两步。第一步先把隔离器两侧的同名信号同时看,例如测 SCLK 在 A 侧输入和 B 侧输出的波形,确认存在延迟但逻辑一致。第二步用 B 侧 SCLK 做触发,看 B 侧 MOSI 或 MISO 的数据跳变相对 SCLK 采样沿的位置。如果数据跳变沿和采样沿挨得太近,就是时序余量不足,必须降速。

还有一个小技巧:检查 CS 相对 SCLK 的时序。主机把 CS 拉低之后,要留足够时间让隔离器把低电平传到从机侧,再开始时钟。如果 CS 上升沿和最后一个时钟边沿挨得太紧,从机在释放总线之前可能还在驱动 MISO,主机会采样到不确定电平。这个现象很难用单纯的数据比对发现,因为大多数时候数据是对的,偶尔错一帧又毫无规律。

5.4 压力测试与长期稳定性

功能调通只是第一步,工业环境里真正的考验是干扰下的稳定性。我习惯把测试分成三层:低速功能测试、高速压力测试、干扰协同测试。

低速功能测试就是前面说的 1MHz 以下回环,主要确认接线和逻辑正确。高速压力测试把波特率提到 4MHz 到 8MHz,主控持续发几千帧数据,统计误码率,如果连续跑十几分钟零错误,说明基本扎实。干扰协同测试最接近实战:给隔离器现场侧的继电器或电机驱动器通电,让它们反复动作,同时让主控跑 SPI 压力测试。这种测试不需要做得多精确,但它能暴露 CMTI、PCB 布局和电源去耦的真实水平。如果电机一动作就误码,多半不是 SPI 配置问题,而是隔离器两侧电源被共模干扰打穿了,要回看 PCB 和去耦。

压力测试用的报文也要有点讲究,不要只用 0x00 和 0xFF 这种单调数据。建议用一组含有相邻位翻转的随机数,例如 0x5A、0xA5、0x55、0xAA 交替,这样能让数据线上的跳变边沿暴露更多时序问题。

6. 经验总结与后续扩展方向

这趟做下来,我最大的体会是:SPI 隔离方案的难点从来不在某颗芯片怎么用,而是在选型阶段有没有把通道方向、传播延迟、PCB 爬电距离这三件事想清楚。选型阶段花半小时把通道方向画清楚,比上板后拿示波器抓两小时波形值钱得多。

再分享一个小技巧,如果你后续要挂多个 SP 从机,不需要给每个从机都配一颗 MAX14431BAEE+。常见做法是只隔离一组 SPI 信号,CS 信号分别用独立的隔离通道控制,或者把多个从机的片选信号通过一颗小型数字隔离器扩展过去。只要总线上的所有设备都在同一个隔离域里,共用一组 SPI 隔离通道就够。只有设备分散在物理距离比较远的两个隔离区域时,才需要再评估是否增加第二条隔离链路。

如果你未来想把速率再往上推,比如超过 10MHz,那就得重新算时序预算,甚至考虑换用延迟更低、skew 更小的隔离器。这个项目里我留的余量不算大,最后稳定在 4MHz 使用,对于大部分工业应用已经足够。真正的目标是让通信链路在恶劣的电磁环境里依然可靠,而不是盲目追求更快的时钟,这一点在整个工业嵌入式设计里都适用。

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

基于MAX14431与PIC18F46K22的隔离SPI通信设计实践

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

作者头像 李华
网站建设 2026/11/3 1:07:26

基于MAX14431与PIC32MZ的隔离SPI通信设计实战

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

作者头像 李华
网站建设 2026/11/3 1:07:26

基于STM32的智能午休枕:DS18B20与DS1302集成设计

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

作者头像 李华
网站建设 2026/11/3 1:04:55

工业级隔离SPI通信链路设计:MAX14431BAEE+与STM32F429ZI实战

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

作者头像 李华
网站建设 2026/11/3 1:04:48

SPI隔离设计实战:MAX14431BAEE+搭配STM32F746ZG

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

作者头像 李华
网站建设 2026/11/3 1:04:28

I2C协议核心机制解析:多主机仲裁与时钟延展的工程实践

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

作者头像 李华