鈺坽 发表于 2024-6-2 18:38:19

NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下

先上两个通用Modbus帮助类,下面这个是多线程不安全版,在多线程多电机同一端口通信下,可能造成步进电机丢步或者输出口无响应等,还有个多线程安全版,只是基于这个不安全版加上了LOCK,THIS
using Modbus.Device;
using Sunny.UI;
using System;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;<br><br>namespace SunnyUI_NuclearForm
{
    /// <summary>
    /// Modbus通用帮助类
    /// </summary>
    public class ModbusHelper : IDisposable
    {<br><br>      IModbusMaster master;<br><br>      //串口通信
      SerialPort serialPort;<br><br>      //网口-TCP
      TcpClient tcpClient;<br><br>      //网口-套接字(备用)
      Socket socket;<br><br>
      //private static readonly object threadObj = new object();//线程安全锁<br><br>      //ManualResetEvent manualEvent = new ManualResetEvent(true);//true运行,false不运行<br><br>
      #regionconnection<br><br>      /// <summary>
      /// 串口连接
      /// </summary>
      /// <returns></returns>
      public bool ConnectForSerialPort(string com, int baudRate)
      {
            bool result = false;<br><br>            if (!string.IsNullOrWhiteSpace(com) && baudRate > 0)
            {
                serialPort = new SerialPort();<br><br>                serialPort.PortName = com;
                serialPort.BaudRate = baudRate;<br><br>                //下面三个参数看情况要不要开放
                serialPort.DataBits = 8;
                serialPort.StopBits = StopBits.One;
                serialPort.Parity = Parity.None;<br><br>                try
                {
                  if (!serialPort.IsOpen)
                  {
                        serialPort.Open();
                  }<br><br>
                  master = ModbusSerialMaster.CreateRtu(serialPort);
                  master.Transport.ReadTimeout = 3 * 1000;//读 超时时间
                  master.Transport.WriteTimeout = 3 * 1000;//写 超时时间
                  master.Transport.Retries = 20;//重试次数
                  master.Transport.WaitToRetryMilliseconds = 3 * 1000;//重试间隔<br><br>                  result = true;
                }
                catch (Exception e)
                {
                  //打开失败
                  LogHelper.Error($"串口无法连接!COM:{com},波特率:{baudRate}。以下为详细信息:\r\n{e.Message}");
                  result = false;
                }
            }
            return result;
      }<br><br>      /// <summary>
      /// 网口TCP连接
      /// </summary>
      /// <param name="ip"></param>
      /// <returns></returns>
      public bool ConnectForTCP(string ip, int port)
      {
            bool result = false;
            if (!string.IsNullOrWhiteSpace(ip) && port > 0)
            {
                try
                {
                  tcpClient = new TcpClient(ip, port);
                  if (tcpClient.Connected)
                  {
                        master = ModbusIpMaster.CreateIp(tcpClient);<br><br>                        master.Transport.ReadTimeout = 3 * 1000;//读 超时时间
                        master.Transport.WriteTimeout = 3 * 1000;//写 超时时间
                        master.Transport.Retries = 20;//重试次数
                        master.Transport.WaitToRetryMilliseconds = 3 * 1000;//重试间隔<br><br>                        result = true;
                  }
                  else
                  {
                        LogHelper.Error($"网口无法连接!IP:{ip},端口:{port}。\r\n");
                        result = false;
                  }
                }
                catch (Exception e)
                {
                  LogHelper.Error($"网口无法连接!IP:{ip},端口:{port}。以下为详细信息:\r\n{e.Message}");
                  return false;
                }<br><br>            }
            return result;<br><br>      }<br><br>      /// <summary>
      ///Socket连接(备用)
      /// </summary>
      /// <param name="ip"></param>
      /// <param name="port"></param>
      /// <returns></returns>
      public bool ConnectForSocket(string ip, int port)
      {
            bool result = false;
            if (!string.IsNullOrWhiteSpace(ip) && port > 0)
            {
                //部分硬件,用TCP发指令没响应,但是用Socket可以,不知道是没支持好TCP还是没支持好Modbus
                //所以在此留下备用通信方法 <br><br>                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);<br><br>                try
                {
                  socket.Connect(IPAddress.Parse(ip), port);
                  result = true;<br><br>                }
                catch (Exception e)
                {
                  //连接失败
                  LogHelper.Error($"Socket(备用)无法连接!IP:{ip},端口:{port}。以下为详细信息:\r\n{e.Message}");
                  result = false;
                }<br><br>            }
            return result;
      }<br><br>      #endregion<br><br>      #region send<br><br>      /// <summary>
      /// 向串口发送数据 Modbus报文(字节)
      /// </summary>
      public void SendSerialByte(byte[] arr)
      {
            //this.manualEvent.WaitOne();//等待
            //lock (threadObj)
            //{
            //this.manualEvent.Reset();//红灯<br><br>            if (serialPort != null && serialPort.IsOpen && arr != null && arr.Count() > 0)
            {
                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();<br><br>                Thread.Sleep(50);<br><br>                serialPort.Write(arr, 0, arr.Length);<br><br>                Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔<br><br>                try
                {
                  // 接收来自读取命令的数据 广播命令执行完毕不响应
                  //byte[] receiveData = new byte;
                  //int bytesRead = serialPort.Read(receiveData, 0, receiveData.Length);<br><br>                  if (serialPort.BytesToRead > 0)
                  {
                        var result = serialPort.ReadExisting();
                  }<br><br>                }
                catch (Exception e)
                {<br><br>                }<br><br>            }<br><br>            //this.manualEvent.Set();//绿灯
            //}
      }<br><br>      /// <summary>
      /// 向串口发送数据 Modbus报文(字符串)
      /// </summary>
      public string SendSerialString(string str)
      {
            string result = string.Empty;<br><br>            //this.manualEvent.WaitOne();//等待<br><br>            //lock (threadObj)
            //{
            //this.manualEvent.Reset();//红灯
            if (serialPort != null && serialPort.IsOpen && !string.IsNullOrWhiteSpace(str))
            {<br><br>                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();<br><br>                Thread.Sleep(50);<br><br>                serialPort.Write(str);<br><br>                Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔<br><br>                try
                {
                  // 接收来自读取命令的数据 广播命令执行完毕不响应
                  if (serialPort.BytesToRead > 0)
                  {
                        result = serialPort.ReadExisting();
                  }
                }
                catch (Exception e)
                {<br><br>                }<br><br>            }
            //this.manualEvent.Set();//绿灯
            //}
            return result;
      }<br><br>      /// <summary>
      /// 向网口发送数据 Modbus报文
      /// </summary>
      /// <param name="arr"></param>
      public void SendTCPByte(byte[] arr)
      {
            if (arr != null && arr.Count() > 0 && tcpClient.Connected)
            {
                // 获取网络流
                NetworkStream stream = tcpClient.GetStream();<br><br>                //tcpClient.Client.Send(arr);
                stream.Write(arr, 0, arr.Length);
                //Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔<br><br>                try
                {
                  // 接收来自读取命令的数据 广播命令执行完毕不响应
                  byte[] receiveData = new byte;
                  int bytesRead = stream.Read(receiveData, 0, receiveData.Length);
                }
                catch (Exception)
                {<br><br>                  throw;
                }<br><br>            }<br><br>
      }<br><br>      /// <summary>
      /// 向网口发送数据 Modbus报文(Socket备用)
      /// </summary>
      /// <param name="arr"></param>
      public void SendSocketByte(byte[] arr)
      {
            if (arr != null && arr.Count() > 0 && socket.Connected)
            {
                socket.Send(arr);
                //Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔<br><br>                try
                {
                  // 接收来自读取命令的数据 广播命令执行完毕不响应
                  byte[] buffer = new byte;
                  int byteRead = socket.Receive(buffer);
                }
                catch (Exception)
                {<br><br>                  throw;
                }<br><br>
            }
      }<br><br>      #endregion<br><br>      #region read<br><br>
      /// <summary>
      /// 读取单个线圈 Modbus 01功能码
      /// </summary>
      /// <param name="slaveAddress">从站号</param>
      /// <param name="StartAddress">开始读取的寄存器地址</param>
      /// <param name="numberOfPoints">读取数据个数</param>
      /// <returns></returns>
      public bool[] ReadCoils_01(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
      {
            return master.ReadCoils(slaveAddress, startAddress, numberOfPoints);
      }<br><br>      /// <summary>
      /// 读取离散/输入线圈Modbus 02功能码
      /// </summary>
      /// <param name="slaveAddress">从站号</param>
      /// <param name="StartAddress">开始读取的寄存器地址</param>
      /// <param name="numberOfPoints">读取数据个数</param>
      /// <returns></returns>
      public bool[] ReadInputs_02(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
      {
            return master.ReadInputs(slaveAddress, startAddress, numberOfPoints);
      }<br><br>      /// <summary>
      /// 读取保持型寄存器Modbus 03功能码
      /// </summary>
      /// <param name="slaveAddress">从站号</param>
      /// <param name="StartAddress">开始读取的寄存器地址</param>
      /// <param name="numberOfPoints">读取数据个数</param>
      /// <returns></returns>
      public ushort[] ReadHoldingRegisters_03(byte slaveAddress, ushort StartAddress, ushort numberOfPoints)
      {<br><br>            try
            {
                ushort[] Uvalue = master.ReadHoldingRegisters(slaveAddress, StartAddress, numberOfPoints);<br><br>                //ushort 转short
                //short[] value = new short;
                //for (int i = 0; i < Uvalue.Length; i++)
                //{
                //    if (Uvalue > short.MaxValue)
                //    {
                //      value = (short)(Uvalue - 65536);
                //    }
                //    else
                //    {
                //      value = (short)Uvalue;
                //    }
                //}<br><br>                return Uvalue;
            }
            catch (Exception e)
            {
                return null;
            }<br><br>      }<br><br>      /// <summary>
      /// 读取输入寄存器 Modbus 04功能码
      /// </summary>
      /// <param name="slaveAddress">从站号</param>
      /// <param name="StartAddress">开始读取的寄存器地址</param>
      /// <param name="numberOfPoints">读取数据个数</param>
      /// <returns></returns>
      public ushort[] ReadInputRegisters_04(byte slaveAddress, ushort startAddress, ushort numberOfPoints)
      {<br><br>            try
            {
                ushort[] Uvalue = master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints);<br><br>                //ushort 转short<br><br>                //short[] value = new short;
                //for (int i = 0; i < Uvalue.Length; i++)
                //{
                //    if (Uvalue > short.MaxValue)
                //    {
                //      value = (short)(Uvalue - 65536);
                //    }
                //    else
                //    {
                //      value = (short)Uvalue;
                //    }<br><br>
                //}<br><br>                return Uvalue;
            }
            catch (Exception e)
            {
                return null;
            }<br><br>      }<br><br>
      #endregion<br><br>      #region write<br><br>      /// <summary>
      /// 写单个线圈 Modbus 05功能码
      /// </summary>
      /// <param name="slaveAddress"></param>
      /// <param name="coilAddress"></param>
      /// <param name="value"></param>
      public void WriteSingleCoil_05(byte slaveAddress, ushort coilAddress, bool value)
      {<br><br>            master.WriteSingleCoil(slaveAddress, coilAddress, value);<br><br>      }<br><br>      /// <summary>
      /// 写单个保持寄存器 Modbus 06功能码
      /// </summary>
      /// <param name="slaveAddress"></param>
      /// <param name="registerAddress"></param>
      /// <param name="value"></param>
      public void WriteSingleRegister_06(byte slaveAddress, ushort registerAddress, ushort value)
      {<br><br>            try
            {
                //short 转 ushort<br><br>                //ushort Uvalue;
                //if (value < 0)
                //{
                //    Uvalue = (ushort)(ushort.MaxValue + value + 1);
                //}
                //else
                //{
                //    Uvalue = (ushort)value;
                //}
                master.WriteSingleRegister(slaveAddress, registerAddress, value);
            }
            catch (Exception e)
            {
                return;
            }<br><br>
      }<br><br><br><br>      /// <summary>
      /// 写多个寄存器Modbus 10功能码
      /// </summary>
      /// <param name="slaveAddress"></param>
      /// <param name="startAddress"></param>
      /// <param name="data"></param>
      public void WriteMultipleRegisters_10(byte slaveAddress, ushort startAddress, ushort[] data)
      {
            try
            {
                master.WriteMultipleRegisters(slaveAddress, startAddress, data);
                //master.WriteMultipleRegistersAsync(slaveAddress, startAddress, data);
            }
            catch (Exception e)
            {<br><br>            }<br><br><br><br>
      }<br><br>      #endregion<br><br>      #region鸣志伺服-多线程不安全模式<br><br>      // 设置加减速,减速度,速度 数值1-10k/分钟
      public void SetAccelerationSpeedDeceleration(byte slaveAddr, ushort acceleration, ushort deceleration, ushort speed)
      {
            ushort parametersAddress = 344;<br><br>            ushort[] parameters = new ushort;
            parameters = 0;
            parameters = acceleration;
            parameters = 0;
            parameters = deceleration;
            parameters = 0;
            parameters = speed;<br><br>            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
      }<br><br>      /// <summary>
      /// 读取伺服电机加速度减速度速度
      /// </summary>
      /// <param name="slaveAddr"></param>
      /// <returns></returns>
      public ushort[] ReadAccelerationSpeedDeceleration(byte slaveAddr)
      {
            ushort[] result = new ushort;
            ushort[] speedArr = ReadHoldingRegisters_03(slaveAddr, 344, 6);
            if (speedArr != null && speedArr.Length >= 6)
            {
                //AC加速度344+1
                //DC减速度346+1
                //VE速度348+1
                result = speedArr;
                result = speedArr;
                result = speedArr;
            }
            return result;
      }<br><br>      // 设置距离 数值1-100/圈
      public void Distance(byte slaveAddr, long distance)
      {<br><br>            ushort parametersAddress = 350;
            ushort[] parameters = new ushort;
            parameters = (ushort)distance;<br><br>            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);<br><br>      }<br><br>      // 设置距离 脉冲数
      public void DistanceForPulse(byte slaveAddr, int pulse)
      {
            string convert = pulse.ToString("X").PadLeft(8, '0'); //转十六进制然后八位补零<br><br>            var sendByte = new byte[] {
                  slaveAddr,
                  0X10, 0x01, 0x5E, 0x00, 0x02 ,0x04 ,
                  Convert.ToByte(convert.Substring(0, 2), 16),
                  Convert.ToByte(convert.Substring(2, 2), 16),
            Convert.ToByte(convert.Substring(4, 2), 16),
            Convert.ToByte(convert.Substring(6, 2), 16)};<br><br>            var crc = CRC16.CRC16Calc(sendByte);
            var newSend = sendByte.Concat(crc).ToArray();<br><br>            SendSerialByte(newSend);<br><br>
      }<br><br>
      //Modbus 寄存器表中寄存器40125被定义为操作码寄存器,向40125寄存器写入相应的操作码,即执行相应操作码的动作
      //FL 0x66
      //SK 0xE1
      //SO 0x8B<br><br>      /// <summary>
      /// 电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离)
      /// </summary>
      /// <param name="slaveAddr"></param>
      public void Opcode_FL(byte slaveAddr)
      {
            ushort operationCodeAddress = 124;
            byte opCode = 0x66;<br><br>            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
      }<br><br>      /// <summary>
      /// 强制停止
      /// </summary>
      /// <param name="slaveAddr"></param>
      public void Opcode_SK(byte slaveAddr)
      {
            ushort operationCodeAddress = 124;
            byte opCode = 0xE1;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
      }<br><br>      ///// <summary>
      ///// 强制停止-带缓冲
      ///// </summary>
      ///// <param name="slaveAddr"></param>
      //publicvoid Opcode_SKD(byte slaveAddr)
      //{
      //    ushort operationCodeAddress = 124;
      //    byte opCode = 0xE2;
      //    master.WriteSingleRegister(slaveAddr, operationCodeAddress, opCode);
      //}<br><br>      /// <summary>
      /// 报警清除
      /// </summary>
      /// <param name="slaveAddr"></param>
      public void Opcode_AX(byte slaveAddr)
      {
            ushort operationCodeAddress = 124;
            byte opCode = 0xBA;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
      }<br><br>
      //1 0x31 数字量输入/输出端口1
      //2 0x32 数字量输入/输出端口2
      //3 0x33 数字量输入/输出端口3
      //4 0x34 数字量输入/输出端口4
      //5 0x35 数字量输入/输出端口5
      //6 0x36 数字量输入/输出端口6
      //7 0x37 数字量输出端口7
      //8 0x38 数字量输出端口8
      //9 0x39 数字量输出端口9
      //L 0x4C 低电平(光耦导通)
      //H 0x48 高电平(光耦断开)<br><br>      /// <summary>
      /// Y口输出L 0x4C H 0x48
      /// </summary>
      /// <param name="slaveAddr"></param>
      public void Opcode_SO(byte slaveAddr, byte ioPoint, byte condition)
      {
            ushort operationCodeAddress = 124;
            byte operationCode = 0x8B;<br><br>            ushort[] operationCodeParameters = new ushort;
            operationCodeParameters = operationCode;
            operationCodeParameters = ioPoint;
            operationCodeParameters = condition;<br><br>            WriteMultipleRegisters_10(slaveAddr, operationCodeAddress, operationCodeParameters);<br><br>
      }<br><br>      /// <summary>
      /// X口输入 ,0触碰1断开
      /// </summary>
      /// <param name="slaveAddr"></param>
      public string Opcode_IS(byte slaveAddr)
      {
            string result = string.Empty;
            try
            {
                ushort inputPortAddress = 5;
                ushort numberRegisters = 1;<br><br>                ushort[] data = ReadHoldingRegisters_03(slaveAddr, inputPortAddress, numberRegisters);
                if (data != null && data.Count() > 0)
                {
                  result = Convert.ToString(data, 2).PadLeft(12, '0');//转2进制,然后12位补零;
                }
            }
            catch (Exception)
            {<br><br>                throw;
            }
            return result;
      }<br><br>      /// <summary>
      /// 伺服使能
      /// </summary>
      /// <param name="slaveAddr"></param>
      /// <returns></returns>
      public void Opcoded_ME(byte slaveAddr)
      {
            ushort operationCodeAddress = 124;
            byte opCode = 0x9F;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
      }<br><br>      /// <summary>
      /// 伺服使能关闭
      /// </summary>
      /// <param name="slaveAddr"></param>
      /// <returns></returns>
      public void Opcoded_MD(byte slaveAddr)
      {
            ushort operationCodeAddress = 124;
            byte opCode = 0x9E;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
      }<br><br>      //以下单独设置参数,在不知道其他参数,不改动其他参数的时候使用(备用)<br><br>      // 设置加速度
      public void Acceleration(byte slaveAddr, ushort acceleration)
      {
            ushort parametersAddress = 344;<br><br>            ushort[] parameters = new ushort;
            parameters = 0;
            parameters = acceleration;<br><br>
            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
      }<br><br>      // 设置减速度
      public void Deceleration(byte slaveAddr, ushort deceleration)
      {
            ushort parametersAddress = 346;<br><br>            ushort[] parameters = new ushort;
            parameters = 0;
            parameters = deceleration;<br><br>
            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
      }<br><br>      // 设置速度
      public void Speed(byte slaveAddr, ushort speed)
      {
            ushort parametersAddress = 348;<br><br>            ushort[] parameters = new ushort;
            parameters = 0;
            parameters = speed;<br><br>
            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
      }<br><br>      #endregion<br><br>      #region 鸣志步进-多线程不安全模式 <br><br>      //鸣志步进写入,读取命令延时在 ModbusHelper.SendSerialString 统一设置<br><br>      //步进电机10以及以上从站地址以符号表示 <br><br>      //AC25 - 将加 设置为25转/秒/秒          344
      //DE25 - 将减 设置为25转/秒/秒          346
      //VE1.5 - 将速度设置为1.5转/秒          348
      //FL20000 - 执行20000步的按长度给进移动到距离 350<br><br>      /// <summary>
      ///步进电机设置加速,减速,速度 支持整数和小数 数值1-10转/秒
      /// </summary>
      public void Stepping_SetAccelerationSpeedDeceleration(object slaveAddr, string acceleration, string deceleration, string speed)
      {
            string strAC = $"{slaveAddr}AC{acceleration}\r";
            SendSerialString(strAC);
            //加速度<br><br>
            string strDE = $"{slaveAddr}DE{deceleration}\r";
            SendSerialString(strDE);
            //减速度<br><br>
            string strVE = $"{slaveAddr}VE{speed}\r";
            SendSerialString(strVE);
            //速度
      }<br><br>      /// <summary>
      /// 步进电机读取当前电机的加速度减速度速度
      /// </summary>
      /// <param name="slaveAddr"></param>
      /// <returns></returns>
      public int[] Stepping_Read_AC_DE_VE(object slaveAddr)
      {
            int[] result = new int;<br><br>
            //加速度
            string strAC = $"{slaveAddr}AC\r";
            var speed = SendSerialString(strAC);
            speed = speed.Replace($"{slaveAddr}AC=", string.Empty);
            speed = speed.Replace("\r", string.Empty);
            if (!string.IsNullOrWhiteSpace(speed))
            {
                result = Convert.ToInt32(speed);
            }<br><br>            //减速度
            string strDE = $"{slaveAddr}DE\r";
            speed = SendSerialString(strDE);
            speed = speed.Replace($"{slaveAddr}DE=", string.Empty);
            speed = speed.Replace("\r", string.Empty);
            if (!string.IsNullOrWhiteSpace(speed))
            {
                result = Convert.ToInt32(speed);
            }<br><br>            //速度
            string strVE = $"{slaveAddr}VE\r";
            speed = SendSerialString(strVE);
            speed = speed.Replace($"{slaveAddr}VE=", string.Empty);
            speed = speed.Replace("\r", string.Empty);
            if (!string.IsNullOrWhiteSpace(speed))
            {
                result = Convert.ToInt32(speed);
            }<br><br>            return result;
      }<br><br>
      /// <summary>
      ///设置步进电机移动距离 (支持正负数)   数值1w脉冲/圈
      /// </summary>
      /// <param name="slaveAddr"></param>
      /// <param name="speed"></param>
      public void Stepping_Distance(object slaveAddr, long distance)
      {
            string strDI = $"{slaveAddr}DI{distance}\r";
            SendSerialString(strDI);
            //移动距离
      }<br><br>      /// <summary>
      ///电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离) 数值同DI
      /// </summary>
      public void Stepping_FL(object slaveAddr, string numberStr)
      {
            string str = $"{slaveAddr}FL{numberStr}\r";
            SendSerialString(str);
      }<br><br>
      /// <summary>
      ///设置步进电机强制停止
      /// </summary>
      public void Stepping_SK(object slaveAddr)
      {
            string str = $"{slaveAddr}SK\r";
            SendSerialString(str);
      }<br><br>      /// <summary>
      ///设置步进电机报警复位(部分电机是限位开关,不是限位传感器)
      /// </summary>
      public void Stepping_AR(object slaveAddr)
      {
            string str = $"{slaveAddr}AR\r";
            SendSerialString(str);
      }<br><br>      /// <summary>
      ///设置步进电机Y口开关,L开,H关
      /// </summary>
      public void Stepping_SO(object slaveAddr, int number, string _switch)
      {
            string str = $"{slaveAddr}SO{number}{_switch}\r";
            SendSerialString(str);
      }<br><br>      /// <summary>
      ///读取步进电机X口开关,0触碰1断开
      /// </summary>
      public string Stepping_IS(object slaveAddr)
      {
            string str = $"{slaveAddr}IS\r";
            str = SendSerialString(str);
            return str;<br><br>      }<br><br>      /// <summary>
      ///设置步进电机运转时同时输出 1 支持 0 不支持
      /// </summary>
      public void Stepping_MT(object slaveAddr, int number)
      {
            //2024年 鸣志研发确认鸣志伺服暂不支持MT多任务并行
            string str = $"{slaveAddr}MT{number}\r";
            SendSerialString(str);
      }<br><br><br><br>
      #endregion<br><br>      #region close关闭串口、网口、Socket连接<br><br>      /// <summary>
      ///关闭串口,Modbus485连接
      /// </summary>
      public void CloseSerialPort()
      {
            serialPort.Close();
            master.Dispose();
      }<br><br>      /// <summary>
      ///关闭网口,Modbus485连接
      /// </summary>
      public void CloseTCP()
      {
            tcpClient.Close();
            master.Dispose();
      }<br><br>      /// <summary>
      /// 关闭socket,Modbus485连接
      /// </summary>
      public void CloseSocket()
      {
            socket.Close();
            socket.Disconnect(true);<br><br>            //master.Dispose();
      }<br><br>      /// <summary>
      /// 垃圾回收,串口,网口,socket,Modbus485 全部关闭
      /// 单个设备调试用using,多线程流程用手动连接并关闭
      /// </summary>
      public void Dispose()
      {
            try
            {
                //串口部分
                if (serialPort != null)
                {
                  serialPort.Close();
                }<br><br>                //网口部分
                if (tcpClient != null)
                {
                  tcpClient.Close();
                }<br><br>                //socket 备用部分
                if (socket != null)
                {
                  socket.Close();
                }<br><br>                //modbus-485
                if (master != null)
                {
                  master.Dispose();
                }<br><br>            }
            catch (Exception e)
            {
                LogHelper.Error($"垃圾回收处理异常:" + e.Message);
            }<br><br>      }<br><br>      #endregion<br><br>    }<br><br>    /// <summary>
    ///16位CRC校验
    /// </summary>
    public static class CRC16
    {
      /// <summary>
      /// 输出CRC校验码
      /// </summary>
      /// <param name="data">输入字节数组</param>
      /// <returns>字节0是高8位,字节1是低8位</returns>
      public static byte[] CRC16Calc(byte[] data)
      {
            //crc计算赋初始值
            int crc = 0xffff;
            for (int i = 0; i < data.Length; i++)
            {
                crc = crc ^ data;
                for (int j = 0; j < 8; j++)
                {
                  int temp;
                  temp = crc & 1;
                  crc = crc >> 1;
                  crc = crc & 0x7fff;
                  if (temp == 1)
                  {
                        crc = crc ^ 0xa001;
                  }
                  crc = crc & 0xffff;
                }
            }
            //CRC寄存器的高低位进行互换
            byte[] crc16 = new byte;
            //CRC寄存器的高8位变成低8位,
            crc16 = (byte)((crc >> 8) & 0xff);
            //CRC寄存器的低8位变成高8位
            crc16 = (byte)(crc & 0xff);<br><br>            return crc16;
      }<br><br>
    }<br><br>} 
以下是多线程安全版,基于上方代码封装的版本
using System;
using System.Linq;
using System.Threading;<br><br>namespace SunnyUI_NuclearForm
{
    /// <summary>
    /// 线程安全版 鸣志步进/伺服,松下伺服,华庆军继电器模块通用帮助类
    /// </summary>
    public class ThreadModbusHelper
    {<br><br>      private static readonly object threadLock = new object();//线程安全锁<br><br>      /// <summary>
      ///松下
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void PannasonicSendByte(string com, int baudRate, byte[] arr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  modbus.SendSerialByte(arr);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 华庆军
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void HuaqingjunSendByte(string com, int baudRate, byte[] arr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  modbus.SendSerialByte(arr);<br><br>                }
            }
      }<br><br>
      /// <summary>
      /// 称重模块获取数值/去皮归零
      /// </summary>
      public string GetWeight(string com, int baudRate, byte slaveAddr)
      {
            string result = string.Empty;<br><br>            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  //去皮
                  //slaveAddr = 18;
                  ushort operationCodeAddress = 240; //去皮地址
                  modbus.WriteSingleCoil_05(slaveAddr, operationCodeAddress, true);<br><br>                  Thread.Sleep(100);//去皮再称重延迟<br><br>                  ushort[] weightArr = modbus.ReadHoldingRegisters_03(slaveAddr, 1, 3);
                  if (weightArr != null && weightArr.Length > 0)
                  {
                        ushort weight = weightArr;
                        var weight1 = weight.ToString().Substring(0, weight.ToString().Length - 1);//正整数部分
                        var weight2 = weight.ToString().Substring(weight.ToString().Length - 1, 1);//小数部分<br><br>                        result = $"{weight1}.{weight2}";
                  }<br><br>
                }
            }<br><br>            return result;<br><br>      }<br><br>
      #region Stepping_Moons<br><br>      /// <summary>
      /// 步进电机设置加速,减速,速度 支持整数和小数 数值1-10转/秒
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_SetAccelerationSpeedDeceleration(string com, int baudRate, object slaveAddr, string acceleration, string deceleration, string speed)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string strAC = $"{slaveAddr}AC{acceleration}\r";
                  modbus.SendSerialString(strAC);
                  //加速度<br><br>
                  string strDE = $"{slaveAddr}DE{deceleration}\r";
                  modbus.SendSerialString(strDE);
                  //减速度<br><br>
                  string strVE = $"{slaveAddr}VE{speed}\r";
                  modbus.SendSerialString(strVE);
                  //速度<br><br>                }
            }
      }<br><br>
      /// <summary>
      /// 设置步进电机移动距离 (支持正负数)   数值1w脉冲/圈
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_Distance(string com, int baudRate, object slaveAddr, long distance)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string strDI = $"{slaveAddr}DI{distance}\r";
                  modbus.SendSerialString(strDI);
                  //移动距离<br><br>                }
            }
      }<br><br>
      /// <summary>
      /// 电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离) 数值同DI
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_FL(string com, int baudRate, object slaveAddr, string numberStr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string str = $"{slaveAddr}FL{numberStr}\r";
                  modbus.SendSerialString(str);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 设置步进电机强制停止
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_SK(string com, int baudRate, object slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string str = $"{slaveAddr}SK\r";
                  modbus.SendSerialString(str);<br><br>                }
            }
      }<br><br>      /// <summary>
      ///设置步进电机报警复位(部分电机是限位开关,不是限位传感器)
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_AR(string com, int baudRate, object slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string str = $"{slaveAddr}AR\r";
                  modbus.SendSerialString(str);<br><br>                }
            }
      }<br><br>      /// <summary>
      ///设置步进电机Y口开关,L开,H关
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Stepping_SO(string com, int baudRate, object slaveAddr, int number, string _switch)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  string str = $"{slaveAddr}SO{number}{_switch}\r";
                  modbus.SendSerialString(str);<br><br>                }
            }
      }<br><br>      /// <summary>
      ///读取步进电机X口开关,0触碰1断开
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public string Stepping_IS(string com, int baudRate, object slaveAddr)
      {
            string str = string.Empty;
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  str = $"{slaveAddr}IS\r";
                  str = modbus.SendSerialString(str);<br><br>                }
            }
            return str;
      }<br><br>      /// <summary>
      ///设置步进电机运转时同时输出 1 支持 0 不支持
      /// </summary>
      public void Stepping_MT(string com, int baudRate, object slaveAddr, int number)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  modbus.Stepping_MT(slaveAddr, number);<br><br>                }
            }
      }<br><br><br><br>      #endregion<br><br>      #region Servo_Moons<br><br>      /// <summary>
      /// 伺服电机设置加速,减速,速度 支持整数 数值1-10k/分钟
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_SetAccelerationSpeedDeceleration(string com, int baudRate, byte slaveAddr, ushort acceleration, ushort deceleration, ushort speed)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort parametersAddress = 344;<br><br>                  ushort[] parameters = new ushort;
                  parameters = 0;
                  parameters = acceleration;
                  parameters = 0;
                  parameters = deceleration;
                  parameters = 0;
                  parameters = speed;<br><br>                  modbus.WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 设置距离 数值1-100/圈
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_Distance(string com, int baudRate, byte slaveAddr, long distance)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort parametersAddress = 350;<br><br>                  ushort[] parameters = new ushort;
                  parameters = (ushort)distance;<br><br>                  modbus.WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离)
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_FL(string com, int baudRate, byte slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort operationCodeAddress = 124;
                  byte opCode = 0x66;<br><br>                  modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 强制停止
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_SK(string com, int baudRate, byte slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort operationCodeAddress = 124;
                  byte opCode = 0xE1;
                  modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 报警清除
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_AX(string com, int baudRate, byte slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort operationCodeAddress = 124;
                  byte opCode = 0xBA;
                  modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);<br><br>                }
            }
      }<br><br>
      /// <summary>
      /// Y口输出L 0x4C H 0x48
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_SO(string com, int baudRate, byte slaveAddr, byte ioPoint, byte condition)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  ushort operationCodeAddress = 124;
                  byte operationCode = 0x8B;<br><br>                  ushort[] operationCodeParameters = new ushort;
                  operationCodeParameters = operationCode;
                  operationCodeParameters = ioPoint;
                  operationCodeParameters = condition;<br><br>                  modbus.WriteMultipleRegisters_10(slaveAddr, operationCodeAddress, operationCodeParameters);<br><br>                }
            }
      }<br><br>
      /// <summary>
      /// X口输入
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public string Servo_IS(string com, int baudRate, byte slaveAddr)
      {
            string result = string.Empty;
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  try
                  {
                        ushort inputPortAddress = 5;
                        ushort numberRegisters = 1;<br><br>                        ushort[] data = modbus.ReadHoldingRegisters_03(slaveAddr, inputPortAddress, numberRegisters);
                        if (data != null && data.Count() > 0)
                        {
                            result = Convert.ToString(data, 2).PadLeft(12, '0');//转2进制,然后12位补零;
                        }
                  }
                  catch (Exception)
                  {<br><br>                  }<br><br>
                }
            }
            return result;
      }<br><br>      /// <summary>
      /// 伺服使能关闭
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="arr"></param>
      /// <returns></returns>
      public void Servo_MD(string com, int baudRate, byte slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  modbus.Opcoded_MD(slaveAddr);<br><br>                }
            }
      }<br><br>      /// <summary>
      /// 伺服使能
      /// </summary>
      /// <param name="com"></param>
      /// <param name="baudRate"></param>
      /// <param name="slaveAddr"></param>
      public void Servo_ME(string com, int baudRate, byte slaveAddr)
      {
            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                  modbus.ConnectForSerialPort(com, baudRate);<br><br>                  modbus.Opcoded_ME(slaveAddr);<br><br>                }
            }
      }<br><br><br><br>      <br><br><br><br>
      #endregion<br><br>    }
}支持串口通讯,也支持网口通讯。先讲讲Modbus 通讯是什么,软件部署在工控机上作上位机控制下位机运转,比如各种电机和继电器模块(将电脑的小电流信号转换为大电流信号发送到硬件上)
ModbusRTU的报文格式: 从站地址/设备Id(1个字节)+功能码(1个字节)+数据部分(N个字节)+校验、CRC检验(2个字节)
 
Modbus-RTU的功能码是用于指示Modbus协议进行何种数据操作的标识符。以下是常用的Modbus-RTU功能码及其含义:
01:读取线圈状态,用于读取开关量输入(DO)。
02:读取离散输入状态,用于读取开关量输入(DI)。
03:读取保持寄存器,用于读取模拟量输入(AI)。
04:读取输入寄存器,用于读取模拟量输入(AI)。
05:写单个线圈,用于控制开关量输出(DO)。
06:写单个保持寄存器,用于控制模拟量输出(AO)。
15:写多个线圈,用于控制多个开关量输出(DO)。
16:写多个保持寄存器,用于控制多个模拟量输出(AO)。
需要注意的是,不同设备支持的功能码可能不同,因此在使用Modbus-RTU通信时需要根据实际情况选择合适的功能码。一般常用的01,03,05,06,10
01是读线圈,03是读寄存器
05是写线圈,06是写寄存器
10是往多个寄存器里写数值(16)
15不怎么用
线圈是什么,软件层面可以简单的理解为开关阀门
寄存器是什么,可以简单的理解为一个存放数值的地址,每个地址有不同的作用,写入不同的数值发挥不同的效应
Modbus 报文如何编写?

 

 举几个栗子:
01 功能码示例:
读取 00A0h 伺服准备状态 (S-RDY) ReadOnly 0:准备 OFF 1:准备 ON
01 01 00 A0 00 01 FD E8
01 01 01 01 90 48 请求报文: 01 ,从站地址 ,一般一个485占一个COM口能接入31轴地址
                   01 ,Modbus 功能码 读取线圈地址
                   00 a0 ,读取地址00a0
                   00 01 ,读取一个字节长度,modbus通讯里,基本上是两个字节为一个地址
                   FD E8 ,crc 16位校验,可以自行生成
