c#温室监控系统

上位机采用串口与下位机信,使用modbus通信协议控制和采集数据

可以参考一下(直接翻到modbus的章节):

MODBUS技术协议(第三章).pdf · chuan/临时的 - 码云 - 开源中国 (gitee.com)

首先实现通信用的modbus类

代码先实现了3个基础的功能 读输出状态、读保存寄存器和强置单线圈

  public class Modbus
    {
        #region 基础设置
        private SerialPort serialPort;
        private byte[]? ReceivedData;
        private bool ReceiveFlag=false;
        public Modbus(string portName, int baudRate, Parity parity,
            StopBits stopBits, int dataBits)
        {
            serialPort = new SerialPort();
            serialPort.PortName = portName;
            serialPort.BaudRate = baudRate;
            serialPort.Parity = parity;
            serialPort.StopBits = stopBits;
            serialPort.DataBits = dataBits;
        }

        public void OpenModbusConnet()
        {
            try
            {
                serialPort.Open();
                serialPort.DataReceived += SerialPort_DataReceived;
            }
            catch (Exception ex)
            {
                throw new Exception("串口打开失败:" + ex.Message);
            }
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int ReceivedDataLenth = serialPort.BytesToRead;
            ReceivedData = new byte[ReceivedDataLenth];
            serialPort.Read(ReceivedData, 0, ReceivedDataLenth);
            ReceiveFlag=true;
        }
        ~Modbus()
        {
            if (serialPort is not null)
            {
                if (serialPort.IsOpen)
                    serialPort.Close();
            }
        }
        #endregion
        //等待数据返回,超过200毫秒退出等待
        private void awiteReceive()
        {
            ReceiveFlag= false;
            DateTime dateTime = DateTime.Now;
            while (ReceiveFlag == false)
            {
               var t= DateTime.Now-dateTime;
                if(t.Milliseconds>200)
                    ReceiveFlag= true;
            }
        }
        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="cmd"></param>
        /// <exception cref="Exception"></exception>
        private void SendCommand(byte[] cmd)
        {
            if (!serialPort.IsOpen)
            {
                throw new Exception("modbus连接未打开");
                return;
            }
            try
            {
                serialPort.Write(cmd,0,cmd.Length);
                awiteReceive();
            }
            catch (Exception ex)
            {

                throw new Exception("发送失败:"+ex.Message);
            }
        }

        #region 读输出状态 功能码0x01
        /// <summary>
        /// 读输出状态 功能码0x01
        /// </summary>
        /// <param name="SalavAddr"></param>
        /// <param name="StartAddr"></param>
        /// <param name="coils"></param>
        /// <returns>返回接收到的报文 <see langword="byte[]"/></returns>
        public byte[] ReadCoilsStatus(byte SalavAddr, int StartAddr, int coils)
        {
            byte[] Cmd= FillMessage(SalavAddr,0x01, StartAddr, coils);
            try
            {
                SendCommand(Cmd);
            }
            catch (Exception ex)
            {

                throw ex;
            }
           
            if (ReceivedData is not null && ReceivedData.Length is >3 )
            {
                if (ReceivedData[1] == (byte)0x01)
                {
                    if (verifyReciveCRC(ReceivedData))
                    {
                        return ReceivedData;
                    }
                }
            }
            return null!;
        }
        #endregion

        #region 读取保持型(保存)寄存器 功能码0x03
        /// <summary>
        /// 读取保持型寄存器 功能码0x03
        /// </summary>
        /// <param name="SalavAddr"></param>
        /// <param name="StartAddr"></param>
        /// <param name="RegisterCount"></param>
        /// <returns>返回接收到的报文<see langword="byte[]"/></returns>
        public byte[] ReadRegister(byte SalavAddr, int StartAddr, int RegisterCount)
        {
            byte[] Cmd = FillMessage(SalavAddr, 0x03, StartAddr, RegisterCount);
            try
            {
                SendCommand(Cmd);
            }
            catch (Exception ex)
            {

                  throw new Exception(ex.Message);
            }
            if (ReceivedData is not null && ReceivedData.Length is > 3)
            {
                if (ReceivedData[1] == (byte)0x03)
                {
                    if (verifyReciveCRC(ReceivedData))
                    {
                        return ReceivedData;
                    }
                }
            }
            return null!;
        }
        #endregion

        #region  强置单线圈 功能码0x05
        public enum ColiStatus
            { 
              ON=0xFF,
              OFF=0x00
            }
        /// <summary>
        /// 设置当个线圈的状态为ON高电平或低电平OFF
        /// </summary>
        /// <param name="SalavAddr"></param>
        /// <param name="CoilAddr"></param>
        /// <param name="ONOFF">断通标志=FF00,置线圈 ON(1)
        /// 断通标志=0000,置线圈 OFF(0)</param>
        /// <returns>返回接收到的报文<see langword="byte[]"/></returns>
        public byte[] ForceSingleCoil(byte SalavAddr, int CoilAddr, ColiStatus ONOFF)
        {
            byte[] Cmd = FillMessage(SalavAddr, 0x05, CoilAddr,
                ONOFF== ColiStatus.ON ?65280:0000);//65280 ==0xFF00
            try
            {
                SendCommand(Cmd);
            }
            catch (Exception ex)
            {

                throw ex;
            }
            if (ReceivedData is not null && ReceivedData.Length is > 3)
            {
                if (ReceivedData[1] == (byte)0x05)
                {
                    if (verifyReciveCRC(ReceivedData))
                    {
                        return ReceivedData;
                    }
                }
            }
            return null!;
        }
        #endregion

        /// <summary>
        /// 填充8位数据报文
        /// </summary>
        /// <param name="SalavAddr">从机地址</param>
        /// <param name="FCode">功能码</param>
        /// <param name="StartAddr">起始地址</param>
        /// <param name="dataBit">2位数据位</param>
        /// <returns>填充好的报文</returns>
        private byte[] FillMessage(byte SalavAddr, byte FCode, int StartAddr, int dataBit)
        {
            byte[] message = new byte[8];
            message[0] = SalavAddr;
            message[1] = FCode;
            message[2] = (byte)((StartAddr - StartAddr % 256) / 256);//起始地址高位
            message[3] = (byte)(StartAddr % 256);//起始地址地位
            message[4] = (byte)((dataBit - dataBit % 256) / 256);
            message[5] = (byte)(dataBit % 256);
            var crc = CRC16(message);
            message[6] = crc[0];
            message[7] = crc[1];

            return message;
        }

        [DebuggerHidden]
        public byte[] CRC16(byte[] byteData)
        {
            byte[] CRC = new byte[2];
            UInt16 wCrc = 0xFFFF;
            for (int i = 0; i < byteData.Length - 2; i++)
            {
                wCrc ^= Convert.ToUInt16(byteData[i]);
                for (int j = 0; j < 8; j++)
                {
                    if ((wCrc & 0x0001) == 1)
                    {
                        wCrc >>= 1;
                        wCrc ^= 0xA001;
                    }
                    else
                    {
                        wCrc >>= 1;
                    }
                }
            }
            CRC[1] = (byte)((wCrc & 0xFF00) >> 8);
            CRC[0] = (byte)(wCrc & 0x00FF);
            return CRC;
        }
        /// <summary>
        /// 校验接收到的数据是否正确
        /// </summary>
        /// <param name="byteData"></param>
        /// <returns>正确返回true</returns>
        [DebuggerHidden]
        private bool verifyReciveCRC(byte[] byteData)
        {
            var resCrc = CRC16(byteData);
            if (resCrc[0] == byteData[byteData.Length - 2] &&
                  resCrc[1] == byteData[byteData.Length - 1])
                return true;
            else return false;
        }
    }

