如何在 C# 中向串行设备发送字节?

发布于 2024-10-29 06:19:27 字数 1414 浏览 1 评论 0原文

我有一个使用串行(通过 USB 适配器)与我的 PC 连接的设备。我很难让它在 C# 中很好地发挥作用。我知道它可以正常工作,因为供应商提供的软件的行为符合预期。我还知道,由于测试模式反复发送“OK”,我能够使用我的代码接收数据。

这是我的代码:

    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);
    }

目前我正在使用另一种测试模式,该模式应该回显它收到的任何内容。因此,我使用以下字节调用 SendCommand:

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

但似乎没有任何内容被发送回。

我不知道下一步该尝试什么。有人有什么建议吗?


对于一个附属问题,我发布了一些 PortMon 日志。我认为它们在这里也可能有用,所以它们在这里:

  1. 供应商软件 - 包含所有 < code>IOCTL_SERIAL_GET_COMMSTATUS 条目已过滤掉
  2. 我的脚趾尝试

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

音盲 2024-11-05 06:19:27

每个 RS-232 设备都需要使用某种流量控制机制来通知对方正在进行的操作沟通。共有三种基本机制:

  • 基于硬件的RTS/CTS,通过两条专用线路,通常用于控制发送各个数据块 基于
  • 硬件的DTR/DSR,通过两条专用线路线,通常用于
  • 通过一对专用字符控制基于软件的XON/XOFF整个通信会话(请注意,在这种情况下需要对数据进行编码以防止与控制字符发生冲突)

(开始更新)

旧版应用程序中的初始队列长度和超时:

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

当您未设置它们时。尝试使用 SerialPort 设置队列大小。 WriteBufferSize<代码>SerialPort.ReadBufferSize。同样使用 SerialPort.ReadTimeout< 设置超时/code>SerialPort.WriteTimeout

(结束更新)

在您的情况下,旧应用程序会:

12  IOCTL_SERIAL_CLR_RTS
13  IOCTL_SERIAL_SET_DTR

当您这样做时:

12  IOCTL_SERIAL_CLR_RTS
13  IOCTL_SERIAL_CLR_DTR

您没有设置DTR(数据终端就绪)信号,因此设备不期望串行上有任何数据或命令线。因此设置 SerialPort.DtrEnabletrue

第二个问题是你没有打开握手功能。旧版应用程序会:

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

当您这样做时:

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

通过设置 SerialPort.HandshakeHandshake.RequestToSend

此外,您似乎非常频繁地打开、关闭和重新配置串行端口。尝试设置端口,然后在整个会话中使用相同的 SerialPort 实例。不要尝试重新打开它,因为您将导致物理端口引脚状态的重新配置。

串行通信并不是黑魔法,但它对设置非常敏感,并且各种设备需要特殊设置和处理。命令的正确时机也可能是一个问题。

如果您的设备确实有一些技术文档,请务必阅读两遍并首先遵守。至少应该正确记录握手模式、命令等。

如果您没有任何文档,请尝试一一缩小差异。


更新:发送和接收数据。

您写道您发送了命令'Ping'(从十六进制解码为 ASCII)。但是,您没有提及发送和命令终止序列。 通常串行设备期望行结束序列(通常是 CR LF)作为命令的终止。在设备收到包括线路结束在内的完整命令之前,它无法回复。

您正在通过调用 ReadLine 来处理数据接收事件 - 但是,在您不能期望完整行数据的地方(即包括能够检测完整行的行尾)。您应该检查提供的事件参数并逐字节读取输入。

创建一个包装类是一个好主意,它将提供定制的发送命令、接收响应、发送数据、接收数据功能。包装器必须在内部与代码的其余部分异步工作。这样您将拥有可用的自定义 API 和良好的串行端口处理。

请注意,SerialPort.NewLine 属性用于指定行尾序列的外观。 (在您的另一个问题中,您提到您试图将其设置为一组特殊字符。这确实是错误的。)


我已经有一段时间成为串行通信英雄了(那些日子我们没有 vmware,只有两台 486 驱动的计算机) PC 和一对直接连接的调制解调器来开发和调试通信应用程序:-)),但我希望这至少有一点帮助。

最后但并非最不重要的一些常见术语:

  • DTE — 数据终端设备 = 您的计算机
  • DCE — 数据通信设备 = 您的设备,例如调制解调器

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 (i.e. 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, e.g. a modem
隐诗 2024-11-05 06:19:27

您是否尝试过更改 握手 属性?也许设备在接受数据之前需要在控制引脚上进行一些握手。

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

白云不回头 2024-11-05 06:19:27

您应该打开 RtsEnable 或 DtrEnable 属性,当设备没有从这些信号中检测到您在线时,设备将忽略您发送的任何内容,也不会发回任何内容。不过,将握手属性设置为 RTS 应该可以做到这一点。

请注意,ReadLine() 方法将阻塞,直到获得 NewLine 字符。您不会发送一封邮件,因此您也不会收到一封邮件。使用 Read() 会是一个更好的测试。

首先使用已知可以工作的程序进行一些基本的故障排除,消除接线问题、错误的波特率或设备根本不回显。使用超级终端或 Putty。

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.

心欲静而疯不止 2024-11-05 06:19:27

汤姆,

我也遇到过类似的问题,

我将赌注押在 ReadLine() 仅在收到 \r\n 或 .NET 认为是的东西后才返回的事实换行。

在您的日志中,我没有看到 Winbird 报告任何新行字符,我相信 APP 正在逐字节读取,而不是等待换行。

因此,您可以向它发送许多字节的数据,它会继续阻塞,直到收到正确的换行符。

因此,尝试一下 SerialPort.Read,对于初学者来说,一次读取 1 个字节,

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

如果这不能解决问题,您可能需要考虑以下

问题我的一些问题通过添加一些 Thread.Sleep() 得到了解决,因为我意识到 PC 的 UART 速度不够快,无法通过 RS232/RS485 端口正确地以电方式传输数据,并且在打开端口后还要等待几毫秒。

我还建议您创建一个单独的线程来处理串行通信,而不是使用 SerialDataReceivedEventHandler,创建一个发送缓冲区,以正确的增量发送而不会阻塞您的应用程序,还读取数据并将收到的字节附加到缓冲区,直到您已收到足够的数据。

您还需要第二个串行设备来捕获数据,以确保您的 C# 应用程序传输正确的数据。

要消除握手问题,请仅连接 PIN 2,3 + PIN 5 (GND) 进行测试。

希望这有帮助

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文