Panda - Serial Com1 - Works w/Loopback - Garbage Panda to Panda

Sorry for the long title but trying to hit keywords. I’m working on a project I have started and stopped multiple times, using the original Fez Panda. It is a project that has two pieces. One is serial communications, and the other will be logging what is captured during the communications to a memory card.

I’m still at the serial communications piece. I am starting small with a very simple program as I find an architecture to work with. I have one sample working when I have the Com1 port hooked up in loop back so that the data sent out is the same that comes in. I can run it all the way up from 300baud to 19200 and it works. But as soon as I move the code to a second Panda, and hook both panda’s together, I run into problems.

The biggest problem is that I’m getting garbage on the serial line and I’m not sure how to handle it. Is it something I need to handle, or is it a result of something I am doing wrong? The two are connect to each other by two jumper wires. One set plugged into Panda 2, and the other set clipped to Panda 1 (with tape).

Panda 1 has a LCD Keypad Shield on it and I’m using it’s buttons to initiate the message and the code for that is below:



using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.IO.Ports;
using GHIElectronics.NETMF.FEZ;

namespace pandaScreen
{
    public class mainProg
    {
        readonly static SerialPort _serialPort = new SerialPort("COM1", 300) { ReadTimeout = 0 };
       
        static int readcount = 0;
        readonly static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false);
        static string[] recv;
        static bool HandledKeyPress = true;

        public static void Main()
        {
        
            byte[] yx_data = System.Text.UTF8Encoding.UTF8.GetBytes ("Y                  ");
            byte[] qx_data = System.Text.UTF8Encoding.UTF8.GetBytes ("Q                  ");
            byte[] yx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Y - Pressed");
            byte[] qx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Q - Pressed");

            FEZ_Shields.KeypadLCD.Initialize();
            FEZ_Shields.KeypadLCD.TurnBacklightOn();
            FEZ_Shields.KeypadLCD.CursorHome();
            FEZ_Shields.KeypadLCD.Print("Initialized");


            _serialPort.Open();
            _serialPort.Flush();
            while (true)
            {
                switch (FEZ_Shields.KeypadLCD.GetKey())
                {
                    case FEZ_Shields.KeypadLCD.Keys.Left:
                        if (HandledKeyPress)
                        {
                            HandledKeyPress = false;
                            _serialPort.Write(qx_data, 0, qx_data.Length);
                            FEZ_Shields.KeypadLCD.Clear();
                            FEZ_Shields.KeypadLCD.SetCursor(0, 0);
                            FEZ_Shields.KeypadLCD.Print("Pressed Left Q");

                            Debug.Print("Left 'Q' Pressed:");
                        }
                        break;
                    case FEZ_Shields.KeypadLCD.Keys.Right:
                        if(HandledKeyPress)
                        {
                            HandledKeyPress = false;
                            _serialPort.Write(yx_data, 0, yx_data.Length);
                            FEZ_Shields.KeypadLCD.Clear();
                            FEZ_Shields.KeypadLCD.SetCursor(0, 0);
                            FEZ_Shields.KeypadLCD.Print("Pressed Left Y");
                            Debug.Print("Right 'Y' Pressed:");
                        }
                        break;
                    case FEZ_Shields.KeypadLCD.Keys.Up:
                        HandledKeyPress = true;
                        break;
                    default:
                        break;
                }

                Thread.Sleep(100);
                ReadSerial();
                if (recv != null)
                {
                    if (recv[0].IndexOf("Y") >= 0)
                    {
                        _serialPort.Write(yx_data2, 0, yx_data2.Length);
                        recv = null;
                        Debug.Print("Wrote Y Response");
                        led.Write(false);
                    }
                    else
                    {
                        if (recv[0].IndexOf("Q") >= 0)
                        {
                            _serialPort.Write(qx_data2, 0, qx_data2.Length);
                            recv = null;
                            Debug.Print("Wrote Q Response");
                            led.Write(false);
                        }
                        else
                        {
                            if (recv[0] == "Message")
                            {
                                FEZ_Shields.KeypadLCD.Clear();
                                FEZ_Shields.KeypadLCD.SetCursor(1, 1);
                                FEZ_Shields.KeypadLCD.Print(recv[1]);
                                HandledKeyPress = true;
                            }
                        }
                    }
                }
            }
        }

        static void ReadSerial()
        {

            readcount = 0;
            byte[] buffer = new byte[300];
            if (_serialPort.BytesToRead >= 19)
            {
                readcount = _serialPort.Read(buffer, 0, buffer.Length);
                //_serialPort.DiscardInBuffer();
                if (readcount > 0)
                {
                    recv = new string(System.Text.UTF8Encoding.UTF8.GetChars(buffer)).Split('\n')[0].Split('|');
                    if (recv.Length > 1)
                    {
                        led.Write(true);
                        //Debug.Print("Received a response");
                        //'good value
                    }
                }
            }
            else
            {
                if (recv != null)
                {
                    recv = null;
                    //Debug.Print("Set recv Null");
                }
            }
        }

    }
}

Panda 2 is a plain old panda with no keypad so it’s code is slightly modified by based on the Panda 1 code and looks like this:



using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using System.IO.Ports;

namespace pandaSerial
{
    public class Program
    {
        readonly static SerialPort _serialPort = new SerialPort("COM1", 300) { ReadTimeout = 0 };

        static int readcount = 0;
        readonly static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false);
        static string[] recv;
  

        public static void Main()
        {
            byte[] yx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Y - Pressed");
            byte[] qx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Q - Pressed");
            
            _serialPort.Open();
            _serialPort.Flush();
            while (true)
            {
                Thread.Sleep(1000);
                ReadSerial();
                if (recv != null)
                {
                    if (recv[0].IndexOf("Y") >= 0)
                    {
                        _serialPort.Write(yx_data2, 0, yx_data2.Length);
                        recv = null;
                        Debug.Print("Wrote Y Response");
                        led.Write(false);
                    }
                    else
                    {
                        if (recv[0].IndexOf("Q") >= 0)
                        {
                            _serialPort.Write(qx_data2, 0, qx_data2.Length);
                            recv = null;
                            Debug.Print("Wrote Q Response");
                            led.Write(false);
                        }
                        else
                        {
                            recv = null;
                        }
                    }
                }
            }
        }
        static void ReadSerial()
        {
            readcount = 0;
            byte[] buffer = new byte[300];
            if (_serialPort.BytesToRead >= 19)
            {
                readcount = _serialPort.Read(buffer, 0, buffer.Length);
                //_serialPort.DiscardInBuffer();
                if (readcount > 0)
                {
                    recv = new string(System.Text.UTF8Encoding.UTF8.GetChars(buffer)).Split('\n')[0].Split('|');
                    if (recv.Length > 1)
                    {
                        led.Write(true);
                        //Debug.Print("Received a response");
                        //'good value
                    }
                }
            }
            else
            {
                if (recv != null)
                {
                    recv = null;
                    //Debug.Print("Set recv Null");
                }
            }
        }

    }
    
}

Your read buffer should be instantiated the same length as the incoming bytes. Try this for the second line of your ReadSerial() method:


 byte[] buffer = new byte[_serialPort.BytesToRead];

Also, why do you need the first if statement in this method? There’s no guarantee that all bytes sent will trigger only one read event. You may have several occur before you get the whole thing.

The buffer size was set to a size to a number with no relation to the current data passing. I wasn’t sure when I made it. But, the buffer size should have no impact on the data transitioning from the UARTS internal read buffer. It will only fill what it has, and the rest will be zero filled.

The

if (_serialPort.BytesToRead >= 19)

Was initially used to pass only my message through which is padded to 19 characters. For my test I could set the buffer = new byte[19] even as that is the max size I send.

I’ll try and clean some of that up and test to make sure it isn’t necessary for some reason. Still looking for ideas on the garbage characters.

The following line is incorrect. You declare your buffer with 300 bytes. The third parameter of the Read is the number of bytes to read, which is certainly not a constant value of 300. IF you change the buffer length as I suggested, then this line would work (indirectly fixing the issue). The garbage characters are whatever happens to be in memory out past the actual rx buffer length.

 readcount = _serialPort.Read(buffer, 0, buffer.Length);

You said the two Pandas are connected with TWO jumper wires. If you have bi-directional data flowing, then you need three wires; Tx, Rx and Ground.

Yeah, I smell a GND problem too.

this is very bad BTW

static void ReadSerial()
        {
            readcount = 0;
            byte[] buffer = new byte[300];
            if (_serialPort.BytesToRead >= 19)
            {
          

it should be


static byte[] buffer = new byte[300];
static void ReadSerial()
        {
            readcount = 0;
            
            if (_serialPort.BytesToRead >= 19)
            {
          

GND ::slight_smile: hahaha… yep, did not even think about a common ground. Once I saw the word, a light bulb went off. :smiley:

That fixed it taking care of the anomalous data. I’ve re-factored the code to make it more efficient and taking into account some things seen here. Garbage collector now doesn’t need to run while it processes.

LCD Keypad side now uses left button to send a Q and Right button to send a Y. The Up button is used to reset the handler for the keypad and the buffer tracking counter, used during debugging.

A ‘Q’ is sent down the serial port and line 1 of the keypad reports the button was pressed. The Panda on the other end of the serial connection parses the Q and returns the string “Message|Q - Pressed”. That string is then displayed on line2 of the lcd keypad.

The project is stored in SVN here: http://code.google.com/p/pandaserialchat/
And revised code is below. Comments welcome.
LCD Keypad Shielded Fez Code


using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.IO.Ports;
using GHIElectronics.NETMF.FEZ;

namespace pandaScreen
{
    public class mainProg
    {
        readonly static SerialPort _serialPort = new SerialPort("COM1", 300) { ReadTimeout = 0 };
     
        readonly static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false);  
        static string[] recv;                           // Used to parse the message
        static byte[] MessageBuffer = new byte[300];    // Used to build the message
        static byte[] serialBuffer = new byte[300];     // Used to receive the bytes
        static int readcount = 0;               // Tracks the count of bytes being received through the serial interface.
        static int mbFillLevel = 0;             // Running total of the current fill level of our message buffer.
        static int Newcount = 0;                // Tracks the new count of bytes received to update mbFillLevel
        static int iLocation = 0;               // Tracks the location for array insertion in ReadSerial

        static bool HandledKeyPress = true;     //  Value tracked to prevent double presses of buttons on the serial keybpad until the message is handled


        public static void Main()
        {
        
            byte[] yx_data = System.Text.UTF8Encoding.UTF8.GetBytes("Y");
            byte[] qx_data = System.Text.UTF8Encoding.UTF8.GetBytes("Q");

            byte[] yx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Y - Pressed");
            byte[] qx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Q - Pressed");

            FEZ_Shields.KeypadLCD.Initialize();
            FEZ_Shields.KeypadLCD.TurnBacklightOn();
            FEZ_Shields.KeypadLCD.CursorHome();
            FEZ_Shields.KeypadLCD.Print("Initialized");

            _serialPort.Open();
            _serialPort.Flush();
            while (true)
            {
                switch (FEZ_Shields.KeypadLCD.GetKey())
                {
                    case FEZ_Shields.KeypadLCD.Keys.Left:
                        if (HandledKeyPress)
                        {
                            HandledKeyPress = false;
                            _serialPort.Write(qx_data, 0, qx_data.Length);
                            FEZ_Shields.KeypadLCD.Clear();
                            FEZ_Shields.KeypadLCD.SetCursor(0, 0);
                            FEZ_Shields.KeypadLCD.Print("Pressed Left Q");

                            //Debug.Print("Left 'Q' Pressed:");
                        }
                        break;
                    case FEZ_Shields.KeypadLCD.Keys.Right:
                        if(HandledKeyPress)
                        {
                            HandledKeyPress = false;
                            _serialPort.Write(yx_data, 0, yx_data.Length);
                            FEZ_Shields.KeypadLCD.Clear();
                            FEZ_Shields.KeypadLCD.SetCursor(0, 0);
                            FEZ_Shields.KeypadLCD.Print("Pressed Left Y");
                            //Debug.Print("Right 'Y' Pressed:");
                        }
                        break;
                    case FEZ_Shields.KeypadLCD.Keys.Up:
                        HandledKeyPress = true;
                        mbFillLevel = 0;
                        break;
                    default:
                        break;
                }

                Thread.Sleep(10);
                ReadSerial();
                if (mbFillLevel == 19)
                {
                    //Debug.Print("GotThere");
                    recv = new string(System.Text.UTF8Encoding.UTF8.GetChars(MessageBuffer)).Split('\n')[0].Split('|');
                    if (recv[0] == "Message")
                    {
                        FEZ_Shields.KeypadLCD.Clear();
                        FEZ_Shields.KeypadLCD.SetCursor(1, 1);
                        FEZ_Shields.KeypadLCD.Print(recv[1]);
                        HandledKeyPress = true;
                        mbFillLevel = 0 ;
                    }
                }
                else
                {
                    if (mbFillLevel == 1)
                    {
                        switch (MessageBuffer[0])
                        {
                            case 81:
                                _serialPort.Write(qx_data2, 0, qx_data2.Length);
                                //Debug.Print("Wrote Q Response");
                                led.Write(false);
                                mbFillLevel = 0;
                                break;
                            case 89:
                                _serialPort.Write(yx_data2, 0, yx_data2.Length);
                                //Debug.Print("Wrote Y Response");
                                led.Write(false);
                                mbFillLevel = 0;
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }

        static void ReadSerial()
        {

            readcount = 0;
            Newcount = 0;
            iLocation = mbFillLevel;
            readcount = _serialPort.BytesToRead;
            _serialPort.Read(serialBuffer, 0, serialBuffer.Length);
           
            if (readcount > 0)
            {
                Newcount = mbFillLevel + readcount;
                if (readcount >= 1)
                {
                    mbFillLevel = Newcount;
                    Array.Copy(serialBuffer, 0, MessageBuffer, iLocation, readcount);
                    led.Write(true);
                    //Debug.Print("Received a response");
                }
            }
        }

    }
}

Naked Panda Code


using System;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using System.IO.Ports;

namespace pandaSerial
{
    public class Program
    {
        readonly static SerialPort _serialPort = new SerialPort("COM1", 300) { ReadTimeout = 0 };

        readonly static OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, false);
        static string[] recv;                           // Used to parse the message
        static byte[] MessageBuffer = new byte[300];    // Used to build the message
        static byte[] serialBuffer = new byte[300];     // Used to receive the bytes
        static int readcount = 0;               // Tracks the count of bytes being received through the serial interface.
        static int mbFillLevel = 0;             // Running total of the current fill level of our message buffer.
        static int Newcount = 0;                // Tracks the new count of bytes received to update mbFillLevel
        static int iLocation = 0;               // Tracks the location for array insertion in ReadSerial

 

        public static void Main()
        {
            byte[] yx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Y - Pressed");
            byte[] qx_data2 = System.Text.UTF8Encoding.UTF8.GetBytes("Message|Q - Pressed");
            
            _serialPort.Open();
            _serialPort.Flush();
            while (true)
            {
                Thread.Sleep(10);
                ReadSerial();
                if (mbFillLevel == 1)
                {
                    switch (MessageBuffer[0])
                    {
                        case 81:
                            _serialPort.Write(qx_data2, 0, qx_data2.Length);
                            //Debug.Print("Wrote Q Response");
                            led.Write(false);
                            mbFillLevel = 0;
                            break;
                        case 89:
                            _serialPort.Write(yx_data2, 0, yx_data2.Length);
                            //Debug.Print("Wrote Y Response");
                            led.Write(false);
                            mbFillLevel = 0;
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        static void ReadSerial()
        {
            readcount = 0;
            Newcount = 0;
            iLocation = mbFillLevel;
            readcount = _serialPort.BytesToRead;
            _serialPort.Read(serialBuffer, 0, serialBuffer.Length);

            if (readcount > 0)
            {
                Newcount = mbFillLevel + readcount;
                if (readcount >= 1)
                {
                    mbFillLevel = Newcount;
                    Array.Copy(serialBuffer, 0, MessageBuffer, iLocation, readcount);
                    led.Write(true);
                    //Debug.Print("Received a response");
                }
            }
        }

    }
    
}

Would love to see it posted in the code area here. Stuff like this tends to get “lost” in the forum over time (i.e. hard to find by searching).

code.tinyclr.com :slight_smile:

Done and Done. Made a quick video showing the setup and function and put some code there as well.

http://code.tinyclr.com/project/365/serial-comm-between-2-pandas/

great contribution Mike. Love the music, just waiting for something to jump out at me !!!

LOL you should be a movie director! I love how the music starts and the camera rotates! :smiley:

Looks like the next steps will be:
Add a checksum function to support some very basic error identification and then message retries.
Attach SD Card reader to a panda and aquire sd cards.
Implementing a write buffer to accumulate the reads until an optimal size for writing is buffered or so much time has passed until a new byte is received.
Implement writing of the write buffer to the SD card.

Once that is all put together and working, then I need to get the actual device I will be reading from. Then modify the code, adding a larger message set both byte wise, as well as variety wise.

Mike, Excellent posting and project… the video’s not bad either!

I am going to be implementing serial communications in a Panda II in one of my future projects, and I was going to use multiple threads and event processing because I thought that would be the most efficient way to do it, but your statement “a simple process loop is used for this code which avoids the cost of event processing” has me wondering. Have you done any benchmarks or research on this? Is there a big loss of speed or too much latency? Thanks.

I haven’t done any benchmarking on it. My first attempt was using the Blocking Queue and spawning a thread for the communication and using events. I got it working in loopback mode, but I found the garbage collector working quite a bit. I tried some optimization’s, but I wasn’t confident that I could really get away from the GC running more than I liked. Much I’m sure due to my inexperience.

It really comes down to having to do the synchronization to access shared objects which adds overhead that you simply don’t need if you don’t use threading. I’m going to have a byte array representing a buffer that gets written by the data writer, and read by the data reader. Avoiding the sync on that object has to be beneficial.

Now at the clock speed of this machine, I don’t know that I really need to worry about all this with my application. I’m just trying to find the optimal approach for performance on a single cpu machine that has plenty of memory. If I run into memory pressure, I’ll for sure have to do something different and deal with the garbage collection. In the future, if I prove out my prototype, I will look at re-factoring things to work in a threaded pattern (or if we see multi-core panda’s ;D).

Thus, I feel that you really just need to look at your application and figure out what it’s need will be. Mine is a bit unclear as I have grand goals, but am starting piece by piece and feel I will eventually want more than a single Panda can handle. But we’ll see.