Unity中基于Modbus通信协议实现的RTU串口通信

2 篇文章 0 订阅

关于 Modbus 通信,这里不再多赘述,网上有很多教程讲的很详细,对于通信协议,推荐这个文章,Modbus+RTU标准通信协议可以较为详细的了解具体的通信协议
以下为我的代码实现,注释很详细,相信大家都能看明白

需要注意的是,System.IO.Ports命名空间需要再C# 4.X才能运行,报错需要再Play Setting中自行更改

5.5.0202 代码段更新

using UnityEngine;
using System.IO.Ports;
using System;
using System.Collections.Generic;
using System.Threading;

/// <summary>
/// 串口通信类,挂载至任意对象下即可使用,建议(Main Camera)
/// </summary>
public class PortControl : MonoBehaviour
{
    #region 定义串口属性
    //定义基本信息
    public string portName;//串口名
    public int baudRate = 9600;//波特率
    public Parity parity = Parity.None;//效验位
    public int dataBits = 8;//数据位
    public StopBits stopBits = StopBits.One;//停止位
    protected SerialPort sp = null;
    #endregion

    protected Thread dataReceiveThread;
    protected Thread dataProcessorThread;

    public List<byte> receive = new List<byte>();   //接收到的所有消息
    protected List<byte> message = new List<byte>();  //接收的一条消息
    protected static string[] text;                          //处理后的消息
    protected bool sendState = false;                 //接收状态
    protected bool readTextState = false;             //读取状态
    public bool ReadTextState
    {
        get
        {
            return readTextState;
        }
    }
    public bool SendState
    {
        get
        {
            return sendState;
        }
    }

    private void Awake()
    {
        dataReceiveThread = new Thread(new ThreadStart(DataReceiveFunction));
        dataProcessorThread = new Thread(new ThreadStart(DataProcessor));
    }
    void Update()
    {
    }



    #region 创建串口,并打开串口
    public virtual void OpenPort()
    {
        //创建串口
        sp = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
        sp.ReadTimeout = 400;
        try
        {
            sp.Open();
            dataReceiveThread.Start();
            dataProcessorThread.Start();
            sendState = true;
            Debug.Log("串口开启!!!!");
        }
        catch (Exception ex)
        {
            Debug.Log(ex.Message);
        }
    }
    #endregion



    #region 程序退出时关闭串口
    private void OnApplicationQuit()
    {
        ClosePort();
    }
    public void ClosePort()
    {
        try
        {
            sp.Close();
            dataReceiveThread.Abort();
            dataProcessorThread.Abort();
        }
        catch (Exception ex)
        {
            Debug.Log(ex.Message);
        }
    }
    #endregion

    /// <summary>
    /// 数据处理
    /// </summary>
    /// <returns></returns>
    public virtual void DataProcessor()
    {
        while (true)
        {
            if (receive.Count > 0)
            {
                if (receive[0] == id && readTextState == false)
                {
                    if (receive.Count >= 3)
                    {
                        int index = 0;
                        message.Add(receive[index++]);
                        message.Add(receive[index++]);
                        message.Add(receive[index++]);
                        switch (message[1])
                        {
                            case 0x01:
                            case 0x03:
                                int length = Convert.ToInt32(Convert.ToString(message[2], 10));
                                if (receive.Count >= length + 5)
                                {
                                    while (index < length + 3)
                                    {
                                        message.Add(receive[index++]);
                                    }
                                }
                                else
                                {
                                    goto case 0x03;
                                }
                                break;
                            case 0x05:
                                if (receive.Count >= 7)
                                {
                                    message.Add(receive[index++]);
                                    message.Add(receive[index++]);
                                }
                                else
                                {
                                    goto case 0x05;
                                }
                                break;
                            case 0x06:
                            case 0x0f:
                            case 0x10:
                                if (receive.Count >= 8)
                                {
                                    message.Add(receive[index++]);
                                    message.Add(receive[index++]);
                                    message.Add(receive[index++]);
                                }
                                else
                                {
                                    goto case 0x10;
                                }
                                break;
                            case 0x81:
                            case 0x83:
                            case 0x85:
                            case 0x86:
                            case 0x8F:
                            case 0x90:
                            default:
                                if (receive.Count >= 5)
                                {
                                    message.Add(receive[index++]);
                                    message.Add(receive[index++]);
                                    message.Add(receive[index++]);
                                }
                                else
                                {
                                    goto case 0x90;
                                }
                                break;
                        }
                        message.Add(receive[index++]);
                        message.Add(receive[index++]);
                        receive.RemoveRange(0, message.Count);
                        byte[] data = new byte[message.Count - 2];
                        message.CopyTo(0, data, 0, data.Length);
                        uint crc16 = Crc16_Modbus(data, (uint)data.Length);
                        byte crcH = Convert.ToByte(crc16 & 0xFF);
                        byte crcL = Convert.ToByte(crc16 / 0x100);
                        //crc验证,如果通过,代表收到的数据无误,使用text接收,不通过就自动重发
                        if ((message[message.Count - 2].Equals(crcH) && message[message.Count - 1].Equals(crcL)) || (message[message.Count - 1].Equals(crcH) && message[message.Count - 2].Equals(crcL)))
                        {
                            text = new string[message.Count];
                            lock (text)
                            {
                                for (int i = 0; i < message.Count; i++)
                                {
                                    text[i] = Convert.ToString(message[i], 10);
                                }
                                readTextState = true;
                                sendState = true;
                            }
                        }
                        else
                        {
                            WriteData(sendData);
                        }
                        message.Clear();
                    }
                }
                else
                {
                    receive.RemoveAt(0);            //*移除多余的数据*
                }
            }
            Thread.Sleep(20);
        }
    }
    /// <summary>
    /// 为外界提供获取数据的方法
    /// </summary>
    /// <returns>获取是否成功</returns>
    public bool GetData(out string[] result)
    {
        try
        {
            if (!readTextState) {
                result = null;
                return false;
            }
            result = new string[text.Length];
            for (int i = 0; i < text.Length; i++)
            {
                result[i] = text[i];
            }
            lock(text) {
                text = null;
            }
            readTextState = false;
        }
        catch
        {
            result = null;
            return false;
        }
        return true;
    }

    #region 接收数据
    private void DataReceiveFunction()
    {
        byte[] buffer = new byte[1];
        int bytes = 0;
        while (true)
        {
            lock (receive)
            {
                if (sp != null && sp.IsOpen)
                {
                    try
                    {
                        int tmp = sp.ReadBufferSize;
                        for(int i = 0; i < tmp; i++) {
                            bytes = sp.Read(buffer, 0, 1);//接收字节
                            if(bytes == 0) {
                                continue;
                            }
                            else {
                                receive.Add(buffer[0]);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType() != typeof(ThreadAbortException))
                        {
                        }
                    }
                }
            }
            Thread.Sleep(10);

        }
    }
    #endregion



    #region 发送数据
    private byte[] sendData;                                    //上一次发送的数据,暂存,用于通信失败后自动重发
    protected virtual void WriteData(byte[] dataStr)
    {
        if (sp.IsOpen)
        {
            sendState = false;
            readTextState = false;
            text = null;
            sp.Write(dataStr, 0, dataStr.Length);
        }
    }
    /// <summary>
    /// 以Modbus协议发送数据
    /// </summary>
    /// <param name="id">从机地址</param>
    /// <param name="fc">功能码</param>
    /// <param name="addrH">寄存器地址高位或起始地址高位</param>
    /// <param name="addrL">寄存器地址低位或起始地址低位</param>
    /// <param name="dinumH">目标地址高位或线圈数高位或地址数高位</param>
    /// <param name="dinumL">目标地址低位或线圈数低位或地址数低位</param>
    /// <param name="value">传输的数据,可空,仅更新数据时用,首位表示数据长度</param>
    public void SendData(byte id, byte fc, byte addrH, byte addrL, byte dinumH, byte dinumL, params byte[] value)
    {
        sendData = new byte[0];
        uint crc16;
        switch (fc)
        {
            case 0x01:
            case 0x03:
            case 0x05:
            case 0x06:
                sendData = new byte[8];
                sendData[0] = id; //从机地址
                sendData[1] = fc; //功能码
                sendData[2] = addrH; //寄存器地址高字节
                sendData[3] = addrL; //寄存器地址低字节
                sendData[4] = dinumH; //读取的寄存器数高字节
                sendData[5] = dinumL;  //寄存器数低字节
                crc16 = Crc16_Modbus(sendData, 6);
                sendData[6] = Convert.ToByte(crc16 & 0xFF);
                sendData[7] = Convert.ToByte(crc16 / 0x100);
                break;
            case 0x0F:
                sendData = new byte[8 + value.Length];
                sendData[0] = id; //'站号
                sendData[1] = fc; //'功能码
                sendData[2] = addrH; //'
                sendData[3] = addrL; //'起始地址
                sendData[4] = dinumH;
                sendData[5] = dinumL; //'线圈数量
                sendData[6] = value[0]; //字节数
                for (int i = 1; i < value.Length; i++)
                {
                    sendData[6 + i] = value[i];//输入值
                }
                crc16 = Crc16_Modbus(sendData, (uint)(5 + value.Length));
                sendData[6 + value.Length] = Convert.ToByte(crc16 & 0xFF);
                sendData[7 + value.Length] = Convert.ToByte(crc16 / 0x100);
                break;
            case 0x10:
                sendData = new byte[8 + value.Length];
                sendData[0] = id; //'站号
                sendData[1] = fc; //'功能码
                sendData[2] = addrH; //'
                sendData[3] = addrL; //'起始地址
                sendData[4] = dinumH;
                sendData[5] = dinumL; //'
                sendData[6] = value[0]; //字节数
                for (int i = 1; i < value.Length; i++)
                {
                    sendData[6 + i] = value[i++];//输入值
                    sendData[6 + i] = value[i];
                }
                crc16 = Crc16_Modbus(sendData, (uint)(5 + value.Length));
                sendData[6 + value.Length] = Convert.ToByte(crc16 & 0xFF);
                sendData[7 + value.Length] = Convert.ToByte(crc16 / 0x100);
                break;
            default:
                break;
        }
        this.id = sendData[0];
        WriteData(sendData);
    }
    #endregion
    /// <summary>
    /// crc验证
    /// </summary>
    /// <returns></returns>
    private uint Crc16_Modbus(byte[] modebusdata, uint length) //length为modbusdata的长度
    {
        uint i, j;
        uint crc16 = 0xFFFF;

        for (i = 0; i < length; i++)
        {
            crc16 ^= modebusdata[i];  //CRC = BYTE xor CRC(^=取反)
            for (j = 0; j < 8; j++)
            {
                if ((crc16 & 0x01) == 1)  //如果CRC最后一位为1,右移一位后carry=1,则将CRC右移一位后,再与POLY16=0xA001进行xor运算
                {
                    crc16 = (crc16 >> 1) ^ 0xA001;
                }
                else
                {
                    crc16 = crc16 >> 1;   //如果CRC最后一位为0,则只将CRC右移一位
                }
            }
        }

        return crc16;
    }

    #region 发送协议封装
    public byte id = 0x01;      //从机地址
    public byte Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }
    /// <summary>
    /// 读可读写数字量寄存器
    /// </summary>
    /// <param name="start">起始寄存器地址</param>
    /// <param name="numb">读取的寄存器数</param>
    public void ReadDR0x01(ushort start, ushort numb)
    {
        byte startH, startL, numbH, numbL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(numb, out numbH, out numbL);
        SendData(id, 0x01, startH, startL, numbH, numbL);
    }
    /// <summary>
    /// 写可读写数字量寄存器
    /// </summary>
    /// <param name="start">需下置的寄存器地址</param>
    /// <param name="status">下置的数据</param>
    public void WriteDR0x05(ushort start, ushort status)
    {
        byte startH, startL, statusH, statusL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(status, out statusH, out statusL);
        SendData(id, 0x05, startH, startL, statusH, statusL);
    }
    /// <summary>
    /// 读可读写模拟量寄存器
    /// </summary>
    /// <param name="start">起始寄存器地址</param>
    /// <param name="numb">读取的寄存器数</param>
    public void ReadAR0x03(ushort start, ushort numb)
    {
        byte startH, startL, numbH, numbL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(numb, out numbH, out numbL);
        SendData(id, 0x03, startH, startL, numbH, numbL);
    }
    /// <summary>
    /// 写可读写模拟量寄存器
    /// </summary>
    /// <param name="start">需下置的寄存器地址</param>
    /// <param name="status">下置的数据</param>
    public void WriteAR0x06(ushort start, ushort status)
    {
        byte startH, startL, statusH, statusL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(status, out statusH, out statusL);
        SendData(id, 0x06, startH, startL, statusH, statusL);
    }
    /// <summary>
    /// 写多个可读写数字量寄存器
    /// </summary>
    /// <param name="start">修改起始寄存器地址</param>
    /// <param name="numb">修改的寄存器数</param>
    /// <param name="value">修改的值,需注意该数组长度需正确</param>
    public void WriteDR0x0F(ushort start, ushort numb, params byte[] value)
    {
        byte startH, startL, numbH, numbL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(numb, out numbH, out numbL);
        ushort lenght;
        lenght = (ushort)(numb >> 3);
        if ((numb & 0x07) != 0)
            lenght++;
        if (lenght != value.Length)
        {
            Debug.Log("输入有误,重新输入");
            return;
        }
        else
        {
            lenght++;
            byte[] valu = new byte[lenght];
            valu[0] = (byte)(lenght - 1);
            for (ushort i = 1; i <= lenght; i++)
            {
                valu[i] = value[i - 1];
            }
            SendData(id, 0x0F, startH, startL, numbH, numbL, valu);
        }
    }
    /// <summary>
    /// 写多个可读写模拟量寄存器
    /// </summary>
    /// <param name="start">修改起始寄存器地址</param>
    /// <param name="numb">修改的寄存器数</param>
    /// <param name="value">修改的值,需注意该数组长度需正确</param>
    public void WriteAR0x10(ushort start, ushort numb, params byte[] value)
    {
        byte startH, startL, numbH, numbL;
        UShortToTwoByte(start, out startH, out startL);
        UShortToTwoByte(numb, out numbH, out numbL);
        ushort lenght;
        lenght = (ushort)(numb << 1);
        if (lenght != value.Length)
        {
            Debug.Log("输入有误,重新输入");
            return;
        }
        else
        {
            lenght++;
            byte[] valu = new byte[lenght];
            valu[0] = (byte)(lenght - 1);
            for (ushort i = 1; i <= lenght; i++)
            {
                valu[i] = value[i - 1];
            }
            SendData(id, 0x10, startH, startL, numbH, numbL, valu);
        }
    }
    #endregion

    /// <summary>
    /// 16位拆分位两个8位
    /// </summary>
    private void UShortToTwoByte(ushort s, out byte high, out byte low)
    {
        high = (byte)((s >> 8) & 0xff); //高8位
        low = (byte)(s & 0xff); //低8位
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值