关于c#和智能仪表通讯的问题,如何解决?

“startIndex 不能大于字符串长度。Arg_ParamName_Name”
下面是程序,主要是substring截取的开始字符超范围

private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                string[] str = SerialPort.GetPortNames();   //获取连接到电脑的串口号并存进数组
                if (str.Length > 0)
                {
                    serialPort1.PortName = "com5";
                    serialPort1.BaudRate = 9600;
                    serialPort1.DataBits = 8;
                    serialPort1.Parity = Parity.None;
                    serialPort1.StopBits =StopBits.Two;
                    serialPort1.Encoding = Encoding.BigEndianUnicode;
                    serialPort1.Open();
                }
                else
                {
                    MessageBox.Show("当前无串口连接!");
                }
            }
            catch
            {
                MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");
            }
        }
        //向低阻仪发送
        private void timer1_Tick(object sender, EventArgs e)
        {
            char c1 = (char)259;         //01H 03H
            char c2 = (char)0001;        //00H 01H
            char c3 = (char)0014;        //00H 0EH
            char c4 = (char)38350;       //95H CEH
            string OutData = c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString();
            serialPort1.Write(OutData);
        }
        //触发事件,读取低阻仪返回数据
        List<byte> sp_buffer = new List<byte>(4096);    //串口缓存区
        int sp_buffer_max = 4096;   //串口缓存区最大缓存字节数
        byte[] data = new byte[9192]; //用来存放缓冲区的数据流
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            int Byte_len = serialPort1.BytesToRead; //读取缓存的数据长度
            byte[] Rc_byte = new byte[Byte_len];

            serialPort1.Read(Rc_byte, 0, Byte_len);   //将缓存数据存储进字节数组里面

            if (sp_buffer.Count > sp_buffer_max)    //缓存超过字节数 先丢弃前面的字节  
                sp_buffer.RemoveRange(0, sp_buffer_max); //丢弃前面的字节0到sp_buffer_max

            sp_buffer.AddRange(Rc_byte);    //存入缓存区

            
            if (sp_buffer.Count > 4)
            {
                sp_buffer.CopyTo(0, data, 0, sp_buffer.Count);
                this.Invoke(new EventHandler(DisplayText));
            }
                
        }
        string data3;
        string data4;
        string data5;
        string data6;
        string data7;
        private  void DisplayText(object sender,EventArgs e)
        {
            short  data2;
            data7 = data.ToString();
            char[] data1 = data7.ToCharArray();
            for(int i=0;i<data1 .Length;i++)
            {
                 data2 = (short)data1[i];
                 data3 = Convert.ToString(data2, 16); 
            }        
            data3 = data3.Substring(14, 2) + data3.Substring(12,2) + data3.Substring(10,2) + data3.Substring(8,2);
            data4 = data3.Substring(28, 2) + data3.Substring(26,2) + data3.Substring(24,2) + data3.Substring(22,2);
            data5 = data3.Substring(42, 2) + data3.Substring(40,2) + data3.Substring(38,2) + data3.Substring(36,2);
            data6 = data3.Substring(56, 2) + data3.Substring(54,2) + data3.Substring(52,2) + data3.Substring(50,2);
            textBox1.Text = DataChenge(data3).ToString();
            textBox2.Text = DataChenge(data4).ToString();
            textBox3.Text = DataChenge(data5).ToString();
            textBox4.Text = DataChenge(data6).ToString();
        }
        private float DataChenge(string s)
        {
            
            MatchCollection matches = Regex.Matches(s, @"[0-9A-Fa-f]{2}");
            byte[] bytes = new byte[matches.Count];
            for (int i = 0; i < bytes.Length; i++)
                bytes[i] = byte.Parse(matches[i].Value, System.Globalization.NumberStyles.AllowHexSpecifier);
            float m = BitConverter.ToSingle(bytes.Reverse().ToArray(), 0);
            return m;
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox2_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox3_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox4_TextChanged(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            serialPort1.Close();
            Close();
        }
        private void Form1_FormClosing(object sender,FormClosingEventArgs e) 
        {
            if (serialPort1.IsOpen) serialPort1.Close();
        }
    }
}


下面是我用串口助手回读的信息,红框内是需要转成十进制小数显示的数据

img

还有一个问题,我怎么能够不要前两个接收的数据,只要第3个接收数据?

TechWhizKid参考GPT回答:

  • 出现“startIndex不能大于字符串长度。Arg_ParamName_Name”的错误,这个主要是在你用Substring(startIndex, length)的时候,你的startIndex超过了字符串的长度,这就会抛出这个错误。在你的代码中,这个错误可能发生在DisplayText函数内,当你在对data3,data4,data5和data6进行Substring操作时。

你可以先检查字符串的长度是否足够,再调用Substring。比如:

if (data3.Length >= 16)
    data3 = data3.Substring(14, 2) + data3.Substring(12,2) + data3.Substring(10,2) + data3.Substring(8,2);
if (data4.Length >= 32)
    data4 = data4.Substring(28, 2) + data4.Substring(26,2) + data4.Substring(24,2) + data4.Substring(22,2);
// 同样对data5和data6也做如此处理


第二个问题是只想获取第三个接收的数据,可以通过设置一个计数器来实现。你可以在类内部设置一个private int类型的变量,每次接收到数据时递增,当该变量值为3时,处理并显示数据,否则不处理。例如:

private int receiveCount = 0; // 定义类内部变量

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    // 处理数据的代码...

    // 判断是不是第三次接收数据
    receiveCount++;
    if (receiveCount == 3)
    {
        // 处理并显示数据
    }
}


using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

public partial class Form1 : Form
{
    private int receiveCount = 0; // 添加计数器
    private List<byte> sp_buffer = new List<byte>(4096);    //串口缓存区
    private int sp_buffer_max = 4096;   //串口缓存区最大缓存字节数
    private byte[] data = new byte[9192]; //用来存放缓冲区的数据流

    private void Form1_Load(object sender, EventArgs e)
    {
        // ...
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        // ...
    }

    private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        // ...

        // 判断是不是第三次接收数据
        receiveCount++;
        if (receiveCount == 3)
        {
            // 处理并显示数据
            if (sp_buffer.Count > 4)
            {
                sp_buffer.CopyTo(0, data, 0, sp_buffer.Count);
                this.Invoke(new EventHandler(DisplayText));
            }
        }
    }

    private void DisplayText(object sender, EventArgs e)
    {
        short data2;
        string data7 = data.ToString();
        char[] data1 = data7.ToCharArray();
        string data3 = "", data4 = "", data5 = "", data6 = "";

        for (int i = 0; i < data1.Length; i++)
        {
            data2 = (short)data1[i];
            string hexData = Convert.ToString(data2, 16);
            
            if (hexData.Length >= 16)
            {
                data3 = hexData.Substring(14, 2) + hexData.Substring(12, 2) + hexData.Substring(10, 2) + hexData.Substring(8, 2);
            }
            if (hexData.Length >= 32)
            {
                data4 = hexData.Substring(28, 2) + hexData.Substring(26, 2) + hexData.Substring(24, 2) + hexData.Substring(22, 2);
            }
            if (hexData.Length >= 48)
            {
                data5 = hexData.Substring(42, 2) + hexData.Substring(40, 2) + hexData.Substring(38, 2) + hexData.Substring(36, 2);
            }
            if (hexData.Length >= 64)
            {
                data6 = hexData.Substring(56, 2) + hexData.Substring(54, 2) + hexData.Substring(52, 2) + hexData.Substring(50, 2);
            }
        }

        textBox1.Text = DataChenge(data3).ToString();
        textBox2.Text = DataChenge(data4).ToString();
        textBox3.Text = DataChenge(data5).ToString();
        textBox4.Text = DataChenge(data6).ToString();
    }

    private float DataChenge(string s)
    {
        // ...
    }

    // ...

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (serialPort1.IsOpen) serialPort1.Close();
    }
}


关于第一个问题,可以在 substring 前添加一个判断,如果开始截取的索引大于字符串长度,就将其修改为字符串长度减一。修改后的代码如下:

if (data3.Length > 56)
{
    data3 = data3.Substring(Math.Min(56, data3.Length - 1), 2) + 
            data3.Substring(Math.Min(54, data3.Length - 1), 2) + 
            data3.Substring(Math.Min(52, data3.Length - 1), 2) +
            data3.Substring(Math.Min(50, data3.Length - 1), 2);
} 

关于第二个问题,可以在 if (sp_buffer.Count > 4) 判断语句中,将 sp_buffer 数组复制到 data 数组中,并从 data 中获取第三个数据进行处理,代码如下:

if (sp_buffer.Count > 4)
{
    sp_buffer.CopyTo(0, data, 0, sp_buffer.Count);
    int offset = 0;
    while (offset < sp_buffer.Count - 4)
    {
        if (data[offset] == 0x01 && data[offset + 1] == 0x03)
        {
            byte len = data[offset + 2];
            if (offset + 4 + len <= sp_buffer.Count)
            {
                byte[] buffer = data.Skip(offset + 3).Take(len + 1).ToArray();
                string hexData = BitConverter.ToString(buffer);
                textBox5.Text = hexData;
                float value = DataChange(hexData.Substring(4, 8));
                textBox6.Text = value.ToString();
                break;
            }
        }
        offset++;
    }
}

其中,offset 表示当前处理到的位置,从 0 开始循环,每次循环判断当前位置的数据是否符合要求,若符合,则取出该数据进行处理并退出循环。具体的实现方式可以自行修改。

private void DisplayText(object sender, EventArgs e)
{
    short data2;
    data7 = Encoding.ASCII.GetString(data, 0, sp_buffer.Count); // 将字节数组转换为字符串

    if (data7.Length >= 58)
    {
        data3 = data7.Substring(14, 8); // 提取第一个接收数据
        data4 = data7.Substring(28, 8); // 提取第二个接收数据
        data5 = data7.Substring(42, 8); // 提取第三个接收数据
        data6 = data7.Substring(56, 8); // 提取第四个接收数据

        textBox1.Text = DataChenge(data3).ToString();
        textBox2.Text = DataChenge(data4).ToString();
        textBox3.Text = DataChenge(data5).ToString();
        textBox4.Text = DataChenge(data6).ToString();
    }
}

private float DataChenge(string s)
{
    MatchCollection matches = Regex.Matches(s, @"[0-9A-Fa-f]{2}");
    byte[] bytes = new byte[matches.Count];
    for (int i = 0; i < bytes.Length; i++)
        bytes[i] = byte.Parse(matches[i].Value, System.Globalization.NumberStyles.AllowHexSpecifier);
    float m = BitConverter.ToSingle(bytes.Reverse().ToArray(), 0);
    return m;
}


关于“startIndex 不能大于字符串长度。Arg_ParamName_Name”的错误,是由于在使用Substring()方法的时候截取的位置超出了字符串的长度。可以在调用Substring()方法之前,先用if语句判断一下字符串是否足够长。

关于只要第三个接收数据的问题,您可以在serialPort1_DataReceived()方法中,在获取到完整的第三个数据后,就不再处理后面的数据了。可以设置一个计数器,每次接收到数据就加1,当计数器等于3时,就不再处理后面的数据了。

以下是修改后的代码片段:

int count = 0; //计数器
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    int Byte_len = serialPort1.BytesToRead;
    byte[] Rc_byte = new byte[Byte_len];

    serialPort1.Read(Rc_byte, 0, Byte_len);

    if (sp_buffer.Count > sp_buffer_max)
        sp_buffer.RemoveRange(0, sp_buffer_max);

    sp_buffer.AddRange(Rc_byte);

    if (sp_buffer.Count > 4)
    {
        sp_buffer.CopyTo(0, data, 0, sp_buffer.Count);
        
        count++; //计数器加1

        if(count == 3) //只处理第三个数据
        {
            this.Invoke(new EventHandler(DisplayText));
            return; //不再处理后面的数据
        }
    }
}


古老的问题,因为串口为流式传输,所以他没有固定长度
所以任何试图使用固定长度判定方式的代码都会必然产生该类问题
比如 if(buffer[0]==0xff)----------------------buffer[0]未必每次都是0xff
比如题主的 data3.Substring(Math.Min(56, data3.Length - 1), 2) , data未必每次都会那么长

所以请正确理解流,水流
假设你有3杯水(红,绿,蓝)依次倒入一个水管,然后你在另一端打开水龙头来看,你看到了什么??
你看到的时连续不断的水,而不是3杯水,如果你不停的开关龙头(从缓冲区接收数据),你又会看到什么?
你会看到的是 一段一段的水流,只有第一次你能保证 buffer[0]==0xff是对的,其他时间都是巧合
同样道理我们不能保证“龙头只开闭了3次并且龙头开闭的时间正好是3杯水交界“

所以你需要做的第一步是另外在放个“颜色判定”,自己去判定颜色的交界(封包协议边界)

你把你的读到数据后处理方法改进一下,不要转字符串再操作,直接操作二进制byte[],示例:

        static void ReadDataSample()
        {
            byte[] bytes = { 0x01, 0x03, 0x1C, 0x01, 0x58, 0x2F, 0x00, 0x42, 0x6D, 0x50, 0x02, 0x00, 0x3C, 0x1C, 0x46, 0x55, 0x46, 0x03, 0x4A, 0x4D, 0xD1, 0x41, 0x6D, 0x50, 0x04, 0xD8 };
            MemoryStream stream = new MemoryStream(bytes);
            BinaryReader reader = new BinaryReader(stream);

            reader.ReadBytes(4);  //跳过4个字节
            float val1 = reader.ReadSingle(); //读一个float(4字节)
            reader.ReadBytes(3);  //跳过3个字节
            float val2 = reader.ReadSingle(); //读一个float(4字节)
            reader.ReadBytes(3);  //跳过3个字节
            float val3 = reader.ReadSingle(); //读一个float(4字节)

            Console.WriteLine(val1);
            Console.WriteLine(val2);
            Console.WriteLine(val3);
            /* 输出结果:
                32.04623
                9999
                26.16274             
             */
        }

首先呢看你有两个问题:

  1. substring截取的startIndex超出范围,导致错误。
  2. 想丢弃前两个接收的数据,只显示第三个。

1:只需要检查startIndex是否超出substring(startIndex, endIndex)字符串的长度,如果超出则不执行substring操作。
例子:

data3 = data3.Substring(14, 2); 
// 这里检查14是否超出data3的长度,如果没有才执行Substring
if (14 < data3.Length) {
    data3 = data3.Substring(14, 2); 
} 

2:定义一个计数变量count,初始化为0。
每当接收到数据并处理的时候,count++。
如果count < 3,则不显示在TextBox中,只有当count == 3时才显示。
count继续++,下次接收到数据时继续判断,只有count为3的倍数时才显示,以此达到丢弃前两个数据的效果。
例子:

int count = 0; 

private  void DisplayText(object sender,EventArgs e) 
{
    ...
    // 判断count是否为3的倍数,只有是才显示在TextBox中
    if (count % 3 == 0) {
        textBox1.Text = DataChenge(data3).ToString();
        ...
    }
    count++; // count继续++
}