C# - RS232 ve Timer Sorunu

Başlatan Bahadır AYDINOĞLU, 02 Haziran 2016, 16:33:02

Bahadır AYDINOĞLU

Merhaba arkadaşlar,
Bir seri port haberleşmeli terazim var. Putty ile veri alabiliyorum. C# ile Arduino için daha önceden yapmış olduğum bir arayüzüm vardı ordan da veri okuyabiliyorum. Fakat çok sağlıklı olmadığı için bir kaç düzenleme yaptım ama çözemediğim ve önerilerinizi beklediğim bir kaç sorun ortaya çıktı.

1-) Data'yı timer ile alıyorum. Terazi 9600 baudda çok hızlı gönderiyor ölçtüğü ağırlığı.
Bu yüzden daha önceden,
sonuc = serialPort1.ReadExisting();

olan komutu
sonuc = serialPort1.ReadLine();

olarak çevirdim.

Timer Interval süresini 200ms ve daha yukarısı yaptığımda terazi verileri düzgün gelmiyor, geç kalıyor sanki kasıyormuş gibi.
Süreyi 5-75 ms civarı yaptığımda da veri tam istediğim gibi geliyor fakat bu sefer de arayüzdeki hiçbir butonu kullanamıyorum, "bağlantıyı kopar" butonum var basıyorum işlemiyor, 20-30 sn sonra işliyor.

2-) Terazinin gönderdiği data
"ST,GS, 15.6 g" tarzında geliyor. Sadece sayıyı nasıl çekebilirim. Split modunu tam uygulayamadım. Yardım ederseniz çok sevinirim. Kodlar aşağıda.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;

namespace Data_Okuma
{
    public partial class Form1 : Form
    {
        string[] ports = SerialPort.GetPortNames();
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {   
            foreach (string port in ports)
            {
                comboBox1.Items.Add(port);
                comboBox1.SelectedIndex = 0;
            }            
            comboBox2.Items.Add("2400");
            comboBox2.Items.Add("4800");
            comboBox2.Items.Add("9600");
            comboBox2.Items.Add("19200");
            comboBox2.Items.Add("115200");
            comboBox2.SelectedIndex = 2;
            //AÇILIŞTA BAĞLANTININ KAPALI OLDUĞUNU BELİRT.
            label3.Text = "Bağlantı Kapalı";    }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            {
            // Form kapandığında SerialPort1 portu kapat.
            if (serialPort1.IsOpen == true)
            {
                serialPort1.Close();
            }
        }
        }
        string sonuc;
       
        private void yaz(object o, EventArgs e)
        {
            label1.Text = sonuc + ""; ;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer1.Start();
            if (serialPort1.IsOpen == false)
            {
                if (comboBox1.Text == "")
                    return;
                serialPort1.PortName = comboBox1.Text;
                serialPort1.BaudRate = Convert.ToInt16(comboBox2.Text);
                try
                {
                    serialPort1.Open();
                    label3.ForeColor = Color.Green;
                    label3.Text = "Bağlantı Açık";


                }
                catch (Exception hata)
                {
                    MessageBox.Show("Hata:" + hata.Message);
                }
            }
            else
            {
                label3.Text = "Bağlantı kurulu !!!";
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            //BAĞLANTIYI KES BUTONU
            timer1.Stop();
            if (serialPort1.IsOpen == true)
            {
                serialPort1.Close();
                label3.ForeColor = Color.Red;
                label3.Text = "Bağlantı Kapalı";
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            listBox1.Items.Add(label1.Text);
        }

        private void button4_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {

            sonuc = serialPort1.ReadExisting();
            this.Invoke(new EventHandler(yaz));
        }     

        
                       

    }
        }
 


muhittin_kaplan

seri bilgiyi timer la almak hata. Thread olusturmaniz lazim. Ve datareceive olayiyla veriyi almaniz gerekir.

LG-D723 cihazımdan Tapatalk kullanılarak gönderildi


robomaster

Alıntı yapılan: muhittin_kaplan - 04 Haziran 2016, 11:59:50
seri bilgiyi timer la almak hata. Thread olusturmaniz lazim. Ve datareceive olayiyla veriyi almaniz gerekir.

LG-D723 cihazımdan Tapatalk kullanılarak gönderildi



Eventhandler tanimlamasi lazim.


Hadi


olemiss

#5
Birinci kısım:

Projenizi kendi sistemimde çalıştırdım.  Mesajınızda sorun ile Readline() metodu kullanınca karşılaştığınızı, programın ReadExisting() metodu kullanırken sorunsuz çalıştığını söylüyorsunuz.  Ben de aynı davranışı gördüm. 

Bunun nedeni, ReadLine() metodunun veri gelene kadar arayüzü bloke ediyor olması.

Şu şekilde çözdüm:

        private void timer1_Tick(object sender, EventArgs e)
        {
            serialPort1.ReadTimeout = 10;
            try
            {
                sonuc = serialPort1.ReadLine();
            }
            catch { }

            this.Invoke(new EventHandler(yaz));
        }


Yani yine ReadLine() kullanıyorum fakat bloke etmesini önlemek için bir timeout değeri belirliyorum.  ReadLine() metodunun MSDN'deki sayfasına göre sıfır dışında herhangi bir değer kullanılabilirmiş; ben rastgele 10 değerini seçtim.

Okuyacak veri olmadığında, ReadLine() bloke etmek yerine exception veriyor; fakat yukardaki exception handler kodu bu durumu yok sayıyor (catch {}) ve böylece, program mutlu bir şekilde devam ediyor.

Timer interval değerini 1 mili saniye değerine kadar düşürmeme rağmen, arayüz hiç donmadı.

Ek notlar:
Terazi kısmını başka bir Windows Forms programı ile simule ettim.
Karşılıklı konuşan COM portlarını da, sistemde simulasyon amacı ile COM port ikizleri yaratan şu driver ile gerçekleştirdim: com0com http://com0com.sourceforge.net/
Benim sistemimde terazi simulatörü COM37 portuna yazıyor, Data_Okuma programı da (yani sizin programınız) COM36 portundan okuyor.
Testlerimde okuma programı hiç geri kalmadı ve hiç veri kaybetmedi.

Projelerin kaynak kodlarını isterseniz lütfen bildirin, ama yukardaki küçük değişiklik dışında farklı birşey yapmadım.

MSDN ReadLine metodu: https://msdn.microsoft.com/en-us/library/system.io.ports.serialport.readexisting%28v=vs.110%29.aspx



İkinci kısım:

Terazi tarafından gönderilen verinin ondalık kısmının string içinden çekilip çıkarılmasını ve double değerine dönüştürülmesini yapan C# console programı:

using System;

namespace ExtractNumber
{
    class Program
    {
        static void Main(string[] args)
        {
            string data  = "ST,GS, 15.6 g"; // Örnek veri.
            string[] temp;
            double agirlik;

            temp = data.Split(' ');
            agirlik = Convert.ToDouble(temp[1]);

            Console.WriteLine("Result: " + agirlik.ToString());
            Console.ReadKey();
        }
    }
}


Not: Program, ondalık değerin ilk boşluktan sonra geldiğini ve ertesinde de bir boşluk olduğunu varsayıyor.
Yazılım Mühendisi, Çevirmen.

muhittin_kaplan

tekrar ediyorum, timer la kullanılmaz, eninde sonunda patlar program. yukarıda nasıl yapılması gerektiğini verdim.

olemiss

Timer ile yaptım ve çalıştı.  Detaylı açıklamasını da yaptım.  Sizin yaptığınız?  Tek satırlık yol göstermek!
Yazılım Mühendisi, Çevirmen.

muhittin_kaplan

Hadi Buyur Burdan Yak.
verdiğim linke baktın mı
oradan alıp buraya mı yapıştırayım.
using System;
using System.IO.Ports;

class PortDataReceived
{
    public static void Main()
    {
        SerialPort mySerialPort = new SerialPort("COM1");

        mySerialPort.BaudRate = 9600;
        mySerialPort.Parity = Parity.None;
        mySerialPort.StopBits = StopBits.One;
        mySerialPort.DataBits = 8;
        mySerialPort.Handshake = Handshake.None;
        mySerialPort.RtsEnable = true;

        mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);

        mySerialPort.Open();

        Console.WriteLine("Press any key to continue...");
        Console.WriteLine();
        Console.ReadKey();
        mySerialPort.Close();
    }

    private static void DataReceivedHandler(
                        object sender,
                        SerialDataReceivedEventArgs e)
    {
        SerialPort sp = (SerialPort)sender;
        string indata = sp.ReadExisting();
        Console.WriteLine("Data Received:");
        Console.Write(indata);
    }
}

olemiss

> Burdan yak

Bu tür konuşmaları lisede bıraktım ben.

> verdiğim linke baktın mı

Ben size siz diye hitap ediyorum.  Linke bakmak bir yana, tüm sistemi yazdım ve simule ettim, daha ne yapayım?

> oradan alıp buraya mı yapıştırayım.

Önemli olan soruyu sorana yardımcı olmak.  Dediğim gibi, ben alıp yapıştırmak ötesinde sistemi birebir çalıştırdım.

Karşınızdakileri kim zannediyorsunuz, bilemem, ama forumda hoş bir ortam yaratmıyorsunuz.  En azından başkalarının harcadığı emeğe saygılı olun.  Tavrınızı ayıplıyorum.
Yazılım Mühendisi, Çevirmen.

muhittin_kaplan

#10
BakıNIZ, verdiğiniz örnek yanlış. timer la yapılan bir datareceived olayı eninde sonuda patlar (ben bunu dedikten sonra-Siz Ne Yaptınız Tek Satırlık Yol Göstermek diyerek Sataştınız- Gerçi Sizin Bu Davranışınız beni Fazla Etkilemez beni Etkileyecek Olan Yanlış Yönlendirme Yapmanız.) Yaptığınız Deneme Şu An çalışmış Olabilir.
belli bir zamana kadar çalışır, yada siz öyle sanırsınız, devamlı seri portu Timerla Kontrol etmek zorunda kalırsınız. veriyi yarım alır vb sorunlar oluşur.
bir thread oluşturup kontrol işini bu thread a vermeniz gerekir. gerisi laf.

mesaj birleştirme:: 05 Haziran 2016, 00:39:15

"ama forumda hoş bir ortam yaratmıyorsunuz" Siz Bunu Dediniz Ya Yarın Giderim Ben.

mesaj birleştirme:: 05 Haziran 2016, 00:40:56

Ayrıca Merak Ettim
Alıntı Yaptekrar ediyorum, timer la kullanılmaz, eninde sonunda patlar program. yukarıda nasıl yapılması gerektiğini verdim.

dediğimde siz neden üstünüze alındınız da

Alıntı YapTimer ile yaptım ve çalıştı.  Detaylı açıklamasını da yaptım.  Sizin yaptığınız?  Tek satırlık yol göstermek!

yazdınız ki ?

olemiss

> "Bakınız"
> "tekrar ediyorum"
> "gerisi laf"

...

Benim için bu tür söylemler, genelde kendini büyük gören, sadece kendi yaptığının doğru olduğunu zanneden, diğerlerini aşağılayan bir anlayışın göstergesi.

Windows'da thread kullanmadan çalışma yöntemleri var ve çoklukla kullanılıyor.  En basit örneği, eskiden Visual Basic dilinde bulunan DoEvents() metodunun Microsoft tarafından .NET'te bile devam ettirilmesi - Application.DoEvents().  Tek thread kullanan, ara sıra duraksayıp, arayüze çalışma fırsatı veren milyonlarca Windows programı şu anda dünyanın her tarafında ticari olarak kullanılıyor.

Programları gerektiğinden önce optimize etmek yanlıştır.  Eğer bir gün gelir, söz konusu program "patlarsa", o zaman thread yöntemine geçmek düşünülebilir.

Buradaki esas konu, daha önce de dediğim gibi, başkalarının emeğine saygı göstermek.  Sizde maalesef bu özellik bulunmuyor, yerine bol miktarda ukelalık var.

Size iyi şanslar dilerim.
Yazılım Mühendisi, Çevirmen.

muhittin_kaplan

#12
Ben Sizin Yazdığınız Tukaka Dedim mi, Çalışmaz Dedim mi, ama işin doğrusu budur dedim. seçen istediğini alır hatta hiç thread felan kullanmaz crossthread hatasını kapatır kullanır. Alınganlık neden, o garip.

mesaj birleştirme:: 05 Haziran 2016, 01:02:11

Kodunuzu İncelememiştim nereden baksan hata
ben rastgele 10 değerini seçtim.
neden 10 onuda yazın bari.
fakat yukardaki exception handler kodu bu durumu yok sayıyor
try hataları yoksaymak için kullanılmaz, error veya exp. yakalamak için kullanılır. Ön görülemeyen Hatalar da bile belli bir Log kaydı tutulur. Saygılar.
(evet emeğe saygı, ama emeğe.)

mesaj birleştirme:: 05 Haziran 2016, 04:17:44

Bu arada cross thread demişken,
http://www.serkanayaz.com/kategoriler/programlama/cross-thread-operation-not-valid-hatasi/

t2

#13
Belli ki porttan gelen veriyi başka bir thread okuyup depoluyor. Timer ile okunabilmesi bundan dolayidir. Double buffer vardir. Hatta daha fazla buffer vardir. Bunlarin threadi ni .Net kendisi halletmistir. Siz gormezsiniz. Thread kullanmasaniz da olur. Timer ile okunur.

serialPort1.ReadLine

Derken, aslinda thread vasitasiyla gelmis, buffer daki veriyi okuyorsunuz. Thread islerine girmek gereksiz. Veri gelmis. Kebap sekilde bekliyor. Okuyuveriyoruz.


Olayi damardan yapacam direk kendim okuyacam dersek thread gerekir.

robomaster

Bu işi timer ve readline ile halletmek bir yere kadar işi çözebilir. Amatörce bir çözümde olabilir.
Eninde sonunda veri kaybı oluyor okunurken.  Timeout süresi dolduğunda bir sonraki timer tetiklenene kadar aradaki boşlukta oluyor zaten problem.
Bu sefer timer aralığını timeout süresinden daha kısa sürelere kurmak gibi saçma işlere girişiliyor. Aynı süre olsa gene gap oluşuyor.

Ben söyledim @muhittin hoca da güzelce de yol göstermiş. İşin düzgün yapmak isteyen de tercih edebilir.

(Kayıpları ihmal edebiliriz derseniz o yoldan devam etmenizde sakınca yok)