STM32F407硬件SPI驱动IC_MU芯片实战:从时序配置到DMA批量传输

简介:SPI_IC_MU.rar 是一份面向嵌入式开发者的实战源码工程,聚焦在 STM32F407 微控制器上通过 SPI 接口读取绝对值编码器 IC_MU 的完整实现。STM32F407 基于 ARM Cortex-M4 内核,SPI 作为同步串行总线常用于连接传感器与编码器,而绝对值编码器可直接输出当前位置、无需从零点计数,适合机器人控制、精密机械等对定位精度要求较高的工业场景。资源包共 207 个文件,约 4.86MB,以 c 与 h 源文件、d 依赖文件、o 目标文件、crf 索引文件为主,另含 uvprojx 工程文件、hex 固件、sct 分散加载脚本及少量说明文档,覆盖从工程配置到编译产物的完整链路。源码包含 GPIO 与 SPI 初始化、数据传输与错误检查、中断处理以及位置解析等模块,读者可借此理解 SPI 时序参数配置、编码器通信协议与绝对位置解码思路,并作为 STM32 系列移植参考。目前已有 1575 人学习下载。

1. 从一份 SPI_IC_MU.rar 说起:STM32F407 上怎么把 IC_MU 跑通

前阵子整理硬盘,翻出一个叫 SPI_IC_MU.rar 的压缩包,解压后是一套跑在 STM32F407 上的 SPI 通信例程,核心是驱动一颗代号 IC_MU 的芯片。这类资源在网盘里一抓一大把,但真正能直接编译、接线、跑出数据的没几个。我把它拆开逐行过了一遍,发现它解决的是一个很具体的问题:用 STM32F407 的硬件 SPI 外设,按 IC_MU 的时序要求完成寄存器读写和批量数据搬运,并且把底层驱动和上层业务做了分层。适合谁?手上正好有 F407 开发板、需要对接类似 SPI 从机芯片的嵌入式工程师,或者想找一个结构干净的 SPI 驱动模板来改的新手。下面按「这是什么 → 怎么接线配参数 → 坑在哪 → 怎么验证」的顺序,把这份资源拆透。

2. IC_MU 与 STM32F407 SPI 的匹配逻辑:先搞清时序再动手

2.1 IC_MU 的通信模型与 SPI 模式选择

IC_MU 这类芯片的 SPI 接口,本质是一个从机,靠 CS 片选、SCK 时钟、MOSI/MISO 数据线完成全双工通信。它的寄存器访问通常分两步:先发命令字节(含读写位和地址),再跟数据字节。关键参数是 CPOL 和 CPHA,也就是时钟极性和相位。IC_MU 手册里一般会写明数据在 SCK 的哪个边沿采样、空闲电平是高还是低。STM32F407 的 SPI 外设支持四种模式,对应关系是:

SPI 模式CPOLCPHA采样边沿
Mode 000第一个边沿(上升)
Mode 101第二个边沿(下降)
Mode 210第一个边沿(下降)
Mode 311第二个边沿(上升)

我拿到一份 SPI 从机资料,第一件事不是写代码,而是翻到时序图确认它工作在 Mode 0 还是 Mode 3。IC_MU 常见的是 Mode 0,但不同批次或不同子型号可能不一样,这一步搞错,后面读出来的全是 0xFF 或 0x00,非常玄学。确认模式后,再决定数据位宽是 8 位还是 16 位、MSB 先行还是 LSB 先行。F407 的 SPI_CR1 寄存器里 CPOL、CPHA、DFF、LSBFIRST 这几个位就是干这个的。

2.2 为什么用硬件 SPI 而不是软件模拟

有人图省事,用 GPIO 翻转模拟 SPI 时序。短距离、低速场景确实能跑,但 IC_MU 如果要求几 MHz 以上的时钟,软件模拟的抖动和 CPU 占用就会成为瓶颈。STM32F407 的硬件 SPI 挂在 APB2 上,最高时钟可以到 84MHz,分频后轻松跑到十几 MHz。硬件 SPI 的移位寄存器自动处理收发,CPU 只需要读写 DR 寄存器,配合 DMA 还能把大批量数据搬运的负担也卸掉。这份资源里用的是 SPI1,引脚是 PA5(SCK)、PA6(MISO)、PA7(MOSI),CS 用了一个普通 GPIO 比如 PA4 来控制。选 SPI1 是因为它时钟快、DMA 通道成熟,例程多,出了问题好查。

