
简介本资源是面向工业自动化工程师与嵌入式开发者的Fatek PLC源码深度解析资料包聚焦PLC底层逻辑、通信协议与实时控制机制的代码级学习适用于具备C语言基础及PLC使用经验的技术人员开展二次开发、故障溯源或系统优化。压缩包共51个文件以14个VB源文件含frm_main.vb、Fatek.vb等核心逻辑模块、4个resx本地化资源、2个sln/.vbproj工程文件及2个cd类图文件为主干辅以exe可执行示例、xml配置、pdb调试符号等完整呈现从UI层到驱动层的模块化架构总大小仅147KB轻量但结构严谨。已有63人下载学习资源目录清晰体现典型PLC软件分层设计My Project含配置与设置obj/bin含编译产物_UpgradeReport_Files记录迁移日志Backup保留历史版本便于按功能模块逐层研读。读者可借此掌握梯形图编译逻辑、Modbus通信实现、中断响应流程及RTOS任务调度等关键能力是少有的可直接运行、可调试、可对照分析的Fatek源码实践样本。1. 这不是PLC固件包而是一套可调试的VB.NET上位机工程源码打开Fatek-Src.rar后你不会看到.hex、.bin或.fw这类嵌入式固件文件也不会找到任何 ARM Cortex-M 或 8051 汇编代码——它实际是一个完整的 Visual Studio 2010或兼容版本Windows Forms 项目核心是Fatek.vbproj和frm_main.vb。这意味着它不运行在 PLC 硬件上而是运行在 PC 上作为 Fatek PLC 的上位监控/编程/通信调试工具。它的价值不在“控制逻辑执行”而在“如何与 Fatek PLC 对话”解析 FBs-PLC 协议帧、构建 Modbus RTU/TCP 请求、处理串口超时重传、映射寄存器地址到 UI 控件、同步多线程数据刷新。对自动化工程师而言这不是拿来即用的软件而是可打断点、单步跟踪、修改协议字段、注入测试指令的“活体通信沙盒”。如果你正被 Fatek 的CMD0x10写多个寄存器失败卡住或搞不清FBs-PLC响应包中Status Code0x03 和 0x04 的区别这个源码就是最接近硬件手册的实操入口。它适合有 VB.NET 基础、熟悉串口通信原理、且手头已有 Fatek PLC如 FBs-10MAR、FBS-20MN进行联调验证的现场工程师。2. 从 frm_main.vb 入口切入理解上位机与 PLC 的通信生命周期2.1 主窗体结构与事件驱动模型解析frm_main.vb是整个项目的 UI 入口和逻辑中枢。它并非简单的按钮点击响应而是构建了一个典型的工业上位机状态机初始化 → 连接建立 → 数据轮询 → 异常恢复 → 断开清理。关键成员变量包括Private WithEvents serialPort As New SerialPort() 串口通信对象支持事件驱动 Private plcCommTimer As New Timer() 定时器控制轮询周期默认 200ms Private regDataBuffer(65535) As UShort 寄存器数据缓存区按地址索引 Private isConnected As Boolean False 连接状态标志影响UI控件使能注意WithEvents关键字使serialPort能触发DataReceived、ErrorReceived等事件这是避免阻塞主线程、实现高响应性的基础。若直接用serialPort.Read()同步读取UI 将在通信卡顿时完全冻结。Load事件中完成初始化加载My.Settings中保存的串口参数COM3,9600,None,8,1绑定plcCommTimer.Tick到PollPLCData()方法设置serialPort.DataReceived事件处理器为HandleSerialDataReceived这决定了整个通信流程的节奏定时器驱动轮询请求发送串口事件驱动响应接收二者异步解耦。2.2 FBs-PLC 协议帧构造与校验逻辑Fatek 自有协议非标准 Modbus的帧格式在Fatek.vb模块中定义。典型读取 10 个保持寄存器Holding Register的请求帧为字段长度值示例说明STX1 byteH02起始符 ASCII SOHStation ID1 byteH01PLC 站号1~31CMD1 byteH11命令码0x11读HR0x12写HR0x13读IRStart Address2 bytesH0000起始地址高位在前Count2 bytesH000A读取数量10个CRC2 bytesHXXXXModbus-RTU CRC16 校验Fatek.vb中BuildReadHRFrame()方法生成该帧Public Function BuildReadHRFrame(stationID As Byte, startAddr As UShort, count As UShort) As Byte() Dim frame As New List(Of Byte) frame.Add(H02) STX frame.Add(stationID) frame.Add(H11) CMD frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(CShort(startAddr)))) 高位在前 frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(CShort(count)))) Dim crc As UShort CalculateCRC16(frame.ToArray(), 0, frame.Count) frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(CShort(crc)))) Return frame.ToArray() End Function逻辑说明IPAddress.HostToNetworkOrder()是关键它将小端序主机字节序Intel x86转换为大端序网络字节序确保startAddr和count的高位字节排在前面。若遗漏此转换PLC 将解析出错误地址如0x0000变成0x0000正常但0x1234会变成0x3412。CRC 计算使用标准 Modbus CRC16 多项式0xA001算法在CalculateCRC16()中实现需严格匹配 PLC 端计算逻辑。2.3 响应帧解析与状态码映射PLC 返回的响应帧结构为STX (H02)Station IDCMD EchoData Length (1 byte)Data Bytes (长度由 Data Length 指定)Status Code (1 byte)CRCHandleSerialDataReceived()中调用ParseResponseFrame()解析If response(4) H00 Then Status Code 非零表示错误 Select Case response(4) Case H01 : MessageBox.Show(PLC Busy) Case H02 : MessageBox.Show(Invalid Address) Case H03 : MessageBox.Show(Invalid Count) Case H04 : MessageBox.Show(CRC Error) Case Else : MessageBox.Show($Unknown Status: {response(4):X2}) End Select Exit Sub End If 正常响应解析 Data Bytes 到 regDataBuffer Dim dataLen As Integer response(3) For i As Integer 0 To dataLen - 1 regDataBuffer(startAddr i) BitConverter.ToUInt16(response, 5 i * 2) Next参数说明response(4)是状态码位置response(3)是数据长度字节。dataLen表示后续数据字节数每个寄存器占 2 字节故循环步长为 2。startAddr来自请求帧用于定位缓存区写入起始位置。此设计允许同一帧响应多个寄存器避免频繁通信。3. 工程配置与依赖VS2010 环境下的关键设置项3.1 项目属性与目标框架适配Fatek.vbproj文件指明了 .NET Framework 版本TargetFrameworkVersionv4.0/TargetFrameworkVersion这意味着必须使用 Visual Studio 2010 或更高版本如 VS2019 需启用 .NET Framework 4.0 兼容模式。在 VS 中打开项目后需检查项目 → 属性 → 编译 → 高级编译选项Option Strict应设为On强制类型安全Option Explicit为On禁止隐式变量声明。源码中大量使用Dim addr As UShort而非Dim addr正是此设置的体现。项目 → 属性 → 应用程序 → 目标框架确认为.NET Framework 4.0。若误选.NET Core或.NET 5SerialPort类将不可用需改用System.IO.Ports.SerialPort且 API 不同。3.2 My Project 配置与用户设置持久化My Project文件夹包含Settings.settings定义了可序列化的用户配置设置名类型默认值用途ComPortNameStringCOM3串口号保存至user.configBaudRateInteger9600波特率DataBitsInteger8数据位ParityStringNone校验位StopBitsStringOne停止位这些设置通过My.Settings在代码中访问serialPort.PortName My.Settings.ComPortName serialPort.BaudRate My.Settings.BaudRate ... 其他参数提示首次运行时user.config文件位于%USERPROFILE%\AppData\Local\AppName\下。修改设置后需调用My.Settings.Save()才能持久化。若发现设置不生效检查该文件是否被权限锁定或路径不存在。3.3 UpgradeLog.XML 与迁移兼容性处理UpgradeLog.XML是 VS 升级项目时生成的日志记录了从旧版如 VS2008迁移到当前版本的变更。关键信息包括UpgradeIssue标签列出不兼容项如Microsoft.VisualBasic.Devices.Computer在 .NET 4.0 中行为变更SolutionUpgrade显示解决方案文件格式升级.sln文件头从# Visual Studio 2008变为# Visual Studio 2010若在新 VS 中打开报错 “无法加载项目”需手动编辑.vbproj文件将TargetFrameworkVersion改为当前环境支持的版本并删除OldToolsVersion节点。_UpgradeReport_Files文件夹中的 HTML 报告可辅助定位具体问题。4. 实战调试定位通信超时与寄存器读写异常4.1 串口通信超时的三层排查法当serialPort.ReadTimeout设为 500ms 但始终触发TimeoutException按以下顺序排查4.1.1 硬件层验证使用串口调试助手如 XCOM发送原始十六进制帧02 01 11 00 00 00 0A C7 2E读 HR 0x0000 共 10 个观察 PLC 是否返回响应。若无响应检查接线RS485 A/B 线是否反接终端电阻是否启用PLC 设置COM1是否启用为 RS485 模式站号是否设为0x01电源PLC 与 PC 是否共地隔离模块是否故障4.1.2 驱动层验证在设备管理器中确认COM3存在且无黄色感叹号运行mode COM3命令检查返回的Baud: 9600等参数是否与代码一致用SerialPort.GetPortNames()在代码中打印可用端口确认COM3在列表中4.1.3 代码层验证在serialPort.Open()后添加日志Debug.WriteLine($Port {serialPort.PortName} opened, IsOpen{serialPort.IsOpen})在serialPort.Write()前打印发送帧Debug.WriteLine($Send: {BitConverter.ToString(frame)})在DataReceived事件开头添加Debug.WriteLine($Received {serialPort.BytesToRead} bytes)关键技巧serialPort.BytesToRead返回缓冲区字节数若为 0 说明 PLC 未发回数据若大于 0 但serialPort.Read()读取字节数小于预期可能是帧不完整需等待更多字节或 CRC 错误被丢弃。4.2 寄存器地址映射错位的定位与修复常见现象UI 显示D100值为0但 PLC 实际为1234。根源在于地址偏移计算错误。Fatek.vb中ConvertPLCToVBAddress()方法负责转换Public Function ConvertPLCToVBAddress(plcAddr As String) As UShort PLC 地址格式D100, R1000, M100 If plcAddr.StartsWith(D) Then Return CUInt(plcAddr.Substring(1)) D100 → 100 ElseIf plcAddr.StartsWith(R) Then Return CUInt(plcAddr.Substring(1)) 10000 R1000 → 11000 End If End Function但 Fatek PLC 的 D 区实际起始地址为0x0000而 R 区为0x271010000 十进制。若 UI 输入D100代码返回100则BuildReadHRFrame()中startAddr100PLC 解析为D100正确。但若 PLC 文档规定 D 区地址需加偏移0x1000则此处应改为Return CUInt(plcAddr.Substring(1)) H1000。验证方法在PollPLCData()中于BuildReadHRFrame()调用后插入Debug.WriteLine($Reading HR from address {startAddr:X4}, count {count})对比 PLC 编程软件如 WinProladder中D100对应的实际寄存器编号通常为0x0064确认startAddr值是否匹配。5. 协议扩展向源码注入 Modbus TCP 支持5.1 构建 TCP 通信抽象层现有代码仅支持串口SerialPort要支持以太网需新增TcpPLCClient类。核心是替换serialPort为TcpClient并复用帧构造逻辑Public Class TcpPLCClient Private client As New TcpClient() Private stream As NetworkStream Private stationID As Byte H01 Public Sub Connect(ip As String, port As Integer) client.Connect(ip, port) stream client.GetStream() End Sub Public Function ReadHoldingRegisters(startAddr As UShort, count As UShort) As UShort() Dim frame As Byte() Fatek.BuildReadHRFrame(stationID, startAddr, count) stream.Write(frame, 0, frame.Length) 读取响应先读固定头STXIDCMDDataLenStatus再读数据 Dim header(6) As Byte STX(1)ID(1)CMD(1)DataLen(1)Status(1)CRC_H(1)CRC_L(1) stream.Read(header, 0, header.Length) If header(4) H00 Then Throw New Exception($PLC Error: {header(4):X2}) Dim dataLen As Integer header(3) Dim data(dataLen - 1) As Byte stream.Read(data, 0, dataLen) 解析 data 到 UShort 数组 Dim result(dataLen \ 2 - 1) As UShort For i As Integer 0 To dataLen - 1 Step 2 result(i \ 2) BitConverter.ToUInt16(data, i) Next Return result End Function End Class逻辑说明TcpClient的NetworkStream提供同步读写避免了串口事件的复杂性。Read()方法会阻塞直到读取指定字节数因此需精确计算响应帧长度。header(4)仍为状态码位置确保错误处理逻辑复用。5.2 UI 集成与连接模式切换在frm_main.vb中添加RadioButtonrbSerial串口、rbTCPTCPTextBoxtxtIPIP 地址、txtPort端口默认 502连接按钮事件Private Sub btnConnect_Click(sender As Object, e As EventArgs) Handles btnConnect.Click If rbSerial.Checked Then 原有串口连接逻辑 serialPort.Open() Else 新增 TCP 连接 tcpClient.Connect(txtIP.Text, CInt(txtPort.Text)) isConnected True lblStatus.Text Connected via TCP End If End Sub参数说明Fatek PLC 的 Modbus TCP 默认端口为502但部分型号如 FBs-20MN可能使用8502。txtPort.Text需做数值校验防止输入非数字导致CInt()异常。5.3 帧格式兼容性处理Fatek 的自有协议与标准 Modbus TCP 不同。若 PLC 固件支持 Modbus TCP则请求帧无需STX和Station ID而是标准 MBAP 头字段长度值Transaction ID2 bytesH0001Protocol ID2 bytesH0000Length2 bytesH0006后续字节数Unit ID1 byteH01等效 Station IDFunction Code1 byteH03读保持寄存器Start Address2 bytesH0064D100Quantity2 bytesH000A10个此时需在BuildReadHRFrame()中根据通信模式分支If isTCP Then 构造 Modbus TCP 帧 frame.AddRange(BitConverter.GetBytes(CUShort(H0001))) Transaction ID frame.AddRange(BitConverter.GetBytes(CUShort(H0000))) Protocol ID frame.AddRange(BitConverter.GetBytes(CUShort(H0006))) Length frame.Add(stationID) Unit ID frame.Add(H03) Function Code frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(CShort(startAddr)))) frame.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(CShort(count)))) Else 原有 FBs-PLC 帧 End If关键技巧isTCP标志需全局传递建议在Fatek.vb模块中定义Public Shared isModbusTCP As Boolean False并在连接时设置。这样所有帧构造函数都能感知模式避免重复修改。本文还有配套的精品资源点击获取