如何使用.NET / C#进行强大的SerialPort编程?

时间:2009-01-14 01:19:03

标签: c# .net serial-port

我正在编写一个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的打开/关闭和升高使我的Mag-stripe读取器上电(等待1秒),关闭(等待1秒)等等。

如果我启动HyperTerminal并连接到同一个COM端口,HyperTerminal告诉我该端口当前正在使用中。如果我在HyperTerminal中反复按ENTER,尝试重新打开 几次重试后它将成功的端口。

这会导致我的工作线程中出现IOExceptions,这是预期的。但是,即使我关闭HyperTerminal,我的工作线程仍然会得到相同的IOException。唯一的办法就是重启计算机。

其他程序(不使用.NET库进行端口访问)似乎在这一点上正常工作。

关于导致这种情况的任何想法?

9 个答案:

答案 0 :(得分:24)

@thomask

是的,Hyperterminal确实在SetCommState的DCB中启用了fAbortOnError,这解释了SerialPort对象抛出的大多数IOExceptions。有些PC /手持设备也有UART,默认情况下会打开错误标志中止 - 所以串口的init例程必须清除它(微软忽略了这一点)。我最近写了一篇长篇文章来更详细地解释这一点(如果你有兴趣的话,请参阅this。)

答案 1 :(得分:4)

您无法关闭某人与某个端口的连接,以下代码将无法使用:

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

答案 2 :(得分:2)

您是否尝试在应用程序中打开端口,只打开/关闭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语义,所以我不知道这是否有用。

答案 3 :(得分:2)

如何进行可靠的异步通信

不要使用阻塞方法,内部帮助程序类有一些微妙的错误。

将APM与会话状态类一起使用,其实例管理跨调用共享的缓冲区和缓冲区游标,以及在EndRead中包装try...catch的回调实现。在正常操作中,try块应该执行的最后一件事是通过调用BeginRead()来设置下一个重叠的I / O回调。

当出现问题时,catch应异步调用重新启动方法的委托。回调实现应该在catch块之后立即退出,以便重启逻辑可以销毁当前会话(会话状态几乎肯定已损坏)并创建新会话。重启方法必须在会话状态类上实现,因为这样可以防止它破坏并重新创建会话。

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

可以依赖此架构不要意外地保留SerialPort对象。

restart方法管理串口对象的关闭和重新打开。在Close()对象上致电SerialPort后,请致电Thread.Sleep(5),让它有机会放手。其他东西可以抓住端口,所以准备好在重新打开它时处理它。

答案 4 :(得分:1)

我尝试过改变这样的工作线程,结果完全相同。一旦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();
    }
}

答案 5 :(得分:1)

此代码似乎正常工作。我已经在我的本地机器上在控制台应用程序中测试了它,使用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();
            }
        }
    }

答案 6 :(得分:1)

我想我已经得出结论,超级终端不能很好地发挥作用。我进行了以下测试:

  1. 以“控制台模式”启动我的服务,它开始/关闭设备(我可以通过它的LED告诉)。

  2. 启动超级终端并连接到端口。 设备保持打开状态(超级终端提升DTR) 我的服务写入事件日志,它无法打开端口

  3. 停止超级终端,我使用任务管理器验证它已正确关闭

  4. 设备保持关闭状态(超级终端降低了DTR),我的应用程序继续写入事件日志,说它无法打开端口。

  5. 我启动第三个应用程序(我需要共存的应用程序),并告诉它连接到端口。我这样做了。这里没有错误。

  6. 我停止了上述申请。

  7. VOILA,我的服务再次启动,端口成功打开,LED开启/关闭。

答案 7 :(得分:0)

这个答案很长时间才成为评论...

我相信当你的程序在Thread.Sleep(1000)中并打开超级终端连接时,HyperTerminal会控制串口。当您的程序唤醒并尝试打开串行端口时,将抛出IOException。

重新设计您的方法并尝试以不同的方式处理端口的打开。

编辑: 关于你必须在程序失败时重新启动计算机......

这可能是因为您的程序并未真正关闭,请打开您的任务管理器,看看您是否可以找到您的程序服务。在退出应用程序之前,请务必停止所有线程。

答案 8 :(得分:0)

是否有充分理由让您的服务“拥有”该端口?看看内置的UPS服务 - 一旦你告诉它有一个连接到COM1的UPS,你可以亲吻那个端口再见。我建议你这样做,除非有强大的操作要求来共享端口。