news 2026/9/5 6:54:46

Modbus TCP底层逻辑与实战:报文解析、硬件组网及三菱FX5U配置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Modbus TCP底层逻辑与实战:报文解析、硬件组网及三菱FX5U配置

1. 为什么工程师绕不开Modbus TCP

做了几年工业自动化现场,你会发现一个挺有意思的现象:项目越做越多,但遇到的通信问题翻来覆去就那么几个。尤其是这两年,只要设备一多、数据一密,现场总线就开始吃力,甲方开口闭口都是以太网。这时候Modbus TCP几乎成了默认选项——不管是PLC、仪表、变频器还是上位机,几乎都带这个协议。你可以说它老,但不能否认它好用。很多工程师朋友一开始接触Modbus TCP,最困惑的问题其实不是“怎么用”,而是“它到底是怎么工作的”——一个请求发出去,为什么设备能正确响应?同一个连接里怎么做到又读又写?硬件上到底要注意什么?三菱FX5U做主站和从站时该怎么配置?

这些问题在官方手册里都能找到答案,但零散得很,而且手册只会告诉你“按这个步骤点”,不会告诉你“为什么是这样”。所以这篇内容,我打算从一个现场工程师的角度,把Modbus TCP的底层逻辑拆开来讲。不聊虚的,直接说报文、说电路、说配置、说踩坑。你要是正在做设备联网、上位机对接、PLC之间的以太网通信,这篇内容应该能帮你省不少翻手册的时间。

顺便说一句,Modbus TCP虽然名字里带个TCP,但它跟HTTP、MQTT这些常见的以太网协议完全不是一个套路。它的核心设计思路其实非常朴素:把传统串口上跑得稳稳的Modbus协议,原封不动地搬到以太网上来。理解了这个“搬运”的本质,后面所有细节都会顺理成章。

2. 底层逻辑:从Modbus RTU到Modbus TCP的“搬运”思路

2.1 协议模型没有变,变的只是“快递方式”

很多人听到“Modbus TCP是Modbus RTU的以太网版”,第一反应是:直接把RTU报文塞进TCP就行了吧?其实没那么简单,但也没那么复杂。核心思路确实是“包装一下再发出去”。在串口时代,Modbus RTU的报文结构是:地址码、功能码、数据区、CRC校验。其中地址码用来区分总线上的不同从站设备,CRC用来保证数据在传输过程中没被干扰。

到了以太网上,物理链路完全变了。以太网天然支持多设备寻址——每个设备有IP地址和MAC地址,根本不需要再用一个“地址码”来区分从站。同时,TCP协议本身就有可靠传输机制,丢包了会重发,顺序乱了会重组,数据错了上层能发现,所以CRC校验也显得多余。

那是不是把地址码和CRC删掉就行了?设计者确实是这么想的,但他们还多做了一件事——为了保证Modbus协议在不同传输层上的统一性,他们设计了一个MBAP报文头(Modbus Application Protocol header)。这个头长度为7个字节,把原来RTU里的地址码、校验码“替换”掉,同时额外增加了事务处理标识符、协议标识符和长度字段。简单理解:地址码变成了Unit ID(单元标识符),CRC变成了TCP协议自身的可靠性保证,整个数据帧再用MBAP头做了一层“快递包装”。

2.2 MBAP报文头:读懂这7个字节,就懂了一半

直接贴一段实打实的抓包数据,这是我之前在调试一台温控表和上位机通信时抓到的:

事务处理标识符: 0x0001 协议标识符: 0x0000 长度: 0x0006 单元标识符: 0x01 功能码: 0x03 起始地址: 0x0064 读取数量: 0x0002

这段报文看起来很简单,但每个字段都有讲究。事务处理标识符(Transaction Identifier)是客户端自己维护的一个计数器,每次发请求就加1,用来匹配“请求”和“响应”。为什么需要这个?因为TCP是字节流,不是消息流,你发10个请求,服务器可能一口气就把10个响应全返回来了,没有这个ID,你根本分不清哪个响应对应哪个请求。这个字段在单连接场景下容易被人忽略,一旦你用一个TCP长连接同时处理多台设备的数据请求,事务ID的作用就体现出来了。

协议标识符(Protocol Identifier)在标准Modbus TCP里永远是0x0000,它存在的意义是给协议扩展留后路。如果哪天有人在Modbus TCP上再套一层别的协议,就可以通过这个字段区分。

长度字段(Length)表示的是“从单元标识符开始,到报文结束”的字节数。再强调一遍,不是整个TCP包的长度,只是Modbus应用层的剩余长度。很多人在自己写协议解析的时候在这里栽过跟头。

单元标识符(Unit ID)就是原来RTU里的从站地址。因为一个Modbus TCP服务器(比如网关)可以同时桥接多个串口从站设备,所以客户端需要在请求里注明“我找的是哪个从站”。直接连PLC的场景下,这个值通常固定为0xFF或者0x01,具体取决于设备的实现。

2.3 功能码和存储区映射:能读写哪些数据,全看这张表

MBAP头后面跟的就是标准的Modbus PDU(协议数据单元),包括功能码和数据。功能码的语义和Modbus RTU完全一致,这一点是它“老而弥坚”的关键——底层换了,指令没换,从设备串口时代升级到以太网时代的迁移成本几乎为零。

常用的功能码就那么几个,我整理了一份现场最常用的清单:

功能码作用对应存储区类型常见PLC地址示例
0x01读线圈状态位输出FX5U: M、Y
0x02读离散输入位输入FX5U: X
0x03读保持寄存器字输出(16位)FX5U: D
0x04读输入寄存器字输入(16位)FX5U: 特殊寄存器
0x05写单个线圈位输出FX5U: M、Y
0x06写单个寄存器字输出FX5U: D
0x0F写多个线圈位输出(批量)FX5U: M、Y
0x10写多个寄存器字输出(批量)FX5U: D

地址映射是个大坑。Modbus TCP的报文里用的是“起始地址”,这个地址是协议层面的逻辑地址,从0开始编号。但各家PLC在软件里显示的软元件号往往是从1开始的,而且还有各种偏移。比如某款仪表,你在报文里写地址0x0064,对应的是设备的第101个寄存器(如果从1开始编号的话)。至于三菱FX5U,它在做Modbus TCP从站的时候,内部软元件和Modbus地址之间还有一套自己的映射规则,后面实战部分我会细说。

3. 同一个连接里怎么实现又读又写

3.1 搞清楚“读写”的本质:一个请求对应一个响应

很多新手对“又读又写”这件事有误解,以为需要在程序里开两个端口、建两个连接,一个专门用来读,一个专门用来写。实际上完全不需要。Modbus TCP的读写操作在同一个TCP连接里就可以完成,原理特别简单:客户端一个请求一个响应地轮流发就行。你可以先发一个0x03读保持寄存器,收到响应处理完之后,紧接着发一个0x10写多个寄存器,再收响应,再发下一个请求。

这个过程就像你去窗口办业务:你说一次需求,工作人员给你处理一次,然后再轮到下一个。TCP连接本身就是全双工的,数据收发互不干扰,协议层面也没规定“一条连接只能读或者只能写”。所以一个连接里交替发读请求和写请求,完全合法,而且这也是工程上的推荐做法——连接越少,资源占用越小,排查问题越方便。

但这里有一个容易踩坑的点:不要在没收到上一次响应之前,连续发送多个请求。从TCP的底层能力来说,请求是可以流水线式发送的,因为事务处理标识符就是用来区分乱序响应的。但Modbus的从站设备响应速度千差万别,有些老设备甚至没有一个内部缓冲区来缓存多笔请求。你要是噼里啪啦一次性丢给它三五个请求,它可能只回前面一两个,后面的直接丢进“内存黑洞”。所以最稳妥的做法是:发一个请求,阻塞等待响应,收到后在超时时间内继续发下一个。如果响应超时,再重发或者报错。

3.2 实操技巧:用事务ID优雅地处理大批量读写

还有人会问:那我要读50台设备,每台读100个寄存器,总不可能一台一台串行等吧?确实,工业现场对实时性有要求,你不可能为了等一台老仪表的响应,让整条产线卡住。这时候就要把“事务ID”的作用发挥出来了。你可以同时往连接里发多个请求,每个请求的事务ID递增,然后异步等待响应。收到响应时,通过事务ID就能知道这个响应对应哪一个请求,再分别解析数据。

这种做法在实际代码里特别常见,比如用C#写上位机的时候,我会维护一个Dictionary<ushort, Action<byte[]>>,key是事务ID,value是回调方法。发出一个请求时往字典里注册一个回调,收到响应时根据事务ID找到对应的回调去执行。这样一套机制下来,几十台设备的轮询可以在毫秒级别内并行完成,而不是一台一台傻等。当然,并发数量不能无限制放大,一般建议同时挂起的请求数不要超过5到10个,具体取决于从站设备的处理能力和网卡缓冲区大小。

如果你用的是西门子S7、三菱MC协议这类以太网协议,事务ID的概念会被封装得更深,但Modbus TCP把这层逻辑暴露给了开发者,这也算它“朴素”的一种体现——好懂,但需要你稍微懂点网络编程。

4. 硬件电路与组网:不要把Modbus TCP当成纯“软件活”

4.1 电气基础:其实就是标准以太网物理层

很多人一提到Modbus TCP,就只顾着看软件和报文,忽略了硬件层面的组网设计。其实Modbus TCP的硬件电路就是标准的以太网物理层,跟电脑上网用的一模一样——RJ45接口、网线、交换机,全是成熟的东西。绝大多数支持Modbus TCP的设备,接口都是RJ45,引脚定义遵循EIA/TIA-568B标准。常规网线里8根芯,实际通信只用到1、2、3、6这四根:1和2负责发送,3和6负责接收。

设备直连场景下,如果电脑直接连PLC的以太网口,现在的设备和网卡大多支持自适应交叉直连,所以随便找根网线插上基本都能通。但如果是两台PLC之间走Modbus TCP从站通信,最好还是通过交换机中转一下,避免自己做交叉线带来的麻烦。

关于网线选择,我之前提过一个建议,今天再展开说说:柜内短距离连接,使用超五类(Cat5e)网线完全够用,跑100Mbps没问题。如果走线距离超过50米,或者现场变频器、伺服驱动器特别多,电磁环境比较恶劣,建议直接上六类(Cat6)屏蔽网线,并且保证屏蔽层在两端都做了可靠的接地处理。工业现场最容易出的问题不是网线带宽不够,而是网线屏蔽没接地导致通信偶发中断,这种问题排查起来非常头疼。

4.2 IP规划:比你想的更重要的一个“硬件”环节

Modbus TCP通信能不能建立,第一道关卡不是协议,而是IP连通性。组网之前一定要做好IP规划。最典型的原则:同一个局域网内,所有设备的IP地址必须在同一个网段,子网掩码一致,IP不能冲突。听起来像废话,但现场真的经常出问题——走之前一切正常,到了现场发现PLC的IP是192.168.1.10,触摸屏是192.168.0.10,网关是192.168.2.1,三个设备三个网段,然后一群人围着交换机查半天不知道问题出在哪。

我个人的习惯是:给每个设备预留一个备注标签,把IP地址、设备型号、所在柜号全部写在标签上,贴在设备外壳上。另外,PLC的IP地址不要放在DHCP自动获取上,固定IP是工业通信的基本素养。如果项目规模大、设备数量多,建议给每类设备划一个IP段,比如PLC用192.168.1.x,仪表用192.168.2.x,上位机用192.168.10.x,这样后期排查故障、新增设备都轻松很多。

还有一种常见场景是跨越网段通信,比如办公网和设备网要互通。这时候就要靠路由器做静态路由或者NAT转换。但说句实话,这种场景在单机调试时不太会遇到,更多的是在整厂信息化项目里才需要。普通工程师能把同一网段内的Modbus TCP玩明白,已经能解决80%以上的现场问题了。

4.3 硬件网关:串口设备怎么“蹭”上Modbus TCP

现场不可能全是原生以太网设备。大量温控表、流量计、老旧PLC,都只有RS485串口。这类设备要上Modbus TCP也很简单——加一个串口服务器或者协议网关。这类网关一般长这个样子:一侧是RS485/RS232串口,另一侧是以太网口,内部完成Modbus RTU和Modbus TCP的协议转换。

选型的时候要重点看几个参数:串口数量、支持的从站数量、Modbus寄存器区大小映射能力、是否支持自定义功能码、是否支持多客户端同时访问。特别要注意的是“多客户端同时访问”这个能力。有的网关只允许一个TCP客户端连接,如果你想同时让上位机触摸屏、数据采集系统、远程运维平台一起访问,就得选支持多客户端的型号。还有些网关支持将串口总线上多个Modbus RTU从站的寄存器区统一映射到网关自己的Modbus地址空间里,这样上位机只需要跟网关一个IP通信,就能拿到所有从站的数据。这个功能对于简化上位机编程非常有帮助。

硬件接线方面,RS485要特别注意A/B线不要接反,屏蔽层单端接地,总线末端加120欧姆终端电阻。我用过很多款串口服务器,说实话,硬件本身出问题的概率不大,90%的问题都出在接线和参数配置上。

5. 三菱FX5U实战:主站、从站、读写指令一次讲透

5.1 FX5U做Modbus TCP主站:GX Works3里的配置思路

三菱FX5U内置了以太网口,做Modbus TCP主站的时候,不需要额外加通信模块,直接用GX Works3软件配置就行。具体路径是:导航窗口里选“参数”->“FX5U CPU”->“模块参数”->“以太网端口”,然后设置IP地址。假设我给FX5U分配的是192.168.1.10,子网掩码255.255.255.0,默认网关192.168.1.1。

接下来才是关键——添加Modbus TCP主站功能。在“以太网端口”配置界面里找到“内置以太网端口通信支持设置”,勾选“Modbus TCP通信”,然后配置连接。这里有一个“开放式通信”的概念,其实就是把FX5U当成一个TCP客户端,去主动连接远程从站设备的IP和端口。

如果从站设备是第三方仪表,一般不需要三菱专门的协议库,直接用“套接字发送/接收”功能(SP.SOCSND和SP.SOCRCV指令)按Modbus TCP格式组帧发送就行。但这样做的代码量比较大,而且自己要处理事务ID、长度字段、字节序,容易出错。更推荐的方式是使用MELSOFT库里的Modbus TCP功能块,比如MB_TCP_Client系列(三菱官网上可以下载,装在GX Works3里就能用)。这些功能块封装好了报文组帧、连接管理、超时处理,你只要填参数就行:

  • 执行指令(REQ)
  • 通信目标IP(通常是字符串形式,比如"192.168.1.20")
  • 端口号:默认502
  • 从站单元ID
  • 功能码:比如16#0003表示读保持寄存器
  • 起始地址:比如16#0000
  • 读取点数/写入数据区

启动之后,把REQ置ON,功能块内部就会完成整个Modbus TCP请求-响应过程,完成后Done信号置ON,错误时Error输出错误代码。这套东西的最大好处是省心,调试效率高,不用自己抠报文细节。

5.2 FX5U做Modbus TCP从站:让别人来读你的数据

FX5U做从站,就是让上位机或者其他主站设备来读写它的内部软元件。这个配置比做主站还简单。在GX Works3的以太网端口设置里,找到“Modbus TCP从站”相关选项,启用后配置软元件映射。三菱FX5U的Modbus从站映射规则大概是这样的(不同固件版本有差异,以官方手册为准):

Modbus功能码对应FX5U软元件范围
0x01/0x05/0x0F(线圈)M、Y(部分范围)
0x03/0x06/0x10(保持寄存器)D、W等
0x02(离散输入)X
0x04(输入寄存器)特殊寄存器/缓存区

这里有一个现场很常见的坑:上位机工程师按照手册上的Modbus地址来读写FX5U的D寄存器,结果发现地址对不上。比如他想操作D100,但是报文里的起始地址填的却是100,读回来的数据根本不是D100的内容。原因在于三菱的Modbus从站映射里,D寄存器有基地址偏移,实际Modbus地址是“基地址+偏置+软元件序号”的关系。所以做设备对接前,一定要先打开GX Works3里的“软元件映射确认”界面,把每一个Modbus地址对应的实际软元件号抄下来,再让上位机工程师按这个表去配置。

我接过的一个项目里,上位机一直读不到数据,两边工程师在微信上对了一下午地址都没对上,最后我远程看了一眼映射表,发现D0对应Modbus地址40001,但通讯模块还加了一个单位偏置,实际要读40002才是D0。这种问题如果不看实测数据,光靠猜是真的无解。

5.3 读写程序示例:一个周期里完成“先读后写”

下面给出一段FX5U的ST语言示例代码,实现一个完整业务场景:从从站设备读取当前温度(保持寄存器地址0x0000,1个字),然后根据温度值判断是否超限,如果超限则写一个报警标志到从站的另一个寄存器(地址0x0001,1个字)。用三菱的Modbus TCP功能块来实现:

// 变量声明 // mbReadDone : BOOL; // 读完成标志 // mbReadData : ARRAY[0..9] OF WORD; // 读取的数据缓存 // mbWriteDone : BOOL; // 写完成标志 // mbError : BOOL; // tempValue : INT; // 温度值 // alarmFlag : WORD; // 报警标志 // 第一步:复位上一次的完成标志 IF mbReadDone THEN mbReadDone := FALSE; END_IF; // 第二步:发起读请求,读取从站地址0x0000开始的1个寄存器 MB_TCP_Client( REQ := (NOT mbReadDone) AND (NOT mbWriteDone), IP_Address := '192.168.1.20', Port := 502, Unit_ID := 1, Func_Code := 16#03, Start_Addr := 16#0000, Reg_Num := 1, Data_Buffer := mbReadData, Done := mbReadDone, Error := mbError, Error_Code := mbErrorCode ); // 第三步:读完成后处理 IF mbReadDone THEN tempValue := INT_TO_INT(mbReadData[0]); // 取读取到的温度值 IF tempValue > 80 THEN alarmFlag := 16#0001; // 超限则写入报警标志 ELSE alarmFlag := 16#0000; END_IF; // 第四步:发起写请求,把报警标志写入从站地址0x0001 MB_TCP_Client( REQ := TRUE, IP_Address := '192.168.1.20', Port := 502, Unit_ID := 1, Func_Code := 16#06, // 写单个寄存器 Start_Addr := 16#0001, Reg_Num := 1, Data_Buffer := alarmFlag, Done := mbWriteDone, Error := mbError, Error_Code := mbErrorCode ); END_IF;

这段代码的思路是:串行执行读和写,先读后写,不并发,保证逻辑简单清晰。实际项目中,如果你还需要同时采集多台设备,可以用数组和循环结构扩展上面的逻辑,把每一台设备的IP、起始地址、读取长度都放进配置表里,然后用FOR循环逐台轮询。这样写出来的代码不复杂,而且扩展性很好。

需要提醒的是,每次调用MB_TCP_Client功能块,它内部会自动建立TCP连接或者复用已有的连接。有的版本功能块每次执行完毕会断开连接,下一次执行再重新建立。频繁建立连接会带来不必要的开销,建议查看所用功能块是否支持“保持连接”参数,尽量设置成保持连接,除非你连接的从站设备数量非常多、需要释放Socket资源。

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

6.1 能ping通,但Modbus请求无响应

这是我最常遇到的问题,没有之一。设备IP能ping通,说明网络链路是通的,但Modbus请求发出去就是没有响应。遇到这种情况,按下面的顺序排查:

第一,确认从站设备的端口号。Modbus TCP默认端口是502,但有不少设备支持自定义端口,比如502 ALT、503等。如果端口不对,TCP连接根本建立不起来,或者连接建立了但无从响应。用抓包软件看一眼就知道连接有没有建立成功。

第二,确认单元标识符(Unit ID)。有些设备对Unit ID有严格要求,比如只接受1或者255,你填了2,它直接不搭理你。

第三,确认功能码和地址是否在设备的支持范围内。比如设备只实现了0x03读保持寄存器,你发一个0x04读输入寄存器,它可能返回异常码,也可能什么都不返回。

第四,确认数据长度是否越界。读取的起始地址+读取数量超过设备的最大寄存器范围,设备会返回异常码0x02(非法数据地址)。

6.2 通信正常,但读上来的数据明显不对

数据能读上来,但数值“完全不对”,这个也很常见。大概率是字节序问题。Modbus寄存器是16位的,一个寄存器里的两个字节,谁在前谁在后,不同设备厂家实现不一样。有的设备是大端序(高字节在前),有的是小端序(低字节在前)。更麻烦的是32位浮点数、32位整数这种跨两个寄存器的数据,不仅有字节序问题,还有字序问题——高字在前还是低字在前。

遇到这个问题,最简单的办法是查设备手册里的“数据格式说明”,或者用Modbus Poll手动读几个已知数值的寄存器,然后对比字节和字的排列规则。现场临时调试的时候,我一般是读一串连续寄存器,用手持计算器把原始HEX转成十进制,跟设备显示面板上的数值对比,很快就能反推出字节序规则。

还有一类问题:设备面板显示的值,跟Modbus读上来的原始值差了100倍或者10倍。这是缩放因子的问题,有些仪表内部把温度存成0.1℃为单位,原始值25000代表250.0℃。上位机侧要除以10才能得到正确显示值。这类问题不算通信故障,但很容易让新手怀疑人生,一定记得看量纲。

6.3 大批量读写时响应变慢、“卡死”

如果你在循环里快速、连续地发送Modbus请求,偶尔会出现响应超时,甚至连接被重置。大概率是单位时间内请求频率太高,超出了从站设备的处理能力。

解决思路有两个方向:一是降低轮询频率,比如把每组设备的数据读取周期从100ms放宽到200ms或500ms,牺牲一点实时性,换稳定性;二是把多个分散的读取合并成一个批量读取,比如原来读10个地址各读1个寄存器,改成读1次,从起始地址连续读10个寄存器,这样请求次数从10次降到1次,大大减轻从站负担。后者通常更推荐,这也是为什么Modbus TCP的0x03和0x10功能码支持批量操作的原因。

我在做某项目的时候,现场有40台温控器,一台PLC做主站,原来每台单独读当前温度,一轮需要40个请求,轮询周期超过2秒,操作手感明显有延迟。后来改成每台一次批量读取多个参数(温度、设定值、输出百分比),40台依然分4组轮询,轮询周期直接压到500ms以内,体感好非常多。

6.4 常见问题速查表

问题现象可能原因排查思路
ping不通IP不在同一网段 / 网线故障检查IP、换网线、查交换机端口指示灯
ping通但请求无响应端口错、Unit ID错、功能码不支持抓包确认TCP连接,核对报文格式
响应异常码0x01功能码不支持查阅设备手册支持的功能码列表
响应异常码0x02起始地址越界 / 长度越界核对寄存器地址映射表
响应异常码0x03数据值非法 / 写入了不允许的值检查写入值范围和寄存器属性
数据读上来数值不对字节序、字序、缩放因子用已知值对比验证数据格式
偶发性超时轮询频率过高 / 网线屏蔽不良降低频率、换屏蔽网线、检查接地
TCP连接被重置从站连接数超限 / 长时间无数据被断开减少客户端连接数,配置保活机制

7. 再做一次“底层逻辑”的收尾,顺便聊点实在的

从报文结构到硬件组网,从FX5U的主从配置到现场问题排查,Modbus TCP说到底就是一条朴素可靠的“数据搬运通道”。它把串口时代积累下来的成熟寄存器模型完整保留了下来,同时借助以太网的普及,让设备互联不再受距离和节点数的限制。从应用层来看,掌握它不需要深厚的网络编程功底,但从工程落地来看,理解它的底层逻辑确实能帮你在现场省下大量排查时间。

我个人这几年做设备联网项目,最大的体会是:不要把通信问题只当成“软件问题”或“硬件问题”。很多Modbus TCP的故障,是IP规划不当、网线屏蔽没做好、字节序没对齐、请求频率过高等多个因素叠加出来的。真正高效的调试方法,是先分层排查——先解决物理层连通性,再验证协议层报文正确性,最后才去怀疑应用层逻辑。

最后分享一个小技巧:电脑上常备一个Modbus Poll(客户端模拟工具)和一个Wireshark(抓包工具),很多看似玄学的问题,一抓包立刻现出原形。你不需要是网络专家,只要能看懂请求和响应的HEX数据,Modbus TCP的调试难度马上就降一个等级。这个协议能做到今天这个地位,靠的正是这种“简单到你几乎不用看手册就能上手”的气质。

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

STM32F103C8T6蓝药丸开发板:从入门到进阶的完整实战指南

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

作者头像 李华
网站建设 2026/9/5 6:48:11

基于FPGA的CameraLink转SFP光口设计:工业相机光纤传输方案

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

作者头像 李华
网站建设 2026/9/5 6:47:10

AQS 源码拆解:锁为什么能排队、唤醒、公平

关键词&#xff1a;AQS、ReentrantLock、CLH队列、LockSupport、公平锁、源码解析一次“卡死”引发的 AQS 溯源假设我们的 order-service 在双十一大促时突然出现大量请求超时&#xff0c;线程池被打满&#xff0c;日志里全是 Thread is blocked 但没有任何一条死锁检测报警。你…

作者头像 李华
网站建设 2026/9/5 6:46:51

CNN+Transformer双路径建模运动想象脑电信号

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

作者头像 李华
网站建设 2026/9/5 6:43:15

EDEM仿真建模核心:颗粒建模、接触模型与求解器收敛

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

作者头像 李华
网站建设 2026/9/5 6:37:34

实测SQLBot:开源智能问数工具在真实数据下的准确率与落地经验

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

作者头像 李华