【问题标题】:Windows application wont read all data from serial portWindows 应用程序不会从串口读取所有数据
【发布时间】:2019-08-08 05:54:33
【问题描述】:

我创建了一个 Windows 窗体,我打算使用它从数据记录 Arduino 中提取数据。目前,Arduino 上安装了一个 SD 卡,其中有一个包含我想要接收的数据的日志文件。使用 Arduino IDE 串行监视器,我能够测试发送和接收串行数据。 Ardunio 草图等待一个“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();
    }
  }
}

“如果 readData = EOF”应该检查文件的结尾,因为一旦整个日志文件输出到串行,Arduino 草图就会写这个,但我从来没有在那里,所以另一个 if 语句检查读取完成的次数,如果为 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 草图和串口的波特率匹配为 115200。经过更多测试,这似乎是串口的问题,而不是文件的问题。

【问题讨论】:

  • 你已经发现了问题。您希望 Arduino 和 Windows 完美同步,而在现实世界中,任何一方的轻微滞后或延迟可能会产生意想不到的结果,并且概率是累积的,这就是为什么它总是在一段时间后发生。您必须在每行或多行之后实现流控制,以确保 arduino 等待窗口完成并准备好接收。
  • 是的,正如@AlessandroMandelli 指出的那样,这似乎是一个时间问题。从 Windows 窗体接收到“r”命令后,我只会发送一行。然后,当 Windows 窗体处理完这一行时,它会发送一个新命令“r”。
  • 我认为在您的SerialPort_DataReceived 事件中,您会想要使用serialPort.ReadExisting() 而不是serialPort.ReadLine()。可能有不止一行可供阅读。或者完整的行可能不可用,ReadLine 调用将阻塞,直到收到完整的行。 docs.microsoft.com/en-us/dotnet/api/…
  • @B.Letz 我现在已经编辑了 Windows 表单和 Arduino 草图,所以现在草图在接收到“r”命令时一次只发送一行,我发送“r” ' 每次在 Windows 端写入后的命令。这和以前有同样的问题。我可以反复单击读取按钮并最终到达文件末尾,但它只适用于不同大小的小块。我应该上传这个修改后的代码版本吗?对于什么可能会有所帮助,您还有其他想法吗?
  • 你还是做错了。除非已经有一行可供读取,否则您无法读取一行。因为在 115200 一位是 1e-5 秒,1 个字节是 1e-4 秒,所以在尝试读取之前,您必须每 10 个字节至少等待 1ms。因此,作为第一次尝试,在 readline 之前放置一个 Thread.Sleep(10)。

标签: vb.net winforms logging arduino serial-port


【解决方案1】:

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

  1. DataReceived 事件会随机触发,例如它可以触发中间字符串,因此您可能只会在一个事件中获得 EO 而在另一个事件中获得F。为了解决这个问题,您需要构建一个字符串并依赖一个让您知道何时拥有整个字符串的字符。这通常通过回车 (\r) 或换行 (\n) 来完成。
  2. 正如@gunnerone 建议的那样,您应该使用ReadExisting() 函数,因为它会返回数据接收缓冲区中的所有数据,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 事件是多线程的,因此它可以在其中触发。所以我们想尽快清除它,以便它可以开始构建下一行。

【讨论】:

  • 2 个问题:我假设 CurrentLine 是一个字符串变量?这是否意味着在 Arduino 一次发送所有数据时实现,或者一次只发送 1 行?似乎它是一次性的,但我无法让这个解决方案以我的形式工作。
  • *更新:我没有正确检查换行符,我忘记了 VB 使用 vbLf 而不是 \n。表单现在按预期工作,但我在列表框的末尾得到一堆 EOF 行,我将值放入其中。我无法让 if 语句与串行输入中的 EOF 字符串匹配以匹配我检查的任何字符串,所以串口永远不会关闭。如何比较字符串?我试过检查“EOF”+ vbLf 但这不起作用
  • EOF 不是一行,也不是一个字符串。它是一个字节。你确定你检查的是正确的类型吗?
  • @AlessandroMandelli 在我的例子中,“EOF”一个字符串,一旦到达日志文件的末尾,它就会由 Arduino 草图串行输出。我只是选择了字符串“EOF”,因为它很熟悉,而不是“END”或“DONE”,它们也可以工作。我遇到的问题是由于我解析字符串的方式以及包含 vbCr 和 vbLf 字符。不过我已经解决了这个问题。
  • 很高兴你能弄明白。是的,CurrentLine 只是一个临时字符串变量,用于在您进行解析之前快速将字符串构建器字符串放入(解析可能需要一些时间,我们希望字符串构建器开始构建下一个字符串)。对于比较字符串,我通常会去与.Contains("EOF")。在您使用上面的示例的情况下,if(CurrentLine.Contains("EOF")) MsgBox("Done")
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 2014-12-24
相关资源
最近更新 更多