Stm32 ve Mbed ile Nextion Ekran Kullanımı

Merhabalar..

Bu konuda Nextion ekran kullanarak yaptığımız Arduino uygulamasını STM32 serisi kartlar kullanarak yapacağız. Bu konuda yine önceki örneklerde kullandığım gibi Mbed kullanacağım. Muhtemelen Mbed Stduio (merak edenler bağlantıdan bakabilir) kullanıma açılana ve stabil hale gelene kadar Mbed ile anlatacağım son konu olacak. Bundan sonra fırsat buldukça STM32CubeMx ile bazı uygulamalar yapmaya çalışacağım.

Bu konuda Arduino’ daki konudan farklı olarak kütüphane kullanmayacağız. Her veriyi byte byte kendimiz işleyeceğiz. Bu şekilde yapmanın avantajlarını ve dezavantajlarını birlikte göreceğiz. Arduino’ daki konuyu incelemek isteyenler bağlantıdan ulaşabilirler. Nextion tasarım ide’ sinin kullanımına ve tasarımın nasıl yapılacağına bağlantıdaki konuda detaylı olarak anlattığım için tekrar değinmeyeceğim. Bu kısımda daha çok yazılım ağırlıklı olacaktır. Ben projede Nucleo-F767Zi model numaralı bir kart kullanacağım. Üzerinde Uart hattı olan ve Mbed destekleyen herhangi bir STM32 serisi kart ile bu uygulamayı yapabilirsiniz. Elbet de bağlantı şemasını uygun şekilde değiştirmelisiniz.

Önceki uygulamaya ek olarak birde slider widget’ i ekledim. Bu widget’ i ikinci sayfaya ekledim. Özellikle buraya ekledim. Çünkü checkbox ile kullanımı özel bir durum gerektirmekte. Yeri geldiğinde bundan bahsedeceğim. Öncelikle yapacağımız bağlantı şemasına bakalım. Bağlantı şemasında F401 kullandım. Çünkü F767′ in fritzing çizimi bulunmamakta.

f401fritzing

Çizimdeki FTDI dönüştürücü göstermeliktir. Siz onun yerine Nextion erkan bağlıyacaksınız (Rx ve Tx pinleri resimdeki gibi olacak). Fritzing’ de Nextion ekran bulamadığım için FTDI dönüştürücü koydum.

Bağlantıları siz kendi kartınıza göre değiştirebilirsiniz. Dikkat etmeniz gereken Nextion ekranı UART pinlerine, led’ lerden birini de PWM pinine bağlamalısınız. Diğer led’ leri herhangi bir dijital pine bağlıyabilir yada kartın üzerindeki onboard led’ leri kullanabilirsiniz. PWM pinine bağlayarak kullanacağımız ledin paralıklığını Nextion ekrandaki slider ile kontrol edeceğiz. Slider’ i ikinci ekrana yerleştirdikten sonra “touch release” eventini aktif etmeyi unutmayın.

Led’ lerden ikisi toggle işlemi için, bir tanesi checkbox için, 3. sü ise Nextion ekran ile kurulan iletişimin kontrolü için kullanacağız. Şimdi sıra geldi yazılım kısmına. Yazılımda kütüphane kullanmadan doğrudan Nextion haberleşme protokolü ile işlemlerimizi gerçekleştireceğiz. Itead tarafından hazırlanmış instruction sete bağlantıdan ulaşabilirsiniz. Dökümanın başında belirtildiği gibi bütün komutlar 3 adet 0xFF byte’ ı ile sonlanmalıdır. Yani siz listeden herhangi bir komut göndermek istediğiniz zaman sonuna mutlaka 0xFF 0xFF 0xFF eklemelisiniz. Öncelikle kullanacağımız sınıfların tanımlarını yapalım.

Serial pc(USBTX, USBRX);      //debug values from pc
Serial nex(D1, D0);           //nextion communicate pins
AnalogIn aIn1(A0), aIn2(A1);  //analog input pins
Ticker sendValTicker;         //ticker need for update values on Nextion screen
Timer t1;                     //timer need for measure pass time in serial read interrupt

Serial ve analog sınıflarının neden kullanıldığı rahat anlaşılabiliyor. Burda ek olarak Ticker ve timer kullanımına değinmek istiyorum. Ticker mbed’ de belirli sürelerde işlem yaptırmak istediğiniz kullanabileceğiniz bir sınıftır. Belirlediğiniz süre dolduğu zaman ana fonksiyon(main) dışında bir callback içerisinde yazdığınız işlemleri gerçekleştirir ve ana fonksiyona geri döner. Ancak bu kesinlikle paralel bir işlem değildir. Senkron bir olaydır. İşlemler yine sıra ile gerçekleşir. Bu yüzden callback içerisinde uzun sürecek işlemler yapılması çok tavsiye edilmez. Ticker’ ı biz 500ms’ de bir Nextion ekran üzerindeki değerleri güncellemek için kullanacağız. Timer sınıfı ise süre ölçmek için oluşturulmul bir sınıf. Yapacağınız işlem için geçen süreyi saniye, milisaniye veya mikrosaniye cinsinden timer ile ölçebilirsiniz. Biz timer’ ı Nexiton’ dan veri okurken veri kaçırılması durumunda timeout yapabilmek için kullanacağız.

Main fonksiyon içerisinde ayarlamalarımızı yapalım. Ana fonksiyonun başlangıcında uart ayarlamalarımızı yapıp, timer’ ı ve ticker’ ı ayarlamalıyız.

  pc.baud(115200);      //pc uart comm baud rate
  nex.baud(9600);       //nextion uart comm baud rate
  t1.start();           //start timer
  pageID = 0;           //set first page id 
  
  //callback for nextion uart receive interrupt
  //when data comes from uart this callback function will call
  nex.attach(&nexReceiveCallback, Serial::RxIrq); 

  //set ticker callback update time to 0.5 ms
  sendValTicker.attach(&sendValCallback, 0.5);

Bu kısımda ek olarak pageID değişkeni mevcut. Bu önemli bir değişken olduğu için buraya koydum. Bu değişken Nextion ekran üzerindeki widget’ larda güncelleme yaparken hangi sayfada olduğumuzu tutan değişkendir. Daha açık anlatacak olursam, siz ikinci sayfaya geçince nextion ekrana hala ilk sayfadaki bir widget’ ı değişmek için komut göndermeye çalışırsanız ekran size sürekli olarak hata komutu gönderir. Bu çok istenmeyen bir durumdur. Bunun önüne geçmenin en kolay yolu budur. Şimdi ticker sınıfımızın callback fonksiyonuna bakabiliriz.

void sendValCallback()
{
  //if uart receive callback is using uart to read data return to main function
  if(isUARTBusy) return;

  //check which nextion page is showing now
  switch(pageID)
  {
    //if page0 showiing send t0 and t1 update value
    case 0:
      nex.printf("t0.txt=\"%d\"%c%c%c", analogVal1, 0xFF, 0xFF, 0xFF);
      nex.printf("t1.txt=\"%d\"%c%c%c", analogVal1, 0xFF, 0xFF, 0xFF);
      break;
    case 1:
    //if page 1 showing sed t0 and j0 update value
      nex.printf("t0.txt=\"%d\"%c%c%c", analogVal1, 0xFF, 0xFF, 0xFF);
      nex.printf("j0.val=%d%c%c%c", analogVal1, 0xFF, 0xFF, 0xFF);
      break;
  }
}

Üstteki yazılımda yine dikkat edilmesi gereken bir nokta mevcut. Gördüğünüz gibi callback fonksiyonun başında UART kullanılıyormu diye kontrol ettim. UART aslında asenkron fullduplex bir haberleşme protokolüdür. (Yani veri okurken aynı zamanda veri gönderebilir) Ancak burada nextion’ a veri gönderirken widget’ lardan gelen eventleri okumaya çalışmak bazı eventlerin yakalanmamasına sebebiyet verebiliyor. Daha açık hali, siz Nextion ekran’ da bir text’ i sürekli güncellerken butona basıldı mı diye kontrol etmek isterseniz bazen algılama sorunları olabiliyor. Nextion içerisindeki yazılımdan kaynaklı olduğunu düşündüğüm bu sorun çok can sıkıcı olabiliyor.  Bunun dışında printf’ in kullanımına dikkat etmelisiniz. Komutun sonunda gönermemiz gerken 3 adet 0xFF komutunu karakter olarak göndermeliyiriz. Aksi takdir NExtion yanlış parselleme yapacak ve size sürekli hata komutu gönderecektir. Şimdi sıra geldi uart interrupt’ ına. Uart Interrupt’ ında gelen data yı okuduğumuz kısmı ele alacak olursak,

  while(buffIndex != 8)
  {
    if(nex.readable())
    {
      nexBuffer[buffIndex] = nex.getc();
      if(nexBuffer[buffIndex] == 0xFF) EOCcnt++;
      buffIndex++;
    }

    //if 0xFF comes 3 times break
    if(EOCcnt == 3)  break;

    //if wait time exceeds 50ms break
    if(t1.read_ms() > 50) break;
  }

Bu kısımda nelere dikkat etmeliyiz. Öncelikle dizimiz “buffIndex” değişkeni 8 olana kadar dönüyor. Sebebi ise yine Nextion instruction set’ ten kaynaklı. Yukarıda bağlantısını veridğim dökümanda 27. sayfaya bakarsanız Nextion’ un size gönderebileceği verileri ve uzunluklarını görebilirsiniz. Dökümanda en uzun data 9 byte uzunlukta ancak onu kullanmadığım için ben 8 olarak tanımladım. Hangilerini kullandım, “0x65” ile başlayan “touch event return data” ve “0x71” ile başlayan “numeric variable data returns”.  0x65 ile başlayan veri 7 byte, 0x71 ile başyana veri ise 8 byte uzunlukta. Verinin bir tanesi 7 byte uzunlukta olduğu için 3 adet 0xFF gelmiş mi kontolü de yapmam gerekiyor. Aksi taktirde döngü her zaman 8 byte veri okumaya çalışır. Peki bunlar yeterli mi? Elbette hayır. Burada bir döngü oluşturduk ve bu döngü 8 btye veri okuyana yada 3 adet 0xFF gelene kadar dönsün istiyoruz. Ancak arada kaçan byte’ lar olabilir. Bu da yazılımımızın o döngü içerisinde sonsuza dek kalmasına sebep olur. Bunu önlemek için bir timeout oluşturmalıyız. Bunuda timer ile sağlıyoruz. Daha önce de bundan bahsetmiştik.

Burada kullandığımız gibi interrupt içerisindeki döngülü bir yapı olması aslında çok tavsiye edilen bir kullanım şekli değilr. Bizim okumak istediğimiz veri çok kısa olduğu için sorun oluşturmamakta ancak ESP8266 gibi cihazlar ile işlem yaparken bu döngü asenkron olarak kullanılmalı ve gelen veri büyük bir tampon tutulmalıdır. Nasıl yapılacağına bağlantıdaki konuyu inceleyerek görebilirsiniz. Bu proje için böyle bir çözüm kullanmayı tercih ettim. Şimdi sıra geldi gelen veriye göre işlem yapmaya. Öncelikle gelen veri 0x65 ile başlıyor yani bir “touch event” ise ne yapmalıyız ona bakalım.

//check first byte and last 3 bytes 
  //if first byte equal to 65 and data length equal 7
  if(nexBuffer[0] == 0x65 && nexBuffer[4] == 0xFF && 
        nexBuffer[5] == 0xFF && nexBuffer[6] == 0xFF)
  {
    isParsed = true;

    //second byte carries widget ID number
    widgetID = nexBuffer[2];
    //third byte carries touch or touch release event
    readVal = nexBuffer[3];

    //debug values from pc serial terminal
    pc.printf("%d %d %d\n\r", pageID, widgetID, readVal);

    //check page change button pushed
    if(widgetID == 6 && readVal == 1 && pageID == 0)
    {
      pageID = 1;
    }else if(widgetID == 2 && readVal == 1 && pageID == 1)
    {
      pageID = 0;
    }
  }

Üstteki yazılımın son kısmında hangi sayfa değiştirme işlemlerini de algıladık. Bunu burada yapmamızın sebebi main’ e geri döndüğümüzde eğer sayfa değişmişse ticker ile yeni veri gönderme işlemini yeni sayafaya göre yapmamız gerekiyor olması. Yani o işlemi main içerisinde yapsak. Interrupt çıktığımız anda sayfa değişikliğini algılama yazılımına sıra gelmeden ticker interrupt’ ı çalışabilir ve eski sayfa için veri gönderebilir. Bu da hataya sebebiyet verecektir. Peki hangi sayfa değişmesini nasıl anlıyoruz. Örneğin ilk sayfadan ikinci sayfaya geçmek için butona basmışsak o butonun bilgileri seri porttan gelecektir. Bizde seri porttan veriyi alınca gelen veri butondan mı geldi diye kontrol edersek sayfa değişimini algılamış oluruz.

