news 2026/10/4 1:16:37

Lattice FPGA入门:从工具链到点亮LED的完整开发流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Lattice FPGA入门:从工具链到点亮LED的完整开发流程

做FPGA的人几乎绕不开Xilinx和Altera(现在叫Intel PSG),这两个大厂垄断了绝大多数教学资源、开源项目和岗位需求。但在实际工程项目里,还有一家公司一直活得很好,甚至在很多细分领域是绝对的王者——Lattice(莱迪思半导体)。我最早接触Lattice是因为一个便携式设备项目,整板功耗压得很死,FPGA必须随时待机但功耗不能过百毫瓦,挑来挑去最后选了Lattice的ICE40系列。从那以后,我在好几款工业产品里都用Lattice做逻辑核心,对这个“小厂”算是摸得比较透了。

这篇先把Lattice的家底和基础开发流程讲清楚。适合刚接触FPGA、但不想一上来就掉进Vivado和Quartus庞杂体系里的新手,也适合做低功耗或小封装产品的硬件工程师用来建立选型思路。内容基于我实际用Diamond和Radiant做项目的经验,会尽量把操作细节和踩过的坑都写出来。

1. Lattice到底是什么:低调但能打的FPGA二线巨头

1.1 不只是“便宜版FPGA”

很多人把Lattice理解成“FPGA里的性价比替代品”,这个说法对,但只说对了一半。Lattice真正厉害的地方在于它对“低功耗、小封装、快启动”这三个指标的极致追求,而这恰恰是Xilinx和Altera长期不重视的领域。大厂的高端FPGA动辄几十瓦功耗,启动配置要几百毫秒,封装最小也是QFN-48起步,这在很多消费电子、工业手持设备和车载场景里根本没法用。

Lattice的产品线覆盖了从CPLD到中端FPGA的整个区间。MachXO系列主打“瞬时启动”,上电几十微秒就能工作,常用于系统里做胶合逻辑、上电时序控制和接口转换;ICE40系列把功耗压到了微瓦级,在很多电池供电的穿戴设备和传感器hub里都能看到它的影子;ECP5系列则是性能担当,支持DDR3、高速SERDES,虽然规模比不上大厂百万门级的产品,但在几十K LUT级别的项目里完全够用。

我接触最多的就是ICE40UP5K这颗芯片,只有不到2万LUT,但集成了HIMD、I2C、SPI硬核,还内置了一小块NVCM非易失配置存储。很多需要在小封装里做逻辑控制、同时不想外挂配置Flash的场景,它都是非常合适的选择。Lattice的生态思路其实很清晰:不跟大厂拼规模,而是在“够用”的范围内把每一分功耗和每一平方毫米面积都做到极致。

1.2 三大家族怎么选:MachXO、ICE40、ECP5定位解析

Lattice目前主流的FPGA家族大致可以分成三类,选型时核心看三点:上电启动速度、功耗预算、以及需要什么接口资源。

MachXO系列(包括MachXO2、MachXO3)走的是“上电即用”路线,启动时间通常在1ms以内,很多服务器主板、通信设备用它做系统管理控制器和FPGA之间的桥接。它内部有Flash-based的LUT,不需要外部配置芯片,上电瞬间逻辑就是活的,这点在需要快速响应的系统里非常关键。MachXO2甚至内置了用户Flash,可以拿来存一些参数和校准数据,省掉一颗外部EEPROM。

ICE40系列是超低功耗的代表,静态电流能做到微安级别。ICE40LP和ICE40UP两个子系列,前者偏纯逻辑,后者集成了DSP和更多硬核。这个系列在消费电子里最出名的是被某知名开源硬件平台用在了开发板上,但在工业界,它更多地出现在传感器采集、电机控制、电池管理这些对功耗和面积极度敏感的场景。

ECP5系列则是Lattice的“大块头”,最多有超过80K LUT,支持DDR3/LPDDR3存储器接口,内置SERDES可以跑千兆以太网或PCIe。很多网络设备、视频采集设备用ECP5做数据通路处理,它的性价比和功耗表现比同级别大厂芯片有明显优势。选型的时候先想清楚自己是“要立刻跑起来”还是“要跑得快”,再决定选哪条产品线。

1.3 为什么Lattice常被忽略但非常值得学

客观说,Lattice在开发者生态上确实不如Xilinx和Altera。后者的Vivado和Quartus功能庞大、文档海量、社区问答丰富,而Lattice的官方文档相对精简,第三方教程更是少得可怜。但这不代表Lattice不值得学,反而因为资源少,你需要更扎实地理解FPGA底层的工作原理,而不是被IDE的自动流程推着走。

从求职和项目角度看,Lattice在某些垂直领域的使用率高得惊人。我之前做过安防监控的项目,里面的视频拼接和图像缩放芯片用的是ECP5;后来做医疗设备的同事,他们用的也是MachXO3做传感器时序控制;做消费电子朋友那边,ICE40几乎成了标配。这些领域的工程师平时不声张,但在社招简历里,“熟悉Lattice开发流程”是一个真正的加分项,因为市面上会的人确实比会Vivado的人少得多。

我个人实际体验是,Lattice的Diamond工具虽然没有Vivado那么“智能”,但它综合速度快、占内存少、界面简洁。普通工程十秒内就能完成综合布局布线,不像Vivado开个工程就得等半天。对于中小规模逻辑开发,这种“轻量感”反而是巨大的效率优势。

2. 工具链选型与安装:Diamond还是Radiant,别再纠结

2.1 两代IDE的来龙去脉

Lattice目前有两套主流的FPGA开发工具:Diamond和Radiant。很多人第一次接触时都会懵,不知道装哪个,其实按芯片系列分清楚就行。

Diamond是上一代的旗舰工具,支持MachXO、MachXO2、MachXO3、ICE40、ECP5、LatticeSC这些老产品线。它发布于2011年左右,界面偏传统,但胜在稳定,工业界大量存量项目都跑在Diamond上。Radiant是2017年前后推出的新一代工具,支持更新的芯片如CrossLink-NX、Certus-NX、MachXO5-NX,以NX结尾的新产品一律用Radiant。Radiant的界面和流程明显参考了Vivado那套,综合、布局布线、时序分析分得更清楚,对free run时钟和复杂时序约束的支持也更好。

选哪套不是看个人喜好,而是看你用的芯片。如果你做ECP5或MachXO2项目,老老实实用Diamond;如果你要用CrossLink-NX做MIPI CSI/DSI接口处理,就一定得装Radiant。当然,两套工具可以在同一台机器上共存,我这台电脑就是Diamond和Radiant都装了,切换使用没遇到过冲突。有一点要注意:Radiant对操作系统更新更敏感,Ubuntu 20.04以上系统跑Radiant有时会缺32位动态库,需要自己手动补装lib32stdc++等依赖包。

2.2 申请License与安装的完整步骤

Lattice的IDE本身是免费下载的,但综合和布局布线功能需要license。好在它不像大厂那样需要采购加密狗,直接去官网注册一个账号,选择Free License,申请后会收到一个license文件。有两点非常关键:一是MAC地址和网卡选择,Lattice的license同时绑定了MAC和主机名,申请时填的hostname一定要和本机完全一致;二是环境变量,以Windows为例,需要在系统环境变量里新建LM_LICENSE_FILE,路径指到license文件所在的目录,注意是目录而不是文件本身。

安装过程我踩过一次坑,Diamond安装完后启动时提示找不到bs.dll,当时折腾了很久,最后发现是安装路径里有中文和空格导致的。建议安装时一律用纯英文路径,最好是C:\lscc\diamond\这种。Lattice的安装包会把所有版本的工具放在同一个根目录下,如果你装了Diamond 3.13又装3.14,它们会共存于同一目录,这个设计虽然有点占空间,但切换版本确实方便。

环境变量设置完整示例如下:

# Windows环境变量 LM_LICENSE_FILE=C:\lscc\license\license.dat # Linux环境变量(写入~/.bashrc) export LM_LICENSE_FILE=/home/yourname/lscc/license/license.dat export LD_LIBRARY_PATH=/home/yourname/lscc/diamond/3.13/bin/lin64:$LD_LIBRARY_PATH

设置完环境变量后,打开Diamond,在Help -> License Manager里能看到当前license的到期时间和绑定的node名。如果显示not found,多半是LM_LICENSE_FILE路径指错了,或者license申请时的hostname和当前机器不一致。Linux下还可以用lmutil lmdiag命令来诊断license服务器状态,实测很管用。

2.3 工具界面速览:从一个空工程开始

打开Diamond后的界面和旧版Quartus有点像,左边是File List和Process窗口,中间是代码编辑区,下面是在线仿真窗口。新建工程的方式是File -> New -> Project,然后选设备系列和具体型号。注意用户在向导里就要选对封装和速度等级,这两个参数会影响最终的布局布线结果,中途也可以改,但改完后所有流程都得重跑,不如一开始就确认清楚。

这里要特别提一下Diamond里的“Strategy”概念,它是工程编译流程的配置集合,可以切换综合工具(Synplify Pro或Lattice Synthesis Engine)、布局布线策略、优化目标(面积优先还是速度优先)。默认的Strategy已经够用,但如果你的设计时序紧张,建议把Place & Route的Effort Level从Standard改成High,能明显改善布局结果,代价是编译时间会拉长一些。Radiant这边则简化了很多,界面更贴近现代化IDE,打开工程后左侧直接就是Flow Navigator,类似Vivado的布局。

3. 第一个工程实战:LED流水灯从代码到上板

3.1 Verilog代码与时序逻辑基础

不管用Diamond还是Radiant,写FPGA的逻辑代码都是通用的。如果消息的最早输入是Verilog或VHDL,我这里以最经典的LED流水灯为例,带你把整个流程跑通。虽然例子简单,但涉及到的“编译-管脚约束-时序约束-下载”这个流程,是所有Lattice工程都要走的,学会了就等于入门了。

代码部分用一个计数器和一个移位寄存器实现流水灯:

module led_shift ( input wire clk, // 12MHz系统时钟 input wire rst_n, // 低电平复位 output reg [3:0] led // 4个LED ); reg [23:0] cnt; reg [3:0] shift_reg; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 24'd0; shift_reg <= 4'b0001; end else begin if (cnt == 24'd1_200_000 - 1) begin // 12MHz下计时0.1秒 cnt <= 24'd0; shift_reg <= {shift_reg[2:0], shift_reg[3]}; // 循环左移 end else begin cnt <= cnt + 1'b1; end end end assign led = shift_reg; endmodule

这段代码需要的主要逻辑是计数器和移位寄存器。cn就是分频计数器,每0.1秒加1,满后清零并触发移位。shift_reg的赋值写法是循环左移,相当于把最高位挪到最低位。实际工程里我会建议把计数周期做成了参数,方便后期调整速度,比如改成parameter CNT_MAX = 24'd1_200_000 - 1;,这样改代码时只需改一处参数即可。

有一点必须强调:FPGA里的always块描述的是硬件电路,不是软件顺序执行。上面两个always块并行存在,cnt和shift_reg两个寄存器各自工作,这在硬件上是“同时”发生的,而不是像C语言那样按行执行。理解这点是最关键的FPGA思维转换。

3.2 引脚约束与时钟约束,一个都不能少

代码写完后,最关键的一步是“管脚约束”,也就是告诉软件:led[0]应该连接到芯片的哪个物理引脚,clk进入的是哪个时钟引脚。在Diamond里,这个步骤是通过Spreadsheet View完成的:综合完成后,双击左侧Process窗口的“Floor Planner”或直接点“Spreadsheet View”标签,就能看到芯片的所有引脚列表。在表格里把clk设为芯片的时钟专用引脚(通常是PLL输入或全局时钟引脚),led[0]到led[3]设为普通IO,电平标准根据板子的实际供电电压选择。

注意这里有个细节:时钟引脚的分配会直接影响布局布线的时序收敛。如果非要把时钟信号放在普通IO上,软件会通过内部走线绕到全局时钟网络,延迟会增加,而且实际板子不一定能工作正常。所以只要电路板设计时给FPGA留了专用时钟引脚,就一定优先用专用引脚。

写完引脚约束后,还要写时序约束,告诉工具时钟频率是多少。Diamond里可以用Constraint Spreadsheet,也可以手动写LPF文件。最基本的时序约束只需一行:

// LPF文件片段 BLOCK RESETPATHS; FREQUENCY NET "clk" 12 MHz;

其中FREQUENCY命令定义了时钟频率。如果没有这句,布局布线器会按默认频率(通常很保守)来做时序收敛,虽然大概率也能通过,但没法保证极端温度电压下的可靠性。对于LED流水灯这种慢速设计无所谓,但如果你的设计里有时钟域交叉、高速接口,时序约束必须从一开始就认真写。

3.3 综合、布局布线与生成比特流

约束配好后,回到Process窗口,依次执行Synthesis Design(综合)、Map(映射)、Place & Route(布局布线)、Timing Analysis(时序分析)和Export Bitstream(生成下载文件)。最简单的办法是右键点击“Export Bitstream”,选择“Run All”,软件会把前面的步骤全部自动跑一遍。Diamond的综合速度相当快,这几乎是代码级的体验。

整个流程跑完后,会在工程目录下生成一个.bit或.bin文件。这里有个很重要的区别:Lattice的芯片有两种配置方式,一种是下载到SRAM,掉电就丢,用于调试;另一种是烧录到芯片内部的Flash配置区或外部SPI Flash,上电自动加载,用于量产。MachXO系列本身就有内部Flash,所以用Programmer直接把bit文件写入就行;ECP5则一般需要外挂SPI Flash,工具里会生成一个bootloader文件格式。

我建议在开发初期用“Download SRAM File”方式调试,速度快,不怕反复烧写。等代码稳定后,再改成“FLASH Programming”,把程序固化到Flash里。这两个操作在Diamond的Programmer工具里是分开的两个选项,用的时候注意区分,避免调试期反复擦写Flash导致寿命损耗。

# Diamond Programmer常用操作 # 1. 打开Programmer: Tools -> Programmer # 2. 添加设备: Detected Devices,选择你的下载器 # 3. 选择操作: SRAM Download / FLASH Programming # 4. 选择文件: 对应.bit或.bin文件 # 5. 点击Program开始下载

3.4 硬件下载与验证实测

下载器方面,Lattice官方的USB下载器是HW-USBN-2B,价格不算便宜。很多爱好者会自己做FT232H方案的兼容下载器,实测在Diamond下也能识别,前提是安装好驱动,并且把下载器固件模式设置成JTAG。我建议新手在预算允许的情况下直接买官方的,省去自己折腾驱动兼容性的时间。

上电后如果LED没有按预期闪烁,优先检查三件事:第一是复位引脚的电平状态,如果rst_n悬空,FPGA内部的上拉会大概率让它保持高电平,但外部噪声可能造成随机复位,所以最好在硬件上用一个10k电阻把复位引脚拉高;第二是时钟是否真正到了芯片,有条件用示波器量一下晶振输出;第三是引脚约束是否和原理图一致,这点最坑,很多“板子不工作”的排查到最后发现就是因为丝印上的引脚号和FPGA引脚编号搞混了。

我实测过一个用ICE40UP5K做的流水灯,12MHz时钟,0.1秒切换一次LED,下载后运行非常稳定,整板功耗不到10mA(不含LED本身)。这种超低功耗表现正是Lattice的核心竞争力所在。

4. 更多实用细节:时序分析、存储器与资源配置

4.1 时序分析报告怎么看

很多初学者会忽略Diamond里的Trace Report(时序报告),编译过了就以为万事大吉。实际上“编译通过”只代表语法和硬件资源足够,并不代表电路在目标频率下一定能正常工作。用LED流水灯看不出区别,但如果你的设计里有时钟频率较高的外设接口,时序就变得至关重要。

打开Trace Report后,找到“Timing Summary”部分,看关键路径的Setup Time Slack。Slack为正值说明这条路径在目标时钟周期内能完成计算,留有余量;Slack为负值说明时序违例,实际工作时可能出现偶发错误。常见解决时序违例的方法有三种:一是降低时钟频率或者用PLL分频;二是在关键路径中间插寄存器(也就是流水线化);三是调整布局布线策略里的Effort Level。

我们在Diamond里双击“Timing Analysis”后,工具会自动把所有时钟域和关键路径列出来。查看“Data Path Detail”可以看到路径上每一级的组合逻辑延迟、走线延迟和扇出情况。组合逻辑延时过长是最常见的瓶颈,比如一个always块里串联了太多的加法器和比较器。遇到这种情况,我通常会把它拆成两个always块分步计算,时序就能很快收敛。

4.2 Lattice芯片的存储器和DSP资源

ECP5和ICE40UP这两款芯片都内置了Block RAM和DSP乘法器,用起来原理和Xilinx一样,但初始化的IP核方式不同。Lattice提供的是Clarity Designer工具(Diamond环境下),类似Xilinx的IP Catalog。在Clarity Designer里,可以配置Block RAM的位宽和深度、选择是否有输出寄存器,也可以配置DSP的乘法器输入位宽。

用Clarity Designer生成IP时,注意数据位宽的选择不只是“能用”,还要考虑Block RAM的物理结构。比如ECP5每块Block RAM是18Kbit,如果配置成1K x 9bit,会浪费一小部分存储。如果是大量使用存储器的工程,建议先算好总bit数,再选一个和物理Block RAM整除的位宽,能显著节省资源。这个细节在任务书不明确时容易被忽略,但实际优化效果很可观。

4.3 全局时钟与PLL/DLL配置

Lattice芯片内部都有全局时钟网络和PLL,用于频率合成和时钟相位调整。ECP5的PLL在Clarity Designer里配置非常直观,可以设置输入频率、倍频系数、分频系数。通常系统时钟经过PLL后,可以得到多个不同频率的时钟域供逻辑使用,同时减少Skew。

PLL配置完成后,在顶层模块里实例化生成的PLL模块,并通过输出时钟引脚接到内部逻辑。有一点值得特别注意:PLL的输入信号必须是专用的全局时钟引脚,否则PLL可能无法锁定或者输出不稳定。如果你的原理图设计里把晶振引脚连到了普通IO,那不仅要改代码,还得改板子,这个坑在开发初期就要避开。

5. 常见问题与排查技巧实录

5.1 编译阶段高频错误速查

我把使用Lattice工具链以来遇到最多的问题整理成了表格,照着排查能省不少时间。

错误现象可能原因解决办法
license not foundLM_LICENSE_FILE路径错误或hostname不匹配检查环境变量路径指向目录,确认hostname与license申请时一致
综合报错:Unknown identifier代码里用了未声明的信号或打错信号名检查Verilog代码中的信号声明与拼写
引脚约束无法保存IO引脚已被其他信号占用或电平标准不支持检查Spreadsheet View里是否有重复分配,检查IO类型是否支持目标电平
Place & Route失败:IO bank错误同一BANK的IO被分配了不同电平标准把同一BANK的引脚电压统一到同一电平
Programmer无法识别设备下载器驱动或JTAG连接问题重新安装USB驱动,检查JTAG链上的TDI/TDO/TMS/TCK连接
下载时报CRC错误芯片型号选择与目标芯片不一致确认工程中设备型号和封装与板子一致

5.2 时序不收敛的经典场景

时序不收敛在低速流水灯里不会出现,但在做稍复杂一点的设计时几乎人人都得碰上一次。我印象最深的一次,是一个ECP5的千兆以太网项目,串行接口数据路径上的组合逻辑太深,导致Setup Slack为负,跑起来偶尔丢包。当时排查的过程让我总结出了一个通用套路:先把不符合要求的路径找出来,看是组合逻辑太多还是走线太长,如果是组合逻辑太多就中间插寄存器,如果是走线太长就换引脚约束,让关键信号靠近。

另外一个容易踩的坑是异步信号的跨时钟域处理。如果一个信号从一个时钟域直接接到另一个时钟域,而不做同步处理,布局布线工具不会报错,但硬件上会偶发采到亚稳态,表现为“偶尔出错但找不到规律”。这种情况在LED流水灯里不存在,但在多时钟设计中是头号潜在bug。我的习惯是,所有跨时钟域信号一律经过两级寄存器同步后再使用。

5.3 硬件上板调试的独家技巧

Lattice的在线逻辑分析仪叫Reveal,类似Xilinx的ChipScope。它可以把内部信号实时抓出来,在电脑上观察波形。使用Reveal需要在综合设置里打开对应的探针插入选项,然后在工程里例化Reveal IP,把想观察的信号连上去。综合下载后,Reveal Analyzer就可以像示波器一样采样内部信号。

实际调试时我更喜欢先抓寄存器值而不是靠猜。比如LED流水灯不亮,先用Reveal抓一下rst_n和clk能看到什么,再抓移位寄存器的值,就能快速定位问题是复位没释放、时钟没进来,还是移位逻辑本身有bug。这个习惯帮我避免了很多次“盲改代码然后全凭运气”的调试过程。

5.4 关于Flash烧录的一个坑

烧录Flash这一步,我栽过一次跟头。当时用ECP5,明明比特流下载到SRAM能正常工作,但烧录到外部SPI Flash后上电却不启动。折腾了很久才发现,问题出在bootloader配置上。ECP5从SPI Flash启动需要一个golden bitstream做引导,如果在Programmer里直接选FLASH Programming,工具默认写的是普通bit文件,没有带上配置头,上电时找不到配置数据。

解决方法是,在Export Bitstream时把文件格式选为“Bitstream for FLASH Programming”(在Diamond里叫.bin,Radiant里叫.bit),然后再用这个文件烧Flash。烧完后最好用编程器回读验证Flash内容,确认第一行数据不是全FF,否则大概率没烧进去。

6. 后续还能怎么玩:从流水灯走向正式项目

如果你按上面的步骤成功点亮了LED,恭喜,你已经掌握了Lattice FPGA开发的核心流程,剩下的就是深度问题。下一步建议从三个方向继续深入:第一,做一个小型状态机项目,比如UART收发或SPI主机,把“状态机设计”这个FPGA基础功练扎实;第二,折腾一下Clarity Designer里生成的PLL,试着把CPU或外设的时钟频率跑高一点,体会时序收敛的意义;第三,如果手头有带ECP5的开发板,可以尝试跑一个简单DDR3读写测试,这会让你对存储接口和时序约束有更深刻的理解。

我个人体会是,FPGA学习最大的坎不是语法,而是“能不能把代码映射到电路上去思考”。Lattice这套工具链虽然不如大厂IDE那么“傻瓜”,但也正因为如此,每一步都需要你亲手配置和确认,反而能逼着你把每个环节都想明白。很多人学FPGA一上来就用Vivado,最后被海量功能和脚本系统淹没,连Quartus里最基本的RTL分析都没做过,其实是走了弯路。

最后再分享一个小技巧:Lattice的Diamond工程里会生成大量的中间文件,如果做项目版本管理,记得只提交源码文件(.v、.lpf、.sdc、.vhd)和工程配置文件(.ldf),其他编译器生成的目录可以直接加到.gitignore里。这样每次编译结果不同也不会污染版本库,回溯bug时能省很多时间。

格(Lattice)基础这个系列我打算继续写下去,下一篇预计会重点拆解ECP5的DDR3接口和高速SERDES应用,这也是Lattice在工业项目中用得最深的方向。第一篇能看完并动手跑通板子的人,后续再聊这些才会有真正的体会。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 1:16:07

MRAM替代EEPROM和Flash的PIC18F67K40存储驱动实践

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

作者头像 李华
网站建设 2026/10/4 1:16:05

FPGA上实现ARM双核锁步DCLS技术:原理、架构与踩坑实录

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

作者头像 李华
网站建设 2026/10/4 1:16:05

单细胞RNA-seq差异基因分析全流程:聚类、注释与环形热图可视化

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

作者头像 李华
网站建设 2026/10/4 1:15:46

基于PyQt5的语义分割系统:从模型原理到GUI部署实战

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

作者头像 李华
网站建设 2026/10/4 1:15:44

工业数据记录存储升级:STM32驱动MRAM替换NOR Flash完整方案

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

作者头像 李华
网站建设 2026/10/4 1:15:26

FPGA+STM32脉冲线性调频雷达系统设计与实现

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

作者头像 李华