QSPI PSRAM稳定运行实战:刷新调度、时序余量与控制器重构

“(3)”这个后缀说明它是系列里的第三篇。前两篇我们完成了APS6404L-3SQR这颗QSPI PSRAM的基本初始化、寄存器读写和裸读写验证,当时以为只要ID能读、数据能写就算打通了。真正开始跑连续数据流之后才发现,距离“能用”还有一大截,这一篇记录的就是从“能读能写”到“敢拿来当内存用”之间踩过的坑、推倒重来的部分和最终稳定运行的控制器结构。内容集中在三个核心问题上:刷新调度、时序余量、控制器模块化重构。适合那些已经把PSRAM点亮的开发者参考,尤其是准备把这类伪静态随机存储器用于图像缓存、音频缓冲等数据流场景的朋友。

1. 初版控制器能跑测试向量,却扛不住真实数据流

1.1 三个典型症状与排查方向

我们当时的应用场景很普通,图像采集模块把一帧数据写入PSRAM,然后图形输出模块从同一个地址空间把数据读出来显示。结果并不是完全不显示,而是花屏、局部错位、长时间运行后整块数据消失。

整理下来主要是三个现象:

  • 固定数据测试(比如全0x55、0xAA交替)可以过,但随机数测试偶发字节错误;
  • 运行一段时间后,某个固定地址段的数据全部变成0xFF或0x00;
  • 掉电重新上电后,某些地址写入不生效,像是芯片自己“忘掉”了之前的状态。

这三个症状对应完全不同的根因:随机字节错误多半是时序余量不够,固定区域数据消失基本可以锁定刷新缺失,写入不生效则要回到初始化流程查寄存器配置。这也是排查顺序的依据——先解决初始化配置问题,再解决刷新问题,最后才是时序调优。因为前两个问题如果存在,时序调优做得再细也是白搭。

1.2 QPI模式下的地址位序:驱动里最容易埋雷的地方

先讲一个我们在初版驱动里真实踩过的坑。SPI接口的存储芯片通常按MSB优先传输地址,QSPI PSRAM在QPI模式下地址字段也是同样的规则。但QPI模式下一次并行传4个bit,地址线3字节共23位有效,部分驱动写惯了普通SPI的逐bit移位,到了QPI下仍然按单线思路处理,结果地址在bit级别上发生了错位。

这种错位有个很迷惑人的特点:单个字节的读写测试看起来是正常的,因为错位后的地址仍然是一个合法地址,数据能写进去也能读出来。只有当你把地址当作连续区域访问,比如按顺序往0x000000到0x00FFFF写满递增数据,再按同样顺序读回来时才会发现数据出现在错误的位置。

验证方法很简单:用两个已知地址,一个写0x55,另一个写0xAA,然后反向读取。如果交叉读到了对方地址的数据,基本可以断定是地址位序或位宽处理出错。排查时对照芯片手册里Read/Write命令的地址时序图,把每一位的对应关系列一张表,再跟RTL里移位寄存器的顺序逐一比对。

1.3 管脚维持时间:看似能过,实际上余量已经耗尽

地址错位解决之后,随机字节错误仍然偶发。这次问题在采样窗口上。QSPI模式下4根双向数据线SIO[3:0]在SCLK的边沿附近翻转,FPGA内部还要经过IO单元、布线资源才能到达触发器的D端。如果PCB走线不等长、IO逻辑资源布局分散,再加上温度变化,采样时刻就会向窗口边缘移动。

数据手册里那些参数,什么输出有效时间、输出维持时间,看起来只是理论值,但在QSPI PSARM这种高速翻转的接口上,它们直接决定了控制器时钟能否稳定采样。我们当时的做法是先用片外的等长约束约束了SIO到SCLK的走线组,然后在FPGA里给SIO信号加了输入延迟约束。建议直接把QSPI的采样时钟和FPGA系统时钟设计成相位可调的关系,而不是赌布线延迟足够小。实测下来,仅仅把采样点从默认的上升沿移到下降沿,字节错误率就明显下降。这属于STA时序收敛的范畴,可以在综合工具里用report_timing检查路径余量。

2. 刷新机制:不主动刷,PSRAM就是一块会丢数据的SRAM