Yazılımdan da anlaşılacağı üzere ilk byte’ ı ve son 3 byte formata uygun mu diye kontrol ettik. Eğer uygunsa veri dizisi içerisinden ihtyiacımız olan verileri alabiliriz demektir. Peki ya ihtiyacımız olan veriler neler? “touch event” i gönderen widget’ ın ID’ si ve gelen event dokunma mı yoksa bırak ma eventi mi olduğu bilgisi. Bunları alıp birer değişkende tutuyoruz. İkinci olarak de verimiz “get” eventine gelen bir cevap mı diye kontrol etmemiz lazım. Bunun için de şu şekilde bir kontrol gerçekleştirebiliriz.

//if first byte equal 0x71 and data length equal 8
  else if(nexBuffer[0] == 0x71 && 
      nexBuffer[5] == 0xFF && nexBuffer[6] == 0xFF && nexBuffer[7] == 0xFF)
  {
    //if checkbox pressed
    if(getCheckBox)
    {
      //second byte carries widget value
      sliderVal = nexBuffer[1];
      ledPwm = (float)(sliderVal / 100.0);
      pc.printf("slider %d\n\r", sliderVal);
      getCheckBox = false;      //set checkbox flag false
    } //if slider pressed
    else if(getSlider)
    {
      //second byte carries widget value
      checkBoxVal = nexBuffer[1];
      pc.printf("checkbox %d\n\r", checkBoxVal);
      getSlider = false;      //set slider flag false
    }
  }

Bu kısımda ise ilk byte 0x71 ve son 3 byte 0xFF mi diye kontrol ediyoruz. Eğer bu şekilde ise bu bir get eventi cevabıdır. Yani slider’ ın yada checkbox widget’ inin cevabı dönmüş demektir. Ancak burda neyin cevabı geldiği veri paketinde yazmıyor. Bunu anlamamızın tek yolu get eventi gönderdiğimizde gönderdiğimiz eleman için yazılımda bir bayrak set’ lemek. Böylece cevap geldiği zaman bayrakları tutan değişlenlere bakıp hangi widget eventi geldiğini anlayabiliriz. Şimdi sıra geldi döngü kısmına.

Ana döngüde potansiyometre değerlerini okuyup, seri porttan gelen veride yaptığımız veri ayırma işlemine göre mikrodenetleyicide çıkışlar üretebiliriz. Ben çıkış olarak basit olması için led kullandım.

while(1) {
    // put your main code here, to run repeatedly:
    analogVal1 = aIn1.read() * 100;
    analogVal2 = aIn2.read() * 100;

    led4 = checkBoxVal;
    
    if(isParsed)
    {
      //button1
      if(pageID == 0 && widgetID == 2 && readVal == 0)
      {
        led1 = !led1;
      } //butonn2
      else if(pageID == 0 && widgetID == 3 && readVal == 0)
      {
        led3 = !led3;
      } //slider
      else if(pageID == 1 && widgetID == 7 && readVal == 0)
      {
        while(isUARTBusy);
        nex.printf("get h0.val%c%c%c", 0xFF, 0xFF, 0xFF);
        getCheckBox = true;
      } //checkbox
      else if(pageID == 1 && widgetID == 4 && readVal == 1)
      {
        while(isUARTBusy);
        nex.printf("get bt0.val%c%c%c", 0xFF, 0xFF, 0xFF);
        getSlider = true;
      }



      isParsed = false;
    }

  }

Ana döngüde dikkat edilmesi gereken yer işlem yapmadan önce bir bayrak değişken kullandım. Bu işlemin sadece bir keren yapılmasını sağlamak için gerekli. Eğer o bayrak orda olmazsa her döngü dönmesinde veri dizisinin içerisi boşaltılmamışsa aynı işlemleri yapıp durur. Eğer bayrak setlenmiş yani veri güncellenmişse, dizideki verileri kontrol ederek gelen “touch event” in hangi widget’ dan geldiğini seçiyoruz ve gerekli işlemi yaptırıyoruz.

Capture1Capture2

STM32 ile Nextion kullanımı hakkında anlatacaklarım genel olarak bu kadar. Biraz uzun bir konu oldu. Ancak en çok içeriği tek bir konuda açık bir şekilde toplamak istedim. Yazılımın tamamına bağlantıdan ulaşabilirsiniz. Nextion Dosyasının güncellenmiş hali bağlantıda mevcuttur.

İyi çalışmalar dilerim…

 

2 Comments

Add a Comment

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