如果需要其他可能码基本都是复制粘贴改一下 if (ReceivedData[1] == (byte)0x05) 这个功能码就好

使用modbus类要先实例化一个,然后调用打开连接方法,就可以正常调用(使用)功能方法了

可以使用仿真软件进行测试

试了一下是能正常使用并返回正确报文

Modbus拓展方法:

再编写一个类,为返回的报文提供数据解析

 public static class ModbusExtension
    {
        /// <summary>
        /// 提取读线圈返回的报文数据
        /// </summary>
        /// <param name="modbus"></param>
        /// <param name="bytes"></param>
        /// <returns><see langword="List<bool>"/> 下标0为起始地址线圈 </returns>
        public static List<bool> BytesOfReadColisToBoolList(this Modbus modbus, byte[] bytes) 
        {
            List<bool> coilsStatas = new List<bool>();
            if (bytes is not null && bytes.Length >= 5)
            {
                if (bytes[1] == (byte)0x01)
                {
                    for (int i = 0; i < Convert.ToInt32(bytes[2]); i++)
                    {
                        for (int j = 0; j < 8; j++)
                        {
                            coilsStatas.Add((byte)((byte)((byte)(bytes[i + 3] >> j) << 7) >> 7) == (byte)0x01 ? true : false);
                        }
                    }
                }
            }
            return coilsStatas;
        }
        /// <summary>
        /// 提取读寄存器返回的报文数据
        /// </summary>
        /// <param name="modbus"></param>
        /// <param name="bytes"></param>
        /// <returns><see langword="int[]"/>从0开始,每一项为一个寄存器的值
        /// error return null</returns>
        public static int[] BytesOfReadKeepRegisterToIntArr(this Modbus modbus,byte[] bytes)
        {
            if (bytes is null || bytes.Length < 3) return null!;
            int dataLenth = (int)bytes[2];
            int[] result = new int[dataLenth / 2];
            int databitStart = 3;
            int RegisterLen = 2;
            int j = 0;
            try
            {
                for (int i = databitStart; i < dataLenth * 2; i += RegisterLen)
                {
                    if (i >= bytes.Length) break;
                    result[j] = bytes[i];
                    result[j] <<= 8;
                    result[j] |= bytes[i + 1];
                    j++;
                }
            }
            catch (Exception)
            {

            }

            return result;
        }
    }

简单测试

使用Winform测试

只有一个窗体代码如下

using CModbus;
using System.IO.Ports;
using Timer = System.Windows.Forms.Timer;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private Modbus? modbus;
        
        public Form1()
        {
            InitializeComponent();
            string[] portName= SerialPort.GetPortNames();
            this.comboBox1.DataSource= portName;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string portname=this.comboBox1.SelectedValue.ToString()!;
            modbus = new Modbus(portname,9600,Parity.None,StopBits.One,8);
            modbus.OpenModbusConnet();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (modbus is not null)
            {
                Timer t = new Timer();
                t.Interval = 1000;
                t.Tick += (_, _) => 
                {
                   var coilsData=  modbus.ReadCoilsStatus(0x01,0000,0008);
                   var str1= coilsData.Select(x => x.ToString("X2")+" ").ToArray();
                    string t1="";
                    string t2 = "";
                    var coilStatus=modbus.BytesOfReadColisToBoolList(coilsData);
                    for (int i = 0; i < str1.Length; i++)
                    {
                        t1 += str1[i];
                    }
                    for (int i = 0; i < coilStatus.Count; i++)
                    {
                        t2 += coilStatus[i].ToString()+"  ";
                    }
                    textBox1.Text= t1;
                    textBox2.Text= t2;
                   //var RegisterData = modbus.ReadRegister(0x01, 0000, 0008);
                    //var str2 = RegisterData.Select(x => x.ToString("X2") + " ").ToArray();
                    //string t3 = "";
                    //string t4 = "";
                    //var RegisterValue = modbus.BytesOfReadKeepRegisterToIntArr(RegisterData);
                    //for (int i = 0; i < str2.Length; i++)
                    //{
                    //    t3 += str2[i];
                    //}
                    //for (int i = 0; i < RegisterValue.Length; i++)
                    //{
                    //    t4 = RegisterValue[i].ToString()+"  ";
                    //}
                    //textBox3.Text = t3;
                    //textBox4.Text = t4;

                };
                t.Start();
              
            }
        }
    }
}

}

读取8个线圈结果如下:

 读寄存器:

 最后简单实现了一下温度采集和一些控制

下位机链接:http://t.csdn.cn/TC52y

最后直接附上源码链接了:(3条消息) 【免费】c#上位机(温室监控系统源码)资源-CSDN文库

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值