STM32F4多通道DMA ADC配置指南:从原理到避坑
简介这份资源面向STM32F4嵌入式开发者与学习者聚焦多通道ADC与DMA协同采集这一实战主题帮助解决多路模拟信号高速采样时CPU占用高、中断频繁、实时性不足的问题。压缩包共103个文件约846KB以47个.h头文件与46个.c源文件为主体涵盖ADC、DMA、RCC、TIM、USART等标准外设驱动另含uvprojx与uvoptx工程文件、hex烧录文件、s启动文件及少量png、bat辅助文件构成可直接编译的完整工程。内容围绕ADC初始化、DMA通道配置、ADC与DMA请求连接、连续转换启动及DMA传输完成中断处理等关键环节展开读者可据此掌握多通道采样序列设置、采样时间与分辨率选择、内存到外设的数据搬运逻辑并理解如何用DMA释放CPU资源、提升采集效率与实时性。目前已有1514人学习下载适合需要快速搭建多通道数据采集原型、对照工程查漏补缺的开发者参考。1. 多通道 DMA ADC 到底在解决什么问题如果你用 STM32F4 做过采样大概率经历过这样的场景主循环里轮询 ADC采样率一高CPU 全耗在等 EOC 标志位上稍微加点滤波或串口输出波形就开始丢点。多通道 DMA ADC 就是冲着这个痛点来的——让 ADC 按规则序列自动轮转多个通道每转换完一个就把结果直接搬进内存数组CPU 全程不参与搬运只在缓冲区填满时被中断叫醒一次。这套机制适合谁做电机控制要同步采三相电流的、做音频采集要连续抓波形的、做传感器阵列要轮询多路模拟量的基本都绕不开它。标题里的 STM32F4 是关键因为 F4 系列的 ADC 支持规则序列 扫描模式 DMA 请求三者配合才能跑通。很多人卡住不是因为不会写代码而是没搞清「扫描模式、连续转换、DMA 循环模式」这三个开关的联动关系配置错一个要么只采一个通道要么 DMA 只搬一次就停。下面按我实际调通的顺序把选型、配置、踩坑和验证一步步拆开。2. 先把 ADC 与 DMA 的联动关系理清楚2.1 规则序列、扫描模式与 DMA 请求的三角关系STM32F4 的 ADC 有规则通道和注入通道两组。多通道采样一般用规则序列你可以在 SQR1/SQR2/SQR3 寄存器里排最多 16 个通道的顺序。但光排序不够必须打开扫描模式SCANADC 才会按序列逐个转换而不是只转第一个。转换结果放在 DR 寄存器里问题是多通道时每个通道的结果都会覆盖 DRCPU 根本来不及读。这时候 DMA 请求就派上用场每次 EOC转换结束产生一个 DMA 请求DMA 把 DR 的值搬到内存。所以链路是「扫描模式让 ADC 轮转通道 → 每次 EOC 触发 DMA → DMA 按顺序填数组」。这里有个容易忽略的点DMA 的搬运次数必须和规则序列长度一致。你排了 4 个通道DMA 缓冲区就得是 4 的整数倍否则数据会错位。我一般把缓冲区设成通道数 × NN 是每个通道采多少轮这样数组里第 i 个元素对应第 i%通道数 个通道。2.2 连续转换模式与 DMA 循环模式的配合连续转换CONT打开后ADC 一轮序列转完立刻开始下一轮不用软件再触发。DMA 这边要开循环模式CIRCULAR否则搬完一轮就停你得在中断里重新使能。两者都开系统就进入「ADC 自动转、DMA 自动搬、CPU 只在半满/全满中断处理」的稳态。但要注意连续转换 DMA 循环模式下ADC 的采样率由「总转换时间」决定不是你想设多少就多少。总转换时间 (采样周期 12 个 ADCCLK 周期) × 通道数。ADCCLK 又来自 PCLK2 分频分频系数在 ADC 通用控制寄存器里设。算错这一步实际采样率和预期差好几倍波形频率对不上。2.3 用 CubeMX 生成骨架还是手写寄存器新手我建议先用 CubeMX 把时钟树和引脚配好生成初始化代码再在它基础上改。原因是 F4 的 ADC 时钟分频、DMA 流选择、中断优先级这些细节手写容易漏。但 CubeMX 生成的 DMA 配置默认是 Normal 模式你得手动改成 Circular并且把 ADC 的 ContinuousConvMode 打开。熟手可以直接手写关键是记住 DMA 流和 ADC 的对应关系ADC1 对应 DMA2 的 Stream0 或 Stream4通道 0具体看数据手册的 DMA 请求映射表。选错流DMA 请求根本不会触发现象是数组一直是初始值。// ADC1 DMA2 Stream0 初始化骨架基于标准外设库风格 // 关键扫描模式 连续转换 DMA 循环三者缺一不可 ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; // 1. 使能时钟ADC1 在 APB2DMA2 在 AHB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // 2. DMA2 Stream0 配置外设到内存循环模式 DMA_InitStructure.DMA_Channel DMA_Channel_0; // ADC1 对应通道0 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize ADC_CH_NUM * SAMPLE_ROUNDS; // 必须等于序列长度×轮数 DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; // 内存地址递增 DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环别用 Normal DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_Init(DMA2_Stream0, DMA_InitStructure); DMA_Cmd(DMA2_Stream0, ENABLE); // 3. ADC 配置扫描 连续 DMA 使能 ADC_InitStructure.ADC_Resolution ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 多通道必须开 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 自动连续转换 ADC_InitStructure.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion ADC_CH_NUM; // 规则序列长度 ADC_Init(ADC1, ADC_InitStructure); // 4. 规则序列把通道按顺序排进去 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_84Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_84Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_84Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 4, ADC_SampleTime_84Cycles); ADC_DMACmd(ADC1, ENABLE); // 打开 ADC 的 DMA 请求 ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConv(ADC1); // 软件触发第一轮之后连续模式接管这段代码的逻辑说明DMA 的BufferSize设成ADC_CH_NUM * SAMPLE_ROUNDS意味着 DMA 会连续搬这么多半字才回到起点正好对应「4 个通道各采 SAMPLE_ROUNDS 轮」。DMA_MemoryInc_Enable让内存地址递增否则所有数据都堆在数组第一个元素。ADC 的NbrOfConversion必须和实际排入的通道数一致写多了会读到无效通道。参数说明ADC_SampleTime_84Cycles是采样周期周期越长采样越准但总转换时间越长。信号源阻抗高就选长周期低阻抗可以选 3 或 15 周期换速度。DMA_Priority_High在多路 DMA 竞争时保证 ADC 数据不丢但别所有流都设 High会互相抢。3. 采样率、缓冲区与中断的工程化配置3.1 算准总转换时间别让采样率变成玄学很多人配完发现实际采样率和算的对不上问题出在 ADCCLK 上。F4 的 ADC 时钟来自 PCLK2 经过 ADC 预分频器分频系数有 2/4/6/8。假设 PCLK2 84MHz分频 4 得 ADCCLK 21MHz这是常见值。总转换时间 (采样周期 12) / ADCCLK × 通道数。采样周期 84 时单通道 (8412)/21M ≈ 4.57us4 通道一轮约 18.3us采样率约 54.6kHz。这个数字决定了你 DMA 缓冲区多久填满一次。如果开半满中断中断频率就是采样率 / (缓冲区长度/2)。中断太频繁 CPU 扛不住太少实时性差。我一般让半满中断频率在 1kHz 到 10kHz 之间据此反推缓冲区长度。3.2 双缓冲与半满/全满中断的取舍DMA 循环模式下半满HT和全满TC各触发一次中断。用双缓冲思路HT 时处理前半段TC 时处理后半段这样处理数据和处理 DMA 搬运并行不会因为处理慢导致覆盖。标准库用DMA_IT_HT | DMA_IT_TC两个中断HAL 库用HAL_ADC_ConvHalfCpltCallback和HAL_ADC_ConvCpltCallback。注意中断里别做耗时操作只置标志位或拷贝到另一个缓冲区滤波和计算放主循环。我见过有人在 TC 中断里跑 FFT结果中断嵌套把栈撑爆直接进 HardFault。// 半满/全满中断处理只做搬运不做计算 #define ADC_CH_NUM 4 #define SAMPLE_ROUNDS 64 #define BUF_LEN (ADC_CH_NUM * SAMPLE_ROUNDS) volatile uint16_t adc_buf[BUF_LEN]; volatile uint8_t half_ready 0; volatile uint8_t full_ready 0; void DMA2_Stream0_IRQHandler(void) { if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_HTIF0)) { DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_HTIF0); half_ready 1; // 前半段就绪主循环处理 } if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) { DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0); full_ready 1; // 后半段就绪 } }逻辑说明中断里只置标志主循环轮询half_ready和full_ready再处理对应半段。这样即使主循环偶尔卡顿DMA 仍在后台搬运不会丢数据。参数上SAMPLE_ROUNDS取 64 是折中太小中断频繁太大延迟高。实际项目里我会根据信号最高频率调整保证半段数据覆盖至少一个完整信号周期。3.3 电压换算与参考电压的坑ADC 读出来是 0 到 4095 的原始值换算成电压要乘 VREF/4096。VREF 一般是 3.3V但如果你板子上 VDDA 和 VREF 接了独立基准就得按实际值算。我踩过的坑是拿 3.3V 算结果和万用表测的差 50mV查了半天发现 VREF 实际是 3.28V。高精度场景建议外接基准芯片别用电源直接当参考。换算代码很简单但要注意整数运算的截断误差。先乘后除别先除后乘否则小数部分全丢了。// 原始值转电压先乘后除避免截断 float adc_to_voltage(uint16_t raw) { const float vref 3.28f; // 按实际基准改别默认 3.3 return (float)raw * vref / 4096.0f; }4. 多通道 DMA ADC 的避坑与排查清单4.1 只采到第一个通道后面全是 0现象数组里只有第一个元素在变其余不动。原因通常是扫描模式没开或者 DMA 的MemoryInc没使能。ADC 只转序列第一个通道DMA 每次搬同一个地址数组自然只有第一个位置被覆盖。解决确认ADC_ScanConvMode ENABLEDMA_MemoryInc DMA_MemoryInc_Enable并且NbrOfConversion等于实际通道数。4.2 DMA 搬一次就停数组不再更新现象上电后数组更新一轮就冻结。原因是 DMA 配成了 Normal 模式搬完BufferSize就停止。解决改成DMA_Mode_Circular。另外检查 ADC 的连续转换是否打开如果 CONT 没开ADC 转完一轮序列就停DMA 自然没请求了。4.3 数据错位通道和值对不上现象本该是通道 0 的值出现在通道 1 的位置。原因多半是 DMA 缓冲区长度和序列长度不成比例或者中断里读数组时没考虑当前 DMA 写到哪了。解决缓冲区长度设成通道数的整数倍处理时按index % ADC_CH_NUM判断通道。如果开了双缓冲注意半满和全满对应的数据段不同。4.4 采样值跳动大噪声明显现象同一路信号读出来上下跳几十个 LSB。原因可能是采样周期太短信号源阻抗高采样电容没充够。解决把ADC_SampleTime调长比如从 3 周期改到 84 或 144 周期。另外模拟地和数字地要分开走线ADC 输入脚加 RC 滤波。软件上可以做多次采样取平均但别在中断里做放主循环。4.5 中断进不去或进错流现象DMA 中断没反应或者进了别的 IRQHandler。原因是 DMA 流和 ADC 的映射关系搞错或者中断向量没使能。解决查数据手册的 DMA 请求映射表确认 ADC1 对应哪个 DMA 哪个流。然后在 NVIC 里使能对应的DMA2_StreamX_IRQn优先级别设太低被其他中断压住。5. 用定时器触发把采样率钉死前面用的是软件触发 连续转换采样率靠 ADCCLK 和采样周期算出来实际会有抖动。要精确控制采样率得用定时器触发。思路是定时器按固定频率产生 TRGO 事件ADC 配成外部触发上升沿启动每来一个触发转一轮序列。这样采样率完全由定时器决定和 ADCCLK 无关适合做同步采样或音频采集。配置要点定时器选一个设好 ARR 和 PSC 得到目标频率输出触发选 Update 事件。ADC 的ExternalTrigConv选对应的定时器触发源比如ADC_ExternalTrigConv_T2_TRGO边沿选上升沿。注意这时候连续转换要关掉否则触发一次后会自己连续转定时器就失效了。// 定时器2 TRGO 触发 ADC采样率 定时器更新频率 TIM_TimeBaseInitTypeDef TIM_InitStructure; // 假设 APB1 定时器时钟 84MHz目标 10kHz // PSC83 得 1MHz 计数ARR99 得 10kHz 更新 TIM_InitStructure.TIM_Prescaler 83; TIM_InitStructure.TIM_Period 99; TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); // 选择 Update 事件作为 TRGO TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); TIM_Cmd(TIM2, ENABLE); // ADC 改为外部触发关闭连续转换 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 关键关掉连续 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_TRGO; ADC_InitStructure.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_Rising; ADC_Init(ADC1, ADC_InitStructure);逻辑说明定时器更新频率 84MHz / (831) / (991) 10kHz每个更新事件触发 ADC 转一轮 4 通道序列DMA 照常搬运。这样每轮采样间隔严格 100us抖动在纳秒级。参数上 PSC 和 ARR 的乘积决定频率注意 ARR 是 16 位别超 65535。如果目标频率很低先加大 PSC 再调 ARR。验证方法把一路通道接已知频率方波采一段数据用串口打到上位机看波形周期对得上就说明触发链路通了。我一般还会在触发中断里翻转一个 GPIO用示波器看触发频率和实际采样是否一致这是最直接的后悔药——一旦发现频率不对立刻能定位是定时器还是 ADC 配置的问题。这套方案我用了几年最深的教训是别信「配完就能跑」一定要用示波器或逻辑分析仪验证触发和采样时序。很多玄学问题看一眼波形就清楚了。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

业务部门用了ChatBI后,怎么优化实现持续决策增值

业务部门用了ChatBI后,怎么优化实现持续决策增值

导语 一句话答案:业务部门用ChatBI(观远问数Agent,是基于大语言模型打造的智能数据问答产品,支持业务人员用自然语言快速取数、获取数据洞察)实现持续决策增值,核心是从“单次临时问数”升级为“场景拓展-知…

2026/10/15 18:55:43 阅读更多 →
Tomcat8+Java7+ExtJS WebSocket聊天室实现与避坑指南

Tomcat8+Java7+ExtJS WebSocket聊天室实现与避坑指南

简介:基于Tomcat8Java7ExtJS的WebSocket聊天室实现是一份完整的实时通信入门源码,适合刚接触Java Web与前端开发的读者。项目演示了如何通过JSR 356标准API定义服务端端点,并利用ExtJS组件搭建聊天界面,帮助理解网页端与服务器之间…

2026/10/15 18:55:43 阅读更多 →
cash-ls 完全指南:纯 ES6 跨平台实现的 Unix ls 命令

cash-ls 完全指南:纯 ES6 跨平台实现的 Unix ls 命令

开发工具CLI 【免费下载链接】cash Cross-platform Linux commands in ES6 项目地址: https://gitcode.com/gh_mirrors/ca/cash 点击查看 免费下载 导读 cash-ls 是 Cash 项目(Cross-platform Linux commands in ES6)中负责目录与文件列表的…

2026/10/15 18:54:42 阅读更多 →

最新新闻

AI论文写作要点梳理与前沿研究成果应用实践指南

AI论文写作要点梳理与前沿研究成果应用实践指南

外文文献“白嫖”与高效阅读全攻略 宝子们,是不是一提到下载外文文献就头大? 想找的文献要么全网搜不到,要么下载流程复杂到让人原地爆炸。好不容易在某个小网站找到了资源,又担心版权问题不敢点,更别说有些平台动辄几…

2026/10/15 19:38:32 阅读更多 →
人脸识别开集测试:从闭集幻觉到真实拒识的工程实践

人脸识别开集测试:从闭集幻觉到真实拒识的工程实践

1. 开集与闭集测试:人脸识别系统落地前必须跨过的那道坎 我第一次在某高校实验室参与人脸识别项目验收时,被现场演示结果惊得愣在原地——模型在内部测试集上准确率高达99.2%,可当现场随机邀请三位未录入过人脸的访客走进识别区域&#xff0c…

2026/10/15 19:38:32 阅读更多 →
深耕支付宝收钱码推广领域,拓推客以专业合作服务赋能地推从业者

深耕支付宝收钱码推广领域,拓推客以专业合作服务赋能地推从业者

移动支付已深度融入日常消费与商业经营场景。线下商户对于便捷收款、数字化经营工具及相关支付服务的需求持续受到关注,与之相关的商户拓展和推广服务,也逐渐成为连接支付产品与线下经营场景的重要环节。在这一行业背景下,专业化、规范化的推…

2026/10/15 19:38:32 阅读更多 →
记忆系统演化:从简单缓存到多层认知架构

记忆系统演化:从简单缓存到多层认知架构

在大模型驱动的智能体(AI Agent)系统研发初期,绝大多数团队对“记忆”的理解仅仅停留在会话上下文的传递上。早期的 LangChain 会话记忆组件(如 ConversationBufferMemory 或 ConversationSummaryMemory)通过简单的字符…

2026/10/15 19:38:32 阅读更多 →
观测全景:基于 OpenInference 的可观测体系总结

观测全景:基于 OpenInference 的可观测体系总结

随着大语言模型(LLM)与多智能体(Multi-Agent System)系统从概念验证(POC)全面进入企业生产级核心业务,传统的微服务可观测性体系(Metrics、Logs、Traces)遭遇了前所未有的…

2026/10/15 19:38:32 阅读更多 →
OpenCV+Python轻量物体识别:纯CPU端到端方案

OpenCV+Python轻量物体识别:纯CPU端到端方案

简介:本资源是一套面向Python与OpenCV初学者及计算机视觉入门开发者的自定义物体识别实战教程,聚焦于零基础构建可识别任意目标的检测模型,适用于智能监控、工业质检、教学实验等场景。压缩包共709个文件,含663张标注用JPG图像、2…

2026/10/15 19:37:32 阅读更多 →

日新闻

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/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

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

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

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

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

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

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 9:49:14 阅读更多 →