C#实现三菱FX3SA PLC串口通信测试实例
硬件:
使用RS232或RS485转USB转换器
连接PLC的编程口(通常是8针圆口)
软件VS2019
PLC设置:
确保PLC的通信参数与程序设置一致:
波特率:9600
数据位:7
校验位:偶校验
停止位:1
程序功能实发送报文 0x02, 0x30, 0x31, 0x30, 0x31, 0x34, 0x30, 0x32, 0x03, 0x35, 0x42
对接收到的报文数据进行处理实时读取PLC中数据寄存器D10存储数据,程序如下:
using System;
using System.IO.Ports;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Diagnostics;namespace PLCCommunicationTest
{public partial class Form1 : Form{private ComboBox comboBoxPorts;private Button btnRefreshPorts;private Button btnConnect;private Button btnDisconnect;private Button btnStartReading;private Button btnStopReading;private Label lblValueDisplay;private Label lblStatus;private Label lblRawData;private SerialPort serialPort1;private Thread readThread;private ManualResetEvent stopEvent = new ManualResetEvent(false);private object serialLock = new object();private bool isReading = false;public Form1(){InitializeCustomComponents();InitializeSerialPort();RefreshSerialPorts();}private void InitializeCustomComponents(){this.Text = "三菱FX3SA PLC数据监视 - 修正版";this.Size = new Size(600, 450);this.FormClosing += Form1_FormClosing;this.BackColor = Color.White;// 创建控件comboBoxPorts = new ComboBox();btnRefreshPorts = new Button();btnConnect = new Button();btnDisconnect = new Button();btnStartReading = new Button();btnStopReading = new Button();lblValueDisplay = new Label();lblStatus = new Label();lblRawData = new Label();serialPort1 = new SerialPort();// 配置comboBoxPortscomboBoxPorts.Location = new Point(12, 12);comboBoxPorts.Size = new Size(80, 21);comboBoxPorts.DropDownStyle = ComboBoxStyle.DropDownList;// 配置btnRefreshPortsbtnRefreshPorts.Location = new Point(100, 10);btnRefreshPorts.Size = new Size(60, 23);btnRefreshPorts.Text = "刷新";btnRefreshPorts.Click += btnRefreshPorts_Click;// 配置btnConnectbtnConnect.Location = new Point(170, 10);btnConnect.Size = new Size(60, 23);btnConnect.Text = "连接";btnConnect.Click += btnConnect_Click;// 配置btnDisconnectbtnDisconnect.Location = new Point(240, 10);btnDisconnect.Size = new Size(60, 23);btnDisconnect.Text = "断开";btnDisconnect.Enabled = false;btnDisconnect.Click += btnDisconnect_Click;// 配置btnStartReadingbtnStartReading.Location = new Point(310, 10);btnStartReading.Size = new Size(80, 23);btnStartReading.Text = "开始读取";btnStartReading.Enabled = false;btnStartReading.Click += btnStartReading_Click;// 配置btnStopReadingbtnStopReading.Location = new Point(400, 10);btnStopReading.Size = new Size(80, 23);btnStopReading.Text = "停止读取";btnStopReading.Enabled = false;btnStopReading.Click += btnStopReading_Click;// 配置lblValueDisplaylblValueDisplay.Location = new Point(12, 50);lblValueDisplay.Size = new Size(560, 150);lblValueDisplay.Text = "0";lblValueDisplay.TextAlign = ContentAlignment.MiddleCenter;lblValueDisplay.Font = new Font("Arial", 48, FontStyle.Bold);lblValueDisplay.ForeColor = Color.Blue;lblValueDisplay.BackColor = Color.LightGray;lblValueDisplay.BorderStyle = BorderStyle.FixedSingle;// 配置状态标签lblStatus.Location = new Point(12, 210);lblStatus.Size = new Size(560, 25);lblStatus.Text = "就绪";lblStatus.TextAlign = ContentAlignment.MiddleCenter;lblStatus.ForeColor = Color.Green;lblStatus.Font = new Font("Arial", 10, FontStyle.Regular);// 配置原始数据显示lblRawData.Location = new Point(12, 245);lblRawData.Size = new Size(560, 60);lblRawData.Text = "原始数据: 无";lblRawData.TextAlign = ContentAlignment.MiddleCenter;lblRawData.ForeColor = Color.DarkBlue;lblRawData.Font = new Font("Arial", 9, FontStyle.Regular);lblRawData.BorderStyle = BorderStyle.FixedSingle;// 添加控件到窗体this.Controls.Add(comboBoxPorts);this.Controls.Add(btnRefreshPorts);this.Controls.Add(btnConnect);this.Controls.Add(btnDisconnect);this.Controls.Add(btnStartReading);this.Controls.Add(btnStopReading);this.Controls.Add(lblValueDisplay);this.Controls.Add(lblStatus);this.Controls.Add(lblRawData);}private void InitializeSerialPort(){serialPort1.BaudRate = 9600;serialPort1.DataBits = 7;serialPort1.Parity = Parity.Even;serialPort1.StopBits = StopBits.One;serialPort1.Handshake = Handshake.None;serialPort1.ReadTimeout = 1000;serialPort1.WriteTimeout = 1000;}private void RefreshSerialPorts(){comboBoxPorts.Items.Clear();string[] ports = SerialPort.GetPortNames();comboBoxPorts.Items.AddRange(ports);if (comboBoxPorts.Items.Count > 0)comboBoxPorts.SelectedIndex = 0;}private void ReadThreadMethod(){while (!stopEvent.WaitOne(0) && isReading){if (serialPort1.IsOpen){PerformSingleRead();}Thread.Sleep(300);}}private void PerformSingleRead(){lock (serialLock){try{serialPort1.DiscardInBuffer();serialPort1.DiscardOutBuffer();// 发送读取命令byte[] commandBytes = new byte[]{0x02, 0x30, 0x31, 0x30, 0x31, 0x34, 0x30, 0x32, 0x03, 0x35, 0x42};serialPort1.Write(commandBytes, 0, commandBytes.Length);Thread.Sleep(150);if (serialPort1.BytesToRead > 0){byte[] buffer = new byte[serialPort1.BytesToRead];int bytesRead = serialPort1.Read(buffer, 0, buffer.Length);if (bytesRead > 0){string result = ProcessPLCData(buffer);UpdateUI(result, buffer);}}else{UpdateUI("无响应", new byte[0]);}}catch (Exception ex){UpdateUI($"错误: {ex.Message}", new byte[0]);}}}private void UpdateUI(string value, byte[] rawData){if (lblValueDisplay.InvokeRequired){lblValueDisplay.Invoke(new Action<string, byte[]>(UpdateUI), value, rawData);return;}if (value.StartsWith("错误") || value == "无响应"){lblValueDisplay.Text = value;lblValueDisplay.ForeColor = Color.Red;lblStatus.Text = "读取失败";lblStatus.ForeColor = Color.Red;}else{lblValueDisplay.Text = value;lblValueDisplay.ForeColor = Color.Blue;lblStatus.Text = "读取成功";lblStatus.ForeColor = Color.Green;}// 显示原始数据if (rawData.Length > 0){string hexString = BitConverter.ToString(rawData).Replace("-", " ");lblRawData.Text = $"原始数据: {hexString}";}else{lblRawData.Text = "原始数据: 无";}}// 处理接收到的PLC数据并提取数值 - 修正版本private string ProcessPLCData(byte[] receivedData){try{// 检查数据长度是否足够且格式正确if (receivedData.Length < 6 || receivedData[0] != 0x02){return "格式错误";}// 查找结束符int endIndex = -1;for (int i = 1; i < receivedData.Length; i++){if (receivedData[i] == 0x03){endIndex = i;break;}}if (endIndex == -1 || endIndex < 5){return "帧错误";}// 提取第2到第5个字节 (索引1-4)byte[] extractedBytes = new byte[4];Array.Copy(receivedData, 1, extractedBytes, 0, 4);// 重新排列: 第4、5字节在前,第2、3字节在后byte[] rearrangedBytes = new byte[4];rearrangedBytes[0] = extractedBytes[2]; // 原第4字节rearrangedBytes[1] = extractedBytes[3]; // 原第5字节rearrangedBytes[2] = extractedBytes[0]; // 原第2字节rearrangedBytes[3] = extractedBytes[1]; // 原第3字节// 关键修正:将ASCII字节转换为字符串,然后解析为十六进制string hexString = Encoding.ASCII.GetString(rearrangedBytes);// 将十六进制字符串转换为十进制int decimalValue = Convert.ToInt32(hexString, 16);return decimalValue.ToString();}catch (Exception ex){return $"解析错误: {ex.Message}";}}// 其他方法保持不变...private void btnRefreshPorts_Click(object sender, EventArgs e){RefreshSerialPorts();}private void btnConnect_Click(object sender, EventArgs e){if (comboBoxPorts.SelectedItem == null){MessageBox.Show("请选择串口");return;}try{serialPort1.PortName = comboBoxPorts.SelectedItem.ToString();serialPort1.Open();btnConnect.Enabled = false;btnDisconnect.Enabled = true;btnStartReading.Enabled = true;UpdateUI("0", new byte[0]);lblStatus.Text = "已连接";lblStatus.ForeColor = Color.Green;}catch (Exception ex){MessageBox.Show($"连接失败: {ex.Message}");}}private void btnDisconnect_Click(object sender, EventArgs e){StopReading();try{if (serialPort1.IsOpen)serialPort1.Close();btnConnect.Enabled = true;btnDisconnect.Enabled = false;btnStartReading.Enabled = false;btnStopReading.Enabled = false;UpdateUI("0", new byte[0]);lblStatus.Text = "已断开";lblStatus.ForeColor = Color.Blue;}catch (Exception ex){MessageBox.Show($"断开连接失败: {ex.Message}");}}private void btnStartReading_Click(object sender, EventArgs e){StartReading();}private void btnStopReading_Click(object sender, EventArgs e){StopReading();}private void StartReading(){if (!isReading && serialPort1.IsOpen){isReading = true;stopEvent.Reset();readThread = new Thread(ReadThreadMethod);readThread.IsBackground = true;readThread.Start();btnStartReading.Enabled = false;btnStopReading.Enabled = true;lblStatus.Text = "正在读取...";lblStatus.ForeColor = Color.Blue;}}private void StopReading(){if (isReading){isReading = false;stopEvent.Set();if (readThread != null && readThread.IsAlive){readThread.Join(1000);}btnStartReading.Enabled = true;btnStopReading.Enabled = false;lblStatus.Text = "读取已停止";lblStatus.ForeColor = Color.Blue;}}private void Form1_FormClosing(object sender, FormClosingEventArgs e){StopReading();if (serialPort1.IsOpen)serialPort1.Close();}private void InitializeComponent(){this.SuspendLayout();this.ClientSize = new System.Drawing.Size(284, 261);this.Name = "Form1";this.ResumeLayout(false);}}
}
运行后如图: