G120 serial port loses bytes at low baud rates 4800 or lower

To repro the issue you will need 2 G120’s (or Cobra II’s) with Firmware 4.2.9.0.

One G120 will be called the “Source”. Its job is to send an array of bytes at 115200 baud to the other G120, called the “Echo”. When the Echo receives the data, it simply regurgitates it out via a different serial port at 4800 baud back to the Source. Upon receipt of the echoed data, the Source checks it for errors. I have found that a delay of 14 ms or longer is required at the Echo side when sending the data back at 4800 baud. If this delay is not present, bytes will randomly disappear when writing to the port. It appears to be some sort of low-level timing issue.

The data being sent is 240 bytes long. At 4800 baud, this should take about 0.5 seconds to travel down the wire. The data is sent every 1 second, leaving 0.5 seconds of margin, so bottlenecking will not be an issue.

The following connections should be made between the two G120’s:
Source COM1 TXD —> Echo COM1 RXD
Echo COM2 TXD —> Source COM2 RXD
Source GND —> Echo GND

Optionally, the 3V3 pins can be connected on both, so you only have to power one unit, instead of two, if powering via USB. 3.3V power will simply daisy-chain to the other unit for convenience.

Here’s the test code for the Source:


// Required references:
// GHI.Premium.Hardware.G120
// GHI.Premium.System
// Microsoft.SPOT.Hardware
// Microsoft.SPOT.Hardware.SerialPort
// Microsoft.SPOT.Native
// mscorlib

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.IO.Ports;
using GHI.Premium.Hardware;

namespace Source
{
    public class Program
    {
        // This LED lights up whenever a glitch is found in the echoed data
        public static OutputPort LED = new OutputPort((Cpu.Pin)G120.Pin.P1_15, false);

        // Serial port that sends the primary data
        public static SerialPort PrimaryDataSender = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One);

        // Serial port that receives the echoed data and verifies its integrity
        public static SerialPort EchoedDataReceiver = new SerialPort("COM2", 4800, Parity.None, 8, StopBits.One);

        // Keeps track of data errors accumulated so far
        public static int GlitchRunningTally = 0;

        // The original data which will eventually be echoed back at a different baud rate
        public static byte[] PrimaryData = GenerateCyclicMonotoneBytes(240);

        public static void Main()
        {
            // Start the thread which manages the LED
            BlinkLEDThread.Start();

            // Open the serial ports
            PrimaryDataSender.Open();
            EchoedDataReceiver.Open();

            // This receives the echoed data and examines it for errors
            EchoedDataReceiver.DataReceived += new SerialDataReceivedEventHandler(EchoedDataReceiver_DataReceived);

            // Send primary data at regular intervals
            while (true)
            {
                Debug.Print("********************************************************************************");

                PrimaryDataSender.Write(PrimaryData, 0, PrimaryData.Length);
                
                Debug.Print("SENT " + PrimaryData.Length + " BYTES");

                Thread.Sleep(1000);
            }
        }

        // Checks echoed data for errors
        private static void EchoedDataReceiver_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // Read data that was echoed back
            int numBytes = EchoedDataReceiver.BytesToRead;          
            byte[] data = new byte[numBytes];
            EchoedDataReceiver.Read(data, 0, data.Length);

            // Check for errors
            int glitches = CountMonotoneGlitches(data);

            // If errors found, activate LED
            if (glitches > 0)
                LEDTrigger.Set();

            // Track the number of errors accumulated so far
            GlitchRunningTally += glitches;

            Debug.Print("Received " + numBytes.ToString() + " bytes. Total glitches accumulated so far = " + GlitchRunningTally.ToString());
        }

        // This block of code is responsible for keeping the LED lit for a minimum amount of time when triggered.
        // Otherwise, the LED will barely be visible if lit only briefly.
        public static ManualResetEvent LEDTrigger = new ManualResetEvent(false);
        public static Thread BlinkLEDThread = new Thread(new ThreadStart(BlinkLED));
        public static void BlinkLED()
        {
            while (true)
            {
                LEDTrigger.WaitOne();
                LED.Write(true);
                Thread.Sleep(50);   // Keep LED on for at least 50 msec
                LED.Write(false);
                LEDTrigger.Reset();
            }
        }

        /// <summary>
        /// Generates a sequence of monotonic bytes, where each byte value is offset by 1 from its neighbor.
        /// 
        /// Example: { 0, 1, 2, 3, 4, 5, 4, 3, 2, 1 }
        /// 
        /// The difference between each successive byte is always +/-1, when going forward or backward. 
        /// The first element in the array and the last element are also offset by 1, so that if the
        /// sequence is repeated, the absolute difference between bytes will ALWAYS be 1, even when the last 
        /// element wraps into the first element and the cycle repeats.
        /// 
        /// It can be used to check the integrity of a communication channel. If we send this sequence through a channel,
        /// we expect the difference between each successive element to always be +/-1. If this is not the case,
        /// that means one or more bytes have been corrupted or lost.
        /// </summary>
        /// <param name="sequenceLength">
        /// The desired number of bytes in the sequence. 
        /// This must be an even number in order for the first element and last element to be offset by 1 (cyclic). 
        /// If this is an odd number, the first and last elements will have the same value,
        /// resulting in an offset of 0 whenever the sequence is repeated.
        /// </param>
        /// <returns>
        /// A monotone sequence of bytes of specified length.
        /// </returns>
        private static byte[] GenerateCyclicMonotoneBytes(int sequenceLength)
        {
            byte[] output = new byte[sequenceLength];

            int periodLength = 2 * (byte.MaxValue - byte.MinValue);

            int index = 0;
            byte value = 0;

            // Define the first element
            output[index] = value;

            int numCompleteCycles = sequenceLength / periodLength;

            // Populate complete cycles (if any)
            if (numCompleteCycles > 0)
            {
                bool currentlyIncrementing = true;
                int finalCompleteCycleIndex = numCompleteCycles * periodLength - 1;
                int offset = 1;

                while (index < finalCompleteCycleIndex)
                {
                    index += 1;
                    value = (byte)(value + offset);
                    output[index] = value;

                    if (currentlyIncrementing == true && value == byte.MaxValue)
                    {
                        offset = -1 * offset;   // Time to decrement
                        currentlyIncrementing = false;
                    }
                    else if (currentlyIncrementing == false && value == byte.MinValue)
                    {
                        offset = -1 * offset;   // Time to increment
                        currentlyIncrementing = true;
                    }
                }
            }

            int numLeftovers = sequenceLength % periodLength;   // Get the remainder

            if (numLeftovers > 0)
            {
                int offset = 1;
                int maxTargetValue = numLeftovers / 2 + value;

                while (index < sequenceLength - 1)
                {
                    index += 1;
                    value = (byte)(value + offset);
                    output[index] = value;

                    if (value == maxTargetValue)
                        offset = -1 * offset;
                }
            }

            return output;
        }

        /// <summary>
        /// Examines each element in an array and verifies that each successive value is offset by +/-1 from
        /// its neighbor. The number of times this condition is violated is counted.
        /// </summary>
        /// <param name="seq">The sequence to check.</param>
        /// <returns>
        /// The number of times monotinicity has failed.
        /// </returns>
        private static byte prevByteValue = 1;
        private static int CountMonotoneGlitches(byte[] seq)
        {
            int glitchCount = 0;

            if (seq.Length > 0)
            {
                for (int k = 0; k < seq.Length; k++)
                {
                    byte currByteValue = seq[k];

                    if (System.Math.Abs(currByteValue - prevByteValue) != 1)
                        glitchCount += 1;

                    prevByteValue = currByteValue;
                }
            }
            return glitchCount;
        }

    }
}

Here’s the test code for the Echo:


// Required references:
// GHI.Premium.Hardware.G120
// GHI.Premium.System
// Microsoft.SPOT.Hardware
// Microsoft.SPOT.Hardware.SerialPort
// Microsoft.SPOT.Native
// mscorlib

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.IO.Ports;
using GHI.Premium.Hardware;

namespace Echo
{
    public class Program
    {
        // This LED lights up whenever data is received from the source
        public static OutputPort LED = new OutputPort((Cpu.Pin)G120.Pin.P1_15, false);

        // Grounding this input will remove the time delay from the DataReceived event handler.
        // The time-delay is required for glitch-free operation, and its absence will cause bytes to be dropped
        // over time. This input is currently assigned to P2.10 (LDR0) on the Cobra II board for button-pushing 
        // convenience, where button down is GND. It may be assigned to other pins if desired.
        public static InputPort DelayPin = new InputPort((Cpu.Pin)G120.Pin.P2_10, false, Port.ResistorMode.PullUp);

        // Serial port that receives data from the source G120
        public static SerialPort PrimaryDataReceiver = new SerialPort("COM1", 115200, Parity.None, 8, StopBits.One);
        
        // Serial port that echoes the data back at a different baud rate. Glitches appear at 4800 baud and lower.
        public static SerialPort EchoedDataSender = new SerialPort("COM2", 4800, Parity.None, 8, StopBits.One);

        public static void Main()
        {
            // Start the thread which manages the LED
            BlinkLEDThread.Start();

            // Open the serial ports
            PrimaryDataReceiver.Open();
            EchoedDataSender.Open();

            // This receives the primary data and echoes it through a different serial port at a different baud rate
            PrimaryDataReceiver.DataReceived += new SerialDataReceivedEventHandler(PrimaryDataReceiver_DataReceived);

            Thread.Sleep(Timeout.Infinite);
        }

        // Receives the primary data and echoes it out a different port
        private static void PrimaryDataReceiver_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // Activate LED to indicate data was received
            LEDTrigger.Set();
            
            // Read primary data
            int numBytes = PrimaryDataReceiver.BytesToRead;
            byte[] data = new byte[numBytes];
            PrimaryDataReceiver.Read(data, 0, data.Length);

            // Echo the data through a different port at a different baud rate
            EchoedDataSender.Write(data, 0, data.Length);

            // Whenever input is High, implement a time delay to prevent glitches.
            // 14 milliseconds appears to be the minimum time needed for long-term (several hours?)
            // stability at 4800 baud. Longer delays may be used for added safety, especially at lower baud rates.
            if (DelayPin.Read() == true)
                Thread.Sleep(20);
        }

        // This block of code is responsible for keeping the LED lit for a minimum amount of time when triggered.
        // Otherwise, the LED will barely be visible if lit only briefly.
        public static ManualResetEvent LEDTrigger = new ManualResetEvent(false);
        public static Thread BlinkLEDThread = new Thread(new ThreadStart(BlinkLED));
        public static void BlinkLED()
        {
            while (true)
            {
                LEDTrigger.WaitOne();
                LED.Write(true);
                Thread.Sleep(50);   // Keep LED on for at least 50 msec
                LED.Write(false);
                LEDTrigger.Reset();
            }
        }

    }
}

Anyone wanna give it a shot?

@ Iggmoe - I don’t have the proper hardware to test your code, but i was wondering… do you run your test with one or both of your g120’s connected to the usb port of your PC… if so could you see if it makes a difference when you power up your boards and run the test without usb connected…

@ Robvan - I originally discovered the problem when the boards were powered by a benchtop supply. USB or independent power yields the same results. I’ve tested this across 4 different units now, under different conditions, so it’s likely the low level software. It took days to track down.

@ Iggmoe - Frustrating to spend so many days… Hopefully GHI can reproduce the issue. I was asking about the USB connection because i discovered that managed interrupt code, related to network,was not always called when i have usb hooked up…

@ Robvan - Hmm, interesting. Do you have a link to that topic? It’s always good to keep abreast of current issues. Thank you.

@ Iggmoe - It mentioned it in this topic today… http://www.tinyclr.com/forum/topic?id=10855&page=6#msg110367

Has anyone from GHI been able to take a look at this yet?

We will this week

We believe that we have corrected the issue. Please see http://www.tinyclr.com/forum/topic?id=11074 for a new firmware. Let us know if this fixes the issue you were seeing.

2 Likes

Based on my initial tests so far, Hotfix Firmware 4.2.9.1 appears to have solved the issue. I will run more extensive tests and let you know if any anomalies pop up, but so far the system is performing well. Once again, thank you for your top-notch support!