首页 > 其他分享 >NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下伺服电机,华庆军继电器模块)

NET工控,上位机,Modbus485网口/串口通讯(鸣志步进电机,鸣志伺服电机,松下伺服电机,华庆军继电器模块)

时间:2024-06-02 10:10:47浏览次数:23  
标签:string 伺服电机 ushort public modbus 串口 byte 鸣志 slaveAddr

先上两个通用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;

namespace SunnyUI_NuclearForm
{
    /// <summary>
    /// Modbus通用帮助类
    /// </summary>
    public class ModbusHelper : IDisposable
    {

        IModbusMaster master;

        
        SerialPort serialPort
        //网口-TCP
        TcpClient t//private static readonly object threadObj = new object();//线程安全锁

        //ManualResetEvent manualEvent = new ManualResetEvent(true);//true运行,false不运行


        #region  connection

        /// <summary>
        /// 串口连接
        /// </summary>
        /// <returns></returns>
        public bool ConnectForSerialPort(string com, int baudRate)
        {
            bool result = false;

            if (!string.IsNullOrWhiteSpace(com) && baudRate > 0)
            {
                serialPort = new SerialPort();

                serialPort.PortName = com;
                serialPort.BaudRate = baudRate;

                //下面三个参数看情况要不要开放
                serialPort.DataBits = 8;
                serialPort.StopBits = StopBits.One;
                serialPort.Parity = Parity.None;

                try
                {
                    if (!serialPort.IsOpen)
                    {
                        serialPort.Open();
                    }


                    master = ModbusSerialMaster.CreateRtu(serialPort);
                    master.Transport.ReadTimeout = 3 * 1000;//读 超时时间
                    master.Transport.WriteTimeout = 3 * 1000;//写 超时时间
                    master.Transport.Retries = 20;//重试次数
                    master.Transport.WaitToRetryMilliseconds = 3 * 1000;//重试间隔

                    result = true;
                }
                catch (Exception e)
                {
                    //打开失败
                    LogHelper.Error($"串口无法连接!COM:{com},波特率:{baudRate}。以下为详细信息:\r\n{e.Message}");
                    result = false;
                }
            }
            return result;
        }

        /// <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);

                        master.Transport.ReadTimeout = 3 * 1000;//读 超时时间
                        master.Transport.WriteTimeout = 3 * 1000;//写 超时时间
                        master.Transport.Retries = 20;//重试次数
                        master.Transport.WaitToRetryMilliseconds = 3 * 1000;//重试间隔

                        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;
                }

            }
            return result;

        }

        /// <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
                //所以在此留下备用通信方法 

                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                try
                {
                    socket.Connect(IPAddress.Parse(ip), port);
                    result = true;

                }
                catch (Exception e)
                {
                    //连接失败
                    LogHelper.Error($"Socket(备用)无法连接!IP:{ip},端口:{port}。以下为详细信息:\r\n{e.Message}");
                    result = false;
                }

            }
            return result;
        }

        #endregion

        #region send

        /// <summary>
        /// 向串口发送数据 Modbus报文(字节)
        /// </summary>
        public void SendSerialByte(byte[] arr)
        {
            //this.manualEvent.WaitOne();//等待
            //lock (threadObj)
            //{
            //this.manualEvent.Reset();//红灯

            if (serialPort != null && serialPort.IsOpen && arr != null && arr.Count() > 0)
            {
                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();

                Thread.Sleep(50);

                serialPort.Write(arr, 0, arr.Length);

                Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔

                try
                {
                    // 接收来自读取命令的数据 广播命令执行完毕不响应
                    //byte[] receiveData = new byte[1024];
                    //int bytesRead = serialPort.Read(receiveData, 0, receiveData.Length);

                    if (serialPort.BytesToRead > 0)
                    {
                        var result = serialPort.ReadExisting();
                    }

                }
                catch (Exception e)
                {

                }

            }

            //this.manualEvent.Set();//绿灯
            //}
        }

        /// <summary>
        /// 向串口发送数据 Modbus报文(字符串)
        /// </summary>
        public string SendSerialString(string str)
        {
            string result = string.Empty;

            //this.manualEvent.WaitOne();//等待

            //lock (threadObj)
            //{
            //this.manualEvent.Reset();//红灯
            if (serialPort != null && serialPort.IsOpen && !string.IsNullOrWhiteSpace(str))
            {

                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();

                Thread.Sleep(50);

                serialPort.Write(str);

                Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔

                try
                {
                    // 接收来自读取命令的数据 广播命令执行完毕不响应
                    if (serialPort.BytesToRead > 0)
                    {
                        result = serialPort.ReadExisting();
                    }
                }
                catch (Exception e)
                {

                }

            }
            //this.manualEvent.Set();//绿灯
            //}
            return result;
        }

        /// <summary>
        /// 向网口发送数据 Modbus报文
        /// </summary>
        /// <param name="arr"></param>
        public void SendTCPByte(byte[] arr)
        {
            if (arr != null && arr.Count() > 0 && tcpClient.Connected)
            {
                // 获取网络流
                NetworkStream stream = tcpClient.GetStream();

                //tcpClient.Client.Send(arr);
                stream.Write(arr, 0, arr.Length);
                //Thread.Sleep(50);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔

                try
                {
                    // 接收来自读取命令的数据 广播命令执行完毕不响应
                    byte[] receiveData = new byte[1024];
                    int bytesRead = stream.Read(receiveData, 0, receiveData.Length);
                }
                catch (Exception)
                {

                    throw;
                }

            }


        }

        /// <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);//写入读取命令后延时读取 或者跟下一条命令之间产生间隔

                try
                {
                    // 接收来自读取命令的数据 广播命令执行完毕不响应
                    byte[] buffer = new byte[1024];
                    int byteRead = socket.Receive(buffer);
                }
                catch (Exception)
                {

                    throw;
                }


            }
        }

        #endregion

        #region read


        /// <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);
        }

        /// <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);
        }

        /// <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)
        {

            try
            {
                ushort[] Uvalue = master.ReadHoldingRegisters(slaveAddress, StartAddress, numberOfPoints);

                //ushort 转short
                //short[] value = new short[Uvalue.Length];
                //for (int i = 0; i < Uvalue.Length; i++)
                //{
                //    if (Uvalue[i] > short.MaxValue)
                //    {
                //        value[i] = (short)(Uvalue[i] - 65536);
                //    }
                //    else
                //    {
                //        value[i] = (short)Uvalue[i];
                //    }
                //}

                return Uvalue;
            }
            catch (Exception e)
            {
                return null;
            }

        }

        /// <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)
        {

            try
            {
                ushort[] Uvalue = master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints);

                //ushort 转short

                //short[] value = new short[Uvalue.Length];
                //for (int i = 0; i < Uvalue.Length; i++)
                //{
                //    if (Uvalue[i] > short.MaxValue)
                //    {
                //        value[i] = (short)(Uvalue[i] - 65536);
                //    }
                //    else
                //    {
                //        value[i] = (short)Uvalue[i];
                //    }


                //}

                return Uvalue;
            }
            catch (Exception e)
            {
                return null;
            }

        }


        #endregion

        #region write

        /// <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)
        {

            master.WriteSingleCoil(slaveAddress, coilAddress, value);

        }

        /// <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)
        {

            try
            {
                //short 转 ushort

                //ushort Uvalue;
                //if (value < 0)
                //{
                //    Uvalue = (ushort)(ushort.MaxValue + value + 1);
                //}
                //else
                //{
                //    Uvalue = (ushort)value;
                //}
                master.WriteSingleRegister(slaveAddress, registerAddress, value);
            }
            catch (Exception e)
            {
                return;
            }


        }



        /// <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)
            {

            }




        }

        #endregion

        #region  鸣志伺服-多线程不安全模式

        // 设置加减速,减速度,速度 数值1-10k/分钟
        public void SetAccelerationSpeedDeceleration(byte slaveAddr, ushort acceleration, ushort deceleration, ushort speed)
        {
            ushort parametersAddress = 344;

            ushort[] parameters = new ushort[6];
            parameters[0] = 0;
            parameters[1] = acceleration;
            parameters[2] = 0;
            parameters[3] = deceleration;
            parameters[4] = 0;
            parameters[5] = speed;

            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
        }

        /// <summary>
        /// 读取伺服电机加速度减速度速度
        /// </summary>
        /// <param name="slaveAddr"></param>
        /// <returns></returns>
        public ushort[] ReadAccelerationSpeedDeceleration(byte slaveAddr)
        {
            ushort[] result = new ushort[3];
            ushort[] speedArr = ReadHoldingRegisters_03(slaveAddr, 344, 6);
            if (speedArr != null && speedArr.Length >= 6)
            {
                //AC  加速度  344+1
                //DC  减速度  346+1
                //VE  速度  348+1
                result[0] = speedArr[1];
                result[1] = speedArr[3];
                result[2] = speedArr[5];
            }
            return result;
        }

        // 设置距离 数值1-100/圈
        public void Distance(byte slaveAddr, long distance)
        {

            ushort parametersAddress = 350;
            ushort[] parameters = new ushort[1];
            parameters[0] = (ushort)distance;

            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);

        }

        // 设置距离 脉冲数
        public void DistanceForPulse(byte slaveAddr, int pulse)
        {
            string convert = pulse.ToString("X").PadLeft(8, '0'); //转十六进制然后八位补零

            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)};

            var crc = CRC16.CRC16Calc(sendByte);
            var newSend = sendByte.Concat(crc).ToArray();

            SendSerialByte(newSend);


        }


        //Modbus 寄存器表中寄存器40125被定义为操作码寄存器,向40125寄存器写入相应的操作码,即执行相应操作码的动作
        //FL 0x66 
        //SK 0xE1
        //SO 0x8B

        /// <summary>
        /// 电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离)
        /// </summary>
        /// <param name="slaveAddr"></param>
        public void Opcode_FL(byte slaveAddr)
        {
            ushort operationCodeAddress = 124;
            byte opCode = 0x66;

            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
        }

        /// <summary>
        /// 强制停止
        /// </summary>
        /// <param name="slaveAddr"></param>
        public void Opcode_SK(byte slaveAddr)
        {
            ushort operationCodeAddress = 124;
            byte opCode = 0xE1;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
        }

        ///// <summary>
        ///// 强制停止-带缓冲
        ///// </summary>
        ///// <param name="slaveAddr"></param>
        //public  void Opcode_SKD(byte slaveAddr)
        //{
        //    ushort operationCodeAddress = 124;
        //    byte opCode = 0xE2;
        //    master.WriteSingleRegister(slaveAddr, operationCodeAddress, opCode);
        //}

        /// <summary>
        /// 报警清除
        /// </summary>
        /// <param name="slaveAddr"></param>
        public void Opcode_AX(byte slaveAddr)
        {
            ushort operationCodeAddress = 124;
            byte opCode = 0xBA;
            WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);
        }


        //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 高电平(光耦断开)

        /// <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;

            ushort[] operationCodeParameters = new ushort[3];
            operationCodeParameters[0] = operationCode;
            operationCodeParameters[1] = ioPoint;
            operationCodeParameters[2] = condition;

            WriteMultipleRegisters_10(slaveAddr, operationCodeAddress, operationCodeParameters);


        }

        /// <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;

                ushort[] data = ReadHoldingRegisters_03(slaveAddr, inputPortAddress, numberRegisters);
                if (data != null && data.Count() > 0)
                {
                    result = Convert.ToString(data[0], 2).PadLeft(12, '0');//转2进制,然后12位补零;
                }
            }
            catch (Exception)
            {

                throw;
            }
            return result;
        }

        /// <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);
        }

        /// <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);
        }

        //以下单独设置参数,在不知道其他参数,不改动其他参数的时候使用(备用)

        // 设置加速度
        public void Acceleration(byte slaveAddr, ushort acceleration)
        {
            ushort parametersAddress = 344;

            ushort[] parameters = new ushort[2];
            parameters[0] = 0;
            parameters[1] = acceleration;


            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
        }

        // 设置减速度
        public void Deceleration(byte slaveAddr, ushort deceleration)
        {
            ushort parametersAddress = 346;

            ushort[] parameters = new ushort[2];
            parameters[0] = 0;
            parameters[1] = deceleration;


            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
        }

        // 设置速度
        public void Speed(byte slaveAddr, ushort speed)
        {
            ushort parametersAddress = 348;

            ushort[] parameters = new ushort[2];
            parameters[0] = 0;
            parameters[1] = speed;


            WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);
        }

        #endregion

        #region 鸣志步进-多线程不安全模式 

        //鸣志步进写入,读取命令延时在 ModbusHelper.SendSerialString 统一设置

        //步进电机10以及以上从站地址以符号表示 

        //AC25 - 将加 设置为25转/秒/秒          344
        //DE25 - 将减 设置为25转/秒/秒          346
        //VE1.5 - 将速度设置为1.5转/秒          348
        //FL20000 - 执行20000步的按长度给进移动到距离 350

        /// <summary>
        ///  步进电机设置加速,减速,速度 支持整数和小数 数值1-10转/秒
        /// </summary>
        public void Stepping_SetAccelerationSpeedDeceleration(object slaveAddr, string acceleration, string deceleration, string speed)
        {
            string strAC = $"{slaveAddr}AC{acceleration}\r";
            SendSerialString(strAC);
            //加速度


            string strDE = $"{slaveAddr}DE{deceleration}\r";
            SendSerialString(strDE);
            //减速度


            string strVE = $"{slaveAddr}VE{speed}\r";
            SendSerialString(strVE);
            //速度
        }

        /// <summary>
        /// 步进电机读取当前电机的加速度减速度速度
        /// </summary>
        /// <param name="slaveAddr"></param>
        /// <returns></returns>
        public int[] Stepping_Read_AC_DE_VE(object slaveAddr)
        {
            int[] result = new int[3];


            //加速度
            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[0] = Convert.ToInt32(speed);
            }

            //减速度
            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[1] = Convert.ToInt32(speed);
            }

            //速度
            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[2] = Convert.ToInt32(speed);
            }

            return result;
        }


        /// <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);
            //移动距离
        }

        /// <summary>
        ///  电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离) 数值同DI
        /// </summary>
        public void Stepping_FL(object slaveAddr, string numberStr)
        {
            string str = $"{slaveAddr}FL{numberStr}\r";
            SendSerialString(str);
        }


        /// <summary>
        ///  设置步进电机强制停止
        /// </summary>
        public void Stepping_SK(object slaveAddr)
        {
            string str = $"{slaveAddr}SK\r";
            SendSerialString(str);
        }

        /// <summary>
        ///  设置步进电机报警复位(部分电机是限位开关,不是限位传感器)
        /// </summary>
        public void Stepping_AR(object slaveAddr)
        {
            string str = $"{slaveAddr}AR\r";
            SendSerialString(str);
        }

        /// <summary>
        ///  设置步进电机Y口开关,L开,H关
        /// </summary>
        public void Stepping_SO(object slaveAddr, int number, string _switch)
        {
            string str = $"{slaveAddr}SO{number}{_switch}\r";
            SendSerialString(str);
        }

        /// <summary>
        ///  读取步进电机X口开关,0触碰1断开
        /// </summary>
        public string Stepping_IS(object slaveAddr)
        {
            string str = $"{slaveAddr}IS\r";
            str = SendSerialString(str);
            return str;

        }

        /// <summary>
        ///  设置步进电机运转时同时输出 1 支持 0 不支持
        /// </summary>
        public void Stepping_MT(object slaveAddr, int number)
        {
            //2024年 鸣志研发确认鸣志伺服暂不支持MT多任务并行
            string str = $"{slaveAddr}MT{number}\r";
            SendSerialString(str);
        }




        #endregion

        #region close  关闭串口、网口、Socket连接

        /// <summary>
        ///  关闭串口,Modbus485连接
        /// </summary>
        public void CloseSerialPort()
        {
            serialPort.Close();
            master.Dispose();
        }

        /// <summary>
        ///  关闭网口,Modbus485连接
        /// </summary>
        public void CloseTCP()
        {
            tcpClient.Close();
            master.Dispose();
        }

        /// <summary>
        /// 关闭socket,Modbus485连接
        /// </summary>
        public void CloseSocket()
        {
            socket.Close();
            socket.Disconnect(true);

            //master.Dispose();
        }

        /// <summary>
        /// 垃圾回收,串口,网口,socket,Modbus485 全部关闭
        /// 单个设备调试用using,多线程流程用手动连接并关闭
        /// </summary>
        public void Dispose()
        {
            try
            {
                //串口部分
                if (serialPort != null)
                {
                    serialPort.Close();
                }

                //网口部分
                if (tcpClient != null)
                {
                    tcpClient.Close();
                }

                //socket 备用部分
                if (socket != null)
                {
                    socket.Close();
                }

                //modbus-485
                if (master != null)
                {
                    master.Dispose();
                }

            }
            catch (Exception e)
            {
                LogHelper.Error($"垃圾回收处理异常:" + e.Message);
            }

        }

        #endregion

    }

    /// <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[i];
                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[2];
            //CRC寄存器的高8位变成低8位,
            crc16[1] = (byte)((crc >> 8) & 0xff);
            //CRC寄存器的低8位变成高8位
            crc16[0] = (byte)(crc & 0xff);

            return crc16;
        }


    }

}

以下是多线程安全版,基于上方代码封装的版本

using System;
using System.Linq;
using System.Threading;

namespace SunnyUI_NuclearForm
{
    /// <summary>
    /// 线程安全版 鸣志步进/伺服,松下伺服,华庆军继电器模块通用帮助类
    /// </summary>
    public class ThreadModbusHelper
    {

        private static readonly object threadLock = new object();//线程安全锁

        /// <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);

                    modbus.SendSerialByte(arr);

                }
            }
        }

        /// <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);

                    modbus.SendSerialByte(arr);

                }
            }
        }


        /// <summary>
        /// 称重模块获取数值/去皮归零
        /// </summary>
        public string GetWeight(string com, int baudRate, byte slaveAddr)
        {
            string result = string.Empty;

            lock (threadLock)
            {
                using (ModbusHelper modbus = new ModbusHelper())
                {
                    modbus.ConnectForSerialPort(com, baudRate);

                    //去皮
                    //slaveAddr = 18;
                    ushort operationCodeAddress = 240; //去皮地址
                    modbus.WriteSingleCoil_05(slaveAddr, operationCodeAddress, true);

                    Thread.Sleep(100);//去皮再称重延迟

                    ushort[] weightArr = modbus.ReadHoldingRegisters_03(slaveAddr, 1, 3);
                    if (weightArr != null && weightArr.Length > 0)
                    {
                        ushort weight = weightArr[2];
                        var weight1 = weight.ToString().Substring(0, weight.ToString().Length - 1);//正整数部分
                        var weight2 = weight.ToString().Substring(weight.ToString().Length - 1, 1);//小数部分

                        result = $"{weight1}.{weight2}";
                    }


                }
            }

            return result;

        }


        #region Stepping_Moons

        /// <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);

                    string strAC = $"{slaveAddr}AC{acceleration}\r";
                    modbus.SendSerialString(strAC);
                    //加速度


                    string strDE = $"{slaveAddr}DE{deceleration}\r";
                    modbus.SendSerialString(strDE);
                    //减速度


                    string strVE = $"{slaveAddr}VE{speed}\r";
                    modbus.SendSerialString(strVE);
                    //速度

                }
            }
        }


        /// <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);

                    string strDI = $"{slaveAddr}DI{distance}\r";
                    modbus.SendSerialString(strDI);
                    //移动距离

                }
            }
        }


        /// <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);

                    string str = $"{slaveAddr}FL{numberStr}\r";
                    modbus.SendSerialString(str);

                }
            }
        }

        /// <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);

                    string str = $"{slaveAddr}SK\r";
                    modbus.SendSerialString(str);

                }
            }
        }

        /// <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);

                    string str = $"{slaveAddr}AR\r";
                    modbus.SendSerialString(str);

                }
            }
        }

        /// <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);

                    string str = $"{slaveAddr}SO{number}{_switch}\r";
                    modbus.SendSerialString(str);

                }
            }
        }

        /// <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);

                    str = $"{slaveAddr}IS\r";
                    str = modbus.SendSerialString(str);

                }
            }
            return str;
        }

        /// <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);

                    modbus.Stepping_MT(slaveAddr, number);

                }
            }
        }



        #endregion

        #region Servo_Moons

        /// <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);

                    ushort parametersAddress = 344;

                    ushort[] parameters = new ushort[6];
                    parameters[0] = 0;
                    parameters[1] = acceleration;
                    parameters[2] = 0;
                    parameters[3] = deceleration;
                    parameters[4] = 0;
                    parameters[5] = speed;

                    modbus.WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);

                }
            }
        }

        /// <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);

                    ushort parametersAddress = 350;

                    ushort[] parameters = new ushort[1];
                    parameters[0] = (ushort)distance;

                    modbus.WriteMultipleRegisters_10(slaveAddr, parametersAddress, parameters);

                }
            }
        }

        /// <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);

                    ushort operationCodeAddress = 124;
                    byte opCode = 0x66;

                    modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);

                }
            }
        }

        /// <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);

                    ushort operationCodeAddress = 124;
                    byte opCode = 0xE1;
                    modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);

                }
            }
        }

        /// <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);

                    ushort operationCodeAddress = 124;
                    byte opCode = 0xBA;
                    modbus.WriteSingleRegister_06(slaveAddr, operationCodeAddress, opCode);

                }
            }
        }


        /// <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);

                    ushort operationCodeAddress = 124;
                    byte operationCode = 0x8B;

                    ushort[] operationCodeParameters = new ushort[3];
                    operationCodeParameters[0] = operationCode;
                    operationCodeParameters[1] = ioPoint;
                    operationCodeParameters[2] = condition;

                    modbus.WriteMultipleRegisters_10(slaveAddr, operationCodeAddress, operationCodeParameters);

                }
            }
        }


        /// <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);

                    try
                    {
                        ushort inputPortAddress = 5;
                        ushort numberRegisters = 1;

                        ushort[] data = modbus.ReadHoldingRegisters_03(slaveAddr, inputPortAddress, numberRegisters);
                        if (data != null && data.Count() > 0)
                        {
                            result = Convert.ToString(data[0], 2).PadLeft(12, '0');//转2进制,然后12位补零;
                        }
                    }
                    catch (Exception)
                    {

                    }


                }
            }
            return result;
        }

        /// <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);

                    modbus.Opcoded_MD(slaveAddr);

                }
            }
        }

        /// <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);

                    modbus.Opcoded_ME(slaveAddr);

                }
            }
        }



        




        #endregion

    }
}

支持串口通讯,也支持网口通讯。先讲讲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
0E  10 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();

modbusHelper.ConnectForTCP("IP", "端口")

// 发送数据到硬件
//00 00 00 00 00 06 01 05 00 00 ff 00 开
byte[] arr = new byte[12];
arr[0] = 0x00;
arr[1] = 0x00;
arr[2] = 0x00;
arr[3] = 0x00;
arr[4] = 0x00;
arr[5] = 0x06;
arr[6] = 0x01;
arr[7] = 0x05;
arr[8] = 0x00;
arr[9] = 0x00;
arr[10] = 0xff;
arr[11] = 0x00;
modbusHelper.SendTCPByte(arr);
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);

      //从站地址
      byte slaveAddr = 1;
    com6_modbusHelper.Stepping_SetAccelerationSpeedDeceleration(slaveAddr, "8", "8", "6");//设置加速度减速度速度
int[] arr=com6_modbusHelper.Stepping_Read_AC_DE_VE(slaveAddr);//读取指定电机的加速度减速度速度
//比如一个电机走一段路,从0米处运转到时速100码的过程是加速度,然后按照速度120码均速前进到终点,再终点前按照减速度逐渐停止到0码是减速度。
com6_modbusHelper.Stepping_Distance(slaveAddr, 10000);//1w是脉冲数,一般绝大部分电机都是1w脉冲一圈, -1w就是反转1圈
com6_modbusHelper.Stepping_FL(slaveAddr, "同DI脉冲数");//一般设置了DI后在执行FL,电机才会运行
com6_modbusHelper.Stepping_SK(slaveAddr);//强制停止电机当前的运转
com6_modbusHelper.Stepping_AR(slaveAddr);//报警清除,一般电机运行到限位处或者负载过大,驱动器会拒绝继续执行操作命令,需要先清除报警
com6_modbusHelper.Stepping_SO(slaveAddr, 1, "L"); //电机的Y_口开启
com6_modbusHelper.Stepping_SO(slaveAddr, 1, "H"); //电机的Y_口关闭
com6_modbusHelper.Stepping_IS(slaveAddr)//根据这个从站地址读取这个电机的全部输出口,0开1关
com6_modbusHelper.Stepping_MT(slaveAddr, number);//设置步进电机运转时同时输出 1 支持 0 不支持,鸣志步进电机支持此指令,伺服电机不支持
}

接下来说鸣志伺服电机的指令和控制代码

在 #region Servo_Moons  endregion,这个折叠标签里

从上到下依次是常用的十几个命令如下:

设置加减速,减速度,速度
读取伺服电机加速度减速度速度
设置距离 数值1-100/圈
设置距离 脉冲数
电机旋转运行到指定距离 (执行相对移动命令,移动距离和方向来自最后一个DI命令 按给定长度进至距离)
强制停止
报警清除
Y口输出  L 0x4C H 0x48
X口输入 ,0触碰1断开
伺服使能
伺服使能关闭

伺服和步进的区别在于,伺服是闭环控制自带编码器,步进是开环控制没有数据反馈容易丢步。伺服更精准更贵。

在软件层面,步进电机通过写入简单明了的字符串指令轻易控制,但是同品牌的伺服电机要写地址操作起来更复杂

调用方式如下:

     private static ThreadModbusHelper modbus_com5 = new ThreadModbusHelper();//在这里控制伺服全部使用多线程安全帮助类

      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);//伺服使能开启,在使用伺服电机需要先伺服使能才能使用其功能



 

接下来说华庆军继电器模块的指令和控制代码

示例如下:

// 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[0], 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 ON
01 05 00 60 00 00 CD D4  , 指定从站,伺服使能关闭  0060线圈写入0000 off


01 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 14

06 寄存器
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=500
01 06 46 10 00 64 设置a0=100
01 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实拍

 

 祝各位在工控/自动化的道路越走越舒坦

标签:string,伺服电机,ushort,public,modbus,串口,byte,鸣志,slaveAddr
From: https://www.cnblogs.com/Gao1234/p/18096175

相关文章

  • (11.1)iic串口读写EEPROM实验:EEPROM介绍
    一、EEPROM简介EEPROM(ElectricallyErasableProgrammableReadOnlyMemory),带电可擦除可编程只读存储器,是一种掉电后数据不丢失的非易失性存储器,用户可以通过高于普通电压的作用来擦除和编程(重写)非易失性存储器主要包括:EEPROM:以字节为单位改写;结构复杂,成本高;存储......
  • WiFi串口服务器与工业路由器:局域网应用的协同之力
    在工业物联网(IIoT)迅猛发展的当下,局域网(LAN)作为连接工业设备与数据中心的桥梁,其重要性日益凸显。WiFi串口服务器与工业路由器作为局域网中的关键组件,以其独特的性能和功能,为传统行业的数字化转型提供了强有力的支持。本文将深入探讨WiFi串口服务器与工业路由器如何协同工作,以实现......
  • STM32_HAL_串口IAP
    1.IAP简介        IAP(InApplicationProgramming)即在应用编程。STM32可以通过设置MSP的方式从不同的地址启动:包括Flash地址、RAM地址等,在默认方式下,我们的嵌入式程序是以连续二进制的方式烧录到STM32的可寻址Flash区域上的。如果我们用的Flash容量大......
  • STM32F4平台使用SerialPort串口库教程
    这一期教程的内容主要是在STM32F4平台使用SerialPort串口库和上位机通信,上位机(例如串口调试助手)向下位机发送字符串,下位机接收到字符串后,经过加工处理再发送给上位机。SerialPort串口库使用C++编写,用户只需在外部实现4个中断回调函数即可完成串口DMA收发,然后使用read函数读......
  • 【STM32】串口不定长接收 保姆级教程
    为什么要使用串口不定长接收正常的接收函数HAL_UART_Receive(UART_HandleTypeDef*huart,uint8_t*pData,uint16_tSize,uint32_tTimeout)huart:句柄pData:盛放接收数据的变量Size:接收数据的大小中断接收函数HAL_UART_Receive_IT(UART_HandleTypeDef*huart,uint......
  • Python串口控制MS伺服电机(多圈角度)
    """多圈电机转动模式1"""defdecimal_to_hex_bytes(decimal_number,byte_size):#检查字节大小参数的有效性valid_sizes={"int8":1,"int16":2,"int32":4,"int64":8}ifbyte_sizenotinval......
  • qt一个在线程管理的串口
    qt一个在线程管理的串口 #include<QObject>#include<QSerialPort>#include<QSerialPortInfo>#include<QDebug>#include<QFile>#include<QTextStream>#include<QThread>#include<QTimer>#include<QEventLoop>......
  • C#串口通讯 源码Demo
    在C#中进行串口通讯主要涉及到以下几个步骤:引入命名空间usingSystem.IO.Ports;创建SerialPort对象SerialPortport=newSerialPort();设置串口属性//设置串口名:port.PortName="COM1";//设置波特率:port.BaudRate=9600;//设置校验位:port.Parity=Parity.None;//......
  • Mobexterm 串口换行错误
    问题描述使用Mobexterm连接串口时,在回显中存在换行错误问题,严重影响阅读串口数据。解决方法在左侧操作窗口中右击发生换行错误问题的窗口,比如我需要右击COM9右击后,在下拉列表中选择Editsession在该窗口下方三个选择项中,选择Terminalsettings,继续选择Expor......
  • QT使用串口通信点击主界面通信阻塞
    1.问题导入在使用QT进行串口上位机开发时遇到鼠标点击主界面导致串口接收数据阻塞,无法继续在QT控件中更新接收到的数据。2.解决办法使用多线程:将串口通信放入子线程中。`QSerialPort*UARTDevide;//串口UART*newUART;//串口类QThreadUARTThred;//串口线程newUART->mo......