C# serial port data reading and processing solutions-ancestral code modification notes

original plan

Welcome everyone to visit my personal website www.joezhouman.com to view the original text.
First of all, I do not have a deep understanding of serial communication. There will be a lot of errors in all the explanations about this aspect, and sometimes the words are inconsistent. Just take a good look. .

Code

private char _start; // 起始位标志
private char _end;   // 结束位标志
private string recvstr;         // 用于存储一组数据的全局变量

public void sendMsg(){
    
    
    Thread thread;
    thread = new Thread(() =>//新开线程,执行接收数据操作
    {
    
    
        while (enablescan)//如果标识为true
        {
    
    
            Thread.Sleep(1);
            try
            {
    
    
                serialPort1.WriteLine(":READ?");
                Thread.Sleep(AppCfg.devicepara.Scan_interval);
            }
            catch (Exception ex){
    
    ;}
        }
    });
    thread.Start();//启动线程
    thread.IsBackground = true;
}

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
    
    
            string str = serialPort1.ReadExisting();
            if (str.IndexOf(_start) != -1)//当前数据存在起始位
            {
    
    
                str = str.Substring(str.IndexOf(_start), str.Length - str.IndexOf(_start);//截取起始位到字符串末尾
            }
            if(str.IndexOf(_end==-1)//当前数据不存在结束位
                recvstr += str;
            else{
    
    //当前数据存在起始位
                str = str.Substring(0, str.IndexOf(_end);
                recvstr = recvstr + str;
                DoSomething(recvstr)//对一组串口数据进行其他处理
                recvstr = "";//清空数据
            }
            
        }

The basic idea

Basic Principles of Serial Communication

Let me first explain the way that the collector sends data. A group of data generally 起始位starts with a 结束位logo and ends with a logo, with actual data characters in the middle.
We Srepresent the start bit, the Eend bit, and the Dvalid data, so a group of data is like this

SDDDDDDDDDDDDDDE

Moreover, a group of data is generally not sent all at once, but sent in segments, such as:

SDDD
DDDD
DDDD
DDDE

The sent data will be placed in the serial port communication 缓存区.

Data collection process

  1. Start collecting, change enablescanfrom falsetotrue
  2. Open a new thread, if it is detected enablescanis true, the transmission period of each acquired signal to the logger
  3. The collector sends data to the serial port and puts the data into the buffer
  4. After the serial port receives the data, an serialPort_DataRecivedevent is triggered , and all the data in the current buffer is taken away for processing (see the code for detailed processing methods)
  5. After the data is processed, exit the serialPort_DataRecivedfunction and wait for the next trigger of the event
  6. Stop collecting, change enablescanfrom truetofalse
  7. The thread sending the data detects that it enablescanis false, ends the work, and destroys the thread.

problem

In actual use, part of the received data is often missing, DoSomethingand exceptions are often thrown from time to time, causing a lot of trouble.

The first idea was to try...catchcatch exceptions and discard the problematic data, but sometimes by chance, a set of normal data is not available, and the frequency of this problem is not low.
After learning from the pain, decided to refactor this ancestral code, really 前人拉屎总不冲,后人难忍终清洁.

Refactor

Problem review

Let’s review the process of the problem first. The
normal process should be like this.

Collector action Receive event Current buffer str recvstr
Start On standby null null null
send data trigger event SDDD SDDD SDDD
send data -Processing- DDDD SDDD SDDD
-Sending interval- Processing complete DDDD SDDD SDDD
send data trigger event DDDDDDDE DDDDDDDE SDDDDDDDDDDE
Start to rest Processing complete null null null

When an error occurs:

Collector action Receive event Current buffer str recvstr
Start On standby null null null
send data trigger event SDDD SDDD SDDD
send data -Processing- DDDD SDDD SDDD
send data -Processing- DDDDDDDE SDDD SDDD
-Standby- Processing complete DDDDDDDE SDDD SDDD
-Standby- -On standby- DDDDDDDE SDDD SDDD
send data trigger event DDDDDDDESDDD SDDD SDDDSDDD

Look, it should have been received SDDDDDDDDDDESDDD..., but now what this group really received is SDDDSDDD...that some of the data is lost.

It is too troublesome to modify the source code, so I chose to refactor this part of the function directly.

Refactoring process

ReadTo function

The first thing that comes to mind is, is it too difficult to read a complete set of data before processing it?

In fact, it is possible. C#Built-in ReadTomethod to view MSDN

After reading the end bit mark, put this group of data into the specified parameters. There must be some details in the bottom layer. In short, it can ensure that a complete set of data is read every time.

Its usage is

    string endStr = ((char)13).ToString
    string targetStr = SerialPort.ReadTo("endStr");

Multithreading

ReadToThe problem of data reading is solved. However, when the method is reading data, it has been waiting for the whole process. After the reading is successful, the subsequent operations can be performed. During this waiting process,
any other trigger events cannot be completed. The effect is that the software "stuck".

Without solving this problem, we can adopt a multi-threaded approach. One thread is dedicated to reading data, and one thread is processing data.
To communicate between several threads, we have designed a
read data continue to put in another thread continuously from the extraction data. In addition, we also need to ensure that the data first put into the pool is processed first.

Based on the above characteristics, we naturally thought of a 先入先出data structure- 队列.


To put it aside, some people always say that the problem in elementary school is very 一个池子,一边注水,一边放水silly and has no practical significance. . . In fact, it is everywhere in reality.
Not necessarily true , but also abstract .


Not much to say, on the code.

Code

private char _start; // 起始位标志
private char _end ;   // 结束位标志
private private Queue<string> _serialPortData; // 数据池

///<summary>
///向测试仪发送读取信号及接受测试仪发回数据的线程。根据之前所说ReadTo的性质,正好保证读完一组数据后,再发送指令让测试仪测试下一组数据
///<summary>
public void ReadDataThread() {
    
    
    _enableScan = true;
    Thread thread = new Thread(() => //新开线程,执行接收数据操作
    {
    
    
         while (_enableScan) //如果标识为true
        {
    
    
            Thread.Sleep(1);
            try {
    
    
                serialPort1.WriteLine(":READ?");
                Thread.Sleep(10);
                if (serialPort1.BytesToRead != 0) _serialPortData.Enqueue(serialPort1.ReadTo(_end));//将读到的数据放入池中
                Thread.Sleep(100);
            }
            catch (Exception ex) {
    
    //其他的改进
                HandleException(ex);//加上异常处理,是系统更稳健
                Log.Error(ex);//打logger,记录异常原因,为修改bug提供依据
            }
        }
    });
    thread.Start(); //启动线程
    thread.IsBackground = true;//线程在后台运行
}

public void SolveDataThread(){
    
    
    Thread thread = new Thread(() => //新开线程,执行接收数据操作
    {
    
    
        while (_enableScan) //如果标识为true
        {
    
    
            if (_serialPortData.Count == 0)continue; 
            try{
    
    
                string str = _serialPortData.Dequeue();//从池中读取数据
                DoSomething(str);
                thread.Sleep(100);
            }
            catch (Exception ex) {
    
    //其他的改进
                HandleException(ex);//加上异常处理,是系统更稳健
                Log.Error(ex);//打logger,记录异常原因,为修改bug提供依据
            }
        }
}


The information description is in the code comments.

Guess you like

Origin blog.csdn.net/manmanaa/article/details/115000425