news 2026/10/19 1:06:42

ESP32开源迷你四旋翼:69元自制飞控与Web遥控全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32开源迷你四旋翼:69元自制飞控与Web遥控全解析

1. 项目缘起与整体方案拆解

1.1 为什么我会盯上"69元四旋翼"这个方向

先说结论:这个项目的核心吸引力不在于"便宜",而在于它把无人机开发里最劝退新手的几个门槛——飞控算法、无线通信、传感器融合、上位机协议——全部压缩到了一个可以在一周业余时间内跑通的体量里。我前后做过几版不同规模的多旋翼,从几百元的玩具级到上千元的自组穿越机,踩过的坑基本都集中在"硬件选型不匹配"和"调试链路太长"这两件事上。69元这个价位段,恰好卡在一个很微妙的位置:它逼着你放弃"堆料"的思路,转而认真思考每一个模块的必要性。

这个项目的本质,是一台基于 ESP32 主控的开源迷你四旋翼,具备手机 Web 遥控、IMU 校准、MAVLink 协议适配、定高定点飞行这几项能力。它解决的问题很具体:让一个没有飞控开发经验的人,能用最低的硬件成本,完整走一遍"传感器采集→姿态解算→电机控制→无线指令下发→地面站监控"的全链路。适合谁来参考?我认为有三类人:一是想入门嵌入式飞控的学生或爱好者;二是想给课程设计、毕业设计找一个有完整技术栈的选题的人;三是已经会写单片机代码、但没碰过姿态控制和无线遥控的开发者。

需要提前说明的是,69元这个数字通常指的是核心电子物料成本,不含机架结构件、电池、充电器、遥控手机这些"复用资产"。如果你从零开始,实际到手能飞的总投入大概在120到180元之间。这个预期要先摆正,否则做到一半发现预算翻倍会很难受。

1.2 整机架构是怎么划分的

我把整机拆成五个功能层,这样后面调试的时候出问题能快速定位到是哪一层:

层级组成核心职责
感知层IMU(加速度计+陀螺仪)、气压计采集姿态角速度、加速度、高度
控制层ESP32 主控姿态解算、PID 运算、电机混控
执行层4×有刷电机 + 驱动 MOS接收 PWM 信号,输出推力
通信层ESP32 内置 WiFi手机 Web 页面遥控 + 遥测回传
协议层MAVLink 精简实现与地面站/上位机对接

这个分层的好处是:每一层都可以单独验证。比如你怀疑是姿态解算的问题,就先把电机拆了,只看串口打印的欧拉角;怀疑是通信问题,就先不装桨,只看 Web 页面能不能收到遥测。我强烈建议按这个顺序逐层打通,而不是一上来就整机通电——整机通电一旦出问题,你根本不知道是哪一层炸的。

1.3 为什么选 ESP32 而不是 STM32

这是整个项目里最关键的选型决策,值得展开说。传统飞控几乎清一色用 STM32F4 系列,理由是浮点运算强、定时器资源丰富、生态成熟。但在这个项目里,我最终选了 ESP32,理由有三条:

第一,ESP32 自带 WiFi。如果用 STM32,你还得额外挂一个无线模块(比如 NRF24 或 ESP8266),不仅增加成本,还要多写一套串口通信协议。ESP32 把无线和主控合二为一,直接省掉一个模块和一层协议。

第二,双核架构。ESP32 有两个核心,我可以把 WiFi 协议栈和 Web 服务跑在 Core 0 上,把姿态解算和 PID 控制跑在 Core 1 上。这样网络抖动不会直接影响控制环的实时性。实测下来,控制环稳定在 500Hz 左右,网络任务偶尔卡顿几十毫秒也不会导致炸机。

第三,成本。一颗 ESP32 模组的价格在 10 到 15 元区间,而 STM32F405 加上无线模块轻松超过 40 元。在 69 元的总预算下,这个差价是决定性的。

