干了这么多年嵌入式我见过不少人对I2C的态度是两个极端要么觉得它简单得不行两根线谁不会要么被它坑得欲仙欲死一提到总线就头疼。我的看法是I2C的体量确实小但它身上每一个细节——开漏输出、上拉电阻、时序参数、多主仲裁——都藏着完整的物理层逻辑和协议智慧。把这两根线真正讲透一周时间绰绰有余。这篇文章不是把芯片手册翻译一遍而是从实际调试的角度出发把“为什么要用开漏”“上拉电阻到底怎么选”“多主仲裁现场会出什么乱子”“逻辑分析仪怎么逮住问题”这些事掰开揉碎讲清楚。可能你的项目里只是用单片机读个传感器温度也可能你要设计一块带多个从机的板子甚至要做多主机冗余通信——这几个场景下I2C底层那点物理知识决定了你后续是“基本能用”还是“稳如老狗”。1. 先搞懂为什么I2C要用开漏两根线的物理层逻辑很多教程上来就让你配置GPIO、选上拉电阻但对“为什么I2C偏偏要用开漏”这件事一笔带过。等到出了问题你连故障在物理层还是协议层都分不清自然无从下手。1.1 推挽和开漏到底差在哪普通GPIO输出常见的是推挽结构内部有PMOS和NMOS两个开关管输出高电平时PMOS导通把引脚直接顶到VDD输出低电平时NMOS导通把引脚拉到GND。这种结构的好处是输出阻抗低、上升下降都快坏处是如果两个推挽输出并联一个输出高、一个输出低就会形成从VDD到GND的低阻抗通路瞬间电流可能烧毁芯片。开漏就不一样。它只有NMOS那半边引脚本身没有主动拉高的能力只能把引脚拉低。输出高电平时引脚实际上处于高阻态电平全靠外部上拉电阻把这条线抬到VDD。你可以把它想象成一个多人共用的拉绳铃每个人都能拉绳子把铃拉响拉低但没人拉的时候必须靠弹簧让铃锤复位上拉电阻。特性推挽输出开漏输出高电平驱动主动输出VDD无靠外部上拉低电平驱动主动输出GND主动输出GND多个输出并联禁止会短路允许线与逻辑电平转换不方便天然支持上升沿速度快依赖RC充电较慢1.2 I2C为什么选择开漏而不是推挽原因一就是上面对比表里的“并联安全”。I2C允许同一个总线上挂几十个器件从机之间基本都会通过SDA线发送数据。如果采用推挽两个从机一个发高、一个发低时总线就短路了。而开漏结构下任何器件拉低SDA整条线就是低谁也别想硬顶“先拉低者胜”。原因二是“线与”带来的协议能力。因为开漏输出天然实现了线与逻辑只要有一个设备拉低总线电平就是低。这既是ACK/NACK的基础也是多主仲裁的基础。地址仲裁时多个主设备同时发数据一旦某个主设备发送的位是高却发现SDA被另一个主设备拉低它立刻知道自己仲裁失败可以优雅退出。这个机制能够成立根子在物理层开漏。原因三是电平转换。开漏输出只负责拉低高电平由外部上拉提供所以简单的电平转换电路就是SDA/SCL各接一个上拉电阻分别上拉到各自的电源域两边芯片就能直接互连。这在混合电平系统比如3.3V主控挂5V传感器或者1.8V CPU挂3.3V外设时特别实用。1.3 开漏的代价速度上不去的真正原因天下没有免费的午餐。开漏结构省了短路风险却限制了速率上升沿不是晶体管的开关沿而是上拉电阻给总线电容充电的过程。总线电容包括每个器件的引脚寄生电容、PCB走线电容、过孔电容。电容越大、电阻越大上升沿就越慢。I2C标准模式最快100kbit/s快速模式400kbit/s快速Plus模式的1Mbit/s都要在规定的上升时间窗里完成。如果你发现400k跑不过去八成不是协议配置问题而是物理层的上升沿太慢。这也是为什么搞清开漏结构之后下一步就要认真对待上拉电阻。2. 上拉电阻选值一欧之差天壤之别上拉电阻是I2C物理层最容易被随手贴个“4.7k就完事”的环节。实际上电阻选小了低电平抬不下去选大了上升沿冲不上去完全是两头受气的活。要搞明白就得从两个方向同时约束。2.1 上拉电阻为什么必须算两个方向的约束I2C规范对输出低电平和上升时间都有要求。先看最小电阻方向。最小电阻的目的是当某个从机把SDA或SCL拉低时引脚上的低电平电压不能超过协议允许的最大值。以常见的V_OL最大值0.4V为例如果拉低灌电流是3mA工作电压3.3V那么上拉电阻最小不能低于3.3 - 0.4/ about 0.97k。这里的灌电流以器件手册里的I_OL参数为准不同器件差异很大低驱动能力器件可能只有几个mA不能盲目用1k。再看最大电阻方向。规定时间内要让总线电容充电到高电平的门限理论上用上升时间公式t_rise 0.8473 × R_p × C_bus。其中C_bus是总线总电容把主控、从机引脚电容加PCB和连接器电容一起估算。I2C快速模式要求最大上升时间300ns如果总线电容估算为200pF那么R_p最大不能超过300ns / (0.8473 × 200pF) ≈ 1.77k。这个值往往比很多人随手选的4.7k激进得多。速率模式最大上升时间常见最小时钟低电平时间标准模式 100kbit/s1000ns4.7us快速模式 400kbit/s300ns1.3us快速Plus 1Mbit/s120ns0.5us2.2 工程上怎么选估算表与实际案例实际设计里我不会只靠一次计算而是估算一个区间再结合经验选值。对于3.3V系统、总线电容100pF左右、跑400kbit/s的情况计算可得R_p最大约3.5k所以选2.2k到3.3k之间比较稳。如果是100kbit/s且总线很短4.7k完全够用但如果总线上挂了四五个器件电容上去之后4.7k就可能让上升沿超过1000ns。我真实踩过这样一个坑某款传感器适配的评估板默认用的10k上拉主控3.3V、2.8V传感器供电跑100kbit/s的时候很正常。我为了调试把I2C速度提到400kbit/s结果读回来的数据每帧都有随机错误。刚开始我还以为是传感器固件问题后来用逻辑分析仪测上升沿SCL和SDA从低到高足足花了700ns严重超出快速模式的300ns上限。换上3.3k上拉电阻后同样速率立刻稳定。问题根源就是上拉太弱电容充得慢主机采样时电平还没高到位。2.3 高速模式和多从机时的上拉设计到了1Mbit/s以上的高速模式普通电阻上拉基本到了物理极限。为了在极小上升时间内把总线拉高I2C规范引入了电流源上拉高电平阶段由电流源快速给电容充电相当于把电阻换成了主动驱动。但电流源上拉的电路实现复杂常规设计里很少碰知道有这么回事就行。更常见的是多从机总线。每个器件都会贡献几个pF到十几pF的引脚电容PCB走线再贡献一些。最容易犯的错误是把上拉电阻当成“每个器件板子上都放一个”结果多板级联后上拉电阻全部并联总阻值变小上升沿快了但每个器件的V_OL有可能被过强的上拉抬高。正确做法是一条总线上只在主控那一端放一份标准上拉电阻或者按整体电容计算好后放在主板子板全部用高阻或直接不上拉避免并联上拉值不可控。2.4 我的上拉电阻调试清单实际调I2C我总结了一个快速排查顺序基本能解决七成电气问题用示波器或逻辑分析仪看SCL和SDA的上升沿是否符合当前速率对应的上限。如果上升沿太慢优先减小上拉电阻但不要一次性减小太多先对比2.2k和4.7k的波形。如果拉低阶段的低电平静态值超过了0.4V说明上拉电阻可能偏小或者器件灌电流不够考虑增大电阻或换更强驱动能力的器件。高电平是否稳定在VDD附近如果偏低查上拉电源和电阻是否虚焊。多板互联时先断开所有子板直接用主板上拉测波形再做在线测试。3. 时序、ACK与时钟拉伸协议如何在两根线上逐位博弈I2C是同步串行协议SCL提供时钟SDA承载数据。这句话说起来容易真正读时序图的时候很多人被建立时间、保持时间、起始条件、停止条件搞得头大。3.1 从时序图读懂I2C的关键参数一次完整传输以主机拉高SDA同时SCL保持高电平时SDA由高到低的跳变作为起始条件。起始之后从机通过地址匹配判断是否被呼叫。然后是数据位SDA在SCL低电平期间变化SCL高电平期间保持稳定主机在SCL高电平中间采样。也就是说SDA不能在高电平期间变化否则就不是数据而是起始或停止标记。参数里比较关键的有起始建立时间t_SU;STA、起始保持时间t_HD;STA、数据稳定保持时间t_HD;DAT、数据建立时间t_SU;DAT还有停止建立时间t_SU;STO。低速模式这些时间往往不是瓶颈但到了400k甚至1M尤其是用了长排线引到外设板时一不小心就会违反建立时间主机采样到错误的电平。我自己习惯在调试阶段直接用逻辑分析仪解码然后把波形放大看每一个字节是不是干净利落。到了估算层面只需要掌握最保守的原则SDA在SCL高之前必须先稳定SCL高到采样点之间留足余量。3.2 ACK/NACK第九个时钟的秘密I2C的数据传输是8位一个字节但真正有效的“回合”是9个时钟。前8个时钟传输数据第九个时钟是响应位主机在第九个时钟高电平前释放SDA由从机拉低表示ACK如果从机不拉低或者拉不低主机读取到高电平就是NACK代表从机不存在、地址错误或指令无法执行。很多初学者在写软件I2C时会漏掉这个细节发送完8位后主机必须切换到输入模式并释放SDA否则从机想拉低被主机推挽输出顶着总线直接冲突。这也是为什么官方示例代码里通常会在第9个时钟前把SDA配置成浮空输入或开漏模式。只要这个步骤没处理好无论代码逻辑多正确一次通信都不会成功。3.3 时钟拉伸从机要求慢一点时发生了什么I2C还有一个诡异机制从机可以把SCL拉低强制主时钟进入等待状态主机必须等到SCL恢复高电平才能继续。这看起来像是从机“抢走了”时钟控制权本质上是为了配合慢速内部操作。最典型的例子是EEPROM写操作。写入一个页面的数据需要几毫秒编程时间在这期间从机无法响应新传输但它又不能主动发STOP于是它把SCL拉低告诉主机“你先等着”。很多硬件I2C控制器在驱动库里默认支持时钟拉伸——也就是SCL被拉低时主机会停下来等待。但如果你用的是软件实现只按固定延时发送时钟那就会在下一拍还没等到SCL恢复到高电平就继续拉导致从机状态完全错乱。我在用某款气压传感器时读取校准寄存器前需要等它内部测量完成数据手册明确说它会拉长SCL低电平。我用硬件I2C没问题但换成GPIO模拟后频繁读到0xFF最后发现是我的软件I2C根本没做“等待SCL拉高”的机制。改成发送每一位前先读SCL低电平就死等问题才消失。4. 多主仲裁与总线复用多个大脑抢一根线的胜负手一主多从的场景只要配置好地址基本不会出大乱子。真正头疼的是多主I2C——同一根总线上挂两个主控它们在空闲时可以发起传输谁先启动不固定。如果两个主控同时启动仲裁机制就要发挥作用。4.1 仲裁的位级规则一比特一比特地抢多个主设备同时发送起始条件后仲裁过程从第一个数据位就开始。因为I2C开漏结构和线与逻辑所有主设备都在驱动SDA而SDA上的实际电平是所有驱动器的线与结果。每个主设备在发送每个位之后都会反读SDA如果自己发送的是高电平但总线上是低电平说明另一个主设备抢先拉低了SDA自己输掉仲裁立即退出传输。奇怪的是输掉仲裁的设备不会浪费太多时间因为它一直到仲裁失败的那个位才退出前面的数据其实已经发送出去了而且和胜者完全一致。这有点像大家一起喊口号直到某个字音调不同一个嗓门大的压过另一个另一个就闭嘴。4.2 时钟同步保证仲裁公平仲裁过程中SCL时钟不是其中一个主设备说了算而是所有主设备共同拉低的结果。当多个主设备同时输出低电平时SCL被拉低的时间是其中最长的那个谁短谁早早就释放了但总线还是低直到最长的那位释放SCL才能一起变高。这样SCL高电平时所有参与仲裁的主设备都到了同一个节奏点从机制上避免了有人快有人慢导致的错位。这种时钟同步机制也带来了一个工程启示软件模拟I2C多主时两个主设备的时钟频率必须一致而且要考虑外部低速因素。否则仲裁过程中快的那个在慢的那个还没释放SCL时就开始发下一位时序就彻底乱了。4.3 多主项目中常见的死锁与故障我在一个双主控冗余项目里吃过一次亏。两个MCU之间通过I2C互相对话其中一台作为主机读取另一台需要实时上传数据时也要抢总线发起传输。硬件I2C外设本身有仲裁逻辑理论上是没问题的。但实际联调时经常出现“整个总线死掉”的现象SCL是高的SDA却是低的两边谁也不动。排查发现问题出在一个主控内部异常复位后它的I2C外设还在认为自己是当前总线的主人不知道外面总线状态已经断了。因为从机还在忙、还在做时钟拉伸主控的驱动库又没有超时处理于是它一直死等SCL恢复高电平。解决方法是给SCL的低电平时长加监督超时超过比如25ms就强制复位本地I2C控制器发送一个STOP把总线状态恢复成空闲再重新初始化。另一个多主场景的坑是两个主控都在没有空闲检测的情况下直接发送START。即使从设备地址不同仲裁失败的一方退出了但胜者发起的传输和数据可能被第一次仲裁消耗掉导致通信随机失败。正确的做法是发起START前先监听总线是否空闲SCL和SDA都是高电平且持续一段时间再动手。4.4 模拟I2C实现仲裁的注意点如果你要在Arduino或裸机环境下用GPIO模拟多主I2C强烈建议在每个发送位后做“监视比较”。原理很简单主设备把SDA当作输出写一个电平延时一小段时间后切到输入读回来如果读到的和写的相同这个位发送成功如果不相同说明仲裁失败立即退回释放总线等待下一次空闲窗口。这里有两个常见错误。第一只在字节级别做回读以为ACK之前仲裁不会发生——其实仲裁发生在每个数据位不按字节算。第二读回SDA的时机太晚等SCL高电平都快结束了才发现冲突这时候两个主机的数据已经错乱连停止条件都可能发不对。至少要在SCL高电平的前半段就读取采样预留足够时间决定是否退出。5. 实测篇用逻辑分析仪让I2C不再“玄学”很多I2C问题看着像软件时序问题实际是物理层问题看着像上拉电阻问题实际又是从机没应答。只靠眼睛盯代码一辈子也看不明白。逻辑分析仪是让I2C“现形”的最直接工具。5.1 工具选择与接线要点调试I2C我建议准备一台采样率至少20MHz的逻辑分析仪能到100MHz更好。I2C本身最高1Mbit/s20MHz已经是20倍采样足以看清边沿和毛刺。通道数8到16个都行最多用两个通道但多通道可以同时抓其他信号。接线时把SCL、SDA、GND三根线引出来用逻辑分析仪的夹子或排针接上注意不要把GND漏接地回路不共地采集到的信号全是悬浮的解码器会抓狂。探针尽量靠近主控芯片的引脚不要跨很长的杜邦线还不加滤波否则逻辑分析仪会看到反射和过冲把你的真实波形毁掉。5.2 I2C解码设置怎么让解码器读出地址和数据拿到逻辑分析仪软件后设置I2C解码器逻辑电平按你的系统电压选3.3V或5V然后设置起始沿触发。解码后软件会标出START、地址字节、R/W位、ACK/NACK、DATA、STOP。这时你就能明确看到主机到底发了哪个地址、外设有没有应答。一个很实用的小技巧先解码一段已知数据比如在EEPROM里写入0xA5、0x5A然后读出来。解码结果应该是两条读写事务地址相同写事务数据是A5 5A读事务返回同样数据。如果写事务完整、读事务在地址后出现NACK说明EEPROM的写入保护或读使能设置有问题而不是I2C时序问题。5.3 三个真实波形案例帮你快速定位故障案例A地址发出后从机始终NACK。波形显示主机发送了地址位但第九个时钟SDA是高电平。这种一般是从机地址不对、从机没上电、或者I2C地址引脚接错了。曾经有块GT911触摸屏I2C地址是由Reset引脚电平决定的硬件上默认拉高了但驱动里按低地址去读逻辑分析仪上一眼就能发现“发的地址和屏实际地址对不上”。案例BSTART后面紧跟着STOP没有CLK看起来像是主机放弃传输。这种往往是主机检测到总线忙比如刚上电时SDA被某个器件拉低驱动库发了一个“无数据停止”。对策是查总线默认状态看看是不是被动器件在上电初期异常占用SDA必要时主机先发一个延迟的STOP来复位总线。案例CSCL低电平无限拉长甚至超过几百毫秒。这是从机时钟拉伸异常要么是从机处于异常状态要么是从机本身坏了也可能主机在SCL低的时候没有释放SDA导致从机死锁。逻辑分析仪能清晰看到SCL一直停在高之后的低电平对着手册把SCL低电平超时时间调到几十ms加复位重试逻辑就能解决。5.4 记录自己的总线健康度基线我给自己的团队定了一条规矩I2C联调通过后把SCL/SDA在最高工作频率下的波形截图保存到版本库里。图上最好标注上升时间、低电平电压、时钟频率。下次扩容设备、换传感器、或者换主控方案时拿新波形和基线比较立刻知道总线的裕量还剩多少。这种“基线”思维能避免很多隐性问题。比如某天你多挂了一个设备通信偶尔失败但整体还能跑。拿逻辑分析仪一抓发现上升沿比基线慢了一截那就说明总线电容已经接近极限哪怕程序没有任何改动你已经知道该考虑降速率或增加驱动了。6. 一周学透I2C我的路线图和自检清单既然标题写了“一周让I2C无所遁形”那我来给一套自己实际带人用过的学习路线。不推荐捧着数据手册从头读到尾那样第三天就烦了。核心思路是第一天就打地基第二天就动手最后几天必须上波器。6.1 前三天吃透物理层和协议层Day1只看物理层。用示波器分别观察普通GPIO推挽输出和开漏输出的波形对比高低电平切换速度。如果没有现成模块自己用两个MOS管搭一下开漏电路用不同电阻看上升沿变化比看十页文档都管用。Day2学习I2C的基础时序。不用碰真实外设直接用STM32或Arduino的硬件I2C把总线速度调到100k在SCL和SDA上挂逻辑分析仪对照起始条件、数据位、ACK位逐一标记。读懂一张干净的时序图后面所有协议问题都迎刃而解。Day3尝试用GPIO软件模拟I2C主机不看硬件I2C外设。手动拉SCL、写SDA、释放SDA模拟ACK把所有时序参数都自己控制一遍。这个过程会逼你理解“为什么第九个时钟要切换到输入”也让你以后遇到硬件I2C外设奇怪行为时不至于手足无措。6.2 后四天抓波形和实战多主Day4开始使用逻辑分析仪解码器对一个EEPROM进行完整的写读操作把写地址、写数据、读数据、ACK/NACK全部对应到波形。这一步建议亲自抓至少十次事务并故意制造一次NACK场景比如把从机地址改错观察解码器如何显示。Day5找两块开发板让它们通过I2C互为主从一块板作为主机主动读取另一块板的数据另一块板同时在空闲时主动发送事件。先让两边的地址不同再故意设置相同地址触发多主仲裁观察仲裁失败后的总线状态和重启流程。Day6在真实项目中加入时钟拉伸和超时恢复逻辑。可以故意让EEPROM在页面写入期间不释放SCL检查主机能否正确等待能否在异常拉低下25ms内复位总线。能被稳定恢复的系统才叫可用系统。Day7做一个小工具比如I2C总线扫描器扫描0x03到0x77地址发现哪些地址上有设备ACK并在终端打印出来。这个工具以后调试所有带I2C的开发板都能用上。6.3 自检清单可以直接抄走下面这张表是我调试I2C时必过一遍的检查清单建议收藏后每次联调前先扫一眼检查项现象原因解决方向物理层上拉通信随机失败尤其高速时上拉电阻过大上升沿太慢用示波器看波形按计算调小电阻开漏配置第九个时钟SDA异常GPIO没有切输入推挽冲突确认SCL/SDA配置为开漏或浮空输入地址匹配从机NACK地址引脚电平错误或器件地址冲突用逻辑分析仪对比实际地址和驱动地址时钟拉伸SCL长时间为低从机忙或异常加超时恢复机制Reset从机多主仲裁总线死锁或数据错乱未做空闲检测或仲裁回读发START前检测总线空闲位级回读SDA电平转换高电平电压偏低上拉电源未接入或转换电路不良检查双向电平转换电路配置我个人的感受是凡是能熟练讲出开漏、上拉、仲裁、时钟拉伸这四个概念的人写I2C驱动基本不会踩到硬坑。但理论归理论真正让你长记性的还是那些波形截图第一次看到上升沿慢到离谱的斜线第一次看到从机SCL被拉低一整屏第一次在双主项目里撞见总线直接死掉——这些经历比任何文档都管用。最后分享一个我自己的习惯调试I2C时无论问题多明显我都先把上拉电阻、地址、时序参数在纸上列一遍再动代码。这样能避免很多“改了代码好了但不知道刚才改了什么”的玄学调试。希望你也能享受这两根线带来的乐趣——毕竟它们虽然简单却把一个完整通信系统的精髓都浓缩在里面了。
企业数字化 ERP 产品动态
相关推荐
C# 实现欧姆龙 FINS 协议通信:AMS Net ID、字节序与TCP握手详解 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:35:10
Atlas 300V 24G推理加速卡实战:从YOLO部署到性能优化 最近后台收到好几个朋友问同一个问题:“Atlas 300V 24G 是运算加速卡吗?” 还有人直接问“Atlas 部署 YOLO 怎么搞,有没有现成流程”。说实话,这类问题我在不同技术群里见过很多次,因为 Atlas 这个名字在华为昇腾的推理… · 2026/9/25 7:35:10
STM32开源项目实战:环境监测与超声波测距的代码原理图仿真全解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 7:35:10
python-dotenv 完整变更历史解析:从版本演进看 .env 配置管理库的核心能力 后端 【免费下载链接】python-dotenv Reads key-value pairs from a .env file and can set them as environment variables. It helps in developing applications following the 12-factor principles. 项目地址: https://gitcode.com/gh_mirrors/py/python-doten… · 2026/9/25 7:55:35
Webnovel Writer FAQ:写完全章后,这 3 个状态与关卡问题还卡住你吗 Webnovel Writer FAQ:写完全章后,这 3 个状态与关卡问题还卡住你吗 【免费下载链接】webnovel-writer 基于 Claude Code 的长篇网文辅助创作系统,解决 AI 写作中的「遗忘」和「幻觉」问题,支持 200 万字量级 连载创作。 项目地址… · 2026/9/25 7:55:29
ROS 2 Jazzy 实现工业级端到端机械臂抓取 简介:本资源是一套基于ROS 2 Jazzy框架实现的端到端机械臂抓取系统,面向机器人方向本科生、研究生及初入ROS开发的工程师,聚焦毕业设计、课程实践与AI机器人融合项目落地。系统完整覆盖感知—规划—控制闭环,集成MoveIt运动规划、… · 2026/9/25 7:55:23
rpcx 方法级服务注册:用 RegisterWithMethods 白名单只暴露你点名的 RPC 方法 后端RPC框架微服务 【免费下载链接】rpcx Best microservices framework in Go, like alibaba Dubbo, but with more features, Scale easily. Try it. Test it. If you feel its better, use it! 𝐉𝐚𝐯𝐚有𝐝&#x… · 2026/9/25 7:55:17
从漏洞分析到主动防护:安全加固与路由器配置实践 抱歉,我无法协助撰写涉及漏洞分析、漏洞链拆解或攻击链构建等技术细节的内容,这类话题可能被用于网络攻击或入侵行为,即使以防御或研究为背景,也存在被滥用的风险。如果你有路由器配置、安全加固、大模型应用等其他合规主题的写作… · 2026/9/25 7:55:04
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37