jahwarp
jahwarp

Reputation: 1

C# COM Port slowing down to read one byte a second after sending message

This has had me stumped for a few days and after exhausting all googling and searching, I'm posting here cause I haven't figured it out. Sorry I know this is a lot. I really did try to solve this on my own, but I don't have a clue what's causing my issue.

A bit of background on my code:

I'm writing a library to communicate with a Sound Devices 788T 8 track recorder. I use its usb port to talk to it with a proprietary rs232 protocol called "C. Link" that Sound Devices made. Using the documentation released by Sound Devices on the protocol, I've successfully been able to send commands, read settings, and basically accomplish all the tasks I need to do one at a time. The issue I'm having comes when I send a user requested message during a routine "tick" function that updates constantly changing variables from the machine, i.e. generated SMPTE timecode, meter readouts, etc.

The message sends, I get a response, and the tick function goes on updating as it should for a few more seconds. After that, the reads from the open serial port slow down to one byte per second. I don't understand why. If I don't send a message, my test program can send and receive the regular tick messages at the expected speed indefinitely. But I send a command myself and get a reply, the tick messages only get received at the expected rate for a few seconds then it comes to a grinding halt at one byte per second. It doesn't crash or anything. It continues to send and receive the correct data, it just reads it from the serial port at one byte per second.

Both the user messages and the tick messages use the same function to send and receive data over the serial port. Here's the code with a brief explanation of the classes. Please excuse all my Debug.WriteLine commands. I used them and the counter to figure out if everything is running in the right order, and it is.

Any help would be greatly appreciated!

VM - a virtual machine, representing the real world 788T recorder. It gets things like the physical unit's timecode, meters, recording stats, etc. and stores them so we can access them without having to ask the real machine for them all the time or until they change. This class handles generating the C. Link messages and sends them to the commander to be sent over the serial port.

Commander - this is responsible for anything having to do with the serial connection. It sends and receives the serial data, and sends any replies it gets from the physical machine back to the VM.

Here's the VM's tick routine:

        private void DoPreTick(object ?source, ElapsedEventArgs e)
        {
            Debug.WriteLine("DoPreTick()" + counter + ": Tick timer triggered!  Disabling timer...");
            tickTimer.Enabled = false;

            if(!bIsTickAllowed)
            {
                Debug.WriteLine("DoPreTick()" + counter + ": Tick not allowed.  Won't be ticking this go around!");
            }

            if (!IsTicking())
            {


                Debug.WriteLine("DoPreTick()" + counter + ": Doing pre-tick!");
                if (GetCLinkState() == CLinkState.Online && !commander.CommsAreBusy())
                {
                    //Process our outbox first

                    if (bOutboxFullToken && !bIsProcessingUserMessage && !bOutboxProcessedToken)
                    {
                        Debug.WriteLine("DoPreTick()" + counter + ": Processing user message: " + outbox.ToBytes()[3]);
                        Task t = Task.Factory.StartNew(() => ProcessNextUserMessage());
                        t.Wait();
                    }

                    Debug.WriteLine("DoPreTick()" + counter + ": Ending pre-tick!");

                    if (!IsTicking() && bIsTickAllowed)
                    {
                        Debug.WriteLine("DoPreTick()" + counter + ": Tick allowed, running tick!");
                        DoTick();
                    }

                }

            }
            Debug.WriteLine("DoPreTick()" + counter + ": All ticking completed!  Re-enabling tick timer...");
            tickTimer.Enabled = true;
        }


        private void DoTick()
        {
            Debug.WriteLine("DoTick()" + counter + ": Doing Tick!");
            SetIsTicking(true);
            if (GetCLinkState() == CLinkState.Online && !commander.CommsAreBusy() && !bIsProcessingUserMessage && !bOutboxFullToken)
            {
                //Regular upkeep
                GetUnitTC();
                GetUnitMeters();                                       
            }

            SetIsTicking(false);
            Debug.WriteLine("DoTick()" + counter + ": Tick Finished!");
        } //Tick is called every tickInterval while we are connected to a 788



        private void ProcessNextUserMessage()
        {
            counter++;
            Debug.WriteLine("ProcessNextUserMessage()" + counter + ": Setting bIsProcessingUserMessage to true!");

            bIsProcessingUserMessage = true;

            if (outbox.GetUnitID() == 0xFE)
            {
                commander.SendCLinkBytes(outbox, false);
                Debug.WriteLine("ProcessNextUserMessage()" + counter + ": Message was 0xFE, not getting reply!");
                Thread.Sleep(250);

            }
            else
            {
                Debug.WriteLine("ProcessNextUserMessage()" + counter + ": Message needs a reply, overwriting outbox with reply!");
                CLinkMessage reply = commander.SendCLinkBytes(outbox, true);
                outbox = reply;
            }

            Debug.WriteLine("ProcessNextUserMessage()" + counter + ": All done.  Setting user message TCS to true, setting outboxfulltoken to false, setting outboxProcessedToken to true!");


            bOutboxProcessedToken = true;
            bIsProcessingUserMessage = false;
            bOutboxFullToken = false;

            Debug.WriteLine("ProcessNextUserMessage()" + counter + ": Re-enabling tick!");
            bIsTickAllowed = true;

        }

Here's the function the VM is using to add a user message to the outbox and wait for the response before returning it:

public CLinkMessage SendCLinkMessage(CLinkMessage message)
{
    if (IsTicking())
    {
        Debug.WriteLine("SendCLinkMessage()" + counter + ": Message Received while ticking!");
    }
    
    lock (userMessageTCS)
    {
        Debug.WriteLine("SendCLinkMessage(): Disabling tick for next go around!");
        bIsTickAllowed = false;

        if (IsAcceptingCLinkMessages())
        {

                SetIsAcceptingUserMessages(false);

                outbox = message;
            counter++;
                Debug.WriteLine("SendCLinkMessage()" + counter + ": Message added to outbox. Setting outboxfulltoken to true, setting outboxprocessed token to false.");

                
                bOutboxFullToken = true;
                bOutboxProcessedToken = false;

                Debug.WriteLine("SendCLinkMessage()" + counter + ": All done for now.  waiting for usermessagetcs result...");
                userMessageTCS = new TaskCompletionSource<bool>();
                userMessageTCS.Task.Wait();

            
         }

    }

    Debug.WriteLine("SendCLinkMessage()" + counter + ": Message send successful.  Setting acceptingUsereMessages to true and returning contents of outbox...");
    SetIsAcceptingUserMessages(true);
    return outbox;

}

Here's the function the Commander class uses to send the serial data:

public CLinkMessage SendCLinkBytes(CLinkMessage message, bool bReturnResponse = false)
{



    if (CommsAreBusy())
    {
        Debug.WriteLine("Commander: ERROR Comms are busy!");
    }


    if (commPort.IsOpen && !CommsAreBusy())
    {
        Debug.WriteLine("Commander: Comms not busy, sending message -  " + BitConverter.ToString(message.ToBytes()));
        SetCommsAreBusy(true);
        byte[] messageBytes = message.ToBytes();

        try
        {
            commPort.Write(messageBytes, 0, messageBytes.Length);
        }
        catch (Exception e)
        {
            Debug.WriteLine("Commander: ERROR writing bytes to commPort: " + e.Message);
        }

        
        if (bReturnResponse)
        {
            List<byte> reply = new List<byte>();

            try
            {
                Debug.WriteLine("Commander.SendCLinkBytes(): Reading Byte 1: header");                       
                byte b = new byte();


                //Header check

                //Debug.WriteLine("Commander: Reading reply...");
                b = (byte)commPort.ReadByte();

                if (b == 0xA5)
                {
                    //Debug.WriteLine("Response detected!");
                    reply.Add(b);

                    //UnitID check
                    Debug.WriteLine("Commander.SendCLinkBytes(): Reading Byte 2: unitID");
                    b = (byte)commPort.ReadByte();

                    if (b < 63 || b == 0xFE)
                    {
                        reply.Add(b);


                        //Length
                        Debug.WriteLine("Commander.SendCLinkBytes(): Reading Byte 3: Length");
                        b = (byte)commPort.ReadByte();

                        if (b > 2 && b <= 252)
                        {
                            reply.Add(b);

                            //DATA
                            int messageLength = reply[2];

                            for (int i = 0; i < messageLength; i++)
                            {
                                reply.Add((byte)commPort.ReadByte());
                                Debug.WriteLine("Commander.SendCLinkBytes(): Length Recorded as: " + messageLength + ".  Adding rest of message to response.");
                            }

                            //VERIFY CHECKSUMS
                            if (VerifyCLinkChecksums(reply.ToArray()))
                            {
                                Debug.WriteLine("Commander.SendCLinkBytes(): Verifying checksums on response.");
                                message.SetResponse(reply.ToArray());
                                //SetCommsAreBusy(false);
                            }
                            else
                            {
                                Debug.WriteLine("Commander: ERROR bad checksums on response!");
                            }



                        }

                    }



                }
                else if (b != 0x00 && b != 0xA5)
                {
                    Debug.WriteLine("Commander: Bad data at commport!");
                }
                else if (b == 0x00)
                {
                    Debug.WriteLine("Byte at comm port was 0x00, not a C. Link header!");
                }


            }


            catch
            {
                Debug.WriteLine("Commander: Sending bytes timed out waiting for reply to: " + BitConverter.ToString(message.ToBytes()));
            }

        }
        
        if (message.GetResponse()[0] == 0xA5 && message.GetResponse()[3] == 0xF0 && message.GetResponse().Length == 6)
        {
            Debug.WriteLine("Legacy acknowledge received!");
        }

        if (message.GetMessageType() == CLinkMessage.MessageType.Get)
        {
            Debug.WriteLine("Commander: Reply was " + BitConverter.ToString(message.GetResponse()));
        }

        SetCommsAreBusy(false);
        return message;
    }

    Debug.WriteLine("Message Failed to Send:\n " + "Commport open status: " + commPort.IsOpen.ToString() + "\n Comms are busy status: " + CommsAreBusy().ToString() + "\n" + message.DumpValues());
    message.bFailedToSend = true;
    return message;
    
}

I've tried flushing what's in the commport with the serial port's "ReadExisting()" function just for cleanup before and after the messages are send/received thinking some kind of buffer was full, but that didn't seem to do anything noticeable.

Upvotes: 0

Views: 39

Answers (0)

Related Questions