Go Down

Topic: Need faster serial transfer - Arduino and C# (Read 3482 times) previous topic - next topic

amatic

Hello,
I'm new to arduino and serial protocol. I've made a program in C# to communicate with arduino - reading pot values and sending commands for motor movements. My problem is that I would need faster serial transfer. It's averaging about 15 ms  per send-receive cycle. Only 20 bytes are sent and then 20 bytes received. Is this normal or can it go faster?


el_supremo

What speed have you set the Serial device? It looks like it might be 19200 bps, in which case it can go a lot faster.

Pete

amatic

#2
Apr 24, 2012, 01:54 am Last Edit: Apr 24, 2012, 01:58 am by amatic Reason: 1
The baud rate is 28800. I've tried raising it, but i haven't seen much, if any, improvement.

I'm sending ASCII data. Would it make much difference to change it to bytes?

Here's some code to see what I'm doing:

Computer output message is formated as "XDPPPDPPPDPPPDPPP\n"
where X is start signal, D is 0,1,2 indicating direction of motor movement and PPP is a pwm signal value for a motor.
Code: [Select]

if (Serial.available() >= 18)
  {
     c = Serial.read();
     if (c=='X')
     {
        for (int i=0; i<4; i++)
        {
            Direction[i] = Serial.read();
            num[0]=Serial.read();
            num[1]=Serial.read();
            num[2]=Serial.read();

            Speed[i] = atoi(num);
         }
         c=Serial.read();

       SetPins();
       PrintPots();
   }
 
void PrintPots()
{
  Serial.print ("X");
  for (int i = 0; i<5;i++)
  {
    PrintFormated(pot[i]);
    if (i<4) Serial.print(" ");
  }
  Serial.print ('\n');
}

void PrintFormated (int x)
{
  if (x < 100) Serial.print ('0');
  if (x < 10) Serial.print ('0');
  Serial.print (x);
}



Arduino output messages are formated as "X### ### ### ### ###\n"

This is C# code:

Code: [Select]


serialPort1.BaudRate = 28800;
serialPort1.PortName = "COM5";
serialPort1.ReceivedBytesThreshold = 21;

//..snip            

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
       {

           if (ClosePort)
           {
               serialPort1.WriteLine(NullSignal);
               serialPort1.Close();
           }
           else
           {
               this.BeginInvoke(new EventHandler(ReadPots));
           }
       
       }

       private void ReadPots(object sender, EventArgs e)
       {
           
           SerialInput = "";
           try
           {
               SerialInput = serialPort1.ReadLine();
           }
           catch
           {
               label31.Text = "READ FAILED";
               return;
           }
           DataReceived = true;

           label1.Text = SerialInput;

          if (SerialInput[0] == 'X') // All messages are formated as "X### ### ### ### ###\n"
           {
               String[] values = SerialInput.Split('X', ' ', '\n');
               for (int i = 0; i < 4; i++)
               {
                   Angle[i].Position = Convert.ToDouble(values[i + 1]);
               }

       

el_supremo

Quote
Would it make much difference to change it to bytes?

A bit, but it would be much easier to just change the baud rate to 56700 or higher. If that isn't helping then perhaps you are limited by how fast the motor can respond to the commands.

Pete

el_supremo

Code: [Select]
             num[0]=Serial.read();
             num[1]=Serial.read();
             num[2]=Serial.read();


I hope you have declared num to have at least 4 elements and that you've set num[3] to zero. Otherwise you could be getting strange results for the PWM values.

Pete

Osgeld

the speed reported by various users can get near 1Mbs over serial so you have lots of room
http://arduino.cc/forum/index.php?action=unread;boards=2,3,4,5,67,6,7,8,9,10,11,66,12,13,15,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,86,87,89,1;ALL

robtillaart


Have had excellent results with 345600 baud sending to putty.exe (lots of raw data ~8K analog samples/second) 

what is the highes number you need to send?
if the range is between 0..255 a byte would be sufficient and you could write

XDBDBDBDB\n

that is ~50% less communication!

furthermore you can do the conversion to integer yourself directly...
Code: [Select]

if (Serial.available() >= 18)
   {
      c = Serial.read();
      if (c=='X')
      {
         for (int i=0; i<4; i++)
         {
             Direction[i] = Serial.read();
             speed[i] = Serial.read() - '\0';
             speed[i] = speed[i] * 10 + Serial.read() - '\0';
             speed[i] = speed[i] * 10 + Serial.read() - '\0';
          }
          c=Serial.read();

        SetPins();
        PrintPots();
    }


I see 2 loops in your code : for (int i = 0; i<5;i++)   and  for (int i=0; i<4; i++)  there is a difference, is it intentional?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

amatic

Thank you for your replies.

@elsupremo
yes, the num is 4 chars long (I set it to 4 only after having the error you described :)
I've set the baud rate to 57600. That part works good. Even the 115200 works and I haven't seen any errors.

@robtillaart
Output messages from arduino are analog readings (0-1023), so two bytes are needed. And you're right, pwm values can be placed in only byte. I can even cram the direction values to one byte, so plus 4 pwm values, that's only 5 bytes.

The two loops you mention are intentionaly different. The reading loop has 4 chunks of data to read - motor direction and speed. The printing loop has 5 chunks from analog pins - four are potentiometer readings, and one is a thermistor reading.

----
More questions:
1. Would it be advisable not to use the start and stop bytes, just raw messages?


2. Does anyone know of a way to speed up C# serial communication? The fastes I can get it to run using DataReceived event is 15 ms.
I'm googleing as we speak, but nothing seems to be working.

by the way, I've found an elegant way of converting integers to bytes:
Code: [Select]

byte output [12];
// snip

for (int i=0;i<5;i++)
    {
      int x=analogRead(i);
      output[1+2*i] = x & 0xFF;
      output[2+2*i] = (x >> 8) & 0xFF;
    }
    Serial.write(output,12);


On the C# side, the code is:
Code: [Select]

           byte [] SI = new byte[12];
// formated as '0aabbccddee0'
// snip

            serialPort1.Read(SI, 0, 12);
            for (int i = 0; i < 4; i++)
            {
                Angle[i].Position = SI[i * 2 + 1] | (SI[i * 2 + 2] << 8);
            }

            temperature = SI[10] | (SI[11] << 8);
            DataReceived = true;
           

Currently I'm having trouble

amatic

I'm thinking that the biggest problem is on the C# side and the separate serial threads. My knowledge on threads is low.

Here's the code:
Code: [Select]

        public MainForm()
        {
            InitializeComponent();

            serialPort1.BaudRate = 57600;
            serialPort1.PortName = "COM5";
            serialPort1.ReceivedBytesThreshold = 12;
           
            stopwatch.Start();
            Application.Idle += new EventHandler(MainLoop);
        }



private void MainLoop(object sender, EventArgs e)
          {
                  if (DataReceived)
                  {
                      label31.Text = stopwatch.ElapsedMilliseconds.ToString();
                      DataReceived = false;
                      stopwatch.Reset();
                      stopwatch.Start();
                    }
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
                serialPort1.Read(SI, 0, 12);
                this.Invoke(new EventHandler(ReadPots));
}
private void ReadPots(object sender, EventArgs e)
{
      for (int i = 0; i < 4; i++)
      {
                Angle[i].Position = SI[i * 2 + 1] | (SI[i * 2 + 2] << 8);
       }

       temperature = SI[10] | (SI[11] << 8);
       DataReceived = true;
}

PaulS

Quote
I'm thinking that the biggest problem is on the C# side and the separate serial threads.

No. The problem is with you creating a new thread for each read. My code looks like:
Code: [Select]
public static System.IO.Ports.SerialPort port;
delegate void SetTextCallback(string text);

// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker hardWorker;

private Thread readThread = null;

public Form1()
{
InitializeComponent();

hardWorker = new BackgroundWorker();
sendBtn.Enabled = false;
}

private void btnConnect_Click(object sender, EventArgs e)
{
System.ComponentModel.IContainer components =
new System.ComponentModel.Container();
port = new System.IO.Ports.SerialPort(components);
port.PortName = comPort.SelectedItem.ToString();
port.BaudRate = Int32.Parse(baudRate.SelectedItem.ToString());
port.DtrEnable = true;
port.ReadTimeout = 5000;
port.WriteTimeout = 500;
port.Open();

readThread = new Thread(new ThreadStart(this.Read));
readThread.Start();
this.hardWorker.RunWorkerAsync();

There is ONE thread to read serial data. When data arrives, the Read() method is called:
Code: [Select]
public void Read()
{
while (port.IsOpen)
{
try
{
if (port.BytesToRead > 0)
{
string message = port.ReadLine();
this.SetText(message);
}
}
catch (TimeoutException) { }
}
}


The SetText() method then determines what to do with the data:
Code: [Select]
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.receiveText.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
                this.receiveText.Text += "Text: ";
                this.receiveText.Text += text;
                this.receiveText.Text += Environment.NewLine;
            }
}

It either Invokes a method on another thread (the UI thread) if called by the serial thread, or, if called by the UI thread, does something with the text.

Of course, your thread method can have a different name, and do something different with the serial data, but you should not be creating new threads/event handlers each time data arrives on the serial port.

amatic

@PaulS
Wonderfull!
I've combined your advice with what I have now, works like a charm! I've managed to get it down to 7ms per cycle, but I'll be using 10ms because of some calculations that need to be done.
Thank you very much, the bit about not calling new threads or event handlers for every serial data receive helped the most!

So, now I have a gui update thread and a serial read thread.

Here's the working code, the important parts, if anyone else needs it. Questions (or suggestions) are wellcome:
The gui update thread:
Code: [Select]

public MainForm()
{   
InitializeComponent():
         Application.Idle += new EventHandler(MainLoop);
}

private void MainLoop(object sender, EventArgs e)
          {
              while (AppStillIdle)
              {
                  while (stopwatch.ElapsedMilliseconds < 10)
                  {
                      ;
                  }
                  label31.Text = Angle[0].Position.ToString() + " " + Angle[1].Position.ToString() + " " +
                              Angle[2].Position.ToString() + " " + Angle[3].Position.ToString() + " " +
                              temperature.ToString();
                      if (serialPort1.IsOpen)
                      {
                          serialPort1.Write("WWWWW"); // just a test signal
                      }                 
              }
private bool AppStillIdle
          {
              get
              {
                  NativeMethods.Message msg;
                  return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
              }
          }
   
// snip

class NativeMethods
    {
        /// MainLoop helper class, found it here:
        /// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx

        [StructLayout(LayoutKind.Sequential)]
        public struct Message
        {
            public IntPtr hWnd;
            public UInt32 msg;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public System.Drawing.Point p;
        }
        [System.Security.SuppressUnmanagedCodeSecurity]

        [DllImport("User32.dll", CharSet = CharSet.Auto)]

        public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
    }


the serial reader thread
Code: [Select]

private Thread readThread = null;

private void StartSerialButton_Click(object sender, EventArgs e)
        {
                try
                {
                    serialPort1.Open();
                    StartSerialButton.Enabled = false;
                    stopwatch.Start();
                    first = true;
           
                    readThread = new Thread(new ThreadStart(this.Read));
    readThread.Start();
            serialPort1.Write("WWWWW");
                     

                }
                catch
                {
                    System.Windows.Forms.MessageBox.Show("Could not opet COM5", "Error!");
                }
           
        }   

   
       
public void Read()
        {
            while (serialPort1.IsOpen)
            {
                if (serialPort1.BytesToRead >= 12)
                    {
                        byte[] SI = new byte[12];
                        serialPort1.Read(SI, 0, 12);
                        this.ReadValues(SI);
                    }
            }
        }

private void ReadValues(byte [] SI)
{
  for (int i = 0; i < 4; i++)
   {
        Angle[i].Position = SI[i * 2 + 1] | (SI[i * 2 + 2] << 8);
    }
   temperature = SI[9] | (SI[10] << 8);
}

amatic


btw. Is there an 'edit post' in this forum?
a little correction:
Code: [Select]

while (AppStillIdle)
  {
                  while (stopwatch.ElapsedMilliseconds < 5)
                  {
                      ;
                  }
                  label31.Text = Angle[0].Position.ToString() + " " + Angle[1].Position.ToString() + " " +
                              Angle[2].Position.ToString() + " " + Angle[3].Position.ToString() + " " +
                              temperature.ToString();
                  label1.Text = stopwatch.ElapsedMilliseconds.ToString("D3");
                  if (serialPort1.IsOpen)
                  {
                          label32.Text = serialPort1.BytesToRead.ToString();
                          serialPort1.Write("WWWWW");
                  }
                  stopwatch.Reset();
                  stopwatch.Start();
}

el_supremo

Quote
Is there an 'edit post' in this forum?

Yes. At the top right of the post there's an icon labelled Modify.

Pete

Go Up