电子竞赛水管测控系统实战:从PID算法到硬件调试全解析
在实际电子设计竞赛项目中水管流量、压力或液位控制类题目是经典且富有挑战性的方向。这类题目通常要求参赛者综合运用传感器技术、信号处理、控制算法和嵌入式系统开发能力在有限时间内搭建一个稳定、精确的实时测控系统。题目中提到的“手干起泡了”形象地反映了这类项目对动手能力、调试耐心和细节把控的极高要求——从机械结构搭建、电路焊接、传感器校准到软件算法调试每一个环节都可能耗费大量精力任何一个微小疏忽都可能导致系统失灵或精度不达标。本文将以一个典型的电子竞赛水管测控系统H题风格为背景从零开始构建一个完整的解决方案。我们将围绕“流量与液位双参数控制”这一核心任务展开涵盖系统设计、硬件选型与搭建、信号采集与处理、PID控制算法实现、系统集成与调试等全流程。目标是让读者不仅能理解此类项目的技术框架更能掌握从原理图到稳定运行产品的实战技能特别是如何高效排查那些让“手干起泡”的典型问题。1. 系统需求分析与整体设计框架在动手焊接和写代码之前明确的需求分析和清晰的系统架构是避免后期大量返工的关键。一个典型的水管测控H题可能包含以下核心指标测量对象管道内水的流量L/min、储水容器的液位高度cm、管道压力kPa。可能要求同时测量其中多个参数。控制目标通过调节水泵转速或阀门开度将流量或液位稳定在设定值。设定值可能静态也可能动态变化如按特定曲线变化。性能指标稳态误差如液位控制误差±0.5cm、调节时间如从初始状态到稳定的时间、超调量首次超过设定值的幅度。人机交互通过按键输入设定值通过LCD或OLED屏幕实时显示测量值、设定值、控制输出等。通信与上报可能需要通过串口将数据上传到上位机进行显示或记录。基于以上需求我们设计一个以微控制器为核心包含传感器模块、执行器模块、人机交互模块和电源模块的闭环控制系统。1.1 系统框图与信号流整个系统的信号流和工作原理可以概括为以下闭环设定值 (来自按键/上位机) | v [微控制器] -- 偏差计算 | | v (控制信号) ^ (反馈信号) [执行器] [传感器] | | v (物理作用) ^ (物理量) [被控对象水管系统] --- (流量/液位/压力)微控制器周期性地读取传感器数据反馈值与设定值比较得到偏差经过控制算法如PID计算后输出相应的控制信号如PWM波驱动执行器如水泵从而改变被控物理量使其向设定值靠近。1.2 核心模块选型建议选型直接影响系统成本、精度和开发难度。以下是针对学习/竞赛环境的常见选型方案。模块推荐型号/类型关键参数与接口备注主控MCUSTM32F103C8T6 (蓝桥杯/电赛常用)ARM Cortex-M3, 72MHz, 64KB Flash, 20KB RAM, 多路ADC 定时器支持PWM资源丰富社区支持好。也可用STM32G系列、ESP32带Wi-Fi或Arduino Mega快速原型。流量传感器霍尔流量传感器如YF-S201脉冲输出每升水产生一定数量的脉冲工作电压5V价格低廉但需注意精度一般且脉冲频率与流量非线性。需软件校准。液位传感器压力式液位传感器如Water Pressure Sensor模拟电压输出如0.5-4.5V对应0-1米水压需接MCU的ADC引脚测量的是底部压力需根据液位高度 压力值 / (水的密度 * 重力加速度)换算。压力传感器MPX5700AP模拟电压输出0.2-4.7V对应0-700kPa用于管道压力测量同样需要ADC采集。执行器水泵微型直流隔膜水泵工作电压12V控制方式PWM调速或继电器开关PWM调速更连续但需要电机驱动模块如L298N、TB6612。继电器控制简单但只有开/关两种状态。执行器阀门舵机驱动的球阀或电磁阀舵机控制角度0-180度电磁阀控制开关舵机控制模拟阀开度需MCU输出PWM信号。电磁阀通常只有开/关状态。人机交互0.96寸OLED (SSD1306, I2C) 4x4矩阵键盘OLED显示清晰I2C接口节省IO。矩阵键盘扫描输入。也可用LCD1602但显示信息较少。电机驱动TB6612FNG 或 L298N驱动直流电机支持PWM调速和正反转控制。TB6612效率高、发热小更推荐。电源LM2596降压模块 (12V转5V) AMS1117 (5V转3.3V)为水泵12V、传感器5V、MCU3.3V提供稳定电压。极其重要电源噪声是传感器读数跳动和系统不稳定的主要元凶之一。注意传感器和执行器的具体型号需根据题目要求的量程和精度选择。购买时务必查阅其数据手册确认供电电压、输出信号类型和量程。2. 硬件电路搭建与关键注意事项硬件搭建是“手干起泡”的第一关也是最容易埋坑的地方。不稳定的硬件会让软件调试变得异常痛苦。2.1 电源电路设计稳定的电源是系统基石。建议采用三级供电方案输入12V/2A直流电源适配器。第一级降压使用LM2596模块将12V降至5V为传感器、电机驱动模块和OLED供电。第二级降压使用AMS1117-3.3稳压芯片将5V降至3.3V为STM32单片机供电。接线要点在LM2596的输入和输出端并联大电容如100uF电解电容 0.1uF陶瓷电容以滤除低频和高频噪声。AMS1117的输入输出端同样需要并联去耦电容10uF 0.1uF。务必共地将12V电源地、5V地、3.3V地、所有模块的地GND最终连接到一起。2.2 传感器信号采集电路脉冲型流量传感器YF-S201输出信号是5V脉冲。STM32的IO口可承受5V输入但为保险起见可以串联一个1kΩ电阻或使用电平转换芯片。将传感器信号线接到MCU的具有外部中断或定时器输入捕获功能的引脚如PA0, PA1。模拟型压力/液位传感器输出0.5-4.5V模拟信号。STM32的ADC基准电压通常是3.3V因此不能直接将4.5V信号接入否则可能损坏ADC。必须进行分压。分压电路在传感器输出和MCU ADC引脚之间串联一个10kΩ电阻再并联一个20kΩ电阻到地。这样可以将0-5V输入大致映射到0-3.3V。计算V_adc V_sensor * (R2/(R1R2)) V_sensor * (20k/(10k20k)) ≈ V_sensor * 0.667。滤波电路在ADC引脚处对地并联一个0.1uF的陶瓷电容可以滤除高频干扰使读数更稳定。2.3 执行器驱动电路以PWM控制直流水泵为例使用TB6612驱动芯片。接线TB6612的VM接12VVCC接5V逻辑供电GND接地。控制线将STM32的两个GPIO接到TB6612的AIN1、AIN2控制方向一个定时器PWM输出通道接到PWMA控制速度。输出线TB6612的AO1、AO2接到水泵的正负极。关键配置STM32的PWM频率不宜过高对于直流电机1kHz到10kHz是比较合适的范围。频率太低电机可能啸叫太高则驱动芯片可能响应不了。2.4 常见硬件坑与排查清单问题现象可能原因排查步骤传感器读数跳动剧烈毫无规律1. 电源噪声大2. 信号线过长且未屏蔽3. ADC参考电压不稳4. 未使用滤波电容1. 用万用表测量供电电压是否稳定示波器看纹波。2. 缩短传感器到MCU的连线使用双绞线或屏蔽线。3. 检查MCU的VREF引脚是否连接了稳定的3.3V。4. 在ADC引脚加0.1uF对地电容。流量传感器无脉冲信号1. 供电错误2. 水管内有气泡3. 叶轮卡住4. 信号线接错1. 确认传感器供电为5V。2. 确保水管充满水排空气泡。3. 检查传感器内部叶轮是否可灵活转动。4. 用示波器或逻辑分析仪探测信号线是否有脉冲。水泵不转或转速不可控1. 驱动模块供电不足2. PWM频率设置不当3. 控制逻辑错误4. 电机驱动芯片损坏1. 测量驱动模块的VM端是否有12V电压。2. 确认PWM频率在1-10kHz用示波器看波形。3. 检查AIN1/AIN2的电平组合是否正确如10正转01反转。4. 触摸驱动芯片是否异常发烫。OLED屏幕不显示或花屏1. I2C地址错误2. 上拉电阻未接3. 初始化时序问题1. 用I2C扫描程序确认设备地址通常0x3C或0x3D。2. 在I2C的SCL和SDA线上各接一个4.7kΩ上拉电阻到3.3V。3. 确保电源稳定后再发送初始化命令。3. 嵌入式软件设计与核心代码实现硬件稳定后软件是实现精准控制的大脑。我们将基于STM32 HAL库进行开发软件层次分为驱动层、传感器数据处理层、控制算法层和应用层。3.1 工程配置与驱动初始化使用STM32CubeMX初始化项目是高效且不易出错的方式。时钟树将系统时钟HCLK配置为最高频率如72MHz。ADC启用用于液位/压力传感器的ADC通道如ADC1_IN1。配置为“连续转换模式”开启DMA以非阻塞方式获取数据。采样周期可以设置为中等速度如239.5 Cycles。定时器用于PWM输出选择一个定时器如TIM3配置一个通道如CH1为PWM Generation模式。预分频器和自动重载值ARR根据所需PWM频率计算。例如72MHz主频要产生1kHz PWM则PSC 72-1,ARR 1000-1。用于输入捕获选择一个定时器如TIM2用于捕获流量传感器的脉冲。配置一个通道为输入捕获模式并开启中断。用于系统时基配置一个定时器如TIM6产生1ms中断用于系统心跳、控制周期和按键扫描。GPIO配置控制电机方向的IO、按键IO、I2C引脚等。I2C配置为I2C通信模式用于驱动OLED。生成代码生成基于HAL库的MDK-ARM或Makefile工程。3.2 传感器数据采集与处理流量测量脉冲计数法// 在TIM2输入捕获中断服务函数中 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { pulse_count; // 全局变量记录脉冲数 } } // 在1ms定时器中断中每1000ms1秒计算一次瞬时流量 void flow_calculate(void) { static uint32_t last_count 0; uint32_t current_count pulse_count; uint32_t pulse_diff current_count - last_count; // 假设传感器系数K450脉冲/升 float flow_rate_L_per_min (pulse_diff / 450.0f) * 60.0f; // 转换为升/分钟 last_count current_count; // 简单的滑动平均滤波 flow_rate_filtered flow_rate_filtered * 0.7 flow_rate_L_per_min * 0.3; }液位/压力测量ADC均值滤波#define ADC_BUFFER_SIZE 100 uint32_t adc_buffer[ADC_BUFFER_SIZE]; uint32_t adc_index 0; // 在ADC DMA完成半传输和全传输中断中填充缓冲区 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { // 处理前半部分数据... } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // 处理后半部分数据... } // 获取滤波后的ADC值 uint32_t get_filtered_adc_value(void) { uint64_t sum 0; for(int i0; iADC_BUFFER_SIZE; i) { sum adc_buffer[i]; } return (uint32_t)(sum / ADC_BUFFER_SIZE); } // 将ADC值转换为液位高度cm float adc_to_level_cm(uint32_t adc_val) { float voltage (adc_val / 4095.0f) * 3.3f; // 12位ADC3.3V参考电压 // 假设经过分压电路后0.5-4.5V对应0-100cm且分压系数为0.667 // 则传感器原始电压 V_sensor voltage / 0.667 float sensor_v voltage / 0.667f; // 线性映射 (sensor_v - 0.5) / (4.5 - 0.5) * 100.0 float level (sensor_v - 0.5f) / 4.0f * 100.0f; if(level 0) level 0; if(level 100) level 100; return level; }3.3 PID控制算法实现PID是此类控制系统的核心。这里实现一个位置式PID并加入抗积分饱和和输出限幅。typedef struct { float Kp, Ki, Kd; // PID系数 float integral; // 积分项 float prev_error; // 上一次误差 float integral_limit; // 积分限幅 float output_limit; // 输出限幅 } PID_Controller; void PID_Init(PID_Controller *pid, float kp, float ki, float kd, float i_limit, float out_limit) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-integral 0; pid-prev_error 0; pid-integral_limit i_limit; pid-output_limit out_limit; } float PID_Calculate(PID_Controller *pid, float setpoint, float measurement, float dt) { float error setpoint - measurement; // 比例项 float proportional pid-Kp * error; // 积分项抗饱和 pid-integral error * dt; // 积分限幅 if (pid-integral pid-integral_limit) pid-integral pid-integral_limit; if (pid-integral -pid-integral_limit) pid-integral -pid-integral_limit; float integral pid-Ki * pid-integral; // 微分项使用测量值微分对设定值变化更平滑 float derivative pid-Kd * (measurement - pid-prev_measurement) / (-dt); // 注意符号 pid-prev_measurement measurement; // 新增变量记录上一次测量值 // 计算总输出 float output proportional integral derivative; // 输出限幅 if (output pid-output_limit) output pid-output_limit; if (output 0) output 0; // 假设水泵控制输出非负 return output; } // 在主循环或定时中断中调用控制周期dt0.1秒 float level_setpoint 50.0; // 目标液位50cm float current_level get_current_level(); // 获取当前液位 float control_output PID_Calculate(level_pid, level_setpoint, current_level, 0.1f); // 将control_output (0.0 ~ output_limit) 映射到PWM占空比 (0 ~ ARR) uint16_t pwm_duty (uint16_t)((control_output / level_pid.output_limit) * __HAL_TIM_GET_AUTORELOAD(htim3)); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pwm_duty);3.4 人机交互与系统集成在main.c的主循环或定时中断中整合所有功能。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM2_Init(); // 输入捕获 MX_TIM3_Init(); // PWM输出 MX_TIM6_Init(); // 1ms系统定时器 MX_I2C1_Init(); // ... 其他初始化 PID_Init(level_pid, 2.5, 0.1, 0.8, 100, 100); // 初始化PID参数需实际调试 OLED_Init(); OLED_ShowString(0, 0, Level Ctrl Sys); while (1) { // 1. 扫描按键更新设定值 key_scan(level_setpoint); // 2. 每100ms执行一次控制循环 if (control_timer_flag) { // 由TIM6中断置位 control_timer_flag 0; current_level get_filtered_level(); control_output PID_Calculate(level_pid, level_setpoint, current_level, 0.1f); set_pump_speed(control_output); // 设置PWM // 3. 更新显示 char buf[32]; sprintf(buf, Set:%.1f cm, level_setpoint); OLED_ShowString(0, 2, buf); sprintf(buf, Cur:%.1f cm, current_level); OLED_ShowString(0, 3, buf); sprintf(buf, Out:%.1f %%, control_output); OLED_ShowString(0, 4, buf); } // 4. 处理串口命令如需 uart_command_handler(); } }4. 系统调试、参数整定与性能优化系统能跑起来只是第一步稳定、快速、精确地达到控制目标才是难点这也是最可能“手干起泡”的软件调试阶段。4.1 PID参数整定从理论到实践PID参数 (Kp,Ki,Kd) 没有万能值必须根据你的具体系统水管粗细、水泵功率、容器尺寸现场调试。首先将Ki和Kd设为0只使用比例控制。逐渐增大Kp直到系统开始出现持续振荡。记录此时的Kp值称为Ku以及振荡周期Tu。使用齐格勒-尼科尔斯Ziegler-Nichols经验公式计算初始参数P控制:Kp 0.5 * KuPI控制:Kp 0.45 * Ku,Ki 0.54 * Ku / TuPID控制:Kp 0.6 * Ku,Ki 1.2 * Ku / Tu,Kd 0.075 * Ku * Tu微调以上是起点。通常需要降低Ki来减小超调微调Kd来抑制振荡。一个手动调试口诀“先比例后积分再微分调参过程不着急比例从大大小变积分时间往大放微分时间小到大”。4.2 调试工具与技巧串口打印将关键变量设定值、测量值、误差、PID输出通过串口以固定频率发送到上位机如串口助手、Plotter。这是最直接的观察方式。软件示波器使用类似VOFA、SerialPlot等工具可以实时绘制多条曲线直观观察系统响应。分段调试先断开控制环手动给一个固定PWM看水泵能否正常响应传感器读数是否合理。再闭合控制环给定一个阶跃设定值如液位从0到50cm观察系统的响应曲线。记录“黑匣子”数据在出现异常时将一段时间内的关键数据存入数组事后通过串口一次性吐出分析。4.3 高级优化策略当基本PID效果不佳时可以考虑以下策略积分分离当误差很大时取消积分作用防止积分饱和当误差进入一定范围后再引入积分以消除静差。if(fabs(error) error_threshold) { // 误差大只用PD integral 0; } else { // 误差小启用积分 pid-integral error * dt; }微分先行只对测量值进行微分而不对误差微分。这样在设定值突变时微分项不会产生巨大的冲击设定值突变时误差微分会很大。变参数PID根据误差大小或系统不同工作阶段使用不同的PID参数组。前馈控制如果系统干扰可测量如出水阀门突然打开可以加入前馈补偿提前调整控制量比反馈更快。5. 从学习环境到竞赛/生产环境的考量在实验室跑通的系统在竞赛现场或更复杂环境下可能会暴露新问题。环境干扰赛场电磁环境复杂加强电源滤波传感器信号线使用屏蔽线模拟地和数字地单点连接。机械稳定性水管接头、传感器安装务必牢固防止漏水或震动导致读数跳变。水泵避免空转。系统鲁棒性软件看门狗启用独立看门狗IWDG防止程序跑飞。参数存储调试好的PID参数应存储到STM32的Flash中上电自动加载。异常恢复检测传感器断线ADC值持续为0或4095、水泵堵转电流异常等情况并进入安全模式停止水泵和报警。上位机监控开发一个简单的上位机如C# WinForm、Python PyQt通过串口实时接收数据并绘制曲线便于裁判观察系统性能。可以增加一键保存数据、导出报告的功能。文档与注释代码关键部分添加清晰注释。整理一份简明的系统操作说明书说明如何启动、设置目标值、查看状态等。完成一个可靠的水管测控系统是对嵌入式开发全栈能力的综合锻炼。它要求开发者不仅会写代码更要懂电路、会调试、能分析。过程中遇到的每一个“手干起泡”的问题——读数不稳、控制振荡、机械漏水——都是宝贵的经验。解决问题的核心思路永远是分解问题、定位层级电源、硬件、信号、软件、算法、设计实验验证、迭代优化。当你能够系统性地运用这套方法时这类综合性的工程挑战就将从令人头疼的难题转变为展示你技术实力的舞台。

相关新闻

VS Code配置Java开发环境:从JDK安装到项目调试完整指南

VS Code配置Java开发环境:从JDK安装到项目调试完整指南

1. 项目概述:为什么选择VS Code写Java?如果你还在纠结用Eclipse、IntelliJ IDEA还是VS Code来写Java,我的建议是:对于学习、中小项目或者追求轻量化的开发者,VS Code现在完全能打。几年前,我可能不会这么说…

2026/10/26 1:59:38 阅读更多 →
CSS自适应布局实战:Flexbox与Grid解决方案

CSS自适应布局实战:Flexbox与Grid解决方案

1. 自适应布局的本质与挑战前端开发中最让人头疼的场景之一,就是面对动态内容时的布局适配问题。上周我接手的一个后台管理系统需求,要求展示用户上传的图片集合,数量可能在1-20张之间随机变化。产品经理还特别强调:"无论横屏…

2026/10/27 8:23:32 阅读更多 →
HTML5语义化标签与文档结构详解:从基础到最佳实践

HTML5语义化标签与文档结构详解:从基础到最佳实践

1. 项目概述:为什么HTML是每个数字创作者的起点?如果你打开任何一个网页,无论是新闻门户、电商平台,还是你正在阅读的这篇博文,背后都有一套名为HTML的“骨架”在支撑着一切。HTML,全称超文本标记语言&…

2026/10/28 11:05:05 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/27 17:33:32 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/28 7:28:00 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/28 9:02:11 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/27 18:20:50 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/28 9:00:54 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/28 7:29:02 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 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/27 13:55:19 阅读更多 →
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/28 9:01:47 阅读更多 →
黑夜航拍船只数据集训练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/27 19:19:37 阅读更多 →