【问题标题】:Parsing data from serial port thread-safe从串口线程安全解析数据
【发布时间】:2019-03-27 07:39:12
【问题描述】:

我从串口读取数据并在一个单独的类中解析它。然而,数据被错误地解析,一些样本被重复,而另一些则丢失了。

这是一个解析数据包的例子。它从 packetIndex 开始(应该从 1 开始并递增)。您可以看到 packetIdx 如何重复,以及其他一些值如何重复。我认为这是由于多线程造成的,但我不确定如何解决。

    2 -124558.985180734 -67934.4168823262 -164223.049786454 -163322.386243628
    2 -124619.580759952 -67962.535376851 -164191.757344217 -163305.68949052
    3 -124685.719571795 -67995.8394760894 -164191.042088394 -163303.119039907 
    5 -124801.747477263 -68045.7062179692 -164195.288919841 -163299.140429394 
    6 -124801.747477263 -68045.7062179692 -164221.105184687 -163297.46404856 
    6 -124832.8387538 -68041.9287731563 -164214.936103217 -163294.983004926 

这是我应该收到的:

1 -124558.985180734 -67934.4168823262 -164223.049786454 -163322.386243628
2 -124619.580759952 -67962.535376851 -164191.757344217 -163305.68949052
3 -124685.719571795 -67995.8394760894 -164191.042088394 -163303.119039907 
4 -124801.747477263 -68045.7062179692 -164195.288919841 -163299.140429394 
 ...

这是 SerialPort_DataReceived

 public void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        lock (_lock)
        {
            byte[] buffer = new byte[_serialPort1.BytesToRead];
            _serialPort1.Read(buffer, 0, buffer.Length);

            for (int i = 0; i < buffer.Length; i++)
            {
                //Parse data
                double[] samplesAtTimeT = DataParserObj.interpretBinaryStream(buffer[i]);
                //Add data to BlockingCollection when parsed 
                if (samplesAtTimeT != null)
                    _bqBufferTimerSeriesData.Add(samplesAtTimeT);
            }
        }
    }

以及解析数据的类:

public class DataParser
{
    private int packetSampleCounter = 0;
    private int localByteCounter = 0;
    private int packetState = 0;
    private byte[] tmpBuffer = new byte[3];
    private double[] ParsedData = new double[5]; //[0] packetIdx (0-255), [1-4] signal


    public double[] interpretBinaryStream(byte actbyte)
    {
        bool returnDataFlag = false;

        switch (packetState)
        {
            case 0: // end packet indicator
                if (actbyte == 0xC0)
                    packetState++;
                break;
            case 1: // start packet indicator
                if (actbyte == 0xA0)
                    packetState++;
                else
                    packetState = 0;
                break;
            case 2: // packet Index 
                packetSampleCounter = 0;
                ParsedData[packetSampleCounter] = actbyte;
                packetSampleCounter++;
                localByteCounter = 0;
                packetState++;
                break;
            case 3: //channel data (4 channels x 3byte/channel)
                // 3 bytes
                tmpBuffer[localByteCounter] = actbyte;
                localByteCounter++;
                if (localByteCounter == 3)
                {
                    ParsedData[packetSampleCounter] = Bit24ToInt32(tmpBuffer);
                    if (packetSampleCounter == 5)
                        packetState++; //move to next state, end of packet
                    else
                        localByteCounter = 0;
                }
                break;
            case 4: // end packet
                if (actbyte == 0xC0)
                {
                    returnDataFlag = true;
                    packetState = 1;
                }
                else
                    packetState = 0;
                break;
            default:
                packetState = 0;
                break;
        }
        if (returnDataFlag)
            return ParsedData;
        else
            return null;
    }
}

【问题讨论】:

  • if (actbyte == 0xA0) {packetState++},但初始值为零?所以下一次,你又要打案例 1 了?这甚至正确吗?我认为 DataParser 是需要单元测试的类的一个很好的例子!
  • @Odrai True,packetState 在初始化时应该为 1。这只会影响第一次迭代,对于其余的迭代 packetState 在情况 1 中应该已经是 1。
  • if (actbyte = 0xC0),缺少一个等号?请更新您的帖子并使用大括号。
  • 是的,在我的代码中是正确的。返回 0 的 packetState 是要重新同步,而不是 1 是要重新同步。
  • @AlexeiLevenkov: DataReceived 在工作线程上被调用,所以这个问题绝对涉及多线程。在假设告诉 OP 如何改进他的问题之前,请检查您是否知道您在说什么。

标签: c# multithreading serial-port


【解决方案1】:

摆脱DataReceived 事件,改为使用await serialPort.BaseStream.ReadAsync(....) 来在数据进入时获得通知。async/await 更简洁,不会强迫您进行多线程数据处理。对于高速网络,并行处理非常棒。但是串口很慢,所以额外的线程没有任何好处。

另外,BytesToRead 有问题(它会返回排队的字节数,但会破坏其他状态),你永远不应该调用它。

最后,不要忽略来自Read(或BaseStream.ReadAsync)的返回值。您需要知道字节实际上是如何放入缓冲区的,因为不能保证与您要求的数字相同。

private async void ReadTheSerialData()
{
     var buffer = new byte[200];
     while (serialPort.IsOpen) {
         var valid = await serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
         for (int i = 0; i < valid; ++i)
         {
             //Parse data
            double[] samplesAtTimeT = DataParserObj.interpretBinaryStream(buffer[i]);
            //Add data to BlockingCollection when parsed 
            if (samplesAtTimeT != null)
                _bqBufferTimerSeriesData.Add(samplesAtTimeT);
         }
     }
}

在打开端口并设置好流控、超时等后调用该函数,你可能会发现不再需要阻塞队列,直接处理samplesAtTimeT的内容即可。

【讨论】:

  • 你有例子吗?我发现 DataReceived 有很多,但 RedSync 没有很多。这个好么social.msdn.microsoft.com/Forums/en-US/…
  • @nabrugir:不,那个太可怕了。我将添加 ReadAsync 应该如何工作的框架。
  • 它可以工作,但是当我绘制接收到的数据时,它似乎并没有持续接收数据(它使用 DataReceived)。我将缓冲区大小减小到字节 [10]。我通过每 20 毫秒读取一次 bqBufferTimeSeriesData 并绘制所有可用样本来绘制数据。设备每 4 毫秒采样一次。
  • 您是否尝试在“解析数据”步骤之后立即进行绘图?你的波特率是多少?对于每 4 毫秒的样本组,您需要 15*250*10 = 37500 波特或更高(这并不难实现)。请注意,您也可以调用ReadAsync 并保存它返回的Task&lt;int&gt;,然后进行一些绘图,然后await 任务并进行解析,然后重复。
  • 顺便说一句,缓冲区大小为 10 是没有意义的,因为您的每条消息都是 15 个字节。您还提到要一次收集 20 毫秒的数据,即 4 条消息。因此,建议使用 60 左右的缓冲区大小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-07
  • 1970-01-01
  • 2015-06-30
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多