locked
C# Problem with multiple SerialDataRecieved event handlers RRS feed

  • Pergunta

  • Hi all,

    I'm relatively new to C# and have no formal programming experience so apologies in advance if the way I do things are bad practice etc - I generally implement things in a way which seems logical to me! As a small project I'm making a toy car which can drive itself, the problem I've currently got is reading data from two serial port devices at once. The way I tend to work is to write smaller applications to do one function and get that working before integrating it into a larger application which will pull all the functions together.

    I've written a "Serial_Port_Communications" class which allows you to pass a serial port object into it and then perform some typical functions: open a port, close it, read data, write data etc. Thats common code across all of my serial devices (they all need to do the same operation so why re-write it?)

    Then for each device I've got a class which handles the operations and data specific to it: GPS & Compass. Each of those two create instances of the Serial_Port_Communications class.

    In the GPS & Compass classes, they each have a SerialPort object which has an eventhandler assigned to it for when data is received e.g GPS.datareceived += & Compass.datareceived +=...

    They each work flawlessly when they run as the mini-applications i.e. I can Parse the GPS data that is received and I can read the bearing from the compass. I can also run the two seperate mini applications in two different debugging windows at the same time and it still works. The problem occurs when I try and join it all up! I tried simply adding the existing code into a single project and creating an instance of each of the classes. If I connect to the compass, it works fine. If i connect to the GPS it works fine. If I run them both at the same time however, the GPS always takes preference? It's as though the datareceived event stops working for the compass when the GPS one is initialized. If I stop the GPS the compass works again.

    I've been banging my head against the wall for a few days now and as people always say in forums: google is your friend - Well three days later and I've not got to the bottom of my problem so I'm now writing a plea for help!!

    Any suggestions of what I might be doing wrong/ doing things in an odd way would be greatly appreciated! I'm happy to post code but wasn't sure where the problem may lie so didn't want to just post a big block of code.

    Thanks in advance!

    Paul.

    • Movido Rudedog2 sexta-feira, 10 de setembro de 2010 13:44 : Move to more appropriate forum (From:Visual C# General)
    sexta-feira, 10 de setembro de 2010 13:22

Respostas

  • Observe the Performance graph tab in Task Manager.  Note the CPU usage of each core. 

    Add this statement:

            spPort.ReceivedBytesThreshold = 10;

    To this code block:

        public bool blConnect_GPS(string strCOMPort, int intBaudRate)
        {
          
    if (Serial_Comms.blOpen_Port(strCOMPort, intBaudRate, spPort))
          {
            spPort.ReceivedBytesThreshold = 10;
            spPort.DataReceived += 
    new SerialDataReceivedEventHandler(spPort_DataReceived);
            
    return true;
          }
          
    return false;
        }

    Observe Task Manager and the relative performance of your program.

    Change the Threshold value to 100 and again observe Task Manager and the relative performance of your program.

    Any difference?

    • Marcado como Resposta SmallPaulUK sábado, 11 de setembro de 2010 11:30
    sábado, 11 de setembro de 2010 10:02

Todas as Respostas

  • What's the connection between the two ports?  They should be completely independent of each other.  What COM port is the GPS on and what COM port is the compass on?
    sexta-feira, 10 de setembro de 2010 13:47
  • Firstly, thanks for a response!

    There is no connection, they are two seperate devices on two different ports (COM3 = GPS, COM4 = Compass).

    Within the GPS class I've got:

    SerialPort spGPS = new SerialPort();

    spGPS.DataReceived += new SerialDataReceivedEventHandler(spGPS_DataReceived);

    Within the Compass class I've got:

    SerialPort spCompass = new SerialPort();

    spCompass.DataReceived += new SerialDataReceivedEventHandler(spCompass_DataReceived);

    Basically in the inidiviual mini applications the datareceived event fires and it updates the GUI using invoke. If I try running the two applications from a common application then the GPS tends to take over and the compass datareceived event does nothing until the GPS stops. From what I've read I don't see any issues with having multiple serial port objects and event handlers in the same application?

    Is there anything else you would like to know?

    Thanks again,

    Paul.

    sexta-feira, 10 de setembro de 2010 14:16
  • Can you upload your application to Skydrive?  Are you doing the GPS calculations on the DataReceived thread or the UI thread?  Does the UI thread remain reponsive?
    sexta-feira, 10 de setembro de 2010 14:26
  • I don't know what Skydrive is so probably not. I don't mind posting code on here though, it might just be a bit clunky...

    The datareceived event in the GPS class reads the data and passes it out to another function which distinguishes what message has been received (NMEA standard) and then updates variables depending on what message was received.

    In the compass class, there are 4 bytes returned each time and they represent: the bearing (2-bytes), pitch & roll. They are pretty much the raw data so they just update variables and no real processing is done.

    The GUI seems to run fine and doesn't seem to have any issues.

    I use get and set to update the GPS and Compass variables and event handlers to signify that the data has changed. The GUI subscribes to the data event handlers to update it upon data change.

    I've tried putting breakpoints in the code on the datareceived routines and the compass one isn't firing when the GPS one is running. It's like they're conflicting? I've tried probing the sender on the data received class to see if for some strange reason the data from both com ports are going to the same place but it works as expected - the com port the eventhandler is tied to receives the correct data.

    Paul

    sexta-feira, 10 de setembro de 2010 14:56
  • To post on these forums you need a hotmail account.  Skydrive is your storage location.

    Of the code I can see, every thing looks fine.

    sexta-feira, 10 de setembro de 2010 15:09
  • I'd never heard of Skydrive; I've just logged into it for the first time so lets give it a go.

    What's the most useful way of uploading code? Zip the whole project, just include the .cs files?

    Paul

    sexta-feira, 10 de setembro de 2010 15:19
  • I'd never heard of Skydrive; I've just logged into it for the first time so lets give it a go.

    What's the most useful way of uploading code? Zip the whole project, just include the .cs files?

    Paul


    Clean the solution, delete the Bin and Obj folders and delete the .suo and .user files.  Navigate in explorer to your solution folder, right click and Send To Compressed (zipped) folder.  Upload the zipped folder.
    sexta-feira, 10 de setembro de 2010 15:24
  • Ok, I've uploaded the Compass code. The GPS is a mirror image with just a few things tweaked.

    As I said though they both work perfectly by themselves it's just when I put them both together that the go awry...

    I've put it in my public folder on skydrive, I'm guessing this link is how to access it:

    http://cid-82910e729194a96a.skydrive.live.com/redir.aspx?page=browse&resid=82910E729194A96A!231&type=6&authkey=emqvvzrezWc$&Bsrc=EMSHHM&Bpub=SN.Notifications

    Paul

    sexta-feira, 10 de setembro de 2010 15:40
  • I looked at your code.  The GPS code shouldn't be a mirror image of the Compass code.  For GPS you should be reading lines and parsing the message(s) of interest. 

    I don't see any synchronization or error checking code in the Compass code.  You're counting of the 4 bytes in the recieved buffer being the correct bytes.  Is that working?

    The two serial ports should be completely independent unless you're using a single core CPU. 

    Not sure what looking at the Compass code will do when the problem is with the GPS code.

    sexta-feira, 10 de setembro de 2010 17:47
  • By mirror image I meant that for the serial port bit it re-uses 99% of the code, following exactly the same structure. i.e. create SerialPort object, open it, hook the datareceived event handler, wait for data to do something with.

    Whilst writing this reply, I've just been going through the code and discovered something which I didn't realise I'd done:

    When the datareceived events fire, the respective event handlers grab the data from the respective serial port. I thought that for the GPS code the data was read using strRead_String (Serial_Communications) and that the compass data was read using the btarrRead_byte routine. The GPS code however doesn't use that routine at all. I've got it reading from the port directly using: GPSPort.ReadTo("$") ($ denoting the start of another GPS message). Because I'm not reading all of the data at the port could it be that the datareceived event is constantly firing/never properly finishing and therefore not letting the other one get chance to do anything? I might try reading all of the data from the GPS serial port into a buffer and then splitting it into seperate messages using the '$' character... I've never really done anything on threading so don't know if what I've just said makes sense.

    As for the synchronisation of the compass data, it's not a constant data stream, it requires polling to return data - (I guess another difference!!) and so I can guarantee that unless there is an error, there will always be the correct number of bytes at the port. When the port's opened it polls the device, relying on the datareceived event to read the returned data and then send another poll. Without the datarecevied event firing, it will never send any more data; hence when the GPS port is open and reading data it seems to stop the compass from returning data.

    I guess I've got a few more avenues to explore!!

    I'll try modifying the code as I described above and see what happens.

    If you've got any suggestions then I'd be very grateful!

    Thanks again for taking the time to help me!

    Paul

    sexta-feira, 10 de setembro de 2010 18:25
  • For GPS, use ReadLine in the DataReceived event.  For each line the event will fire after the port receives a byte.  The event will then block (it's on its own thread) until it receives a complete NEMA message.  Process the one(s) you want and discard the rest.  Use Invoke to pass info back to the UI thead so the event doesn't fire again until you've finished.
    sexta-feira, 10 de setembro de 2010 18:36
  • That's essentially what I do already, well like I said I use ReadTo, not ReadLine. When you create two serialport objects, will that create them on different threads? What I'm getting at is, could the GPSSerialPort object block the CompassSerialPort object from reading if they are two seperate entities? 
    sexta-feira, 10 de setembro de 2010 18:42
  • Two SerialPorts are like any other unique objects; entities unto themselves.  They can only affect each other when they complete for time on the same CPU core.  Their respective DataReceived events occur on different threads, but they Invoke the same thread.  All of this is occurring extremely slowly, in the millisecond time range.  You have to be doing something overt to cause one SerialPort to affect the other.
    sexta-feira, 10 de setembro de 2010 18:50
  • That was the way I thought it worked so I'm still not quite sure whats going on; how one seems to be affecting the other. I'm just trying some other stuff but if I'm still struggling, I'll try tidying up the GPS code and upload that too!
    sexta-feira, 10 de setembro de 2010 19:10
  • Ok so I've tried re-writing some of the GPS code - mainly around how the data is read when the datareceived event is triggered.

    Instead of using ReadTo('$'), I've tried using ReadLine() instead; The GPS code still works fine in isolation. When I run the compass code alongside it, it works in fits-and starts. Basically when there is data being read from the GPSPort, the CompassPort freezes. It seems like only one datareceived event can be running at any one time and because there is a lot more data coming from the GPS receiver, it tends to over power the compass opeations. It's like the ReadLine operation takes longer than the ReadTo and so it seems that the compass can sneak in and do a bit of work before the next GPS cycle. I tried adding a Thread.sleep() to the GPS datareceived handler and it did exactly the same thing and you could physically see the compass data stopping when the GPS was reading.

    I really don't get how these two events are related to each other when there are seperate serialport objects being initiated from different classes adn with different event handlers. The CPU usage is very low so it's not like it should be struggling for resource.

    Out of ideas again.

    I'll try tidying up the GPS code and upload that to see if anyone else can spot where it's all going wrong!

    Paul

    sexta-feira, 10 de setembro de 2010 20:54
  • Ok, so I've uploaded my GPS code too - It follows a very similar format to the Compass code.

    I'm not sure where to go from here.

    http://cid-82910e729194a96a.skydrive.live.com/redir.aspx?page=browse&resid=82910E729194A96A!231&type=6&authkey=emqvvzrezWc$&Bsrc=EMSHHM&Bpub=SN.Notifications

    Paul

    sexta-feira, 10 de setembro de 2010 21:53
  • Observe the Performance graph tab in Task Manager.  Note the CPU usage of each core. 

    Add this statement:

            spPort.ReceivedBytesThreshold = 10;

    To this code block:

        public bool blConnect_GPS(string strCOMPort, int intBaudRate)
        {
          
    if (Serial_Comms.blOpen_Port(strCOMPort, intBaudRate, spPort))
          {
            spPort.ReceivedBytesThreshold = 10;
            spPort.DataReceived += 
    new SerialDataReceivedEventHandler(spPort_DataReceived);
            
    return true;
          }
          
    return false;
        }

    Observe Task Manager and the relative performance of your program.

    Change the Threshold value to 100 and again observe Task Manager and the relative performance of your program.

    Any difference?

    • Marcado como Resposta SmallPaulUK sábado, 11 de setembro de 2010 11:30
    sábado, 11 de setembro de 2010 10:02
  • Ok, well I tried that this morning; here are the results and it's good news!!

    GPS standalone without threshold set: CPU 4-5%
    GPS standalone threshold 10: CPU 6-8%
    GPS standalone threshold 100: CPU 3-6%

    GPS & Compass without threshold set: CPU 5-8%
    GPS & Compass threshold 10: CPU 15-25%
    GPS & Compass threshold 100: CPU 4-5%

    Combined GPS & Compass, no threshold set: CPU 3-8% (Compass is being blocked)
    Combined GPS & Compass, threshold 10: CPU 6-15% (Compass works in fits and starts)
    Combined GPS & Compass, threshold 100: CPU 5-6% (Seems to work perfectly!!)

    So it seems that I've finally got a working solution, so thank you very very much for that!!!

    Can I just check whether I understand whats going on:

    ReceivedBytesThreshold sets the number of bytes required at the serial port buffer before the serialdatareceived event triggers. By not setting the value, does it default to as soon as any data is at the buffer it triggers? As there is a constant data stream coming from the GPS receiver it's always firing. By upping the amount of data required at the port to 100 bytes before the event fires it's letting other things happen (i.e. the compass data to be read) in between?

    I still don't get whats going on behind the scenes because as I understood it, the two serial ports should be two completely seperate entities and so not affect each other in any way?

    Thank you again for helping me!

    Paul

    sábado, 11 de setembro de 2010 11:41
  • What's going on is that the .NET SerialPort isn't a very good component.  Why is a 10 threshold worse than the default 1 threshold? 

    I suggest that you use a timer instead of the DataReceived event for the GPS.

        Timer tmr = new Timer();
        
    public bool blConnect_GPS(string strCOMPort, int intBaudRate)
        {
          
    if (Serial_Comms.blOpen_Port(strCOMPort, intBaudRate, spPort))
          {
            tmr.Tick += 
    new EventHandler(spPort_DataReceived);
            tmr.Interval = 1000;
            tmr.Start();
            
    return true;
          }
          
    return false;
        }
        
    public bool blDisconnect_GPS()
        {
          tmr.Tick -= 
    new EventHandler(spPort_DataReceived);
          
    return Serial_Comms.blClose_Port(spPort) ? true : false;
        }
        
    private void spPort_DataReceived(object sender, EventArgs e)        
        {
          try
          {
            
    while (spPort.BytesToRead > 0)
            {
              
    string strMessage = Serial_Comms.strRead_To("$", spPort);
              
    if (blChecksum(strMessage))
              {
                GPSData.GPSMessage = strMessage;
                
    if (strMessage.Contains("GPGGA"))
                  msg_GPGGA(strMessage);
                
    if (strMessage.Contains("GPRMC"))
                  msg_GPRMC(strMessage);
                
    if (strMessage.Contains("GPGSA"))
                  msg_GPGSA(strMessage);
              }
            }
          }
          
    catch (IOException)
          {
          }
        }

    sábado, 11 de setembro de 2010 12:04
  • It's intersting that that is how i originally started with my code! I had a timer which 'ticked' every second which was when I checked to see if data had arrived. I then learnt a bit more about c# and learnt a bit about event driven code and thought that was better practice than having a load of timers. So thats when I re-factored the code to include events.

    Well thank you so much for all your help! Hopefully I'll manage to go forward from here and get the code doing what I want!

    Paul

    sábado, 11 de setembro de 2010 16:03
  • It is ages after your post, but maybe somebody else can benefit from it.

    So, just make sure that you do not initialize serial port object with assigned keyword static and you should have DataReceived events triggered from all ports.


    segunda-feira, 12 de outubro de 2020 15:18