27

I've built a C# application which reads and writes data from a serial port. The device connected to the serial port is a FTDI USB to serial converter which communicates to hardware through a XBee wireless module. The hardware tests battery modules for capacity and steady state voltage etc. these tests take days to complete.

Every now and then the serial port seems to stop responding and throws a System.IO.IOException: A device attached to the system is not functioning error.

Here is the stack trace:

at system.IO.Ports.InternalResources.WinIOError
at system.IO.Ports.SerialStream.EndWrite
at system.IO.Ports.SerialStream.Write
at system.IO.Ports.SerialPort.Write
at BatteryCharger.CommunicationClass.ApiTransmission

after this error is thrown a System.UnauthorizedAccessException: Access to the port is denied error is thrown and this error occurs every time the software tries to write to the port and it never recovers until i stop debugging and restart the software only for the same thing to happen a few days later.

How do I prevent this error from occurring or is there a way to successfully recover from these errors in the catch statement of the error?

I'm reading the serial port continuously in a background worker thread and writing from a different thread.

I've also already tried all the legacy error handling bits and pieces that have been suggested on this forum but none of them seem to make any difference. The error occurs on windows XP Pro SP3 32bit and Windows7 Pro 32bit.

Here is the CommunicationClass.cs - serial transmission code.

    public static bool ApiTransmission(TXpacket transmission)
    {
        //clear all previous data that may have been in the buffer before doing a transmission
        Variables.dataParserPacket_buff.Clear();
        //TXpacket xbeetransmision = new TXpacket();
        byte[] packet = transmission.GeneratePacket();

        try
        {
            if (_serialPort.IsOpen)
            {
#if Console
                Log.write("TX-Packet: " + StringHandler.listToString(packet.ToList<byte>()));
#endif
                _serialPort.Write(packet, 0, packet.Length);
                Thread.Sleep(100);
            }
            else
            {
#if Console
                Log.write("serial port is closed");
#endif
                return false;
            }
        }
        catch (UnauthorizedAccessException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("UnauthorizedAccessException");
        }
        catch (IOException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("IOexception");
            //_serialPort.Close();
            //Thread.Sleep(100);
            //_serialPort.Open();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
#if Console
            Log.write(ex.ToString());
#endif
        }
        return true;

    }

This is how I initialize my serial port

    public CommunicationClass(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
    {
        _analysePacketBGW.DoWork += new DoWorkEventHandler(_analysePacketBGW_DoWork);
        _analysePacketBGW.WorkerReportsProgress = true;
        _analysePacketBGW.WorkerSupportsCancellation = true;

        _readBGW.DoWork += new DoWorkEventHandler(_readThread_DoWork);
        _readBGW.WorkerSupportsCancellation = true;
        _readBGW.WorkerReportsProgress = true;

        _parserStarterBGW.DoWork += new DoWorkEventHandler(_parserStarterThread_DoWork);
        _parserStarterBGW.WorkerSupportsCancellation = true;
        _parserStarterBGW.WorkerReportsProgress = true;
        if (_readBGW != null)
        {
            _readBGW.CancelAsync();
        }

        _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);

        //SerialPortFixer.Execute(portName);
        //Thread.Sleep(1000);
        //using (_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
        //{
        //    //_serialPort.Open();
        //}

        _serialPort.ErrorReceived += new SerialErrorReceivedEventHandler(_serialPort_ErrorReceived);

        _dataqueuepp = new ManualResetEvent(false);

        _serialPort.Open();
        _readBGW.RunWorkerAsync();
        _parserStarterBGW.RunWorkerAsync();
        CommunicationClass.PacketReceived += new DataPacketReceived(CommunicationClass_PacketReceived);
    }

And the background worker that handles the reading of the serial port

    void _readThread_DoWork(object sender, DoWorkEventArgs e)
    {
#if Console
        Log.write("Read()");
#endif
        while (!_readBGW.CancellationPending)
        {
            try
            {
                int message = _serialPort.ReadByte();
                try
                {
                    Variables.dataQueue.Enqueue(message);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message + "     " + message.ToString());
                }
                _dataqueuepp.Set();
                //Console.Write(String.Format("{0:X2}", message) + " ");
            }
            catch (TimeoutException) { Log.write("read timeout"); }
            catch (IOException) { Log.write("read IOException"); }
            catch (ThreadAbortException) { Log.write("read thread aborted"); }
            catch (Exception ex) { MessageBox.Show(ex.ToString()); }
            finally { }
        }
    }

I will now rewrite the code to read and write to/from the serial port from the same thread to see if that makes any difference.

EDIT

Based on Jim's comments I've added the following to the IOException Catch statement:

        catch (IOException ex)
        {
            MessageBox.Show(ex.ToString());
            Log.write("IOexception");
            _readBGW.CancelAsync();
            Thread.Sleep(100);
            _serialPort.Close();
            Thread.Sleep(100);
            _serialPort.Open();
            Thread.Sleep(100);
            _readBGW.RunWorkerAsync();
            _serialPort.Write(packet, 0, packet.Length);
        }

Hopefully by stopping the background worker's _serialPort.Read, closing the port, re-opening the port, running the background worker again and attempting to write the same command again will be enough to successfully recover from this error. The MessageBox still blocks the code so that i can see when the error occurs and can monitor how it recovers.

I don't like patching software like this but if it works then it works.

EDIT 2

After adding the code above my software crashed again but now it throws an "UnauthorizedAccessException - Access to the port is denied" when i call _serialPort.Close();

System.UnauthorizedAccessException was unhandled
Message=Access to the port is denied.
Source=System
StackTrace:
at System.IO.Ports.InternalResources.WinIOError(Int32 errorCode, String str)
at System.IO.Ports.InternalResources.WinIOError()
at System.IO.Ports.SerialStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Ports.SerialPort.Dispose(Boolean disposing)
at System.IO.Ports.SerialPort.Close()
at BatteryCharger.CommunicationClass.ApiTransmission(TXpacket transmission) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\CommunicationClass.cs:line 436
at BatteryCharger.CommunicationClass.tx1(TXpacket packet, String callingMethod) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\CommunicationClass.cs:line 356
at BatteryCharger.XBee.setPin(String pinID, Byte status, XBee slave) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\XBee.cs:line 215
at BatteryCharger.XBee.setPins(Int32 pins, XBee slave) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\XBee.cs:line 177
at BatteryCharger.BatteryCharger.requestVoltage(Int32 block) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\BatteryCharger.cs:line 595
at BatteryCharger.BatteryCharger.requestVoltages() in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\BatteryCharger.cs:line 612
at BatteryCharger.Form1.RunCommandOn(List`1 battList, Int32 command, Double lowerLimit, Double upperLimit) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 522
at BatteryCharger.Form1.chargeBlock(Int32 blockNr, Double lowerLimit, Double upperLimit) in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 689
at BatteryCharger.Form1.<btnCheckCapacities_Click>b__13() in E:\Mijn Documenten\Research\Copy of BatteryCharger_V12\BatteryCharger\Form1.cs:line 619
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: 

What is going on here?

Michael Currie
  • 13,721
  • 9
  • 42
  • 58
Peter Leijen
  • 271
  • 1
  • 3
  • 4
  • 1
    It sounds like a hardware or driver issue to me. Most likely you'll need to close the serial port and re-open it. You *shouldn't* have to shut your program down and restart. – Jim Mischel Sep 10 '13 at 21:46
  • @JimMischel, thanks for the reply. I've tried closing and opening the port, but didn't stop my read background worker doh! I'll have another go at it. I have all the latest drivers etc installed so i don't think the error lies there. – Peter Leijen Sep 10 '13 at 21:53
  • What happens if there are no bytes to read when you call .ReadByte()? I think you should maybe check the serial port BytesToRead property before calling .ReadByte() – Andy Sep 10 '13 at 23:34
  • 2
    The FTDI driver is notorious for problems like this. You'll need to actually talk to the people that use your software. Very high odds that there's *another* problem in your code and they don't know what to do about it other than unplugging the USB connector. The only good thing that happens when they do that is that your code will crash and produce *something* diagnosable. That it doesn't fix the problem is not going to stop them, it does get you to pay attention so it actually works. You'll need to hit the road and spend a couple of days on-site to find the real problem. – Hans Passant Sep 10 '13 at 23:34
  • @andy i'll add the bytestoread to the code. as far as i know readbyte blocks until there is data present in the input buffer. – Peter Leijen Sep 11 '13 at 05:12
  • @HansPassant I'm the only one using the software at the moment... is there another driver that you recommend to use other than the FTDI driver? – Peter Leijen Sep 11 '13 at 05:13
  • @Andy adding the serialport.bytestoread check before reading made the software very slow, this may keep the serial port running. but i'm not going to test it like this. Is there another solution? – Peter Leijen Sep 11 '13 at 06:28
  • Peter, we had some issues using FTDI not releasing ports reliably if the cord was yanked while reading, but never tracked down for sure whether the problem was do to the dot net app it was written in, or whether the problem was ftdi itself. In our java aps, it seemed to disconnect if we asked the port to close and reopen (using serialio). We used the latest FTDI drivers, but this was 2 years ago... I do know we had some users having problems getting the FTDI drivers installed on window 7. Installing as administrator fixed that mostly but was painful. Was much easier on XP – Andy Dingfelder Sep 12 '13 at 01:26
  • Thanks Andy, I've rolled back the driver to 2.4.16.0 from 2009 and it hasn't failed yet (besides the power cut we had...). Fingers crossed! – Peter Leijen Sep 15 '13 at 07:16
  • Fingers crossed.... but I had problems with my own USB device (ATSAM7S) and Standard Windows USB/Serial Driver (our .inf just tells windows to use usbser.sys for standard USB/ACM/CDC modem-like device). Especially unplugging the device and pluging it back caused application crashes when used old .NET 3.5 SerialPort (there was a bug in Dispose() that caused exception to be thrown in gabage-collector thread). – firda Jul 27 '14 at 14:19
  • Even though it's more than 3 years ago... Any news/fixes/workarounds on this? Encountered the same thing with a device using FTDI under Windows 10, exception on writing (same stack trace) – Andreas Duering Apr 24 '17 at 13:30
  • Hi Andreas, I stick to a 2008 version of the ftdi Driver. I have found with win10 that it is most stable. – Peter Leijen Apr 27 '17 at 00:35

3 Answers3

3

In my experience 9 times out of 10 this happens when another thread (terminated or not) doesn't have exclusive access to the hardware port.

Try writing a wrapper for the port operations, most importantly the open/closing of it, using SyncLock. https://msdn.microsoft.com/en-us/library/3a86s51t.aspx

On a similar note, I would generally consider try/catches controlling hardware a bad practice, unless there's adequate exception handling.

The reason (which could apply here) is that in the case where an exception is thrown the hardware will lock, and what is worse, the exception will mask the true cause of the error(s).

In the code above I see output of messages in the style

DebugPrint(ex.Message); 

it would be quite better to do this like

DebugPrint(ex.tostring()); 

as this will also export the stack trace in the exception.

What I would do is implement an exception logger that writes those exceptions to a (time stamped) text file somewhere in the computer this is running. Following on the exception data logged (along with all pertinent information) can lead to a better understanding why exactly this happens.

Sotirios
  • 51
  • 3
2

I use intensively SerialPort class to communicate continuously with PLCs through USB Adapters for months without any break. So I cannot agree who says SerialPort .NET class does not work. Try to insert creation of class into thread, here is a sample of code with BackgroundWorker.

    void ThreadEngine_DoWork(object sender, DoWorkEventArgs e)
    {
        // Do not access the form's BackgroundWorker reference directly.
        // Instead, use the reference provided by the sender parameter.
        BackgroundWorker objBackgroundWorker = sender as BackgroundWorker;

        try
        {
            mSerialPort = new SerialPort(GetPortName(mPortName), DefaultBaudRate, Parity.Even, 7, StopBits.Two);
            mSerialPort.Open();
            objBackgroundWorker.ReportProgress(0);

            while (objBackgroundWorker.CancellationPending == false)
            {
                if (IOScanner(objBackgroundWorker, false) == true)
                {
                    ScannerStationData();
                    IsReady = true;
                    IsError = false;
                }
                else
                {
                    IsReady = false;
                    IsError = true;
                }
                Thread.Sleep(1);
            }

            // Performs last scan before thread closing
            if (objBackgroundWorker.CancellationPending == true)
            {
                IOScanner(objBackgroundWorker, true);
            }

            mSerialPort.Close();
            mSerialPort = null;
            e.Result = null;
        }
        catch (Exception objErr)
        {
            string sMessage = string.Format("PlcService.ThreadEngine_DoWork Err={0}", objErr.Message);
            mLogSysService.AddItem(sMessage);
            IsError = true;
        }
    }

Method IOScanner calls other methods for to communicate like following one.

    protected bool WriteDMWord(int iAddress, int[] aryValues)
    {
        bool bRetValue = true;
        try
        {
            mSerialPort.NewLine = "\r";
            mSerialPort.ReadTimeout = DefaultTimeout;
            string sTxData = HostLinkProtocol.BuildWriteDMWord(iAddress, aryValues);
            mSerialPort.WriteLine(sTxData);

            string sRxData = string.Empty;
            sRxData = mSerialPort.ReadLine();
            if (HostLinkProtocol.ParseWriteDMWord(sRxData) == true)
            {
                bRetValue = true;
            }
        }
        catch (Exception objErr)
        {
            Console.WriteLine("WriteDMWord [{0}]", objErr.Message);
            bRetValue = false;
        }
        return bRetValue;
    }
0

I use a FTDI USB, and I communicate with it on Serial .net class, sometimes on WindowsXp I have this kind of exception, I solve it with devcon.exe, I perform disable and enable and the error did not appears.

  • 2
    Could you please ellaborate? which error didn't appear any more? the "port acces denied" or "stopped responding"? – ephraim Aug 30 '20 at 07:00