应答报文:01, 从站地址
                  01, 响应功能码01
                  01,返回一位字节
                  01,这时候返回的响应字节,就得看每个厂家是如何解释的了,比如这里,00 就是 off ,01 就是 on
                  9048,crc 16位校验,可以自行生成
03 功能码示例:
读取电压
01 03 60 2C 00 02 1B C2
01 03 04 05 28 00 05 BA F4
00050528H = 329000 (D请求报文: 01 ,从站地址 ,一般一个485占一个COM口能接入31轴地址
                   03 ,Modbus 功能码 读取寄存器地址
                   602c ,读取地址602c
                   00 02 ,读取2字节长度,modbus通讯里,基本上是两个字节为一个地址
                   1B C2 ,crc 16位校验,可以自行生成
应答报文:01, 从站地址
                  03, 响应功能码03
                  04,返回4位字节
                  05280005,这时候返回的响应字节,就得看每个厂家是如何解释的了,比如这里,一般是高位在前低位在后,但是这个厂家,低位在前高位在后,所以两组字节要反过来转换十进制。
                  0005在前0528在后,所以最后得到的电压是 00050528HEX = 329000 DEC
05 功能码示例:
01 05 00 61 FF 00 DD E4   ,报警清除 0000h:输入 OFF、FF00h:输入 ON
01 05 00 61 00 00 9C 14请求报文: 01 ,从站地址 ,一般一个485占一个COM口能接入31轴地址
                   05 ,Modbus 功能码 写线圈
                   0061 ,写线圈0061
                   FF00,写入FF00,硬件的编码器看到FF00编译为NO,机器开,00000编译为0FF,机器关
                   DDE4 ,crc 16位校验,可以自行生成
应答报文:05,06的正确应答报文一般都是把请求报文原样输出回来表示已经执行
06 功能码示例:
加速度减速度速度
01 06 46 00 01f4 设置v0=500请求报文: 01 ,从站地址 ,一般一个485占一个COM口能接入31轴地址
                   06 ,Modbus 功能码 写寄存器
                   4600,写寄存器4600
                   01f4, 十六进制的500,modbus中全部都是十六进制字节请求和应答
                   这里还有两节CRC,因为速度经常改变,所以没有带CRC,每次请求报文的字节发生变化时CRC一定会变化。
应答报文:05,06的正确应答报文一般都是把请求报文原样输出回来表示已经执行
10 功能码示例:
0E 10 01 5E 00 02 04 00 1E 84 80//200w
0E10 015E 0002 04 FFE1 7B80 //-200w这里展示一个稍微复杂一点的报文
请求报文: 0E , 是十进制从站地址14 ,
                   10,Modbus 功能码 写多个寄存器
                   015E,写寄存器015E
                   0002, 从015e开始写两个寄存器地址
                   04,四组字节
                   00 1E 84 80 ,高位字节在前低位字节在后,两组字节为一个寄存器地址,两个寄存器地址合起来存放一个大数值。比如这里的200w, 00 1E 84 80 转换为十进制=200w
                   这里还有两节CRC,因为速度经常改变,所以没有带CRC,每次请求报文的字节发生变化时CRC一定会变化。
应答报文:10的正确应答报文一般都是把请求报文原样输出回来表示已经执行
下面一组报文其实同上,只是写入的数值是-200w,

 根据计算器可以直观的看到十进制-200w=FFE1 7B80
 
接下来说说两个通用帮助类如何使用
1. 网口通讯
   
ModbusHelper modbusHelper = new ModbusHelper();<br><br>modbusHelper.ConnectForTCP("IP", "端口")<br><br>// 发送数据到硬件
//00 00 00 00 00 06 01 05 00 00 ff 00 开
byte[] arr = new byte;
arr = 0x00;
arr = 0x00;
arr = 0x00;
arr = 0x00;
arr = 0x00;
arr = 0x06;
arr = 0x01;
arr = 0x05;
arr = 0x00;
arr = 0x00;
arr = 0xff;
arr = 0x00;
modbusHelper.SendTCPByte(arr);<br>modbusHelper.CloseTCP();发送的字节数组是上面曾说过的06功能码,每个厂家都有自己的操作指令
2. 串口通讯
using (ModbusHelper com5_modbusHelper = new ModbusHelper())
{
      com5_modbusHelper.ConnectForSerialPort("COM5", 9600);
      byte slaveAddr = 2;    //开盖抓手X
      com5_modbusHelper.Stepping_AR(slaveAddr);//报警清除
      //你操作厂家硬件的指令代码集
}我的Modbus 通用帮助类 ,你可以选择用Using连接Dispose自动垃圾回收,也可以自己控制连接和关闭,建议每次发完一组操作指令后就断开和硬件的连接
接下来说鸣志步进电机的指令和控制代码
在 region 鸣志步进-多线程不安全模式  endregion,这个折叠标签里
从上到下依次是常用的十几个命令如下:
步进电机设置加速,减速,速度 支持整数和小数 数值1-10转/秒
步进电机读取当前电机的加速度减速度速度
设置步进电机移动距离 (支持正负数)   数值1w脉冲/圈
电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离) 数值同DI
设置步进电机强制停止
设置步进电机报警复位
设置步进电机Y口开关,L开,H关
读取步进电机X口开关,0触碰1断开
设置步进电机运转时同时输出调用方式如下:
using (ModbusHelper com6_modbusHelper = new ModbusHelper())
{
      com6_modbusHelper.ConnectForSerialPort("COM6", 9600);<br><br>      //从站地址
      byte slaveAddr = 1;<br>    com6_modbusHelper.Stepping_SetAccelerationSpeedDeceleration(slaveAddr, "8", "8", "6");//设置加速度减速度速度<br>    int[] arr=com6_modbusHelper.Stepping_Read_AC_DE_VE(slaveAddr);//读取指定电机的加速度减速度速度//比如一个电机走一段路,从0米处运转到时速100码的过程是加速度,然后按照速度120码均速前进到终点,再终点前按照减速度逐渐停止到0码是减速度。<br>      com6_modbusHelper.Stepping_Distance(slaveAddr, 10000);//1w是脉冲数,一般绝大部分电机都是1w脉冲一圈, -1w就是反转1圈<br>      com6_modbusHelper.Stepping_FL(slaveAddr, "同DI脉冲数");//一般设置了DI后在执行FL,电机才会运行<br>      com6_modbusHelper.Stepping_SK(slaveAddr);//强制停止电机当前的运转<br>      com6_modbusHelper.Stepping_AR(slaveAddr);//报警清除,一般电机运行到限位处或者负载过大,驱动器会拒绝继续执行操作命令,需要先清除报警<br>      com6_modbusHelper.Stepping_SO(slaveAddr, 1, "L"); //电机的Y_口开启<br>      com6_modbusHelper.Stepping_SO(slaveAddr, 1, "H"); //电机的Y_口关闭<br>      com6_modbusHelper.Stepping_IS(slaveAddr)//根据这个从站地址读取这个电机的全部输出口,0开1关<br>      com6_modbusHelper.Stepping_MT(slaveAddr, number);//设置步进电机运转时同时输出 1 支持 0 不支持,鸣志步进电机支持此指令,伺服电机不支持<br>
}接下来说鸣志伺服电机的指令和控制代码
在 #region Servo_Moons  endregion,这个折叠标签里
从上到下依次是常用的十几个命令如下:
设置加减速,减速度,速度
读取伺服电机加速度减速度速度
设置距离 数值1-100/圈
设置距离 脉冲数
电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离)
强制停止
报警清除
Y口输出L 0x4C H 0x48
X口输入 ,0触碰1断开
伺服使能
伺服使能关闭伺服和步进的区别在于,伺服是闭环控制自带编码器,步进是开环控制没有数据反馈容易丢步。伺服更精准更贵。
在软件层面,步进电机通过写入简单明了的字符串指令轻易控制,但是同品牌的伺服电机要写地址操作起来更复杂
调用方式如下:
   private static ThreadModbusHelper modbus_com5 = new ThreadModbusHelper();//在这里控制伺服全部使用多线程安全帮助类<br>      modbus_com5.Servo_SetAccelerationSpeedDeceleration("com5", 9600, slaveAddr, 3000, 3000, 5000);//设置加速度减速度速度,这里的加减速度要/6,速度要/240,才是真正的RPM(每分钟X转)
    ushort[] arr=modbus_com5.ReadAccelerationSpeedDeceleration("com5", 9600, slaveAddr);//读取加速度减速度速度,这个方法在多线程不安全版本里,没有集成进多线程安全版本      modbus_com5.Servo_Distance("com5", 9600, slaveAddr, 15);//通过圈数控制电机运转,支持正负数
      modbus_com5.DistanceForPulse(slaveAddr, pulse);//通过脉冲控制电机运转,支持正负数
      modbus_com5.Servo_FL("com5", 9600, slaveAddr);//让电机运转执行DI命令
      modbus_com5.Servo_SK("com6", 9600, slaveAddr);//电机强制停止
      modbus_com5.Servo_AX("com5", 9600, slaveAddr);//报警清除
      modbus_com5.Servo_SO("com5", 9600, slaveAddr, 0x34, 0x4C);//Y口输出,这里展示的是Y4口,开启。0x34可以替换为 0x31-0x38 对应y1-y8地址,0x4c是开启,0x48是关闭
      modbus_com5.Servo_IS("com5", 9600, slaveAddr)//读取X口,0触碰1断开,如果8个输入口都是触碰状态,可能返回的是0,需要强制补零
      modbus_com5.Servo_MD("COM6", 9600, slaveAddr);//伺服使能关闭
      modbus_com5.Servo_ME("COM6", 9600, slaveAddr);//伺服使能开启,在使用伺服电机需要先伺服使能才能使用其功能
<br><br> 
接下来说华庆军继电器模块的指令和控制代码
示例如下:
// 0A05000AFF00AD43 Y11打开
modbus_com8.HuaqingjunSendByte("com8", 9600, new byte[] { 0x0A, 0X05, 0X00, 0X0A, 0XFF, 0X00, 0XAD, 0X43 });又是一串很熟悉的开关线圈字节数组指令,华庆军官网上下载调试软件QingJunTestV3.0.exe
左侧设置开关量型选择,比如我选择的是32路输入输出
左侧下方输入地址或者IP
右侧点击对应的Y1-32,蓝色的一条操作码就是我们需要的字节数组指令

上面是华庆军输出指令,输入指令如下
//十进制
var modbusData = modbusHelper.ReadHoldingRegisters_03(0x01, 0x04, 1);

//十进制转二进制并八位补零
var binary = modbusData != null && modbusData.Length > 0 ? Convert.ToString(modbusData, 2).PadLeft(8, '0') : "00000000";

//二进制转数组
var inArray = StringToArray(binary);

//IN1-IN8数据绑定         //0断开1闭合 接下来说松下伺服电机的指令和控制代码
 松下modbus请求报文
00 05 00 60 FF 00 8D F5,00 为广播全部从站伺服开启00 05 00 60 00 00 CC 05,00 为广播全部从站伺服关闭01 05 00 60 FF 00 8C 24 , 指定从站,伺服使能开启0060线圈写入FF00 ON01 05 00 60 00 00 CD D4, 指定从站,伺服使能关闭0060线圈写入0000 off01 06 44 14 00 00 DD 3E,指定运行block编号,4414寄存器地址写入0000,指定运行block_0号动作这里解释下block 是什么,松下伺服电机需要先编辑好block动作,然后调用block,电机才会运转。block里包括常用的正转反转加速减速速度,回原点,紧急刹车等功能01 05 01 20 FF 00 8C 0C,0120线圈对应地址,STB开关, FF00开启01 05 01 20 00 00 CD FC ,0120线圈对应地址,STB开关, 0000关闭05线圈 01 05 00 61 FF 00 DD E4   ,报警清除 0000h:输入 OFF、FF00h:输入 ON
01 05 00 61 00 00 9C 1406 寄存器01 06 46 3a 00 00   原点有效化01 06 46 3a 00 01   原点无效化01 06 10 20 61 73   保存到 EEPROM加速度减速度速度
01 06 46 00 01f4 设置v0=50001 06 46 10 00 64 设置a0=10001 06 46 20 00 64 设置d0=100读取 00A0h 伺服准备状态 (S-RDY) ReadOnly 0:准备 OFF 1:准备 ON
01 01 00 A0 00 01 FD E8
01 01 01 01 90 48 读取电压
01 03 60 2C 00 02 1B C2
01 03 04 05 28 00 05 BA F4
00050528H = 329000 (D 
以下为本人编辑好的Block动作一览
 

 
 
//松下电机 Block相关动作编号
//从站地址10 桶盖
//0 正10w
//1 负10w
//2 正1000
//3 负1000
//4 163w3k
//5 正3k
//6 负3k
//7 绝对定位 0 回原点
//8 减速停止/即刻停止
比如调用紧急刹车如下
这里就要用到CRC动态生成校验数组了
using (ModbusHelper bottle_modbusHelper = new ModbusHelper())
{
   bottle_modbusHelper.ConnectForSerialPort("COM6", 9600);

   //16 10
   //10 06 44 14 00 08指定运行block编号
   byteArr = new byte[] { 0x10, 0x06, 0x44, 0x14, 0x00, 0x08 };
   crc = CRC16.CRC16Calc(byteArr);
   newArr = byteArr.Concat(crc).ToArray();
   bottle_modbusHelper.SendSerialByte(newArr);

   Thread.Sleep(50);//松下命令间隔

   //10 05 01 20 FF 00   ,STB开
   byteArr = new byte[] { 0x10, 0x05, 0x01, 0x20, 0xFF, 0x00 };
   crc = CRC16.CRC16Calc(byteArr);
   newArr = byteArr.Concat(crc).ToArray();
   bottle_modbusHelper.SendSerialByte(newArr);
}以下为工厂部分硬件实拍

 
以下为部分UI实拍

 

 祝各位在工控/自动化的道路越走越舒坦<br><br>来源:https://www.cnblogs.com/Gao1234/p/18096175
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下