Arduino'da 8 byte'lık veriyi float'a çevirme

Başlatan burç tankal, 19 Eylül 2018, 13:55:54

burç tankal

İyi günler arkadaşlar. Bir cihazdan modbus üzerinden arduino nano ile veri alıyorum.16 ve 32 bitlik verileri sıkıntısız işleyebiliyorum. Ancak almak istediğim bir tane veri 64 bit double formatında. Modbus üzerinden 8 farklı byte'ı bir dizine atıyorum ancak bu değerleri kullanabileceğim, karşılaştırma yapabileceğim, yazdırabileceğim ve aritmetik işleme sokabileceğim bir formata (Float) çevirmeyi başaramadım. Nano'da float değişkeni 32 bit, sadece duo 64 bit destekliyor ve ben nano kullanmalıyım. Aşağıdaki aldığım ham verileri float'a çevirmenin bir yolu var mıdır? Çözünürlük kaybetmeye razıyım.

veriler: 0x3F, 0xC6,  0x94, 0x31, 0x7A, 0xCC, 0x4E, 0xF8

elde etmek istenen: 0.176



Endorfin35+

Sorunuzun cevabini bilmiyorum ama arduinoda en basitinden float bir a degiskeni icin a=3/2 isleminden 1.5 sonucunu alamadim. Sonuc almak icin a=3.0/2 veya a=3/2.0 gibi islem yapmak gerekiyordu. Belki denediginiz islemlerde boyle basit bir yerden takikiyorsunuzdur.
"İşi bilen yapar, az bilen akıl verir, bilmeyen eleştirir, yapamayan çamur atar."

ziyaretci

#3
@Endorfin35+ bilgisayarcada iki tam sayıyı birbirine bölerek veya çarparak float değer elde edemezsiniz. Bilgisayar iki tam sayının sonucunu yine tam sayı olarak kayıt eder. 3/2=1 der, 5/2=2 der. İki tam sayının odalık kısmı yoktur. Bilgisayar iki tam sayının işlem sonucu tam sayıdır der. Bu yüzden float, değişkeni tam sayı olarak görür ve sadece tam kısmını yazar. Biz 1.0 ile çarparak(değer değişebilir) sonucun float olmasını sağlarız. Tam sayı ile ondalıklı sayının işlem sonucu ondalıklı sayıdır.

Böylelikle doğru sonucu bu işlemi yaptıktan sonra alırsınız.

Diyeceksin; "peki koca koca sistemler tasarlamışlar bu durumu neden düşünmemişler? Derleyiciler iki tam sayının işlem sonucunun ondalıklı sayı olabileceğinide tanımlasalarmış." 

Eğer bu olasılığı hesaba katsalardı integer diye bir değişken türü olmazdı. Ve benim sistemim sadece tam sayı işlem yapan bir sistem olsaydı fazladan hafıza harcayacaktım.

Eğer dersen ki; "derleyici bilmiyor mu 3'ün 2'ye tam bölünmeyeceğini, ona göre bir hafıza tipi ayarlasın." İsterse yapılır. Ama derleyici bu sefer senin amacını bilmez. Belki sen bu 3/2 değerinin sonucunu tam sayı olarak almak istiyorsun. O zaman derleyiciye, sen bana sormadan kafana göre nasıl işlem yapıyorsun dersen derleyici üzülmez mi? Üzülür çünkü kızdın ona. Kaldıki bu iki değer dışarıdan(sensör) alınan bir değişkene bağlıysa? Derleyici bunu nasıl bilecek?

Bilgisayar aptal bir makinedir. Bütün olasılıkları bizim hesaplamamıza, ön görmemize muhtaçtır.

Dersen ki olmaz öyle şey "bilecek!".

 İşte ona insanın gücü yetmez, ruh ve akıl veremezsin makineye.

Arkadaş bahsettiğiniz sıkıntıyı sormuyor. Ben diyor 64 bit veri alıyorum 16 ve 32 bit için hazır dönüşüm algoritması var (%f, %u, %Lu gibi) ama 64 bit için yok. Yani derleyicim desteklemiyor, bilmiyor.  Ben bu 64 bit olan parçalanmış double değişken dizisini nasıl istediğim formata dönüştürürüm diyor.

burç tankal

Sayın erkantr67,  tam olarak yazınızın sonunda ifade ettiğiniz sorunla uğraşıyorum. Verdiğiniz linkte, girilen 8 byte veri doğru olarak dönüştürülüyor ancak bunu arduino üzerinde nasıl yapacağımı hala bulamadım. Sanırım benim sorunum IEEE754 konusuyla alakalı ve bu konu benim için epey yeni. Şimdiye kadar compiler benim için bu işlemleri yapıyordu. Ancak 64 bitlik "sign" "exponent" "fraction" gibi taşıyıcıları nasıl 32 bite çeviririm en ufak bir fikrim yok. En azından bir yön gösterebilirseniz çok memnun kalırım.

ziyaretci

#5
@burç tankal  ben gerisini halledersiniz diye detaya girmedim. Öncelikle hiç arduino kullanmadım. İlgili derleyici üzerinden bilgi veremem ama C üzerinden yapmanız gerekenleri açıklamaya çalışırım.

Öncelikle 8 byte veriyi bir dizi tamponuna yerleştirdiğinizi varsayıyorum. Yanlış anlamadıysam bunda sıkıntı yok. Şimdi size attığım fotoğraf ve bağlantı aslında algoritmayı bize vermiş.

Şimdi resime bakarsak ilk 12 bit işaretli bir tam sayı(aldığınız ilk verinin tamamı ve 2. verinin son 4 biti). Öncelikle 2. alınan verinin ilk 4 biti ile son 4 bitini ayırmalıyız. Ayırdıktan sonra ayırdığımız 2. verinin yüksek değerlikli 4 bitinin en düşük değerinden başlayarak tânımladığınız işaretli tam sayının ilk 4 bitine yerleştirin. Ardından ilk aldığınız verinin 0. bitinden 6. bitine(dahil) kadar yerleştirmeye devam edin. İlk aldığınız verinin 7. Biti tanımladığınız değişkenin 15. biti olacak.

Bunu bir kenera aldık. Şimdi ikinci olarak ondalıklı kısmı float formda yazmamız lazım. Bunun için ayırdığımız 2. verinin 4 bitinden başlayarak 4 bit, 4 bit ters bcd uygulayacağız.

Bunun için 13 elemanlı bir diziye ihtiyacımız var. Her 4 bitin decimal karşılığını soldan sağa yerleştireceğiz. Sonra dizideki her bir elemanı aşağıdaki işleme tabi tutacağız.

ondalik_float+=(1.0)*Dizi[i]*16^((-1)*(i+1));

Ve son olarakta ilk tanımladığımız işaretli tam sayı ile ondalik_float'ı çarpıp sonuç float değişkenimize yazacağız.

Algoritma bu. Şimdi bilgisayarı açıyorum, fonksiyonuda yazıp paylaşacağım.

ziyaretci

#6
Aşağıdaki bağlantıdan c(vc) dilini seçerek test edebilirsin.
http://rextester.com/l/c_online_compiler_visual

//gcc 5.4.0

#include  <stdio.h>
#include <math.h>

int gelen_veri[8]={0x3F, 0xC6,  0x94, 0x31, 0x7A, 0xCC, 0x4E, 0xF8};

float float_sonuc(int *veri)
{
  int ondalik_donusum[13]={0};
  int ilk_islem_degiskeni=0;
  int isaret=0, i=0;
  float ondalik_taraf=0.0;
  
  if(veri[0]&0x80)
      isaret=1;
  else               // İsterseniz 
      isaret=0;   // burayı kaldırabilirsiniz.
  
  veri[0] = veri[0]&127;
  
  ilk_islem_degiskeni += veri[0];
  ilk_islem_degiskeni <<= 4;
  ilk_islem_degiskeni += veri[1]>>4;
  
  ondalik_donusum[0] = veri[1]&0x0f;   // 2. bilginin ayırma işlemi tamamlandı.0xc6
  for(i=0;i<6;i++)
  {  
      ondalik_donusum[(2*i)+1] = veri[i+2] >> 4;
      ondalik_donusum[(2*i)+2] = veri[i+2] & 0x0f;    
  } //ondalık kısmın dönüşümü tamamlandı.
  
  //16'lık tabandan 10'luk tabana dönüşüm yapacağız.
  for(i=0;i<13;i++)
       ondalik_taraf += (1.0)*ondalik_donusum[i]*(1/(pow(16,(i+1))));
    
  //blok 1(Hafızadan tasarruf için bu bloğu kullanabilirsin yalnız blok 2'yi kaldır.(sil)
  /*
    ondalik_taraf = ((pow(2,(ilk_islem_degiskeni-1023))))*(1 + ondalik_taraf); 
    if(isaret==1)
        return (-1.0)*ondalik_taraf;
    else
        return (1.0)*ondalik_taraf;
  */  

  //blok 2  
  if(isaret==1)
       return (-1.0)*((pow(2,(ilk_islem_degiskeni-1023))))*(1 + ondalik_taraf);
  else
       return (1.0)*((pow(2,(ilk_islem_degiskeni-1023))))*(1 + ondalik_taraf);
  //blok 2 sonu
}

int main()
{
    printf("%f",float_sonuc(gelen_veri));
}

burç tankal

Sayın Hocam; ellerinize emeğinize sağlık. online compiler üzerinde sorunsuz çalıştı. Siz bu dönüşümün nasıl mümkün olacağını ifade ettiğinizde ben de araştırmamı o yönde gerçekleştirdim ve aşağıdaki fonksiyona ulaştım. Sanırım sizin bahsettiğiniz mantıkla işliyor. Ancak çalıştırabilmek için dn dizinin eleman sırasını terslemem gerekti. örneğin dn[0] yazan yer orjinalde dn[7]'ydi. Sanırım endian formatıyla ilgili bir durum. Fonksiyonla ilgili fikirlerinizi söylerseniz çok memnun olurum. Yardımlarınız için çok teşekkürler.

float conv(byte *dn) 
{
   union {
       float f;
       byte b[4];
   } fn;    
   int expd = ((dn[0] & 127) << 4) + ((dn[1] & 240) >> 4);
   int expf = expd ? (expd - 1024) + 128 : 0;
   fn.b[3] = (dn[0] & 128) + (expf >> 1);
   fn.b[2] = ((expf & 1) << 7) + ((dn[1] & 15) << 3) + ((dn[2] & 0xe0) >> 5);
   fn.b[1] = ((dn[2] & 0x1f) << 3) + ((dn[3] & 0xe0) >> 5);
   fn.b[0] = ((dn[3] & 0x1f) << 3) + ((dn[4] & 0xe0) >> 5);

  return fn.f;
}

ziyaretci

#8
Alıntı yapılan: burç tankal - 20 Eylül 2018, 02:36:54
float conv(byte *dn) 
{
  union {
      float f;
      byte b[4];
  } fn;    
  int expd = ((dn[0] & 127) << 4) + ((dn[1] & 240) >> 4);
  int expf = expd ? (expd - 1024) + 128 : 0;
  fn.b[3] = (dn[0] & 128) + (expf >> 1);
  fn.b[2] = ((expf & 1) << 7) + ((dn[1] & 15) << 3) + ((dn[2] & 0xe0) >> 5);
  fn.b[1] = ((dn[2] & 0x1f) << 3) + ((dn[3] & 0xe0) >> 5);
  fn.b[0] = ((dn[3] & 0x1f) << 3) + ((dn[4] & 0xe0) >> 5);

  return fn.f;
}

İlk 3 byte'ın tam kısmını ayırmış, bunu bizde yaptık.
int expd = ((dn[0] & 127) << 4) + ((dn[1] & 240) >> 4);

Ayırılan tam kısım sıfırdan büyük bir değer ise ayırılan tam sayıdan 1024 çıkarmış 128(7. biti 1 yapmış) eklemiş, sıfır . Bundan sonrasında ne yaptığını pek anlayamadım. Yapıdaki f değişkenini kullanmamış ama f ile dönmüş. Belki farklı bir algoritmadır ama f hiç kullanılmıyor. Veya bilmediğim farklı bir durum var.
int expf = expd ? (expd - 1024) + 128 : 0;

Açıkçası anlam veremedim.

Bizim fonksiyonu biraz daha kısalttım.
//gcc 5.4.0

#include  <stdio.h>
#include <math.h>

int gelen_veri[8]={0x3F, 0xC6,  0x94, 0x31, 0x7A, 0xCC, 0x4E, 0xF8};

float float_sonuc(int *veri)
{
  int ilk_islem_degiskeni=0;
  int isaret=0, i=0;
  float ondalik_taraf=0.0;
  
  if(veri[0]&0x80)
      isaret=1;
  
  veri[0] = veri[0]&127;
  
  ilk_islem_degiskeni += veri[0];
  ilk_islem_degiskeni <<= 4;
  ilk_islem_degiskeni += veri[1]>>4;

  ondalik_taraf = (veri[1]&0x0f)*0.0625;  
  for(i=0;i<6;i++)
  {  
      veri[0]=veri[i+2] >> 4;
      ondalik_taraf += (1.0)*(veri[0])*(1/(pow(16,((2*i)+2))));
      veri[0]=veri[i+2] & 0x0f;
      ondalik_taraf += (1.0)*(veri[0])*(1/(pow(16,((2*i)+3))));    
  } //ondalık kısmın dönüşümü tamamlandı.
  
    ondalik_taraf = ((pow(2,(ilk_islem_degiskeni-1023))))*(1 + ondalik_taraf); 
    if(isaret==1)
        return (-1.0)*ondalik_taraf;
    else
        return (1.0)*ondalik_taraf;
    
}

int main()
{
    printf("%f",float_sonuc(gelen_veri));
}