基于STM32的温控风扇闭环控制:从传感器选型到PID调优
简介这是一份面向嵌入式初学者与电子设计竞赛选手的STM32温控风扇完整工程源码围绕温度采集、PID运算与PWM调速展开可用于课程设计、毕业设计或智能散热方案的二次开发。压缩包共181个文件约3.73MB以44个c源文件与45个h头文件为核心涵盖ADC、TIM、I2C等外设驱动另有25个o、24个crf等编译中间文件及uvprojx、uvoptx工程配置配合hex烧录文件与map、lst映射清单方便直接编译下载与调试。系统集成1602液晶显示与1x4键盘加减调速支持温度阈值设定、过热保护与状态指示完整呈现从传感器采样到电机驱动的控制链路。目前已有5801人学习下载适合希望掌握STM32外设配置、PID闭环控制与PWM调速实战的读者参考借鉴。1. 基于 STM32 的温控风扇从“能转”到“该转多快”的那条分界线很多人第一次做温控风扇代码烧进去风扇转了就以为成了。但真正上手才发现问题不在“转不转”而在“什么时候转、转多快、温度降下来之后怎么收”。基于 STM32 的温控风扇本质是一个闭环控制系统NTC 或数字温度传感器采集环境温度MCU 按预设策略计算目标转速再通过 PWM 驱动风扇功率级。它解决的是“温度变了风量跟着变”这件事适合刚学完 GPIO 和定时器、想找一个能摸到完整信号链的嵌入式练手项目也适合需要给机箱、电源、小空间做低成本散热的从业者。下面按“选型—采集—控制—驱动—避坑—调优”的顺序把这条链路拆开讲清楚。2. 先定方案再画板传感器、风扇和驱动怎么选2.1 温度采集NTC 分压还是数字传感器温控风扇的第一颗扣子是温度怎么进 MCU。常见做法有两类NTC 热敏电阻加分压电阻进 ADC或者 DS18B20、LM75 这类数字传感器走单总线或 I2C。NTC 方案便宜、电路简单但它是非线性器件ADC 读到的电压要经过 Steinhart-Hart 公式或查表才能换成温度。好处是响应快、成本低适合批量产品坏处是每一颗 NTC 的 B 值有离散性不校准的话不同板子之间可能差 2 到 3 摄氏度。数字传感器省去了标定环节直接读寄存器就是摄氏度分辨率常见 0.0625 或 0.125 摄氏度但成本高一些而且 I2C 走线长了容易被风扇电机干扰。我一般会这样分如果只是做一台样机、追求开发速度选 DS18B20 或 LM75如果要上量、对 BOM 敏感选 NTC 加 ADC但必须留一个校准工位。下面给一个 NTC 查表法的核心代码假设用的是 10k NTC、B 值 3950、分压电阻也是 10k、ADC 12 位、参考电压 3.3V。// NTC 温度查表10k25C, B3950, 上拉 10k 到 3.3V // adc_val: 0~4095对应 NTC 两端电压 float ntc_get_temp(uint16_t adc_val) { // 先算 NTC 当前阻值R_ntc R_pull * (adc_val / (4095 - adc_val)) float r_ntc 10000.0f * ((float)adc_val / (4095.0f - (float)adc_val)); // 查表温度点每 5 度一个阻值由 B 值公式反推 // 这里只列几个关键点实际项目建议用 1 度间隔的 256 点表 const float temp_table[] {-20, -10, 0, 10, 25, 40, 60, 80, 100}; const float res_table[] {97000, 55000, 32000, 20000, 10000, 5300, 2500, 1200, 600}; // 线性插值找到 r_ntc 落在哪两个点之间 for (int i 0; i 8; i) { if (r_ntc res_table[i1] r_ntc res_table[i]) { float ratio (r_ntc - res_table[i1]) / (res_table[i] - res_table[i1]); return temp_table[i1] ratio * (temp_table[i] - temp_table[i1]); } } return -100.0f; // 超出范围返回错误值 }这段代码的逻辑是先把 ADC 值换算成 NTC 阻值再用查表加线性插值得到温度。参数上10000.0f是分压电阻4095.0f是 12 位 ADC 满量程这两个值必须和实际电路一致否则温度会整体偏移。查表点越密插值误差越小如果只做 5 度间隔中间段的误差可能到 1 摄氏度左右。实际项目里我会把表做成 1 度间隔用脚本生成直接放在const数组里省 RAM。2.2 风扇选型两线、三线还是四线风扇选错后面控制策略全白搭。两线风扇只有电源和地转速靠调电压或 PWM 斩波但低速时可能启动不了而且没有测速反馈。三线风扇多一根 FG 信号输出与转速成正比的方波可以用来测实际转速但调速仍然靠外部 PWM。四线风扇是 PWM 调速加 FG 测速PWM 信号直接进风扇内部驱动 IC外部只需要给一个 25kHz 左右的方波调速线性度最好低速启动也稳。如果做温控风扇我优先推荐四线风扇。原因很简单PWM 频率可以固定在 25kHz避开人耳可闻范围不会听到啸叫FG 反馈能形成闭环代码里可以判断“我让它转 2000 转它实际转了多少”。三线风扇也能用但 PWM 斩波会干扰 FG 信号测速需要加滤波。两线风扇只适合“温度高了就全速转”的粗放场景。2.3 驱动级MOSFET 低边还是专用驱动风扇电流一般不大12V 风扇常见 0.1A 到 0.5A5V 风扇更小。低边 NMOS 加续流二极管是最简单的驱动方式PWM 直接打 MOS 管栅极。但要注意风扇是感性负载关断瞬间会产生反电动势必须加续流二极管否则 MOS 管容易被击穿。如果风扇电流超过 1A或者需要防反接、过流保护就用专用低边驱动芯片内部集成续流和保護。下面是一个低边驱动的 GPIO 和定时器配置片段以 STM32F103 为例用 TIM3 的通道 1 输出 PWMPA6 作为输出引脚。// STM32F103 TIM3_CH1 PWM 初始化PA6 输出频率 25kHz void fan_pwm_init(void) { // 1. 开时钟GPIOA 和 TIM3 RCC-APB2ENR | RCC_APB2ENR_IOPAEN; RCC-APB1ENR | RCC_APB1ENR_TIM3EN; // 2. PA6 配成复用推挽输出50MHz GPIOA-CRL ~(0xF 24); GPIOA-CRL | (0xB 24); // 复用推挽50MHz // 3. TIM3 时基72MHz / (711) 1MHz 计数频率 TIM3-PSC 71; // 25kHz 周期1MHz / 25000 40所以 ARR 39 TIM3-ARR 39; // 4. PWM 模式 1CNT CCR1 时输出高 TIM3-CCMR1 | (0x6 4); // OC1M 110 TIM3-CCMR1 | (1 3); // OC1PE 预装载使能 TIM3-CCER | (1 0); // CC1E 输出使能 // 5. 初始占空比 0风扇不转 TIM3-CCR1 0; // 6. 使能自动重装载预装载开定时器 TIM3-CR1 | (1 7); TIM3-CR1 | (1 0); } // 设置风扇转速duty 0~100 void fan_set_speed(uint8_t duty) { if (duty 100) duty 100; // ARR 39所以 CCR duty * 40 / 100 TIM3-CCR1 (uint16_t)(duty * 40 / 100); }这段代码的关键参数是PSC 71和ARR 39。STM32F103 的 TIM3 挂在 APB1 上时钟是 72MHz预分频 72 之后得到 1MHz 计数频率再除以 40 就是 25kHz。CCR1决定占空比fan_set_speed(50)就是 50% 占空比。注意四线风扇的 PWM 信号是给风扇内部驱动用的占空比和转速大致成线性但不同型号有差异最好用 FG 反馈做一次标定。3. 控制策略落地从阈值开关到 PID 的代码实现3.1 最简单的阈值控制先跑通再优化刚上手时别急着上 PID。先用阈值控制把整条链路跑通温度低于 35 度停转35 到 45 度之间线性映射到 30% 到 70% 占空比高于 45 度全速。这样代码简单调试直观能快速验证传感器和驱动是否正常。// 阈值加线性映射temp 单位摄氏度返回 0~100 占空比 uint8_t temp_to_duty(float temp) { if (temp 35.0f) { return 0; // 低于 35 度停转 } else if (temp 45.0f) { // 35~45 度映射到 30~70 return (uint8_t)(30.0f (temp - 35.0f) * 4.0f); } else { return 100; // 高于 45 度全速 } }这个函数的参数是 35、45、30、70 四个点。35 和 45 是温度阈值30 和 70 是对应占空比。实际调试时你会发现风扇在阈值附近频繁启停听起来像“喘气”。解决办法是加迟滞比如低于 33 度才停高于 37 度才启动中间保持当前转速。迟滞带一般设 2 到 4 摄氏度具体看风扇惯性和环境热容。3.2 加入 FG 测速把“实际转速”读回来四线风扇的 FG 信号是开漏输出需要上拉电阻一般 10k 到 4.7k。FG 频率和转速的关系是转速 FG 频率 × 60 / 每转脉冲数。常见风扇每转 2 个脉冲所以 100Hz 对应 3000 转。用定时器的输入捕获功能测 FG 周期就能算出实际转速。// TIM2_CH1 输入捕获测 FG 周期PA0 接 FG 信号 volatile uint32_t fg_period 0; // 单位微秒 volatile uint8_t fg_ready 0; void fg_capture_init(void) { RCC-APB2ENR | RCC_APB2ENR_IOPAEN; RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // PA0 浮空输入 GPIOA-CRL ~(0xF 0); GPIOA-CRL | (0x4 0); // TIM2 时基72MHz / 72 1MHz1 计数 1 微秒 TIM2-PSC 71; TIM2-ARR 0xFFFF; // CH1 输入捕获上升沿 TIM2-CCMR1 | (0x1 0); // CC1S 01IC1 映射到 TI1 TIM2-CCER | (1 0); // CC1E 使能 TIM2-CCER ~(1 1); // 上升沿捕获 // 开捕获中断 TIM2-DIER | (1 1); NVIC_EnableIRQ(TIM2_IRQn); TIM2-CR1 | (1 0); } // 中断里读捕获值算周期 void TIM2_IRQHandler(void) { static uint32_t last_capture 0; if (TIM2-SR (1 1)) { uint32_t now TIM2-CCR1; if (now last_capture) { fg_period now - last_capture; } else { fg_period (0xFFFF - last_capture) now; } last_capture now; fg_ready 1; TIM2-SR ~(1 1); } } // 换算转速每转 2 脉冲 uint32_t fan_get_rpm(void) { if (fg_period 0) return 0; // 转速 60秒 / (周期微秒 * 2脉冲) * 1e6 return 30000000UL / fg_period; }这段代码用输入捕获测 FG 周期fg_period单位是微秒fan_get_rpm按每转 2 脉冲换算。参数上PSC 71保证 1 微秒分辨率ARR 0xFFFF防止溢出太频繁。实际调试时如果转速读数为 0先检查 FG 上拉电阻是否接好再用示波器看 PA0 有没有方波。如果读数跳动大可以在中断里做多次平均或者用定时器每 100ms 读一次捕获值。3.3 PID 控制让转速平滑跟随温度阈值控制够用但如果你想让转速随温度平滑变化PID 是绕不开的。温控风扇的 PID 比较特殊被控量是温度执行器是风扇转速但温度变化慢风扇响应快所以积分时间要长微分项可以很弱甚至不用。// 增量式 PID输入目标温度和当前温度输出占空比增量 typedef struct { float kp; float ki; float kd; float last_err; float prev_err; float out; } pid_t; float pid_calc(pid_t *pid, float target, float actual) { float err target - actual; float delta; // 增量式Δu Kp*(e - e1) Ki*e Kd*(e - 2*e1 e2) delta pid-kp * (err - pid-last_err) pid-ki * err pid-kd * (err - 2.0f * pid-last_err pid-prev_err); pid-prev_err pid-last_err; pid-last_err err; pid-out delta; // 限幅 0~100 if (pid-out 100.0f) pid-out 100.0f; if (pid-out 0.0f) pid-out 0.0f; return pid-out; }参数整定上我一般先设kp 2.0、ki 0.05、kd 0然后观察温度阶跃响应。如果温度到目标值后还在缓慢上升说明积分不够加大ki如果转速来回振荡说明kp太大先减kp再加ki。温控风扇的采样周期建议 500ms 到 1s太快了温度没变化PID 会积分饱和太慢了响应迟钝。注意PID 输出要限幅否则积分项会累积到很大温度降下来后风扇还在全速转这就是“积分饱和”血泪经验。4. 避坑与排查温控风扇最容易翻车的 5 个点4.1 风扇不转或转速上不去现象PWM 占空比给到 100%风扇还是不转或者转得很慢。原因通常是驱动级供电不足或 MOS 管没完全导通。先量风扇两端电压如果只有 5V 而风扇是 12V 的那肯定转不起来。如果电压正常但电流很小检查 MOS 管栅极电压是否达到阈值STM32 的 GPIO 是 3.3V有些 MOS 管需要 4.5V 以上才能完全导通这时候要加一级三极管或专用驱动。解决换低阈值 MOS 管或者用驱动芯片。4.2 温度读数跳变严重现象ADC 读到的温度在 2 到 3 摄氏度范围内随机跳。原因有两个一是 NTC 走线太长引入了电源噪声二是 ADC 采样时间太短采样保持电容没充够。解决在 NTC 两端并一个 100nF 电容ADC 采样时间设到 239.5 周期软件里做 8 次平均。如果还跳检查参考电压是否稳定VDDA 和 VSSA 之间要加 100nF 加 10uF 去耦。4.3 PWM 频率选错导致风扇啸叫现象风扇转起来有刺耳的“滋滋”声。原因是 PWM 频率落在人耳可闻范围通常是 1kHz 到 5kHz。解决把 PWM 频率提到 25kHz 以上超出人耳范围。但注意频率太高会导致 MOS 管开关损耗增加一般 25kHz 到 30kHz 是平衡点。四线风扇直接给 25kHz 方波内部驱动会处理两线风扇用低边斩波时频率也要避开可闻段。4.4 阈值附近频繁启停现象温度在阈值附近时风扇一会儿转一会儿停像喘气。原因是阈值控制没有迟滞温度微小波动就触发状态切换。解决加迟滞带比如启动阈值 37 度停止阈值 33 度中间保持当前状态。如果用了 PID检查积分项是否太小导致输出在 0 附近抖动。另外温度采样加一阶低通滤波temp_filtered 0.9 * temp_filtered 0.1 * temp_raw能明显改善。4.5 FG 测速读数为 0 或翻倍现象风扇在转但fan_get_rpm返回 0或者读数比实际高一倍。原因FG 上拉电阻没接或者每转脉冲数设错。常见风扇每转 2 脉冲但有些是 1 脉冲或 4 脉冲。解决先用示波器看 FG 引脚有没有方波没有就加上拉有方波但读数不对改fan_get_rpm里的除数。另外输入捕获中断里要清标志位否则会反复进中断读数乱跳。5. 进阶调优用串口绘图和分段 PID 把温度压到 ±1 度把基础功能跑通之后真正拉开差距的是调优。我习惯用串口把温度和转速实时打出来上位机用简单曲线看趋势。下面是一个 100ms 定时上报的格式每行包含时间、温度、目标转速、实际转速方便直接丢进绘图工具。// 每 100ms 上报一次格式t,temp,target_rpm,actual_rpm void report_task(void) { static uint32_t tick 0; float temp ntc_get_temp(adc_read()); uint32_t target duty_to_rpm(g_fan_duty); // 根据占空比估算目标转速 uint32_t actual fan_get_rpm(); printf(%lu,%.2f,%lu,%lu\n, tick, temp, target, actual); }参数上tick每 100ms 加一temp保留两位小数target和actual都是整数。把这段输出接到串口绘图工具你能直观看到温度到目标值后有没有超调、转速有没有振荡。如果超调大先把kp减半如果稳定时间太长把ki加倍。注意串口打印本身会占用时间如果波特率低于 115200打印一行可能超过 1ms影响控制周期所以上报任务要放在低优先级或者用 DMA 发送。分段 PID 是另一个实用技巧。温度离目标远时用大kp快速响应温度接近目标时换小kp和小ki避免超调。实现上就是根据误差绝对值切换两组参数// 分段 PID误差大于 5 度用激进参数小于 5 度用保守参数 float pid_calc_segmented(pid_t *pid, float target, float actual) { float err target - actual; if (err 5.0f || err -5.0f) { pid-kp 4.0f; pid-ki 0.10f; pid-kd 0.0f; } else { pid-kp 1.5f; pid-ki 0.03f; pid-kd 0.0f; } return pid_calc(pid, target, actual); }这套参数不是万能但能给你一个起点。我自己的习惯是先让风扇在 40 度目标下跑 10 分钟看温度曲线能不能压在 ±1 度以内如果压不住先检查传感器是不是被风扇气流直吹导致读数偏低。传感器位置比 PID 参数重要得多这是踩过坑才明白的。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

