2.10- MicroPython ile UART Uygulamaları

Buraya kadar yaptığımız uygulamalarla basit seviyede de olsa mikrodenetleyici uygulamaları geliştirebilirsiniz. Bir dönemin en popüler mikrodenetleyicileri yukarıda saydığımız işlerden çok da fazlasını yapamıyordu. Günümüzde mikrodenetleyiciler devre kartını kontrol eden eleman olmaktan ziyade devrenin kendisi olma yolunda hızla ilerlemektedir. Pek çok devre de artık birer çip veya modül haline getirilmiş ve seri iletişim protokolleri ile haberleşme sağlanır hale gelmiştir. Pek çok uygulama için seri iletişim protokolleri olsa da burada daha çok devreler arası seri iletişimden söz etmek gerekir. USB veya Ethernet de bir seri iletişim protokolü olsa da bir mikrodenetleyici ile bir entegreyi haberleştirmek için çok komplike olmaktadır. Bu yüzden maliyeti düşük, yapısı basit ve mesafe yönüyle birbirine yakın elemanları haberleştirmeye yönelik protokoller kullanılmaktadır.

Bu protokollerin başlıcaları UART, SPI ve I2C protokolleridir. Biz bu bölümde bu protokolleri MicroPython’da nasıl kullanacağımızı öğrenip aynı zamanda gönderilen verileri lojik analizör ile inceleyip çalışma mantığını daha iyi anlayacağız. İşin teorik kısmı üzerinde durmaktansa lojik analizörle bu sinyalleri incelemek çok daha öğretici olmakta.

UART

UART protokolü bilgisayar tarafında RS-232’ye karşılık gelmektedir. UART protokolü donanımsal olduğu kadar yazılımsal olarak da bit yükleme (bit banging) yöntemleriyle kullanılabilmektedir. RS-232 ile TTL/CMOS arasında gerilim farkı olduğu için gerilim çevirici bir entegre kullanarak (ör. MAX232) çok eski mikrodenetleyicilerin bile bilgisayarla haberleşmesi mümkündür. Çok eski bilgisayarlarda bile olan RS-232 portu bir dönem mikrodenetleyicilerin bilgisayarla haberleşmesinde kullanılan yegâne yöntem olmuştur. Günümüzde ise USB-TTL çeviriciler ile bu seri iletişim USB portu üzerinden sağlanmaktadır.

RS-232 standardının neden yüksek gerilimlerde veri iletimi yaptığını araştırınız.

UART protokolünü birinci kullanım amacımız bilgisayarla (USB ya da RS-232) üzerinden kolay bir şekilde haberleşmektir. Kullandığımız geliştirme kartında yer alan CP210x aygıtı da bu çevirimi gerçekleştirmektedir. Bu sayede yazdığımız Python dosyalarını yükleyebileceğimiz gibi Python’ın etkileşimli kabuğuna erişim sağlayabiliriz. Bunun için herhangi bir seri terminal uygulaması kullanmamız yeterlidir.

İkinci olarak UART protokolü ile pek çok modül ve cihaz ile iletişim sağlayabiliriz. Pek çok cihaz bilgisayar üzerinden iletişime geçmeye yönelik konsol portlarına sahiptir. Biz bu konsola mikrodenetleyici ile de erişebiliriz. GPS, Bluetooth, Wi-Fi, GSM ve daha pek çok haberleşme modülü UART üzerinden kullanılabilir. UART asenkron bir iletişim yöntemi olduğu için iki cihazın da bant genişliğinin eşleşmesi gereklidir. Bu bazen manuel olarak ayarlanırken bazı aygıtlar bunu otomatik olarak yapabilmektedir.

ESP32’de UART protokolünü kullanmak için öncelikle doğru ayakları ve birimi bilmemiz gereklidir. UART0 birimi (GPIO1(TX), GPIO3(RX)) kart üzerinde yer alan USB-TTL dönüştürücü entegreye bağlıdır ve MicroPython’da bilgisayarla haberleşmek için kullanılır. Bunun dışında UART1 ve UART2 protokollerini çakışan ayakları hariç olmak üzere istediğimiz ayaklara atayıp kullanabiliriz. Varsayılan değerleri UART1 için TX GPIO10, RX GPIO9 ve UART2 için TX GPIO17 ve RX GPIO16’dır. Şimdi MicroPython’ın UART sınıfıyla çok basit UART uygulamaları yaparak protokolü daha iyi anlayalım.

ESP32’yi bilgisayarla haberleştirmemiz için FT-232, CH341 veya benzeri USB-TTL çevirici modül kullanmak gereklidir. UART iletişimde her zaman RX ve TX çapraz bağlanmalıdır. Yani RX ayağını öteki aygıtın TX ayağına, TX ayağını ise öteki aygıtın RX ayağına bağlamak gereklidir. Elinizde lojik analizör varsa bu hatların ortasına probları yerleştirebilirsiniz.

Bilgisayarda terminal üzerinde seri portu kullanmak istiyorsanız çeşitli terminal programlarını kullanabilirsiniz. Arduino IDE’nin Serial Monitor özelliği, Putty veya Realterm bunların başlıcalarıdır. RealTerm’i aşağıdaki bağlantıdan indirebilirsiniz.

https://sourceforge.net/projects/realterm/

RealTerm’i kullanıma hazır hale getirmek için “Port” sekmesine geliyoruz ve ayarları aşağıdaki gibi yapıyoruz.

Burada Baud kısmı saniyede kaç bitlik iletim sağlanacağı (bps), Port kısmı aygıtın port numarasıdır. Bunları doğru bir şekilde ayarladıktan sonra şimdilik parity ve stop bits kısımlarını varsayılan halde bırakıyoruz ve “Change” düğmesine basarak ayarları kaydediyoruz. “Open” düğmesine bastığımızda port açılacak ve veri iletişime hazır hale gelecektir.

ESP32 kartındaki USB-TTL çeviricinin MicroPython etkileşimli kabuğu için kullanıldığını unutmayın. Bu yüzden harici bir çeviriciye ihtiyaç duyulmaktadır.

RealTerm’i açıp ayarlarını yaptıktan sonra kullanacağımız USB-TTL çeviricinin sürücüsünü kurmayı unutmamak gereklidir.

FT-232 sürücülerini aşağıdan indirebilirsiniz.

https://ftdichip.com/drivers/vcp-drivers/

CH-341 sürücüsünü aşağıdan indirebilirsiniz.

http://www.wch-ic.com/downloads/CH341SER_EXE.html

Kurulum tamamlandıktan sonra artık ilk kodumuzu çalıştıralım.

Kodu çalıştırdığımızda RealTerm ekranında gelen veriyi görebilirsiniz.

Eğer herhangi bir okuma sağlanmıyorsa büyük ihtimal RX ve TX hatları paralel bağlanmıştır. Bunların yerini değiştirip bir daha deneyebilirsiniz. Biz kod üzerinde veya seri terminalde bu iletişimin gerçekte nasıl gerçekleştirildiğini göremiyoruz. Bize sadece alınan ve gönderilen veriler gösterilmekte. Bu yüzden lojik analizör veya gelişmiş bir osiloskopla bunu gözlememiz gereklidir.

Ben burada Saleae lojik analizörü kullandım. Uygun fiyatlı bir lojik analizör olarak bunu tercih edebileceğiniz gibi günümüzde biraz gelişmiş dijital osiloskoplarda da lojik analizör özelliği bulunmaktadır. Dijital bir osiloskopta “Single shot” özelliğini kullanmanız da bu sinyali yakalayıp ekranda incelemenize imkan verecektir. Şimdi daha derinlemesine incelemek için tek bir harf gönderelim ve tek bir baytın nasıl ele alındığına bakalım. Çoklu baytlarda değişen bir durum yoktur sadece art arda bu şekilde veriler gönderilmektedir.

Burada sinyalimiz boştayken HIGH konumunda durmaktadır. İletişimin başlayacağını ifade etmek adına START biti kullanılmaktadır. START biti bir süre LOW konumunda kaldıktan sonra en sağdaki bitten (LSB) itibaren veriler bir sıra halinde gönderilmektedir. En soldaki bit gönderildikten (MSB) sonra STOP biti ile bir süre HIGH konumuna getirilip veri çerçevesinin bittiği ifade edilmektedir. Bu bir baytın gönderilmesi sırasında gerçekleşen olaylar olup sıralı veri gönderdiğimizde START-Veri1-STOP, START-Veri2-STOP… şeklinde bu iletişim devam etmektedir.

“a” harfi ise burada ASCII formatında gönderilmiştir. ASCII tablosunu aşağıdan inceleyebilirsiniz.

https://www.asciitable.com/

Bu gerçekleştirdiğimiz iletişim asenkron iletişim olup herhangi bir zamanlama sinyali içermemektedir. Bu yüzden iki cihazın da bit oranları (baud rate) eşit olmak zorundadır. Her cihaz kendi içinde bulunan saat devresi sayesinde belli zaman aralıklarında hattın durumunu okuyup bunu 1 veya 0 olarak değerlendirmektedir. Baud oranında küçük değişimler bile verinin ciddi şekilde bozulmasına sebep olmaktadır.

Şimdi de UART üzerinden basit bir şekilde veri okumanın nasıl yapıldığını görelim. Bunun için uygulamaya 2 adet LED bağlayalım ve bilgisayar üzerinden bunları kontrol edelim.

Devre11_UartLed.fzz

Burada GPIO18 ve GPIO19’a birer adet LED bağladık. Uygulamaya göre röle, transistör gibi elemanları da aynı şekilde kullanabiliriz. Örneğin bilgisayardan vantilatörü kontrol etmek isteseydik dijital çıkışa bir röle modülü bağlamamız yeterli olacaktı.

Gönderdiğimiz veriye göre belli başlı işleri yaptıracağımız için bir komut seti oluşturmamız gereklidir. Burada 1. LED’i açacağız, kapatacağız ve 2. LED’i yine açıp kapatacağız. Bu durumda 4 farklı iş için 4 farklı komut belirleyelim.

Üstelik biz bir geri bildirim de almak istiyoruz. Bu yüzden her bir okumadan sonra mikrodenetleyici bilgisayara yaptığı işi bildirsin.

Devreyi kurup programı yükledikten sonra Realterm’de “Display” sekmesinde newLine mode kısmını işaretleyelim ve sonrasında “Send” sekmesini açalım ve komutları gönderelim.

Bu programda dikkat etmemiz gereken birkaç nokta vardır. Öncelikle UART nesnesini oluştururken timeout özelliğine 100 değerini verdik. Bu her bir read() metodunda verileri 100 milisaniye bekleyeceğini ifade etmektedir. Bu bekleme olmazsa veya bekleme süresi çok düşük tutulursa verilerin bir kısmı veya tamamı okunmayabilir. Veri okurken uart1.read(1) diyerek kaç bayt okuyacağımızı belirttik. Biz sadece 1 bayt okuyacağımız için 1 bayt okuduktan sonra fonksiyon okumayı sonlandıracaktır. Okuduğumuz değerler string değil bayt sizisi (bytes) olduğu için karşılaştırma işlerinde b’’ ön ekini kullandık. İstersek bunu şu şekilde de yapabilirdik.

Burada decode() metodu ile bytes() tipindeki değeri karakter dizisine (str) çeviriyoruz. Herhangi bir veri okunmadığı zaman uart_okuma değişkeninin tipi None olacaktır. Seri porttan veri okuma işi ana program döngüsünde yapıldığı için bu değişken sürekli güncellenmektedir. Şimdi okuma uygulamasını daha etkili bir şekilde yapalım.

UART’dan veri okunurken genelde yoklama yöntemi ya da kesmeler kullanılmaktadır. MicroPython ise bunu arka planda yaptığı için okunan verileri bir tampon belleğe kaydetmektedir. Yani ana program akışında mikrodenetleyici başka işler yaparken UART üzerinden gelen verilen kaybolmamakta, mikrodenetleyici kesmeye gidip bunları kaydetmektedir. Biz ise bize gerekli olan zamanda bu verileri okuyabiliriz.

Biz bu programda read() metodunu her kullanışımızda tampon bellekteki veri silinmekte ve read() metodu ile geri döndürülmektedir. Biz bunu sırasıyla okuyup buf dizisine aktarmaktayız. Göndereceğimiz bayt sayısını bilsek de bu gönderimin birden fazla gerçekleşmesi halinde okuyacağımız bayt sayısı belirsiz olmaktadır. Bunu önlemek için any() metodunu kullanarak tampon bellekte hiç veri kalmayana kadar okuma metodu çalıştırılmıştır.

Burada okuyacağımız veriyi değerlendirme noktasında bir sorun yaşamamız muhtemeldir. Tampon bellekteki bütün verileri okusak da bizim aradığımız verinin kaçıncı indiste olduğunu bilmemiz gereklidir. Bunun için veri gönderirken bir çerçeve halinde göndermeliyiz. Yani verinin başı ve sonu belli olmalıdır.

ASCII tablosunda 0x02 değeri “Start of text”, 0x03 değeri ise “End of text” ifadesine karşılık gelmektedir. Biz veriyi gönderirken verinin başına 0x02 değerini, sonuna ise 0x03 değerini eklememiz gerekir.

Şimdi bu uygulamayı denemek için Realterm’den aşağıdaki veriyi gönderelim.

0x02 0x30 0x31 0x32 0x33 0x03

Bu sefer onaltılık notasyonda belirttiğimiz için “Send Numbers” seçeneği ile göndereceğiz. MicroPython etkileşimli kabuğunda veri aşağıdaki gibi yazdırılacaktır.

Veri: 0123

Veri olarak göndermek istediğimiz 0x30, 0x31, 0x32, 0x33 değerleri ASCII olarak 0, 1, 2 ve 3 karakterlerine karşılık gelmektedir. Biz burada veriye biraz istenmeyen ilaveler yapalım. Aşağıdaki verileri terminalden göndererek veri bütünlüğünün sağlanıp sağlanmadığını kontrol edelim.

0x43 0x02 0x30 0x31 0x32 0x33 0x03 0x36 0x35

Bu veriyi gönderdiğimizde de sadece 0x02 ve 0x03 arasındaki değerler okunmaktadır. Artık verimizi bu yönden güvence altına aldığımıza göre şimdi bu veri çerçevesindeki verileri birbirinden ayıralım.

Verileri birbirinden ayırmanın en kolay yollarından biri veriler arasına ayırıcı (delimiter) koymaktır. Rakam veya harf ifade etmeyen herhangi bir karakter olabilir. Genellikle veriler arasına virgül (,), boşluk veya noktalı virgül (;) koyulmaktadır. Biz şimdi bilgisayardan üç servo motorun açılarını içeren bir veri aldığımızı varsayalım. Burada x, y, z olmak üzere üç farklı eksendeki servonun açı değerlerini ifade eden değişkenlerimiz var. Bunları doğru bir şekilde ayırıp kullanmaya hazır hale getirelim.

Şimdi bu programa uygun bir şekilde bir veri çerçevesi gönderelim. Göndereceğimiz veri şu şekilde olacaktır.

Başlangıç işareti + virgül + X değeri + virgül + Y değeri + virgül + Z değeri + Bitiş işareti

ASCII tablosundan virgülün karakter değerinin 0x2C olduğunu öğreniyoruz. Şimdi Realterm’den şu veriyi gönderelim.

0x02 0x30 0x30 0x30 0x2C 0x31 0x31 0x31 0x31 0x2C 0x32 0x32 0x32 0x03

Veriyi gönderdiğimizde okunan değerler artık bizim için çok daha anlamlı hale gelecektir. Bu sayede uygulamalarda rahatça kullanabiliriz.

{‘y’: ‘111’, ‘x’: ‘000’, ‘z’: ‘222’}

Okuma işlemlerini bitirdiğimize göre şimdi de aynı şekilde nasıl veriyi göndereceğimizi görelim. Bu uygulamada okuduğumuz 3 farklı analog değer olduğunu varsayalım. Bu değerleri yine bir veri çerçevesi içinde virgül ile ayırarak gönderelim.

Programı çalıştırdığımızda Realterm’de verilerin belirlediğimiz formatta gönderildiğini görmekteyiz.

Buraya kadar gönderilen veriyi bir düzenli hale getirdik ama seri iletişimde verinin çeşitli sebeplerden dolayı bozulması durumunda bu bozulan veriyi olduğu gibi kullanmaktayız. Buradaki denemelerde verinin doğruluğu çok sorun çıkarmıyor gibi görünse de uygulamada her zaman verinin bozulmadan iletilme garantisi yoktur. Seri iletişimde verinin doğruluğunu denetleme adına eşlik biti(parity), checksum ve CRC gibi yöntemler sıkça kullanılmaktadır.

Eşlik biti UART’ın donanımsal bir özelliği olup her zaman etkili bir biçimde doğruluğu sağlayamamaktadır. Verinin doğruluğunu sağlamak için çeşitli gelişmiş algoritmalar bulunsa da biz günü kurtarma adına basit bir checksum algoritmasını uygulamakla yetineceğiz. Oluşturduğumuz veri çerçevesinde baştan itibaren bütün karakterler sayısal değerleriyle ele alınıp XOR işlemine tabi tutulmakta ve bu veri çerçevesine eklenmektedir. Bu sayede alıcı tarafta da bütün karakterleri yine XOR işlemine tabi tutup karşı tarafın gönderdiği değerle eşleşip eşleşmediğini kontrol edebiliriz. Eğer veride bir yerde bozulma yaşandıysa iki XOR işleminin değerleri birbirini tutmayacak ve veride bir bozulma olduğu anlaşılacaktır.

Programı çalıştırdığımızda şu şekilde bir veri gönderecektir.

100,200,300,1c

Buradaki 1c değeri checksum fonksiyonundan dönen baytın onaltılık notasyonda ifade edilmesidir. Bunu okuma tarafında değerlendirmek için şöyle bir program yazabiliriz.

Programı çalıştırdığımızda şöyle bir çıktı alacağız.

[49, 48, 48, 44, 50, 48, 48, 44, 51, 48, 48, 44, 49, 99]

Sum: 28

checksum: 28

checksum doğru

[‘100’, ‘200’, ‘300’]

Programı incelediğimizde öncelikle seri_oku() fonksiyonunda 16 baytlık bir okuma yaptığımızı ve bu okumada 0x02 karakterini aradığımızı görüyoruz. Bu karakterin mevcut olması durumunda ise sonraki aşamaya geçilmekte ve başlangıç indisinden itibaren bitiş işareti olan 0x03 değeri aranmaktadır. Bu değer de bulunursa bu ikisinin arasındaki değerleri ayıklayıp geri döndürüyoruz. Bu sayede 0x02 karakteri ve 0x03 karakteri olmayan okumalar dikkate alınmadığı gibi 0x02 başlangıç ve 0x03 bitiş olmak üzere sadece bunların arasındaki okumaları dikkate almaktayız.

ser.inWaiting() metodu uart.any() metoduyla aynı işlevi görmektedir. Eğer bir veri geldiyse program seri_oku() fonksiyonuna gitmektedir.

Metot ve fonksiyon terimleri birbiri yerine kullanılabilse de metot tabirini bir sınıfa ait fonksiyon için kullanmak gereklidir.

veri değerini okuduktan sonra bunu bir sonraki işlem için karakterlere ayırmaktayız. Daha önceki uygulamada nasıl her bir bayt XOR işlemine tabi tutulduysa burada da aynı işlemi gerçekleştireceğiz. Burada dikkat etmemiz gereken nokta checksum değerinin XOR işlemine tabi tutulmaması gerektiğidir. Bu yüzden bunu sonraki karşılaştırma işlemi için bir kenara ayırıyoruz.

sum = int(chr(charlist[-2]) + chr(charlist[-1]), 16)

Biz checksum değerini iki nibble (4 bitlik değer) olarak gönderdik. Bir nibble bir onaltılık karaktere denk geldiği için iki onaltılık karakteri birleştirdiğimizde bir bayt elde ederiz. Elde ettiğimiz charlist listesi bytes değerinde olduğu için bunu önce chr() fonksiyonu ile karaktere sonrasında bunları birleştirerek karakter dizisine sonrasında ise 16’lık tabanda olduğunu belirterek int() fonksiyonu ile tamsayı değerine çeviriyoruz. Böylelikle elimizde sayısal bir değer oluyor.

charlist’i XOR işlemine tabi tutacağımızdan dolayı artık bize gerekmeyen checksum değerini atmamız gereklidir. Bunun en pratik yollarından biri pop() metodunu kullanmaktır. Bu metot bir listenin en son listesini silmek için kullanılır.

Sonrasında checksum fonksiyonu ile listeyi aynı işleme tabi tutup karşılaştırmayı yapıyoruz. Eğer bir hata olursa raise anahtar kelimesiyle bir istisna (Exception) oluşturuyoruz.

Programcının kolayca ekrandan takip etmesi için belli başlı değerler ekrana yazdırılmıştır. Aynı şekilde gönderilen veriyi lojik analizör üzerinden de takip etmeniz mümkündür.

Daha gelişmiş bir uygulama için veri çerçevesi şu şekilde geliştirilebilir: [7]

· İki adet başlık (header) baytı (Biz 0x02 değerini kullandık)
· Komut baytı
· Veri boyutu baytı
· Veri
· İki adet bitiş (trailer) baytı (Biz 0x03 değerini kullandık).
· CRC formülü

UART bize esnek bir iletişim ortamı sunmaktadır. Bu iletişimi ihtiyacımız doğrultusunda farklı yöntemlerle uygulama imkânımız vardır. Uygulama geliştirmek için yukarıdaki örnekler şimdilik bize yeterli olacaktır.

ESP32’yi bilgisayarla haberleştirmemiz için FT-232, CH341 veya benzeri USB-TTL çevirici modül kullanmak gereklidir. UART iletişimde her zaman RX ve TX çapraz bağlanmalıdır. Yani RX ayağını öteki aygıtın TX ayağına, TX ayağını ise öteki aygıtın RX ayağına bağlamak gereklidir. Elinizde lojik analizör varsa bu hatların ortasına probları yerleştirebilirsiniz.

--

--