当然代价也有:ESP32 的硬件浮点性能不如 F4,定时器精度也稍逊。但对于一台 65mm 轴距的迷你机来说,这些差距在实际飞行中几乎感知不到。选型的本质是匹配需求,不是堆参数。

1.4 手机 Web 遥控这个方案为什么值得做

很多人第一反应是"为什么不用遥控器"。原因很现实:一个像样的航模遥控器加接收机,价格就超过整机预算了。而手机 Web 遥控的好处是零额外硬件成本,且跨平台——安卓、iOS、甚至带浏览器的电脑都能用。

技术实现上,ESP32 开一个 HTTP 服务,手机浏览器访问它的 IP 地址,页面用 JavaScript 的touchstart/touchmove事件捕获摇杆操作,通过 WebSocket 把油门、俯仰、横滚、偏航四个通道的数值实时推给飞控。WebSocket 比轮询 HTTP 延迟低得多,实测端到端延迟在 30 到 60ms 之间,对于定高定点这种温和飞行模式完全够用。

注意:Web 遥控不适合做激烈机动飞行,因为浏览器的事件采样率和网络抖动不可控。它的定位是"低成本入门验证平台",不是"竞技飞行方案"。这个心理预期要提前建立。

2. 硬件选型与核心细节解析

2.1 物料清单与成本拆解

先把账算清楚。下面是我这一版实际用到的物料,价格按批量采购的均价估算:

物料规格数量单价(元)小计(元)
主控模组ESP32-WROOM-3211313
IMUMPU6050166
气压计BMP280155
有刷电机8520 空心杯4416
驱动 MOSSI230240.52
机架65mm 塑料/碳纤11212
螺旋桨31mm 正反桨414
电池1S 3.7V 300mAh188
杂项电阻、电容、PCB、线材-33
合计69

可以看到,电机和机架占了大头。如果你想进一步压成本,可以把机架换成 3D 打印件,能再省 5 到 8 元,但结构强度会打折扣,炸机时容易断臂。

2.2 MPU6050 与 BMP280 的搭配逻辑

姿态感知用 MPU6050,这是最经典也最便宜的六轴 IMU,集成三轴加速度计和三轴陀螺仪,I2C 接口,价格常年稳定在 6 元左右。它的缺点是陀螺仪零偏随温度漂移明显,所以上电必须做校准,这一点后面会详细讲。

高度感知用 BMP280,气压计,同样 I2C 接口。为什么定高需要气压计而不是只用加速度计?因为加速度计积分两次得到高度,误差会随时间平方级累积,飞个十几秒高度就飘得没法看了。气压计测的是绝对气压换算的高度,虽然受气流和温度影响,但短期稳定性远好于纯惯性积分。两者融合(加速度计做高频补偿,气压计做低频基准)才能得到可用的高度估计。

实操心得:MPU6050 和 BMP280 可以挂在同一条 I2C 总线上,地址不冲突(前者 0x68,后者 0x76 或 0x77)。但布线时尽量让这两颗芯片远离电机驱动走线,否则 MOS 开关产生的高频噪声会串进 I2C,导致读数跳变。我第一版就是把 IMU 放在电机正上方,结果陀螺仪数据里全是尖刺,后来挪到机架中心偏后位置才干净。

2.3 有刷电机与驱动电路的关键细节

8520 空心杯电机是这类迷你机的标配,工作电压 3.7V,空载转速约 40000 转,堵转电流能到 3A 以上。驱动用 SI2302 这颗 N 沟道 MOS,导通电阻约 60mΩ,配合 ESP32 输出的 PWM 信号就能调速。

这里有个必须注意的坑:ESP32 的 GPIO 输出高电平是 3.3V,而 SI2302 的开启电压(Vgs(th))在 0.65V 到 1.5V 之间,3.3V 完全能驱动它进入低阻导通区,所以不需要额外的电平转换。但如果你用的是逻辑电平阈值更高的 MOS,就可能出现"半开"状态,MOS 发热严重甚至烧毁。选 MOS 时一定要看 Vgs(th) 参数,确保 3.3V 能可靠开启。

PWM 频率我设在 20kHz 以上,超出人耳听觉范围,避免电机发出刺耳的啸叫。ESP32 的 LEDC 外设支持这个频率,用ledcSetup()配置即可。占空比分辨率设 10 位(0 到 1023),对应油门 0 到 100%。

2.4 电源方案与噪声抑制

整机用 1S 锂电池供电,标称 3.7V,满电 4.2V,放电截止 3.3V。ESP32 和传感器需要 3.3V 稳定供电,所以要么用低压差稳压器(LDO),要么直接用电池电压(因为 1S 电压范围刚好在 ESP32 工作区间内)。

我选择的是直接电池供电 + 大电容滤波的方案。原因是用 LDO 会引入额外的压降和发热,而 1S 电池的电压范围(3.3 到 4.2V)正好在 ESP32 的 3.0 到 3.6V 推荐工作区间边缘——等等,这里有个问题:满电 4.2V 超过了 ESP32 的 3.6V 上限。所以严格来说,必须加 LDO 或者用带保护板的电池。

我最终的方案是:电池经过一颗 3.3V LDO 给 ESP32 和传感器供电,电机则直接从电池取电(电机不怕电压波动)。这样电机的大电流波动不会影响主控供电。同时在电机电源两端并联 100μF 电解电容和 0.1μF 陶瓷电容,吸收换向噪声。

注意:电机和主控共地是必须的,否则 PWM 信号没有参考电平。但共地会带来噪声耦合,所以地线要走粗、走短,最好用星型接地——所有地线汇聚到一个点,而不是串成一串。

3. 实操过程与核心环节实现

3.1 开发环境搭建与基础验证

第一步是把开发环境跑通。我用的是 Arduino IDE 配合 ESP32 开发板支持包,版本选 2.0.x 系列,比较稳定。安装步骤:

  1. 在 Arduino IDE 的"首选项"里添加 ESP32 的板管理器网址。
  2. 在"开发板管理器"里搜索 esp32,安装最新稳定版。
  3. 开发板选择"ESP32 Dev Module",上传速率设 921600。
  4. 先用一个最简单的 Blink 程序验证烧录链路正常。

这一步看似简单,但新手最容易卡在驱动上。ESP32 开发板通常用 CP2102 或 CH340 串口芯片,Windows 下可能需要手动装驱动。如果设备管理器里看不到串口,先换一根数据线试试——很多线是"只充电不传数据"的。

基础验证通过后,先单独测试 MPU6050。烧一段 I2C 扫描代码,确认能在 0x68 地址上找到设备。然后读取原始加速度和角速度数据,静止放置时,加速度计的 Z 轴应该接近 1g(约 16384 LSB,如果量程设 ±2g),陀螺仪三轴应该接近 0(会有零偏,正常)。

3.2 IMU 校准的完整流程

IMU 校准是这个项目里最不能省的一步。陀螺仪零偏如果不校准,飞机会持续往一个方向漂,你打杆修正都来不及。

校准分两部分:

陀螺仪零偏校准:把飞机水平静止放置,采集 2000 个样本,求每个轴的平均值,这个平均值就是零偏。后续每次读取陀螺仪数据都减去这个零偏。代码逻辑大致是:

float gyroBias[3] = {0, 0, 0}; for (int i = 0; i < 2000; i++) { readMPU6050(); gyroBias[0] += gyroX; gyroBias[1] += gyroY; gyroBias[2] += gyroZ; delay(2); } gyroBias[0] /= 2000.0; gyroBias[1] /= 2000.0; gyroBias[2] /= 2000.0;

加速度计校准:加速度计需要校准的是比例因子和零偏。简单做法是六面校准——把飞机分别朝上、朝下、朝前、朝后、朝左、朝右六个方向静止放置,每个方向采集数据,理论上每个方向应该读到 ±1g。通过这六组数据可以解算出每个轴的零偏和比例误差。

实操心得:校准时的"水平"很重要。我一开始随手把飞机放在桌上就校准,结果桌面本身有 2 度倾斜,导致飞机起飞后一直往一个方向飘。后来我用手机的水平仪 App 找了个真正水平的台面,问题才解决。校准环境还要远离震动源,比如别放在洗衣机旁边。

校准数据要存到 ESP32 的 NVS(非易失存储)里,这样每次上电不用重新校准。但要注意,温度变化大的时候零偏会漂,所以如果从室内拿到室外飞,最好重新校准一次。

3.3 姿态解算:从原始数据到欧拉角

有了校准后的数据,下一步是姿态解算。我用的是 Mahony 互补滤波算法,比卡尔曼滤波简单,计算量小,在 ESP32 上跑 500Hz 毫无压力。

算法的核心思想是:陀螺仪积分得到姿态,短期准但长期漂;加速度计测重力方向,长期准但短期噪声大。两者互补融合,用加速度计去修正陀螺仪的漂移。

具体实现上,Mahony 算法通过加速度计测量值和估计重力方向的叉积得到误差,用这个误差去修正陀螺仪的角速度,再积分得到四元数,最后转成欧拉角。关键参数是两个增益:Kp和Ki。Kp决定加速度计修正的强度,Ki消除稳态误差。

我实测下来,Kp = 2.0、Ki = 0.005这组参数在 500Hz 采样率下表现比较均衡。Kp太大姿态会跟着加速度计噪声抖,太小则修正太慢、漂移明显。这个参数需要根据你的实际噪声水平微调。

// Mahony 互补滤波核心片段 float recipNorm = invSqrt(ax*ax + ay*ay + az*az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // 估计重力方向 float vx = 2*(q1*q3 - q0*q2); float vy = 2*(q0*q1 + q2*q3); float vz = q0*q0 - q1*q1 - q2*q2 + q3*q3; // 误差 = 测量叉乘估计 float ex = ay*vz - az*vy; float ey = az*vx - ax*vz; float ez = ax*vy - ay*vx; // 修正陀螺仪 gx += Kp*ex + Ki*exIntegral; // ... 四元数更新

3.4 PID 控制环的设计与调参

姿态解算出来的是角度,控制的目标是让角度稳定在期望值。这里用串级 PID:外环是角度环,输入是期望角度和实际角度的差,输出是期望角速度;内环是角速度环,输入是期望角速度和陀螺仪实测角速度的差,输出是电机修正量。

为什么用串级而不是单环?因为角速度环响应快,能快速抑制扰动;角度环响应慢,负责跟踪目标。串级结构让系统既有快速抗扰能力,又有准确的稳态跟踪。

调参顺序是先内环后外环。先把外环参数设为零,只调内环,让飞机在手里能感受到明显的"抵抗"——你转动它,电机会反向补偿。内环P从 0.5 开始加,加到电机开始高频抖动就回退一点。然后调外环,P从 2.0 开始,加到飞机能快速回正但不振荡。

环参数起始值调整方向现象判断
角速度内环P0.5增大电机高频抖动说明过大
角速度内环I0.002微增消除稳态误差
角速度内环D0.001微增抑制超调
角度外环P2.0增大回正慢说明过小,振荡说明过大

注意:调参一定要拆桨!我见过太多人带桨调参,一振荡就飞起来撞墙。拆桨后用手感受电机响应,安全又直观。

3.5 手机 Web 遥控页面的实现

Web 页面这块,我用了一个极简方案:HTML + CSS + 原生 JavaScript,不依赖任何框架。页面主体是两个虚拟摇杆(左摇杆控制油门和偏航,右摇杆控制俯仰和横滚),用 Canvas 或 div 加触摸事件实现。

通信走 WebSocket。ESP32 端用WebSocketsServer库开一个服务,端口 81。手机连上后,每次触摸事件触发就发送一个 JSON 字符串,包含四个通道的值。ESP32 收到后解析并更新目标姿态。

// 前端发送逻辑 const ws = new WebSocket('ws://' + location.hostname + ':81/'); function sendControl() { const cmd = { thr: throttle, // 0-1000 yaw: yawRate, // -500 to 500 pit: pitch, // -500 to 500 rol: roll // -500 to 500 }; ws.send(JSON.stringify(cmd)); }

ESP32 端要做失控保护:如果超过 500ms 没收到新指令,自动把油门降到零。这个逻辑必须有,否则手机断连后飞机会一直往上飞。

3.6 MAVLink 协议适配与定高定点

MAVLink 是一套轻量级的无人机通信协议,地面站(比如 QGroundControl)通过它显示遥测和发送指令。在 ESP32 上实现完整的 MAVLink 不现实,但实现一个精简子集是可行的。

我实现的消息类型包括:HEARTBEAT(心跳)、ATTITUDE(姿态)、GLOBAL_POSITION_INT(位置,这里用气压高度代替)、RC_CHANNELS_OVERRIDE(遥控通道)。这些消息通过串口或 WiFi UDP 发给地面站,就能在地面站上看到飞机的实时姿态和高度。

定高功能的实现逻辑是:气压计测高度,和期望高度做差,经过一个 PID 控制器输出油门修正量,叠加到基础油门上。这里的关键是气压计数据要滤波,因为气流和温度会让气压读数抖动。我用的是滑动平均加低通滤波,窗口大小 10,截止频率 2Hz。

定点功能在只有 IMU 和气压计的情况下,严格来说做不到真正的水平定点(那需要光流或 GPS)。但可以做"姿态锁定"——即飞机受到扰动后自动回到水平姿态,这在无风环境下视觉上接近定点。如果要真正的水平定点,需要加一个光流模块,成本会增加 20 元左右。

4. 常见问题与排查技巧实录

4.1 上电后电机不转或只转一个

这是最高频的问题。排查顺序如下:

现象可能原因排查方法
全部不转电源未接通/电池没电万用表测电池电压
全部不转PWM 引脚配置错误检查代码引脚定义
只转一个该路 MOS 损坏替换 MOS 测试
只转一个该路电机损坏直接给电机加电压测试
转速异常PWM 频率不对示波器看波形

我遇到过一次"只转一个"的情况,查了半天代码没问题,最后发现是那颗 MOS 的栅极电阻虚焊。所以焊接质量是这类小项目里最容易被忽视的变量。建议用放大镜检查每个焊点,尤其是 MOS 和电机接线。

4.2 起飞后往一个方向猛飘

这个问题几乎都和 IMU 校准有关。排查步骤:

  1. 确认校准时的水平度。用手机水平仪验证台面。
  2. 检查校准数据是否正确写入 NVS。可以在串口打印出来看。
  3. 检查电机安装方向。四个电机的正反桨和旋转方向必须正确,否则会产生持续的偏航力矩。
  4. 检查机架重心。重心不在几何中心会导致悬停时需要额外的姿态补偿。

实操心得:我建议在机架中心画一个十字标记,装机时确保电池和所有重物都对称分布。重心偏差 5mm 就足以让飞机明显往一边飘。

4.3 姿态数据抖动严重

如果串口打印的欧拉角一直在跳,先看原始数据。如果原始数据就抖,那是硬件问题;如果原始数据干净但解算后抖,那是算法参数问题。

硬件层面的抖动通常来自:电源噪声(加电容)、I2C 走线太长(缩短或加屏蔽)、电机干扰(IMU 远离电机)。算法层面则是Kp太大,把加速度计噪声放大了。

4.4 Web 遥控延迟高或断连

