繁体   English   中英

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

[英]How to do robust SerialPort programming with .NET / C#?

我正在编写一个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库进行端口访问)似乎在这一点上正常工作。

关于是什么导致这个的任何想法?

@thomask

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

你不能关闭别人与端口的连接,以下代码永远不会工作:

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语义,所以我不知道这是否有用。

如何做可靠的异步通信

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

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

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

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

可以依赖此体系结构不会意外地保留SerialPort对象。

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

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

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

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

  2. 启动HyperTerminal并连接到端口。 设备保持打开状态(HyperTerminal引发DTR)我的服务写入事件日志,无法打开端口

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

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

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

  6. 我停止上面提到的申请。

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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