2.3 时钟分频与波特率的实际计算

SPI 的波特率由 APB 总线时钟除以预分频系数决定。F407 的 SPI1 在 APB2 上,APB2 通常跑 84MHz。SPI_CR1 里的 BR[2:0] 三位控制分频:

BR[2:0]分频系数SPI1 时钟(APB2=84MHz)
000242 MHz
001421 MHz
010810.5 MHz
011165.25 MHz
100322.625 MHz
101641.3125 MHz
110128656.25 kHz
111256328.125 kHz

IC_MU 能接受多高的 SCK,必须查它的 datasheet。我一般先保守用 5.25MHz 或 2.625MHz 调通,再逐步往上加,用示波器看波形有没有振铃或边沿变缓。分频设太小,信号完整性容易翻车;设太大,吞吐上不去。这份资源默认用的是 16 分频,也就是 5.25MHz,属于稳妥起步值。

3. 把驱动跑起来:从工程结构到收发函数

3.1 工程目录与关键文件说明

解压 SPI_IC_MU.rar 后,目录结构大致是这样:

SPI_IC_MU/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── stm32f4xx_hal_conf.h │ │ └── ic_mu.h // IC_MU 驱动头文件 │ └── Src/ │ ├── main.c │ ├── stm32f4xx_it.c │ └── ic_mu.c // IC_MU 驱动实现 ├── Drivers/ │ ├── STM32F4xx_HAL_Driver/ │ └── CMSIS/ └── SPI_IC_MU.ioc // CubeMX 工程文件

核心是 ic_mu.c 和 ic_mu.h,里面封装了初始化、寄存器读写、批量传输三个层次的函数。main.c 里只做调用和结果打印,分层比较清楚。如果你用 Keil 或 STM32CubeIDE,直接打开 .ioc 重新生成一遍代码也能对上,但要注意 CubeMX 版本差异可能导致 HAL 库函数名微调。

3.2 SPI1 初始化:CubeMX 配置与手写寄存器对照

用 CubeMX 配 SPI1 的步骤:在 Connectivity 里选 SPI1,Mode 选 Full-Duplex Master,Hardware NSS Signal 选 Disable(因为 CS 用软件 GPIO 控制),Data Size 选 8 Bits,CPOL 和 CPHA 按 IC_MU 要求选,Prescaler 选 16 分频,First Bit 选 MSB。生成代码后,MX_SPI1_Init 函数里会填好 hspi1 结构体。对应的关键寄存器配置逻辑是:

// SPI1 初始化核心参数(HAL 库封装后的等效寄存器操作) hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // 主机模式 hspi1.Init.Direction = SPI_DIRECTION_2LINES; // 全双工 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8 位数据 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0,即 Mode 0 hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制 CS hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 16 分频 hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // MSB 先行 hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;

这段代码里,CLKPolarity 和 CLKPhase 必须和 IC_MU 的时序图严格对应。NSS 设为软件模式后,PA4 这个 GPIO 要单独配成推挽输出,初始拉高。BaudRatePrescaler 先给 16,调通后再改小。FirstBit 大多数 SPI 从机都是 MSB 先行,但 IC_MU 如果手册写了 LSB,这里就得改。

3.3 寄存器读写函数:命令字节的拼装

IC_MU 的寄存器操作通常是一个命令字节加一个数据字节。命令字节的最高位是读写标志,比如 0 表示写、1 表示读,低 7 位是地址。下面是一个典型的读寄存器函数:

// 读 IC_MU 单个寄存器 uint8_t IC_MU_ReadReg(uint8_t addr) { uint8_t tx_buf[2]; uint8_t rx_buf[2]; tx_buf[0] = 0x80 | (addr & 0x7F); // 最高位置 1 表示读,低 7 位地址 tx_buf[1] = 0x00; // dummy 字节,用来产生时钟 IC_MU_CS_LOW(); // 拉低片选 HAL_SPI_TransmitReceive(&hspi1, tx_buf, rx_buf, 2, 100); IC_MU_CS_HIGH(); // 拉高片选 return rx_buf[1]; // 第二个字节才是寄存器值 }

逻辑说明:SPI 是全双工,发一个字节的同时会收一个字节。第一个字节发命令,从机在第二个字节的时钟周期把寄存器值放到 MISO 上,所以 rx_buf[1] 才是有效数据。tx_buf[1] 发什么无所谓,0x00 或 0xFF 都行,它只是用来产生时钟。参数 addr 是 7 位地址,& 0x7F 是防止高位污染。CS 拉低和拉高之间必须完整包住这次传输,中间不能被打断,否则 IC_MU 会认为通信异常。

写寄存器类似,只是命令字节最高位清 0:

// 写 IC_MU 单个寄存器 void IC_MU_WriteReg(uint8_t addr, uint8_t data) { uint8_t tx_buf[2]; tx_buf[0] = addr & 0x7F; // 最高位 0 表示写 tx_buf[1] = data; IC_MU_CS_LOW(); HAL_SPI_Transmit(&hspi1, tx_buf, 2, 100); IC_MU_CS_HIGH(); }

这里用 HAL_SPI_Transmit 而不是 TransmitReceive,因为写操作不关心 MISO 返回什么。超时参数 100 是毫秒,实际几微秒就完成了,给大一点防止偶发阻塞。

3.4 批量数据搬运:DMA 配置与双缓冲思路

IC_MU 如果有 FIFO 或连续地址读取功能,单字节循环读效率太低。这份资源里用 DMA 做批量搬运,SPI1 的 RX 通道是 DMA2_Stream0,TX 是 DMA2_Stream3。配置步骤:在 CubeMX 的 DMA Settings 里给 SPI1_RX 和 SPI1_TX 各加一个通道,模式选 Normal 或 Circular,数据宽度都选 Byte。然后调用 HAL_SPI_TransmitReceive_DMA。关键点是 DMA 传输完成中断里要拉高 CS,不能在调用完 DMA 启动函数后立刻拉高,否则数据还没搬完 CS 就失效了。常见做法是用一个标志位,在 HAL_SPI_TxRxCpltCallback 回调里置位,主循环检测到标志再拉高 CS。

// DMA 批量读 IC_MU 连续地址 void IC_MU_ReadBurst(uint8_t start_addr, uint8_t *buf, uint16_t len) { uint8_t cmd = 0x80 | (start_addr & 0x7F); IC_MU_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); // 先发命令字节 HAL_SPI_Receive_DMA(&hspi1, buf, len); // DMA 接收数据 // CS 在 DMA 完成回调里拉高,不能在这里立刻拉高 }

参数 len 是要读的字节数,buf 是接收缓冲区,必须保证在 DMA 完成前不被释放或改写。start_addr 是起始地址,IC_MU 内部地址会自动递增。这个函数只负责启动,实际完成靠回调,所以调用后不能马上用 buf 里的数据,要等标志位。

4. 避坑与排查:SPI 调不通时先查这五条

4.1 读出来全是 0xFF 或 0x00

现象:调用读寄存器函数,返回值始终是 0xFF 或 0x00,换地址也没变化。原因:MISO 线没接好、从机没供电、CS 没拉低,或者 SPI 模式配错导致采样边沿对不上。解决:先用示波器看 SCK 和 MOSI 有没有波形,再看 MISO 在 CS 有效期间有没有电平变化。如果 MISO 一直高或一直低,检查 IC_MU 的供电和使能引脚。模式问题就对照时序图改 CPOL/CPHA,Mode 0 和 Mode 3 最容易混。

4.2 偶尔读对一次,之后全错

现象:上电第一次读正常,连续读几次后数据错乱。原因:CS 拉高和拉低之间间隔太短,IC_MU 还没完成内部状态复位;或者两次传输之间 SCK 出现了额外脉冲。解决:在 CS 拉高后加一个微秒级延时再拉低,给从机留恢复时间。另外检查 GPIO 初始化,CS 引脚如果被复用成其他功能,会出现意外脉冲。我一般会在 CS 操作前后各加一个 __NOP() 或几微秒延时,血泪经验。

4.3 DMA 传输数据只对了一半

现象:用 DMA 读 16 字节,前 8 字节对,后 8 字节是旧数据或 0。原因:DMA 传输完成中断优先级低于其他中断,回调被延迟,CS 提前拉高导致后半段没时钟。解决:把 DMA 中断优先级设到较高,或者在启动 DMA 后轮询 DMA 传输完成标志再拉高 CS,不依赖回调。另外检查 DMA 的 FIFO 和突发配置,SPI 的 DMA 一般用单次传输,不要开 FIFO 突发。

4.4 波特率一高就通信失败

现象:分频设 4 或 2 时,读数据出错率飙升。原因:杜邦线太长、没有屏蔽、SCK 边沿振铃,或者 IC_MU 本身不支持那么高的时钟。解决:先降回 16 分频确认功能正常,再逐步提高。硬件上缩短接线、加地线包围、在 SCK 上串 22 到 100 欧姆电阻。如果 IC_MU 手册标了最高 SCK 频率,不要超。

4.5 编译通过但运行卡死

现象:程序下载后卡在 HAL_SPI_Transmit 或 HAL_SPI_TransmitReceive 里出不来。原因:SPI 外设时钟没使能、GPIO 复用没配、或者从机把 MISO 拉死导致主机等不到接收完成。解决:检查 __HAL_RCC_SPI1_CLK_ENABLE() 有没有调用,GPIO 的 Alternate Function 是不是 AF5。如果 MISO 被从机拉低不放,主机在全双工模式下会一直等,可以改用只发不收的模式,或者加超时处理。

5. 验证与进阶:用逻辑分析仪抓一次完整时序

调 SPI 最直接的手段是逻辑分析仪,几十块的那种就够。抓 SCK、MOSI、MISO、CS 四根线,采样率设到 24MHz 以上。触发条件设 CS 下降沿,抓一次读寄存器操作。正常波形应该是:CS 拉低,SCK 出现 16 个脉冲(两个字节),第一个字节 MOSI 上是 0x80|addr,第二个字节 MISO 上是从机返回的数据,CS 拉高。如果 SCK 脉冲数不对,说明数据长度配错;如果 MISO 数据在错误的边沿变化,说明 CPHA 反了。

进阶用法是把 IC_MU 的驱动抽象成函数指针结构体,方便换不同芯片:

typedef struct { uint8_t (*read_reg)(uint8_t addr); void (*write_reg)(uint8_t addr, uint8_t data); void (*read_burst)(uint8_t addr, uint8_t *buf, uint16_t len); } IC_MU_Driver; IC_MU_Driver ic_mu = { .read_reg = IC_MU_ReadReg, .write_reg = IC_MU_WriteReg, .read_burst = IC_MU_ReadBurst, };

这样上层业务只依赖 ic_mu 这个结构体,换芯片时只改底层实现,不动业务代码。验证方法上,我习惯先写一个寄存器回环测试:往一个可读写的测试寄存器写 0x5A,再读回来比对,对了再测 0xA5,两个都过说明读写通路没问题。然后再测连续地址的批量读写,确认地址递增逻辑正确。最后用逻辑分析仪抓一次完整时序存档,以后出问题有对照。

从那以后我每次拿到新的 SPI 从机芯片,都强制先做三件事:翻时序图确认模式、用逻辑分析仪抓一次上电初始化波形、写一个寄存器回环测试。这三步走完,后面基本不会翻车。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

AI编程工具选型指南:Cursor、Lingma、CodeX实测对比与体验

AI编程工具选型指南:Cursor、Lingma、CodeX实测对比与体验

上个月,我给自己布置了一个硬核任务:把手头三款AI开发工具——CodeX、Cursor、Lingma——全部装进真实项目里各跑两周,用来回答一个别人反复问我、我自己也一直想知道答案的问题:AI编程工具到底该怎么选?这个念头源自一…

2026/10/20 4:55:53 阅读更多 →
YOLO表情识别系统实战:单模型检测人脸表情与PySide6界面开发

YOLO表情识别系统实战:单模型检测人脸表情与PySide6界面开发

这些年陆陆续续接过不少目标检测的活,最常被问到的就是“YOLO能不能做表情识别?”——正经做表情的论文基本都用分类网络,但把人脸检测和表情分类拆成两段,在工程落地时反而麻烦:你要先跑一个人脸检测器,把…

2026/10/20 4:55:53 阅读更多 →
lingbot-map:面向语言智能体的语义映射系统设计与实现

lingbot-map:面向语言智能体的语义映射系统设计与实现

项目标题“lingbot-map”目前未提供任何正文描述、关键词或摘要信息,仅有一个名称。根据任务定义,我必须仅基于项目标题进行深度拆解与延展——这是核心约束,也是专业能力的真正考验。在缺乏上下文的情况下,“lingbot-map”是一个…

2026/10/20 4:54:53 阅读更多 →

最新新闻

Keras大文件训练内存溢出解决方案:三层数据流架构

Keras大文件训练内存溢出解决方案:三层数据流架构

1. 为什么“大文件无法一次性读入内存”是Keras用户绕不开的坎?Keras作为高层深度学习API,最大的优势是简洁、直观、上手快——写个model.fit(X_train, y_train)就能跑通一个模型。但这个看似简单的接口背后,藏着一个被新手反复踩坑、被老手默…

2026/10/20 5:39:13 阅读更多 →
为什么代码写得对却不值钱?工程师价值重估指南

为什么代码写得对却不值钱?工程师价值重估指南

1. 这不是代码批判,而是工程师价值重估的现场“你的代码不值钱,你为什么这么写才值钱”——这句话刚在技术社区刷屏时,我正帮某高校实验室重构一套运行了七年的教学评估系统。那套代码语法完全合规,单元测试覆盖率92%,…

2026/10/20 5:39:13 阅读更多 →
Playwright驱动浏览器自动化:破解无限瀑布流动态加载的爬虫实战

Playwright驱动浏览器自动化:破解无限瀑布流动态加载的爬虫实战

直接开始。玩爬虫的人多少都遇到过这种场景:目标网站数据明明就摆在页面上,但用 requests 怎么抓都只能拿到首页那一小段,往下翻页面,心跳都跟着滚轮一起加速,可手里的代码就是纹丝不动。小红书这种重 JS 交互的平台&a…

2026/10/20 5:39:13 阅读更多 →
32位系统内存补丁实战:PatchPae2让老电脑用满4G以上内存

32位系统内存补丁实战:PatchPae2让老电脑用满4G以上内存

简介:PatchPae2补丁面向32位的Windows 8.1操作系统,通过启用系统物理地址扩展功能,突破4GB内存识别上限,解决系统只能使用部分物理内存的痛点,适合受内存容量制约的用户、开发者和运维人员;补丁以修改内核方…

2026/10/20 5:39:13 阅读更多 →
MCP工具调用机制与优化实践:从原理到工程落地

MCP工具调用机制与优化实践:从原理到工程落地

1. MCP 工具调用的核心机制拆解1.1 从“对话”到“行动”:MCP 到底解决了什么问题MCP(Model Context Protocol)现在几乎是 AI 应用开发绕不开的协议了,但很多刚接触的人容易把注意力放在“这是什么”上面,忽略了一个更…

2026/10/20 5:39:13 阅读更多 →
原生JavaScript实现贪吃蛇:核心逻辑与Canvas渲染实战

原生JavaScript实现贪吃蛇:核心逻辑与Canvas渲染实战

最近整理硬盘上的旧文件夹,翻出一个好几年前写的JavaScript贪吃蛇。当时是某段时间练手用的,代码写得很粗糙,注释也不全,但核心逻辑基本完整,跑起来没有问题。这几天花时间把代码理清爽、补上注释、顺手修掉几个当年没…

2026/10/20 5:38:13 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →