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

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

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

  1. STM32与匿名上位机通信——使用串口DMA实现

    背景:匿名上位机功能强大,这里想要采用匿名上位机输出一些调试信息,以波形的形式显示,方便观察和调试. 平台: 硬件:STM32F405RGT6 通信:2.4G zigbee无线串口收发模块 CC253 ...

  2. .net全栈开发-c#面向对象与工控自动化分拣上位机

    一.前言 开始做了两年web.期间也整了一段时间winform.后来做了两年工控上位机,也就是做工控这两年发现机器跟面向对象真是如此贴切,也是我从处理数据和流程的思维转变为面向对象思维的开始.这对我后 ...

  3. 上位机开发之三菱FX3U以太网通信实践

    上次跟大家介绍了一下上位机与三菱Q系列PLC通信的案例,大家可以通过点击这篇文章:上位机开发之三菱Q系列PLC通信实践(←戳这里) 今天以三菱FX3U PLC为例,跟大家介绍一下,如何实现上位机与其之 ...

  4. 上位机C#通过OPCUA和西门子PLC通信

    写在前面: 很多人在学习OPCUA的时候,有个非常苦恼的问题,就是没有OPCUA服务器的环境,这时候,有些人可能会想到通过类似于KepServer这样的软件来实现.那么,有没有一种方式,实现快速搭建O ...

  5. 上位机开发之西门子PLC-S7通信实践

    写在前面: 就目前而言,在中国的工控市场上,西门子仍然占了很大的份额,因此对于上位机开发而言,经常会存在需要与西门子PLC进行通信的情况.然后对于西门子PLC来说,通信方式有很多,下面简单列举一下: ...

  6. 上位机面试必备——TCP通信灵魂二十问【下】

    上篇文章跟大家介绍了TCP通信常见的前10个面试题,没看过的小伙伴可以点击下方链接进行查看: 上位机面试必备——TCP通信灵魂二十问[上] 今天就后面的10个面试题接着做下说明:欢迎关注[dotNet ...

  7. 【C#上位机必看】你们要的Iot物联网项目来了

    新阁教育喜科堂不得不说,工业圈是个比较奇怪的圈子,各种各样的项目需求都有,有的人就希望价格低,功能实现即可,有的人又不在乎价格,就要界面好看.最近有一个小伙伴又提出了这样的需求,用最简单的方式,最低的 ...

  8. 【新阁教育】穷学上位机系列——搭建STEP7仿真环境

    经常有学员问我,学习上位机要不要买PLC硬件? 我一般的回答是:富则自行购买,穷则搭建仿真. PLC硬件,对于学习上位机来说,是锦上添花的事,经济条件允许,有则更好,条件不允许,我们也可以通过搭建仿真 ...

  9. C#做一个简单的进行串口通信的上位机

    C#做一个简单的进行串口通信的上位机   1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是 ...

  10. 2018最新mfc作为上位机接收硬件端USB或串口数据显示成图片 解决串口接收数据丢字节丢包问题

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9490616.html 本文用的是VS2013MFC写串口数据接收: 第一步:首先建立一个MFC ...

随机推荐

  1. WPF开发随笔收录-获取程序专有内存

    分享一个C#获取程序当前所占用的内存大小的方法,实测跟任务管理器上的内存值一样 /// <summary> /// 性能计数器组件类 /// </summary> privat ...

  2. etcd 历史版本回溯的方法

    在使用 etcd 作为配置存储或者其他的场景,如果因为误操作对其中 key 的值进行了修改,如果想要找回原来的值,可以利用 etcd 的版本机制进行回溯找回以前的值.在具体操作之前,我们首先获取一下 ...

  3. 大数据ETL开发之图解Kettle工具入门到精通(经典转载)

    大数据ETL开发之图解Kettle工具(入门到精通) 置顶 袁袁袁袁满 文章目录 第0章 ETL简介 第1章 Kettle简介 1.1 Kettle是什么 1.2 Kettle的两种设计 1.3 Ke ...

  4. flutter 移动应用程序中打开URL

    url_launcher: ^6.2.5   在Flutter中,url_launcher库是用于在移动应用程序中打开URL的常用工具.它允许你通过调用系统的浏览器或其他应用程序来打开指定的URL,比 ...

  5. 码住!Flink Contributor 速成指南

    简介: 不管初衷是什么,Flink 都非常欢迎大家一起建设和完善社区.在开始具体的贡献步骤之前,我们先简要介绍一下参与贡献的几种途径,以及 Clarify 关于开源贡献的一些固有印象. 作者:伍翀(云 ...

  6. 阿里巴巴开源大规模稀疏模型训练/预测引擎DeepRec

    ​简介:经历6年时间,在各团队的努力下,阿里巴巴集团大规模稀疏模型训练/预测引擎DeepRec正式对外开源,助力开发者提升稀疏模型训练性能和效果. ​ 作者 | 烟秋 来源 | 阿里技术公众号 经历6 ...

  7. 开课啦 dubbo-go 微服务升级实战

    简介: 杭州开课啦教育科技有限公司是一家致力于为中小学生提供学习辅导的在线教育公司,目前公司后端服务基础设施主要依托于阿里云原生,其中包含计算.网络.存储以及 Kubernetes 服务. 技术选型背 ...

  8. QUIC技术创新 让视频和图片分发再提速

    ​简介:在1月12日的「阿里云CDN产品发布会-新一代传输协议QUIC让CDN更快一步」之上,阿里云技术专家淮叶分享了QUIC技术及其应用落地实践,内容包含:QUIC协议介绍.相比TCP有哪些优势.应 ...

  9. [ML] 详解 ChatGLM-webui 的启动使用与 ChatGLM-6B 常见问题

      1. ChatGLM-webui 总共支持以下几个命令选项: 2. 以 windows 为例,在 PowerShell 里运行命令: # 安装依赖 pip install torch==1.13. ...

  10. [PHP] Laravel 的 503 Service Unavailable 模板提示的来源

    当我们看到 Laravel 的 503 样式的模板时,是启用维护模式的时候的提示(php artisan down). 开启访问运行 php artisan up. Refer:Laravel 503 ...