C# GUI reading serial from Arduino?

I've been trying to get the Arduino to communicate with C# GUI that I made for a couple days, I managed to get *MOST of it working by myself... but ran into a wall and cant seem to figure out a way pass this problem.

The project i'm working on is to use the PING))) Parallax Ultrasonic sensor to read the distance, then send it back to the arduino, the arduino converts it into cm, then send the signal to C# using serial.

I've gotten everything working to the point of C#. I got the Arduino to send the serial signal (distance in cm) to C# and C# reading it and saving it into a result (double).

The problem.... I cant seem to get C# to loop this reading and continue to read... if I put a While loop into it, the GUI wont load up at all.
If I put the code into the GUI_Load event handler, then it'll do the reading once, then that's it.

What do I need to do to make it read continuously? Or at least... once a second to "detect" a signal change.

The Arduino is sending a distance reading every half a second.

I'll try to figure out how to upload my codes up. It's been very interesting to get working... but I'm stumped... any help or finger in the right way would be very appreciated.

you could try putting Application.DoEvents(); in your while loop, after the arduino stuff.

if that doesn't work you could use a timer object and put the arduino call in it's "tick" event.

both of these approaches should free up the GUI to update on a separate thread.

Isn't there some kind of "OnSerialReady" event handler in C# that you can put your code in?

Andrew

You can use the serialPort.DataReceived event.

But be prepared for some threading issues.

The serialPort.DataReceived event is running in another thread than the GUI, so you can not directly update the GUI from that event.

You need to implement a delegate to do that.

try to google "c# serialport thread delegate" or something like that.

int pingPin = 7;
void setup()
{ 
Serial.begin(9600);
Serial.print("?Bff");
delay (100);
Serial.print("?G420"); //set screen to be 4x20
delay (100);
}
void loop()
{
//Serial.print("?f");  //clear screen
long duration, cm;
// triggers the ping sensor with the sequence
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  
// Set pinPing to read the time it takes to get a HIGH back
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);

//Convert to centimeters using the method
cm = microsecondsToCentimeters(duration);

//Prints the RESULT! WOO!!!
Serial.print (cm);
Serial.println (" cm");
delay (500);
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

That's my arduino's code....... very simple... when I do serial monitor with The Arduino software, I get a three or two digit number then a space, then a cm.

My code for C#.... this version only run ONCE the reading, but it work. I tried to put while loop in it, but it wont work at all, wont load up the Gui... so I figure this is probably the first step.
The objective is to "display" the correct picture correlating to the amount of water in the pitcher depending on the distance from the water to the U/S sensor. Sorry if my code is a litte messy.

namespace WaterPitcher
{
    public partial class WaterMeterGUI : Form
    {

        public SerialPort com1 = new SerialPort
            ("COM16", 9600, Parity.None, 8, StopBits.One);


        public WaterMeterGUI()
        {
            InitializeComponent();
        }
        

        private void WaterMeterGUI_Load(object sender, EventArgs e)
        {
            try
            {
                com1.PortName = "COM16";
                com1.BaudRate = 9600;
                com1.Parity = Parity.None;
                com1.DataBits = 8;
                com1.StopBits = StopBits.One;
                com1.Handshake = Handshake.None;
                com1.ReadTimeout = 1000;
                com1.WriteTimeout = 500;
                com1.Open();
      
                com1.DiscardInBuffer();
            string READ = "";
            READ = com1.ReadLine();
            string[] Split = READ.Split(new Char[] { ' ' });
            //SHOW RESULT
            double result = Convert.ToDouble(Convert.ToString(Split[0]));
            lblresult.Text = Convert.ToString(result);
            if (result < 3)
            {
                SetVis();
                this.pic16.Visible = true;
            }
            else if (result >= 3 && result < 5)
            {
                SetVis();
                this.pic15.Visible = true;
            }
            else if (result >= 5 && result < 7)
            {
                SetVis();
                this.pic14.Visible = true;
            }
            else if (result >= 7 && result < 9)
            {
                SetVis();
                this.pic13.Visible = true;
            }
            else if (result >= 9 && result < 11)
            {
                SetVis();
                this.pic12.Visible = true;
            }
            else if (result >= 11 && result < 13)
            {
                SetVis();
                this.pic11.Visible = true;
            }
            else if (result >= 13 && result < 15)
            {
                SetVis();
                this.pic10.Visible = true;
            }
            else if (result >= 15 && result < 17)
            {
                SetVis();
                this.pic9.Visible = true;
            }
            else if (result >= 17 && result < 19)
            {
                SetVis();
                this.pic8.Visible = true;
            }
            else if (result >= 17 && result < 19)
            {
                SetVis();
                this.pic7.Visible = true;
            }
            else if (result >= 19 && result < 21)
            {
                SetVis();
                this.pic6.Visible = true;
            }
            else if (result >= 21 && result < 23)
            {
                SetVis();
                this.pic5.Visible = true;
            }
            else if (result >= 23 && result < 25)
            {
                SetVis();
                this.pic4.Visible = true;
            }
            else if (result >= 25 && result < 27)
            {
                SetVis();
                this.pic3.Visible = true;
            }
            else if (result >= 27 && result < 29)
            {
                SetVis();
                this.pic2.Visible = true;
            }
            else if (result >= 29 && result < 31)
            {
                SetVis();
                this.pic1.Visible = true;
            }
            }

            catch (Exception Ex)
            {
                MessageBox.Show(Ex.Message.ToString(), "ERROR");
            }
         

        }


        void SetVis()
        {
            this.picempty.Visible = false;
            this.pic16.Visible = false;
            this.pic15.Visible = false;
            this.pic14.Visible = false;
            this.pic13.Visible = false;
            this.pic12.Visible = false;
            this.pic11.Visible = false;
            this.pic10.Visible = false;
            this.pic9.Visible = false;
            this.pic8.Visible = false;
            this.pic7.Visible = false;
            this.pic6.Visible = false;
            this.pic5.Visible = false;
            this.pic4.Visible = false;
            this.pic3.Visible = false;
            this.pic2.Visible = false;
            this.pic1.Visible = false;
        }
    }
}

Subscribing to the serial event and then invoking a delegate is definitely the most elegant and "correct" way to do this. but conceptually it's a bit of a hurdle for a beginner.

what I'd suggest is:

  • on the design view, drag a timer out onto the form.
  • rightclick on it and select "properties"
  • set the "enabled" property to "true"
  • set the "interval" property to however often (in milliseconds) you want the program to check the serial port
  • next doubleclick on the timer. that will automatically create a function in your code called "timername_tick(...)" which will get excecuted every time the timer's interval is up.
  • put the code you want executed over and over in that function (leave the com1 initialization code where it is though)

You must user Serial port component events, not directly the comN.read

        private void serialPortCom_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
         {
             int bytesToRead = serialPortCom.BytesToRead;
             char[] z = new char[bytesToRead];
             serialPortCom.Read(z, 0, bytesToRead);
             
             foreach(char s in z)
                 WriteLog(String.Format(Properties.Resources.Log_Received,s,(int)s));
         }

http://code.google.com/p/ciclopeee/source/browse/branches/ciclopeee_dotnet/PruebaArduino/FormMain.cs