problem with two .NET threads and hardware access
Asked Answered
E

4

8

I'm creating an application which communicates with the device via FT2232H USB/RS232 converter. For communication I'm using FTD2XX_NET.dll library from FTDI website.
I'm using two threads:

  • first thread continuously reads data from the device
  • the second thread is the main thread of the Windows Form Application

    I've got a problem when I'm trying to write any data to the device while the receiver's thread is running. The main thread simply hangs up on ftdiDevice.Write function.

    I tried to synchronize both threads so that only one thread can use Read/Write function at the same time, but it didn't help.

    Below code responsible for the communication. Note that following functions are methods of FtdiPort class.

    Receiver's thread

    
            private void receiverLoop()
            {
                if (this.DataReceivedHandler == null)
                {
                    throw new BackendException("dataReceived delegate is not set");
                }
    
                FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;
                byte[] readBytes = new byte[this.ReadBufferSize];
    
                while (true)
                {
                    lock (FtdiPort.threadLocker)
                    {
                        UInt32 numBytesRead = 0;
    
                        ftStatus = ftdiDevice.Read(readBytes, this.ReadBufferSize, ref numBytesRead);
    
                        if (ftStatus == FTDI.FT_STATUS.FT_OK)
                        {
                            this.DataReceivedHandler(readBytes, numBytesRead);
                        }
                        else
                        {
                            Trace.WriteLine(String.Format("Couldn't read data from ftdi: status {0}", ftStatus));
                            Thread.Sleep(10);
                        }                    
                    }
                    Thread.Sleep(this.RXThreadDelay);
    
                }
            }
    


    Write function called from main thread

    
    
    
        public void Write(byte[] data, int length)
        {
            if (this.IsOpened)
            {
                uint i = 0;
    
                lock (FtdiPort.threadLocker)
                {
                    this.ftdiDevice.Write(data, length, ref i);
                }
    
                Thread.Sleep(1);
                if (i != (int)length)
                {
                    throw new BackendException("Couldnt send all data");
                }
            }
            else
            {
                throw new BackendException("Port is closed");
            }
        }
    


    Object used to synchronize two threads

    
    static Object threadLocker = new Object();
    

    Method that starts the receiver's thread

    
            private void startReceiver()
            {
                if (this.DataReceivedHandler == null)
                {
                    return;
                }
                if (this.IsOpened == false)
                {
                    throw new BackendException("Trying to start listening for raw data while disconnected");
                }
                this.receiverThread = new Thread(this.receiverLoop);
                //this.receiverThread.Name = "protocolListener";
                this.receiverThread.IsBackground = true;
                this.receiverThread.Start();
            }
    
    The ftdiDevice.Write function doesn't hang up if I comment following line:
    ftStatus = ftdiDevice.Read(readBytes, this.ReadBufferSize, ref numBytesRead);
  • Euh answered 13/3, 2010 at 16:47 Comment(1)
    This looks like a race condition, the read is blocking...hence the ftdiDevice.Write doesn't hang up...Is there not an asynchronous version of the read?Unmanly
    I
    4

    A few things:

    1. Check to see if your Read call is blocking. If so, you might not be able to call Write while the Read is blocking waiting for a response. Your API documentation may have more details on this.

    2. Some APIs do not support multiple threads very well, even when synchronizing access. If that's the case here, you can use a design where you delegate your Write commands to your comm thread. When I've used this pattern in the past, I typically queue up some sort of Command class containing the information I wish to write, and either use a threading Signal class to allow my calling 'command' methods to block or provide some sort of asynchronous notification.

    Indivisible answered 13/3, 2010 at 16:59 Comment(0)
    H
    6

    An alternative is to use the event notification mechanism from FTDI, this way you don't need a blocking thread to read out data:

    public FTDISample()
    {
        private AutoResetEvent receivedDataEvent;
        private BackgroundWorker dataReceivedHandler;
        private FTDI ftdi;
    
        public FTDISample(string serialNumber){
            ftdi = new FTDI();
            FTDI.FT_STATUS status = ftdi.OpenBySerialNumber(serialNumber);
            receivedDataEvent = new AutoResetEvent(false);
            status = mFTDI.SetEventNotification(FTDI.FT_EVENTS.FT_EVENT_RXCHAR, receivedDataEvent);
            dataReceivedHandler = new BackgroundWorker();
            dataReceivedHandler.DoWork += ReadData;
            if (!dataReceivedHandler.IsBusy)
            {
                dataReceivedHandler.RunWorkerAsync();
            }
        }
    
        private void ReadData(object pSender, DoWorkEventArgs pEventArgs)
        {
            UInt32 nrOfBytesAvailable = 0;
            while (true)
            {
                // wait until event is fired
                this.receivedDataEvent.WaitOne();
    
                // try to recieve data now
                FTDI.FT_STATUS status = ftdi.GetRxBytesAvailable(ref nrOfBytesAvailable);
                if (status != FTDI.FT_STATUS.FT_OK)
                {
                    break;
                }
                if (nrOfBytesAvailable > 0)
                {
                    byte[] readData = new byte[nrOfBytesAvailable];
                    UInt32 numBytesRead = 0;
                    status = mFTDI.Read(readData, nrOfBytesAvailable, ref numBytesRead);
    
                    // invoke your own event handler for data received...
                    //InvokeCharacterReceivedEvent(fParsedData);
                }
            }
        }
    
        public bool Write(string data)
        {
            UInt32 numBytesWritten = 0;
            ASCIIEncoding enconding = new ASCIIEncoding();
            byte[] bytes = enconding.GetBytes(data);
            FTDI.FT_STATUS status = ftdi.Write(bytes, bytes.Length, ref numBytesWritten);
            if (status != FTDI.FT_STATUS.FT_OK)
            {
                Debug.WriteLine("FTDI Write Status ERROR: " + status);
                return false;
            }
            if (numBytesWritten < data.Length)
            {
                Debug.WriteLine("FTDI Write Length ERROR: " + status + " length " + data.Length +
                                " written " + numBytesWritten);
                return false;
            }
            return true;
        }
    
    Heliogravure answered 16/4, 2010 at 9:17 Comment(0)
    I
    4

    A few things:

    1. Check to see if your Read call is blocking. If so, you might not be able to call Write while the Read is blocking waiting for a response. Your API documentation may have more details on this.

    2. Some APIs do not support multiple threads very well, even when synchronizing access. If that's the case here, you can use a design where you delegate your Write commands to your comm thread. When I've used this pattern in the past, I typically queue up some sort of Command class containing the information I wish to write, and either use a threading Signal class to allow my calling 'command' methods to block or provide some sort of asynchronous notification.

    Indivisible answered 13/3, 2010 at 16:59 Comment(0)
    E
    2

    I've found more detailed API documentation. Indeed the ftdiDevice.read function is blocking unless you set the readTimeout value other then 0. Setting this timeout value solved the problem.
    Thanks for your quick response.
    Regards

    Euh answered 13/3, 2010 at 17:34 Comment(0)
    G
    1

    Checking out the API, it looks to me that the driver is capable of emulating a COM port. I see the GetComPort() method returning a "COMx" string. That makes it pretty likely that you can use the System.IO.Ports.SerialPort class. Which already does what your wrapper is trying to do, it supports a DataReceived event. Worth a shot.

    Gurgitation answered 13/3, 2010 at 17:50 Comment(3)
    I've already tried to use SerialPort class, but I've found the problem described in stackoverflow.com/questions/924070 and didn't find any workaround for that.Euh
    @mack: tip #4 here: blogs.msdn.com/bclteam/archive/2006/10/10/…5B00_Kim-Hamilton_5D00.aspxGurgitation
    nice tip, but I cannot use SerialPort class anyway. I checked the maximum baud rate and it is below my expectation - about 2Mb/s. With direct access to the hardware I can have even 4.5Mb/sEuh

    © 2022 - 2024 — McMap. All rights reserved.