2.1 刷新不是“可选优化”,而是必需功能

QSPI PSRAM本质上是DRAM颗粒套了个SPI接口的外壳,靠电容存储电荷,电荷会漏,所以必须周期性刷新。这一点和真正的SRAM完全不同。很多人拿到芯片手册先看读写命令时序,却忽略了刷新章节,初版驱动跑挂大多跟这个有关。

刷新窗口和行数的关系决定了刷新频率。以常见的64Mbit级PSRAM为例,手册通常给出整个存储阵列必须在8ms左右内完成一轮刷新,内部行数有些是512行、有些更多,具体数字以你手里那颗芯片的手册为准,计算公式是固定的:平均刷新间隔 = 刷新窗口 / 内部行数。如果窗口是8ms、行数是512行,那平均 15.625μs 就要完成一行刷新;如果行数是8192行,平均间隔就缩短到 1μs 不到。算出这个数字之后,再换算成你的SPI时钟下的周期数,就能得到一个用于驱动状态机的刷新周期计数器。

当时我们的SPI引擎工作在50MHz,按我们这颗芯片的实际行数折算下来,大约每几百个时钟周期就需要插入一次刷新。这个数量级完全不能靠“偶尔发一条命令”蒙混过关,必须做成自动调度。

2.2 显式刷新命令与自动刷新:我选择前者

APS6404L-3SQR这一类PSRAM通常支持两种刷新方式:一种是通过配置寄存器开启自动刷新,芯片在空闲时自己处理;另一种是主机在总线空闲时主动发送刷新命令。

我们最终采用的是显式刷新。原因很简单,自动刷新的具体触发时机由芯片内部决定,而我们的控制器状态机需要精确知道每次刷新事务何时开始、何时结束,才能在刷新期间避免新的读写命令。如果让芯片内部自动刷新,控制器只靠等待时间兜底,一旦刷新期间来了新的读请求,行为就变得不可预测。

刷新命令本身很短,在QPI模式下通常就是一条命令、CS拉低再拉高的过程。真正要设计的是调度策略:什么时候允许插队、一次刷几行、用户请求等待多久可以打断刷新。我们的策略是刷新请求优先,但每次只执行一笔刷新事务;如果刷新请求到达时总线正忙,则置一个刷新挂起标志,等当前读写事务结束后的第一个总线空闲时刻执行。这样既保证了刷新间隔,又不会无限期延迟用户请求。

这部分的RTL可以做到很简单,关键是刷新请求和用户请求不要互相把对方饿死。给一个示意逻辑:

// 刷新定时器:按SPI时钟计数,到达刷新间隔时拉高refresh_pending module refresh_timer #( parameter REFRESH_INTERVAL = 780 // 由刷新窗口和行数折算得到 )( input wire clk, input wire rst_n, output reg refresh_pending ); reg [15:0] cnt; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 16'd0; refresh_pending <= 1'b0; end else if (cnt == REFRESH_INTERVAL - 16'd1) begin cnt <= 16'd0; refresh_pending <= 1'b1; end else begin cnt <= cnt + 16'd1; refresh_pending <= 1'b0; end end endmodule

这个refresh_pending信号引入主控状态机后,仲裁逻辑就成了一个简单的优先级判断:空闲状态下先看刷新挂起位,再去响应新的读写请求。刷新命令执行完毕的同时,重新武装计数器。

2.3 PASR局部刷新:省电的代价是丢数据

APS6404L-3SQR这类芯片还支持局部阵列自刷新(Partial Array Self Refresh),通过配置寄存器圈定哪些bank需要刷新,未圈定的区域在低功耗模式下直接不刷。这在低功耗待机场景下很有用,但代价是未刷新区域的数据无法保证留存。

我们一开始在调试长时间空闲后的数据丢失时,误以为是刷新命令没发出去,排查半天才发现是寄存器里误配了局部刷新位,把一部分bank排除在刷新范围之外。这里提醒一句:如果你的应用不需要低功耗待机模式,就确认寄存器配置里没有引入局部刷新使能位,否则整个存储区域的数据可靠性都是没有保障的。

3. 控制器重构:从“一次读写一个大状态机”到“可配置事务引擎”

3.1 为什么初版的状态机无法继续扩展

初版控制器为了快速验证,读写、写寄存器、读ID都用独立的大状态机实现。代码倒是直白,但问题很快暴露:每新增一种操作(比如刷新、进入QPI、配置MR寄存器),都要新写一段状态机,而且不同操作之间的公共部分(CS拉低、逐字节发送、等待延迟)被复制粘贴了好几遍。一旦需要调整整条SPI时钟策略,就要找遍每个状态机逐一修改。

其实SPI类接口的事务模式高度统一:一条命令、几字节地址、可选dummy周期、一段数据。与其为每个操作单独写状态机,不如把一次事务描述成一串执行阶段,用一个通用的解释器状态机去执行。这种做法在很多成熟的SPI控制器IP里都能看到,只是早期觉得“不过是一个简单接口,用不上”,结果被可维护性狠狠教育了。

3.2 事务描述符:把SPI时序交给配置,而不是硬编码

我们把一次QSPI访问拆成若干阶段,每个阶段包含类型、传输字节数、预期的数据方向、等待周期数。

阶段类型说明配置参数
PH_CMD发送1字节命令码cmd值
PH_ADDR发送地址字段,通常3字节地址寄存器
PH_DUMMY等待N个周期,不驱动数据线等待周期数
PH_DATA_OUT写出数据,QPI模式一次4bit数据来源FIFO
PH_DATA_IN读入数据,按列宽拼装成字节数据写入FIFO
PH_DONE事务结束,CS拉高无

主控制器做的事情很机械:按描述符取出下一个阶段,执行完毕再取下一条。不同的操作(读、写、刷新、读ID)只是描述符内的阶段组合不同。这样处理的好处是,调整时序时只需要改阶段的等待周期数,而不必动状态机的跳转逻辑。

状态机核心可以压缩成这样一个结构:

case (phase_sel) PH_CMD: begin sck_posedge_count <= 0; if (bit_count == 7) next_phase = PH_ADDR; end PH_ADDR: begin // 3字节地址,按MSB优先逐对发送,QPI下每次发送4bit end PH_DATA_IN: begin // 每个字节需要8个SCLK边沿,边沿到来时采样SIO end endcase

这种重构带来的附带收益是,后续如果要从QSPI SDR升级到DDR模式,只需要在阶段执行逻辑中增加对同一个时钟周期内双沿采样的处理,命令/地址/数据的组合描述完全不用变。

3.3 Read/Write Latency:等待周期必须显式编排

QSPI PSRAM和普通NOR Flash有一个关键差别:它存在读写延迟(Latency),也就是发出命令和地址之后,数据并不会立刻出现在总线上。这个延迟由芯片的配置寄存器决定,通常读延迟和写延迟还不一样。控制器如果在发送完命令地址后立即开始采样数据,读到的就是上一笔事务的残留数据。

这也是初版驱动随机字节错误的另一个来源。当我们把Latency周期数作为参数配置进事务描述符之后,问题立刻消失。建议在RTL中把所有等待周期做成寄存器配置项,初始化流程里从芯片中读取当前生效的延迟配置,再转成等待周期数。千万不要在代码里写死某个数值,因为QPI与SPI模式、单倍率与双倍率模式下延迟参数往往是不同的。

3.4 数据侧的FIFO问题:系统时钟和SPI时钟注定不是同一个世界

重构期间我们还补上了FIFO。之前没有FIFO的原因是系统总线和SPI引擎都跑在同一个FPGA系统时钟下,以为不需要跨时钟域。实际上真正的瓶颈在burst传输的流量整形上:系统侧可能一次请求16字节,而SPI侧一次只能推进几个bit;反过来,读请求发出后,数据若干周期后才陆续到达,这一进一出的节奏差异如果没有缓冲承接,主控逻辑要么被频繁反压,要么丢数据。

写数据侧放置写入FIFO,读数据侧放置读取FIFO,SPI引擎负责按可用空间从FIFO取数或者向FIFO写数。FIFO深度按最坏情况估算,简单版本:一次系统侧写请求的字节数乘以系统时钟与SPI有效时钟的比值,再乘上(1+Latency占比),取整到就近的2的幂。我们当时的应用,深度64字节足够覆盖一次16字节突发加上Latency等待的空档,同时可以把系统侧的反压请求数量控制在可接受范围。

4. 实测数据、常用调试手段与后续扩展思路

4.1 不同工作模式下的性能对比

重构完成后,我对同一颗APS6404L-3SQR在几种模式下做了简单吞吐测试,这里列出的是相对参考值,不同FPGA平台和时钟频率下会略有差别:

模式SCLK频率理论带宽实测读带宽实测写带宽
SPI 1-1-125MHz3.1 MB/s2.4 MB/s2.5 MB/s
QPI SDR50MHz25 MB/s18 MB/s19 MB/s
QPI DDR50MHz50 MB/s33 MB/s30 MB/s

实测带宽普遍比理论值低20%~30%,这是正常的。原因包括命令地址开销、Latency等待、刷新事务插入,以及FIFO空/满时的气泡周期。如果你的应用对带宽有硬指标,应把这些开销提前算进去,而不是直接按理论值规划。

4.2 调试手段:抓时序、跑误码、查刷新

调试QSPI PSRAM控制器,我常用的三件套是逻辑分析仪抓取、误码率测试脚本、刷新窗口与错误时间的关联分析。

逻辑分析仪主要用来抓CS、SCLK、SIO0~SIO3,核对SPI事务的命令字节和地址字段是否符合预期。特别是在QPI模式下,四根数据线的值拼在一起才是完整的半字节,单看一根线毫无意义,所以抓取时务必把SIO0~SIO3作为一个总线整体查看。

误码率测试不是只跑一遍,而是长时间循环写读固定伪随机序列,比如几十亿字节跑完为止。一旦出现错误,记录下错误地址和错误时间戳,再去对照刷新定时器的触发点。如果错误密集出现在刷新命令之后,大概率是刷新事务与读写事务并行导致的总线冲突;如果错误出现在写入后立即读取的阶段,大概率是Latency配置或采样点问题。

4.3 后续可以做的优化

控制器稳定之后,还有几个方向值得继续投入。一个是在QPI DDR模式下做完整的时序收敛,可以在同等芯片成本下把带宽翻倍,适合图像类应用;另一个是双片PSRAM并联,扩展位宽或容量,但需要确认芯片支持多片通过独立CS挂到同一组SIO总线上,并且总线驱动强度要重新评估;再有一个是把PASR局部刷新和自刷新流程做成可配置电源管理状态,让这块内存有机会进入低功耗待机模式。

5. 写在最后:这件事和时序、刷新、抽象有关,但不止于此

整个调试过程中,最有价值的习惯是把所有“偶发”当作“必然”来对待。随机字节错误看起来像运气问题,追到根子上要么是时序余量不足,要么是状态机漏掉了某个Latency周期,要么是刷新和其他事务发生了不该发生的重叠。这类问题有一个共同特点:它们只在长时间运行或高速数据流下才会暴露,恰好是肉眼检查代码最难发现的一类缺陷。

另外想分享一个细节:刷新定时器的分频系数应该和SPI时钟来自同一个时钟源,不要用独立的慢速时钟去产生刷新请求,否则两个时钟域之间的异步事件跑进状态机会引入全新的时序问题。这块我在初版里踩过,后来统一到同一时钟树上才彻底消停。如果有在设计QSPI PSRAM控制器的朋友,建议直接从模块化事务引擎这个结构起步,刷新、延迟、FIFO这些能力一开始就留好接口,会省掉后面大量的返工时间。

相关新闻

SLIME强化学习框架:从工程效率到实验管理的全面解析

SLIME强化学习框架:从工程效率到实验管理的全面解析

直接说结论&#xff1a;如果在强化学习项目里被“训练跑得慢、结果复现难、代码越写越乱”这三座大山压得喘不过气&#xff0c;那么SLIME这类框架就是拿来解决问题的。这几个月我一直在用SLIME&#xff08;某开源强化学习框架的代号&#xff09;跑各种连续控制实验&#xff0c;…

2026/10/20 7:00:49 阅读更多 →
同一个大模型,为什么换一套接口就能更会做事?

同一个大模型,为什么换一套接口就能更会做事?

同一个大模型&#xff0c;为什么换一套接口就能更会做事&#xff1f;你大概率遇到过这种怪事&#xff1a;同一个模型&#xff0c;同一个问题&#xff0c;在网页对话框里问它&#xff0c;回答得四平八稳、滴水不漏&#xff0c;但也没什么用&#xff1b;换到某个工具或者应用里再…

2026/10/20 7:00:49 阅读更多 →
AI毕设选题指南:100个可落地题目与答辩避坑建议

AI毕设选题指南:100个可落地题目与答辩避坑建议

很多同学来问人工智能方向的毕设选题&#xff0c;我干脆把这几年当评审和带项目时见过的选题思路都整理出来&#xff0c;按方向拆了一百个左右可落地的题目&#xff0c;每个都附上为什么能做、难在哪、怎么包装创新点。这篇文章不是让你抄一个标题就完事&#xff0c;而是帮你看…

2026/10/20 7:00:49 阅读更多 →

最新新闻

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

Python数据分析与可视化源码包实战:从数据清洗到图表输出的完整链路拆解

简介&#xff1a;这份资源面向希望系统掌握Python数据分析与可视化的学习者&#xff0c;覆盖从编程基础到实战项目的完整技能链路&#xff0c;适合高校学生、转行者及需要处理结构化数据的从业者。压缩包共162个文件&#xff0c;约31.27MB&#xff0c;以67个ipynb实验代码、11个…

2026/10/20 7:58:20 阅读更多 →
QClaw实测:基于大模型的微信数字分身从入门到避坑

QClaw实测:基于大模型的微信数字分身从入门到避坑

1. 项目概述与产品定位拆解1.1 先说清楚&#xff1a;QClaw到底是个什么东西大概几个月前我就留意到了QClaw这个项目&#xff0c;那会儿还是内测灰度阶段&#xff0c;圈子里已经有人开始传“微信里的AI分身”这个概念。这几天全面公测一开&#xff0c;群里热闹得不行。我用了一周…

2026/10/20 7:58:20 阅读更多 →
AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

AI Agent沦为“流氓智能体”:权限失控、提示注入与责任归属全解析

前阵子一个做零售的朋友跟我吐槽&#xff1a;他们给客服智能体开了退单权限&#xff0c;结果一个晚上它因为一位客户反复输入“给我退钱”&#xff0c;把同一个小订单退了17次&#xff0c;财务第二天早上脸都绿了。这事本身不大&#xff0c;但它特别能说明问题——当智能体从一…

2026/10/20 7:58:20 阅读更多 →
自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化失败了怎么办?从麦睿菱项目看AI自动化恢复与复盘

自动化翻车这件事&#xff0c;做AI落地的团队迟早都会遇到一次。麦睿菱是我们内部一个AI自动化改造项目的代号&#xff0c;前后推进了三个多月&#xff0c;结果在试运行阶段就栽了跟头。今天想把它完整复盘一下&#xff0c;重点不是讲成功经验&#xff0c;而是聊聊自动化失败之…

2026/10/20 7:58:20 阅读更多 →
Agent模型选型不再难:运行时路由规则设计与实现

Agent模型选型不再难:运行时路由规则设计与实现

1. 为什么模型选型这件事&#xff0c;最后都变成了路由问题做过 Agent 项目的人大概都有过这么一个阶段&#xff1a;一开始兴致勃勃地对比各家模型&#xff0c;跑分、看榜单、测延迟、算成本&#xff0c;折腾了两周&#xff0c;最后在代码里写下了一个硬编码的模型名字。然后项…

2026/10/20 7:58:20 阅读更多 →
OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

OpenCV基础扫盲:图片数组、BGR颜色与视频帧核心概念详解

这一篇原本是写给组里新同学的一份内部分享稿&#xff0c;后来想想干脆整理成公开文章。原因是带了好几次新人&#xff0c;发现大家第一次接触OpenCV时&#xff0c;最痛苦的根本不是算法&#xff0c;也不是深度学习模型&#xff0c;而是连“图片长什么样”“颜色为什么是反的”…

2026/10/20 7:57:19 阅读更多 →

日新闻

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实现详解

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

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

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

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

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

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

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在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 阅读更多 →