作为常年跟西门子PLC打交道、又用C#写上位机的工程师我太清楚这个需求的痛点了。很多刚接触工业自动化的朋友第一反应是“我用C#写个客户端怎么把PLC的数据读上来”然后就开始了漫长的百度之旅搜到一堆零散的例程有的讲S7通信、有的讲Modbus、有的讲OPC代码格式五花八门照着抄完一跑就崩连报错都看不懂。这篇文章我就把自己这几年做C#与西门子通信项目的踩坑经验、常用方案、核心代码框架全部摊开讲。不整那些虚的从通信协议选型到代码实现从调试排错到工程化落地把一条可行的路径给你捋明白。这篇文章的目标读者是刚入行一到两年的上位机工程师或者是做产线集成、设备数据采集的兄弟稍微有点C#基础、见过PLC但没亲手写过通信代码读完这篇你至少知道西门子通信到底有哪些路子每种路子的适用场景是什么以及最通用的那套代码该怎么写、坑在哪里。1. 内容整体设计与思路拆解先说明一个很多人容易搞混的点C#和西门子PLC之间通信本质上是“以太网通信”或“串口通信”中的一个具体应用并不存在什么神秘协议。你写的还是Socket、SerialPort这类基础通信组件只是上层跑的协议是西门子专用的或者业界通用的。选型的时候绕不开这几条主流路线S7协议S7comm、Modbus TCP、Modbus RTU、OPC UA/DA、Profinet IO。每种方案背后都有不同的设计考量和适用范围。S7协议是西门子自己定义的以太网通信协议工作在TCP/IP之上默认端口是102。它的特点是直接和PLC的DB块、I/O区、M区打交道效率很高不需要在PLC里做额外的通信组态只要CPU里面开了“允许来自远程对象的通信”即可。适合做上位机直连PLC、实时性要求中等的场景。做C#开发时最常用的是Sharp7和S7.Net两个开源库它们把S7协议体封装好了你只需要调用读函数、传参数就行。说白了就是程序员不用去手拆报文了底层库把事干完了。Modbus TCP是一种应用层协议西门子从S7-1200/1500开始原生支持在博图里组态一个Modbus TCP服务器或客户端即可。它的优点是协议公开、跨品牌兼容性好不只是西门子三菱、欧姆龙、台达都能用它。它的缺点是读写数据时需要在PLC侧做地址映射你读到的数据结构和PLC内存不是一一对上的需要自己做映射表。对于C#开发者来说直接用NModbus、EasyModbus这类库就能干活。Modbus RTU走的是串口RS232或者RS485适合老设备、远距离、抗干扰要求高的场合。S7-200 SMART通过自由口通信或者Modbus RTU指令就能实现。C#这边就是SerialPort组件把Modbus RTU报文按帧收发即可。这个方案的坑在串口参数配置、CRC校验、超时处理下面细说。OPC UA是工业通信里的“大而全”方案适合企业级数据采集平台、MES系统对接。PLC侧需要额外组态OPC UA服务器参数C#这边可以用官方SDKOPCFoundation的UA-.NETStandard库或者商业库来连接。它支持复杂的数据结构、订阅模式、历史数据安全性也更好但学习和部署成本明显高于前几种。选型上我的习惯是单台上位机直连PLC、需要大量读写DB块直接用S7协议效率最高如果只是采集一些状态值、启停信号而且以后要接别的品牌设备用Modbus TCP更灵活如果是老旧设备或者现场距离远走Modbus RTU如果客户要求上MES、做集团级的数据平台那就上OPC UA。没有万能方案只有最合适的方案。还有个经常被忽略的点Profinet IO是PLC和IO从站之间的实时以太网协议它和上位机通信基本没关系千万别搞混。很多人搜“C#和西门子通信”会搜到PROFINET相关内容那是完全不同的方向。2. 核心细节解析与实操要点通信这种事关键不在代码量大而在于协议细节和边界条件。很多项目跑不起来不是因为语法错而是因为“握手没握上”、“报文少读了一个字节”、“地址索引算错了”。我把自己踩过的关键点按协议路线拆开讲。2.1 S7协议通信的核心机制与常见坑S7协议是三层结构TCP层的连接建立RFC1006协议头也就是ISO-on-TCP、S7会话层握手通信报文交互、数据读写请求帧。Sharp7和S7.Net帮我们挡住了大部分底层细节但你至少要理解它在干什么TCP连接建立后先发送一串特殊的握手字节PLC确认后才算建立了S7会话。读取一个DB块的值需要指定DB编号、起始字节偏移、读取长度。数据类型映射是写代码最容易错的地方PLC里的Bool在字节里是按位存的Real是4字节浮点Int是2字节有符号整数这些都要自己在C#侧做转换。项目里最常见的问题就是“为什么读上来的数据是乱的”。十有八九是偏移量没对齐或者忘了西门子的Word是按大端存储的。C#里的BitConverter是依当前系统的字节序通常是“小端”而S7协议传过来的是“大端”所以读取Int、Real的时候得手动翻转字节序。比如byte[] buffer new byte[4]; // 假设从S7读取4字节放置到buffer原始为大端序 float realValue BitConverter.ToSingle(buffer.Reverse().ToArray(), 0);这段代码看着简单但真实项目里到处是这种翻转稍不留神就是错的。我建议自己封装一组转换工具函数读UInt16、读Int32、读Real、读Bool数组统一处理字节序和偏移后续业务代码写起来就干净了。另一个坑是S7连接是会被PLC“踢掉”的。如果PLC侧开启了连接保护或者上位机频繁断连重连PLC的通信负载太高时可能会拒绝新的连接。现场最常见的是多个上位机同时连一个PLC连接数超过了PLC允许的上限S7-1200一般是几十个看型号这样后面连的就上不去。解决办法连接建立后保持长连接不要每次读写都新建连接、读完就关闭。2.2 Modbus TCP和RTU的地址映射与自坑点Modbus的思路是把PLC数据映射到Modbus寄存器地址上。S7-1200/1500做Modbus TCP服务器时你需要在博图里调用MB_SERVER指令把DB块、M区、I/O区映射到Modbus保持寄存器区然后C#才能以“寄存器地址”为单位去读取。这就意味着存在一张“映射表”PLC内部地址和设备Modbus地址之间的对应关系这份表要由电气工程师和上位机工程师共同维护项目交接扯皮大多都出在这里。Modbus TCP的报文结构比较简单事务处理标识符 协议标识符 长度 单元标识符 功能码 数据。用EasyModbus库的话示例代码大概是EasyModbus.ModbusClient client new EasyModbus.ModbusClient(192.168.0.1, 502); client.Connect(); int[] registers client.ReadHoldingRegisters(0, 10); // 从0号寄存器读10个字 client.Disconnect();你看代码确实简单但隐藏的坑在于保持寄存器一个寄存器是16位也就是两个字节如果PLC侧的Real是32位浮点那一个Real要占两个寄存器。读取的时候就要连续读两个寄存器再合成浮点数别忘了字节序的问题。Modbus协议里寄存器内部字节序也分大小端不同PLC厂商处理的还不一样西门子默认是按“大端”处理的。RTU走串口就更是细节地狱了。串口参数必须和PLC侧一致波特率、数据位、停止位、校验位任何一项不一样通信就是废的。然后报文里CRC16校验码是低字节在前这个特殊处理和TCP完全不一样最容易出错。我写过一版针对S7-200 SMART的RTU通信排查了两天才发现CRC字节顺序搞反了。2.3 OPC UA的架构与C#接入方式OPC UA不是简单的“一个连接读数据”它是一个服务端-客户端架构。西门子S7-1500自带OPC UA服务器功能S7-1200从固件版本4.0开始也支持。在博图里开启OPC UA服务器后要设置安全策略、证书、端口号默认4840。C#这边用OPCFoundation的UA-.NETStandard开源库或官方SDK连接步骤一般是找到服务器端点、创建会话、浏览节点、订阅变量。这个方案的坑基本都在证书和网络安全配置上。OPC UA支持匿名、用户名密码、证书三种认证方式工业现场为了省事经常用匿名或用户名密码但有些PLC固件版本在特定的安全策略下会拒绝连接排查起来很头疼。我的经验是先用UA Expert这个官方工具连一遍PLC确认服务器端配置没问题再用C#去连这样能把问题定位在“哪一端”。2.4 硬件连接的隐藏细节很多朋友一上来就卡在物理层网线到底怎么接这里有一个容易混淆的点S7-1200/1500本体上的PROFINET口是支持TCP/IP通信的直连电脑和经交换机连接都行。但如果你用的是S7-200 SMART或者老式S7-200需要用PPI协议转以太网模块比如通过CP243-1模块或者直接用串口这就不是单纯C#代码层面能解决的问题了。另外上位机网卡的IP地址必须和PLC的IP在同一网段。这句话看起来废话但现场我遇到过很多人配了IP不过网关的、子网掩码不同的所以通信就是不通。连不上的时候我第一批检查的就是IP地址冲突和防火墙Windows防火墙默认会拦掉非本机的入站连接尤其是当你的C#程序作为服务端接收PLC连接时防火墙不添加例外规则连接必然失败。3. 实操过程与核心环节实现既然是聊实现我就把我最常用的一套S7通信方案完整写出来。这一套适合S7-1200/1500/300/400系列不需要在博图里做额外组态前提是PLC开启了Put/Get通信即允许远程通信我用Sharp7来演示因为它的API更底层你能看得清协议动作。3.1 环境准备与依赖引入用NuGet安装Sharp7包或者在GitHub上直接下载源码编译成dll我习惯用NuGetInstall-Package Sharp7然后项目里引用命名空间using Sharp7;还要确认自己的.NET版本。老项目用.NET Framework 4.5以上都能跑Sharp7是纯托管代码不依赖本机Windows API跨平台也可以跑在.NET Core/.NET 5上。如果你是在工控机Windows环境下用WinForm或WPF直接Framework版本最省心。3.2 建立连接与读写DB块的核心代码先封装一个类把连接和断开逻辑收敛起来。我项目里管它叫S7Helper。public class S7Helper { private S7Client _client; public bool Connect(string ip, int rack 0, int slot 1) { _client new S7Client(); int result _client.ConnectTo(ip, rack, slot); return result 0; } public void Disconnect() { _client?.Disconnect(); } public bool ReadDb(int dbNumber, int startByte, int length, out byte[] buffer) { buffer new byte[length]; int result _client.ReadArea(S7Area.DB, dbNumber, startByte, length, S7Client.S7WLByte, buffer); return result 0; } public bool WriteDb(int dbNumber, int startByte, byte[] buffer) { int result _client.WriteArea(S7Area.DB, dbNumber, startByte, buffer.Length, S7Client.S7WLByte, buffer); return result 0; } }这段代码里S7Area.DB表示访问的是DB块区域startByte是字节偏移S7Client.S7WLByte表示数据粒度是字节这样分批读取时不会走到位操作的坑里。来一个实际调用示例假设PLC里DB1的第0字节开始存放了一个Real类型的温度值var helper new S7Helper(); if (helper.Connect(192.168.0.1)) { byte[] buffer; if (helper.ReadDb(1, 0, 4, out buffer)) { float temperature S7.GetRealAt(buffer, 0); Console.WriteLine($温度值: {temperature}); } }Sharp7自带了S7.GetRealAt、S7.GetIntAt、S7.GetDIntAt这一组工具方法它们把大端序转换都处理好了比你自己手工翻转字节序靠谱得多。所以我的建议是用了Sharp7就别再去拿原始byte数组自己折腾BitConverter了它那套工具函数是踩过坑后沉淀出来的。3.3 位读写与Bool数据类型的处理读单个Bool位是S7通信的小难点。在S7协议里Bool是按“字节.位”来定位的比如DB1.DBX0.3表示DB1的第0字节的第3位。Sharp7里有专门的ReadBit和WriteBit方法也可以先读整个字节到上层再按位操作。下面这个函数读取指定字节地址的位调用方式实用public bool ReadBool(int dbNumber, int byteIndex, int bitIndex) { byte[] buffer new byte[1]; int result _client.ReadArea(S7Area.DB, dbNumber, byteIndex, 1, S7Client.S7WLByte, buffer); if (result ! 0) return false; return (buffer[0] (1 bitIndex)) ! 0; }看到没读一整个字节再用位运算取出指定位。这里有个瑕疵需要注意byteIndex说的是DB的字节偏移不是位偏移。很多新手容易把“第几个Bool”当成字节偏移来用比如DB1里有50个Bool他以为第40个Bool的偏移是40实际它可能在第5个字节的某一位上得先算清楚字节偏移和位索引。3.4 字符串类型和结构体的读取PLC里的String类型存储结构比较特殊第一个字节是最大长度第二个字节是当前实际长度后面才是字符数据默认存的是ANSI编码。C#读取时要跳过前两个字节再取字符串byte[] buffer new byte[256]; if (helper.ReadDb(10, 0, 256, out buffer)) { int actualLen buffer[1]; string str Encoding.ASCII.GetString(buffer, 2, actualLen); }如果你是S7-1200/1500博图里默认的中文字符串类型是String不是WString也是ANSI编码。现场读中文出现乱码十有八九是编码问题把ASCII换成GB2312或UTF-8试试看。结构体读取的思路则是把一大块连续内存一次性读出来再用Marshal.PtrToStructure转成C#的结构体对象。这种做法适合整块读取设备状态、配方数据等效率很高但字节对齐问题非常多我一般不用宁愿字段拆开逐一读取代码冗余一点但稳定可靠。3.5 Modbus TCP的完整实现示例如果选的Modbus TCP方案我用EasyModbus做例子因为API更简单网上的资料也多。读取保持寄存器、写入单个线圈基本就能覆盖大多数场景using EasyModbus; ModbusClient client new ModbusClient(192.168.0.10, 502); client.Connect(); // 读保持寄存器起始地址0读10个寄存器 int[] values client.ReadHoldingRegisters(0, 10); // 写单个线圈 client.WriteSingleCoil(0, true); client.Disconnect();这里有个非常隐蔽的坑西门子S7-1200做Modbus TCP服务器时线圈地址和保持寄存器地址在MB_SERVER背景DB里的起始地址是不一致的不是从0开始而是在“数据块起始”里定义的不同偏移。所以你在C#里用的“地址0”可能对应的是PLC里某个“%MW0”实际上这个名词各家不一样必须在博图里核对清楚。3.6 串口Modbus RTU的实战代码如果是走Modbus RTUC#的基础就是SerialPort。初始化串口组帧发出去等回帧解析全程需要超时控制。核心发送函数如下SerialPort sp new SerialPort(COM3, 9600, Parity.None, 8, StopBits.One); sp.Open(); byte[] request new byte[8]; request[0] 0x01; // 从站地址 request[1] 0x03; // 功能码读保持寄存器 request[2] 0x00; // 起始地址高字节 request[3] 0x00; // 起始地址低字节 request[4] 0x00; // 寄存器数量高字节 request[5] 0x02; // 寄存器数量低字节 byte[] crc ModbusCrc(request, 6); request[6] crc[0]; request[7] crc[1]; sp.Write(request, 0, request.Length); // 等待响应读取处理...CRC那段代码很多库自带NModbus也封装好了不需要重复造轮子。重点是帧间隔RTU协议要求帧与帧之间必须有3.5个字符时间的静默间隔如果写得太快或者读得太快设备会丢帧。在现场代码里我会在写请求后加一个短暂延时等响应时用串口DataReceived事件或单独的读取线程避免阻塞UI线程。3.7 OPC UA订阅模式实战OPC UA的优点之一是可以订阅变量变化PLC侧值变了才推送数据不用上位机死循环轮询。C#用UA-.NETStandard的典型代码片段using Opc.Ua; using Opc.Ua.Client; var config new ApplicationConfiguration { ApplicationName MyClient, ApplicationUri urn:MyClient, SecurityConfiguration new SecurityConfiguration { ApplicationCertificate new CertificateIdentifier(), SecurityPolicies new StringCollection() }, TransportConfigurations new TransportConfigurationCollection(), TransportQuotas new TransportQuotas { OperationTimeout 60000 }, ClientConfiguration new ClientConfiguration { DefaultSessionTimeout 60000, MinSubscriptionLifetime 10000 } }; await config.InitAsync(); // 注意实际API略有差异视版本而定 using (var session await Session.Create(config, new ConfiguredEndpoint(null, new EndpointDescription(opc.tcp://192.168.0.10:4840)), false, session, 60000, new UserIdentity(new AnonymousIdentityToken(ProfileNames.UaNoSecurity)), null)) { var subscription new Subscription(session) { PublishingInterval 100 }; session.AddSubscription(subscription); subscription.Create(); // 添加要订阅的节点 var items new ListMonitoredItem { new MonitoredItem(subscription.DefaultItem) { DisplayName Temperature, StartNodeId new NodeId(ns3;s\DB1\.\Temperature\) } }; items.ForEach(i i.Notification OnNotification); subscription.AddItems(items); await Task.Delay(Timeout.Infinite); }OPC UA的C#代码在不同SDK版本里API差异很大上面这段只是参考骨架。实现时建议直接下载官方示例在示例基础上改节点名和地址少走弯路。4. 常见问题与排查技巧实录写代码只是第一步真正让项目动起来的是调试。这章我把这些年现场遇到的高频问题整理成一张速查表再加几个独门排查技巧。4.1 通信失败的常见原因速查现象可能原因排查方向TCP连接建立失败IP网段不对、PLC未开启允许远程访问、防火墙拦截先ping通PLC查PLC连接机制临时关闭Windows防火墙测试连接成功但读取返回错误码DB号不存在、偏移超范围、DB未优化访问核对博图里的DB编号和偏移S7-1200/1500的DB如果是“优化的块访问”部分地址无法通过S7协议直接访问数据读出来全是0DB地址映射错、字节序反转、PLC值本身就是0用博图的监控表对比在线值读多几个字节观察规律Modbus TCP连上但读不到预期值映射表没对、功能码不对、寄存器地址超范围核对MB_SERVER组态里的起始地址用Modbus Poll工具单步验证串口通信乱码波特率/校验位不一致、CRC错误、帧间隔太短串口调试助手先验证确认PLC侧指令的波特率参数OPC UA连接被拒绝证书不信任、安全策略不匹配、用户名密码错误用UA Expert先连PLC在PLC侧添加信任列表4.2 优化DB块访问的坑S7-1200/1500的DB块默认是“优化的块访问”在这种模式下DB里的变量不是按照物理偏移连续存放的S7协议按原始字节偏移读写会失败或者读到的数据对不上。解决办法有三个一是把DB块的属性改成“非优化的块访问”在博图里右键DB属性把“优化的块访问”勾去掉这样DB内部按物理地址排布S7协议读写地址就好算了。缺点是会牺牲一点PLC内部访问效率但对通信来说最省事。二是在PLC里做一个专门的“通信数据区”把要交换的数据用MOVE指令或SCL代码拷贝到一个非优化的DB块上位机只读写这个通信DB业务DB保持不变。这种方式结构清晰也方便后期维护。第三种是直接用Symbolic寻址的S7通信方式但C#库里支持不好实际项目用得少我就不推荐了。4.3 性能优化与轮询策略很多人写第一个版本是“一秒循环读一次所有数据”PLC数据量小的时候没问题但一旦点数上百、还有多个客户端同时连PLC的通信负载就上来了容易出现响应延迟甚至连接被断开。我的经验是把要读的数据分成“高速区”和“低速区”高速区域100ms轮询一次低速区域500ms到1s轮询一次。尽量用一次读取多个连续字节的方式而不是一条条读。比如你要读DB1里10个Real一口气读了40个字节比分10次读快得多。在C#侧开独立的通信线程不要用UI线程跑通信否则界面一卡通信就断了。举个典型的优化前代码for (int i 0; i 50; i) { ReadDb(10, i * 4, 4); // 循环50次 }优化后ReadDb(10, 0, 200); // 一次读取200字节效率不是一个数量级的。4.4 日志与状态记录的建议通信程序最容易出“偶发性”问题跑一小时后突然断一次、数据出现毛刺、某一次读写超时。这种问题不带日志根本没法查所以从一开始就要打好日志。我不建议你用Console.WriteLine打几条就完事最好用NLog或者Serilog这种库把通信时间、函数名、错误码、原始报文全部记录下来。我自己惯用的做法是所有通信操作都包一层Log成功时记录耗时失败时记录完整的错误信息和上下文报文层面在调试模式下记录十六进制原始数据。// 伪代码示例 Logger.Info($开始读取DB{dbNumber}, 偏移{offset}, 长度{length}); try { int result _client.ReadArea(...); Logger.Info($读取耗时{sw.ElapsedMilliseconds}ms, 结果:{result}); } catch (Exception ex) { Logger.Error(ex, 读取异常); }有了这份日志现场的很多疑难杂症其实都能在日志里找到答案到底是PLC重启了还是网络闪断还是某个数据点导致异常一目了然。4.5 多客户端并发与连接管理很多产线不是单台上位机连PLC而是有几台上位机、还有触摸屏、还有MES系统全部连同一个PLC。这时就要注意S7-1200/1500的连接资源有限如果连接数不够后来者会被拒。必要的时候要在PLC属性里调整“连接资源”的预留数量。C#端的长连接不能因为一时网络抖动就马上重连要做“断线重连”逻辑并且在重连时加退避延迟比如1s、2s、4s递增防止多台客户端同时疯狂重连把PLC打挂。如果多台上位机需要同步状态优先让PLC做数据源上位机之间不要直接互相通信保持架构简单排查问题也容易。我自己见过最惨的一次某个项目有3台上位机都是同一套代码通信断了以后又同时自动重连结果PLC的通信负载直接拉满连触摸屏都开始卡了。后来加了退避重连和连接数保护这个问题才消停。4.6 项目实践中的独家心得最后分享几个我自己的“独门”习惯不一定写在上位机设计文档里但非常管用第一先在PLC侧用工具软件验证再写代码。连不上PLC先把问题定位清楚。我用过S7客户端调试工具比如Sharp7自带示例、或者S7.NET的测试工具能连上再开始写C#代码这样就能确定是“代码问题”还是“PLC配置问题”。第二所有通信参数做成配置文件。IP地址、端口、DB号、轮询频率、超时时间不要硬编码在代码里统一放在App.config或json里。现场改参数是家常便饭每次改代码重新编译太痛苦。第三UI和数据采集解耦。通信层只负责收发数据把结果放到一个共享的数据模型里UI绑定这个模型显示。如果通信逻辑写在按钮点击事件里或者写在窗体的Timer事件里代码很快就变成意大利面条后期维护成本极高。第四警惕符号寻址的坑。很多客户会在博图里给DB块变量起中文名或语义化名字但S7协议底层不认符号名它只认地址偏移量。项目交接时如果只给PLC程序不给地址映射表上位机工程师会非常难受。所以从一开始就要和电气工程师约定好哪个DB块是通信区每个变量偏移是多少做一张表挂在项目文档里双方都方便。第五上线前做一次“拔线测试”。模拟现场可能出现的网络中断情况通信进行到一半时拔掉网线看程序是否能检测到异常并恢复。很多程序平时跑得好好的一拔线就崩溃有的卡死在Read函数里出不来有的直接报未处理异常。提前做断电、断网、重启PLC等极端测试能省太多现场服务时间。写在最后的一点体会C#和西门子通信表面上是写代码实际上拼的是通信协议的理解、对PLC工作方式的熟悉、以及现场调试的经验。很多人觉得Sharp7、EasyModbus这些库一用就完事真到了现场才发现坑全在代码后面IP配置、DB块属性、防火墙、字节序、连接资源、并发策略哪一个都可能让项目翻车。做了这么多年我的体会是先把S7协议练熟悉因为西门子场景里它最直接最高效再用Modbus TCP兜底因为它的通用性好能应付跨品牌的场合OPC UA是更高阶的选择适合需要进MES、做大数据平台的场景。三者之间并不是竞争关系而是不同场景下的工具组合。如果再让我给刚入行的自己一个建议我会说不要急着抄代码先把PLC和上位机的通信链路画出图来想清楚数据从哪来、经过什么协议、到哪去再动手写每一行代码。通信这件事图纸在脑子里清晰了代码自然就顺了。
企业数字化 ERP 产品动态
相关推荐
工业异构设备协议转换:基于STM32F407的硬件方案与协议栈实现 1. 工业互联升级的核心痛点与方案选型1.1 异构设备协议转换到底难在哪里干了十几年工业自动化,最头疼的事情从来不是写PLC逻辑或者调伺服参数,而是面对一堆来自不同年代、不同品牌的设备,它们各自说着不同的“方言”,却要在一个系… · 2026/9/24 23:34:59
Java Lambda与匿名类深度对比:从原理到重构实践 1. 为什么“Lambda优先于匿名类”一直被反复提1.1 第42条到底在解决什么问题如果你是Java开发者,有一本书绕不过去:Joshua Bloch的《Effective Java》。Java 8之后,这本书新增了一大批关于Lambda和Stream的条目,其中第42条“Lambd… · 2026/9/24 23:34:59
C#与西门子PLC通信实战:从S7协议到OPC UA的全面解析 1. 通信方案选型:没有最好的协议,只有最合适的场景1.1 先搞清你面对的西门子PLC型号在动手写C#代码之前,我建议你先花五分钟确认自己手里到底是哪一代西门子PLC。这决定了后面所有通信方案的选择,选错了方向,代码写得再… · 2026/9/24 23:34:52
F´ 框架 TokenBucket 令牌桶限流工具深度解析:突发流量控制与源码实践 嵌入式系统编程 【免费下载链接】fprime F - A flight software and embedded systems framework 项目地址: https://gitcode.com/gh_mirrors/fp/fprime 点击查看 免费下载 导读
本文围绕 F(F Prime)飞行软件与嵌入式系统框架中 Utils::Tok… · 2026/9/25 1:31:06
Proteus DHT11仿真教程:51单片机温湿度读取与LCD显示 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:31:00
OFDM PAPR抑制新方案:分组SLM结合幅值标记,免边带低复杂度 简介:针对正交频分复用系统中传统SLM方法计算复杂度高、需要额外带宽传输边带信息的问题,这份PDF资料提供了一种低复杂度改进方案。内容以学术论文形式完整呈现:先介绍正交频分复用峰均功率比问题及现有SLM改进思路,再给出发送端对… · 2026/9/25 1:31:00
MCU选型实战指南:从需求分析到国产替代的完整流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:31:00
ESP32-S3麦克风阵列实战:从选型到回声消除 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:31:00
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37