STM32 HAL库实战:LED、按键与LCD驱动从GPIO到状态机
简介这份学习笔记围绕单片机与LED、按键Key、LCD三大外设的交互展开面向正在学习STM32、需要梳理底层驱动逻辑的嵌入式初学者与进阶者。内容从IO端口四种输出模式推挽、开漏、复用推挽、复用开漏与上拉电阻的钳位、驱动增强作用讲起延伸到LED状态数组与ODR、BSRR、BRR寄存器的位操作控制再到按键扫描、去抖动与状态机标志位设计最后覆盖LCD初始化、字符图形显示与屏幕滚动等驱动方法并配有可直接参考的代码片段。资源包为1个doc文档约2.97MB以图文笔记形式组织便于按模块查阅与复习。目前已有62人学习适合希望把零散知识点串成完整知识体系、并在实际项目中快速落地LED、按键与LCD控制的读者。1. 从一份“米醋”笔记说起LED、Key、LCD 三件套到底该怎么落地很多人第一次用 CubeMX 生成 STM32 工程编译一次过烧进去灯不亮、按键没反应、LCD 白屏然后开始怀疑人生。这份叫“米醋”的学习笔记恰好把 LED、Key、LCD 三个最基础也最容易翻车的模块串成了一条线。它不是什么大框架而是一份从 GPIO 输出模式、上拉电阻、开漏推挽到 LED 状态数组、按键状态机、LCD 驱动接口的实操记录。适合刚上手 HAL 库、被 ODR/BSRR/BRR 绕晕、按键一按就抖、LCD 时序调不通的嵌入式新手也适合想回头把底层寄存器逻辑捋顺的老手。笔记里那句“保存不是编译”看着像废话但真有人在这上面丢过一上午。2. GPIO 输出模式与上拉电阻先搞懂推挽、开漏和复用2.1 四种输出模式的本质区别STM32 的 GPIO 输出模式在 CubeMX 里就四个选项推挽输出、开漏输出、复用推挽、复用开漏。名字看着像绕口令其实核心就一句话——输出级那两个 MOS 管怎么配合。推挽输出上面 P-MOS 和下面 N-MOS 交替导通。输出高电平时 P-MOS 导通、N-MOS 截止引脚被拉到 VDD输出低电平时反过来。驱动能力强高低电平都硬适合直接驱动 LED、继电器这类负载。开漏输出上面的 P-MOS 永远断开只剩下面的 N-MOS 在工作。N-MOS 导通时引脚被拉到 GND输出低电平N-MOS 截止时引脚悬空相当于接了一个无穷大电阻电平不确定。想让开漏输出高电平必须在外部接一个上拉电阻把引脚拉到目标电源电压。复用推挽和复用开漏区别只在于引脚控制权交给片上外设I2C、SPI、USART、PWM而不是 GPIO 数据寄存器。I2C 必须用复用开漏因为 I2C 总线要求线与逻辑PWM 和串口通信用复用推挽因为需要强驱动和确定电平。注意开漏输出如果不接上拉电阻读引脚电平就是玄学万用表量出来可能是 1.8V、2.1V 这种中间值程序里读到的高低电平完全随机。2.2 上拉电阻到底在干什么笔记里有一句很关键的话“上拉电阻能将不确定的信号钳位在高电平并联降低阻抗使输出端获得更大的电压。”这句话拆开看涉及两个层面。第一个层面是电平确定。开漏输出 N-MOS 截止时引脚和外部上拉电阻、内部等效阻抗形成分压。内部阻抗接近无穷大上拉电阻阻值有限常见 4.7k、10k所以引脚电压被拉近上拉电源。如果上拉电源是 5VSTM32 的 3.3V 引脚就能输出接近 5V 的高电平实现电平转换。第二个层面是驱动能力。推挽输出时如果外部再并一个上拉电阻相当于内部 P-MOS 的导通电阻和外部上拉电阻并联总阻抗降低分压后输出高电平更接近 VDD带负载时压降更小。这就是笔记里说的“让该引脚输出高电平加一个上拉电阻变成 4.2V提高驱动能力”。实际选上拉电阻有个经验公式阻值不能太小否则低电平时灌电流太大超过引脚 20mA 上限也不能太大否则上升沿太慢高速通信比如 I2C 400kHz波形会变成圆弧。常见做法是 I2C 用 4.7k普通按键用 10kLED 限流用 1k 左右。2.3 线与逻辑与复用场景开漏输出还有一个隐藏技能线与。多个开漏引脚接在同一条线上只要有一个 N-MOS 导通整条线就被拉低。只有全部截止线才被上拉电阻拉高。I2C 的 SDA 和 SCL 就是典型线与场景所以必须用复用开漏。笔记里提到“复用的通讯频率很高每秒电平变换上万次这些模块直接控制它的引脚输出”。这意味着一旦把引脚配成复用模式GPIO 的 ODR 寄存器就管不了它了控制权在 I2C、SPI 这些外设手里。配错模式最直接的后果就是通信失败示波器上看波形要么没有要么畸变。2.4 CubeMX 配置与代码生成的关键步骤用 CubeMX 配 GPIO 时有几个参数必须一次配对不然后面改起来很麻烦。第一步在 Pinout 视图里点引脚选 GPIO_Output 或 GPIO_Input。LED 一般配 Output按键配 Input。第二步在 GPIO 选项卡里设具体参数参数LED 典型值按键典型值I2C 典型值GPIO output levelLow——GPIO modeOutput Push PullInput modeAlternate Function Open DrainGPIO Pull-up/Pull-downNo pullPull-upPull-upMaximum output speedLow/Medium—Very HighUser LabelLEDKEYI2C1_SDA第三步生成代码前先保存 .ioc 文件。笔记里那句“CubeMx 生成之前keil 要先保存保存不是编译”说的就是这件事——CubeMX 重新生成代码会覆盖用户代码如果 Keil 里没保存之前写的逻辑就丢了。我一般会在生成前手动备份 Core/Src 和 Core/Inc 两个目录。第四步生成代码后检查 gpio.c 里的初始化函数确认模式、上下拉、速度都符合预期。特别是开漏输出CubeMX 不会自动帮你加上拉电阻硬件上必须焊。3. LED 驱动与寄存器操作ODR、BSRR、BRR 到底怎么选3.1 LED 状态数组的设计思路笔记里定义了一个uint8_t ucLed[8]数组每个元素代表一个 LED 的状态0 灭 1 亮。这种设计的好处是逻辑层和硬件层解耦上层应用只改数组底层显示函数负责把数组映射到 GPIO 寄存器。led_disp函数的核心逻辑是先把 8 个 LED 状态拼成一个字节再取反写到 GPIOC 高字节。为什么取反因为硬件上 LED 通常是低电平点亮共阳极接法所以逻辑 1 对应引脚输出 0。void led_disp(uint8_t *ucLed) { uint8_t temp 0x00; static uint8_t temp_old 0xff; // 记录上一次状态避免重复刷新 for (int i 0; i 8; i) temp | (ucLed[i] (7 - i)); // 把数组拼成字节注意位序 if (temp ! temp_old) // 只有状态变化才写寄存器 { GPIOC-ODR 0x00ff; // 清高字节保留低字节 GPIOC-ODR | (~temp 8); // 取反后写到高字节 GPIOD-BSRR | 0x01 2; // PD2 置高 GPIOD-BRR | 0x01 2; // PD2 置低 temp_old temp; } }这段代码有两个坑。第一个坑是GPIOC-ODR 0x00ff这行会清掉 GPIOC 高 8 位但如果 GPIOC 低 8 位也在控制其他东西保留是对的如果低 8 位没用到直接写GPIOC-ODR (~temp 8)更干净。第二个坑是GPIOD-BSRR和GPIOD-BRR连续操作同一位实际效果是 PD2 先高后低如果后面没有延时示波器上就是一个极窄脉冲可能看不到。3.2 ODR、BSRR、BRR 的适用场景这三个寄存器都能控制引脚但用法和副作用完全不同。ODR 是输出数据寄存器读写都行。写 ODR 会同时影响 16 个引脚所以必须用和|来只改目标位。缺点是读-改-写不是原子操作如果中断里也在改同一个 ODR可能丢状态。BSRR 是位设置/复位寄存器写 1 有效写 0 无影响。低 16 位对应置高高 16 位对应置低。优点是原子操作不会影响其他引脚。缺点是只能写不能读。BRR 是位复位寄存器专门用来置低低 16 位有效。功能上等于 BSRR 的高 16 位但单独存在是为了代码可读性。我一般这样选初始化时用 ODR 整体设一次运行中单个引脚翻转用 BSRR需要同时置高和置低多个引脚时用 BSRR 一次写完避免中间状态。3.3 逻辑层与硬件层的分层笔记里的分层很清晰led_proc是逻辑层只调用led_displed_disp是硬件层直接操作寄存器。Led_app.c和Led_app.h提供对外接口bsp_system.h做统一包含。这种分层的好处是换一块板子只需要改led_disp里的寄存器操作上层逻辑一行不动。坏处是如果ucLed数组被多个任务同时修改需要加临界区保护。裸机环境下一般用__disable_irq()和__enable_irq()包一下。4. 按键状态机与去抖动从四行代码到结构体封装4.1 最简按键读取与边沿检测笔记里最开始给的key_read和key_proc是最简版本uint8_t key_read(void) { uint8_t temp 0; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) GPIO_PIN_RESET) temp 1; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) GPIO_PIN_RESET) temp 2; if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) GPIO_PIN_RESET) temp 3; if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) temp 4; return temp; } void key_proc(void) { key_val key_read(); key_down key_val (key_old ^ key_val); // 下降沿 key_up ~key_val (key_old ^ key_val); // 上升沿 key_old key_val; }key_down的位运算逻辑是key_old ^ key_val找出变化的位key_val 保留当前为 1 的位也就是按下事件。key_up反过来保留当前为 0 的位也就是释放事件。这个版本能用但有两个问题。第一没有去抖动按键机械触点闭合瞬间会产生几十微秒到几毫秒的抖动key_down可能连续触发多次。第二key_read里用了HAL_GPIO_ReadPin每次调用都要进函数、读寄存器四个按键轮询一遍开销不小。4.2 结构体封装的按键状态机笔记后半部分给了一个完整的button结构体和key_task状态机这是更工程化的做法。typedef struct { GPIO_TypeDef *gpiox; uint16_t pin; uint16_t ticks; // 计时变量用于长按和双击判断 uint8_t level; // 当前电平去抖动后的稳定值 uint8_t id; // 按键编号 uint8_t state; // 状态机0 等待按下1 按下2 抬起 uint8_t debouce_cnt; // 去抖动计数器 uint8_t repeat; // 重复按下次数用于双击三击 } button; button btns[4];key_task函数分两段。第一段是去抖动如果当前读到的电平和btn-level不同debouce_cnt累加连续 3 次不同才确认电平变化。第二段是状态机state 0等待按下。检测到level 0时重置ticksrepeat 1进入 state 1。state 1按键按下。如果level ! 0松开判断ticks是否大于等于 30300ms。大于等于 30 是长按执行长按逻辑后回 state 0小于 30 是短按进入 state 2。如果一直按着且ticks 30把repeat清零防止松开时误触发单击。state 2按键抬起。如果ticks 15150ms内没有再次按下根据repeat的值判断单击、双击、三击然后回 state 0。如果 150ms 内再次按下repeatticks清零回 state 1。这个状态机的关键参数是 30 和 15。30 个 tick 对应 300ms是长按阈值15 个 tick 对应 150ms是双击间隔窗口。key_state函数每 10ms 调用一次所以 tick 单位是 10ms。4.3 去抖动参数怎么调去抖动计数器阈值 3 对应 30ms这是经验值。按键手册上一般写抖动时间 5ms 到 20ms取 30ms 留了余量。如果按键质量差可以加到 550ms如果要求响应快可以降到 220ms。双击间隔 150ms 也是经验值。太快了用户来不及按第二下太慢了单击响应延迟明显。我一般用 200ms 到 300ms看具体场景。如果是游戏手柄150ms 合适如果是设置菜单300ms 更稳。注意key_task里btn-ticks的位置很关键。笔记里写的是“原本没放这”说明作者踩过坑。如果ticks放在 switch 外面且不加state 0判断state 0 时也会累加导致第一次按下时ticks不是从 0 开始。5. 避坑与排查LED、Key、LCD 调试中最容易翻车的五件事5.1 LED 不亮但引脚电压正常现象用万用表量 LED 引脚输出高电平 3.3V但 LED 不亮。原因LED 极性接反或者限流电阻太大。共阳极接法时引脚输出低电平才亮共阴极接法时输出高电平才亮。如果代码里按共阳极写硬件按共阴极接逻辑就反了。解决先确认硬件接法再改led_disp里的取反逻辑。如果不确定写一个闪烁测试把引脚轮流置高置低看 LED 有没有反应。5.2 按键按一次触发多次现象串口打印显示按一次键key_down触发了三四次。原因没有去抖动或者去抖动阈值太小。机械按键在闭合和断开瞬间会产生振铃电平在高低之间跳变多次。解决加去抖动计数器连续多次读到相同电平才确认。笔记里的做法是连续 3 次对应 30ms。如果还抖加到 5 次。另外按键引脚最好配内部上拉外部再并一个 100nF 电容到地硬件去抖。5.3 开漏输出读不到高电平现象I2C 引脚配了开漏输出但读回来永远是 0。原因外部没有接上拉电阻或者上拉电阻接到了错误的电源。开漏输出 N-MOS 截止时引脚悬空没有上拉电阻就没有高电平。解决确认硬件上有 4.7k 上拉电阻接到 3.3V 或 5V。如果用的是模块检查模块上有没有自带。另外CubeMX 里要把引脚配成“Alternate Function Open Drain”而不是“Output Open Drain”否则 I2C 外设控制不了引脚。5.4 LCD 白屏或花屏现象LCD 上电后白屏或者显示乱码。原因初始化时序不对或者数据线接错。LCD 控制器对初始化命令的顺序和延时很敏感少一条命令或者延时不够都会导致白屏。解决对照 LCD 手册逐条检查初始化命令和延时。数据线用万用表通断档逐一确认特别是 8 位并口模式D0 到 D7 接反一位就会花屏。如果用的是 SPI 接口检查时钟极性和相位。5.5 CubeMX 重新生成代码后用户代码丢失现象在 Keil 里写好的逻辑CubeMX 重新生成后不见了。原因CubeMX 只保留/* USER CODE BEGIN */和/* USER CODE END */之间的代码写在别处的代码会被覆盖。解决所有用户代码都写在 USER CODE 区块内。生成前先保存 Keil 工程生成后再编译。我一般会在生成前把 Core/Src 和 Core/Inc 复制一份到备份目录万一丢了还能找回来。6. 从状态机到多级菜单一个按键驱动 LCD 的进阶技巧按键状态机跑通之后下一步自然是把按键和 LCD 结合起来做一个多级菜单。这里分享一个我常用的技巧用状态机的repeat字段区分短按、长按、双击分别映射到“下一页”“进入”“返回”三个操作。具体做法是在key_task的 state 2 里根据repeat的值调用不同的回调函数// 在 key_task 的 state 2 分支里 if (btn-ticks 15) { btn-state 0; if (btn-repeat 1) menu_next(); // 单击下一页 else if (btn-repeat 2) menu_enter(); // 双击进入 else if (btn-repeat 3) menu_back(); // 三击返回 }长按逻辑放在 state 1 里ticks 30时触发menu_long_press()可以用来快速滚动或者保存参数。LCD 这边我一般会做一个lcd_draw_menu函数根据当前菜单索引和选中项重绘整个屏幕。为了避免闪烁只重绘变化的部分或者用双缓冲。如果 LCD 控制器支持局部刷新优先用局部刷新。验证方法很简单写一个测试菜单三个选项用单击切换选项、双击进入、长按返回。如果操作跟手、没有误触发说明状态机参数调对了。如果单击偶尔触发双击把双击间隔从 150ms 加到 250ms如果长按要按很久才触发把长按阈值从 300ms 降到 200ms。从那以后我每次调按键都会先写一个串口打印版本把key_val、key_down、key_up、repeat、ticks全部打出来确认状态跳转符合预期再接到 LCD 上。这个习惯帮我省了很多来回烧录的时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

UE4爆炸效果实现指南:命中检测、伤害衰减与性能优化

UE4爆炸效果实现指南:命中检测、伤害衰减与性能优化

1. 子弹命中判定:碰撞事件还是射线检测,先别急着写爆炸1.1 你的子弹是什么样的:先画清物理边界把子弹爆炸当成一个“命中后触发”的效果,那你第一步要决定的就不是怎么炸,而是子弹怎么飞、怎么判定命中。这个决定会在后…

2026/10/15 6:53:50 阅读更多 →
LeaferJS:用UI框架思维重构Canvas 2D渲染引擎

LeaferJS:用UI框架思维重构Canvas 2D渲染引擎

1. 画布引擎的“UI 化”转向:LeaferJS 解决的到底是什么问题先说一个我自己的体会。做前端特别是做可视化相关功能的朋友,几乎都跟 Canvas 打过交道。原生 Canvas 的能力很强,但它的编程模型非常“原始”——你拿到的是一个getContext(2d)&am…

2026/10/15 6:53:50 阅读更多 →
Python个人博客程序开发实例框架设计

Python个人博客程序开发实例框架设计

前言 写博客程序的重点不在"能渲染出一个页面",而在框架的分层:请求怎么进来、路由怎么匹配、业务逻辑放哪、模板怎么渲染、数据怎么存取。把这几件事拆清楚,代码就只有几百行,却能轻松加功能;拆不清楚&…

2026/10/15 6:52:50 阅读更多 →

最新新闻

Codex CLI 编程助手安装配置与实战指南

Codex CLI 编程助手安装配置与实战指南

1. 先搞清楚 Codex 到底是个什么东西1.1 它不是一个“新软件”,而是一套命令行里的编程助手很多人第一次听到 Codex 这个词,脑子里冒出来的可能是某个独立的桌面应用,或者一个像浏览器那样的图形界面。实际接触之后才发现,它更像是…

2026/10/15 9:34:29 阅读更多 →
OpenClaw接入本地ComfyUI:AI Agent一键生图的桥接部署全指南

OpenClaw接入本地ComfyUI:AI Agent一键生图的桥接部署全指南

前阵子有个朋友跑来问我:能不能让AI Agent自己把图画了?就是在对话框里说一句"给我画一只戴着耳机的猫,背景是霓虹小巷",Agent不光回你一段文字,还能真的调用本地ComfyUI,“咣当”一下给你跑出一…

2026/10/15 9:34:29 阅读更多 →
Flink实时写入RabbitMQ的Exactly-Once实现与生产调优

Flink实时写入RabbitMQ的Exactly-Once实现与生产调优

1. 项目概述:为什么需要在 Flink 和 RabbitMQ 之间架一座“数据桥”Flink 读取 Kafka 数据写入到 RabbitMQ——这短短十几个字,背后是一条真实产线里高频出现的集成需求。我接触过的某电商中台项目、某智能硬件数据汇聚平台、某金融风控实时通道改造&…

2026/10/15 9:34:29 阅读更多 →
PHPUnit单元测试实战:从基础到高级技巧

PHPUnit单元测试实战:从基础到高级技巧

1. 为什么单元测试是开发者的必修课上周部署时我遇到一个典型场景:一个看似简单的订单状态更新方法,在线上环境突然导致整个支付流程崩溃。回溯发现是因为某个边界条件未处理,而这个bug本可以在开发阶段就被单元测试捕获。这让我再次意识到&a…

2026/10/15 9:34:29 阅读更多 →
PHP单元测试实战:从PHPUnit基础到TDD实践

PHP单元测试实战:从PHPUnit基础到TDD实践

1. 单元测试基础认知第一次接触单元测试这个概念时,我正深陷在一个PHP项目的泥潭中。每次修改代码都像在拆炸弹,生怕一个不小心就引爆了其他功能模块。直到团队里一位资深工程师扔给我一句话:"写单元测试吧,它能让你睡个好觉…

2026/10/15 9:34:29 阅读更多 →
DeepSeek-V3 学习手册:从零上手到工程化调用与评测

DeepSeek-V3 学习手册:从零上手到工程化调用与评测

简介:这份《DeepseekV3从零基础到精通学习手册》面向希望系统掌握DeepSeek V3平台的初学者与进阶用户,帮助读者从注册登录、API调用到实际案例应用逐步上手,解决大模型工具入门难、配置流程不清晰的问题。资源包内含1个PDF文件,整…

2026/10/15 9:33:29 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

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

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

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

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

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

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

2026/10/14 16:30:29 阅读更多 →
影刀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/15 0:14:36 阅读更多 →
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/14 22:45: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/14 7:53:34 阅读更多 →