English 中文(简体)
如何使用 .NET / C# 进行稳健的串口编程?
原标题:
  • 时间:2009-01-14 01:19:03
  •  标签:

我正在编写一个Windows服务,用于与串行磁条读卡器和继电器板(门禁系统)通信。

当另一个程序打开与我的服务相同的串口时,我会遇到代码停止工作(我得到IOExceptions)的问题,这被称为“中断”进程。

代码的一部分如下:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

我的示例程序成功地启动了工作线程,并且打开/关闭和提高DTR会导致我的磁条阅读器启动(等待1秒),关闭(等待1秒)等等。

If I launch HyperTerminal and connects to the same COM port, HyperTerminal tells me the port is currently in use. If i repeatedly press ENTER in HyperTerminal, to try to reopen the port it will succeed after a few retries.

这会导致我的工作线程产生IOExceptions,这是预期的。然而,即使我关闭HyperTerminal,我仍然会在工作线程中得到相同的IOException。唯一的解决方法是重启计算机。

其他程序(不使用.NET库进行端口访问)此时似乎正常工作。

有什么想法是造成这种情况的原因吗?

最佳回答

@thomask 翻译成中文:

是的,Hyperterminal实际上在SetCommState的DCB中启用了fAbortOnError,这解释了SerialPort对象抛出的大多数IOExceptions。一些PC / 掌上电脑也具有默认启用错误中止标志的UARTs-因此,串口的初始程序必须清除它(Microsoft忽略了这一点)。我最近写了一篇长文来详细解释这个问题(如果你感兴趣,可以查看这里)。

问题回答

你无法关闭他人的端口连接,以下代码永远不会起作用:

if (serialPort.IsOpen) serialPort.Close();

因为你的对象没有打开该端口,所以你无法关闭它。

即使出现异常情况,您也应该关闭并处理串口。

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

如果你想要这个进程可以被中断,那么你应该先检查端口是否打开,然后暂停一段时间,再尝试一次,类似于这样。

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

你尝试过在你的应用程序中保持端口打开,只需开关DtrEnable,然后在应用程序关闭时关闭端口吗?例如:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

我不熟悉DTR语义,所以我不知道这是否可行。

How to do reliable async comms

不要使用阻塞方法,内部辅助类存在一些微妙的 bug。

使用带有会话状态类的APM,这些实例管理跨调用共享的缓冲区和缓冲区指针,并且使用回调实现将EndRead包装在try ... catch中。在正常操作中,try块应该做的最后一件事是使用调用BeginRead()设置下一个重叠I/O回调。

当事情出现问题时,catch 应异步调用一个委托来重新启动方法。回调实现应立即在 catch 块之后退出,以便重新启动逻辑可以销毁当前会话(会话状态几乎肯定是损坏的)并创建一个新会话。重新启动方法不应在会话状态类上实现,因为这将阻止它销毁和重新创建会话。

当SerialPort对象关闭时(当应用程序退出时),可能存在待处理的I/O操作。 此时,关闭SerialPort将触发回调,并且在这些条件下,EndRead将抛出一种无法与一般通信故障区分的异常。 您应在会话状态中设置一个标志,以阻止catch块中的重新启动行为。 这将阻止您的重新启动方法干扰自然关闭。

This architecture can be relied upon not to hold onto the SerialPort object unexpectedly.

The restart method manages the closing and re-opening of the serial port object. After you call Close() on the SerialPort object, call Thread.Sleep(5) to give it a chance to let go. It is possible for something else to grab the port, so be ready to deal with this while re-opening it.

I think I have come to the conclusion that HyperTerminal does not play well. I ve run the following test:

  1. Start my service in "console mode", it starts switching the device on/off (i can tell by it s LED).

  2. Start HyperTerminal and connect to the port. The device stays on (HyperTerminal raises DTR) My service writes to the event log, that it cannot open the port

  3. 停止HyperTerminal,我使用任务管理器验证它是否正确关闭。

  4. The device stays off (HyperTerminal has lowered DTR), my app keeps on writing to the event log, saying it cannot open the port.

  5. I start a third application (the one I need to coexist with), and tell it to connect to the port. I does so. No errors here.

  6. 我停止了上述应用程序。

  7. 哇,我的服务又开始了,端口成功打开,LED灯也亮/灭了。

我已经尝试过像这样改变工作线程,但结果完全相同。一旦HyperTerminal成功“捕获端口”(而我的线程正在休眠),我的服务将无法再次打开该端口。

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

这段代码似乎能够正常工作。我已经在我的本地机器上使用控制台应用程序进行了测试,使用Procomm Plus打开/关闭了端口,程序仍然能够正常运行。

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

This answer got to long to be a comment...

I believe that when your program is in a Thread.Sleep(1000) and you open your HyperTerminal connection, the HyperTerminal takes control over the serial port. When your program then wakes up and trying to open the serial port, an IOException is thrown.

Redesign your method and try to handle the opening of the port in a different way.

EDIT: About that you have to reboot your computer when your program fails...

That probably because your program isn´t really closed, open your taskmanager and see if you can find your program service. Be sure to stop all your threads before exiting your application.

Is there a good reason to keep your service from "owning" the port? Look at the built-in UPS service -- once you tell it there s an UPS attached to, say, COM1, you can kiss that port goodbye. I d suggest you do the same unless there s a strong operational requirement to share the port.





相关问题
热门标签