locked
Serial Port Communication problem reading bytes into packets RRS feed

  • Question

  • Hello everyone! I was given a project of working with a tilt sensor through an RS-232 serial connection that sends about 100 measurements per second to a serial-to-USB converter and have used C# to read the measurements into bytes. I need to be able to read the data into a data-packet structure in order to use the measurements.
    It comes through the DataEventHandler Buffer as a stream of bytes and in order to establish the right frame for reading packets (which are 18 bytes each, starting with a header and ending with a checksum) I have to sort through the bytes until I find a header byte (255 or 0xFF or 0xFFFF) and a valid checksum. A valid checksum will equal the sum of the bytes from the header divided by the header, like so: checksum = sum({123,343,54, 63 ...})/(255).
    Then, I'm supposed to convert this packet to degrees.

    In C# how can you add together two bytes? Am I calculating the checksum properly/going about this in the right way? I'm not getting a valid checksum at all.

    What I did was include the code that does the function I described and made comments along the way. Please help! Timing is critical for me! And I can't go forward until this part is figured out!

    Please help!
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO.Ports;
    using System.IO;
    
    namespace BufferingUnitTest3
    {
        class Program
        {
            #region Fields
            enum bufferMethod { LIST, QUEUE, ASCII };
            List<byte> bufferData_List = new List<byte>();
            Queue<byte> bufferData_Queue = new Queue<byte>();
            string bufferData_Ascii = String.Empty;
            bufferMethod currentBufferMethod = bufferMethod.LIST;
    
            int bytecounter = 0; // # bytes per packet.
            int headercounter = 0; // Header bytes count
            int othercounter = 0; // Any bytes other than headers count
            StreamWriter logFile = new StreamWriter("log - 12-17-2009 - Test B.txt");
    
            #endregion
            static void Main(string[] args)
    
            {
                new Program();
            }
    
            Program()
            {
                string txt = String.Empty;
                txt = "Started: " + DateTime.Now;
                    Console.WriteLine(txt); logFile.WriteLine(txt);
                txt = "Available Ports: ";
                    Console.WriteLine(txt); logFile.WriteLine(txt);
                foreach (string s in SerialPort.GetPortNames())
                {
                    Console.WriteLine(" : {0}", s);
                    logFile.WriteLine(" : {0}", s);
                }
    
                string PortName = SerialPort.GetPortNames()[2];
                txt = "Opening Port: " + PortName + "...";
                    Console.WriteLine(txt); logFile.WriteLine(txt);
                SerialPort port = new SerialPort(PortName, 38400, Parity.None, 8, StopBits.One);
                port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
                port.Open();
                port.Write("c"); // Switch to angle-mode, which has the shortest packet-protocol.
                port.Write("G"); // Switch sensor to continuous-mode.
    
                txt = "Awaiting incoming Data...";
                    Console.WriteLine(txt); logFile.WriteLine(txt);
                Console.ReadKey();
            }
    
            public void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                SerialPort port = (SerialPort)sender;
                while (port.BytesToRead > 0)
                {
                    if (currentBufferMethod == bufferMethod.LIST)
                    {
                        bufferData_List.Add((byte)port.ReadByte());
                        processBuffer(bufferData_List);
                    }
                    else if (currentBufferMethod == bufferMethod.QUEUE)
                    {
                        bufferData_Queue.Enqueue((byte)port.ReadByte());
                        processBuffer(bufferData_Queue);
                    }
                    else if (currentBufferMethod == bufferMethod.ASCII)
                    {
                        bufferData_Ascii = port.ReadExisting();
                        processBuffer(bufferData_Ascii);
                    }
                }
            }
            public void Write(string txt)
            {
                Console.WriteLine(txt);
                logFile.WriteLine(txt);
            }
            int packetPotentialCount = 0;
            int packetPositionPointer = 0;
            bool packetIsOpen = false;
            public void processBuffer(List<byte> buffer)
            {
                Queue<byte> newPacket = new Queue<byte>();
                foreach (byte item in buffer)
                {
                    if (item == 255 && packetPositionPointer == 0 && packetIsOpen == false)
                    {
                        packetPositionPointer++;
                        packetPotentialCount++;
                        headercounter++;
                        packetIsOpen = true;
                        Write("--Begin Packet #" + packetPotentialCount + ": ");
                        Write(packetPositionPointer + " : " + item + " <-- Header");
                    }
                    else if (packetPositionPointer > 0 && packetIsOpen == true)
                    {
                        othercounter++;
                        if (packetPositionPointer == 18)
                        {
                            Write(packetPositionPointer + " : " + item + " <-- Checksum");
                            packetPositionPointer = 0;
                            packetIsOpen = false;
                        }
                        else
                        {
                            packetPositionPointer++;
                            Write(packetPositionPointer + " : " + item);
                        }
                    }
                }
            }
            public void processBuffer(Queue<byte> buffer)
            {
                foreach (byte item in buffer)
                {
                    bytecounter++;
                    Console.WriteLine("\tQ-{0}: {1}", bytecounter, item.ToString());
                    logFile.WriteLine("\tQ-{0}: {1}", bytecounter, item.ToString());
                    if (bytecounter % 10 == 0)
                    {
                        string txt = "\n\n";
                       Console.WriteLine(txt); logFile.WriteLine(txt);
                    }
                }
            }
    // The rest was cut for brevity.
        }
    }
    

    Here's output from that log file I have it dumping into at the top:
    Started: 12/14/2009 4:49:18 PM
    Available Ports: 
     : COM3
     : COM1
     : COM4
    Opening Port: COM4...
    Awaiting incoming Data...
    	L-1: 255
    	L-2: 255
    	L-3: 0
    	L-4: 255
    	L-5: 0
    	L-6: 0
    	L-7: 255
    	L-8: 0
    	L-9: 0
    	L-10: 0
    Headers: 4; Others: 6; HDR%: 0
    	L-11: 255
    	L-12: 0
    	L-13: 0
    	L-14: 0
    	L-15: 0
    	L-16: 255
    	L-17: 0
    	L-18: 0
    	L-19: 0
    	L-20: 0
    Headers: 6; Others: 14; HDR%: 0
    	L-21: 0
    	L-22: 255
    	L-23: 0
    	L-24: 0
    	L-25: 0
    	L-26: 0
    	L-27: 0
    	L-28: 0
    	L-29: 255
    	L-30: 0
    Headers: 8; Others: 22; HDR%: 0
    	L-31: 0
    	L-32: 0
    	L-33: 0
    	L-34: 0
    	L-35: 0
    	L-36: 255
    	L-37: 255
    	L-38: 0
    	L-39: 0
    	L-40: 0
    Headers: 10; Others: 30; HDR%: 0




    ....etc.
    As you can see, the "255" is all over the data because 255 is a measurement within the packet as well as the header for the beginning of a data-packet, so I have to determine which 255's are headers, and start reading packets from there, and which 255's are just data-values within the packet.
    • Edited by IsaacOIM Thursday, December 17, 2009 7:31 PM Clarified to Make it More Understandle for Someone With the Ability To Help.
    Thursday, December 17, 2009 3:18 PM

Answers

  • Here's a complete form in a single class file.
    It demonstrates all of the members of the BGW.
    Test this by adding a new class to a test project, not a form.
       
    //  Form1.cs  -- copy the code into a new class file, NOT a new form file.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Diagnostics;
    using System.Threading;

    public partial class BGWDemoForm1 : Form
        {
            BackgroundWorker worker;
            private Button btnPause;
            private bool workerPaused;

            int workerData;

            public BGWDemoForm1()
            {
                InitializeComponent();
            }

            private Button btnStart;
            private Button btnStop;
            private ProgressBar progressBar1;
            private Label label1;
            private Label label2;
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.btnStart = new System.Windows.Forms.Button();
                this.btnStop = new System.Windows.Forms.Button();
                this.progressBar1 = new System.Windows.Forms.ProgressBar();
                this.label1 = new System.Windows.Forms.Label();
                this.label2 = new System.Windows.Forms.Label();
                this.btnPause = new System.Windows.Forms.Button();
                this.SuspendLayout();
                //
                // btnStart
                //
                this.btnStart.Location = new System.Drawing.Point(76, 94);
                this.btnStart.Name = "btnStart";
                this.btnStart.Size = new System.Drawing.Size(75, 23);
                this.btnStart.TabIndex = 0;
                this.btnStart.Text = "Start";
                this.btnStart.UseVisualStyleBackColor = true;
                this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
                //
                // btnStop
                //
                this.btnStop.Location = new System.Drawing.Point(239, 93);
                this.btnStop.Name = "btnStop";
                this.btnStop.Size = new System.Drawing.Size(75, 23);
                this.btnStop.TabIndex = 1;
                this.btnStop.Text = "Stop";
                this.btnStop.UseVisualStyleBackColor = true;
                this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
                //
                // progressBar1
                //
                this.progressBar1.Location = new System.Drawing.Point(76, 65);
                this.progressBar1.Name = "progressBar1";
                this.progressBar1.Size = new System.Drawing.Size(238, 23);
                this.progressBar1.TabIndex = 2;
                //
                // label1
                //
                this.label1.AutoSize = true;
                this.label1.Location = new System.Drawing.Point(174, 72);
                this.label1.Name = "label1";
                this.label1.Size = new System.Drawing.Size(35, 13);
                this.label1.TabIndex = 3;
                this.label1.Text = "label1";
                //
                // label2
                //
                this.label2.AutoSize = true;
                this.label2.Location = new System.Drawing.Point(96, 40);
                this.label2.Name = "label2";
                this.label2.Size = new System.Drawing.Size(35, 13);
                this.label2.TabIndex = 4;
                this.label2.Text = "label2";
                //
                // btnPause
                //
                this.btnPause.Location = new System.Drawing.Point(158, 93);
                this.btnPause.Name = "btnPause";
                this.btnPause.Size = new System.Drawing.Size(75, 23);
                this.btnPause.TabIndex = 5;
                this.btnPause.Text = "Pause";
                this.btnPause.UseVisualStyleBackColor = true;
                this.btnPause.Click += new System.EventHandler(this.btnPause_Click);
                //
                // Form1
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(376, 196);
                this.Controls.Add(this.btnPause);
                this.Controls.Add(this.label2);
                this.Controls.Add(this.label1);
                this.Controls.Add(this.progressBar1);
                this.Controls.Add(this.btnStop);
                this.Controls.Add(this.btnStart);
                this.Name = "Form1";
                this.Text = "Form1";
                this.Load += new System.EventHandler(this.Form1_Load);
                this.ResumeLayout(false);
                this.PerformLayout();

            }

            #endregion


            private void Form1_Load(object sender, EventArgs e)
            {
                this.workerPaused = false;
                //
                this.btnStop.Enabled = false;
                this.btnPause.Enabled = false;
                //
                this.label1.Text = "";
                this.label1.Visible = false;
                //
                this.label2.Text = "No Progress To Report.";
                //
                this.progressBar1.Step = 1;
                this.progressBar1.Maximum = 100;
                this.progressBar1.Minimum = 0;
                this.progressBar1.Style = ProgressBarStyle.Continuous;
                //
                InitializeBGW();
                return;

            }

            private void InitializeBGW()
            {
                this.worker = null;
                this.worker = new BackgroundWorker();
                this.worker.WorkerReportsProgress = true;
                this.worker.WorkerSupportsCancellation = true;
                this.worker.DoWork +=
                    new DoWorkEventHandler(worker_DoWork);
                this.worker.ProgressChanged +=
                    new ProgressChangedEventHandler(worker_ProgressChanged);
                this.worker.RunWorkerCompleted +=
                    new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                this.worker.Disposed += new EventHandler(worker_Disposed);
                //
            }

            private void btnStart_Click(object sender, EventArgs e)
            {
                this.InitializeBGW();
                this.label1.Visible = true;
                this.btnStop.Enabled = true;
                this.btnPause.Enabled = true;
                this.btnStart.Enabled = false;
                this.workerPaused = false;
                this.worker.RunWorkerAsync(this.workerData);
            }

            private void btnStop_Click(object sender, EventArgs e)
            {
                this.btnStop.Enabled = false;
                this.btnPause.Enabled = false;
                this.btnStart.Enabled = true;
                this.worker.CancelAsync();
                this.workerData = 0;
                this.progressBar1.Value = this.workerData;
            }

            private void btnPause_Click(object sender, EventArgs e)
            {
                this.workerPaused = true;
                this.btnStop.Enabled = true;
                this.btnPause.Enabled = false;
                this.btnStart.Enabled = true;
                this.worker.CancelAsync();
            }

            void worker_DoWork(object sender, DoWorkEventArgs e)
            {
                DoTheWork(e);
            }

            private void DoTheWork(DoWorkEventArgs e)
            {
                int startPosition = (int)e.Argument;
                for (int i = startPosition; i < 100; i++)
                {
                    System.Threading.Thread.Sleep(90);
                    worker.ReportProgress(i, "Background Worker Is In Progress ...");
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;  // exiting the loop properly,
                        i = 100;  // calling return here can throw future exceptions
                    }
                }
                return;
            }

            void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                this.workerData = e.ProgressPercentage;
                string progressReport = (string)e.UserState;
                this.progressBar1.Value = this.workerData;
                this.label1.Text = this.progressBar1.Value.ToString();
                this.label2.Text = progressReport;
                //
                // fire an event in this class to notify other classes
                //
                return;
            }

            void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                if (e.Error != null)
                {
                    this.label2.Text = "An error was encountered.";
                }
                else
                {
                    if (e.Cancelled)
                    {
                        if (this.workerPaused)
                        {
                            this.label2.Text = "The work was paused by the user.";
                            this.workerPaused = false;
                        }
                        else
                        {
                            this.label2.Text = "The work was cancelled by the user.";
                            this.workerData = 0;
                            this.progressBar1.Value = this.workerData;
                            //this.label1.Visible = false;  // let user see amount of progress
                        }
                    }
                    else
                    {
                        this.label1.Visible = false;
                        this.progressBar1.Value = this.progressBar1.Minimum;
                        this.label2.Text = "Work has been completed.";
                        this.workerData = 0;
                        this.btnStop.Enabled = false;
                        this.btnPause.Enabled = false;
                        this.btnStart.Enabled = true;
                    }
                }
                //
                // Dispose of the instance of the bgw object, do not re-use...!!!
                //
                this.worker.Dispose();
                //
                return;
            }

            void worker_Disposed(object sender, EventArgs e)
            {
                string title = this.Text;
                this.Text = "Worker Has Been Disposed";
                Thread.Sleep(250);
                this.Text = title;
                return;
            }
        }

    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by IsaacOIM Monday, January 25, 2010 6:23 PM
    Friday, January 22, 2010 6:29 PM
  • Wow, that's incredible, Rudy! I'm going over it, but I largely don't yet understand how to use it! I printed it out and am going to try to go over it little by little and understand it - thank you for taking the time to program something like that out for me! It makes it a lot easier! However, would you mind giving me an example of how I could use it?
    Thanks a lot,
    Isaac D.
    OIM



    The structure DataPacket holds your 16 bytes plus a header and checksum byte.
    It may be hard to wrap your head around Object Oriented coding patterns and code abstraction.
    It is also an implementation of C# Generics. 
    Basically, it is programming example Carsten's excellent point about supporting different formats.

    You could use it like this.

    public class PacketTester
        {
            public static void Test1()
            {
                DataPacket<BigEndian> packet = new DataPacket<BigEndian>();  // set breakpoint here!
                byte MSB = 10;
                byte LSB = 4;
                short data = (short)(MSB << 8);
                data += LSB;
                packet.Data0 = data;
                short testData = packet.Data0;
                byte checkSum = packet.CheckSum;
            }
        }

    You would need to give all of the Data# fields the same treatment as Data0. 
    Sorry, I got lazy.  The concept was more important than implementing all of the fields as properties.
    I think I did the shift operations incorrectly, I did not check the code, but I am doing so now.

    EDIT:TESTED ADD THIS TO ORIGINAL SAMPLE IN BIGENDIAN.


            public void SetValue(Int16 num)
            {
                // provide implementation
                MSB = (byte)(num / 256);
                LSB = (byte)(num - (MSB << 8));
            }



    "A class should know how to take care of itself: nor more, no less."

    What the code does is that it abstracts the data format from the actual method calls to compute a checksum.
    The responsiblity for calculating the checksum now lies with the actual data type in use.
    BigEndian knows how to calculate a checksum for its' pair of bytes.
    You could create a LittleEndian definition, which would be nearly identical to BigEndian.

    If you find it useful and have any specific questions about that sample code, just post back here.
    But, I think Carsten knows more about interacting with serial port hardware using .NET than I do.

    Rudy   =8^D

    @Carsten:  It would not be a problem for me to translate from VB to C#.  Having both in the same post might be good, anyway.

    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 3:42 PM
  • IsaacOIM

    As I understand you, you will use the device in Scaled, continuous mode. Is this correct? If it is, your communication is not polled and you therefore need to use the DataReceived event.

    Try to use the sample program from my tutorial to get hole through. This program is able to send any mix of ASCII and hexadecimal data, so you can easily send the one-byte control commands and you should then be able to see the repetitive telegrams from continuous mode.

    The next step is then to make a sequencer, which can separate the various telegrams. Since 0xFF can both be data and header, you need to calculate a rolling checksum. When the first byte is 0xFF and the checksum is OK, you have found the telegram.

    If you are not familiar with cyclic buffers, make it as a shift register with 18 byte positions (1-18) - an 18 byte array. For every byte you receive, simply copy byte 17 to 18, byte 16 to 17, byte 15 to 16 and so on. It is not as smart as a cyclic buffer, but it may be easier for you to overlook. The checksum must be calculated over the last 17 bytes of this shift register (header not included) so subtract the byte from position 18 before you overwrite it with the byte from position 17 and add the byte from position 1 when you move it to position 2, so that your checksum is always the sum of position 2-18. Then divide the checksum with 0xFFFF, take the remainer and compare it with the last two bytes (17 and 18). When the checksum fit and the first byte (position 1) is 0xFF, your telegram is correctly positioned in the shift register and you can marshal the telegram to the UI thread in a BeginInvoke call.

    Due to Christmas I am very busy the next days with family activities so unfortunately I do not have time to write some code for you, but maybe Rudedog2 has time to help. The shift register system may not be the smartest implementation, but the important point is to get hole through. You can then optimize the code later.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 6:16 PM

  • I hope that clarifies things a little bit better for you. :-) How might I use Rudy's code to package the packets in such a way as to convert them to actual numbers for doing calculations on?
    Thanks!
    Isaac D.
    OIM

    Are you sure you want to imitate what I did?  ;)
    Imitate the real world. 
    My snippets defined different a DataPacket that accepted a type as a definition. 

    The coding pattern is pretty straightforward.
    Both Packet and the types used to instantiate packet implement the same interface.
    This is known as the Template Method Pattern .

    interface IDecorator
    {
         void DoWork();
    }

    class Type1 : IDecorator
    {
        public void DoWork(); {   }
    }

    class DecoratedClass<T>  : IDecorator
    {
        private T behaviorDecorator;
        public void DoWork()
        {
            behaviorDecorator.DoWork();
        }
    }

    I used the BigEndian---too lazy to write a LittleEndian class---to define the behavior.
    The DataStructure, or DecoratedClass in this example, is the "Template" to implement methods on the defining generic type.
    In first example I used BigEndian.  In this example, I used Type1.  You could implements several types of defining classes.

    You could apply the same principle to your communication classes. 
    Define a separate type for each style of communications. 
    Imagine what you could do with a collection of these objects.
    Then you could select one of them at runtime, completely altering the behavior of the object.
    This coding pattern would be known as the Strategy Pattern .

    Hope this helps.
    I'm pretty busy, too, over the next several days.
    But, I will be looking in, which does not mean I will or will not be responding.
    Mark the best replies as answers. "Fooling computers since 1971."


    EDIT: PLEASE.  Re-explain that CheckSum business they are doing.
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 7:02 PM

  • Imitate the real world in Object Oriented Programming.  When you code something up and it doesn't "feel" right, it is usually because it isn't right and you just cannot identify what.  When you emulate the real world in code, we know when we have coded something properly because it "feels" natural to us. 

    Besides, coding up something that we are familiar with down to the last detail is easier to create and enhance than something that we do not understand.  So how does this apply to some of the more abstract things that you may be doing?  Write a detailed description of what the as-yet-undefined abstract object should behave.

    Converting that description into code signatures is pretty easy. 
    Convert nouns into classes, and verbs into methods. 
    Adjectives can be properties.
    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 8:17 PM


  • EDIT: PLEASE.  Re-explain that CheckSum business they are doing.

    Now we are getting somewhere.  We have an algorithm for the checksum.

    I believe that my code computes a similar result.  I sum the bytes and ignore any overflow.

    @Isaac
    If you wish to write object oriented code I suggest that you gain some experience with it first.  Get this project working within your allotted time, then make it better if time permits.  First make it work, then make it better.

    The basic approach to what I did with the BigEndian code I posted above was to separate what changes from what remains the same .  The actual methods that were needed remain the same, however the implementations of them differ slightly for BigEndian and LittleEndian data types.  Sounds like an interface to you?

    This same approach could be applied to your communication protocols.  Describe an interface that contains the required class members to use the serial port.  Define classes that implement the different protocols.  You could even implement an abstract base class to provide common default functionality to the different protocols.  The different protocol classes could inherit this abstract class base class, and you would still be able to cast their object instances back to the original interface.

    Mark the best replies as answers. "Fooling computers since 1971."


    EDIT:  I also feel that Carsten is vastly more knowledgeable about all of the subtleties in today's serial port hardware than I am.  I must concede that he is one of the elite that posts on the subject of serial ports in these forums.
    • Marked as answer by IsaacOIM Thursday, January 7, 2010 4:28 PM
    Thursday, January 7, 2010 2:57 PM

All replies

  • OKAY, no one is replying! I'm not impatient, it's that I have a time-limit and am really in need of help and have been stuck with this problem for 2-3 weeks. Well, until someone knowledgeable on their lunch break comes by and notices this question, I'll tackle the problem piece by piece because supposing from the number of views without replies I must have given too complicated a problem and posted too much code. I re-wrote the code as below:

    It copies the structure given by the MSDN here: http://msdn.microsoft.com/en-us/library/system.bitconverter.toint16.aspx
    How do I get around the following error that it gives me?

    "Destination array is not long enough to copy all the items in the collection. Check array index and length."
    Stack Trace:    at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
       at System.BitConverter.ToInt16(Byte[] value, Int32 startIndex)

    I marked the line that gives the error with THIS LINE HERE:

    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    #endregion
    
    namespace PacketUnitTest2
    {
        class Program
        {
            static void Main(string[] args)
            {
                bufferPackets();
            }
            public static void bufferPackets()
            {
                Queue<byte> packet_List = new Queue<byte>();
                const int packetSize_Scaled = 18; // How many bytes to fit into one packet in Scaled Mode
                byte packetSum = 0; // Variable used to sum the bytes to compute the checksum
    
                // Some Sample Packets as Byte Arrays.
                byte[] packet1 = { 255, 255, 61, 3, 144, 134, 12, 2 }; // Bad Packet: Randomly Constructed
                byte[] packet2 = { 255, 255, 1, 255, 253, 233, 2, 230 }; // Angle Mode Packet
                byte[] packet3 = { 255, 33, 254, 248, 213, 4, 255, 0, 0, 0, 0, 0, 0, 255, 33, 254, 248, 213 }; // Scaled Mode Packet
                byte[] packet4 = { 255, 176, 249, 225, 213, 124, 6, 34, 84, 252, 82, 255, 0, 0, 0, 0, 0, 0, 255 }; // Scaled Mode Packet #2
                byte[] packet5 = { 255, 0, 0, 0, 0, 0, 0, 255, 181, 249, 224, 213, 132, 6, 34, 51, 122, 187 }; // Scaled Mode Packet #3
    
                byte[] packet = packet5;    // CHANGE HERE = Choose which packet to test. Default: packet5
    
                
                // This foreach iterates through the packet and performs its operation on each byte.
                // item = a specific byte (255, 63, etc.) within packet
                Console.WriteLine("Initial Statistics");
                Console.WriteLine("------------------");
                Console.WriteLine("Number of Bytes = " + packet5.Length);
                Console.WriteLine("Hexadecimal String = " + ToHexString(packet));
                Console.WriteLine("Bitconverted Hex. String = " + BitConverter.ToString(packet));
                Console.WriteLine();    // Blank Line
                Console.WriteLine("{0,5}{1,17}{2,10}", "Row", "Value", "Short");
                Console.WriteLine("{0,5}{1,17}{2,10}", "-----", "--------------", "-----");
    
                short packetHeader = 0;
                short potentialChecksum = 0;
                Queue<short> packetQueue = new Queue<short>();
                for(int i = 0; i < packetSize_Scaled; i++)
                {
                    //Console.WriteLine(i);
                    if (i == 0)
                    {
                        packetHeader = BitConverter.ToInt16(packet, i);
                    }
                    else if (i == packetSize_Scaled-1)
                    {
                        potentialChecksum = BitConverter.ToInt16(packet, i); // THIS LINE HERE
                    }
                    else
                    {
                        if (i != packetSize_Scaled)
                            packetQueue.Enqueue(BitConverter.ToInt16(packet, i));
                    }
                    printRow(packet, i, BitConverter.ToInt16(packet, i));
                }
    Thursday, December 17, 2009 7:22 PM
  • Your major problem is that you use a very bad protocol, which does not make it possible to separate telegrams. Is there any way to change the protocol from the tilt sensor or is it fixed?

    If you cannot change the protocol, you need to build a cyclic buffer with the size of the telegram length (18 bytes). Start filling this buffer with 00's. Each time you receive a byte from SerialPort in your eventhandler for the DataReceived event, put it into the cyclic buffer and calculate a rolling checksum by subtracting the byte from the other end of the buffer and adding the new byte. In this way, you will have a rolling checksum, which you can compare to the last received byte or word. When a match is found, check that the first byte in the buffer is 0xFF (header). If this is also the case, you have found the telegram and it is ready in the buffer to marshal to the UI thread in for example a BeginInvoke statement.

    You have not written how many bytes your checksum is. You only write that the sum must be divided by 0xFF. Sorry, but this does not sound right. It is much more likely that the sum of the bytes are just truncated to the least significant byte or word, so that all the most significant bytes are just thrown away. I will almost guarantee that your checksum always has a fixed length of 1 or 2 bytes.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, December 18, 2009 8:36 AM
  • Hey, Karsten! Thank you so much for your reply! Yes, I contacted the company that made the sensor and unfortunately there appears to be no way to modify the packet protocol. So, I'll have to work with what I've been given. What I'll do is go ahead and post that given information so that you or someone else can understand enough to help me:

    Data Packet Format

    In general, the digital data representing each measurement is sent as a 16-bit number (two bytes). The data is sent MSB first then LSB.  ... In scaled and angle mode, the data generally represents a quantity that can be positive or negative. These numbers are sent as a 16-bit signed integer in 2's complement format. The data is sent as two bytes, MSB first then LSB.

    The order of data sent will depend on the selected operating mode of the CXTD02. In angle mode, each data packet will begin with a two-byte header (hex 0xFFFF) and end with a two-byte checksum. The checksum is calculated in the following manner:

    1. Sum all packet contents except header and checksum.
    2. Divide the sum by 0xFFFF.
    3. The remainder should equal the checksum.

    So it seems that I wasn't compensating for a Big-Endian/Little-Endian computer architecture problem: when I do BitConverter.IsLittleEndian on my test PC it says "true". To my understanding, that means it puts the Least Significant Byte before the Most Significant Byte and I have to find a way to reverse the order, correct?
    Here's the order of the bytes (I'm using the Scaled Mode - after byte 7, its packet structure is continued as the leftmost column).

    Byte

    Angle Mode

    Scaled Sensor Mode

    Voltage Mode

    0

    Header (255)

    Header (255)

    Header (255)

    1

    Header (255)

    Roll Rate (MSB)

    Undefined

    2

    Pitch Angle (MSB)

    Roll Rate (LSB)

    Undefined

    3

    Pitch Angle (LSB)

    Pitch Rate (MSB)

    Undefined

    4

    Roll Angle (MSB)

    Pitch Rate (LSB)

    Undefined

    5

    Roll Angle (LSB)

    Yaw Rate (MSB)

    Undefined

    6

    Checksum (MSB)

    Yaw Rate (LSB)

    Undefined

    7

    Checksum (LSB)

    Acceleration X (MSB)

    Accel Voltage X (MSB)

    8

    Acceleration X (LSB)

    Accel Voltage X (LSB)

    9

    Acceleration Y (MSB)

    Accel Voltage Y (MSB)

    10

    Acceleration Y (LSB)

    Accel Voltage Y (LSB)

    11

    Acceleration Z (MSB)

    Accel Voltage Z (MSB)

    12

    Acceleration Z (LSB)

    Accel Voltage Z (LSB)

    13

    Temp Voltage (MSB)

    Temp Voltage (MSB)

    14

    Temp Voltage (LSB)

    Temp Voltage (LSB)

    15

    Time (MSB)

    Time (MSB)

    16

    Time (LSB)

    Time (LSB)

    17

    Checksum

    Checksum


    I've never had to put together a cyclic buffer before. Based on the basic outline that you'd given me, and some creative Googling, this is what I've been able to come up with: How do I use this code? I'm completely unsure what to do to make it read the data??

    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    #endregion
    
    namespace PacketUnitTest2
    {
        class Program
        {
            static void Main(string[] args)
            {
                bufferPackets();
            }
            public static void bufferPackets()
            {
                Queue<byte> packet_List = new Queue<byte>();
                const int packetSize_Scaled = 18; // How many bytes to fit into one packet in Scaled Mode
                short packetSum = 0; // Variable used to sum the bytes to compute the checksum
    
                // Some Sample Packets as Byte Arrays.
                byte[] packet1 = { 255, 255, 61, 3, 144, 134, 12, 2 }; // Bad Packet: Randomly Constructed
                byte[] packet2 = { 255, 255, 1, 255, 253, 233, 2, 230 }; // Angle Mode Packet
                byte[] packet3 = { 255, 33, 254, 248, 213, 4, 255, 0, 0, 0, 0, 0, 0, 255, 33, 254, 248, 213 }; // Scaled Mode Packet
                byte[] packet4 = { 255, 176, 249, 225, 213, 124, 6, 34, 84, 252, 82, 255, 0, 0, 0, 0, 0, 0, 255 }; // Scaled Mode Packet #2
                byte[] packet5 = { 255, 0, 0, 0, 0, 0, 0, 255, 181, 249, 224, 213, 132, 6, 34, 51, 122, 187 }; // Scaled Mode Packet #3
    
                byte[] packet = packet4;    // CHANGE HERE = Choose which packet to test. Default: packet5
    
                printPacket(packet);
    
                // This foreach iterates through the packet and performs its operation on each byte.
                // item = a specific byte (255, 63, etc.) within packet
                Console.WriteLine("Initial Statistics");
                Console.WriteLine("------------------");
                Console.WriteLine("Number of Bytes = " + packet5.Length);
                Console.WriteLine("Hexadecimal String = " + ToHexString(packet));
                Console.WriteLine("Bitconverted Hex. String = " + BitConverter.ToString(packet));
                Console.WriteLine();    // Blank Line
                Console.WriteLine("{0,5}{1,17}{2,10}", "Row", "Value", "Short");
                Console.WriteLine("{0,5}{1,17}{2,10}", "-----", "--------------", "-----");
    
                short packetHeader = 0;
                short potentialChecksum = 0;
                Queue<short> packetQueue = new Queue<short>();
    
                for (int i = 0; i < packetSize_Scaled; i++)
                {
                    //Console.WriteLine(i);
                    if (i == 0 && packet[0] == 255)
                    {   // OBTAIN HEADER
                        packetHeader = BitConverter.ToInt16(packet, i);
                        Console.WriteLine("Header: " + packetHeader);
                    }
                    else if (i == packetSize_Scaled - 1)
                    {   // OBTAIN CHECKSUM
                        potentialChecksum = packet[i];
                        Console.WriteLine("Potential Checksum: " + potentialChecksum + " (" + convertBigEndian(BitConverter.ToInt16(packet, i-1)) + ")");
                        break;
                    }
                    else
                    {   // ADD TO PACKET
                        packetQueue.Enqueue(convertBigEndian(BitConverter.ToInt16(packet, i)));   // Add to Packet
                        packetSum += convertBigEndian(BitConverter.ToInt16(packet, i));   // Add to the Sum for Calculating Calculated Checksum
                        //Console.WriteLine(i + " : " + BitConverter.ToInt16(packet, i));
                    }
                    printRow(packet, i, convertBigEndian(BitConverter.ToInt16(packet, i)));
                }
                #region <Fix> Big Endian Problem
                if (BitConverter.IsLittleEndian == true)
                {
                    //byte[] packetArray = System.Net.IPAddress.HostToNetworkOrder(packetQueue.ToArray());
                    short[] packetArray = packetQueue.ToArray();
                    Array.Reverse(packetArray);
                    packetQueue.Clear();
                    int index = 0;
                    foreach (short item in packetArray)
                    {
                        packetQueue.Enqueue(item);
                        packetSum += convertBigEndian(BitConverter.ToInt16(packet, index));
                        index++;
                    }
                }
                #endregion </Fix>
                int calculatedChecksum = (packetSum / Convert.ToInt16((byte)0xFF));
                Console.WriteLine("Packet Sum: " + packetSum);
                Console.WriteLine("Calculated Checksum: " + calculatedChecksum);
                if (potentialChecksum == calculatedChecksum)
                    Console.WriteLine("Valid Packet? True");
                else
                    Console.WriteLine("Valid Packet? FALSE; " + (packetSum / (byte)0xFF) + " != " + potentialChecksum);
    
                printPacket(packetQueue);
    
    
                Console.ReadKey();
            }
    
            #region Endian Fix
            private static short convertBigEndian(short param)
            {
                //if(BitConverter.IsLittleEndian == true)
                //    return (short)(((param & 0xff) << 8) | ((param >> 8) & 0xff));
                //else
                //    return param;
                return param;   // Not yet Implemented.
            }
            private static ushort convertBigEndian(ushort param)
            {
                if(BitConverter.IsLittleEndian == true)
                    return (ushort)(((param & 0xff) << 8) | ((param >> 8) & 0xff));
                return param;
            }
            private static int convertBigEndian(int param)
            {
                if (BitConverter.IsLittleEndian == true)
                    return (int)(((convertBigEndian((short)param) & 0xffff) << 0x10) | (convertBigEndian((short)(param >> 0x10)) & 0xffff));
                return param;
            }
            private static uint convertBigEndian(uint param)
            {
                if (BitConverter.IsLittleEndian == true)
                    return (uint)(((convertBigEndian((ushort)param) & 0xffff) << 0x10) | (convertBigEndian((ushort)(param >> 0x10)) & 0xffff));
                return param;
            }
            public static long convertBigEndian(long param)
            {
                if(BitConverter.IsLittleEndian == true)
                    return (long)(((convertBigEndian((int)param) & 0xffffffffL) << 0x20) | (convertBigEndian((int)(param >> 0x20)) & 0xffffffffL));
                return param;
            }
            public static ulong convertBigEndian(ulong param)
            {
                if (BitConverter.IsLittleEndian == true)
                    return (ulong)(((convertBigEndian((uint)param) & 0xffffffffL) << 0x20) | (convertBigEndian((uint)(param >> 0x20)) & 0xffffffffL));
                return param;
            }
            #endregion /Endian Fix
    
            #region Sample Code for Displaying Packet
            /// <summary>
            ///  This code will just display a byte array's contents as Hexadecimal values in one string.
            ///  "X2" Format is hexadecimal format.
            ///  This is a performance-conscious alternative to BitConverter.ToString(byte[]);
            /// </summary>
            /// <param name="bytearray">bytearray = array of format byte[] to turn into hexadecimal string.</param>
            /// <returns>hexString = </returns>
            private static string ToHexString(byte[] bytearray)
            {
                string hexString = string.Empty;
                for (int i = 0; i < bytearray.Length; i++)
                {
                    hexString += bytearray[i].ToString("X2");
                }
                return (hexString);
            }
            private static void printRow(byte[] bytearray, int rownum, object value)
            {
                Console.WriteLine("{0,5}{1,17}{2,10}", rownum,
                BitConverter.ToString(bytearray, rownum, 2), value);
            }
    
            public static void printPacket(Queue<short> packet)
            {
                // Let's display our resulting Packet.
                Console.WriteLine("\n---------\n");
                Console.WriteLine("Packet Contents: ");
                int contentcount = 0;
                foreach (byte item in packet)
                {
                    contentcount++;
                    Console.WriteLine(Convert.ToString(contentcount).PadLeft(2, '0') + ": " + item);
                }
            }
            public static void printPacket(short[] packet)
            {
                // Let's display our resulting Packet.
                Console.WriteLine("\n---------\n");
                Console.WriteLine("Packet Contents: ");
                int contentcount = 0;
                foreach (byte item in packet)
                {
                    contentcount++;
                    Console.WriteLine(Convert.ToString(contentcount).PadLeft(2, '0') + ": " + item);
                }
            }
            public static void printPacket(byte[] packet)
            {
                // Let's display our resulting Packet.
                Console.WriteLine("\n---------\n");
                Console.WriteLine("Packet Contents: ");
                int contentcount = 0;
                foreach (byte item in packet)
                {
                    contentcount++;
                    Console.WriteLine(Convert.ToString(contentcount).PadLeft(2, '0') + ": " + item);
                }
                Console.WriteLine();
            }
            #endregion
        }
    
    }
    How do I get it to fill a packet and read code?

    Thanks!
    Isaac D.
    OIM
    • Edited by IsaacOIM Monday, December 21, 2009 5:55 PM Formatting Issues Fix
    Monday, December 21, 2009 5:33 PM
  • Isaac, your original code was difficult to test and reproduce your issue. 
    We just didn't have your hardware. 
    Great, I noticed you re-formatted your 2nd post. 
    I recommend pasting into Notepad, then copy from Notepad into the forum when using IE.

    I noticed what might be a type in your 2nd post.

    Console.WriteLine("Number of Bytes = " + packet5.Length);  // Rudy: change packet5 to packet

    That line is near the top of your BufferPackets method.
    I'm still trying to wrap my head around your CheckSum requirements.
    It is an unusual way to compute it.
    Perhaps, the following might help you. 
    It is an object oriented approach to your problem.
    It does not attempt to implement your algorithm.


        public struct DataPacket<T> where T: struct, IEndian
        {
            public byte Header;
            private T _Data0;      
            public short Data0
            {
                get
                {
                    return _Data0.GetValue();
                }
                set
                {
                    _Data0.SetValue(value);
                }
            }
            //public T Data1;
            //public T Data2;
            //public T Data3;
            //public T Data4;
            //public T Data5;
            //public T Data6;
            //public T Data7;
            public byte CheckSum
            {
                get
                {
                    return ComputeCheckSum();
                }
            }
            private byte _CheckSum;
            private byte ComputeCheckSum()
            {
                this._CheckSum = 0;
                unchecked
                {
                    this._CheckSum += this._Data0.ComputeCheckSum(); //sample
                    //this._CheckSum += this.Data1.ComputeCheckSum();
                    //this._CheckSum += this.Data2.ComputeCheckSum();
                    //this._CheckSum += this.Data3.ComputeCheckSum();
                    //this._CheckSum += this.Data4.ComputeCheckSum();
                    //this._CheckSum += this.Data5.ComputeCheckSum();
                    //this._CheckSum += this.Data6.ComputeCheckSum();
                    //this._CheckSum += this.Data7.ComputeCheckSum();
                }
                return this._CheckSum;
            }
        }
        public struct BigEndian : IEndian
        {
            public byte MSB;
            public byte LSB;

            #region IEndian Members

            public short GetValue()
            {
                short result = 0; // assign default value for compiler
                unchecked
                {
                    result = (short)(MSB >> 8 + LSB);
                }
                return result;
            }

            public void SetValue(Int16 num)
            {
                // provide implementation
            }

            #endregion

            #region IDataPacket Members

            public byte ComputeCheckSum()
            {
                byte result;
                unchecked
                {
                    result = (byte)((byte)MSB + (byte)LSB);
                }
                return result;
            }

            #endregion
        }

        public interface IEndian : IDataPacket
        {
            Int16 GetValue();
            void SetValue(Int16 num);
        }
        public interface IDataPacket
        {
            byte ComputeCheckSum();
        }


    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Monday, December 21, 2009 7:03 PM
  • IsaacOIM

    Are you going to receive data in all three modes - Angle mode, Scaled Sensor Mode and Voltage Mode?

    If yes, are the data polled so that it is possible to tell which kind of mode it is by means of the poll?

    If no, you have a serious problem unless it is possible to find some other way to determine the mode.

    The first step is to forget the programming for a while and focus on the communication protocol. Try to make a precise description of this and especially find a way to separate telegrams and determine the mode - even in case of errors!

    The absolutely most important part of any communication protocol is to be able to separate the various telegrams even in case of errors. Unfortunately, your communication protocol does not fulfill this very basic requirement, so it is probably necessary with a buffer, which can hold one telegram and makes it possible to calculate a "rolling" checksum. The buffer works as a shift register, which is bend together and therefore becomes a cyclic buffer. If the first byte is 0xFF and the checksum is OK, there is a good probability that you have found the start of the telegram. Since the header is not included in the checksum, it is necessary to subtract 0xFF before each checksum calculation. You cannot just exclude 0xFF bytes since such a byte can also be a part of the data.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Monday, December 21, 2009 9:40 PM
  • Wow, that's incredible, Rudy! I'm going over it, but I largely don't yet understand how to use it! I printed it out and am going to try to go over it little by little and understand it - thank you for taking the time to program something like that out for me! It makes it a lot easier! However, would you mind giving me an example of how I could use it?
    Thanks a lot,
    Isaac D.
    OIM
    Tuesday, December 22, 2009 10:40 PM
  • IsaacOIM

    Are you going to receive data in all three modes - Angle mode, Scaled Sensor Mode and Voltage Mode?

    If yes, are the data polled so that it is possible to tell which kind of mode it is by means of the poll?

    If no, you have a serious problem unless it is possible to find some other way to determine the mode.

    The first step is to forget the programming for a while and focus on the communication protocol. Try to make a precise description of this and especially find a way to separate telegrams and determine the mode - even in case of errors!

    The absolutely most important part of any communication protocol is to be able to separate the various telegrams even in case of errors. Unfortunately, your communication protocol does not fulfill this very basic requirement, so it is probably necessary with a buffer, which can hold one telegram and makes it possible to calculate a "rolling" checksum. The buffer works as a shift register, which is bend together and therefore becomes a cyclic buffer. If the first byte is 0xFF and the checksum is OK, there is a good probability that you have found the start of the telegram. Since the header is not included in the checksum, it is necessary to subtract 0xFF before each checksum calculation. You cannot just exclude 0xFF bytes since such a byte can also be a part of the data.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.

    Hello, Karsten! I'm still very appreciative of your time and attention to this issue. Yes, it will primarily be used in Scaled mode, which has the 18-byte data packet. The sensor has a protocol in its manual that I've been using. It's a one-letter command system where you send a letter to set the mode and it sends a letter to confirm the sensor made the switch. (Pp. 13-18, 22-23). Rather than copy-and-pasting the manual, I'll give you the cliff notes:

    There are the three measurement modes, Angle, Scaled, and Voltage. Then, there are 2 output modes, Polled (G) and Continuous (C).

    The Measurement Modes define what measurements are taken and sent through the serial connection by the appropriately selected output mode. Measurement type Voltage Mode causes the sensor to output raw sensor voltage in the data packet. Measurement type Scaled Mode causes the sensor to output measurements in scaled engineering units. This has pitch and roll measurements that I'd need to run the testing sessions. Measurement type Angle (VG) Modecauses the sensor to calculate stabilized pitch and roll and also outputs sensor measurements in scaled engineering units.

    So:
    Serial.Write("r"); // Sends back 'R', sets the sensor to Voltage Mode
    Serial.Write("c"); // Sends back 'C', sets the sensor to Scaled Mode
    Serial.Write("a"); // Sends back 'A', sets the sensor to Angle Mode

    Also, there's a one-letter command for verifying that a connection has been established:
    SerialPort.Write("R"); // Pings CXTD02 sensor to verify communications, Responds with 'H'

    I thought it was unnecessary in these testing classes to check to see if they respond accordingly with their designated one-letter responses, so I just went ahead and put in:
                SerialPort port = new SerialPort(PortName, 38400, Parity.None, 8, StopBits.One);
                port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
                port.Open();
         port.Write("c"); // SCALED Measurement Mode.


    Then, for output modes, there is Polled,
         port.Write("G"); // Switch sensor to polled output mode.

    This sends just 1 packet for each call of port.Write("G");
    However, the actual testing sessions that this code will be used for will implement the Continuous Mode, which after sending the command below will continuously stream bytes that need to be sorted as data packets:
         port.Write("C"); // Switch sensor to continuous output mode.

    To stop the sensor from sending data as a continuous stream, one call of port.Write("G"); is used. This stops the stream and lets other commands be sent.

    However, the code typed above was my repackaging of what it had to say in C# commands - the manual offers no programming help whatsohaveyou. All it offers is this one statement on what to do: "The header byte 0xFFFF (or 0xFF) will likely not be the only 0xFFFF (or FF) byte in the data packet. You must count the bytes received at your serial port and use the checksum to ensure you are in sync with the data sent by the CXTD02. This is especially critical when using the continuous data packet output mode," along with the Table I posted in the previous post.

    I'm trying to get from the raw bytes to these calculations for the measurements:

     

    Digital Output Conversion

    Data is sent as 16-bit signed integer for all but Temperature. Temperature sensor data is sent as unsigned integer.

     

    Acceleration (Scaled Mode)

    Roll, Pitch (Angle Mode)

     

     

    Accel

     

     

    (G) = data * GR * 1.5/215

    Angle

     

     

    (°) = data * 180/215

    Temperature (Scaled Mode)

     

     

    Temperature

     

     

    (°C) = [(data * 5/4096) – 1.375]*44.44


    Command (ASCII)

     

    Response

     

    Description

     

    R

     

     

    H

     

     

    Ping: Pings the CXTD02 to verify communications.

     

     

    r

     

     

    R

     

     

    Change to Voltage Mode.

     

     

    c

     

     

    C

     

     

    Change to Scaled Sensor Mode.

     

     

    a

     

     

    A

     

     

    Change to Angle Mode (VG Mode).

     

     

    P

     

     

    None

     

     

    Change to polled mode. Data packets sent when a G is received by the CXTD02.

     

     

    C

     

     

    None

     

     

    Change to continuous data transmit mode. Data packets streamed continuously. Packet rate is dependent on operating mode. Sending "G" stops data transmission.

     

     

    G

     

     

    Data Packet

     

     

    Get Data. Requests a packet of data from the CXTD02. Data format depends on operating mode.

     

     



    I hope that clarifies things a little bit better for you. :-) How might I use Rudy's code to package the packets in such a way as to convert them to actual numbers for doing calculations on?
    Thanks!
    Isaac D.
    OIM
    Tuesday, December 22, 2009 11:06 PM
  • Wow, that's incredible, Rudy! I'm going over it, but I largely don't yet understand how to use it! I printed it out and am going to try to go over it little by little and understand it - thank you for taking the time to program something like that out for me! It makes it a lot easier! However, would you mind giving me an example of how I could use it?
    Thanks a lot,
    Isaac D.
    OIM



    The structure DataPacket holds your 16 bytes plus a header and checksum byte.
    It may be hard to wrap your head around Object Oriented coding patterns and code abstraction.
    It is also an implementation of C# Generics. 
    Basically, it is programming example Carsten's excellent point about supporting different formats.

    You could use it like this.

    public class PacketTester
        {
            public static void Test1()
            {
                DataPacket<BigEndian> packet = new DataPacket<BigEndian>();  // set breakpoint here!
                byte MSB = 10;
                byte LSB = 4;
                short data = (short)(MSB << 8);
                data += LSB;
                packet.Data0 = data;
                short testData = packet.Data0;
                byte checkSum = packet.CheckSum;
            }
        }

    You would need to give all of the Data# fields the same treatment as Data0. 
    Sorry, I got lazy.  The concept was more important than implementing all of the fields as properties.
    I think I did the shift operations incorrectly, I did not check the code, but I am doing so now.

    EDIT:TESTED ADD THIS TO ORIGINAL SAMPLE IN BIGENDIAN.


            public void SetValue(Int16 num)
            {
                // provide implementation
                MSB = (byte)(num / 256);
                LSB = (byte)(num - (MSB << 8));
            }



    "A class should know how to take care of itself: nor more, no less."

    What the code does is that it abstracts the data format from the actual method calls to compute a checksum.
    The responsiblity for calculating the checksum now lies with the actual data type in use.
    BigEndian knows how to calculate a checksum for its' pair of bytes.
    You could create a LittleEndian definition, which would be nearly identical to BigEndian.

    If you find it useful and have any specific questions about that sample code, just post back here.
    But, I think Carsten knows more about interacting with serial port hardware using .NET than I do.

    Rudy   =8^D

    @Carsten:  It would not be a problem for me to translate from VB to C#.  Having both in the same post might be good, anyway.

    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 3:42 PM
  • IsaacOIM

    As I understand you, you will use the device in Scaled, continuous mode. Is this correct? If it is, your communication is not polled and you therefore need to use the DataReceived event.

    Try to use the sample program from my tutorial to get hole through. This program is able to send any mix of ASCII and hexadecimal data, so you can easily send the one-byte control commands and you should then be able to see the repetitive telegrams from continuous mode.

    The next step is then to make a sequencer, which can separate the various telegrams. Since 0xFF can both be data and header, you need to calculate a rolling checksum. When the first byte is 0xFF and the checksum is OK, you have found the telegram.

    If you are not familiar with cyclic buffers, make it as a shift register with 18 byte positions (1-18) - an 18 byte array. For every byte you receive, simply copy byte 17 to 18, byte 16 to 17, byte 15 to 16 and so on. It is not as smart as a cyclic buffer, but it may be easier for you to overlook. The checksum must be calculated over the last 17 bytes of this shift register (header not included) so subtract the byte from position 18 before you overwrite it with the byte from position 17 and add the byte from position 1 when you move it to position 2, so that your checksum is always the sum of position 2-18. Then divide the checksum with 0xFFFF, take the remainer and compare it with the last two bytes (17 and 18). When the checksum fit and the first byte (position 1) is 0xFF, your telegram is correctly positioned in the shift register and you can marshal the telegram to the UI thread in a BeginInvoke call.

    Due to Christmas I am very busy the next days with family activities so unfortunately I do not have time to write some code for you, but maybe Rudedog2 has time to help. The shift register system may not be the smartest implementation, but the important point is to get hole through. You can then optimize the code later.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 6:16 PM

  • I hope that clarifies things a little bit better for you. :-) How might I use Rudy's code to package the packets in such a way as to convert them to actual numbers for doing calculations on?
    Thanks!
    Isaac D.
    OIM

    Are you sure you want to imitate what I did?  ;)
    Imitate the real world. 
    My snippets defined different a DataPacket that accepted a type as a definition. 

    The coding pattern is pretty straightforward.
    Both Packet and the types used to instantiate packet implement the same interface.
    This is known as the Template Method Pattern .

    interface IDecorator
    {
         void DoWork();
    }

    class Type1 : IDecorator
    {
        public void DoWork(); {   }
    }

    class DecoratedClass<T>  : IDecorator
    {
        private T behaviorDecorator;
        public void DoWork()
        {
            behaviorDecorator.DoWork();
        }
    }

    I used the BigEndian---too lazy to write a LittleEndian class---to define the behavior.
    The DataStructure, or DecoratedClass in this example, is the "Template" to implement methods on the defining generic type.
    In first example I used BigEndian.  In this example, I used Type1.  You could implements several types of defining classes.

    You could apply the same principle to your communication classes. 
    Define a separate type for each style of communications. 
    Imagine what you could do with a collection of these objects.
    Then you could select one of them at runtime, completely altering the behavior of the object.
    This coding pattern would be known as the Strategy Pattern .

    Hope this helps.
    I'm pretty busy, too, over the next several days.
    But, I will be looking in, which does not mean I will or will not be responding.
    Mark the best replies as answers. "Fooling computers since 1971."


    EDIT: PLEASE.  Re-explain that CheckSum business they are doing.
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 7:02 PM

  • Imitate the real world in Object Oriented Programming.  When you code something up and it doesn't "feel" right, it is usually because it isn't right and you just cannot identify what.  When you emulate the real world in code, we know when we have coded something properly because it "feels" natural to us. 

    Besides, coding up something that we are familiar with down to the last detail is easier to create and enhance than something that we do not understand.  So how does this apply to some of the more abstract things that you may be doing?  Write a detailed description of what the as-yet-undefined abstract object should behave.

    Converting that description into code signatures is pretty easy. 
    Convert nouns into classes, and verbs into methods. 
    Adjectives can be properties.
    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by eryang Tuesday, December 29, 2009 3:28 AM
    Wednesday, December 23, 2009 8:17 PM
  • Hi IsaacOIM,

    Are those replies helpful for you? It will be very beneficial for other community members having the similar questions if  you mark useful replies as answers.


    Sincerely,
    Eric

    Please remember to mark helpful replies as answers.
    Monday, December 28, 2009 2:23 AM
  • Hello!
    I implemented part of your idea, and now am working on the things Rudy suggested, putting the data into an interface packet structure using Object Oriented Code.
    Rudy, I took up your suggestion and I wrote out everything I could on how a Shift Register would operate, then wrote out the code associated with the different descriptions and thoughts and ideas.

    https://docs.google.com/fileview?id=0BxMapSGtH0JMMmVlZDgyM2QtYzZlOC00ZWNhLWI2MDgtNjRhOWEwMGFjZWIx&hl=en

    And here's the code I came up with, pending the methods that will process the 18 bytes and validate for the header and checksum:

    http://docs.google.com/fileview?id=0BxMapSGtH0JMZDU5NDRkYzEtOTk4YS00ZTg2LThiZTctYTc5ZmE3NTg5NjVh&hl=en

    I'm going to work on that piece of code using Rudy's example as a guideline, plus I'm also waiting for word back from the company about the checksum calculation specifics.

    Thanks a lot guys for the support!

    Edit
    Re-Post of Code:
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    #endregion
    
    namespace ShiftRegisterBufferUnitTest
    {
    
        /// <summary> This class is the Shift Register Buffer.</summary>
        class ShiftRegisterBuffer
        {
            int bufferSize = 0;                     // Number of places to put in buffer, determining the capacity 
    // or # of open positions. private Queue<byte> Buffer = new Queue<byte>(); // Shift Register operates as a FIFO - First in, First Out -
    // collection private static int numOfBuffers = 0; // Number of instances of this class. private const int zero = 0; // Character to use to fill empty positions in the buffer. public ShiftRegisterBuffer(int CapacityOfBuffer) { bufferSize = CapacityOfBuffer; // Sets the number of positions in the buffer for data. AllocateBytes(bufferSize); // Fill the buffer with empty 0's. numOfBuffers++; // Increment number of buffers in memory upon new instance. // This variable is static, so it will increase for each instance of CyclicBuffer. } #region Properties // Read-only Properties public int Length { get { return Buffer.Count; } // Retrieve the number of open positions. } public int Positions { get { return Buffer.Count; } // Returns the number of elements in the LIFO Stack. } public int Count { get { return Buffer.Count; } } public int BuffersInMem { get { return numOfBuffers; } // Retrieve the number of instantiated buffers running. } public int Capacity { get { return bufferSize; } // Returns how many positions the buffer was originally set to hold. } public int Empty { get { return zero; } // Like String.Empty, return the character for specifying empty positions. } #endregion #region Shift Register Buffer Methodologies /// <summary>This function fills the buffer with empty zeros, positions for buffering data.</summary> /// <param name="numPositions">Number of positions to create within the buffer. It pre-fills the empty positions
    /// with the </param> public void AllocateBytes(int numPositions) { // Stack implementation of Array.Clear(Array, indexer, length); for (int i = 0; i < numPositions; i++) { Buffer.Enqueue(zero); } } /// <summary> Add a byte to the first position in the buffer.</summary> /// <param name="newItem">Represents a byte value in the packet.</param> public void Enqueue(byte newItem) { // If the # of positions is greater than or equal to the Capacity/size, delete a value from a position // to keep the same number of positions in the buffer. if (Buffer.Count >= Capacity) Dequeue(); // Now, add the new item to the buffer. Buffer.Enqueue(newItem); } /// <summary>Returns a reference to the byte at the beginning of the buffer.</summary> /// <returns>Return the first value in the buffer without deleting it from the buffer.</returns> public byte Peek() { return Buffer.Peek(); } /// <summary>Removes and returns the byte at the beginning of the buffer.</summary> /// <returns>Returns the first byte in the buffer and then removes it from the buffer.</returns> public byte Dequeue() { return Buffer.Dequeue(); } /// <summary>Clears the buffer of its values and reallocates its empty positions with the given size.</summary> public void Clear() { Buffer.Clear(); AllocateBytes(bufferSize); } #endregion #region Testing Features - Display Buffer [Temporary] public void printBuffer() { IEnumerable<byte> byteCollection = Buffer; IEnumerator<byte> enumerator = byteCollection.GetEnumerator(); while (enumerator.MoveNext()) { Console.Write("{0} ", enumerator.Current); } Console.WriteLine(); } #endregion } #region Sensor Presets /// <summary> /// This class has all of the factory-set sensor information: there are 3 modes: angle, /// scaled, and voltage. Angle Mode's packet-structure is 8 bytes long while Scaled and /// Voltage Mode's are 18 bytes long, as reflected in enum Modes. When the mode is set, /// the packetLength is set based on the mode. Scaled Mode is used primarily. Disregard /// for now the other two. /// This is used to refer to the sensor and its preset properties/settings. /// </summary> class Sensor { public enum Modes { ANGLE = 8, SCALED = 18, VOLTAGE = 18}; // Modes & their packet-lengths private Modes _Mode = Modes.SCALED; private int _packetLength; // Current Packet-length. Changes with Mode Change. public Sensor() { _packetLength = (int)_Mode; } #region Properties public string Mode { get { return "" + _Mode; } } public int PacketLength { get { return _packetLength; } set { _packetLength = value; } } #endregion } #endregion /Sensor /// <summary>This will test of the Shift Register Buffer with the sensor's presets.</summary> class Test { static void Main(string[] args) { Test trial1 = new Test(); byte[] sample_packet = { 255, 0, 0, 0, 0, 0, 0, 255, 181, 249, 224, 213, 132, 6, 34, 51, 122, 187, 0 , 0, 0 }; // Scaled Mode Packet WITH 3 extra values to test
    // shifting of buffer. trial1.Run(sample_packet); Test trial2 = new Test(); byte[] sample_packet2 = {255,145,234,54,5}; trial2.Run(sample_packet2); Console.ReadKey(); } public void Run(byte[] packet) { Sensor sensor = new Sensor(); ShiftRegisterBuffer buffer = new ShiftRegisterBuffer(sensor.PacketLength); Console.WriteLine("**** Statistics"); Console.WriteLine("-------------------------------------"); Console.WriteLine("Sensor - Mode: " + sensor.Mode); Console.WriteLine("Sensor - Allowable Packet Length: " + sensor.PacketLength); Console.WriteLine("Size of Buffer = " + buffer.Length); Console.WriteLine(); // New Line Console.WriteLine("**** Unit Testing: Trial #1"); Console.WriteLine("-------------------------------------"); // Start Test Console.WriteLine("Bitconverted Hex. String = " + BitConverter.ToString(packet)); Console.WriteLine(); // New Line foreach (byte item in packet) { buffer.Enqueue(item); buffer.printBuffer(); } // Stop Test Console.WriteLine(); Console.WriteLine(); } } }
    • Edited by IsaacOIM Tuesday, December 29, 2009 10:16 PM Added Code
    Tuesday, December 29, 2009 10:06 PM
  • Seems to me like an awful complicated implementation of a rather simple problem - read my signature.

    Object oriented methods may be the true teaching, but it is extremely slow - look at Vista. Each time you call a method, you flush the pipeline in the CPU more times and with random RAM access, the CPU speed drops to approximately 25 MHz (!) because you cannot utilize the pipeline in the memory either. Remember that in continuous mode, all this code must run in the handler for the DataReceived event. Making a 18-byte shift register by means of 18 simple move instructions may not be the true teaching among software scientists, but because it can utilize the pipeline in the CPU, it executes in a fraction of the time necessary for all your method calls and it is much easier to overlook and debug.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, December 30, 2009 11:32 AM
  • Carsten,

    The OOP approach says to define a data packet, and delegate the responsibility of calculating the checksum value to the data type. 
    The is one of the hallmarks of OOP, abstraction and delegation of responsibility to other classes.
    The data packet does not know what data type is in use, and nor should it really care.
    That allows the same type of code to be used with more than one data type.
    The data packet itself could no doubt be modified to allow for its' size to be dynamically allocated.

    I didn't code anything to compute a rolling checksum.
    Because I didn't code anything to read/write a serial port where such a feature could come into play.
    Methods to compute a rolling checksum could easily be added to the data packet class.

    You may not like the OOP approach, but many people find it easier to implement functionality into simple packages.
    As for being OOP being slow, I would beg to differ.
    Try this on for size, it is known as the Strategy Pattern, a means to re-write If/Then blocks.
    I included lots of commented out code, and other means to compare speeds.
    You can compare If/Then or Select/Case versus various types of collections.

    This is a console application.

    Module Module1

        Dim watch As New Stopwatch()

        Sub Main()
            InitializeCollection()
            Dim i As Integer
            Dim total As Integer = 1000000
            '
            ' Perform Test using Classic Conditional Statements
            '
            watch.Reset()
            Console.WriteLine(watch.Elapsed.ToString())
            watch.Start()
            For i = 0 To total
                'DoWork_Classic(Tasks.Job1)
                'DoWork_Classic(Tasks.Job2)
                DoWork_Classic(Tasks.Job3)
            Next
            Console.WriteLine("Classic = " & watch.Elapsed.ToString())
            '
            ' Perform Test using OOP, Strategy Pattern
            '
            watch.Reset()
            Console.WriteLine(watch.Elapsed.ToString())
            watch.Start()
            For i = 0 To total
                'DoWork_ObjectOriented(Tasks.Job1)
                'DoWork_ObjectOriented(Tasks.Job2)
                DoWork_ObjectOriented(Tasks.Job3)
            Next
            Console.WriteLine("OOP way = " & watch.Elapsed.ToString())
            '
            ' End speed tests
            '
            Console.ReadLine()
        End Sub

        Delegate Sub MyDelegate()
        Dim workersDelegates() As MyDelegate
        Dim workersList As List(Of IDoWork)  'Compare performance of collection types
        Dim workersArray() As IDoWork        'Note this initialization below, it's sneaky.
        Sub InitializeCollection()
            workersList = New List(Of IDoWork) 'generic collection can use the abstract type
            workersList.Add(New Worker1())
            workersList.Add(New Worker2())
            workersList.Add(New Worker3())
            '
            ' Note that a concrete type, DoWorker, is used with abstract declaration below
            '
            workersArray = New DoWorker() {New Worker1(), New Worker2(), New Worker3()}
            '
            ' Define an array of MyDelegate objects, which are target methods.
            '
            workersDelegates = New MyDelegate() {AddressOf JobTask1, AddressOf JobTask2, AddressOf JobTask3}
        End Sub

        Sub DoWork_ObjectOriented(ByVal work As Tasks)
            'workersList(work).DoWork()
            'workersArray(work).DoWork()
            workersDelegates(work).Invoke()
        End Sub

        Sub DoWork_Classic(ByVal work As Tasks)
            '
            Select Case work
                Case Tasks.Job1
                    JobTask1()
                Case Tasks.Job2
                    JobTask2()
                Case Tasks.Job3
                    JobTask3()
                Case Else
            End Select
            '
            'If (work = Tasks.Job1) Then
            '    JobTask1()
            'ElseIf (work = Tasks.Job2) Then
            '    JobTask2()
            'ElseIf (work = Tasks.Job3) Then
            '    JobTask3()
            'End If
            '
        End Sub

        Sub JobTask1()
            Dim number As Integer = 0
            Return
        End Sub
        Sub JobTask2()
            Dim number As Integer = 0
            Return
        End Sub
        Sub JobTask3()
            Dim number As Integer = 0
            Return
        End Sub

    End Module



    Hope this helps.

    Rudy  =8^D
    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, December 30, 2009 1:22 PM
  • I forgot the support types for the Object Oriented code posted above.

    Interface IDoWork
        Sub DoWork()
    End Interface

    Class DoWorker : Implements IDoWork
        Public Event MyEvent As Module1.MyDelegate
        Public Overridable Sub DoWork() Implements IDoWork.DoWork

        End Sub
    End Class

    Class Worker1 : Inherits DoWorker

        Public Overrides Sub DoWork()
            Module1.JobTask1()
        End Sub
    End Class

    Class Worker2 : Inherits DoWorker

        Public Overrides Sub DoWork()
            Module1.JobTask2()
        End Sub
    End Class

    Class Worker3 : Inherits DoWorker

        Public Overrides Sub DoWork()
            Module1.JobTask3()
        End Sub
    End Class

    Enum Tasks
        Job1
        Job2
        Job3
    End Enum


    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, December 30, 2009 2:35 PM
  • Rudedog2

    I am NOT against OOP. I have used OOP for electronic process control since approximately 1978 - long time before anybody else talked about OOP. The problem is not OOP itself, but the way it is usually implemented with endless nested method calls. A modern computer may have a CPU with a 20 step pipeline and a memory with a 3 step pipeline. Each time you make the slightest jump or random memory access, you flush one of these pipelines and loose a lot of speed and this is exactly what you do when you make method calls instead of in-line code. It may be OK for normal code, but for high speed data communication you really need to think about these problems. Windows is certainly not very efficiently implemented. Just a few standard system calls may kill anything. For example, it takes 43 times more time to convert a received byte to two hexadecimal characters by means of String.Format than your own code based on a look-up table, and a computer game can calculate an entire 3D scenario in less time than it takes to append a few characters to a TextBox!

    Use of enormous amounts of very inefficient code was exactly the reason why Vista never became a success. Finally, the software nerds succeeded in killing even the fastest computer :-) The computer, which could run it fluent, was simply not invented, but I think that even Microsoft is beginning to learn the lesson!

    The OP has a very bad communication protocol, which does not make it possible to separate the various telegrams. This makes it necessary to calculate a rolling checksum for every byte received. At 38400 bit/s, which is the used speed, it means that these calculations must be done every 260 micro seconds and this time includes the necessary time to read the character. OK, if the time between telegrams is sufficiently long, a big receive buffer can solve this problem, but in a typical SCADA system for process control you may have 300 or more telegrams per second (out new fieldbus Max-i can deliver up to 10.000 telegrams per second) so I can assure you that the real bottleneck in a practical application is Windows! It doesn't help to program everything "by the book" if the result is a system too slow to handle the job! Unfortunately, this is what most PC programmers today do.

    The OP has made a shift register class with methods to access everything, but compared to in-line code based on a simple 18-byte array, it is a dead slow implementation. I very much doubt that he is able to perform a complete check sum calculation and comparison in less than 260 micro seconds that way.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, December 30, 2009 4:14 PM
  • Okay, gotcha.

    And, I would agree that the OP's protocol is unusual.

    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, December 30, 2009 4:58 PM
  • Yes, it's a really bad communication protocol. However! The company that made the sensor, Crossbow, offers a program to test to see if the sensor works, called Gyro-View, and it extracts the data and calculates the pitch and roll measurements in the data packets seemlessly. I contacted them and they told me that they couldn't give me the code the program used because they contracted an independent company, in Germany, to create the program. After doing some snooping around recently though, I figured out that they used a $1300 program called LabView Design Systems's Circular Buffer to handle their serial port communication. So, I know that the sensor works and that there's a way to do this programmatically. I'm readying the rest of the code and will update as soon as I can get it straight.

    Monday, January 4, 2010 2:14 PM
  • http://zone.ni.com/devzone/cda/epd/p/id/5883

    ^--- I'm pretty sure that's what they used, but I have no idea how to use it in C#, so am I not stuck with implementing my own?

    This description was associated with their Circular Buffer files:
    "In one loop, the acquisition data is added to the buffer. Another loop monitors the data continuously, meaning that it will pull out each element of the buffer in order. If this loop can not run as fast as the acquisition loop, you will receive an overflow warning. Another loop reads the most recent block of data in the buffer. With this method, you could read the same point multiple times or you could miss data points. Experminent with the behavior of these two buffer modes by changing the wait times associated with each. This example demonstrates how to use the circular buffer to get pre-trigger and post-trigger samples."

    Should I do it the same way?
    Monday, January 4, 2010 2:46 PM
  • NO , you should make a simple implementation based on an 18-byte array and 17 move instructions to implement the shift register - as simple as that.  Forget complicated object oriented methods, interfaces and cyclic buffers, which you obviously don't understand - read my signature.

    First, forget the programming and find out exactly how the communication is done. For example:

    1) Do you want to poll or use continuous mode?

    2) Which mode(s) do you want to use - angle, scaled etc.?

    Simple questions, which I have asked you before, but before you give me the answers, I can't write any code for you.

    Then describe exactly how each telegram looks like - the content of the 18 byte. Have you used my test program to get hole through as I suggested you? This is the first thing I would do. Then you can see what the sensor reply to each command. Give me a hexadecimal list of the 18 bytes in a telegram and tell me how every one of these bytes shall be interpreted including which byte is MSB and which byte is LSB (little- or big-endian).

    You seem to be totally lost here, but it ought to be quite simple to get it to work once we know how the communication works, but I can't help you before you have that information.

    Let us take it easy step by step. You need to find the start of your telegram. Because your protocol does not make that possible directly, you need to pass the telegram through a shift-register (or cyclic buffer) big enough to hold one telegram (18 bytes). When the first byte is 0xFF and the checksum is OK there is a good probability - note probability - not guarantee - that your telegram is now contained in the shift register. Then it is easy to convert the array to a string and marshal this to the UI thread exactly as it is done in my test program.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Monday, January 4, 2010 3:52 PM
  • Carsten,

    Which test program of yours are you referring to?? Are you referring to the one on your website?

    I wrote a similar program to it with all of the functionalities to read the data from the sensor utilizing Windows Forms that works sort of the same way:
    http://www.biblecia.com/projects/oim/sensorRead.exe

    I even have a file of raw Scaled-Mode, Continuous output:
    http://www.biblecia.com/projects/oim/log%20-%2012-09-2009%20-%20Test%20A%20-%20Scaled%20Mode%20With%20Movement.txt - With Movement
    http://www.biblecia.com/projects/oim/log%20-%2012-09-2009%20-%20Test%20A%20-%20Scaled%20Mode%20No%20Movement.txt - Without Movement

    I was sending Unit Tests of just specific functions of the part that I couldn't get to work properly so that I didn't paste hundreds of lines of code.

    However, to answer your question: yes, it'd be used to send data continuously in continuous mode, not polled, with just the Scaled Mode. Byte 0 is the header, byte 17 the checksum. Byte 1 is the MSB of the Roll Rate measurement. Byte 2 is the LSB of the roll rate measurement. Byte 3 is the MSB of the Pitch Rate measurement, Byte 4 is the LSB of the Pitch Rate measurement. Byte 5 is the MSB of the Yaw Rate measurement. Byte 6 is the LSB of the Yaw Rate measurement. Byte 7 is the Acceleration X MSB. Byte 8 Acceleration X LSB. Byte 9 Acceleration Y MSB. Byte 10 Acceleration Y LSB. Byte 11 the Acceleration Z MSB. Byte 12 the Acceleration Z LSB. The Z-axis is fixed, so it doesn't change. Byte 13 is the MSB Temp Voltage. Byte 14 is the LSB Temp Voltage. The Temperature Voltage is the measurement of the internal temperature of the sensor. The temperature data is meaningless to what I'm trying to do with the sensor. Byte 15 is the Time MSB. Byte 16 is the Time LSB. The time measurements are apart of an internal timing that the sensor updates. The hardware sensors are sampled, the data is processed, and then it's transmitted through the RS-232 port. This process is timed and tagged to each data packet - it's a free-running counter that's taken the time the channels are sampled and its value is from 65535, so 1 tick == 0.79 microseconds. The timer starts over every 50 milliseconds (so should this be used to track the sampling rate between packets?). Then, Byte 17 is the checksum.

    The roll and pitch is 2 bytes each. The checksum is 1 byte, the header is 1 byte. The checksum is found by: summing all bits except for the header and then taking the least significant byte which would then be the checksum value.


    I got my program to receive data in continuous mode with the DataReceived event. However, data comes in like this:

    > 505352
    > 5757
    > 50
    > 49
    > 505353
    > 505353
    > 163639820636316363982063631636399226363163639820636316363982063631636398206363163639922636316363992263631636399226363163639922636316363992263631636399226363163631002363631636310023636316363100236363163639922636316363992363631636310023636316363100236363163631002463631636310023636316363992263631636399216363163639920636316363100216363163631002263631636310123636316363102246363163631012463631636310124636316363100246363163631002463631636310125636316363101266363163631002563631636399236363163639923636316363100246363163631002463631636310024636316363101256363163631002563631636310025636316363100256363163631012663631636310125636316363101256363163631012463631636310124636316363101256363163631002463631636399236363163639922636316363992263631636399226363163631002363631636310024636316363100246363163639923636316363100246363163631002463631636310025636316363100256363163639924636316363100256363163631002563631636310025636316363100256363163631002563631636399246363163639924636316363100246363163631002463631636310024636316363100246363163631002563631636310025636316363992463631636399246363163639923636316363992363631636399236363163631002463631636310024636316363100256363163631002563631636310024636316363100246363163639923636316363992463631636399246363163639923636316363982263631636398226363163639821636316363982163631636398216363163639720636316363972063631636398216363163639822636316363982163631636399236363163639923636316363992463631636399246363163639924636316363992363631636398216363163639922636316363992263631636398216363163639822636316363982263631636398226363163639822636316363982263631636397216363163639721636316363972163631636397206363163639720636316363982263631636398226363163639822636316363992463631636310025636316363101266363163631012763631636310126636316363101266363163631002463631636310025636316363992463631636399246363163639925636316363992463631636399246363163639924636316363992463631636399246363163639924636316363100256363163631012663631636310126636316363101266363163631012563631636310024636316363100246363163631002463631636310024636316363100246363163631002463631636310125636316363101256363163631002463631636310023636316363100236363163631002463631636310125636316363101256363163631012563631636310025636316363100256363163631002463631636310024636316363100246363163631002463631636310024636316363100236363163631002363631636310023636316363101236363163631012363631636310123636316363101226363163631012163631636310021636316363992163631636399216363163639821636316363982263631636398226363163639822636316363982263631636397216363163639721636316363962163631636396226363163639723636316363972363631636397246363163639724636316363972463631636398256363163639926636316363992763631636399266363163639925636316363992563631636399256363163639925636316363992563631636398236363163639824636316363982463631636397236363163639723636316363972363631636398246363163639924636316363982263631636398226363163639822636316363982263631636398226363163639822636316363972163631636398226363163639924636316363100256363163631002663631636310025636316363100256363163639925636316363992563631636399246363163639823636316363982463631636398246363163639823636316363982363631636398236363163639925636316363992563631636399246363163639924636316363992363631636399236363163631002463631636310022636316363100226363163631002363631636399226363163639922636316363992263631636399236363163631002463631636399246363163639924636316363992563631636398246363163639925636316363992563631636310026636316363100266363163631002663631636310026636316363100276363163631002763631636310027636316363992663631636399256363163639824636316363982463631636397236363163639723636316363982463631636398246363163639824636316363972363631636397226363163639823636316363982363631636397226363163639621636316363962163631636395206363163639520636316363952063631636395206363163639516363631636396206363163639620636316363962063631636396206363163639620636316363972163631636397216363163639721636316363972163631636397216363163639616363631636396163636316363961636363163639616363631636395163636316363941636363163639416363631636395163636316363951636363163639520636316363941636363163639416363631636395163636316363951636363163639616363631636396163636316363962063631636396206363163639621636316363972263631636396216363163639621636316363962163631636397216363163639616363631636396163636316363972063631636397216363163639722636316363972263631636397226363163639721636316363982363631636398236363163639823636316363972263631636397226363163639722636316363972263631636397226363163639723636316363972363631636397236363163639621636316363962163631636396226363163639622636316363962263631636396236363163639624636316363962463631636396246363163639624636316363972463631636397246363163639723636316363972263631636397236363163639723636316363962263631636396226363163639723636316363972363631636398236363163639822636316363982263631636398236363163639822636316363972163631636398216363163639821636316363971636363163639716363631636397163636316363961636363163639616363631636396163636316363961636363163639616363631636397163636316363971636363163639616363631636397206363163639720636316363982163631636398216363163639821636316363992163631636398206363163639820636316363992163631636399216363163639921636316363982063631636398206363163639820636316363982063631636398216363163639821636316363982163631636398216363163639922636316363992263631636399216363163631002263631636310023636316363100236363163631002363631636310023636316363100226363163631002263631636310022636316363100236363163639922636316363982163631636397206363163639822636316363982263631636398226363163639822636316363992363631636399236363163639923636316363982263631636398216363163639821636316363982163631636397206363163639720636316363972063631636397216363163639822636316363982263631636398226363163639823636316363982363631636398226363163639822636316363982163631636398216363163639922636316363992363631636398226363163639822636316363982363631636398236363163639822636316363982263631636398226363163639822636316363982263631636397216363163639822636316363992263631636398216363163639922636316363992163631636399206363163639820636316363982063631636397163636316363971636363163639716363631636397163636316363971636363163639816363631636398163636316363981636363163639816363631636398163636316363981636363163639816363631636398163636316363982063631636398206363163639716363631636398206363163639820636316363982063631636398206363163639816363631636398163636316363981636363163639816363631636398163636316363981636363163639921636316363100226363163631002263631636310022636316363100216363163631002263631636310022636316363992163631636399216363163631002263631636310023636316363100236363163631002363631636310123636316363102256363163631012463631636310124636363636363636363636363636363636363636316363636363636363636363636363636363163636363636363636316363636363636363636363216363363636399636319898636363199636322163636326363636363636363636363636363636363636363636363631163635636363636363636363297636302636363636363
    > 63631636399226363163639923636316363
    > 992363631636310024
    > 6363163639923
    > 6363163639923
    > 6363163639822
    > 6363163639823
    > 6363163639722
    > 6363163639722
    > 636316363972263
    > 6316363972163
    > 631636397216363163639822
    It's meaningless and insignificant. I'm not sure how to convert an array of bytes to actual numbers or strings or significant data. The part that trips me up is the MSB/LSB and the Little Endian versus Big Endian and what to do with that.
    Even if you don't have time to write any sample code, could you please explain the concept to me a bit more clearly? Even just a pointing in the right direction would be a dramatic help! I tried to produce the Shift Buffer code above based on the short description you gave me. Here was my reasoning over it: https://docs.google.com/fileview?id=0BxMapSGtH0JMMmVlZDgyM2QtYzZlOC00ZWNhLWI2MDgtNjRhOWEwMGFjZWIx&hl=en . Is my thinking clear? What am I not getting?

    Thanks a lot,
    Isaac Dodd
    OIM
    Monday, January 4, 2010 7:31 PM
  • To make my program, http://www.biblecia.com/projects/oim/sensorRead.exe, work, you have to choose the port, clock Open Port, and then click Read Sensor. From there, you can type in the commands I posted above ("R" to ping the sensor to verify communication that makes the sensor send back an "H" response to determine a successful serial cable connection, "P" to switch to Polled Mode [left out of the program], "G" to poll the sensor for 1 data packet measurement, "C" to switch to continuous mode and get continuous output without having to send a "G" for each packet, "S" for an ASCII string of the manufacturer serial number of the sensor, "v" for an ASCII string of the manufacturer version ID of the sensor). The one-letter commands are case-sensitive. "r" (lowercase) is for voltage mode, "c" for scaled sensor mode, and "a" is for angle mode. In continuous mode, sending a single "G" stops the continuous mode data transmission so that the sensor can respond to other commands. The commands are sent in ASCII format.

    Also: in scaled mode, the data represented in the packet can be either positive or negative. The numbers are sent as a 16-bit signed integer in 2's complement format, as two bytes, MSB first then LSB. My goal is to convert the acceleration data into G's:
    Acceleration = data * (GR * 1.5)/(2^15)
    Data is the digital data sent by the CXTD02 sensor, GR is the G-range for it which is +/- 2 G's.
    The angles are standard Euler angles using a 3-2-1 system.

    But, I'm under the impression that this doesn't have anything to do with this part of the code and can be disregarded for now, right?

    Monday, January 4, 2010 7:47 PM
  • IsaacOIM

    Are you going to receive data in all three modes - Angle mode, Scaled Sensor Mode and Voltage Mode?

    If yes, are the data polled so that it is possible to tell which kind of mode it is by means of the poll?

    If no, you have a serious problem unless it is possible to find some other way to determine the mode.


    You set the mode ahead of time with the one-letter code. It will reply with another one-letter code to let you know that the mode has been changed successfully. These one-letter codes are case-sensitive. So, yes, I did a DataReceived event handler in an earlier program to receive data. To set the port to Scaled Mode, I'd do:

    SerialPort.Write("c");

    And in the DataReceived, I'd read the data for an ASCII string "C" as a response. This confirms that it's in the scaled mode. The Angle, Scaled, and Voltage modes are for what measurement data to send back in the data packets. There are two "modes" of data-output, the Polled and the Continuous. Now that the sensor's set to Scaled Mode, I can set the output-mode to Continuous by sending:

    SerialPort.Write("C");

    ...And it will immediately start streaming the data bytes to be sorted into packets. This "C" sets it to continuous mode and is different because it's uppercase.

    Does that make more sense? It could just be me because I've been dealing with this sensor problem for weeks! (And I'm way overdue.)
    Monday, January 4, 2010 9:10 PM

  • how bytes are Endians
    Mark the best replies as answers. "Fooling computers since 1971."
    Monday, January 4, 2010 9:20 PM
  • IsaacOIM

    The reason why your output does not make sense is probably because you read the data as ASCII, which it is not . Every time an ASCII value above 127 (0x7F) is received, SerialPort replaces it with "?" = 63. This is probably why you have so many 63's in your output. Please use the test program from my tutorial . This gives a hexadecimal output so that it is possible to see all transmitted values from 0x00 to 0xFF.

    I am a little worried about the repetition frequency in continuous mode. Is there a pause between telegrams or does the sensor send values in a continuous stream with no pause between the telegrams? Again, use my test program to find out. In case of a continuous stream with no pause, there is a risk of overflow and it may be better to poll.

    How is the checksum calculated? Sorry that I ask, but first you tell me that it is a two-byte checksum where the sum of the bytes shall be divided by 0xFFFF before you take the remainer. Now you tell me that it is only one byte, which is equal to the least significant byte of the sum. Again it is very important with a hexadecimal dump because it makes it possible to calculate the checksum by hand (using a hexadecimal calculator) and check to see which algorithm is correct.

    Sorry to say it, but we could get that sensor to work in a few hours if you would just do what I tell you to do. Download that test program (you only need the .exe file and it will not install anything on your computer) and use this to get hole through! You can transmit your commands as ASCII like "c" and "C" and/or use hexadecimal input (without ""). All output is hexadecimal so for example 0xAA is just written as AA.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Tuesday, January 5, 2010 8:35 AM
  • Hey Guys,
    EDIT:
    Great news, the Engineer from the Crossbow Technical Support answered one of my emails with the proper checksum calculation:

    ____________________

    Hello Isaac,
     
    You generally don’t divide by the header. Usually, after adding the data bytes (header and checksum excluded) the checksum is the two least significant bytes for the angle packet and the least significant byte for the scaled and voltage packets. This means dividing by 0x10000 (65,536) and 0x100 (256) respectively and the remainder is the checksum.
     
    For example:
     
    Angle Packet: FFFF00A2FF9A023B. The header is “FFFF” and the checksum sent in the packet is “023B”. The data bytes are “00”, “A2”, “FF”, and “9A”. So the sum of the data packets is: 00 + A2 + FF + 9A = 023B. Dividing by 0x10000 (65,536) produces a quotient of zero and a reminder of 571 (0x023B). The remainder matches the checksum sent in the packet.
     
    Scaled or Raw/Voltage Packet: FF0123456789ABCDEF0123456789ABCDEF80. The header is “FF” and the checksum sent in the packet is “80”. So the sum of the data packets is: 01 + 23 + 45 + 67 + 89 + AB + CD + EF + 01 + 23 + 45 + 67 + 89 + AB + CD + EF = 780. 0x780 (1920) divided by 0x100 (256) produces a quotient of 7 (0x07) and a remainder of 128 (0x80). And 0x80 matches the checksum sent with the packet.
     
     
    Hope that this helps a bit more.
    Wednesday, January 6, 2010 4:50 PM
  • IsaacOIM



    How is the checksum calculated? Sorry that I ask, but first you tell me that it is a two-byte checksum where the sum of the bytes shall be divided by 0xFFFF before you take the remainer. Now you tell me that it is only one byte, which is equal to the least significant byte of the sum. Again it is very important with a hexadecimal dump because it makes it possible to calculate the checksum by hand (using a hexadecimal calculator) and check to see which algorithm is correct.

    Karsten,
    http://www.biblecia.com/projects/oim/CheckSumCalc2.PNG
    That's an output of the Checksum Calculation program I made to compute the checksum. It works! This is the most progress I've had in weeks!
    Now, I'm going to apply this to the program I'd written following a combination of yours and Rudy's advice - making it Objected Oriented, yet simple without all the fancy things.
    Wednesday, January 6, 2010 6:54 PM
  • It seems that the checksum is just the least significant byte of the sum so that you really don't need to do any division. This is also what I expected.

    Now you just need to do the test I suggested to find the repetition frequency in continuous mode so that it is possible to deside whether to use continuous mode or polled. We cannot write any code before we know that.




    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Thursday, January 7, 2010 10:40 AM
  • I have a problem, though. Your program doesn't seem to work properly for me. It just sends back "TX" on a new line for every transmission.
    Thursday, January 7, 2010 2:24 PM


  • EDIT: PLEASE.  Re-explain that CheckSum business they are doing.

    Now we are getting somewhere.  We have an algorithm for the checksum.

    I believe that my code computes a similar result.  I sum the bytes and ignore any overflow.

    @Isaac
    If you wish to write object oriented code I suggest that you gain some experience with it first.  Get this project working within your allotted time, then make it better if time permits.  First make it work, then make it better.

    The basic approach to what I did with the BigEndian code I posted above was to separate what changes from what remains the same .  The actual methods that were needed remain the same, however the implementations of them differ slightly for BigEndian and LittleEndian data types.  Sounds like an interface to you?

    This same approach could be applied to your communication protocols.  Describe an interface that contains the required class members to use the serial port.  Define classes that implement the different protocols.  You could even implement an abstract base class to provide common default functionality to the different protocols.  The different protocol classes could inherit this abstract class base class, and you would still be able to cast their object instances back to the original interface.

    Mark the best replies as answers. "Fooling computers since 1971."


    EDIT:  I also feel that Carsten is vastly more knowledgeable about all of the subtleties in today's serial port hardware than I am.  I must concede that he is one of the elite that posts on the subject of serial ports in these forums.
    • Marked as answer by IsaacOIM Thursday, January 7, 2010 4:28 PM
    Thursday, January 7, 2010 2:57 PM
  • Absolutely, Rudy. You're right again. What I did was create a really simple program to Shift-Buffer the sensor data using a DataReceived from the SerialDataReceivedEvent and check for a checksum every time the 255 appears and output the results to a Text file. It seems like the sensor sends about 120 or so packets per second. Here's some of the output:

    ||||||||||||||||||||||||||||||||||||||||||
    ||||TRIAL #1||||||||||||||||||||||||||||||
    ||||||||||||||||||||||||||||||||||||||||||
    11:02:07 AM Thursday, January 07, 2010
    
    **** Statistics
    -------------------------------------
    Sensor - Mode: SCALED
    Sensor - Allowable Packet Length: 18
    Size of Buffer = 18
    
    **** Unit Testing: Trial #1
    -------------------------------------
    Bitconverted Hex. String = FF-00-00-00-00-00-00-FF-B5-F9-E0-D5-84-06-22-33-7A-BB-00-00-00
    
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  0  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  0  0  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  0  255  0  0  0  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  255  0  0  0  0  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  255  0  0  0  0  0  0   ::: 255
    0  0  0  0  0  0  0  0  0  0  255  0  0  0  0  0  0  255   ::: 254
    0  0  0  0  0  0  0  0  0  255  0  0  0  0  0  0  255  181   ::: 179
    0  0  0  0  0  0  0  0  255  0  0  0  0  0  0  255  181  249   ::: 172
    0  0  0  0  0  0  0  255  0  0  0  0  0  0  255  181  249  224   ::: 140
    0  0  0  0  0  0  255  0  0  0  0  0  0  255  181  249  224  213   ::: 97
    0  0  0  0  0  255  0  0  0  0  0  0  255  181  249  224  213  132   ::: 229
    0  0  0  0  255  0  0  0  0  0  0  255  181  249  224  213  132  6   ::: 235
    0  0  0  255  0  0  0  0  0  0  255  181  249  224  213  132  6  34   ::: 13
    0  0  255  0  0  0  0  0  0  255  181  249  224  213  132  6  34  51   ::: 64
    0  255  0  0  0  0  0  0  255  181  249  224  213  132  6  34  51  122   ::: 186
    255  0  0  0  0  0  0  255  181  249  224  213  132  6  34  51  122  187   ::: 117
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:07 AM Thursday, January 07, 2010
    Potential Checksum 187 == Calculated Checksum 187 ==  187 % 255
    FF-00-00-00-00-00-00-FF-B5-F9-E0-D5-84-06-22-33-7A-BB
    0  0  0  0  0  0  255  181  249  224  213  132  6  34  51  122  187  0   ::: 118
    0  0  0  0  0  255  181  249  224  213  132  6  34  51  122  187  0  0   ::: 118
    0  0  0  0  255  181  249  224  213  132  6  34  51  122  187  0  0  0   ::: 118
    TEST COMPLETED. ||||||||||||||||||||||||||
    
    
    ||||||||||||||||||||||||||||||||||||||||||
    ||||TRIAL #2||||||||||||||||||||||||||||||
    ||||||||||||||||||||||||||||||||||||||||||
    11:02:07 AM Thursday, January 07, 2010
    
    **** Statistics
    -------------------------------------
    Sensor - Mode: SCALED
    Sensor - Allowable Packet Length: 18
    Size of Buffer = 18
    
    **** Unit Testing: Trial #2
    -------------------------------------
    Bitconverted Hex. String = FF-91-EA-36-05
    
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255   ::: 255
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  145   ::: 144
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  145  234   ::: 122
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  255  145  234  54   ::: 176
    0  0  0  0  0  0  0  0  0  0  0  0  0  255  145  234  54  5   ::: 181
    TEST COMPLETED. ||||||||||||||||||||||||||
    
    
    ||||||||||||||||||||||||||||||||||||||||||
    ||||TRIAL: SERIAL|||||||||||||||||||||||||
    ||||||||||||||||||||||||||||||||||||||||||
    11:02:07 AM Thursday, January 07, 2010
    
    Establishing Serial Port communication RS-232 connection...
     **** Testing: COM3
    Port 'COM3' has invalid device attached.
    An error has occured: Insufficient system resources exist to complete the requested service.
    [ Error Code: WinIOError ]
     **** Testing: COM1
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    	Received: 
    Fail: Port 'COM1' has invalid device attached.
    
     **** Testing: COM7
    	Received: 
    	Received: 
    	Received: H
    	Received: H
    	Received: H
    	Received: H
    	Received: H
    	Received: H
    	Received: H
    	Received: H
    Success: Found Port: 'COM7' with valid device attached.
     --> Serial Port opened successfully.
    Setting CXTD02 to SCALED Measurement Mode:
    	Received: 
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    	Received: C
    SUCCESS: Device command successful.
     --> Sensor successfully set to SCALED Measurement Mode.
    Setting to Continuous Mode & Shift-Buffering Output...
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  67   ::: 67
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  67  255   ::: 66
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  67  255  0   ::: 66
    0  0  0  0  0  0  0  0  0  0  0  0  0  0  67  255  0  0   ::: 66
    0  0  0  0  0  0  0  0  0  0  0  0  0  67  255  0  0  0   ::: 66
    0  0  0  0  0  0  0  0  0  0  0  0  67  255  0  0  0  0   ::: 66
    0  0  0  0  0  0  0  0  0  0  0  67  255  0  0  0  0  0   ::: 66
    0  0  0  0  0  0  0  0  0  0  67  255  0  0  0  0  0  0   ::: 66
    0  0  0  0  0  0  0  0  0  67  255  0  0  0  0  0  0  255   ::: 65
    0  0  0  0  0  0  0  0  67  255  0  0  0  0  0  0  255  35   ::: 100
    0  0  0  0  0  0  0  67  255  0  0  0  0  0  0  255  35  255   ::: 99
    0  0  0  0  0  0  67  255  0  0  0  0  0  0  255  35  255  72   ::: 171
    0  0  0  0  0  67  255  0  0  0  0  0  0  255  35  255  72  213   ::: 128
    0  0  0  0  67  255  0  0  0  0  0  0  255  35  255  72  213  2   ::: 130
    0  0  0  67  255  0  0  0  0  0  0  255  35  255  72  213  2  6   ::: 136
    0  0  67  255  0  0  0  0  0  0  255  35  255  72  213  2  6  19   ::: 155
    0  67  255  0  0  0  0  0  0  255  35  255  72  213  2  6  19  218   ::: 117
    67  255  0  0  0  0  0  0  255  35  255  72  213  2  6  19  218  46   ::: 163
    255  0  0  0  0  0  0  255  35  255  72  213  2  6  19  218  46  97   ::: 193
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 97 == Calculated Checksum 97 ==  97 % 255
    FF-00-00-00-00-00-00-FF-23-FF-48-D5-02-06-13-DA-2E-61
    0  0  0  0  0  0  255  35  255  72  213  2  6  19  218  46  97  255   ::: 193
    0  0  0  0  0  255  35  255  72  213  2  6  19  218  46  97  255  0   ::: 193
    0  0  0  0  255  35  255  72  213  2  6  19  218  46  97  255  0  0   ::: 193
    0  0  0  255  35  255  72  213  2  6  19  218  46  97  255  0  0  0   ::: 193
    0  0  255  35  255  72  213  2  6  19  218  46  97  255  0  0  0  0   ::: 193
    0  255  35  255  72  213  2  6  19  218  46  97  255  0  0  0  0  0   ::: 193
    255  35  255  72  213  2  6  19  218  46  97  255  0  0  0  0  0  0   ::: 193
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 0
    Calculated Checksum 194 =  194 % 255
    Invalid Checksum 0 != Calculated Checksum 194
    35  255  72  213  2  6  19  218  46  97  255  0  0  0  0  0  0  255   ::: 193
    255  72  213  2  6  19  218  46  97  255  0  0  0  0  0  0  255  34   ::: 192
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 34
    Calculated Checksum 159 =  159 % 255
    Invalid Checksum 34 != Calculated Checksum 159
    72  213  2  6  19  218  46  97  255  0  0  0  0  0  0  255  34  255   ::: 192
    213  2  6  19  218  46  97  255  0  0  0  0  0  0  255  34  255  80   ::: 200
    2  6  19  218  46  97  255  0  0  0  0  0  0  255  34  255  80  212   ::: 199
    6  19  218  46  97  255  0  0  0  0  0  0  255  34  255  80  212  248   ::: 189
    19  218  46  97  255  0  0  0  0  0  0  255  34  255  80  212  248  6   ::: 189
    218  46  97  255  0  0  0  0  0  0  255  34  255  80  212  248  6  19   ::: 189
    46  97  255  0  0  0  0  0  0  255  34  255  80  212  248  6  19  184   ::: 155
    97  255  0  0  0  0  0  0  255  34  255  80  212  248  6  19  184  162   ::: 15
    255  0  0  0  0  0  0  255  34  255  80  212  248  6  19  184  162  175   ::: 93
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 175 == Calculated Checksum 175 ==  175 % 255
    FF-00-00-00-00-00-00-FF-22-FF-50-D4-F8-06-13-B8-A2-AF
    0  0  0  0  0  0  255  34  255  80  212  248  6  19  184  162  175  255   ::: 93
    0  0  0  0  0  255  34  255  80  212  248  6  19  184  162  175  255  0   ::: 93
    0  0  0  0  255  34  255  80  212  248  6  19  184  162  175  255  0  0   ::: 93
    0  0  0  255  34  255  80  212  248  6  19  184  162  175  255  0  0  0   ::: 93
    0  0  255  34  255  80  212  248  6  19  184  162  175  255  0  0  0  0   ::: 93
    0  255  34  255  80  212  248  6  19  184  162  175  255  0  0  0  0  0   ::: 93
    255  34  255  80  212  248  6  19  184  162  175  255  0  0  0  0  0  0   ::: 93
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 0
    Calculated Checksum 94 =  94 % 255
    Invalid Checksum 0 != Calculated Checksum 94
    34  255  80  212  248  6  19  184  162  175  255  0  0  0  0  0  0  255   ::: 93
    255  80  212  248  6  19  184  162  175  255  0  0  0  0  0  0  255  36   ::: 95
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 36
    Calculated Checksum 60 =  60 % 255
    Invalid Checksum 36 != Calculated Checksum 60
    80  212  248  6  19  184  162  175  255  0  0  0  0  0  0  255  36  255   ::: 95
    212  248  6  19  184  162  175  255  0  0  0  0  0  0  255  36  255  79   ::: 94
    248  6  19  184  162  175  255  0  0  0  0  0  0  255  36  255  79  212   ::: 94
    6  19  184  162  175  255  0  0  0  0  0  0  255  36  255  79  212  249   ::: 95
    19  184  162  175  255  0  0  0  0  0  0  255  36  255  79  212  249  6   ::: 95
    184  162  175  255  0  0  0  0  0  0  255  36  255  79  212  249  6  19   ::: 95
    162  175  255  0  0  0  0  0  0  255  36  255  79  212  249  6  19  151   ::: 62
    175  255  0  0  0  0  0  0  255  36  255  79  212  249  6  19  151  34   ::: 190
    255  0  0  0  0  0  0  255  36  255  79  212  249  6  19  151  34  16   ::: 31
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 16 == Calculated Checksum 16 ==  16 % 255
    FF-00-00-00-00-00-00-FF-24-FF-4F-D4-F9-06-13-97-22-10
    0  0  0  0  0  0  255  36  255  79  212  249  6  19  151  34  16  255   ::: 31
    0  0  0  0  0  255  36  255  79  212  249  6  19  151  34  16  255  0   ::: 31
    0  0  0  0  255  36  255  79  212  249  6  19  151  34  16  255  0  0   ::: 31
    0  0  0  255  36  255  79  212  249  6  19  151  34  16  255  0  0  0   ::: 31
    0  0  255  36  255  79  212  249  6  19  151  34  16  255  0  0  0  0   ::: 31
    0  255  36  255  79  212  249  6  19  151  34  16  255  0  0  0  0  0   ::: 31
    255  36  255  79  212  249  6  19  151  34  16  255  0  0  0  0  0  0   ::: 31
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 0
    Calculated Checksum 32 =  32 % 255
    Invalid Checksum 0 != Calculated Checksum 32
    36  255  79  212  249  6  19  151  34  16  255  0  0  0  0  0  0  255   ::: 31
    255  79  212  249  6  19  151  34  16  255  0  0  0  0  0  0  255  27   ::: 22
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 27
    Calculated Checksum 252 =  252 % 255
    Invalid Checksum 27 != Calculated Checksum 252
    79  212  249  6  19  151  34  16  255  0  0  0  0  0  0  255  27  255   ::: 22
    212  249  6  19  151  34  16  255  0  0  0  0  0  0  255  27  255  76   ::: 19
    249  6  19  151  34  16  255  0  0  0  0  0  0  255  27  255  76  213   ::: 20
    6  19  151  34  16  255  0  0  0  0  0  0  255  27  255  76  213  3   ::: 30
    19  151  34  16  255  0  0  0  0  0  0  255  27  255  76  213  3  6   ::: 30
    151  34  16  255  0  0  0  0  0  0  255  27  255  76  213  3  6  19   ::: 30
    34  16  255  0  0  0  0  0  0  255  27  255  76  213  3  6  19  117   ::: 252
    16  255  0  0  0  0  0  0  255  27  255  76  213  3  6  19  117  164   ::: 126
    255  0  0  0  0  0  0  255  27  255  76  213  3  6  19  117  164  111   ::: 221
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 111 == Calculated Checksum 111 ==  111 % 255
    FF-00-00-00-00-00-00-FF-1B-FF-4C-D5-03-06-13-75-A4-6F
    0  0  0  0  0  0  255  27  255  76  213  3  6  19  117  164  111  255   ::: 221
    0  0  0  0  0  255  27  255  76  213  3  6  19  117  164  111  255  0   ::: 221
    0  0  0  0  255  27  255  76  213  3  6  19  117  164  111  255  0  0   ::: 221
    0  0  0  255  27  255  76  213  3  6  19  117  164  111  255  0  0  0   ::: 221
    0  0  255  27  255  76  213  3  6  19  117  164  111  255  0  0  0  0   ::: 221
    0  255  27  255  76  213  3  6  19  117  164  111  255  0  0  0  0  0   ::: 221
    255  27  255  76  213  3  6  19  117  164  111  255  0  0  0  0  0  0   ::: 221
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 0
    Calculated Checksum 222 =  222 % 255
    Invalid Checksum 0 != Calculated Checksum 222
    27  255  76  213  3  6  19  117  164  111  255  0  0  0  0  0  0  255   ::: 221
    255  76  213  3  6  19  117  164  111  255  0  0  0  0  0  0  255  30   ::: 224
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 30
    Calculated Checksum 195 =  195 % 255
    Invalid Checksum 30 != Calculated Checksum 195
    76  213  3  6  19  117  164  111  255  0  0  0  0  0  0  255  30  255   ::: 224
    213  3  6  19  117  164  111  255  0  0  0  0  0  0  255  30  255  82   ::: 230
    3  6  19  117  164  111  255  0  0  0  0  0  0  255  30  255  82  212   ::: 229
    6  19  117  164  111  255  0  0  0  0  0  0  255  30  255  82  212  254   ::: 224
    19  117  164  111  255  0  0  0  0  0  0  255  30  255  82  212  254  6   ::: 224
    117  164  111  255  0  0  0  0  0  0  255  30  255  82  212  254  6  19   ::: 224
    164  111  255  0  0  0  0  0  0  255  30  255  82  212  254  6  19  84   ::: 191
    111  255  0  0  0  0  0  0  255  30  255  82  212  254  6  19  84  35   ::: 62
    255  0  0  0  0  0  0  255  30  255  82  212  254  6  19  84  35  208   ::: 159
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 208 == Calculated Checksum 208 ==  208 % 255
    FF-00-00-00-00-00-00-FF-1E-FF-52-D4-FE-06-13-54-23-D0
    0  0  0  0  0  0  255  30  255  82  212  254  6  19  84  35  208  255   ::: 159
    0  0  0  0  0  255  30  255  82  212  254  6  19  84  35  208  255  0   ::: 159
    0  0  0  0  255  30  255  82  212  254  6  19  84  35  208  255  0  0   ::: 159
    0  0  0  255  30  255  82  212  254  6  19  84  35  208  255  0  0  0   ::: 159
    0  0  255  30  255  82  212  254  6  19  84  35  208  255  0  0  0  0   ::: 159
    0  255  30  255  82  212  254  6  19  84  35  208  255  0  0  0  0  0   ::: 159
    255  30  255  82  212  254  6  19  84  35  208  255  0  0  0  0  0  0   ::: 159
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 0
    Calculated Checksum 160 =  160 % 255
    Invalid Checksum 0 != Calculated Checksum 160
    30  255  82  212  254  6  19  84  35  208  255  0  0  0  0  0  0  255   ::: 159
    255  82  212  254  6  19  84  35  208  255  0  0  0  0  0  0  255  34   ::: 163
    Checksum Calculation Activated:::
    Failure:
    Potential Checksum = 34
    Calculated Checksum 130 =  130 % 255
    Invalid Checksum 34 != Calculated Checksum 130
    82  212  254  6  19  84  35  208  255  0  0  0  0  0  0  255  34  255   ::: 163
    212  254  6  19  84  35  208  255  0  0  0  0  0  0  255  34  255  77   ::: 158
    254  6  19  84  35  208  255  0  0  0  0  0  0  255  34  255  77  212   ::: 158
    6  19  84  35  208  255  0  0  0  0  0  0  255  34  255  77  212  243   ::: 147
    19  84  35  208  255  0  0  0  0  0  0  255  34  255  77  212  243  6   ::: 147
    84  35  208  255  0  0  0  0  0  0  255  34  255  77  212  243  6  19   ::: 147
    35  208  255  0  0  0  0  0  0  255  34  255  77  212  243  6  19  50   ::: 113
    208  255  0  0  0  0  0  0  255  34  255  77  212  243  6  19  50  163   ::: 241
    255  0  0  0  0  0  0  255  34  255  77  212  243  6  19  50  163  34   ::: 67
    Checksum Calculation Activated:::
    SUCCESS!!! 11:02:29 AM Thursday, January 07, 2010
    Potential Checksum 34 == Calculated Checksum 34 ==  34 % 255
    FF-00-00-00-00-00-00-FF-22-FF-4D-D4-F3-06-13-32-A3-22
    Would you care to see the code behind this?
    Thursday, January 7, 2010 4:34 PM
  • Oh, right! Let me walk you through the code. What it's doing is first it does 2 simple tests using 2 data packets. The first is an 18-byte byte[] with 3 extra 0's inserted for a total of 21 bytes. So, first it fills the buffer with empty 0's. Then, it there's a for-loop to read the buffer each byte one-by-one. So, it fills the buffer. With each byte added to the buffer, it also adds that byte to a rollingSum byte to keep track of the current Sum of whatever is in the buffer. And, for each byte removed from the buffer, it's also subtracted from the rollingSum. It does this and prints the buffer for each byte added, to show the shifting.

    The moment that a header-byte (0xFF or 255) enters the first position, it activates the checksum calculation. This calculates as in the previous post using the rollingSum, then does the remainder and prints a message for success. That's in Trial 1. That works.

    Trial 2 is a 5-byte packet just to test if it would do the same on a smaller one of different size. That works. But, those just show it's working and can be disregarded.

    Trial: Serial is where it then sets up the SerialDataEvent Handler. From here, it detects which COM port has the sensor attached by pinging it with Serial.Write("R") commands. The port that responds with "H"'s is the port to use. I made it send the same transmission several times to ensure it receives the transmission. So, hence the "H" list.

    Then, it does the same shifting operation on the bytes coming from the port. This is actually just a snippet of the code. The file is 6 Megs after just several seconds!
    Thursday, January 7, 2010 4:46 PM
  • Also,
    The failures are just because after it validates a successful packet, it shifts through the packet to the next few bytes and the packet itself will contain 1 or 2 instances of the header 255 in it before it gets to the start of the next header.

    So,
    255  0  0  0  0  0  0  255  35  255  72  213  2  6  19  218  46  97   ::: 193

    will have a success on the first 255, then a two failed validations on the next two (also, the number after ::: is the rollingSum for that packet tagged to each line).
    Thursday, January 7, 2010 4:49 PM
  • Food for thought.

    The SerialPort. DataReceived Event can fire at times when you least expect it to.
    This is from the Remarks section of the link I just posted.

    Remarks

    <!-- -->

    Serial received events can be caused by any of the items in the SerialData enumeration. Because the operating system determines whether to raise this event or not, not all parity errors may be reported.

    PinChanged , DataReceived , and ErrorReceived events may be called out of order, and there may be a slight delay between when the underlying stream reports the error and when the event handler is executed. Only one event handler can execute at a time.

    The DataReceived event is not guaranteed to be raised for every byte received. Use the BytesToRead property to determine how much data is left to be read in the buffer.

    The DataReceived event is raised on a secondary thread when data is received from the SerialPort object. Because this event is raised on a secondary thread, and not the main thread, attempting to modify some elements in the main thread, such as UI elements, could raise a threading exception. If it is necessary to modify elements in the main Form or Control , post change requests back using Invoke , which will do the work on the proper thread.



    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 7, 2010 5:02 PM
  • IsaacOIM

    My test program appends a CRLF and TX to the received TextBox every time you transmit a command. The purpose of this is to make it easier to separate the commands you send and the received replies. If you only see TX's, nothing is received. Check the settings and the hardware connections and that you have selected a COM port.

    Is it really true that you receive approximately 120 telegrams per second in continuous mode? In that case, it may be better to poll. A normal SCADA system is able to receive approximately 300 telegrams per second, so 40% load for a single sensor is very much. Do you really need data that fast? Besides, in polled mode, you don't need to search for the start of the telegram by means of a rolling checksum because only one telegram is received for each poll.


    Rudedog2

    SerialPort.DataReceived doesn't fire when you least expect it to. It fires when data are received and no eventhandler for SerialPort is running.

    The other points are correct except that perhaps some further explanation is needed. The DataReceived event is not guaranteed to be raised for every byte, but this is because this event will not fire (again) as long as a SerialPort eventhandler is running. If you just empty the receive buffer in the eventhandler for the DataReceived event, you will not miss any bytes.

    It is also correct that all events can get out of order. This is really one of the less clever things about SerialPort. The receiver FIFO in the UART is 11 bit wide to ensure a perfect synchronization between error/status flags and data, and in this way enable modern communication methods like 9th bit communication and telegram separation by means of break. However, Microsoft just use the three error/status bits to fire events and then strips them off before the 8-bit data is stored in a 16-bit stream. In this way, the synchronization is lost and cannot be regained. How foolish can you be? Justin Van Pattern, which is in charge of that department, has promised me to finally make a modern SerialPort implementation, but unfortunately nothing happens. Our new fieldbus Max-i uses break to separate telegrams up to 1.8432 Mbit/s with a length as short as 5 bytes per telegram. This means that we can receive numerous telegrams within the time it takes for the PinChanged event (break detection) to fire, so it doesn't work with Windows. However, break is by far the most efficient and simple way to separate binary telegrams where all values from 0x00 to 0xFF are used, and the hardware can handle it, so we refuse to do it in another way just because nobody at Microsoft seems to know how a modern UART works.

    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, January 8, 2010 8:26 AM
  • Did I actually say "fire when you least expect it to"?
    Yeah, I did.  I guess that came out wrong.

    I was trying to stress the concept that the event might not always mean what you expect it to mean, such as the events getting out of order.  Thanks, for the additional explanation.
    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, January 8, 2010 1:26 PM
  • Carsten, how would you suggest going about turning this into a polled-mode system??? I'm willing to try it if it in fact works! I'll try both the ShiftBuffer and creating a Polled System and see which one works best.

    And once I do have a valid packet array of the 18 bytes, how do I extract the necessary values from bytes to the acceleration values? I'm not used to working with bytes at all... :-? My objective is to convert the Roll and Pitch rates in the packet into X/Y coordinates for moving an object/sprite around the screen.

    Byte 1 = MSB Roll Rate
    Byte 2 = LSB Roll Rate
    Byte 3 = MSB Pitch Rate
    Byte 4 = LSB Pitch Rate

    I have a Queue filled with a valid packet:
    private Queue<byte> Buffer = new Queue<byte>();

    I figure I should then do:
    byte[] PacketArray = Buffer.ToArray(); // Turn current buffer data into packet

    Then should I use BeginInvoke?

    Friday, January 8, 2010 4:56 PM
  • To program both a polled and a continuous system is to my opinion a waist of time. First you must decide how many telegrams you need per second. For a display, 4 values is usually appropriate. This is why most digital multimeters use this rate. For a regulator, many more may be necessary depending on the time constants.

    Then you must find out if the telegram rate in continuous mode can be adjusted. It is usually more efficient to have an automatic repetition, but if the rate is too high and cannot be adjusted, a polled version is preferred. It is crazy to transmit for example 120 telegrams per second if you only need 4.

    It is really not that difficult to get the various bytes. In both continuous mode and polled mode, the most appropriate is to receive in a byte array (shift register for continuous mode). Then you just need to transfer this array to the UI thread in a control.BeginInvoke call when you have received an entire telegram. It is then very easy just to take the various bytes and convert them to the 16-bit data words you need by means of simple shift operations. I am not familiar with the C# notation, but in VB an 8-bit shift left is just written as << 8. C# has something similar. Simply address for example the most significant byte from a wanted value and put this into a word. Then shift this word 8 bits to the left and add the least significant byte of the word. 3 instructions is all you need for each 16-bit value.

    Control.BeginInvoke is described in details in my tutorial.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Saturday, January 9, 2010 11:56 AM
  • Okay, I used Rudy's implementation:

    public

     

    short GetValue(byte MSB, byte LSB)
    {
           return (short)((MSB << 8) | (LSB & 0xFF));
    }

    However, it formats the numbers as:
    Pitch = 237; Roll = -863
    Pitch = 238; Roll = -865
    Pitch = 239; Roll = -858
    Pitch = 1533; Roll = -2386

    I'm trying to get it to display as:

    Roll (deg)     Pitch (deg)
    -1.566         0.761 
    -1.563         0.761 
    -1.563         0.758 
    -1.566         0.758 

    Tuesday, January 19, 2010 10:41 PM
  • What would you suggest??
    Tuesday, January 19, 2010 10:41 PM
  • That you show us some hexadecimal data from the sensor and tell us how you want these data interpreted and shown. You have just shown some code, which converts two bytes to a word - that's all. How can you expect that to give a floating point or fixed point result? There must be some scale factors and/or offsets.

    Besides, the 3 first lines may be converted to the roll and pitch you show, but the last line is so different from the other 3 that no conversion method will give a result similar to them, that is, in the order of -1.56 and 0.76.

    PS. I asked you how many telegrams you want per second. Is it the 100 from your original post or maybe only a few for a display? If you want us to help, maybe it is a good idea to answer the questions we ask? As I told you before, this project could have been solved in a few hours, but now it has taken over one month and you still don't seem to do much progress.

    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, January 20, 2010 8:45 AM
  • I'm sorry, Carsten. :-) You know, I'm inventing this stuff as I go along - I haven't done anything like this before and it's why I'm here and why I really appreciate help from experienced professionals like you and Rudy.

    Since it has a really high-quality medical application, I'm trying to get all 100 of the telegrams for precision because at the end of the test, the Pitch/Roll measurements from Angle Mode will be translated into a heat-map that will show the most and least amount of time spent at which specific pitch/roll's. I do have a working system and have great progress and a lot of it is thanks to some of your suggestions. And I'm a lot further along than I would have been without them and hopefully I can get this all wrapped up in the remaining amount of time. I really have been working hard! I didn't post any further scaling because I didn't quite understand the conversion or the numbers. I do have scale factors and offsets from the manual, but I'm not sure how to take those and convert the bytes into floating point numbers. When I do the conversion using the code I just posted to you guys I get numbers that can be positive and negative, rather than just 0-255.

    The really small numbers are from where the sensor is at its origin, with no movement, and the larger number is from when I moved it around. I can show you some of the raw data. I'll post that after this post, but here are the conversion factors:

    Angle mode is outputing static pitch and roll angles as digital data representing the actual value of the quantities measured. Data is sent as a signed 16-bit 2's complement integer. To convert the digital data to angle: angle = data * (SCALE)/2^15
    Angle is the angle in degrees (pitch/roll), data is the signed integer data output in the data packet, and SCALE is a constant which = 180 degrees for pitch/roll.
    Wednesday, January 20, 2010 2:14 PM
  • Okay, I used Rudy's implementation:

    public

     

    short GetValue( byte MSB, byte LSB)
    {
           return ( short )((MSB << 8) | (LSB & 0xFF));
    }

    However, it formats the numbers as:
    Pitch = 237; Roll = -863
    Pitch = 238; Roll = -865
    Pitch = 239; Roll = -858
    Pitch = 1533; Roll = -2386

    I'm trying to get it to display as:

    Roll (deg)     Pitch (deg)
    -1.566         0.761 
    -1.563         0.761 
    -1.563         0.758 
    -1.566         0.758 




    What a second? 
    You are reading integers, yet you are looking for decimal results. 

    Data is transmitted as 16 bits, in a Big/Little Endian format.  Whatever.
    But, what does the data actually represent. 

    Is it a four digit binary coded decimal? 
    Is it transmitting BCD data with a fixed decimal point?

    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, January 20, 2010 2:51 PM
  • Here's a link to some raw data put to time.
    https://spreadsheets.google.com/ccc?key=0AhMapSGtH0JMdElKcTBVR0t5QXkwQzhQNG0wejBDQUE&hl=en

    When the sensor is still (at origin) for roll and pitch it measures:
    Roll: -1.62 Pitch: 1.263

    However, right around 63.60 seconds or so, when motion is introduced (Right, Left, Front, Back), the measurements begin to change. These are the types of fluctuations I'm trying to pick up and record. At around 68 seconds it begin to look like so:
    Roll:-10.344 Pitch:14.464

    This is data from the proprietary program that came with the sensor, Gyro-View. It's a free program on their website. However, the function I posted above in my program reports numbers that are a lot different.
    Wednesday, January 20, 2010 3:18 PM
  • Just to let you know Rudy, I did use the Control.Invoke command like you'd said and it did work. I'm just trying to get it to convert the bytes to degrees in floating points, put the Pitch/Roll Measurements in as X Y coordinates, and attach a screen sprite to a PointF, and move it in accordance with the movement of the sensor. Would you all like to see the code now that it works?
    Wednesday, January 20, 2010 3:23 PM
  • IsaacOIM

    Shall a value of 237 give 0.761 and a value of -863 give -1.566? In that case, your formula is not right.

    (237 x 180) / 32768 = 1.30 (not 0.768)

    (-863 x 180) / 32768 = -4.74 (not -1.566)

    It is natural to assume that the maximum positive number of the 16-bit variable (32767) corresponds to almost 180 degrees (180 x 32767/32768) and the maximum negative number corresponds to -180 degrees. If this is the case, your formula is right.

    If this is right, just take the 16-bit integer, convert it to floating point and divide it by 182.044.

    Because you need all 100 values per second, you need to use the DataReceived event, but keep the number of BeginInvoke calls to an absolute minimum. There is a huge overhead with generation of TME objects etc.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, January 20, 2010 3:35 PM
  • Right, Carsten. 
    Isaac, what is the range of your engineering units?

    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, January 20, 2010 3:45 PM
  • I re-read your post and saw that you'd asked specifically for hexadecimal data. So, I'm going to work on getting that posted for you: the Spreadsheet I posted to you in that link is what I'm trying to get the data values to look like. I'm trying to get the hexadecimal bytes to convert to numbers similar to the Pitch/Roll angles in Degrees in the spreadsheet. Also, the numbers then get plugged into X/Y coordinates for a PointF, floating-point Point. I'm so thankful that the Shift Register Buffer works within the .29 microseconds in Continuous Output Mode and I don't have to do strange implementations with read and write pointers like I'd read on some other sites. Carsten I have indeed been reading your article.

    Time		Data Packet (Validated)							Pitch 	Roll
    11:10:02 AM	{ FF, 01, 09, FD, F4, 01, FB, FF, FF, 01, 09, FD, FE, 02, 05, FF, FF, 01 } 	2557	-3071
    11:10:04 AM	{ FF, 02, 0C, FD, D5, 01, E0, FF, FF, 01, E6, FE, 3B, 02, 20, FF, FF, 01 } 	3325	-11007
    11:10:05 AM	{ FF, 00, CB, FE, 4F, 02, 18, FF, FF, 00, AD, FE, 3C, 01, E7, FF, FF, 00 } 	-13314	20226
    11:10:06 AM	{ FF, 00, A6, FB, 56, 01, F7, FF, FF, 00, AD, FB, E0, 02, 88, FF, FF, 00 } 	-22789	22017
    11:10:06 AM	{ FF, 01, 04, FE, AD, 01, B0, FF, FF, 00, F9, FE, 58, 02, 4F, FF, FF, 00 } 	1278	-21247
    11:10:09 AM	{ FF, 03, 1D, FA, EC, 02, 06, FF, FF, 03, 1B, FA, E3, 01, FB, FF, FF, 03 } 	7674	-5118
    11:10:09 AM	{ FF, 03, 48, FA, B9, 01, FE, FF, FF, 03, 4E, FA, B8, 02, 03, FF, FF, 03 } 	18682	-18175
    11:10:09 AM	{ FF, 03, 55, FA, B2, 02, 04, FF, FF, 03, 53, FA, AD, 01, FD, FF, FF, 03 } 	22010	-19966
    11:10:09 AM	{ FF, 03, 7C, FA, 1D, 01, 96, FF, FF, 03, 81, F9, EE, 02, 6B, FF, FF, 03 } 	31994	7425
    11:10:13 AM	{ FF, D1, 03, AA, FF, FF, FF, DF, FF, E8, 03, C5, FF, FF, FF, F0, FF, F7 } 	938	-1
    11:10:13 AM	{ FF, 18, 06, F4, 5D, 01, 6F, FF, FF, 18, 4C, F4, 45, 01, 9D, FF, FF, 18 } 	1780	23809
    11:10:14 AM	{ FF, F4, 9F, FE, 7A, 03, 0B, FF, FF, F4, 08, FE, 73, 02, 6D, FF, FF, F3 } 	-24578	31235
    11:10:15 AM	{ FF, FE, 17, FE, 23, 02, 36, FF, FF, FE, 20, FD, AD, 02, C8, FF, FF, FE } 	6142	8962
    11:10:15 AM	{ FF, 60, FD, BC, 03, 18, FF, FF, FF, 61, FD, BA, 03, 17, FF, FF, FF, 62 } 	-580	792
    11:10:18 AM	{ FF, D8, FE, AD, 03, 82, FF, FF, FF, D2, FE, 93, 03, 62, FF, FF, FF, CC } 	-339	898
    11:10:19 AM	{ FF, 00, F8, FC, 4B, 02, 3F, FF, FF, 00, EB, FC, 59, 02, 40, FF, FF, 00 } 	-1796	19202
    11:10:20 AM	{ FF, 00, E0, FB, F3, 02, CE, FF, FF, 00, E0, FB, D6, 02, B1, FF, FF, 00 } 	-7941	-3326
    11:10:20 AM	{ FF, 00, DF, F5, EA, 02, BE, FF, FF, 00, E4, F5, E8, 02, C1, FF, FF, 00 } 	-8203	-5630
    11:10:21 AM	{ FF, 01, CC, F1, 1E, 01, DC, FF, FF, 01, D3, F0, E0, 02, A4, FF, FF, 01 } 	-13071	7681
    11:10:22 AM	{ FF, 00, F2, FA, C9, 02, B5, FF, FF, 00, EE, FA, E2, 02, CA, FF, FF, 00 } 	-3334	-14078
    11:10:22 AM	{ FF, 00, E9, FB, 12, 01, F6, FF, FF, 00, E7, FB, 27, 02, 09, FF, FF, 00 } 	-5637	4609
    11:10:22 AM	{ FF, 02, B7, FC, D8, 02, 8D, FF, FF, 02, CB, FC, AA, 02, 73, FF, FF, 02 } 	-18436	-10238
    11:10:22 AM	{ FF, 05, 41, FB, BA, 01, FB, FF, FF, 05, 69, FB, 9E, 02, 07, FF, FF, 05 } 	16891	-17919
    11:10:23 AM	{ FF, 07, C2, FB, 63, 02, 27, FF, FF, 07, F5, FB, 65, 02, 5C, FF, FF, 08 } 	-15621	25346
    11:10:23 AM	{ FF, 08, 2B, FB, 6F, 01, 9D, FF, FF, 08, 67, FB, 7D, 01, E7, FF, FF, 08 } 	11259	28417
    11:10:25 AM	{ FF, 02, 2E, 11, 6D, 00, AE, FF, FF, 02, 38, 11, 09, 00, 54, FF, FF, 02 } 	11793	27904
    11:10:26 AM	{ FF, 00, 50, 00, BE, 01, 0E, FF, FF, 00, 51, 00, A1, 00, F2, FF, FF, 00 } 	20480	-16895
    11:10:26 AM	{ FF, 00, 47, FF, 86, 01, CC, FF, FF, 00, 3F, FF, 76, 01, B4, FF, FF, 00 } 	18431	-31231
    11:10:26 AM	{ FF, FB, 42, FE, D7, 03, 12, FF, FF, FB, 25, FE, CC, 02, EA, FF, FF, FB } 	17150	-10493
    11:10:29 AM	{ FF, FE, 70, 03, 64, FF, FF, F8, 26, FE, 7D, 02, 99, FF, FF, F8, 4B, FE } 	28675	25855
    11:10:31 AM	{ FF, F8, 95, FF, 8A, 03, 16, FF, FF, F8, BA, FF, B3, 03, 64, FF, FF, F8 } 	-27137	-30205
    11:10:31 AM	{ FF, 11, 02, 73, FF, FF, FC, 79, FF, 20, 02, 94, FF, FF, FC, 89, FF, 32 } 	627	-1
    11:10:34 AM	{ FF, F2, 67, FF, 56, 02, AE, FF, FF, F2, 4C, FF, 0D, 02, 4A, FF, FF, F2 } 	26623	22018
    11:10:35 AM	{ FF, F6, 2A, 00, 1A, 01, 3A, FF, FF, F6, 37, 00, 14, 01, 41, FF, FF, F6 } 	10752	6657
    11:10:35 AM	{ FF, F7, 3E, FF, C4, 02, F8, FF, FF, F7, 42, FF, CA, 03, 02, FF, FF, F7 } 	16127	-15358
    11:10:36 AM	{ FF, F0, 0A, 02, 59, 01, 55, FF, FF, EF, CD, 02, 64, 02, 22, FF, FF, EF } 	2562	22785
    11:10:40 AM	{ FF, 04, 47, 09, 4F, 00, A3, FF, FF, 04, 3E, 09, 15, 00, 60, FF, FF, 04 } 	18185	20224
    11:10:44 AM	{ FF, 02, 52, EF, BC, 01, FF, FF, FF, 01, D2, EF, BF, 02, 81, FF, FF, 01 } 	21231	-17407
    11:10:44 AM	{ FF, FA, EB, F3, DB, 03, B3, FF, FF, FA, 58, F4, 02, 02, 48, FF, FF, F9 } 	-5133	-9469
    11:10:45 AM	{ FF, FE, 47, F9, ED, 03, 2B, FF, FF, FE, 13, FA, 47, 02, 52, FF, FF, FD } 	18425	-4861
    11:10:46 AM	{ FF, 02, D8, 00, B2, 01, 8C, FF, FF, 02, 42, 00, B1, 00, F5, FF, FF, 01 } 	-10240	-19967
    11:10:48 AM	{ FF, 04, BB, 07, 43, 01, 09, FF, FF, 04, 8D, 06, E2, 01, 79, FF, FF, 04 } 	-17657	17153
    11:10:50 AM	{ FF, 00, 95, FC, 0C, 01, 9D, FF, FF, 00, 81, FC, 66, 01, E3, FF, FF, 00 } 	-27140	3073
    11:10:51 AM	{ FF, FF, F1, F9, 8E, 03, 77, FF, FF, 00, 0A, FA, 04, 01, 08, FF, FF, 00 } 	-3591	-29181
    11:10:51 AM	{ FF, 00, F5, FA, EC, 02, DB, FF, FF, 01, 0E, FB, 1B, 01, 25, FF, FF, 01 } 	-2566	-5118
    11:10:53 AM	{ FF, 01, 12, FC, F0, 01, FF, FF, FF, 01, 13, FC, F1, 02, 01, FF, FF, 01 } 	4860	-4095
    

    This is about 1/2 a second's worth of data.

    Wednesday, January 20, 2010 4:16 PM
  • I apologize again, Carsten! No, it wasn't a conversion table - the second set of numbers were just what I was trying to show you as what it should look like. They weren't 1-to-1. However, I'm going to try that method and see the result.

    Wednesday, January 20, 2010 4:24 PM
  • :-) That's a question I'd like to know as well! I'm not really sure at this point. I'm still trying to figure these issues out... I'm going to try to see if the Gyro-View program can give me that info. That's the minimum and maximum distance the sensor can measure in degrees, right Rudy?
    Wednesday, January 20, 2010 4:26 PM
  • :-) That's a question I'd like to know as well! I'm not really sure at this point. I'm still trying to figure these issues out... I'm going to try to see if the Gyro-View program can give me that info. That's the minimum and maximum distance the sensor can measure in degrees, right Rudy?



    Right, but not necessarily. 
    The minimum and maximum distance the sensor can measure would fall within the range of engineering units. 
    They do not have to one and the same.

    Here's a bad example.
    The speedometer in your car can display speeds in the range of 0 to 100 mph/kph. 
    The effective limits of operating range of the car's speed stays within posted speed limits, 0 to 55 mph/kph.
    In practice, transmitted speed values should never exceed the operating range.
    In theory, transmitted speed values can exceed the rated operating range.

    When raw digital data is collected you get a stream of integers.
    This data stream must be normalized to a calibrated scale of engineering units.  (note 2nd definition at top of page.)
    In my bad example, I used mph, miles per hour, and kph, kilometers per hour.

    This step in the processing of the data is what Carsten was doing here.
    He was converting the raw data to engineering units, normalizing it.

    Shall a value of 237 give 0.761 and a value of -863 give -1.566?
    In that case, your formula is not right.

    (237 x 180) / 32768 = 1.30 (not 0.768)

    (-863 x 180) / 32768 = -4.74 (not -1.566)




    Mark the best replies as answers. "Fooling computers since 1971."
    Wednesday, January 20, 2010 5:13 PM
  • Thanks, all. I got it to scale properly. It now displays the numbers in the correct format. I'm not sure how I could have been so stupid... All I was missing was the formula in the manual. Thank you for your patience.
    Thursday, January 21, 2010 2:44 PM
  • I saw that you updated the Serial Communications Page, Carsten. Nice work!
    Thursday, January 21, 2010 3:23 PM
  • I saw that you updated the Serial Communications Page, Carsten. Nice work!

    Please use the test program from my tutorial

    SALUTE THAT!  5 STAR JOB, CARSTEN!
    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 21, 2010 3:36 PM
  • Rudy, you bring up an interesting issue. What I did was attach an Analog Gauge control to the data the sensor is sending out, however I'd need to know a minimum and maximum to put on the scale for the output. I'm trying to represent the Pitch and Roll Angles graphically. Right now that gauge and a Text Label updating X and Y coordinates are attached to the BeginInvoke call. However, if I try to add more to the call, there's a huge delay in updating the UI thread when the sensor is moved. I'm still figuring out the cursor idea, but is this the right way to use Control.BeginInvoke?

    public

     

    void UpdateDisplay(object machinery, DeviceInfoEventArgs device)

    {

                IAsyncResult labelMeasurementsResult = labelMeasurements.BeginInvoke(new EventHandler(delegate { labelMeasurements.Text = "X= " + Math.Round(device.PitchAngle, 4).ToString() + "; Y=" + Math.Round(device.RollAngle, 4).ToString(); analogMeterPitch.Value = (float)Math.Round(device.PitchAngle, 4); analogMeterRoll.Value = (float)Math.Round(device.RollAngle, 4); })); labelMeasurements.EndInvoke(labelMeasurementsResult);
    }

    Or is this the proper way to handle it: 3 separate BeginInvokes?

                IAsyncResult labelMeasurementsResult = labelMeasurements.BeginInvoke(new EventHandler(delegate
                    {
                        labelMeasurements.Text = "X= " + Math.Round(device.PitchAngle, 4).ToString() + "; Y=" + Math.Round(device.RollAngle, 4).ToString();
                    }));
                IAsyncResult aMeterPitchResult = analogMeterPitch.BeginInvoke(new EventHandler(delegate
                    {
                        analogMeterPitch.Value = (float)Math.Round(device.PitchAngle, 4);
                    }));
                IAsyncResult aMeterRollResult = analogMeterRoll.BeginInvoke(new EventHandler(delegate
                    {
                        analogMeterRoll.Value = (float)Math.Round(device.RollAngle, 4);
                    }));
    
                labelMeasurements.EndInvoke(labelMeasurementsResult);
                analogMeterPitch.EndInvoke(aMeterPitchResult);
                analogMeterRoll.EndInvoke(aMeterRollResult);
    Right now I have it publishing the measurements in the ShiftRegisterBuffer class like so:


    (...at the top of the class...)
    
            public delegate void NewMeasurementHandler(
                object machinery, DeviceInfoEventArgs deviceInfo);  // Delegate to handle new information obtained from the sensor.
            public NewMeasurementHandler NewMeasurement;
    
    
    .........
    (Later in the code within the method that validates the data packet)
    
    double PitchAngle = ((this.GetValue(temp[2], temp[3]) * 180) / Math.Pow(2, 15));
                        double RollAngle = ((this.GetValue(temp[4], temp[5]) * 180) / Math.Pow(2, 15));
                        Console.WriteLine("Pitch = {0}; Roll = {1}", PitchAngle, RollAngle);
    
                        //Publish the data for EventHandler subscribers to subscribe to.
                        DeviceInfoEventArgs deviceInfo = new DeviceInfoEventArgs(PitchAngle, RollAngle);
                        if (NewMeasurement != null)
                        {
                            NewMeasurement(this, deviceInfo);
                        }
    

    And here's a closer look at the DeviceInfoEventArgs that takes the new measurements coming off the Shift Register Buffer:

        public class DeviceInfoEventArgs : EventArgs
        {
            public double PitchAngle;
            public double RollAngle;
    
            public short PitchRate;
            public short RollRate;
            public short YawRate;
            public short AccelX;
            public short AccelY;
            public short AccelZ;
            public short TempVoltage;
            public short InternalTime;
    
            public DeviceInfoEventArgs(double PitchAngle, double RollAngle)
            {
                this.PitchAngle = PitchAngle;
                this.RollAngle = RollAngle;
            }
            public DeviceInfoEventArgs(short PitchRate, short RollRate, short YawRate, short AccelX, short AccelY, short AccelZ, short TempVoltage, short InternalTime)
            {
                this.PitchRate = PitchRate;
                this.RollRate = RollRate;
                this.YawRate = YawRate;
                this.AccelX = AccelX;
                this.AccelY = AccelY;
                this.AccelZ = AccelZ;
                this.TempVoltage = TempVoltage;
                this.InternalTime = InternalTime;
            }
        }
    



    Am I using Control.BeginInvoke correctly or is there a better, performance-worthy way to use the function? (Please keep in mind that
    Thursday, January 21, 2010 8:27 PM
  • Okay, bring me up to speed again.
    Did you settle on using a Thread or BackgroundWorker to read the port? 



    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 21, 2010 8:45 PM
  • The BackgroundWorker. I'm sorry the last post looks ridiculous. I forgot about the formatting problem...
    It just simply uses SerialPortDataEventHandler(Data_Received). Then, it makes peanut butter come out of the sensor. Just kidding, but I'm so eager to get this together and working properly. I could show you the SerialPort class I wrote to get that part to work properly. Would you like to see it?
    Thursday, January 21, 2010 9:00 PM
  • Set the ReportsProgress property on the worker instance, and then call worker.ReportsProgress(int, object) inside of the DoWork thread.  This will fire the worker.ProgressChanged event.  You can handle the event on the main thread.  All of the mechanisms to marshal data to the UI thread are built into the BGW class.  Easy as pie.


    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 21, 2010 9:04 PM
  • IsaacOIM

    What are you doing with your EndInvokes? BeginInvoke followed by EndInvoke is equivalent to Invoke, that is, a synchronous call where you lock the UI thread and the thread-pool-thread together. What is the purpose of that? It has no meaning to return anything (IAsyncResult) back to the thread pool thread.

    I would definitely do all the math on the UI thread and just run the shift register on the event handler for the DataReceived event and then transfer the buffer in the BeginInvoke call when a full telegram is found. There is a huge overhead each time you call BeginInvoke or Invoke so do it as little as possible - especially at 100 telegrams per second.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Thursday, January 21, 2010 9:10 PM
  • Good point.  The UI is not going update faster than the video frame rate, which is approximately 25-30 frames per second.  I would think updating the UI around 10 times per second should be sufficient.

    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 21, 2010 9:20 PM
  • Would this be the worker.ProgressChanged event?

            public delegate void NewMeasurementHandler(
                object machinery, DeviceInfoEventArgs deviceInfo);  // Delegate to handle new information obtained from the sensor.
            public NewMeasurementHandler NewMeasurement;

    double PitchAngle = ((this.GetValue(temp[2], temp[3]) * 180) / Math.Pow(2, 15));
                        double RollAngle = ((this.GetValue(temp[4], temp[5]) * 180) / Math.Pow(2, 15));
                        Console.WriteLine("Pitch = {0}; Roll = {1}", PitchAngle, RollAngle);

    ....

                        //Publish the data for EventHandler subscribers to subscribe to.
                        DeviceInfoEventArgs deviceInfo = new DeviceInfoEventArgs(PitchAngle, RollAngle);
                        if (NewMeasurement != null)
                        {
    ----------->             NewMeasurement(this, deviceInfo);
                        }

    Thursday, January 21, 2010 9:42 PM
  • Hmm...  " * 180 / Math.Pow(2, 15) " is the same as " / 182.044 ", but the latter executes much faster.

    You will kill your application with all these heavy system calls. 100 telegrams per second with graphical update is close to the limit if you don't learn to do things efficient.

    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Thursday, January 21, 2010 10:27 PM
  • Of course, easy as pie for you! I've been trying to figure it out for the past 2-3 hours now. Could you possibly offer me an example? I have the Process() function that converts the 18 byte Packet values to data values putting them in a DeviceInfoEventArgs class that derives from EventArgs and publishes it using a delegate NewMeasurementEventHandler(object equipment, DeviceInfoEventArgs deviceInfo). What I've been trying to figure out is what to do with labelMeasurements.BeginInvoke, and where to update the labelMeasurements.text property. And how do I then update another with the same values in DeviceInfoEventArgs?
    I'm so sorry for not being as up-to-speed... Really appreciative of all of your help, all of you.

    Carsten: Great catch, I'll go ahead and make that change.
    Friday, January 22, 2010 6:00 PM
  • Good catch, I'll go ahead and make that change.

    However, wouldn't it be better to multiply the two values by (0.0054931640625) ? (Which is 180/32768)

    Friday, January 22, 2010 6:19 PM
  • Here's a complete form in a single class file.
    It demonstrates all of the members of the BGW.
    Test this by adding a new class to a test project, not a form.
       
    //  Form1.cs  -- copy the code into a new class file, NOT a new form file.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Diagnostics;
    using System.Threading;

    public partial class BGWDemoForm1 : Form
        {
            BackgroundWorker worker;
            private Button btnPause;
            private bool workerPaused;

            int workerData;

            public BGWDemoForm1()
            {
                InitializeComponent();
            }

            private Button btnStart;
            private Button btnStop;
            private ProgressBar progressBar1;
            private Label label1;
            private Label label2;
            /// <summary>
            /// Required designer variable.
            /// </summary>
            private System.ComponentModel.IContainer components = null;

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                base.Dispose(disposing);
            }

            #region Windows Form Designer generated code

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.btnStart = new System.Windows.Forms.Button();
                this.btnStop = new System.Windows.Forms.Button();
                this.progressBar1 = new System.Windows.Forms.ProgressBar();
                this.label1 = new System.Windows.Forms.Label();
                this.label2 = new System.Windows.Forms.Label();
                this.btnPause = new System.Windows.Forms.Button();
                this.SuspendLayout();
                //
                // btnStart
                //
                this.btnStart.Location = new System.Drawing.Point(76, 94);
                this.btnStart.Name = "btnStart";
                this.btnStart.Size = new System.Drawing.Size(75, 23);
                this.btnStart.TabIndex = 0;
                this.btnStart.Text = "Start";
                this.btnStart.UseVisualStyleBackColor = true;
                this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
                //
                // btnStop
                //
                this.btnStop.Location = new System.Drawing.Point(239, 93);
                this.btnStop.Name = "btnStop";
                this.btnStop.Size = new System.Drawing.Size(75, 23);
                this.btnStop.TabIndex = 1;
                this.btnStop.Text = "Stop";
                this.btnStop.UseVisualStyleBackColor = true;
                this.btnStop.Click += new System.EventHandler(this.btnStop_Click);
                //
                // progressBar1
                //
                this.progressBar1.Location = new System.Drawing.Point(76, 65);
                this.progressBar1.Name = "progressBar1";
                this.progressBar1.Size = new System.Drawing.Size(238, 23);
                this.progressBar1.TabIndex = 2;
                //
                // label1
                //
                this.label1.AutoSize = true;
                this.label1.Location = new System.Drawing.Point(174, 72);
                this.label1.Name = "label1";
                this.label1.Size = new System.Drawing.Size(35, 13);
                this.label1.TabIndex = 3;
                this.label1.Text = "label1";
                //
                // label2
                //
                this.label2.AutoSize = true;
                this.label2.Location = new System.Drawing.Point(96, 40);
                this.label2.Name = "label2";
                this.label2.Size = new System.Drawing.Size(35, 13);
                this.label2.TabIndex = 4;
                this.label2.Text = "label2";
                //
                // btnPause
                //
                this.btnPause.Location = new System.Drawing.Point(158, 93);
                this.btnPause.Name = "btnPause";
                this.btnPause.Size = new System.Drawing.Size(75, 23);
                this.btnPause.TabIndex = 5;
                this.btnPause.Text = "Pause";
                this.btnPause.UseVisualStyleBackColor = true;
                this.btnPause.Click += new System.EventHandler(this.btnPause_Click);
                //
                // Form1
                //
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(376, 196);
                this.Controls.Add(this.btnPause);
                this.Controls.Add(this.label2);
                this.Controls.Add(this.label1);
                this.Controls.Add(this.progressBar1);
                this.Controls.Add(this.btnStop);
                this.Controls.Add(this.btnStart);
                this.Name = "Form1";
                this.Text = "Form1";
                this.Load += new System.EventHandler(this.Form1_Load);
                this.ResumeLayout(false);
                this.PerformLayout();

            }

            #endregion


            private void Form1_Load(object sender, EventArgs e)
            {
                this.workerPaused = false;
                //
                this.btnStop.Enabled = false;
                this.btnPause.Enabled = false;
                //
                this.label1.Text = "";
                this.label1.Visible = false;
                //
                this.label2.Text = "No Progress To Report.";
                //
                this.progressBar1.Step = 1;
                this.progressBar1.Maximum = 100;
                this.progressBar1.Minimum = 0;
                this.progressBar1.Style = ProgressBarStyle.Continuous;
                //
                InitializeBGW();
                return;

            }

            private void InitializeBGW()
            {
                this.worker = null;
                this.worker = new BackgroundWorker();
                this.worker.WorkerReportsProgress = true;
                this.worker.WorkerSupportsCancellation = true;
                this.worker.DoWork +=
                    new DoWorkEventHandler(worker_DoWork);
                this.worker.ProgressChanged +=
                    new ProgressChangedEventHandler(worker_ProgressChanged);
                this.worker.RunWorkerCompleted +=
                    new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                this.worker.Disposed += new EventHandler(worker_Disposed);
                //
            }

            private void btnStart_Click(object sender, EventArgs e)
            {
                this.InitializeBGW();
                this.label1.Visible = true;
                this.btnStop.Enabled = true;
                this.btnPause.Enabled = true;
                this.btnStart.Enabled = false;
                this.workerPaused = false;
                this.worker.RunWorkerAsync(this.workerData);
            }

            private void btnStop_Click(object sender, EventArgs e)
            {
                this.btnStop.Enabled = false;
                this.btnPause.Enabled = false;
                this.btnStart.Enabled = true;
                this.worker.CancelAsync();
                this.workerData = 0;
                this.progressBar1.Value = this.workerData;
            }

            private void btnPause_Click(object sender, EventArgs e)
            {
                this.workerPaused = true;
                this.btnStop.Enabled = true;
                this.btnPause.Enabled = false;
                this.btnStart.Enabled = true;
                this.worker.CancelAsync();
            }

            void worker_DoWork(object sender, DoWorkEventArgs e)
            {
                DoTheWork(e);
            }

            private void DoTheWork(DoWorkEventArgs e)
            {
                int startPosition = (int)e.Argument;
                for (int i = startPosition; i < 100; i++)
                {
                    System.Threading.Thread.Sleep(90);
                    worker.ReportProgress(i, "Background Worker Is In Progress ...");
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;  // exiting the loop properly,
                        i = 100;  // calling return here can throw future exceptions
                    }
                }
                return;
            }

            void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                this.workerData = e.ProgressPercentage;
                string progressReport = (string)e.UserState;
                this.progressBar1.Value = this.workerData;
                this.label1.Text = this.progressBar1.Value.ToString();
                this.label2.Text = progressReport;
                //
                // fire an event in this class to notify other classes
                //
                return;
            }

            void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                if (e.Error != null)
                {
                    this.label2.Text = "An error was encountered.";
                }
                else
                {
                    if (e.Cancelled)
                    {
                        if (this.workerPaused)
                        {
                            this.label2.Text = "The work was paused by the user.";
                            this.workerPaused = false;
                        }
                        else
                        {
                            this.label2.Text = "The work was cancelled by the user.";
                            this.workerData = 0;
                            this.progressBar1.Value = this.workerData;
                            //this.label1.Visible = false;  // let user see amount of progress
                        }
                    }
                    else
                    {
                        this.label1.Visible = false;
                        this.progressBar1.Value = this.progressBar1.Minimum;
                        this.label2.Text = "Work has been completed.";
                        this.workerData = 0;
                        this.btnStop.Enabled = false;
                        this.btnPause.Enabled = false;
                        this.btnStart.Enabled = true;
                    }
                }
                //
                // Dispose of the instance of the bgw object, do not re-use...!!!
                //
                this.worker.Dispose();
                //
                return;
            }

            void worker_Disposed(object sender, EventArgs e)
            {
                string title = this.Text;
                this.Text = "Worker Has Been Disposed";
                Thread.Sleep(250);
                this.Text = title;
                return;
            }
        }

    Mark the best replies as answers. "Fooling computers since 1971."
    • Marked as answer by IsaacOIM Monday, January 25, 2010 6:23 PM
    Friday, January 22, 2010 6:29 PM
  • Wow, thank you, Rudy! I can't wait until I'm as good with C# as you presently are! I'm going to analyze and break this down and apply all the concepts to what I'm doing. Thank you.
    Friday, January 22, 2010 8:32 PM
  • Please note that I call Dispose on the worker instances at the end of the RunWorkerCompleted event handler.
    The title text of the form briefly flashes a message to that effect.

    Please note how I exit the loop inside of DoWork instead of simply jumping out of it by calling return.
    Try changing the code. 
    Repeatedly starting/pausing, starting/pausing the worker throws an exception eventually.

    Please note that background workers do not throw exceptions.
    Rather you must detect them in the Completed event handler.

    Please note how the UI is updated on the main thread by calling worker.ReportProgress(int, object);

    Please note how care was taken so that the worker/thread does not reference any class level variables.
    Also note how the worker/thread only references variables declared within its' methods, or passed in as parameters in DoWork.

    Happy Coding.

    Rudy  =8^D

    @CARSTEN:  I have a VB translation of that if you are interested. 
    It was originally posted on the Windows Forms General forum 2-3 months ago. 
    Search is not working well.



    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, January 22, 2010 9:06 PM
  • I finally found the link.

    http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/52dcb460-90a3-45f2-ae23-bcb60514c542

    It has the same form in VB, C#, and C++.

    Rudy  =8^D
    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, January 22, 2010 11:10 PM
  • Rudy,
    I went through your program and saw and understand how it works. BackgroundWorker is an ingenious little feature. However, how could I incorporate the Control.BeginInvoke call into this?
    Or does this make using BeginInvoke unnecessary?

    When the buffer validates a new packet, would you suspect it would be wise to still publish the new measurement as an event, then somehow grab the data from the event into the DoWork() for the UI/Main thread?
    Isaac
    OIM
    Monday, January 25, 2010 5:03 PM
  • IsaacOIM

    What are you doing with your EndInvokes? BeginInvoke followed by EndInvoke is equivalent to Invoke, that is, a synchronous call where you lock the UI thread and the thread-pool-thread together. What is the purpose of that? It has no meaning to return anything (IAsyncResult) back to the thread pool thread.

    I would definitely do all the math on the UI thread and just run the shift register on the event handler for the DataReceived event and then transfer the buffer in the BeginInvoke call when a full telegram is found. There is a huge overhead each time you call BeginInvoke or Invoke so do it as little as possible - especially at 100 telegrams per second.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.


    You might not want to call BeginInvoke, but call ReportProgress instead and transfer an object back to the UI thread.
    Define a custom class to hold your data to transfer as type System.Object.
    You do not need to call ReportProgress more than 10-20 times per second.
    The video frame rate is only 25-30 frames per second.
    Updating the UI faster than the frame rate is a waste.  Save on some of that overhead.

    Mark the best replies as answers. "Fooling computers since 1971."
    Monday, January 25, 2010 6:25 PM
  • Hmm...so actually creating a System.Object is more performance-worthy for passing the data than publishing/raising event data using the delegate/event methods?

    Monday, January 25, 2010 9:41 PM
  • More performance worthy?  I dunno.  One would have to devise a test and see.  I have been simply pointing out how calling ReportProgress on a BGW as an alternative means for getting data from the worker to the UI thread.

    I also wish to stress that you do not want to update the UI for every completed packet or telegram if they are coming in at 100 per second.


    Mark the best replies as answers. "Fooling computers since 1971."
    Monday, January 25, 2010 10:08 PM
  • Sorry, but I still think that this is getting awful complicated.

    Let us face the facts:

    Since you need to receive 100 telegrams per second, you need to utilize the DataReceived event. This means that all reception is done on the thread pool thread used for that event.

    The 100 telegrams per second shall be used for updating the graph. Since that graph is not stored on an object created on the thread pool thread we need to transfer the values to another thread. This can of course be a background worker, but why make it so complicated? What needs multithreading is the reception (shift register), but this is already done on another thread - the thread pool thread - so why make a third one? Just don't call Invalidate more than 20 times per second.

    There are four thread safe methods you can use to transfer data from the thread pool thread to another thread - Invoke, BeginInvoke, EndInvoke and CreateGraphics.  This leaves you 3 options - synchronous transfer by means of Invoke, asynchronous transfer by means of BeginInvoke or direct graphical creation by means of CreateGraphics. In chapter "SerialPort Receive" of my tutorial you can read about the pro's and contras of Invoke and BeginInvoke. Usually I recommend BeginInvoke, but for very high speed reception like yours, it is perhaps worth looking at Invoke since this may prevent an overload. In both cases, run the shift register on the thread pool thread so that you only need to call BeginInvoke or Invoke when you have a finished telegram. It is also possible to create the graphics directly by means of CreateGraphics, but then you must have a handler for the Paint event in case something needs to be redrawn.

    Start designing your software structure as simple as possible and ask yourself some basic questions like:

    1) Where do I want to store the data for later use? In a data base or only on the graph, and how will I handle a repaint if the data is only stored on the graph?

    2) How many display updates per second is needed?

    3) How would I handle an overload condition where telegrams comes in faster than I can handle - it may be necessary to throw some telegrams away?

    4) How shall the data be transfered from the event handler for the DataReceived event - Invoke or BeginInvoke. Note that Invoke may make it possible to use the shift register buffer directly and just use the standard delegate MethodInvoker to "wake" the method on the UI thread. This may save some time for data copying and reduce the number of Invoke calls, and it may also handle the overload condition by simply throwing all telegrams but the last one away. The shift register can only hold one telegram. If there are still data (BytesToRead) when a telegram is received, it means that your system has been overloaded. In that case, just continue to put data into the shift register even though it will overwrite the first telegram. In that case, you can't handle all telegrams anyway, so it is better just to return the latest one.

    Etc.




    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Tuesday, January 26, 2010 8:11 AM
  • Okay, bring me up to speed again.
    Did you settle on using a Thread or BackgroundWorker to read the port? 



    Mark the best replies as answers. "Fooling computers since 1971."


    Carsten, Isaac reported that he is using a BGW, not a thread.
    How do you suggest that he get the data back to the UI threa?  And how oftern?

    My suggestion was to call worker.ReportProgress, and so I posted a sample of how to do it.
    There is no need to update the UI more than 10-15 times per second.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."

    EDIT:
    In retrospect, updating the UI 10-15 times per second, ups, will most likely be too fast for the read.  A numerical digital display would be an unreadable blur at 15 ups, updates per second.  Your eye is not going to really catch anything faster than 200mSec, which is update rate of 5 ups, or onec every 20 messages

    You could calculate a moving average for every 20 messages, and display that while still writing actual raw data to your database.
    • Edited by Rudedog2 Thursday, January 28, 2010 2:31 PM : updates per second, ups
    Tuesday, January 26, 2010 3:51 PM
  • Right Carsten,
    After evaluating the given information, and coming to the realization that the UI only needs to update at the screen refresh rate, I see now that getting 100 per second is unnecessary. Yes, I should get the latest telegram and display that one. I'm also looking to database the measurements because they will be recorded per 30-second testing sessions, so doing a direct CreateGraphics wouldn't be wise. I'm currently re-writing the program I'd made to see if it's better to do a BackgroundWorker or stick to Invokes. Please bear with me a little longer as I've gotten so far and have just this little part to figure out...
    IsaacOIM
    Thursday, January 28, 2010 2:22 PM
  • "I'm currently re-writing the program I'd made to see if it's better to do a BackgroundWorker or stick to Invokes."

    What do you mean? If you use the DataReceived event, you need a thread safe method to transfer the data from the thread pool thread to the UI thread. In practice this means Invoke or BeginInvoke - unless you want to make a PostMessage yourself. BackgroundWorker is used to do something in the background. It is not an alternative to Invoke.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Thursday, January 28, 2010 3:39 PM
  • What if there were no UI thread, only a Console Application?
    This could mean that there is not an object with an Invoke/BeginInvoke method to call.
    How would you get the data back to the main thread in a Console Application scenario?

    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 28, 2010 3:55 PM
  • BackgroundWorker is used to do something in the background. It is not an alternative to Invoke.

    But it is an alternative to BeginInvoke.  The ProgressChanged event is called asynchronously.  The BackgroundWorker is a complex component that greatly simplifies threading for the novice, particularly the transfer of data from the background thread to the UI thread.
    Thursday, January 28, 2010 7:10 PM
  • BackgroundWorker is used to do something in the background. It is not an alternative to Invoke.

    But it is an alternative to BeginInvoke.  The ProgressChanged event is called asynchronously.  The BackgroundWorker is a complex component that greatly simplifies threading for the novice, particularly the transfer of data from the background thread to the UI thread.

    Thanks.
    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 28, 2010 7:26 PM
  • Hmm... I suppose where I'm confused then is the placement of the Control.BeginInvoke/Invoke call within the actual code. I'm trying to follow your advice from when you said, "I would definitely do all the math on the UI thread and just run the shift register on the event handler for the DataReceived event and then transfer the buffer in the BeginInvoke call when a full telegram is found." That's the hard part  - I suppose what I'm really asking is how do I call the BeginInvoke to where I'm transferring the buffer?
    Thursday, January 28, 2010 7:56 PM
  • BackgroundWorker is used to do something in the background. It is not an alternative to Invoke.

    But it is an alternative to BeginInvoke.  The ProgressChanged event is called asynchronously.  The BackgroundWorker is a complex component that greatly simplifies threading for the novice, particularly the transfer of data from the background thread to the UI thread.
    Simply call worker.ReportProgress(Int32, object), and handle the worker.ProgressChanged event on the UI thread.

    Mark the best replies as answers. "Fooling computers since 1971."
    Thursday, January 28, 2010 8:04 PM
  • So when I register the DataReceived event for the Serial Port, should I put it towards the BackgroundWorker.DoWork and have the DoWork enqueue new bytes into the shift register?

    Thursday, January 28, 2010 10:07 PM
  • John Wein and Rudy

    "But it is an alternative to BeginInvoke.  The ProgressChanged event is called asynchronously.  The BackgroundWorker is a complex component that greatly simplifies threading for the novice, particularly the transfer of data from the background thread to the UI thread."

    No, not in this case. When the DataReceived event fires, you are on a thread pool thread - not on a thread of a backgroundworker. Therefore, you cannot call ProgressChanged, but need to use one of the four thread safe methods (or make your own PostMessage).

    Please do not confuse things.


    IsaacOIM

    "Hmm... I suppose where I'm confused then is the placement of the Control.BeginInvoke/Invoke call within the actual code. I'm trying to follow your advice from when you said, "I would definitely do all the math on the UI thread and just run the shift register on the event handler for the DataReceived event and then transfer the buffer in the BeginInvoke call when a full telegram is found. " That's the hard part  - I suppose what I'm really asking is how do I call the BeginInvoke to where I'm transferring the buffer?"

    It is actually very simple. When you have found a telegram in the shift register, just call Invoke or BeginInvoke in one of the next statements - exactly as it is shown in my tutorial. Forget that Backgroundworker. It is only relevant in case you want to poll and need a thread to do that. The only decision you have to make is whether you want to use Invoke or BeginInvoke as I have explained before.




    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Thursday, January 28, 2010 10:43 PM
  • I don't know much about the serial port, but If I were planning on using the port and its DataReceived event, I'd peruse this thread carefully:

    How does SerialPort handle DataReceived

    Our guru seems to have left us, but searching for a subject with the subject's text and "nobugz" generally turns up the most valid result.

    Thursday, January 28, 2010 11:38 PM
  • John Wein

    You could also read my tutorial and especially the chapter "SerialPort Events" , which describes the bahaviour of the DataReceived event in details.

    All the knowledge of the thread you refer to plus a lot more are captured in that tutorial. As you can see, I actually participated in that thread, but of course, somebody with five medals cannot learn anything from a pure amateur with only three!

    If there is something in my tutorial you don't find valid, please let me know, but maybe it is a good idea to read it first before you question the validity! I agree with you that the answers from "nobugz" are among the absolute best in these .net forums, but not all his answers concerning SerialPort are correct (we have had our discussions).


    John and Rudy

    The OP has taken the very basic decision to use the sensor in continuous mode. This means that he will receive 100 telegrams per second, and there is only one way to handle that - use the DataReceived event. Please do not confuse things with "object oriented methods", unnecessary backgroundworkers etc. The OP has a tight time schedule and a job to be done. Let me suggest that I handle the SerialPort stuff - the only thing that I really know something about in .net. Then you can take over when it comes to the graphics where my knowledge is very limited.


    IsaacOIM

    Because you will be pushing your systems to the limit with 100 telegrams per second, I suggest the following:

    1) Run the shift register in the event handler for the DataReceived event.

    2) When you have found a telegram (checksum OK and first byte 0xFF) call Me.Invoke (I think it is this.Invoke in C#) with the standard delegate MethodInvoker. This delegate does not transfer any data, but it will "wake" the method on the UI thread. Invoke will then lock the UI thread and the thread pool thread together. There are more important benefits of this:

    a) The DataReceived event will not fire again until the job on the UI thread is done.

    b) You can use the shift register buffer directly as it cannot be overwritten. This saves some byte copying and therefore some time.

    c) There will never be more messages on the message queue than your system can handle.

    d) Because MethodInvoker has no data to transfer, it can cut some corners internal in .Net and therefore execute faster than other delegates.

    3) When the job is done and you return from the Invoke call, the DataReceived event can fire again. In the meantime you may have received one or more telegrams. Simply run the shift register until there are no more bytes in the receive buffer (BytesToRead = 0) and then search for the telegram termination (checksum OK etc.). In this way, you will just overwrite all telegrams but the last one and in this way prevent an overflow. In case of heavy graphics etc., it may be necessary to throw some telegrams away.

    4) Remember to close the SerialPort from a different thread to prevent the deadlock situation mentioned in my tutorial, or stop the transmissions from the sensor and empty the receive buffer before you close the port.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, January 29, 2010 7:46 AM
  • @Carsten:  The only OOP concepts that I introduced in this thread was for the definition of a Data Packet.  "A class should know how to take care of itself.  No more.  No less."  I never broached the subject of reading the port, only how to best use a Background Worker if that is what you wish to do.

    I noticed that you failed to reply to my rhetorical question for the scenario that I described above.  I assume that you did not see it amongst all of the stuff in this thread.  How would you achieve your design strategy in a Console Application? 

    On what object would you have laying around on which to call Invoke/BeginInvoke?  You mention "this.Invoke" for C# which is equivalent to "me.Invoke" in VB, both of which require a Form instance.  You don't have the luxury of a form instance in a Console Application.  How would you do it?  Just curious.   The only alternative that I see would be to use the .NET APM, which you have seemingly already discarded as a bad choice.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, January 29, 2010 2:14 PM
  • Rudy

    Since control.Invoke and control.BeginInvoke make a PostMessage call, they both depend on a message pump running on the target thread. On the other hand, all threads, which runs a message pump, also have a hidden "Parking Windows", which can be used as the marshaling control - see chapter "Control Invoke/BeginInvoke" of my tutorial. So the answer must be that the thread used for the console application must run a message pump. If it doesn't do that, you will probably need ASP (Asynchronous Procedure Calls) or something like that - see chapter "Interrupts" of the tutorial, but I have never tried it and is certainly not an expert on that field.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, January 29, 2010 2:40 PM
  • I know.  That's why I asked. 
    You had already rejected the APM model, which would not require a message pump.

    There is always more than one way "to skin cat".
    Good health to you.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, January 29, 2010 4:07 PM
  • I  tested a GPS in a continuous mode using code similar to the following.  Set the ReceivedBytesThreshold as large as practical, but at least the size of a packet.  I would have posted more complete code to find the packets, but I couldn't determine how to find the checksum from the data you posted.

    using System;
    using System.IO;
    using System.IO.Ports;
    using System.Windows.Forms;
    namespace WindowsFormsApplication1
    {
      
    public partial class Form1 : Form
      {
        
    public delegate void InvokeReadData();
        
    private InvokeReadData ReadDataDelegate; 
        
    private CXTD02 CXT = new CXTD02();
        
    private bool ReadingData;
        
    private byte[] B;   
        
    public Form1()
        {
          InitializeComponent();
         serialPort1.DataReceived += 
    new SerialDataReceivedEventHandler(SerialPort1_DataReceived);
          ReadDataDelegate += 
    new InvokeReadData(ReadData);
          serialPort1.Open();
        }
        
    private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
        
    if (ReadingData) return;
        ReadingData = 
    true;
        
    this.BeginInvoke(this.ReadDataDelegate);
        }
        
    public void ReadData()
        {
        
    int Readbytes = serialPort1.BytesToRead;
        
    int BCnt = B.Length;
        
    Array.Resize(ref B, BCnt + Readbytes);
        serialPort1.Read(B, BCnt, Readbytes);
        B = CXT.Parse(B);
        ReadingData = 
    false;
        }
      }
      
    public class CXTD02
      {
        
    public byte[] Parse(byte[] B)
        {
        
    int StartCnt = 0; //Start of a packet
        do
        {
          
    //Find packets
        } while (true);
        
    //Save last partial packet
        int EndCnt = B.Length - StartCnt;
        
    Array.Copy(B, StartCnt, B, 0, EndCnt);
        
    Array.Resize(ref B, EndCnt);
        
    return B;
        }
      }
    }

    Wednesday, February 3, 2010 6:37 PM
  • Hey!
    John Wein, thanks for the code. I went through it. I suppose I'm not good at thinking about delegates. I can't seem to figure out how to use them. I have one class called ShiftRegisterBuffer that is buffering the incoming bytes from the SerialPort on DataReceived, receiving 8 bytes by keeping a rolling sum and calculating the checksum on the last position for every header byte that enters the first position. The ShiftRegisterBuffer validates packets - but what to do with them from there to get them to the other part of the code is the issue. I've followed Carsten's wise advice. I used MethodInvoke, BeginInvoke, and added these to the SerialPortaDataReceived. However, there's a 11-12 second delay! So, I'm trying to follow the time-saving methods he's sent recently. I printed out his tutorial and am giving it a more closer look. I also followed Rudy's advice, but I wasn't certain how to get the data from the ShiftRegisterBuffer class using the backgroundWorker to the ReportProgress method.
    Would you guys benefit from seeing the code? I code post it in a separate link so as not to rubbish up this thread? Thank you again for all of your assistance!
    Cheers,
    IsaacOIM
    Friday, February 5, 2010 7:55 PM
  • IsaacOIM

    11-12 seconds is madness. You must be doing something wrong or totally overload the system. What is the CPU load?

    Try to do it as simple as possible now. Forget that shift register class and all the other so-called object oriented methods, which just confuse things and waist a lot of execution time. Declare the 18-byte shift register buffer as a global byte array so that it is accessible from both the thread pool thread and the UI thread, put the shift register code right into the event handler for the DataReceived event (no class) and then call Invoke when you have a telegram. You can then use the byte array directly from the method on the UI thread.

    I can read C#, but unfortunately, I am not sufficiently sure about the syntax to write it, but in VB, a simple statement like this:

    Me.Invoke(New MethodInvoker(AddressOf YourUIRoutine))

    is enough to "wake" the routine on the UI thread, which shall process the received telegram. Then you simply take the global array, which now contain a telegram, and do whatever you need to do with it.

    Be sure to call Invoke within the receive loop of the event handler for the DataReceived event, so that if bytes have been received while the UI job was running, the event handler does not need to close down, but can continue right away. This also saves time.

    Have you read the chapter about delegates in the tutorial. It ought to explain everything, but if there is something you don't understand, just ask. It is quite simple. A delegate is just a way to make one or more subroutine calls by means of a table instead of a direct call.

    Show us the code for the event handler and the shift register.
     
    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, February 5, 2010 10:21 PM
  • Okay, if you download the following files...
    http://www.biblecia.com/projects/oim/DiagnosticInstrumentation.cs
    http://www.biblecia.com/projects/oim/DiagnosticInstrumentation.csproj
    http://www.biblecia.com/projects/oim/DiagnosticInstrumentation.Designer.cs
    Then, create a new Visual C# project, then right-click on Form1.cs and exclude from project, then right-click on Program.cs and exclude from project, then you can Build it using F5 and see the result.
    Monday, February 15, 2010 4:16 PM
  • Yes, after testing it again, there's no delay at the beginning, but the time delay progressively increase the longer the program is left to run. I did read the chapter on delegates, but I'm not so sure I implemented it correctly. I tried the MethodInvoker and it still gives me the same result... but I'm not so certain I've implemented it properly. (Please see my code as per the other posts.)
    Here's the DataReceived method.
    Cheers,
    Isaac D.
    OIM

            public void DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                while (serial.portLink.BytesToRead > 0)
                {
                    object result = buffer.Enqueue((byte)serial.portLink.ReadByte());
                    if(result != null)
                    {
                        this.BeginInvoke(new MethodInvoker(delegate
                        {
                            DoUpdateUI(result);
                        }));
                    }
                }
            }
    Monday, February 15, 2010 5:36 PM
  • It would be a lot easier for someone to look at your project is you zipped the entire solution (after cleaning) to skydrive.

    Monday, February 15, 2010 6:22 PM
  • Monday, February 15, 2010 8:50 PM
  • I hope you sell your Pacer.
    Monday, February 15, 2010 9:10 PM
  • Where is the delay?  How are you timing it?
    Monday, February 15, 2010 9:28 PM
  • IsaacOIM

    I am not a C# programmer, but your handler for the DataReceived event looks indeed very strange to me - especially when you try to transfer a result in a MethodInvoker delegate, which does not transfer anything at all! I do not have time to download and go through the rest of your code.

    We are living in a free world so it is of course up to you whether you want to do things the way I suggest, but since you obviously don't want that, I will leave you here.

    • You still use lots of unnecessary classes on top of each other and heavy system calls.
    • You still don't have any overrun protection although increasing delay is a clear sign of overload!
    • You still use BeginInvoke although you may overload both the message queue and the thread pool that way.
    • You still don't try to understand the system calls you are using. If there is something in the tutorial you don't understand, just ask, but I do believe that for example delegates is explained in a way that everybody should have a chance of understanding it - if they would  just spend the necessary time! There is a reason why 90% of the tutorial is basic knowledge because without this knowledge, forget about all .Net programming!

    If you should once change your opinion, just let me know and we can continue. As I have told you before, this case could have been solved in a few hours, but until you want to play it my way, it is a total waist of time to try to guide you any further.


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Tuesday, February 16, 2010 8:15 AM
  • Actually, I've been trying to think up a polled system all morning! I'm more than willing to do it the Kanstrup way, absolutely! You obviously know a lot more about serial communication than I do! I'm willing to do what it takes to construct a working program - what kind of code is behind it doesn't matter... in the end! I mean, it will be nice, but all people will see is the end result. I was just showing you what I had so far by posting the code, but I'm completely willing to start over, or tear apart what I had so far and create something new! After this morning's meeting I'm realizing my time is running short and I'm just trying to produce something, so if you're willing I would love to take just those few hours and get it done!
    • Edited by IsaacOIM Wednesday, February 17, 2010 3:21 PM
    Wednesday, February 17, 2010 2:30 PM
  • IsaacOIM

    Sorry, but it doesn't look that way. Your last event handler for the DataReceived event has absolutely none of my suggestions and you have not answered my questions and supplied the material I requested. For example, I asked you long time ago to show your shift register code and I asked you about the CPU load. Maybe the shift register code is included in one of your many links, but I simply do not have time to go through all that material.

    I also told you to make some logic to throw some telegrams away in case of overload. I don't see any such logic in your code. You just complain about the consequence of this lack of overload protection - increasing delay due to overload. I told you long time ago that 100 telegrams per second would push the system to the limit - especially with the way you program without the slightest thought of efficiency, but you didn't believe me. Do you do that now? Professional SCADA systems for industrial process control is able to handle approximately 300 telegrams per second so it ought to be possible to get your system to work, but you need a very efficient software structure.

    Here is the deal. If I should help you further, you answer ALL my questions and do EXACTLY what I tell you to do! Are you willing to do that? In that case, start by showing me that shift register code!

    PS. Because I am not a C# programmer, I will make all code in VB, but as I use to say: "Everybody, who choose to program in hieroglyphs instead of plain English, must expect that they need some translation :-)". Maybe Rudy is willing to help?


    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, February 17, 2010 3:08 PM
  • Yes sir:

    I'm willing to preserve just the telegrams necessary for a clean screen refresh, as we discussed.

    Here's the current code that I have for the Shift Register Buffer so far, first in C# (then, in Visual Basic using an online converter):


    C#:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace TherapeuticSeat
    {
        /// <summary> This class is the Shift Register Buffer.</summary>
        public partial class ShiftRegisterBuffer
        {
            int bufferSize = 0;                             // Number of places to put in buffer, determining the capacity <br/>					  // or # of open positions.
            private Queue<byte> Buffer = new Queue<byte>(); // Shift Register operates as a FIFO - First in, First Out - <br/>					  // collection
            private static int numOfBuffers = 0;            // Number of instances of this class.
            private const int char_zero = 0;                     // Character to use to fill empty positions in the buffer.
            private const int char_header = (byte)0xFF; // 0xFF or "255"
            public byte rollingSum = (byte)0x00; // Initial byte-value or "0".
            public byte potentialChecksumMSB = 0;
            public byte potentialChecksumLSB = 0;
            int calculatedChecksum = 0;
            public byte lastItem; // The item last enqueued into buffer.
            public Sensor.Modes MeasurementMode;
            //public delegate void NewMeasurementEventHandler(
            //    object equipment, DeviceInfoEventArgs deviceInfo);  // Delegate to handle new information obtained from the sensor.
            //public event NewMeasurementEventHandler NewMeasurement;
            //public event EventHandler<DeviceInfoEventArgs> NewMeasurementEvent;
    
            public ShiftRegisterBuffer(int CapacityOfBuffer)
            {
                bufferSize = CapacityOfBuffer;  // Sets the number of positions in the buffer for data.
                AllocateBytes(bufferSize); // Fill the buffer with empty 0's.
                numOfBuffers++; // Increment number of buffers in memory upon new instance.
                // This variable is static, so it will increase for each instance of CyclicBuffer.
            }
            public ShiftRegisterBuffer(int CapacityOfBuffer, Sensor.Modes Mode)
            {
                MeasurementMode = Mode;
                bufferSize = CapacityOfBuffer;  // Sets the number of positions in the buffer for data.
                AllocateBytes(bufferSize); // Fill the buffer with empty 0's.
                numOfBuffers++; // Increment number of buffers in memory upon new instance.
                // This variable is static, so it will increase for each instance of CyclicBuffer.
            }
    
            #region Properties
            // Read-only Properties
            public int Positions
            {
                get { return Buffer.Count; }    // Returns the number of elements in the LIFO Stack.
            }
            public int BuffersInMem
            {
                get { return numOfBuffers; }    // Retrieve the number of instantiated buffers running.
            }
            public int Capacity
            {
                get { return bufferSize; }      // Returns how many positions the buffer was originally set to hold.
            }
            public int Empty
            {
                get { return char_zero; }            // Like String.Empty, return the character for specifying empty positions.
            }
            public byte[] ToArray()
            {
                return Buffer.ToArray();
            }
            public bool IsFull
            {
                get
                {
                    if (bufferSize >= Buffer.Count)
                        return true;
                    else
                        return false;
                }
            }
            #endregion
    
            #region Shift Register Buffer Methodologies
            /// <summary>This function fills the buffer with empty zeros, positions for buffering data.</summary>
            /// <param name="numPositions">Number of positions to create within the buffer. It pre-fills the empty positions <br/>        /// with the </param>
            public void AllocateBytes(int numPositions)
            {
                // Collections implementation of Array.Clear(Array, indexer, length);
                for (int i = 0; i < numPositions; i++)
                {
                    Buffer.Enqueue(char_zero);
                }
            }
            /// <summary> Add a byte to the first position in the buffer while simultaneously shifting all positions 
            /// and deleting the last position in the Buffer.</summary>
            /// <param name="newItem">Represents a byte value in the packet.</param>
            public object Enqueue(byte newItem)
            {   // If the # of positions is greater than or equal to the Capacity/Size, delete a value from a position
                // to keep the same number of positions in the buffer.
                if (Buffer.Count >= Capacity)
                    Dequeue();
    
                // Now, add the new item to a position in the buffer variable.
                Buffer.Enqueue(newItem);
    
                rollingSum += newItem; // Add to rolling sum...
    
                //this.printBuffer();
    
                // If this is a header, determine if all packet positions represent a valid packet.
                if (Buffer.Peek() == char_header)
                {   // A header activates validation of current buffer contents for a valid packet.
                    potentialChecksumMSB = lastItem;    // Store the position in front of this one as the MSB & this new one as the LSB.
                    potentialChecksumLSB = newItem;     // If the first position is a header, then the current item represents the potential Checksum to be compared with the calculated checksum.
                    return this.Process();
                }
                this.lastItem = newItem; // Store the new item as the previous item for the next call of this method.
                return null;
            }
            /// <summary>Returns a reference to the byte at the beginning of the buffer.</summary>
            /// <returns>Return the first value in the buffer without deleting it from the buffer.</returns>
            public byte Peek()
            {
                return Buffer.Peek();
            }
            /// <summary>Removes and returns the byte at the beginning of the buffer.</summary>
            /// <returns>Returns the first byte in the buffer and then removes it from the buffer.</returns>
            public void Dequeue()
            {
                //byte oldItem = Buffer.Dequeue();
                //rollChecksum -= oldItem; // Subtract from checksum calculation.
                //return oldItem;
                rollingSum -= Buffer.Dequeue(); // Subtract the item being Dequeued from the rollingSum while removing it from the buffer.
            }
            /// <summary>Clears the buffer of its values and reallocates its empty positions with the given size.</summary>
            public void Clear()
            {
                Buffer.Clear();
                AllocateBytes(bufferSize);
            }
    
            public object Process()
            {
                // Initiate Packet Validation
                Console.WriteLine("Checksum Calculation Activated:::");
    
                MeasurementContainer NewMeasurement;
                bool validChecksum = false;
                byte packetSum = this.rollingSum;
    
                if (MeasurementMode == Sensor.Modes.ANGLE)
                {
                    packetSum -= (byte)0xFF; // Subtract 2 Headers
                    packetSum -= (byte)0xFF;
                    packetSum -= this.potentialChecksumMSB; // Subtract the potential checksum MSB, the second to last position, from the sum.
                    packetSum -= this.potentialChecksumLSB; // Subtract the potential checksum LSB, the last position, from the sum.
    
                    calculatedChecksum = packetSum % 0xFFFF;
    
                    if (calculatedChecksum == this.potentialChecksumLSB)
                    {
                        validChecksum = true;
                        NewMeasurement = new MeasurementContainer(this.ToArray());
                        return NewMeasurement;
                    }
                }
                if (validChecksum == true)
                {
                    Console.Write("{0}\t{1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
                    Console.Write("SUM()=={0}\t", packetSum, (byte)0xFF);
                    Console.WriteLine(BitConverter.ToString(this.ToArray()));
                }
                return null;
            }
            public short GetValue(byte MSB, byte LSB)
            {
                return (short)((MSB << 8) | (LSB & 0xFF));
            }
            #endregion
        }
    
        #region Measurement Data-Structure
        public class MeasurementContainer
        {
            public byte[] MeasurementArray;
            // Angle Measurement Mode
            public double PitchAngle;
            public double RollAngle;
            // Scaled Measurement Mode
            public short PitchRate;
            public short RollRate;
            public short YawRate;
            public short AccelX;
            public short AccelY;
            public short AccelZ;
            public short TempVoltage;
            public short InternalTime;
    
            public MeasurementContainer(byte[] MeasurementArray)
            {
                this.MeasurementArray = MeasurementArray;
            }
            public MeasurementContainer(double PitchAngle, double RollAngle)
            {
                this.PitchAngle = PitchAngle;
                this.RollAngle = RollAngle;
            }
            public MeasurementContainer(short PitchRate, short RollRate, short YawRate, short AccelX, short AccelY, short AccelZ, short TempVoltage, short InternalTime)
            {
                this.PitchRate = PitchRate;
                this.RollRate = RollRate;
                this.YawRate = YawRate;
                this.AccelX = AccelX;
                this.AccelY = AccelY;
                this.AccelZ = AccelZ;
                this.TempVoltage = TempVoltage;
                this.InternalTime = InternalTime;
            }
        }
        #endregion
    }
    
    Visual Basic:
    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    
    Namespace TherapeuticSeat
    	''' <summary> This class is the Shift Register Buffer.</summary>
    	Public Partial Class ShiftRegisterBuffer
    		Private bufferSize As Integer = 0
    		' Number of places to put in buffer, determining the capacity <br/>					  // or # of open positions.
    		Private Buffer As New Queue(Of Byte)()
    		' Shift Register operates as a FIFO - First in, First Out - <br/>					  // collection
    		Private Shared numOfBuffers As Integer = 0
    		' Number of instances of this class.
    		Private Const char_zero As Integer = 0
    		' Character to use to fill empty positions in the buffer.
    		Private Const char_header As Integer = DirectCast(&Hff, Byte)
    		' 0xFF or "255"
    		Public rollingSum As Byte = DirectCast(&H0, Byte)
    		' Initial byte-value or "0".
    		Public potentialChecksumMSB As Byte = 0
    		Public potentialChecksumLSB As Byte = 0
    		Private calculatedChecksum As Integer = 0
    		Public lastItem As Byte
    		' The item last enqueued into buffer.
    		Public MeasurementMode As Sensor.Modes
    		'public delegate void NewMeasurementEventHandler(
    		'    object equipment, DeviceInfoEventArgs deviceInfo);  // Delegate to handle new information obtained from the sensor.
    		'public event NewMeasurementEventHandler NewMeasurement;
    		'public event EventHandler<DeviceInfoEventArgs> NewMeasurementEvent;
    
    		Public Sub New(CapacityOfBuffer As Integer)
    			bufferSize = CapacityOfBuffer
    			' Sets the number of positions in the buffer for data.
    			AllocateBytes(bufferSize)
    			' Fill the buffer with empty 0's.
    				' Increment number of buffers in memory upon new instance.
    				' This variable is static, so it will increase for each instance of CyclicBuffer.
    			System.Math.Max(System.Threading.Interlocked.Increment(numOfBuffers),numOfBuffers - 1)
    		End Sub
    		Public Sub New(CapacityOfBuffer As Integer, Mode As Sensor.Modes)
    			MeasurementMode = Mode
    			bufferSize = CapacityOfBuffer
    			' Sets the number of positions in the buffer for data.
    			AllocateBytes(bufferSize)
    			' Fill the buffer with empty 0's.
    				' Increment number of buffers in memory upon new instance.
    				' This variable is static, so it will increase for each instance of CyclicBuffer.
    			System.Math.Max(System.Threading.Interlocked.Increment(numOfBuffers),numOfBuffers - 1)
    		End Sub
    
    		#region Properties
    		' Read-only Properties
    		Public ReadOnly Property Positions() As Integer
    			Get
    				Return Buffer.Count
    			End Get
    		End Property
    		' Returns the number of elements in the LIFO Stack.
    		Public ReadOnly Property BuffersInMem() As Integer
    			Get
    				Return numOfBuffers
    			End Get
    		End Property
    		' Retrieve the number of instantiated buffers running.
    		Public ReadOnly Property Capacity() As Integer
    			Get
    				Return bufferSize
    			End Get
    		End Property
    		' Returns how many positions the buffer was originally set to hold.
    		Public ReadOnly Property Empty() As Integer
    			Get
    				Return char_zero
    			End Get
    		End Property
    		' Like String.Empty, return the character for specifying empty positions.
    		Public Function ToArray() As Byte()
    			Return Buffer.ToArray()
    		End Function
    		Public ReadOnly Property IsFull() As Boolean
    			Get
    				If bufferSize >= Buffer.Count Then
    					Return True
    				Else
    					Return False
    				End If
    			End Get
    		End Property
    #End Region
    
    		#region Shift Register Buffer Methodologies
    		''' <summary>This function fills the buffer with empty zeros, positions for buffering data.</summary>
    		''' <param name="numPositions">Number of positions to create within the buffer. It pre-fills the empty positions <br/>        /// with the </param>
    		Public Sub AllocateBytes(numPositions As Integer)
    			' Collections implementation of Array.Clear(Array, indexer, length);
    			Dim i As Integer = 0
    			While i < numPositions
    				Buffer.Enqueue(char_zero)
    				System.Math.Max(System.Threading.Interlocked.Increment(i),i - 1)
    			End While
    		End Sub
    		''' <summary> Add a byte to the first position in the buffer while simultaneously shifting all positions 
    		''' and deleting the last position in the Buffer.</summary>
    		''' <param name="newItem">Represents a byte value in the packet.</param>
    		Public Function Enqueue(newItem As Byte) As Object
    			' If the # of positions is greater than or equal to the Capacity/Size, delete a value from a position
    			' to keep the same number of positions in the buffer.
    			If Buffer.Count >= Capacity Then
    				Dequeue()
    			End If
    
    			' Now, add the new item to a position in the buffer variable.
    			Buffer.Enqueue(newItem)
    
    			rollingSum += newItem
    			' Add to rolling sum...
    			'this.printBuffer();
    
    			' If this is a header, determine if all packet positions represent a valid packet.
    			If Buffer.Peek() = char_header Then
    				' A header activates validation of current buffer contents for a valid packet.
    				potentialChecksumMSB = lastItem
    				' Store the position in front of this one as the MSB & this new one as the LSB.
    				potentialChecksumLSB = newItem
    				' If the first position is a header, then the current item represents the potential Checksum to be compared with the calculated checksum.
    				Return Me.Process()
    			End If
    			Me.lastItem = newItem
    			' Store the new item as the previous item for the next call of this method.
    			Return Nothing
    		End Function
    		''' <summary>Returns a reference to the byte at the beginning of the buffer.</summary>
    		''' <returns>Return the first value in the buffer without deleting it from the buffer.</returns>
    		Public Function Peek() As Byte
    			Return Buffer.Peek()
    		End Function
    		''' <summary>Removes and returns the byte at the beginning of the buffer.</summary>
    		''' <returns>Returns the first byte in the buffer and then removes it from the buffer.</returns>
    		Public Sub Dequeue()
    			'byte oldItem = Buffer.Dequeue();
    			'rollChecksum -= oldItem; // Subtract from checksum calculation.
    			'return oldItem;
    			rollingSum -= Buffer.Dequeue()
    			' Subtract the item being Dequeued from the rollingSum while removing it from the buffer.
    		End Sub
    		''' <summary>Clears the buffer of its values and reallocates its empty positions with the given size.</summary>
    		Public Sub Clear()
    			Buffer.Clear()
    			AllocateBytes(bufferSize)
    		End Sub
    
    		Public Function Process() As Object
    			' Initiate Packet Validation
    			Console.WriteLine("Checksum Calculation Activated:::")
    
    			Dim NewMeasurement As MeasurementContainer
    			Dim validChecksum As Boolean = False
    			Dim packetSum As Byte = Me.rollingSum
    
    			If MeasurementMode = Sensor.Modes.ANGLE Then
    				packetSum -= DirectCast(&Hff, Byte)
    				' Subtract 2 Headers
    				packetSum -= DirectCast(&Hff, Byte)
    				packetSum -= Me.potentialChecksumMSB
    				' Subtract the potential checksum MSB, the second to last position, from the sum.
    				packetSum -= Me.potentialChecksumLSB
    				' Subtract the potential checksum LSB, the last position, from the sum.
    				calculatedChecksum = packetSum Mod &Hffff
    
    				If calculatedChecksum = Me.potentialChecksumLSB Then
    					validChecksum = True
    					NewMeasurement = New MeasurementContainer(Me.ToArray())
    					Return NewMeasurement
    				End If
    			End If
    			If validChecksum = True Then
    				Console.Write("{0}" & vbTab & "{1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString())
    				Console.Write("SUM()=={0}" & vbTab, packetSum, DirectCast(&Hff, Byte))
    				Console.WriteLine(BitConverter.ToString(Me.ToArray()))
    			End If
    			Return Nothing
    		End Function
    		Public Function GetValue(MSB As Byte, LSB As Byte) As Short
    			Return DirectCast(((MSB << 8) Or (LSB And &Hff)), Short)
    		End Function
    #End Region
    	End Class
    
    	#region Measurement Data-Structure
    	Public Class MeasurementContainer
    		Public MeasurementArray As Byte()
    		' Angle Measurement Mode
    		Public PitchAngle As Double
    		Public RollAngle As Double
    		' Scaled Measurement Mode
    		Public PitchRate As Short
    		Public RollRate As Short
    		Public YawRate As Short
    		Public AccelX As Short
    		Public AccelY As Short
    		Public AccelZ As Short
    		Public TempVoltage As Short
    		Public InternalTime As Short
    
    		Public Sub New(MeasurementArray As Byte())
    			Me.MeasurementArray = MeasurementArray
    		End Sub
    		Public Sub New(PitchAngle As Double, RollAngle As Double)
    			Me.PitchAngle = PitchAngle
    			Me.RollAngle = RollAngle
    		End Sub
    		Public Sub New(PitchRate As Short, RollRate As Short, YawRate As Short, AccelX As Short, AccelY As Short, AccelZ As Short, _
    			TempVoltage As Short, InternalTime As Short)
    			Me.PitchRate = PitchRate
    			Me.RollRate = RollRate
    			Me.YawRate = YawRate
    			Me.AccelX = AccelX
    			Me.AccelY = AccelY
    			Me.AccelZ = AccelZ
    			Me.TempVoltage = TempVoltage
    			Me.InternalTime = InternalTime
    		End Sub
    	End Class
    #End Region
    End Namespace
    
    '=======================================================
    'Service provided by Telerik (www.telerik.com)
    'Conversion powered by NRefactory.
    'Built and maintained by Todd Anglin and Telerik
    '=======================================================
    
    Wednesday, February 17, 2010 3:33 PM
  • Carsten,
    I posted an executable for you to look at the full program, including a Gauge control that displays the CPU and Memory usage.
    http://www.tiny9.com/u/scope
    Cheers,
    Isaac Dodd
    Wednesday, February 17, 2010 3:42 PM
  • Hmm... I'm sorry, Karsten, I really did think that the DataReceived evanthandler method I posted previously followed your suggestion. I may not have understood. I'll try it again.
    Wednesday, February 17, 2010 3:44 PM
  • That's the most complicated 18 move instructions I have ever seen :-)

    You really need an efficient software structure where things are defined in layers. I can recommend the 7-layer OSI model, which I use myself for communication applications. The eventhandler for the DataReceived event takes care of Layer 2 and 4 in the OSI model, that is, the data link layer and the transport layer, which deal with the telegram frame and error detection. Data interpretation like your MeasurementContainer belongs to layer 6, which will be handled on the UI thread. It is very important to keep thing separated if you should have a chance of changing the code or the protocol later on.

    Once I get a little more time to study your code in details, I will turn back with a highly simplified shift register put directly into the event handler for the DataReceived event - directly in the BytesToRead loop. There is absolutely no reason for making a class out of the shift register. You just waist time and loose the overview. It is just a matter of shifting the received bytes through an 18-byte array, do some rolling checksum calculation and then invoke a method on the UI thread, which can interpret the telegram and do the graphics, when a telegram is ready in the 18-byte array. As simple as that. Read my signature.



    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Wednesday, February 17, 2010 4:03 PM
  • Yes, I did read your signature about as many times as telegrams per second. :-P But, I read it again. That sounds great! I didn't know it was that simple
    Wednesday, February 17, 2010 4:43 PM
  • IsaacOIM

    I have just uploaded and tested a new version of my sample program from the tutorial . The new version is build exactly as I suggest for your application!

    I am extremely busy right now, but what I plan to do is:

    1) Make a global 18-byte array (RXArray) for the shift register. Not a general purpose class with variable length. It is highly overkill. You only need that shift register because of a stupid sensor protocol, which you cannot change, so you will never need a general class again and the number of bytes in the telegram is fixed. Just change the size of the array of the sample program from 2047 to 17 (18 members) and make it a Byte array instead of Char!

    2) Put the shift register code right between the protocol lines (see download) like this:

    RollingSum = RollingSum - RXArray(17)
    RXArray(17) = RXArray(16)
    RXArray(16) = RXArray(15)
    ...
    RXArray(1) = RXArray(0)
    RXArray(0) = COMPort.ReadByte
    RollingSum = RollingSum + RXArray(0)
    If (RXArray(17) = 0xFF) And (RXArray(0) = RollingSum) Then
     ' Telegram found. Do the invokation

    Can you see how simple it really is? 21 instructions compared to your massive classes. It may not be the most advanced way to make the shift register, but since it can utilize the pipeline in the CPU, it executes much faster than any "advanced" code and it is very easy to understand. Maybe the shown checksum calculation is not correct, but it shows the principle. Since the checksum is only one byte wide, you can probably just use byte arithmetic and forget the overflow.

    3) Replace the last "Loop Until COMPort.BytesToRead = 0" with something more intelligent, which is able to throw telegrams away in case of an overload. If there for example is 50 bytes to read when you return from the Invoke statement, just throw 36 bytes away (2 telegrams) and wait until you have received the remaining 4 bytes of the telegram, which is being received right now. If there are only 36 bytes, throw 1 telegram away. You can probably use "\ 18" (note not / 18) to get the remainer and calculate how many bytes you should throw away.

    You actually ought to be able to do this yourself. It may take some time to find the optimized way to handle an overload condition and make the most efficient loop structure, but either you do it or I, and I may not have time for many days.

    PS. You don't need to convert your code to VB. I can read C# and I rather prefer C# that a very bad VB conversion as in your case. I actually used the C# version of your code. I am just not fluent enough in C# to write correct code - especially in case of invokation where the syntax is rather different from VB.
    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Friday, February 19, 2010 1:27 PM
  • Question for you Carsten:
    Why do you in your code use the 'Do... Loop Until (COMPort.BytesToRead = 0)' command twice?

    Isaac D.
    OIM
    Monday, March 15, 2010 4:34 PM
  • To save time in case bytes have been received while the display is being updated.

    SerialPort may receive bytes (interrupt based) while you are in the Invoke call. Therefore, I check if this is the case when I return from the Invoke call. If bytes have been received in the meantime, I collect a new telegram right away (stay in the event handler) instead of first terminating the event handler and then enter it again immediately after due to a new DataReceived event (if bytes have been received).

    At 100 telegrams per second or more, you need to cut every corner you can to save time.
     
    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Monday, March 15, 2010 9:43 PM
  • "At 100 telegrams per second or more, you need to cut every corner you can to save time."

    Why?  There is nothing slower on a modern CPU than the serial port.  Wait for a meaningful return, process it and wait again.  You probably don't need to process the telegrams as they arrive, particularly if there is no response.  Process control using a Windows computer without a microcontroller doesn't make sense to me.
    Monday, March 15, 2010 10:28 PM
  • "Process control using a Windows computer without a microcontroller doesn't make sense to me."

    What about soft-PLC's? A SCADA system, which constantly has to poll tags in PLC's is to my opinion the most stupid way to do automation (I have done automation for a living since 1977).

    Yes, there is something, which is slower than SerialPort - inefficient software! When it comes to real time performance, Windows is dead slow - not the hardware. Our new fieldbus Max-i is able to supply up to approximately 10,000 telegram per second through the serialPort (using a 16C950 UART) - way above what Windows can handle.

    Everything should be made as simple as possible, but not simpler. Any fool can write code that a computer can understand. Good programmers write code that humans understand.
    Monday, March 15, 2010 10:53 PM