简体   繁体   中英

How do I send bytes to a serial device in C#?

I have a device that uses serial (via a USB adaptor) to interface with my PC. I'm having real difficulty getting it to play nicely in C#. I know that it works properly because the vendor-supplied software behaves as expected. I also know that I am able to receive data using my code thanks to a test mode which repeatedly sends "OK".

Here's my code:

    private SerialPort port;

    public SerialConnection()
    {
        this.port = new SerialPort("COM3", 38400, Parity.None, 8, StopBits.One);
        this.port.WriteTimeout = 2000; port.ReadTimeout = 2000;

        this.port.Open();

        this.port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
    }

    public void SendCommand(byte[] command)
    {
        this.port.Write(command,0,command.Length);
        string chars = "";
        foreach (byte charbyte in command) chars += (char)charbyte;
        Console.WriteLine(" -> " + chars);
    }

    void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string data = this.port.ReadLine();
        Console.WriteLine(" <- " + data);
    }

At the moment I'm using another test mode which is supposed to echo back whatever it receives. Accordingly, I'm calling SendCommand with the following bytes:

byte[] {
    0x50,
    0x69,
    0x6E,
    0x67
}

But nothing ever seems to get sent back.

I've no idea what to try next. Anyone got any suggestions?


For a subsidiary question I've posted some PortMon logs. I think they might be useful here too, so here they are:

  1. Vendors software - with all the IOCTL_SERIAL_GET_COMMSTATUS entries filtered out
  2. My toe-dip attempt

Every RS-232 device needs to use some sort of flow control mechanism to notify the opposite part about ongoing communication. There are three basic mechanisms:

  • hardware-based RTS/CTS via two dedicated wires, usually used for controlling sending of individual chunks of data
  • hardware-based DTR/DSR via two dedicated wires, usually used for controlling the whole communication session
  • softwa-based XON/XOFF via a pair of dedicated characters (note that in this case data needs to be encoded to prevent collisions with control characters)

(start update)

Initial queue length and timeouts in the legacy app:

1  IOCTL_SERIAL_SET_QUEUE_SIZE  InSize: 1024 OutSize: 1024
2  IOCTL_SERIAL_SET_TIMEOUT  RI:2000 RM:0 RC:2000 WM:0 WC:2000

while you don't set them. Try setting the queue sizes using SerialPort.WriteBufferSize and SerialPort.ReadBufferSize . Likewise set timeouts using SerialPort.ReadTimeout and SerialPort.WriteTimeout .

(end update)

In your case the legacy app does:

12  IOCTL_SERIAL_CLR_RTS
13  IOCTL_SERIAL_SET_DTR

while you do:

12  IOCTL_SERIAL_CLR_RTS
13  IOCTL_SERIAL_CLR_DTR

You don't set the DTR (Data Terminal Ready) signal and hence the device is not expecting any data or commands on the serial line. Hence set SerialPort.DtrEnable to true .

Second problem is you don't turn on handshaking. The legacy app does:

16  IOCTL_SERIAL_SET_HANDFLOW  Shake:1 Replace:0 XonLimit:0 XoffLimit:0
18  IOCTL_SERIAL_SET_RTS
…
21  IOCTL_SERIAL_CLR_RTS

while you do:

16  IOCTL_SERIAL_SET_HANDFLOW  Shake:0 Replace:0 XonLimit:4096 XoffLimit:4096

Turn it on by setting SerialPort.Handshake to Handshake.RequestToSend .

Also, you seem to be opening, closing, and reconfiguring the serial port very frequently. Try to setup the port and then use the same instance of SerialPort for the whole session. Do not try to reopen it, because you will be causing reconfigurations to the state of the physical port's pins.

Serial communication is not black magic, but it's quite sensitive on setup and various devices require special settings and treatment. Proper timing of commands may be an issue as well.

If you do have some technical documentation for your device, do read it twice and obey it at the first place. At least handshaking modes, commands, etc. should be documented properly.

If you don't have any documentation, try to mitigate differences one by one.


UPDATE: Sending and receiving data.

You wrote you sent the command 'Ping' (as decoded from hex to ASCII). However, you don't mention sending and command termination sequence. Usually serial devices expect an end-of-line sequence (typically a CR LF) as the termination of the command. Before the device receives a complete command including line end, it can't reply.

You are handling the data receive event by calling ReadLine — however, at the place you can't expect a full line of data (ie including a line end to be able to detect a comlete line). You should inspect the event arguments provided and read the input byte-by-byte.

It's a good idea to create a wrapper class that will provide a tailored send-command, receive-response, send-data, receive-data features. The wrapper will have to internally work asynchronously in respect to the rest of your code. That way you will have a usable custom API and a well-behaved serial port handling.

Note that the SerialPort.NewLine property serves the purpose of specifying how a an end-of-line sequence looks like. (In your other question you mentioned you were trying to set it to the set of special characters. That was wrong indeed.)


It's been some time I was a serial comm hero (those were the days we had no vmware but two 486-powered PCs and a pair of directly connected modems to develop and debug communication apps :-)), but I hope this helps at least a bit.

Last but not least, some common terminology:

  • DTE — Data Terminal Equipment = your comupter
  • DCE — Data Communications Equipment = your device, eg a modem

Have you tried changing the Handshake property? Maybe the device requires some handshaking on the control pins before it will accept data.

You should turn on the RtsEnable or DtrEnable properties, the device will ignore anything you send nor send anything back when it doesn't detect you online from these signals. Setting the Handshake property to RTS should have done that though.

Beware that the ReadLine() method will block until it gets the NewLine character. You are not sending one so you won't get one back either. Using Read() would be a better test.

Do some basic troubleshooting first with a known-to-work program, eliminating wiring problems, wrong baudrate or the device simply not echo-ing back. Use HyperTerminal or Putty.

Tom ,

I have had similar problems,

I am going to place my bet on the fact that ReadLine() only returns once it has received a \\r\\n or something that .NET believes is a new line.

In your logs, i dont see Winbird reporting any new line characters, i believe that APP is reading byte by byte, and not waiting for newlines.

So you can send many bytes of data to it, and it will keep on blocking until it receives the correct new line characters.

So give SerialPort.Read a try, by reading 1 byte at a time for starters,

public int Read (
byte[] buffer,
int offset,
int count

)

If this doesnt fix it, you might want to consider the following

Some of my problems were fixed with adding a few Thread.Sleep()'s because i realized the PC's UARTs are not fast enough to electrically transmit the data over the RS232/RS485 ports correctly, also wait a few milliseconds after opening the port.

I would also advise you to rather create a separate thread to handle the serial comms, instead of using SerialDataReceivedEventHandler, create a send buffer, which is sent in the correct increments without blocking your app, also read the data and append bytes received to buffer until you have received enough data.

You are also going to need a second serial device to capture your data to make sure your C# app is transmitting the correct data.

To elimate the issue of handshaking, test by only having PIN 2,3 + PIN 5 (GND) connected.

Hope this helps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM