首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 序列埠通訊實用類別:PortData 同步收發與指令回覆模式

     編輯:關於C#

前言

工業控制常需透過 RS-232/485 進行主從式通訊。本文提供一個 PortData 類別,封裝 SerialPort,支援事件式非同步接收、同步指令回覆 (SendCommand)、錯誤處理與緩衝區管理,適合 Moxa 等工業級裝置開發。

完整程式碼


using System;
using System.IO.Ports;
using System.Threading;

public class PortData
{
    // 事件定義
    public event EventHandler Received;
    public event EventHandler Error;

    public SerialPort Port { get; private set; }
    public bool ReceiveEventFlag = false; // true=暫停事件接收

    public PortData(string portName, int baudRate, Parity parity, SerialInterface.SerialMode mode)
    {
        Port = new SerialPort(portName, baudRate, parity, 8, StopBits.One);
        Port.RtsEnable = true;
        Port.ReadTimeout = 3000;
        Port.DataReceived += OnDataReceived;
        Port.ErrorReceived += OnErrorReceived;
    }

    ~PortData() => Close();

    public void Open() { if (!Port.IsOpen) Port.Open(); }
    public void Close() { if (Port.IsOpen) Port.Close(); }

    // 發送資料
    public void SendData(byte[] data) => SendData(data, 0, data.Length);
    public void SendData(byte[] data, int offset, int count)
    {
        if (Port.IsOpen) Port.Write(data, offset, count);
    }

    // 同步指令:發送 -> 等待回覆 (含逾時)
    public int SendCommand(byte[] sendData, ref byte[] receiveData, int overtimeMs)
    {
        if (!Port.IsOpen) return -1;

        ReceiveEventFlag = true; // 暫停事件,避免干擾同步讀取
        Port.DiscardInBuffer();  // 清空接收緩衝區
        Port.Write(sendData, 0, sendData.Length);

        int elapsed = 0;
        while (elapsed < overtimeMs)
        {
            if (Port.BytesToRead >= receiveData.Length) break;
            Thread.Sleep(1);
            elapsed++;
        }

        int ret = 0;
        if (Port.BytesToRead >= receiveData.Length)
            ret = Port.Read(receiveData, 0, receiveData.Length);

        ReceiveEventFlag = false; // 恢復事件
        return ret;
    }

    private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        => Error?.Invoke(sender, e);

    private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (ReceiveEventFlag) return; // 同步模式下忽略事件
        byte[] data = new byte[Port.BytesToRead];
        Port.Read(data, 0, data.Length);
        Received?.Invoke(sender, new PortDataReciveEventArgs(data));
    }

    public bool IsOpen() => Port.IsOpen;
}

// 事件參數
public class PortDataReciveEventArgs : EventArgs
{
    public byte[] Data { get; set; }
    public PortDataReciveEventArgs() { Data = null; }
    public PortDataReciveEventArgs(byte[] data) { Data = data; }
}

關鍵設計解析

  1. ReceiveEventFlag 雙模式切換:同步指令期間暫停事件處理,避免事件執行緒與主執行緒競爭 BytesToRead/Read 導致資料撕裂。
  2. DiscardInBuffer:發送指令前清空殘留資料,確保讀到的是對應回覆。
  3. 輪詢逾時:while (elapsed < overtimeMs) + Thread.Sleep(1) 簡單實作阻塞等待,精度約 1ms。
  4. COM 埠命名:COM1~COM9 用 "COMx:",COM10 以上舊版建議 "\\\\.\\COMx:",Moxa 裝置需 "$device\\COM{num}\\0" 格式。

使用範例


// 初始化
PortData port = new PortData("COM1", 9600, Parity.None, SerialInterface.SerialMode.RS232);
port.Received += (s, e) => { /* 非同步處理 e.Data */ };
port.Error += (s, e) => { /* 錯誤處理 */ };
port.Open();

// 發送指令並等待回覆 (例如 Modbus RTU)
byte[] cmd = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B }; // 讀保持暫存器
byte[] resp = new byte[9]; // 預期 9 位元組回覆
int len = port.SendCommand(cmd, ref resp, 500); // 500ms 逾時
if (len > 0) { /* 驗證 CRC、解析資料 */ }

// 關閉
port.Close();

進階建議

  • 非同步改用 Task:.NET 4.5+ 可用 SerialPort.BaseStream.ReadAsync 實作真正非同步 SendCommandAsync。
  • 通訊協定封裝:將幀組裝、CRC 計算、重試邏輯封裝於上層協定類別,保持 PortData 專注於傳輸層。
  • 多埠管理:建立 PortManager 統一管理多個 PortData 實例,提供依裝置 ID 取得通道功能。

結語

PortData 類別展示了工業序列埠通訊的核心模式:事件式非同步監聽 + 同步指令回覆雙軌並行。ReceiveEventFlag 切換機制簡單有效,解決了同步/非同步並存的資料完整性問題。配合正確的 COM 埠命名與逾時處理,可直接應用於 Moxa、Advantech 等工業級硬體整合專案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved