春锅锅 發表於 2025-6-15 08:00:00

C#实现欧姆龙 HostLink 通讯协议库

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>C#实现欧姆龙 HostLink 通讯协议库<ul><li>背景介绍<ul><li>欧姆龙 PLC 内存区域介绍</li><li>欧姆龙 PLC 数据类型对应</li><li>欧姆龙 PLC 与 PC 的 RS232 接线线序</li></ul></li><li>HostLink通讯报文分析<ul><li>C-Mode通讯报文分析</li><li>FINS-Mode通讯报文分析</li></ul></li><li>HostLink通讯协议库的C#实现<ul><li>核心实现(FCS校验码生成、串口收发)</li><li>C-Mode实现</li><li>FINS-Mode实现</li></ul></li><li>C#控制台测试功能<ul><li>测试结果</li></ul></li></ul></li></ul></div><p></p>
<h1 id="c实现欧姆龙-hostlink-通讯协议库">C#实现欧姆龙 HostLink 通讯协议库</h1>
<p><strong>运行环境:VS2022 .net framework4.8</strong><br>
<strong>通讯库项目地址(Gitee):通讯库项目Gitee 仓库</strong><br>
<strong>控制台测试项目地址(Gitee):控制台测试项目Gitee 仓库</strong><br>
<strong>HostLink 通讯手册链接(蓝奏云):SYSMAC Series Communications Commands</strong><br>
<strong>官方的 HostLink 串口通讯示例(蓝奏云):C-Mode || FINS-Mode</strong><br>
<strong>通讯工具(蓝奏云):Commix 1.4</strong></p>
<blockquote>
<p><strong>概要</strong>:根据欧姆龙的 HostLink 通讯协议手册内容,使用<strong>串口</strong>实现了 PC 与 PLC 的通讯,能够通过<strong>C-Mode</strong>与<strong>FINS-Mode</strong>两种模式实现 PC 读写 PLC 的<strong>CIO、WR、HR、DM 四个内存区</strong>的内容(<strong>同步/异步方法</strong>),而且可以以<strong>较高的通讯效率</strong>获取所需数据、错误代码等信息,最后用一个 C#控制台项目测试了通讯库功能</p>
</blockquote>
<hr>
<h2 id="背景介绍">背景介绍</h2>
<p>HostLink 协议是欧姆龙 PLC 与主机通讯的一种公开协议,PC 可通过 HostLink 命令对 PLC 的运行状态、I/O 点位的读写</p>
<p>HostLink 分为 C-Mode 和 FINS-Mode 两种模式</p>
<p>C-Mode 命令是专门的主机链路通信命令,它们由主机发出并发送至 CPU 单元。可连接用于串行通信的设备有 CPU 单元、串行通信单元和串行通信板。</p>
<p>FINS-Mode 命令是报文服务通信命令,它们不依赖于特定的传输路径。它们可用于各种网络(控制器链路、以太网等)和串行通信(主机链路)。它们可以从 CPU 单元、特殊 I/O 单元或主机发出,也可以发送到其中任何一个单元。可发送的具体命令取决于目的地。</p>
<p><img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224239989-1530446529.png" alt="HostLink概述图" loading="lazy"></p>
<h3 id="欧姆龙-plc-内存区域介绍">欧姆龙 PLC 内存区域介绍</h3>
<table>
<thead>
<tr>
<th style="text-align: center">内存区域名</th>
<th style="text-align: left">区域说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">CIO</td>
<td style="text-align: left">I/O 继电器区</td>
</tr>
<tr>
<td style="text-align: center">WR</td>
<td style="text-align: left">内部辅助继电器区</td>
</tr>
<tr>
<td style="text-align: center">HR</td>
<td style="text-align: left">保持继电器区</td>
</tr>
<tr>
<td style="text-align: center">DM</td>
<td style="text-align: left">数据存储区</td>
</tr>
<tr>
<td style="text-align: center">TIM</td>
<td style="text-align: left">定时器区</td>
</tr>
<tr>
<td style="text-align: center">CNT</td>
<td style="text-align: left">计数器区</td>
</tr>
<tr>
<td style="text-align: center">IR</td>
<td style="text-align: left">变址寄存器区</td>
</tr>
<tr>
<td style="text-align: center">DR</td>
<td style="text-align: left">数据寄存器</td>
</tr>
<tr>
<td style="text-align: center">AR</td>
<td style="text-align: left">特殊辅助继电器区</td>
</tr>
<tr>
<td style="text-align: center">TR</td>
<td style="text-align: left">暂存区</td>
</tr>
<tr>
<td style="text-align: center">TK</td>
<td style="text-align: left">状态标志、时钟脉冲、任务标志</td>
</tr>
</tbody>
</table>
<h3 id="欧姆龙-plc-数据类型对应">欧姆龙 PLC 数据类型对应</h3>
<table>
<thead>
<tr>
<th style="text-align: center">PLC 数据类型</th>
<th style="text-align: center">PC 数据类型</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">Bit</td>
<td style="text-align: center">bool</td>
</tr>
<tr>
<td style="text-align: center">Byte</td>
<td style="text-align: center">ushort</td>
</tr>
<tr>
<td style="text-align: center">DWord</td>
<td style="text-align: center">uint</td>
</tr>
<tr>
<td style="text-align: center">Int</td>
<td style="text-align: center">short</td>
</tr>
<tr>
<td style="text-align: center">Dint</td>
<td style="text-align: center">int</td>
</tr>
<tr>
<td style="text-align: center">float</td>
<td style="text-align: center">float</td>
</tr>
<tr>
<td style="text-align: center">String</td>
<td style="text-align: center">string</td>
</tr>
</tbody>
</table>
<h3 id="欧姆龙-plc-与-pc-的-rs232-接线线序">欧姆龙 PLC 与 PC 的 RS232 接线线序</h3>
<p><img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224257312-632212607.png" alt="RS232接线线序" loading="lazy"></p>
<hr>
<h2 id="hostlink通讯报文分析">HostLink通讯报文分析</h2>
<p>根据C-Mode和FINS的报文进行分析</p>
<h3 id="c-mode通讯报文分析">C-Mode通讯报文分析</h3>
<p>发送报文如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224310195-1110893154.png" alt="C-Mode发送报文图" loading="lazy"></p>
<p>正常接收报文如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224326501-294916568.png" alt="C-Mode正常接收报文图" loading="lazy"></p>
<p>通讯出错接收报文如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224337789-283800414.png" alt="C-Mode错误接收报文图" loading="lazy"></p>
<p>使用串口工具Commix进行测试如下图所示<br>
TS命令:<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224352098-1254219886.png" alt="C-Mode串口测试图1" loading="lazy"><br>
RD、WD命令:<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224403826-519962961.png" alt="C-Mode串口测试图2" loading="lazy"></p>
<h3 id="fins-mode通讯报文分析">FINS-Mode通讯报文分析</h3>
<p>发送报文如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224416540-602971427.png" alt="FINS-Mode发送报文图" loading="lazy"></p>
<p>接收报文如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224427964-1200198830.png" alt="FINS-Mode接收报文图" loading="lazy"></p>
<p>FINS指令配置如下图所示<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224458271-229735790.png" alt="FINS-Mode指令配置图" loading="lazy"></p>
<p>使用串口工具Commix进行测试如下图所示<br>
0101、0102命令:<br>
<img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224509292-673836032.png" alt="FINS-Mode串口测试图" loading="lazy"></p>
<hr>
<h2 id="hostlink通讯协议库的c实现">HostLink通讯协议库的C#实现</h2>
<h3 id="核心实现fcs校验码生成串口收发">核心实现(FCS校验码生成、串口收发)</h3>
<p>HostLinkCore.cs</p>
<blockquote>
<p>在这里我本来想给异步的方法加上一个<strong>SemaphoreSlim</strong>来限制通讯的信号量的,但是后来想了想还是让使用者在外面自己加好了:)<br>
<strong>SemaphoreSlim</strong>具体的使用方法可以参考文章的C#控制台项目</p>
</blockquote>
<pre><code class="language-CSharp">/// &lt;summary&gt;
/// 通过命令帧计算FCS异或校验码并加上结束符
/// &lt;/summary&gt;
/// &lt;param name="CommandFrame"&gt;命令帧&lt;/param&gt;
/// &lt;returns&gt;4位字节数组,包含FCS校验码与结束符&lt;/returns&gt;
public static List&lt;byte&gt; HostLinkEndCode(List&lt;byte&gt; CommandFrame)
{
    try
    {
      List&lt;byte&gt; EndCode = new List&lt;byte&gt;();
      short FCSNum = HostLinkFCS(CommandFrame);

      string HexString = FCSNum.ToString("X2");

      EndCode.AddRange(Encoding.ASCII.GetBytes(HexString));
      EndCode.AddRange(new List&lt;byte&gt; { 0x2A, 0x0D });
      return EndCode;
    }
    catch (Exception)
    {
      throw;
    }
}


/// &lt;summary&gt;
/// 对字节数组进行异或运算得到数值
/// &lt;/summary&gt;
/// &lt;param name="CommandFrame"&gt;命令帧&lt;/param&gt;
/// &lt;returns&gt;异或运算结果(short类型)&lt;/returns&gt;
public static short HostLinkFCS(List&lt;byte&gt; CommandFrame)
{
    try
    {
      short CheckNum = 0;
      foreach (byte FrameNum in CommandFrame)
      {
            CheckNum ^= FrameNum;
      }
      return CheckNum;
    }
    catch (Exception)
    {
      throw;
    }
}


/// &lt;summary&gt;
/// 检查回复帧是否完整
/// &lt;/summary&gt;
/// &lt;param name="frame"&gt;回复帧&lt;/param&gt;
/// &lt;returns&gt;完整结尾:返回True;不完整:返回False&lt;/returns&gt;
private static bool CheckResponseFrame(List&lt;byte&gt; frame)
{
    if (frame.Count &gt; 0 &amp;&amp; frame == (byte)0x0D)
    {
      return true;
    }
    else
    {
      return false;
    }
}




/// &lt;summary&gt;
/// HostLink报文发送与接收代码
/// &lt;/summary&gt;
/// &lt;param name="serialPort"&gt;串口实例&lt;/param&gt;
/// &lt;param name="CommandFrame"&gt;命令帧&lt;/param&gt;
/// &lt;returns&gt;回复帧&lt;/returns&gt;
public static List&lt;byte&gt; HostLinkCommCore(SerialPort serialPort, List&lt;byte&gt; CommandFrame)
{
    try
    {
      List&lt;byte&gt; ResponseFrame = new List&lt;byte&gt;();

      serialPort.ReadTimeout = 2000;
      serialPort.WriteTimeout = 2000;

      //发送报文
      serialPort.Write(CommandFrame.ToArray(), 0, CommandFrame.Count);

      //循环读取数据
      while (serialPort.IsOpen)
      {
            if (serialPort.BytesToRead &gt; 0)
            {
                byte[] buffer = new byte;
                serialPort.Read(buffer, 0, buffer.Length);

                ResponseFrame.AddRange(buffer);

                if (CheckResponseFrame(ResponseFrame))
                {
                  return ResponseFrame;
                }
            }
      }

      return null;
    }
    catch (Exception)
    {
      throw;
    }
}




/// &lt;summary&gt;
/// HostLink报文发送与接收代码(异步方法)
/// &lt;/summary&gt;
/// &lt;param name="serialPort"&gt;串口实例&lt;/param&gt;
/// &lt;param name="CommandFrame"&gt;命令帧&lt;/param&gt;
/// &lt;returns&gt;回复帧&lt;/returns&gt;
public static async Task&lt;List&lt;byte&gt;&gt; HostLinkCommCoreAsync(SerialPort serialPort, List&lt;byte&gt; CommandFrame)
{
    try
    {
      List&lt;byte&gt; ResponseFrame = new List&lt;byte&gt;();

      serialPort.ReadTimeout = 2000;
      serialPort.WriteTimeout = 2000;

      //发送报文
      await serialPort.BaseStream.WriteAsync(CommandFrame.ToArray(), 0, CommandFrame.Count);

      //循环读取数据
      while (serialPort.IsOpen)
      {
            if (serialPort.BytesToRead &gt; 0)
            {
                byte[] buffer = new byte;
                await serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);

                ResponseFrame.AddRange(buffer);

                if (CheckResponseFrame(ResponseFrame))
                {
                  return ResponseFrame;
                }
            }
      }

      return null;
    }
    catch (Exception)
    {
      throw;
    }
}
</code></pre>
<h3 id="c-mode实现">C-Mode实现</h3>
<p>CmodeEndCode.cs</p>
<pre><code class="language-CSharp">/// &lt;summary&gt;
/// 对比回复帧中的EndCode,看内容是否相符
/// &lt;/summary&gt;
/// &lt;param name="ResponseFrame"&gt;回复帧&lt;/param&gt;
/// &lt;returns&gt;返回结束代码&lt;/returns&gt;
public static string CatchEndCode(List&lt;byte&gt; ResponseFrame, int EndCodeAdr)
{
    try
    {
      List&lt;byte&gt; ResponseEndCode = ResponseFrame.GetRange(EndCodeAdr, 2);
      string EndCodeContents = null;

      if (ResponseEndCode.SequenceEqual(new List&lt;byte&gt; { 0x30, 0x30 }))
      {
            EndCodeContents = "00";
      }
      else
      {
            foreach (var EndCodeMap in EndCodeMapSeq)
            {
                if (ResponseEndCode.SequenceEqual(EndCodeMap.Key))
                {
                  EndCodeContents = EndCodeMap.Value;
                  break;
                }
            }
      }
      return EndCodeContents;
    }
    catch (Exception ex)
    {
      throw ex;
    }
}


private static readonly Dictionary&lt;List&lt;byte&gt;, string&gt; EndCodeMapSeq = new Dictionary&lt;List&lt;byte&gt;, string&gt;
{
    {new List&lt;byte&gt; { 0x30, 0x31 },"EndCode: 01;Contents: Not executable in RUN mode" },
    {new List&lt;byte&gt; { 0x30, 0x32 },"EndCode: 02;Contents: Not executable in MONITOR mode" },
    {new List&lt;byte&gt; { 0x30, 0x33 },"EndCode: 03;Contents: UM write-protected" },
    {new List&lt;byte&gt; { 0x30, 0x34 },"EndCode: 04;Contents: Address over" },
    {new List&lt;byte&gt; { 0x30, 0x42 },"EndCode: 0B;Contents: Not executable in PROGRAM mode" },
    {new List&lt;byte&gt; { 0x31, 0x33 },"EndCode: 13;Contents: FCS error" },
    {new List&lt;byte&gt; { 0x31, 0x34 },"EndCode: 14;Contents: Format error" },
    {new List&lt;byte&gt; { 0x31, 0x35 },"EndCode: 15;Contents: Entry number data error" },
    {new List&lt;byte&gt; { 0x31, 0x36 },"EndCode: 16;Contents: Command not supported" },
    {new List&lt;byte&gt; { 0x31, 0x38 },"EndCode: 18;Contents: Frame length error" },
    {new List&lt;byte&gt; { 0x31, 0x39 },"EndCode: 19;Contents: Not executable" },
    {new List&lt;byte&gt; { 0x32, 0x30 },"EndCode: 20;Contents: Could not create I/O table" },
    {new List&lt;byte&gt; { 0x32, 0x31 },"EndCode: 21;Contents: Not executable due to CPU Unit CPU error" },
    {new List&lt;byte&gt; { 0x32, 0x33 },"EndCode: 23;Contents: User memory protected" },
    {new List&lt;byte&gt; { 0x41, 0x33 },"EndCode: A3;Contents: Aborted due to FCS error in trans-mission data" },
    {new List&lt;byte&gt; { 0x41, 0x34 },"EndCode: A4;Contents: Aborted due to format error in transmission data" },
    {new List&lt;byte&gt; { 0x41, 0x35 },"EndCode: A5;Contents: Aborted due to entry number data error in transmission data" },
    {new List&lt;byte&gt; { 0x41, 0x38 },"EndCode: A8;Contents: Aborted due to frame length error in transmission data" }
};
</code></pre>
<p>CmodeHeaderCode.cs</p>
<pre><code class="language-CSharp">internal class CmodeHeaderCode
{
    public static readonly byte[] HeaderCode_RR = { 0x52, 0x52 };
    public static readonly byte[] HeaderCode_RD = { 0x52, 0x44 };
    public static readonly byte[] HeaderCode_WR = { 0x57, 0x52 };
    public static readonly byte[] HeaderCode_WD = { 0x57, 0x44 };
    public static readonly byte[] HeaderCode_TS = { 0x54, 0x53 };
    public static readonly byte[] HeaderCode_MS = { 0x4D, 0x53 };
    public static readonly byte[] HeaderCode_SC = { 0x53, 0x43 };
}
</code></pre>
<blockquote>
<p>功能实现的代码过长,请自行到通讯库项目Gitee 仓库查看吧</p>
</blockquote>
<h3 id="fins-mode实现">FINS-Mode实现</h3>
<p>定义一个FinsResult的类用来接收所需要的信息</p>
<blockquote>
<p>(目前通信库反馈的大部分数据都是Bool与String列表,所以里面分开成两个Datas)</p>
</blockquote>
<p>FinsResult.cs</p>
<pre><code class="language-CSharp">/// &lt;summary&gt;
/// Fins通信结果类
/// &lt;/summary&gt;
public class FinsResult
{
    /// &lt;summary&gt;
    /// FINS通信状态
    /// &lt;/summary&gt;
    public bool IsSuccessed { get; set; }

    /// &lt;summary&gt;
    /// FINS通信结果信息
    /// &lt;/summary&gt;
    public string ResultMessage { get; set; }

    /// &lt;summary&gt;
    /// 通信命令帧
    /// &lt;/summary&gt;
    public string CommandFrame { get; set; }

    /// &lt;summary&gt;
    /// 通信回复帧
    /// &lt;/summary&gt;
    public string ResponseFrame { get; set; }

    /// &lt;summary&gt;
    /// 数据列表(bool类型)
    /// &lt;/summary&gt;
    public List&lt;bool&gt; Datas_BoolList { get; set; }

    /// &lt;summary&gt;
    /// 数据列表(string类型)
    /// &lt;/summary&gt;
    public string Datas_String { get; set; }


    /// &lt;summary&gt;
    /// 完整构造函数
    /// &lt;/summary&gt;
    /// &lt;param name="isSuccessed"&gt;FINS通信状态&lt;/param&gt;
    /// &lt;param name="resultMessage"&gt;FINS通信结果信息&lt;/param&gt;
    /// &lt;param name="commandFrame"&gt;通信命令帧&lt;/param&gt;
    /// &lt;param name="responseFrame"&gt;通信回复帧&lt;/param&gt;
    /// &lt;param name="datas"&gt;数据列表&lt;/param&gt;
    public FinsResult(bool isSuccessed, string resultMessage, string commandFrame, string responseFrame, List&lt;bool&gt; datas1, string datas2)
    {
      IsSuccessed = isSuccessed;
      ResultMessage = resultMessage;
      CommandFrame = commandFrame;
      ResponseFrame = responseFrame;
      Datas_BoolList = datas1;
      Datas_String = datas2;
    }

    //五参数构造函数(带bool数据列表)
    public FinsResult(bool isSuccessed, string resultMessage, string commandFrame, string responseFrame, List&lt;bool&gt; datas) :
      this(isSuccessed, resultMessage, commandFrame, responseFrame, datas, null)
    { }

    //五参数构造函数(带string数据列表)
    public FinsResult(bool isSuccessed, string resultMessage, string commandFrame, string responseFrame, string datas) :
      this(isSuccessed, resultMessage, commandFrame, responseFrame, null, datas)
    { }

    //四参数构造函数(无数据反馈)
    public FinsResult(bool isSuccessed, string resultMessage, string commandFrame, string responseFrame) :
      this(isSuccessed, resultMessage, commandFrame, responseFrame, null, null)
    { }

    //两参数构造函数(出错时返回)
    public FinsResult(bool isSuccessed, string resultMessage) : this(isSuccessed, resultMessage, null, null, null, null) { }

}
</code></pre>
<p>通讯库定义了一些常用的内存区域枚举类,使用时可以提前设定好</p>
<p>FinsIOMemoryAreaAddress.cs</p>
<pre><code class="language-CSharp">/// &lt;summary&gt;
/// FinsIO内存区域地址类
/// &lt;/summary&gt;
public class FinsIOMemoryAreaAddress
{
    /// &lt;summary&gt;
    /// 内存区域代码
    /// &lt;/summary&gt;
    public FinsMemoryAreaTypeEnum AreaType { get; set; }

    /// &lt;summary&gt;
    /// Word起始地址
    /// &lt;/summary&gt;
    public ushort WordAddress { get; set; }

    /// &lt;summary&gt;
    /// Bit起始地址
    /// &lt;/summary&gt;
    public ushort BitAddress { get; set; }
}
</code></pre>
<p>FinsMemoryAreaTypeEnum.cs</p>
<pre><code class="language-CSharp">/// &lt;summary&gt;
/// Fins内存区域类型
/// &lt;/summary&gt;
public enum FinsMemoryAreaTypeEnum
{
    CIOBit,
    CIOWord,
    WRBit,
    WRWord,
    HRBit,
    HRWord,
    DMBit,
    DMWord
}
</code></pre>
<p>FINS指令配置的代码如下</p>
<p>FinsFrameConfig.cs</p>
<pre><code class="language-CSharp">public class FinsFrameConfig
{
    public string ICF { get; set; }
    public string RSV { get; set; }
    public string GCT { get; set; }
    public string DNA { get; set; }
    public string DA1 { get; set; }
    public string DA2 { get; set; }
    public string SNA { get; set; }
    public string SA1 { get; set; }
    public string SA2 { get; set; }
    public string SID { get; set; }
}
</code></pre>
<blockquote>
<p>功能实现的代码过长,请自行到通讯库项目Gitee 仓库查看吧</p>
</blockquote>
<hr>
<h2 id="c控制台测试功能">C#控制台测试功能</h2>
<p>HostLinkDevice.cs</p>
<pre><code class="language-CSharp">public class HostLinkDevice
{
    public HostLinkDevice(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
    {
      PLC = new HostLinkFinsDevice(portName, baudRate, parity, dataBits, stopBits);
    }

    //HostLinkFins设备
    private HostLinkFinsDevice PLC;

    //Fins帧参数设置
    private static FinsFrameConfig finsFrameConfig = new FinsFrameConfig()
    {
      ICF = "00",
      DA2 = "00",
      SA2 = "00",
      SID = "00"
    };

    /// &lt;summary&gt;
    /// 串口打开
    /// &lt;/summary&gt;
    public void Connect()
    {
      try
      {
            PLC.Connect();
      }
      catch (Exception)
      {
            throw;
      }
    }

    /// &lt;summary&gt;
    /// 串口关闭
    /// &lt;/summary&gt;
    public void Disconnect()
    {
      try
      {
            PLC.DisConnect();
      }
      catch (Exception)
      {
            throw;
      }
    }

    /// &lt;summary&gt;
    /// 读取CIO区的连续Bit位方法(异步方法)
    /// &lt;/summary&gt;
    /// &lt;param name="WordAdr"&gt;读取起始Word地址&lt;/param&gt;
    /// &lt;param name="BitAdr"&gt;读取起始Bit地址&lt;/param&gt;
    /// &lt;param name="ReadCount"&gt;读取Bit位数量&lt;/param&gt;
    /// &lt;returns&gt;返回通信结果的信息&lt;/returns&gt;
    public async Task&lt;List&lt;bool&gt;&gt; ReadCIOBitAsync(ushort WordAdr, ushort BitAdr, ushort ReadCount)
    {
      try
      {
            FinsResult finsResult;
            //IO内存区域设置
            FinsIOMemoryAreaAddress IOMemoryAreaAdr = new FinsIOMemoryAreaAddress()
            {
                AreaType = FinsMemoryAreaTypeEnum.CIOBit,
                WordAddress = WordAdr,
                BitAddress = BitAdr
            };

            //获取FINS通信结果
            finsResult = await PLC.Read_MemoryAreaAsync(IOMemoryAreaAdr, finsFrameConfig, ReadCount);

            if (finsResult.IsSuccessed)
            {
                if (finsResult.ResultMessage.Equals("OK"))
                {
                  return finsResult.Datas_BoolList;
                }
                else
                {
                  throw new Exception($"{finsResult.ResultMessage}{Environment.NewLine}{Environment.NewLine}发送命令帧:{finsResult.CommandFrame}{Environment.NewLine}接收回复帧:{finsResult.ResponseFrame}");
                }
            }
            else
            {
                throw new Exception($"{finsResult.ResultMessage}{Environment.NewLine}{Environment.NewLine}发送命令帧:{finsResult.CommandFrame}{Environment.NewLine}接收回复帧:{finsResult.ResponseFrame}");
            }
      }
      catch (Exception)
      {
            throw;
      }
    }

    /// &lt;summary&gt;
    /// 读取W区的连续Bit位方法(异步方法)
    /// &lt;/summary&gt;
    /// &lt;param name="WordAdr"&gt;读取起始Word地址&lt;/param&gt;
    /// &lt;param name="BitAdr"&gt;读取起始Bit地址&lt;/param&gt;
    /// &lt;param name="ReadCount"&gt;读取Bit位数量&lt;/param&gt;
    /// &lt;returns&gt;返回通信结果的信息&lt;/returns&gt;
    public async Task&lt;List&lt;bool&gt;&gt; ReadWRBitAsync(ushort WordAdr, ushort BitAdr, ushort ReadCount)
    {
      try
      {
            FinsResult finsResult;
            //IO内存区域设置
            FinsIOMemoryAreaAddress IOMemoryAreaAdr = new FinsIOMemoryAreaAddress()
            {
                AreaType = FinsMemoryAreaTypeEnum.WRBit,
                WordAddress = WordAdr,
                BitAddress = BitAdr
            };

            //获取FINS通信结果
            finsResult = await PLC.Read_MemoryAreaAsync(IOMemoryAreaAdr, finsFrameConfig, ReadCount);

            if (finsResult.IsSuccessed)
            {
                if (finsResult.ResultMessage.Equals("OK"))
                {
                  return finsResult.Datas_BoolList;
                }
                else
                {
                  throw new Exception($"{finsResult.ResultMessage}{Environment.NewLine}{Environment.NewLine}发送命令帧:{finsResult.CommandFrame}{Environment.NewLine}接收回复帧:{finsResult.ResponseFrame}");
                }
            }
            else
            {
                throw new Exception($"{finsResult.ResultMessage}{Environment.NewLine}{Environment.NewLine}发送命令帧:{finsResult.CommandFrame}{Environment.NewLine}接收回复帧:{finsResult.ResponseFrame}");
            }
      }
      catch (Exception)
      {
            throw;
      }
    }

    /// &lt;summary&gt;
    /// 写入W区的连续Bit位方法(异步方法)
    /// &lt;/summary&gt;
    /// &lt;param name="WordAdr"&gt;写入起始Word地址&lt;/param&gt;
    /// &lt;param name="BitAdr"&gt;写入起始Bit地址&lt;/param&gt;
    /// &lt;param name="WriteCount"&gt;写入Bit位数量&lt;/param&gt;
    /// &lt;param name="WriteData"&gt;写入数据&lt;/param&gt;
    /// &lt;returns&gt;返回通信结果的信息&lt;/returns&gt;
    public async Task&lt;string&gt; WriteWRBitAsync(ushort WordAdr, ushort BitAdr, ushort WriteCount, string WriteData)
    {
      try
      {
            FinsResult finsResult;
            //IO内存区域设置
            FinsIOMemoryAreaAddress IOMemoryAreaAdr = new FinsIOMemoryAreaAddress()
            {
                AreaType = FinsMemoryAreaTypeEnum.WRBit,
                WordAddress = WordAdr,
                BitAddress = BitAdr
            };

            //获取FINS通信结果
            finsResult = await PLC.Write_MemoryAreaAsync(IOMemoryAreaAdr, finsFrameConfig, WriteCount, WriteData);

            if (finsResult.IsSuccessed)
            {
                return finsResult.ResultMessage;
            }
            else
            {
                throw new Exception($"{finsResult.ResultMessage}{Environment.NewLine}{Environment.NewLine}发送命令帧:{finsResult.CommandFrame}{Environment.NewLine}接收回复帧:{finsResult.ResponseFrame}");
            }
      }
      catch (Exception)
      {
            throw;
      }
    }
}
</code></pre>
<p>Program.cs</p>
<pre><code class="language-CSharp">internal class Program
{
    static async Task Main(string[] args)
    {
      #region C-Mode方式

      HostLinkCmodeDevice plc1 = new HostLinkCmodeDevice("COM5", 115200, Parity.None, 8, StopBits.One);

      SemaphoreSlim semaphoreSlim_Comm1 = new SemaphoreSlim(1, 1);

      plc1.Connect();


      var Task1 = Task.Run(async () =&gt;
      {
            await semaphoreSlim_Comm1.WaitAsync();

            Stopwatch stopwatch1 = new Stopwatch();

            stopwatch1.Start();

            string str1 = await plc1.TestCommandAsync("123123");

            stopwatch1.Stop();

            Console.WriteLine("TestCommand:");

            Console.WriteLine(str1);

            Console.WriteLine($"花费时间:{stopwatch1.ElapsedMilliseconds}ms");

            semaphoreSlim_Comm1.Release();

      });

      var Task2 = Task.Run(async () =&gt;
      {
            await semaphoreSlim_Comm1.WaitAsync();

            Stopwatch stopwatch1 = new Stopwatch();

            stopwatch1.Start();

            string str1 = await plc1.Read_DMAreaAsync(0, 3);

            stopwatch1.Stop();

            Console.WriteLine("ReadDM:");

            Console.WriteLine(str1);

            Console.WriteLine($"花费时间:{stopwatch1.ElapsedMilliseconds}ms");

            semaphoreSlim_Comm1.Release();

      });

      await Task.WhenAll(Task1, Task2);

      plc1.DisConnect();

      semaphoreSlim_Comm1.Dispose();

      #endregion



      #region FINS-Mode方式

      //HostLinkDevice plc2 = new HostLinkDevice("COM5", 115200, Parity.None, 8, StopBits.One);

      //SemaphoreSlim semaphoreSlim_Comm2 = new SemaphoreSlim(1, 1);

      //plc2.Connect();

      //var Task3 = Task.Run(async () =&gt;
      //{
      //    await semaphoreSlim_Comm2.WaitAsync();

      //    Stopwatch stopwatch2 = new Stopwatch();

      //    stopwatch2.Start();

      //    var list1 = await plc2.ReadCIOBitAsync(100, 0, 3);

      //    stopwatch2.Stop();

      //    Console.WriteLine("Read CIOBit:");
      //    foreach (var item in list1)
      //    {
      //      Console.WriteLine(item);
      //    }

      //    Console.WriteLine($"花费时间:{stopwatch2.ElapsedMilliseconds}ms");

      //    semaphoreSlim_Comm2.Release();
      //});

      //var Task4 = Task.Run(async () =&gt;
      //{
      //    await semaphoreSlim_Comm2.WaitAsync();

      //    Stopwatch stopwatch2 = new Stopwatch();

      //    stopwatch2.Start();

      //    var list1 = await plc2.ReadWRBitAsync(0, 0, 3);

      //    stopwatch2.Stop();

      //    Console.WriteLine("Read WRBit:");
      //    foreach (var item in list1)
      //    {
      //      Console.WriteLine(item);
      //    }

      //    Console.WriteLine($"花费时间:{stopwatch2.ElapsedMilliseconds}ms");

      //    semaphoreSlim_Comm2.Release();
      //});

      //var Task5 = Task.Run(async () =&gt;
      //{
      //    await semaphoreSlim_Comm2.WaitAsync();

      //    Stopwatch stopwatch2 = new Stopwatch();

      //    stopwatch2.Start();

      //    var list1 = await plc2.WriteWRBitAsync(0, 0, 3, "000100");

      //    stopwatch2.Stop();

      //    Console.WriteLine("Write WRBit:");
      //    Console.WriteLine(list1);

      //    Console.WriteLine($"花费时间:{stopwatch2.ElapsedMilliseconds}ms");

      //    semaphoreSlim_Comm2.Release();
      //});

      //await Task.WhenAll(Task3, Task4, Task5);

      //plc2.Disconnect();
      //semaphoreSlim_Comm2.Dispose();

      #endregion


      Console.ReadKey();
    }
}
</code></pre>
<blockquote>
<p>具体项目请自行到控制台测试项目Gitee 仓库查看吧</p>
</blockquote>
<h3 id="测试结果">测试结果</h3>
<p>控制台输出图如下:</p>
<p>C-Mode方式</p>
<p><img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224543554-609992350.png" alt="C-Mode测试图" loading="lazy"></p>
<p>FINS-Mode方式</p>
<p><img src="https://img2024.cnblogs.com/blog/2368008/202506/2368008-20250614224553470-1384268178.png" alt="FINS-Mode测试图" loading="lazy"></p>


</div>
<div id="MySignature" role="contentinfo">
    <p><b>作者</b>:Dragonet-Z</p>
<p><b>出处</b>:https://www.cnblogs.com/dragonet-Z/p/18928900</p>
<p><b>版权声明</b>:本博客所有文章除特殊声明外,均遵循BY-NC-SA许可协议,转载请注明出处!</p><br><br>
来源:https://www.cnblogs.com/dragonet-Z/p/18928900
頁: [1]
查看完整版本: C#实现欧姆龙 HostLink 通讯协议库