C# serial port problem - too simple to fail, but

2019-04-12 13:25发布

Ok, this should be dirt simple. I'm trying to read charactes from a serial device. It's such that if I send a space character, it echos back a string of numbers and EOL. That's it.

I'm using Unity 3.3 (.Net 2.0 support), and the 'serial port' is a Prolific serial-to-USB adaptor. BTW: Using Hyperterminal, it all works perfectly, so I know it's not driver nor hardware.

I can open the port ok. It seems I can send my space with port.Write(" "); But if I even TRY to call ReadChar, ReadByte, or ReadLine (like polling), it freezes up until I unplug the USB, and my console output shows nothing (exceptions were caught).

So instead I set up a DataReceviedHandler, but it's never called.

I've read some posts where people have done just this type of thing with Arduinos etc. (this is not an Arduino but hey), using nothing more than ReadLine. Their code does not work for me (and no answers thus far from those authors).

So, any tips? Do I need to use a different thread? If you know any Unity (Mono) coding, any tips along those lines greatly appreciated.

This code a mashup from http://plikker.com/?p=163 and http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.datareceived.aspx#Y537

using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System;

public class SerialTest : MonoBehaviour {

 SerialPort stream;

 void Start () {
  try {
   stream = new SerialPort("COM3", 9600);
   stream.Parity = Parity.None;
   stream.StopBits = StopBits.One;
   stream.DataBits = 8;
   stream.Handshake = Handshake.None;
   stream.DataReceived += new SerialDataReceivedEventHandler(DataReceviedHandler);


   stream.Open();
   Debug.Log("opened ok"); // it DOES open ok!
  } catch (Exception e){
   Debug.Log("Error opening port "+e.ToString()); // I never see this message
  }
 }

 void Update () { // called about 60 times/second
  try {
   // Read serialinput from COM3
   // if this next line is here, it will hang, I don't even see the startup message
   Debug.Log(stream.ReadLine());
   // Note: I've also tried ReadByte and ReadChar and the same problem, it hangs
  } catch (Exception e){
   Debug.Log("Error reading input "+e.ToString());
  }
 }

 private static void DataReceviedHandler(
                        object sender,
                        SerialDataReceivedEventArgs e)
 {
  SerialPort sp = (SerialPort)sender; // It never gets here!
  string indata = sp.ReadExisting();
  Debug.Log("Data Received:");
  Debug.Log(indata);
 }

 void OnGUI() // simple GUI
 {
   // Create a button that, when pressed, sends the 'ping'
   if (GUI.Button (new Rect(10,10,100,20), "Send"))
 stream.Write(" ");
 }
}

5条回答
啃猪蹄的小仙女
2楼-- · 2019-04-12 13:37

Make sure that you are opening the right port, using correct settings. Here is an example of how you could configure it:

serial = new SerialPort();

serial.ReadBufferSize = 8192;
serial.WriteBufferSize = 128;

serial.PortName = "COM1";
serial.BaudRate = 115200;
serial.Parity = Parity.None;
serial.StopBits = StopBits.One;

// attach handlers
// (appears to be broken in some Mono versions?)
serial.DataReceived += SerialPort_DataReceived;
serial.Disposed += SerialPort_Disposed;

serial.Open();

I recommend the open source RealTerm terminal, it has a rich set of features and can help you debug. Try writing a byte manually using such software, and if it works, then the problem is in your program. Otherwise it might be a driver problem (but more likely it isn't).

[Edit]

Calling SerialPort.ReadLine is actually supposed to block the thread until SerialPort.NewLine is received. Also ReadChar and ReadByte will hang until at least one byte is received. You need to make sure that you are actually receiving characters from the other side, and you won't be receiving them if your app is stuck and cannot send the space.

Since I never used Unity, I am not sure how Update is called, but I am presuming it's fired on a foreground thread in regular intervals (otherwise your app wouldn't freeze).

The example that you linked (Arduino and Unity example) shows that Arduino is sending the data continuously, and that is why their Update method is constantly receiving data (no space character needs to be sent towards the device). If they unplug the device, their app will hang just as well.

Well, maybe not, because in .NET 1.1, default value for ReadTimeout was not infinite, like it is in .NET 2.0.

So, what you can do is:

a. Set the ReadTimeout property to a reasonable value. Default in .NET 2.0 is InfiniteTimeout, which doesn't suit your needs. Cons: your update method will still hang for a while on each call, but not infinitely.

b. Someone said that events are not implemented in MONO SerialPort, so I guess using DataReceived only is not an option.

c. Move your sending logic to the Update method also, so that you don't read data at all, until it's time to read it:

private volatile bool _shouldCommunicate = false;
void Update ()
{
  if (_shouldCommunicate) // this is a flag you set in "OnGui"
  {
     try {
       stream.Write(" ");
       Debug.Log(stream.ReadLine());
     } catch (Exception e){
       Debug.Log("Error reading input "+e.ToString());
     }
  }
}

void OnGUI() // simple GUI
{
   if (GUI.Button (new Rect(10,10,100,20), "Send"))
      _shouldCommunicate = true;
}

Note that, if your device is not sending data, it will also block at stream.ReadLine(), so make sure your ReadTimeout is set to a reasonable value. You will also want to stop sending at some point, but I leave that to you.

d. Send the space in OnGui like you are doing now, but always check if there is data in your buffer before reading it:

void Update () { // called about 60 times/second
 try {
   // call our new method
   Debug.Log(ReadLineNonBlocking());
 } catch (Exception e){
  Debug.Log("Error reading input "+e.ToString());
 }
}

private StringBuilder sb = new StringBuilder();
string ReadLineNonBlocking()
{
    int len = stream.BytesToRead;
    if (len == 0)
        return "";

    // read the buffer
    byte[] buffer = new byte[len];
    stream.Read(buffer, 0, len);
    sb.Append(ASCIIEncoding.ASCII.GetString(buffer));

    // got EOL?
    if (sb.Length < 2 ||
        sb[sb.Length-2] != '\r' ||
        sb[sb.Length-1] != '\n')
        return ""; 

    // if we are here, we got both EOL chars
    string entireLine = sb.ToString();
    sb.Length = 0;
    return entireLine;
 }

Disclaimer: this is directly out of my head, untested, so there may be some syntax errors which I am sure you will handle.

查看更多
孤傲高冷的网名
3楼-- · 2019-04-12 13:39

Events are not implemented in Mono SerialPort class, so you won't get any notifications, you have to perform (blocking) read explicitly. Other possible problem - I'm not sure how Unity Behaviours work, are you certain all methods accessing the SerialPort are invoked on the same thread? And you are not disposing you port object, this will also cause problems.

查看更多
Ridiculous、
4楼-- · 2019-04-12 13:50

Maybe your problem is the configuration of the serial port. It is important not only to check for BaudRate or StopBits. Also you should configure DTR, RTS, Handshake, everything. This is important cause maybe another program set some ugly values and the configuration must be explicitly set at every start or some settings of the old connection can run you into trouble.

Also maybe take a look into one of these tools:

They can help you to stub your serial interface or to take a deeper look into the connection. Also maybe try to talk to your serial device by using HyperTerminal or some similar tool that's proven to work.

查看更多
Bombasti
5楼-- · 2019-04-12 13:56

Had similar problem with Mono, upgrading to 2.6.7 helped.

查看更多
我命由我不由天
6楼-- · 2019-04-12 13:57

Do not mix data event and blocking read. What do you expect to happen if data arrives? That both the read method and the event should get the same received data?

You should also read about:

Small serial port tutorial describing all states: http://www.wcscnet.com/Tutorials/SerialComm/Page1.htm

查看更多
登录 后发表回答