Article Directory
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 S
represent the start bit, the E
end bit, and the D
valid 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
- Start collecting, change
enablescan
fromfalse
totrue
- Open a new thread, if it is detected
enablescan
istrue
, the transmission period of each acquired signal to the logger - The collector sends data to the serial port and puts the data into the buffer
- After the serial port receives the data, an
serialPort_DataRecived
event is triggered , and all the data in the current buffer is taken away for processing (see the code for detailed processing methods) - After the data is processed, exit the
serialPort_DataRecived
function and wait for the next trigger of the event - Stop collecting, change
enablescan
fromtrue
tofalse
- The thread sending the data detects that it
enablescan
isfalse
, ends the work, and destroys the thread.
problem
In actual use, part of the received data is often missing, DoSomething
and exceptions are often thrown from time to time, causing a lot of trouble.
The first idea was to try...catch
catch 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 ReadTo
method 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
ReadTo
The 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.