REA模型落地指南:从概念、表结构到SQL实践

REA模型落地指南:从概念、表结构到SQL实践

如果你维护过一套企业后台系统,应该见过这种场面:业务部门问“这批货为什么到了,财务上还没入账”,你翻完订单、入库、应付几张表,最后只能说“状态没对上”。我开始接触REA(Resource-Event-Agent&#xff…

2026/10/14 13:11:13 阅读更多 →
网络安全第一章实战指南:从CIA三元组到攻防思维跃迁

网络安全第一章实战指南:从CIA三元组到攻防思维跃迁

1. 这不是“抄答案”,而是一份帮你真正吃透网络安全底层逻辑的实操指南你搜到“计算机网络安全教程(第三版)第一章简答题答案”,大概率正面临三种真实场景:一是刚翻开教材、被“机密性、完整性、可用性”这九个字绕晕的…

2026/10/14 13:11:13 阅读更多 →
语言驱动的语义地图框架:让机器人真正理解空间与指令

语言驱动的语义地图框架:让机器人真正理解空间与指令

lingbot-map 这个项目,前后我断断续续折腾了大概三个月。名字看起来有点怪,拆开就清楚:ling 来自 language,bot 是机器人,map 就是地图。合起来的意思很直接——让机器人用自然语言跟人打交道的同时,还要有…

2026/10/14 13:11:13 阅读更多 →

最新新闻

降AI率实战指南:用免费额度让论文更像人写的

降AI率实战指南:用免费额度让论文更像人写的

周五晚上十一点,某同学发来消息:3000字课程小论文,AI检测率32%,明天就要交了,问我用什么工具能降下来,最好免费额度就能搞定。我回了他一句:工具只能帮你把句子改得更像人写的,但真正…

2026/10/14 14:08:20 阅读更多 →
CUDA算力与驱动版本兼容性速查指南

CUDA算力与驱动版本兼容性速查指南

简介:本资源是一份系统梳理NVIDIA显卡CUDA算力与版本兼容关系的权威参考文档,面向深度学习工程师、GPU开发者及高性能计算初学者,解决选型时‘某型号显卡支持哪些CUDA版本’‘不同CUDA版本对应哪些计算能力(Compute Capability&am…

2026/10/14 14:08:20 阅读更多 →
AI攻击时代,企业高防IP该升级到什么形态?高防IP给出了一份新答卷

AI攻击时代,企业高防IP该升级到什么形态?高防IP给出了一份新答卷

过去两年,网络安全领域发生了一个容易被忽视却极其关键的变化:攻击的门槛正在被AI快速拉低。过去需要一定技术功底才能发起的攻击,现在借助AI工具,一个懂点基础操作的人就能批量生成。攻击资源打包售卖、自动化攻击脚本一键调用、…

2026/10/14 14:08:20 阅读更多 →
EP进化规划Python实现:从原理到黑盒优化实战

EP进化规划Python实现:从原理到黑盒优化实战

EP进化规划的Python实现,听起来像是一篇课程作业的标题,但真正上手之后你会发现,这套算法在工程优化里意外地好用。进化规划(Evolutionary Programming)算得上进化计算里最朴素的一支,它不搞遗传算法那套“…

2026/10/14 14:08:20 阅读更多 →
自主机器人开发的四层物理现实约束

自主机器人开发的四层物理现实约束

1. 为什么“自主机器人基础”不是入门课,而是分水岭“自主机器人基础”这六个字,乍看像大学选修课的名称,或者某本教材的副标题。但在我带过三十多个机器人项目、从学生团队到工业落地一线摸爬滚打的十年里,它从来不是起点——而是…

2026/10/14 14:08:20 阅读更多 →
Vue+Java篮球馆场地预约系统实战:从表设计到并发控制

Vue+Java篮球馆场地预约系统实战:从表设计到并发控制

篮球馆场地预约这事儿,只要在校园或商业球馆跑过一阵子,都能切身体会到痛点有多具体:场地就那么几块,要打的时段全都挤在一起,以前靠微信群报名接龙、前台手写登记,一到周末高峰期,不是时间撞车…

2026/10/14 14:07:20 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →