简体   繁体   English

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

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

I'm writing a Windows Service for communication with a Serial Mag-stripe reader and a relay board (access control system). 我正在编写一个Windows服务,用于与串行磁条读取器和中继板(访问控制系统)进行通信。

I run into problems where the code stops working (i get IOExceptions) after another program has "interrupted" the process by opening the same serial port as my service. 在另一个程序通过打开与我的服务相同的串行端口“中断”进程后,我遇到代码停止工作的问题(我得到IOExceptions)。

Part of the code is as follows: 部分代码如下:

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

My sample program successfully starts the work-thread, and the opening/closing and raising of DTR causes my Mag-stripe reader to power up (wait 1sec), shut down (wait 1 sec) and so on. 我的示例程序成功启动了工作线程,DTR的打开/关闭和升高使我的Mag-stripe读取器上电(等待1秒),关闭(等待1秒),依此类推。

If I launch HyperTerminal and connects to the same COM port, HyperTerminal tells me the port is currently in use. 如果我启动HyperTerminal并连接到同一个COM端口,HyperTerminal会告诉我该端口当前正在使用中。 If i repeatedly press ENTER in HyperTerminal, to try to reopen the port it will succeed after a few retries. 如果我在HyperTerminal中反复按ENTER键,尝试重新打开端口,它会在重试几次后成功。

This has the effect of causing IOExceptions in my work-thread, which is expected. 这会导致我的工作线程中出现IOExceptions,这是预期的。 However, even if I close down HyperTerminal, i still get the same IOException in my work-thread. 但是,即使我关闭HyperTerminal,我的工作线程仍然会得到相同的IOException。 The only cure is actually to restart the computer. 唯一的办法是重启电脑。

Other programs (which is not using .NET libraries for port-access) seem to work normally at this point. 其他程序(不使用.NET库进行端口访问)似乎在这一点上正常工作。

Any ideas as to what is causing this? 关于是什么导致这个的任何想法?

@thomask @thomask

Yes, Hyperterminal does in fact enable fAbortOnError in SetCommState's DCB, which explains for most of the IOExceptions thrown by the SerialPort object. 是的,Hyperterminal确实在SetCommState的DCB中启用了fAbortOnError,这解释了SerialPort对象抛出的大多数IOExceptions。 Some PCs / handhelds also have UARTs that have the abort on error flag turned on by default - so it's imperative that a serial port's init routine clears it (which Microsoft neglected to do). 有些PC /手持设备也有UART,默认情况下会打开错误标志中止 - 所以串口的init例程必须清除它(微软忽略了这一点)。 I wrote a long article recently to explain this in greater detail (see this if you're interested). 我最近写了一篇长篇文章来更详细地解释这个问题(如果你有兴趣,请看这个 )。

You can't close someone elses connection to a port, the following code will never work: 你不能关闭别人与端口的连接,以下代码永远不会工作:

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

Because your object didn't open the port you can't close it. 因为您的对象没有打开端口,所以无法关闭它。

Also you should close and dispose the serial port even after exceptions occur 即使发生异常,您也应该关闭并处理串行端口

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

If you want the process to be interruptible then you should Check if the port is open and then back off for a period and then try again, something like. 如果您希望该过程可以中断,那么您应该检查端口是否打开然后退回一段时间然后再试一次,例如。

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

Have you tried leaving the port open in your application, and just turning DtrEnable on/off, and then closing the port when your application closes? 您是否尝试在应用程序中打开端口,打开/关闭DtrEnable,然后在应用程序关闭时关闭端口? ie: 即:

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

I'm not familiar with DTR semantics, so I don't know if this would work. 我不熟悉DTR语义,所以我不知道这是否有用。

How to do reliable async comms 如何做可靠的异步通信

Don't use the blocking methods, the internal helper class has some subtle bugs. 不要使用阻塞方法,内部帮助程序类有一些微妙的错误。

Use APM with a session state class, instances of which manage a buffer and buffer cursor shared across calls, and a callback implementation that wraps EndRead in a try...catch . 将APM与会话状态类一起使用,其实例管理跨调用共享的缓冲区和缓冲区游标,以及在try...catch中包装EndRead的回调实现。 In normal operation, the last thing the try block should do is set up the next overlapped I/O callback with a call to BeginRead() . 在正常操作中, try块应该做的最后一件事是通过调用BeginRead()来设置下一个重叠的I / O回调。

When things go awry, catch should asynchronously invoke a delegate to a restart method. 当出现问题时, catch应异步调用委托给重启方法。 The callback implementation should exit immediately after the catch block so that the restart logic can destroy the current session (session state is almost certainly corrupt) and create a new session. 回调实现应该在catch块之后立即退出,以便重启逻辑可以销毁当前会话(会话状态几乎肯定已损坏)并创建新会话。 The restart method must not be implemented on the session state class because this would prevent it from destroying and recreating the session. 不能在会话状态类上实现重新启动方法,因为这会阻止它破坏和重新创建会话。

When the SerialPort object is closed (which will happen when the application exits) there may well be a pending I/O operation. 当SerialPort对象关闭时(将在应用程序退出时发生),可能存在挂起的I / O操作。 When this is so, closing the SerialPort will trigger the callback, and under these conditions EndRead will throw an exception that is indistinguishable from a general comms shitfit. 如果是这样,关闭SerialPort将触发回调,并且在这些条件下, EndRead将抛出一个与通用comms shitfit无法区分的异常。 You should set a flag in your session state to inhibit the restart behaviour in the catch block. 您应该在会话状态中设置一个标志以禁止catch块中的重新启动行为。 This will stop your restart method from interfering with natural shutdown. 这将阻止重启方法干扰自然关机。

This architecture can be relied upon not to hold onto the SerialPort object unexpectedly. 可以依赖此体系结构不会意外地保留SerialPort对象。

The restart method manages the closing and re-opening of the serial port object. restart方法管理串行端口对象的关闭和重新打开。 After you call Close() on the SerialPort object, call Thread.Sleep(5) to give it a chance to let go. SerialPort对象上调用Close()之后,调用Thread.Sleep(5)让它有机会放手。 It is possible for something else to grab the port, so be ready to deal with this while re-opening it. 其他东西可以抓住端口,所以准备好在重新打开它时处理它。

I've tried changing the work-thread like this, with the exact same result. 我试过改变这样的工作线程,结果完全相同。 Once HyperTerminal once succeeds in "capturing the port" (while my thread is sleeping), my service won't be able to open the port again. 一旦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();
    }
}

This code seems to work properly. 此代码似乎正常工作。 I've tested it on my local machine in a console application, using Procomm Plus to open/close the port, and the program keeps on ticking. 我已经在我的本地机器上在控制台应用程序中测试了它,使用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();
            }
        }
    }

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). 以“控制台模式”启动我的服务,它开始/关闭设备(我可以通过它的LED告诉)。

  2. Start HyperTerminal and connect to the port. 启动HyperTerminal并连接到端口。 The device stays on (HyperTerminal raises DTR) My service writes to the event log, that it cannot open the port 设备保持打开状态(HyperTerminal引发DTR)我的服务写入事件日志,无法打开端口

  3. Stop HyperTerminal, I verify it is properly closed using task manager 停止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. 设备保持关闭状态(超级终端降低了DTR),我的应用程序继续写入事件日志,说它无法打开端口。

  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. I stop the above mentioned application. 我停止上面提到的申请。

  7. VOILA, my service kicks in again, the port opens successfully, and the LED goes ON/OFF. VOILA,我的服务再次启动,端口成功打开,LED开启/关闭。

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. 我相信当你的程序在Thread.Sleep(1000)中并打开超级终端连接时,超级终端可以控制串口。 When your program then wakes up and trying to open the serial port, an IOException is thrown. 当您的程序唤醒并尝试打开串行端口时,将抛出IOException。

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. 看看内置的UPS服务 - 一旦你告诉它有一个连接到COM1的UPS,你可以亲吻那个端口再见。 I'd suggest you do the same unless there's a strong operational requirement to share the port. 除非有强大的操作要求来共享端口,否则我建议你这样做。

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

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