STM32 ve ENC28J60 ile TCP Server Uygulaması (MBED)

Merhabalar,

Bu konuda STM32 serisi kartlar ile internete bağlanıp TCP server kurma üzerine bir uygulama yapacağız. İnternete bağlanmak için ucuz ve bulunması kolay olan ENC28j60 modül kullanacağız. STM32 ile oluşturduğumuz TCP server’ a C# ile oluşturduğumuz bir client arayüz ile bağlanarak veri alış verişi gerçekleştireceğiz. İşlemci olarak yine çok tercih edilen STM32F103C8T6 kullanacağız. Yazılımda MBED kullandığımız için, MBED desteği olan bir çok kart ile bu yazılımı kullanmanız mümkün olacaktır. Sadece ENC28j60 ile kullandığınız STM32 arasındaki SPI pinlerini doğru bağlamalı ve yazılımda doğru ayarlamalısınız. 

Yapacağımız uygulamada STM32′ ye bağlı 2 adet LED’ i, C#’ da oluşturduğumuz arayüz ile kontrol edeceğiz. Aynı zamanda STM32′ nin ADC pinlerinden okuduğumuz değerleri arayüze göndereceğiz. STM32′ de oluşturduğumuz server’ a aynı anda birden fazla client bağlanabilecek şekilde ayarlayacağız. STM32 için oluşturduğumuz 2 LED, 1 buton ve 2 potansiyometre’ li bağlantı şeması aşağıdaki gibidir. Çizimde fazladan FTDI dönüştürücüde mevcut. Bu FTDI dönüştürücü, STM32F103 kartı ile programlamak için kullanılan ST-Link üzerinden aynı anda UART kullanılamadığı için bu çizime dahil edilmiştir. Eğer UART’ dan gelen geri bildirimleri görmek istemezseniz kullanmayabilirsiniz. Zorunlu değildir.

Devre Bağlantısı

f103_tcp_server_2

Verilen çizimde ENC28j60 modülü 3.3V beslemeye bağlanmıştır. Modülün üzerinde kullanılan entegrenin datasheet’ inde 3.3V olarak besleme verilmesi gerektiği belirtilmiş. Lakin kimi modüllerin üzerinde dahili regülatörde bulunduğu için besleme olarak 5V verilmesi gerekiyor. Elinizdeki modülün datasheet’ ine bakarak besleme bağlantısını yapmalısınız. Ayrıca ENC28j60′ ın reset pini STM32′ ye bağladım. Bu sayede istenildiği zaman entegreye reset atılabilmesi sağlanabillmektedir. Reset atmak istemezseniz bu pini doğrudan 3.3V’ a bağlayabilir, yada STM32 ile aynı anda reset attırmak isterseniz STM32′ nin reset pinine bağlayabilirsiniz. 

Bağlantı şemasını verdikten sonra yazılım kısmı ile devam edelim. Öncelikle STM32′ de kullandığımız TCP server’ ın yazılımına bakacağız. MBED’ de ENC28J60 kullanmak için yazılımda bir kütüphane kullanacağız.  Bu kütüphaneye bağlantıdan ulaşabilirsiniz. Kütüphaneyi indirip projeye dahil etmelisiniz. MBED Studio’ da bunun nasıl yapıldığını bilmiyorsanız bağlantıdan ulaşabilirsiniz. Projenin derleme aşamasında bir püf noktası bulunmaktadır. Kütüphaneyi eklemeden önce proje boşken projeyi derlemeniz gerekmektedir. Çünkü kullandığımız kütüphane MBED OS5 ile tam uyumlu değil ve derleme aşamasında sorunlar meydana gelmektedir. Bu sebeple kütüphaneyi eklemeden projeyi derlemeli sonra yazılımı ve kütüphaneyi projeye eklemelisiniz. STM32 server olarak çalışacağı statik bir IP vermemiz daha mantıklı olacaktır. Bu sayede IP’ si sabit kalacak ve her seferinde IP’ si ne oldu diye bakmamız gerekmeyecek. Statik IP verirken genel olarka iki önemli şeye dikkat edilmelidir. İlki verilen IP ağda başka bir cihaz tarafından kullanılmıyor olmalıdır. Diğeri ise verilen IP kullanılan ağdaki DHCP sunucusunun yani modem’ in IP verdiği aralıkta olmalıdır. Yani modem 192.168.1.1 ile 192.168.1.255 arasıa IP verebiliyorsa 192.125.125.125 gibi bir IP’ yi statik IP olarak atayamazsınız. 192.168.1.137 gibi bir IP vermek daha mantıklı olacaktır. 

STM32 Yazılım – TCP Server

Yazılım kısmında ağ ayarları ile başlayalım. Ağ ayarlarında öncelikle statik IP vermeyi gerçekleştirelim. Burada 4 adet bilgininin girilmesi gereklidir. Bu bilgiler IP, gateway, netmask ve port’ dur. Eğer dış ağdan bu TCP server’ a bağlanmak istiyorsak kullandığınız port’ u modem üzerinde açmalı ve IP yönlendirmesi yapmalısınız.

// IP Settings
#define IP      "192.168.137.120"
#define GATEWAY "192.168.137.1"
#define NETMASK "255.255.255.0"
#define PORT    61

net.set_network(IP, NETMASK, GATEWAY);
net.connect();

Daha sonra TCP server’ i başlatma ve işlemlerini gerçekleştirelim. Bu işlemler kullandığımız kütüphane sayesinde basit olarak gerçekleştirilebilmekte.

    server.open(&net);

    server.bind(PORT);

    server.listen(4);                   // max client count

Server kurulumu için gerekli ayarlar bu şekildedir. Kurulumu gerçekleştirdikten sonra client’ lardan sitek geldiğinde kabul etmeli ve veri alış verişini gerçekleştirmeliyiz. Bu işlemleri de aşağıdaki şekilde gerçekleştirebiliriz. Kullanılan fonksiyon ana fonksiyonu bloklamadığından diğer işlemleri de rahatlıkla gerçekleştirebilmektedir.

client = server.accept();               // accept client if exist

if (client)            // check client is exist
{                   
       size_t  recvLen;

       recvLen = client->available();           

       if(recvLen > 0)
       {
             client->recv(recvData, recvLen);            // read incoming data from socket
       }
}

Yukarıdaki örnekte kullanılan fonksiyonların açıklmasını çok detaylı anlatmayacağım. Zaten fonksiyon isimlendirmelerine bakarak yaptıklarını işlemleri tahmin etmek mümkün. Burada dikkat edilmesi gereken nokta bir client bağlı mı değil mi sorgusu.  Verilen örnek yazılımda da client’ ın kotnrolü bu şekilde gerçekleştiriliyor. “client” isimli değişken TCPclient sınıfında boş pointer olarak tanımlanmıştır. eğer bir client bağlanmışsa “server.accept()” fonksiyonu bağlanan client’ in bilgilerini TCPclient tipinde döndürüp “client” isimli değişkene atıyacaktır. Böylece if sorgusundaki gelen veriyi okuma işlemleri de gerçekleşecektir. Ben bu işlemi daha anlaşılır olması için kullandığımız kütüphane içerisindeki “client->connected()” ile yapmayı denedim.  Ancak yazılımın çalışmadığını gördüm. Sanırım kütüphane ile MBED arasında bazı uyumsuzlukar mevcut. Verilen kullanımda sorunsuz olarak çalışmaktadır.

TCP soketten gelen bağlantıyı kabul edip veriyi de aldığımıza göre artık gelen veriye göre işlem yapmaya geldi sıra. Client ile gönderdiğimiz veri formatı “0xA5 0x5A id veri” şeklindedir. Bu verideki ilk iki byte verinin başı olduğunu belirten göstergedir. Aslında tcp iletişimde bu verinin karışma ihtimali çok düşük olsa da ben alışkanlıktan yine ekleme ihtiyacı duydum. Veriyi ayırıp işlem yapmadan önce bu iki byte’ ı ve veri paketinin kontrolünü gerçekleştirdim.

                // check incoming data length and first two bytes
                if(recvLen > 2 && (recvData[0] == 0xA5 && recvData[1] == 0x5A))
                {
                    switch(recvData[2]){
                        case 0x01:
                            led1 = int(recvData[3]);
                            break;
                        case 0x02:
                            led2 = int(recvData[3]);
                            break;
                        case 0x10:
                            client->send((uint8_t*)adcArr, 5);      // send adc data if client wants it
                            break;
                        case 0x30:
                            led3 = (float)(int(recvData[3]) / 100.0);       //read pwm value and write LED
                            break;

                    }
                }

Gönderilen veride 3. byte alınan veri için ID olduğundan yapılacak işlem buna bakılarak seçilmektedir. 0x01 ve 0x02 ID’ si için STM32′ ye bağlı LED’ leri açıp kapatıyoruz. 0x10 ID’ si ile STM32′ de ADC ile okunan verileri client’ e gönderdik. Yapılan haberleşmedeki bütün veriler 1 byte olarak seçtiğim için herhangi ek işleme gerek duymadım. Eğer yapılan işlemlerde 1 byte’ den daha uzun veri iletilecekse yapılacak işlemlerde veriyi byte’ lara bölerek iletilmesi gerekmektedir. 0x30 ID si STM32′ ye bağlı olan 3. LED’ i PWM ile kontrol edilebilmesi içindir. C# arayüzünde trackbar kullanarak LED paralaklığı bu ID ile değiştirilebilmektedir. Trackbar kullanıldığında iletilen veri 0 ile 100 arasında değişmektedir. MBED’ de PWM verisi 0 ile 1 arasında float değer olması sebebi ile trackbar’ den gelen değer, server’ da 100′ e bölünerek PWM değerine yazılmaktadır.

Veriyi aldıktan ve gelen veriye göre işlem yaptıktan sonra client tarafından başlatılan bağlantıyı server tarafında kapatmamız gerekmektedir. Bu aslında bir zorunluluk değil. Ancak server’ da kullandığımız denetleyicinin gereksiz meşgul olmaması için bağlanan her client işlemini bitirdikten sonra bağlantısını sonlandırdık. Böylece bir çok client ile işlem yapılabilmesi sağlanabilmiştir.

client->close();

C# Yazılımı

C# yazılımı TCP client olarak çalışacaktır. Oluşturulan arayüz aşağıdaki resimde görülmektedir. Arayüzde bulunan bağlan butonu sürekli bağlantının gerçekleştirilebilmesi için vardır. Bağlan butonuna basmadan da LED’ leri kontrol edebilirsiniz. Ayrıca arayüzde bir adet trackbar bulunmakta. Bu trackbar’ ı “mouse up” eventi ile birlikte kullanmayı tercih ettim. Trackbar ile çoğunlukla kullanılan “scroll” eventi scroll’ un her hareketinde veri gönderdiği için soketi gereksiz meşgul ediyor. “Mouse up” eventi ise trackbar üzerinden mouse kaldırınca çalışmaktadır. Bu sebeple her harekette sadece bir veri gönderiyor. Altta bulunan listbox ise TCP server’ dan gelen verileri görüntülemek içindir. Gelen her veri bir satır halinde bu trackbar’ da görülmektedir.

tcp_client_cs

Bu kısımdan sonra sıra geldi C# arayüzüne. Daha önce de belirttiğim gibi client’ ı C# da oluşturduğumuz arayüz ile gerçekleştireceğiz. Bu kısımda çok fazla detay vermeden sadece veri iletim kısımlarına değineceğim. Zaten arayüzde çok karmaşık bir arayüz değil. Basit C# bilgisi ile anlamak mümkündür. TCP server’ a veri göndermek için aşağıdaki gibi bir fonksiyon oluşturdum. Bu fonksiyon bağlantıyı başlatıp, veri gönderip aldıktan sonra bağlantıyı kapatıyor. Bu sebeple server’ dan sürekli veri istememiz gereken durumlarda bir timer kullanarak bu fonksiyonu sürekli çağırmamız gereklidir.

// send data to tcp server with ID and data
        private void sendTcpData(byte pId, byte pVal)
        {
            TcpClient tcpclnt = new TcpClient();                // create TCP client 

            // try to connect server
            try
            {
                // split ip str and port from string
                String []connVal = connTxt.Text.Split(':');
                ipStr = connVal[0];
                portStr = connVal[1];

                tcpclnt.ConnectAsync(ipStr, Convert.ToInt32(portStr)).Wait(1000);

                Stream stm = tcpclnt.GetStream();

                byte[] send_bytes = { 0xA5, 0x5A, pId, pVal };

                stm.Write(send_bytes, 0, send_bytes.Length);        // send data to tcp server

                byte[] recv_bytes = new byte[100];  
                int k = stm.Read(recv_bytes, 0, 100);               // recv data from tcp server

                // check length and first two bytes of recevived data 
                if (recv_bytes.Length > 2 && (recv_bytes[0] == 0xA5 && recv_bytes[1] == 0x5A))
                {
                    // if third data is 0x30 show it in gui
                    if (recv_bytes[2] == 0x30)
                    {
                        prgLbl.Text = Convert.ToString(recv_bytes[3]);
                        progressBar1.Value = recv_bytes[3];

                        prgLbl2.Text = Convert.ToString(recv_bytes[4]);
                        progressBar2.Value = recv_bytes[4];
                    }
                }

                // show all received data in listbox
                String recStr = ByteArrayToString(recv_bytes, k);
                listBox1.Items.Add(recStr);
                listBox1.SelectedIndex = listBox1.SelectedIndex + 1;

                // close connection after sending and receiving data
                tcpclnt.Close();
            }catch(Exception ex)
            {
                timer1.Stop();
                cnnStat.Text = "Bağlı Değil";
                MessageBox.Show("Bağlantı Zaman Aşımına Uğradı", "Bağlantı Hatası");
            }
        }

Verilen fonksiyonda yapılanları sırası ile açıklayacak olursak, bağlanmak istediğimiz TCP server bilgilerini textbox’ dan okuduk. Bağlantıyı ve sonrasında veri transferini gerçekleştirdik. Veri transferi yaparken üstte verdiğim veri formatına göre olması için bir byte dizisi kullandık. Gelen tüm verileri list box’ da görüntüledik. Bağlantıyı kapattık. Böylece iletişimi gerçekleştirmiş olduk. Yapılan işlemlerin hepsi “try – catch” yapısı içerisinde gerçekleştirildiği için meydana gelen problemlerde arayüzün hata vermesi engellenmiştir. Bu fonksiyonu LED toggle işlemleri için bir kere çağırmamız yeterlidir. Ancak server’ dan sürekli istek yapmamız gereken durumlarda bunu bir timer ile kullanmamız gerekli. Bunu da basit olarak aşağıdaki şekilde gerçekleştirebiliriz.

        private void cnnBtn_Click(object sender, EventArgs e)
        {
            timer1.Start();             // start data request with interval
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            sendTcpData(0x10, 0x00);        // send data which ID is 0x10 to server and request ADC data
        }

 

Yapacağımız işlemler genel olarak bu kadar. STM32′ de ve C#’ da yazdığımız yazılımların tamamına github üzerinden ulaşabilirsiniz.

İyi çalışmalar dilerim…

Add a Comment

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir