Windows 应用程序不会从串行端口读取所有数据

Windows application wont read all data from serial port

我创建了一个 Windows 表单,打算用它从数据记录 Arduino 中提取数据。目前,Arduino 上安装了一张 SD 卡,上面有一个包含我想要接收的数据的日志文件。使用 Arduino IDE 串行监视器,我能够测试发送和接收串行数据。 Ardunio sketch 等待一个 'r' 字符,一旦收到它,它就会将日志文件中的所有数据发送到串行监视器。这已经过测试并且在 Arduino IDE 中 100% 的时间都有效。我现在有一个 Windows 形式的 GUI,允许将数据直接读取到 PC 上的日志文件中,而无需移除 SD 卡(这是有必要的原因)。我连接到串行端口没有问题,读取数据似乎也没有问题,直到我进入文件的一些方法。

当将读取的数据输出到列表框时,我注意到它会在文件结束前停止。假设这可能是一个多线程问题,我决定尝试直接写入文件而不是更新 GUI。这也失败了,在检查创建的日志文件时,我注意到有时它会有数据,有时却没有。然后我不再尝试读取整个文件,而是开始尝试只读取一定数量的数据。在程序无法始终如一地读取之前,我最多可以读取大约 110 行数据。当程序运行失败而我关闭它时,查看日志文件显示我的文件中有2048个字符,这是串口的写缓冲区大小。更改此数字会导致字符数与缓冲区大小相匹配。在每次写入文件后尝试刷新数据会导致文件中剩余的字符数看似随机而不是匹配缓冲区大小。

VB代码:

Public Class mainForm
    Public Shared logFile As System.IO.StreamWriter
    Public Shadows num As Int16 = 0

    'this is the sub that checks for serial data and is writing the read data to the previously created log file
    Private Sub SerialPort_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles serialPort.DataReceived
        Dim readData As String = serialPort.ReadLine()
        num += 1
        If readData = "EOF" Then
            logFile.Close()
            MsgBox("Done")
        End If
        logFile.Write(readData)
        If num = 110 Then
            MsgBox("Done")
            serialPort.Close()
            logFile.Close()
        End If
    End Sub

    'this sub allows the user to connect to the correct serial port
    'I'm almost positive the problem does not lie here
    Private Sub BtnConnect_Click(sender As Object, e As EventArgs) Handles btnConnect.Click
        serialPort.Close()
        Dim comPortName As String = "COM" + txtComPort.Text
        serialPort.PortName = comPortName
        Try
            serialPort.Open()
            lstLog.Items.Add("Connected to serial port " + comPortName)
        Catch
            lstLog.Items.Add("Could not connect to serial port " + comPortName)
        End Try
    End Sub

    'this sub creates the log file and sends the 'r' to the Arduino to begin reading the sd card
    Private Sub BtnReadData_Click(sender As Object, e As EventArgs) Handles btnReadData.Click
        logFile = My.Computer.FileSystem.OpenTextFileWriter("C:\Users\AHNEHR\Documents\Docs\test1.txt", True)
        serialPort.WriteLine("r")
    End Sub
End Class

Arduino 草图:

#include <SdFat.h>
#include <SPI.h>

const int relayPin = 9;
const int sdChip = 10;
SdFat sd;
SdFile dataLog;


void setup() {
  Serial.begin(115200);
  pinMode(relayPin,OUTPUT);
}

void loop() {
  if (Serial.available()) {
    char data = Serial.read();
    if (data == 'r') {
      if (!sd.begin(sdChip)) {
        Serial.println("Failed to Initialize SD Card");
      }
      else Serial.println("SD Card Initialized");
      if (!dataLog.open("log.txt", O_READ)) {         
        Serial.println("Failed to open file.");
      }
      else Serial.println("Opened Log");
      while (dataLog.available()) {
        Serial.write(dataLog.read());
      }
      Serial.println("EOF");
      dataLog.close();
    }
  }
}

"if readData = EOF" 应该检查文件的结尾,因为一旦整个日志文件输出到串行,Arduino sketch 就会写这个,但我从来没有到过那里,所以另一个如果语句检查已完成读取的次数,如果是 110 次则停止。对于小于 100 的数字,我可以将所有数据写入日志文件没有问题,但是一旦达到 100-110,程序就会开始故障。

SD卡上日志文件中的数据格式如下:
2019 年 8 月 4 日 06:57 T:ERRC P:ERRpsi
2019 年 8 月 4 日 06:58 T:ERRC P:ERRpsi
我希望创建的日志文件与 SD 卡上的日志文件直接匹配,但我得到大约 80/90 行匹配,程序停止工作,不会继续写入日志文件。我相信 SD 卡上的日志文件总共有大约 900 行。我的 vb 技能不是很好,所以我希望有一些明显的东西是我遗漏的,但我已经尝试了我所知道的一切,但不知道为什么我只能读取一些串行数据。非常感谢任何帮助弄清楚为什么会发生这种情况以及如何解决它的帮助。

*编辑:我还确保 Arduino sketch 的波特率和串口的波特率在 115200 匹配。经过更多测试,这似乎是串口的问题,而不是文件的问题.

有几件事,我知道您是 SerialPorts 的新手,您的第一次尝试很好。我会推荐以下内容:

  1. DataReceived 事件随机触发,例如它可能触发中间字符串,因此您可能只会在一个事件中获得 EO 而在另一个事件中获得 F。要解决这个问题,您需要构建一个字符串并依靠一个字符让您知道何时拥有整个字符串。这通常通过 Carriage Return (\r) 或 Line Feed (\n) 来完成。
  2. 正如@gunnerone 建议的那样,您应该使用 ReadExisting() 函数,因为它 returns 数据接收缓冲区中的所有数据,ReadLine 可能会导致您丢失数据。
  3. 确保您的波特率匹配。 serialPort.BaudRate = 115200;

这是一些帮助您入门的示例代码,抱歉,它是 C#,但您应该能够翻译成 VB。

char LF = (char)10;
StringBuilder sb = new StringBuilder();
string currentLine = string.Empty;

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string Data = serialPort1.ReadExisting();

    foreach (char c in Data)
    {
        if (c == LF)
        {
            sb.Append(c);

            CurrentLine = sb.ToString();
            sb.Clear();

            //parse CurrentLine here or print it to textbox
        }
        else
        {
            sb.Append(c);
        }
    }
}

有两点需要注意:

  1. LF 是我在这个例子中寻找的字符,让我知道我有整个字符串。我基本上是在构建一个字符串,直到我看到这个字符,然后我解析该字符串或做任何你需要做的事情。
  2. 我立即清除了我正在构建的字符串,因为 DataReceived 事件是多线程的,因此可以在其中触发。所以我们想尽快清除它,以便它可以开始构建下一行。