sobota, 2 kwietnia 2011

TIPS & TRICKS - Temperatura - konwersja na wyświetlacz

Autor: Deucalion
Redakcja: Dondu

Na forum często poruszany jest temat odczytu i wyświetlenia temperatury z czujnika np. DS18B20. Wielki problem stwarza początkującym konwersja do postaci string bez użycia operacji zmiennoprzecinkowych, która zabierają niepotrzebnie tak cenną pamięć programu (czasami nawet kilka kB):

oskar777 Czujnik temperatury, prośba z znalezieniu błędu
Float mi potrzebny by odczytać (i wyświetlić) temperaturę z przecinkiem, nie wiem jak zrobić to inaczej.




Jak więc rozwiązać to bez użycia FLOAT i zyskać kB pamięci?

Przypatrzmy się formatowi temperatury udostępnianym przez DS18B20:


LSB - młodszy bajt
MSB - starszy bajt
Z - znak temperatury (1-ujemna, 0-dodatnia) dlaczego aż na tylu bitach? Pytaj projektantów DS18B20  :-)
Bity 0...3 - część ułamkowa
Bity 4...10 - część całkowita


16 bitowa prezentacja formatu temperatury DS18B20 w kodzie uzupełnień do 2 – U2



Odpowiednie bity mają następujące wartości (wagi bitów):



Dodatnia temperatura

Obliczenie dodatniej temperatury polega na dodaniu powyższych wartości dla bitów, które maja wartość równą 1.

Przykład:

Odczytano LSB=0x91 oraz MSB=0x01. Po złożeniu MSB i LSB otrzymujemy:

Odczytany pomiar = 0x0191 = 0b0000 0001 1001 0001

Dodajemy wartości (wagi) bitów zawierających jedynkę:

T = bit8 + bit7+ bit4 + bit0 = 16 + 8 + 1 + 0,0625 = 25,0625°C

Prawda, że proste? :-)



Ujemna temperatura

Do zrozumienia formatu temperatur ujemnych przyda nam się znajomość kodu U2.

Co to jest ten kod U2? 
Jest to format zapisu liczb całkowitych w systemie dwójkowym. Format jest bardzo prosty, liczby dodatnie od 0 do połowy zakresu mają swoje normalne wartości, np. dla liczb 8 bitowych będą to liczby od 0 do 127. Liczby od połowy zakresu do jego końca reprezentują liczby ujemne, czyli dla liczb 8 bitowych będą to liczby od 128 do 255. Im mniejsza wartość tym większa wartość bezwzględna liczby.

Przykłady:

0b01111111 = 127
0b00000010 = 2
0b00000001 = 1
0b00000000 = 0
0b11111111 = -1
0b11111110 = -2
0b10000000 = -128


Z przykładów tych można zaobserwować, że najbardziej znaczący bit wskazuje znak liczby, jeśli ma wartość 1 to liczba jest ujemna i właśnie tak samo jest w formacie temperatury czujnika DS z tym, że znak liczby Z został powielony na kilku bitach, co nie jest do końca prawdą. Jest to kwestia umowna, bo gdyby przyjąć, że znak jest tylko na najstarszym bicie to i tak by to niczego nie zmieniło z punktu widzenia liczby w kodzie U2.

Jak wyciągnąć wartość bezwzględną z liczby ujemnej w kodzie U2? 
Wystarczy liczbę zanegować i dodać 1. Skąd wzięło się te 1? Jest potrzebne, bez tego 1 mamy wtedy dwa zera -0 i +0, a to jest już kod U1.


0b00000000 = +0
0b11111111 = -0

Dodając 1 pozbywamy się drugiego zera i mamy kod uzupełnień do dwóch (U2).

Co oznacza „uzupełnień do dwóch”?
Analogii można szukać w warzywniaku obok. Mamy zapłacić 64zł, dajemy stówę i co robi sprzedawca przy wydawaniu reszty? Uzupełnia do 100 czyli:

64zł + 1zł + 5zł + 10zł + 20zł = 100zł

A jak ma to się do „uzupełnień do 2”? 
Liczba w kodzie U2 może mieć dowolną, większą od 0 ilość bitów. Im więcej bitów tym większe wartości bezwzględne można na takiej liczbie zapisać. Definicja kodu U2 jest oparta na najmniejszej możliwej ilości bitów, czyli dla liczby 1 bitowej. Taka liczba może przyjąć tylko dwie wartości, a z wcześniejszych przykładów wiemy, że na najstarszym bicie zapisany jest znak liczby.


W tym przypadku ten jeden bit jest również najstarszym bitem i reprezentuje znak liczby, więc w kodzie U2 taka liczba przyjmie wartości 0 i -1. Przeliczenie 1 bitowej liczby ujemnej na kod U2 polega na uzupełnieniu bezwzględnej wartości tej liczby do 2 i stąd kod uzupełnień do 2. W praktyce łatwiej jest zastosować odejmowanie.

0U2 = 0
-1U2 = 2 – 1 = 0b1


Dla n bitowej liczby zamiast 2 podstawiamy 2n czyli dla 2 bitowej będzie to tak wyglądać:

1U2 = 0b01
0U2 = 0b00
-1U2 = 4 – 1 = 0b11
-2U2 = 4 – 2 = 0b10


W formacie temperatury z DS18B20 część całkowita została przesunięta o 4 bity w kierunku starszych bitów, a w miejsce tych 4 bitów została wsunięta część ułamkowa, również zapisana w kodzie U2.


Część całkowita w połączeniu z częścią ułamkową tworzą jedność podlegającą zasadom kodu U2, a z zasad tych wynika, że aby uzyskać wartość bezwzględną liczby ujemnej wystarczy całość zanegować oraz dodać 1.



Jak obliczyć część ułamkową?
Część ułamkowa zajmuje 4 bity więc może przyjąć 16 wartości. Każda z tych wartości to 1/16 czyli 0,0625.

Aby uzyskać część ułamkową w postaci liczby całkowitej łatwej do konwersji na tekst, wystarczy pomnożyć wartość tych 4 bitów przez 625. Uzyskana wartość po przetworzeniu na tekst będzie reprezentować część ułamkową temperatury z dokładnością do 1/10000, czyli dla przykładu 25,3125.

Taka dokładność nie jest nam potrzebna, w przypadku temperatury wystarczy jedna lub dwie cyfry po przecinku. Jeśli decydujemy się na jedną cyfrę po przecinku to uzyskaną wartość dzielimy przez 1000 i temperaturę wyświetlamy w postaci X.X°C, jeśli zdecydujemy się na dwie cyfry po przecinku to uzyskaną wartość dzielimy przez 100 i temperaturę wyświetlamy w formacie X.XX°C.


Część całkowita
Część całkowita temperatury z formatu DSa nie wymaga przeliczeń, wystarczą tylko odpowiednie przesunięcia bitowe.



Kod C

Przekonwertujmy teorię na kod.
typedef struct
{
 unsigned char Calkowita;
 unsigned char Ulamek;
 unsigned char Znak;
}TTemperatura;


char * DSTempToStr(char *buf, unsigned char DStempLSB, unsigned char DStempMSB)
{
 char * ret = buf;
 TTemperatura temp;
 
 unsigned short DStemp = (DStempMSB << 8 ) | DStempLSB;
 
 temp.Znak = DStempMSB >> 7;
 if( temp.Znak )
 {
  DStemp = ~DStemp + 1;
 }
 
 temp.Calkowita = ( unsigned char )(( DStemp >> 4 ) & 0x7F );
 temp.Ulamek = ( unsigned char )((( DStemp & 0xF ) * 625) / 100 );
 
 if( temp.Znak )
 {
  *buf++ = '-';
 }
 
 buf = ltoaz( temp.Calkowita, buf, 10, 0 );
 *buf++ = '.';
 buf = ltoaz( temp.Ulamek, buf, 10, 2);
 *buf++ = 'C';
 *buf = '\0';
 return ret;
}

Funkcja ltoaz()
W powyższym kodzie używana jest pomocnicza funkcja ltoaz() do konwersji 32bitowej wartości na tekst:
char * ltoaz( unsigned long val, char *buf, int radix, int dig )
{
 char tmp[ 32 ];
 int i = 0;
 
 if(( radix >= 2 ) && ( radix <= 16 ))
 {
  do
  {
   tmp[ i ] = ( val % radix ) + '0';
   if(tmp[ i ] > '9' ) tmp[ i ] += 7;
   val /= radix;
   i++;
  }while( val );

  while(( i < dig ) && ( i < 32 ))
  {
   tmp[ i++ ] = '0';
  }

  while( i )
  {
   *buf++ = tmp[ i-1 ];
   i--;
  }
 }
 *buf = 0;
 return buf;
}
gdzie:

val – wartość do konwersji
buf – wskaźnik na bufor znaków
radix - liczba unikalnych cyfr (system)
dig – minimalna ilość cyfr (zera wiodące)
return – wskaźnik na koniec stringu



Użycie funkcji DSTempToStr

Jeżeli Twoja funkcja wyświetlająca na LCD wygląda tak:
PrintToLcd( char * buf, int x, int y);
to temperaturę wyświetlisz tak:
char text[10];
PrintToLcd( DSTempToStr( text, DS_LSB, DS_MSB ),x , y );

lukisio
Zastosowałem i sprawdza się super! Oszczędność około 1,5kb


Autor: Deucalion
Redakcja: Dondu

2 komentarze:

  1. Witam serdecznie!

    Z zaciekawieniem przeczytałem cenne informacje odnośnie obsługi DS18B20 - rzeczywiście takie podejście do tematu znacząco zmniejszyło wielkość kody wynikowego - ponad 1,5kB.

    Pozwoliłem sobie conieco zmodyfikować kody i dodać coś od siebie. Tutaj http://stsystem.elektroda.eu/index.php/programowanie/avr/60-ds18b20 zapraszam celem obejrzenia rezultatów moich działań.

    Pozdrawiam :)

    OdpowiedzUsuń
  2. Witam,

    Czy kolega mógłby zamieścić przykład uzyskiwania wyniku w takiej formie żeby dało się go wyświetlić na segmentowym wyświetlaczu led?

    Pozdrawiam

    OdpowiedzUsuń