WiFi 遥控的稳定性受环境影响很大。几个优化点:

  • 把 ESP32 的 WiFi 模式设为 AP 模式,手机直连,不经过路由器,延迟最低。
  • WebSocket 心跳间隔设短一点,比如 1 秒,及时发现断连。
  • 手机浏览器别开太多后台标签,会抢占网络资源。
  • 飞行时手机和飞机的距离别超过 20 米,迷你机的 WiFi 天线很小,远了信号就弱。

4.5 定高时高度来回振荡

这是高度 PID 参数没调好。气压计本身有噪声,如果P太大,油门会跟着噪声剧烈变化。解决方法是先加大气压计的滤波强度,再调 PID。另外,飞机在室内飞时,螺旋桨产生的气流会冲击气压计,导致读数偏低。可以在气压计上贴一块海绵做气流缓冲,效果立竿见影。

5. 飞行测试与迭代优化

5.1 首飞前的检查清单

首飞是最容易炸机的环节,我整理了一份检查清单,每次首飞前都过一遍:

  • 电池电压是否在 3.8V 以上
  • 螺旋桨是否装紧、方向是否正确
  • IMU 是否已校准、校准数据是否加载
  • 遥控链路是否连通、失控保护是否生效
  • 电机转向是否正确(对角同向)
  • 机架螺丝是否拧紧
  • 飞行区域是否空旷、有无人员

5.2 从悬停到定高的渐进测试

不要一上来就开定高。正确的测试顺序是:

  1. 地面测试:拆桨,推油门看电机响应是否线性。
  2. 低空悬停:手动控制,离地 20cm,感受姿态稳定性。
  3. 姿态模式:松开摇杆,看飞机能否自动回正。
  4. 定高模式:在 1 米高度开启,观察高度保持效果。
  5. 定点模式:在无风环境测试姿态锁定。

每一步稳定了再进下一步。我见过太多人跳过步骤直接开定高,结果飞机一飞冲天撞天花板。

5.3 续航与重量的平衡

1S 300mAh 电池的理论续航约 5 到 7 分钟,实际因为电压下降和电机效率降低,大概 4 分钟。想延长续航有两条路:一是减重,二是换更大容量电池。但换大电池会增加重量,反而可能缩短续航。这里有个经验公式:电池重量每增加 1g,续航增加约 0.5%,但超过某个临界点后续航反而下降。对于 65mm 机架,电池重量控制在 10g 以内比较合适。

5.4 后续可扩展的方向

这个平台跑通后,可以往几个方向扩展:加光流模块实现真正的水平定点;加超声波模块提升低空定高精度;把 Web 遥控换成真正的遥控器接收机;甚至加一个简单的图传模块。每加一个模块,都是对飞控代码的一次重构练习,技术收益很高。

我个人在实际操作中的体会是,这类迷你四旋翼最大的价值不在于飞得多好,而在于它把整个飞控技术栈压缩到了一个你能完全掌控的规模。你改一行 PID 参数,立刻能在飞行中感受到变化;你换一个滤波算法,姿态数据马上不一样。这种即时反馈是学习嵌入式控制最宝贵的东西。最后再分享一个小技巧:每次改完代码,先在串口里把关键数据打印出来看一遍,确认逻辑对了再上电试飞,能省下大量炸机维修的时间。

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

语音方案开发避坑指南:从架构设计到落地优化的实战经验

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

作者头像 李华
网站建设 2026/10/19 1:05:54

音频系统架构:从静态框图到动态权衡的工程实践

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

作者头像 李华
网站建设 2026/10/19 1:05:20

从零基础到电赛拿奖:备赛节奏、团队协作与现场细节全解析

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

作者头像 李华
网站建设 2026/10/19 1:04:43

PyModbus实战:用Python轻松搞定Modbus工业通信

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

作者头像 李华
网站建设 2026/10/19 1:04:43

PSR反激电源EMC整改实战:从噪声源头到PCB布局的完整设计指南

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

作者头像 李华
网站建设 2026/10/19 1:04:38

EMC整改排查:哪些元器件最容易带崩整块板?

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

作者头像 李华