[DS18B20] Prosta obsługa sensora temperatury DS18B20 (1 wire) w prockach AVR

Pozostałe układy mikrokontrolerów, układy peryferyjne i inne, nie mieszczące się w powyższych kategoriach.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[DS18B20] Prosta obsługa sensora temperatury DS18B20 (1 wire) w prockach AVR

Postautor: gaweł » niedziela 14 sty 2018, 00:47

1w_ilu00.jpg


W pewnym sensie tytuł postu jest trochę mylący, gdyż metodologia obsługi tego układu jest niezależna od zastosowanego procka. Użyłem jedynie procka AVR do prezentacji algorytmu (który można z łatwością przenieść na dowolny inny) i przykładowy program jest na AVR'a.

Dużą popularnością w zastosowaniach związanych z pomiarami temperatury cieszy się układ scalony oferowany przez firmę Dalls o symbolu DS18B20. Z punktu widzenia sterowania jest to element cyfrowy (sterowanie do układu oraz odczyt danych odbywa się w sposób cyfrowy) pozwalający na pomiar temperatury, w którym stosowana jest magistrala 1 wire (komunikacja po jednym drucie). Ten sposób przesyłania danych jest stosowany w wielu wynalazkach Dallas'a, toteż poniższy algorytm może być po ewentualnie koniecznych korektach zastosowany do obsługi innych układów scalonych, w których stosowany jest ten sam sposób komunikacji.
Schemat układu badawczego przedstawia następujący rysunek 1 oraz 2.
1w_ilu01.png
Jest to typowa aplikacja mikrokontrolera ATMEGA88 zawierająca niezbędne elementy (układ resetu, przyłącze do programatora, obwód generator kwarcowego) oraz aplikację układu DS18B20, gdzie istotnym elementem jest rezystor R201, którego zadaniem jest wymuszanie stanu logicznej jedynki na linii danych magistrali 1 wire w sytuacji, gdy żadna strona (mikrokontroler lub czujnik tempreatury) nie wymusza stanu logicznego zera.
1w_ilu02.png
Rysunek 2 pokazuje implementację interfejsu szeregowego zrealizowanego na bazie popularnego układu MAX232 ze złączem DB9 (żeńskim) do bezpośredniego połączenia z komputerem (lub innym systemem nadrzędnym).
Przykładowy program do obsługi czujnika temperatury jest pokazany niżej. W programie tym do odmierzania wybranych interwałów czasowych używany jest licznik/zegar 1 generujący przerwanie po przepełnieniu 16-bitowego licznika. Zmierzona wartość temperatury jest wysyłana poprzez układ asynchronicznej transmisji szeregowej.

Kod: Zaznacz cały

#define _1WirePort                      PORTB
#define _1WireDirPort                   DDRB
#define _1WirePin                       PINB
#define _1WireDataPin                   0              // PORTB.0
Parametryzacja programu określająca przyłączenie czujnika DS18B20 do mikrokontrolera, zdefiniowane zostają:
  • 1WirePort – port, do którego przyłączony jest czujnik
  • 1WireDirPort – port określający rejestr sterujący kierunkiem (wejście/wyjście) portu, do którego przyłączony jest czujnik,
  • 1WirePin – port, z którego należy odczytywać dane z magistrali 1 wire,
  • 1WireDataPin – wskazanie na pin w obrębie portu, do którego przyłączony jest czujnik.
Powyższe rozwiązanie pozwala w sposób elastyczny zmieniać miejsce przyłączenie czujnika temperatury w zasobach mikrokontrolera.

Kod: Zaznacz cały

#define Set_1WireAsInput      _1WireDirPort&=~((uint8_t)1<<_1WireDataPin)
#define Set_1WireAsOutput     _1WireDirPort|=(uint8_t)1<<_1WireDataPin
#define Set_1WireLow          _1WirePort&=~((uint8_t)1<<_1WireDataPin)
#define Set_1WireHigh         _1WirePort|=(uint8_t)1<<_1WireDataPin
#define Get_1WirePin          ((_1WirePin)&((uint8_t)1<<_1WireDataPin))
Definicja „makropoleceń” przeznaczonych do sterowania magistralą 1 wire. Taki zabieg pozwala w znacznym stopniu uniknąć prostych pomyłek w programie, co często przekłada się na oszczędność czasu przy uruchamianiu układu. Zdefiniowane zostają instrukcje przeznaczone do:
  • Set_1WireAsInput – do ustawiania odpowiedniego pinu jako wejście sygnału (z punktu widzenia mikrokontrolera),
  • Set_1WireAsOutput – do ustawiania odpowiedniego pinu jako wyjście sygnału (z punktu widzenia mikrokontrolera),
  • 1WireLow – do ustawiania przez mikrokontroler na magistrali 1 wire stanu logicznego zera,
  • 1WireHigh – do ustawiania przez mikrokontroler na magistrali 1 wire stanu logicznej jedynki,
  • 1WirePin – do wczytania bieżącego stanu na magistrali 1 wire.
Warto tu zauważyć, że powyższe instrukcje już zawierają parametryzację przyłączenia czujnika temperatury. Użycie powyższych "makropoleceń" pozwala na uzyskanie "wysokiego poziomu abstrakcji" w programie (zmieniając postać makropoleceń łatwo jest przenieść algorytm na dowolną inną platformę, formalnie w tekście programu nie występuje przywiązanie do modelu procesora).

Kod: Zaznacz cały

static volatile TXDRecT TXDRec ;
static volatile RXDRecT TXDRec ;
static volatile uint8_t TimeEvent ;
static volatile uint16_t _1WireTimeDelay ;
static volatile uint8_t _1WireEvent ;
static volatile uint16_t TimeCounter ;
static uint8_t _1WireAutomatState ;
static int16_t CurrentTemp ;
static uint8_t SensorPresent ;
static uint8_t TempDataValid ;
Wybrane zmienne używane przez program:
  • TimeEvent – flaga sygnalizująca upływ interwału czasowego,
  • 1WireTimeDelay – licznik przerwań do odmierzania interwałów czasowych do wyzwalania pomiaru temperatury,
  • 1WireEvent – flaga sygnalizująca upływ interwału czasowego wyzwalająca akcję dotyczącą czujnika na magistrali 1 wire,
  • TimeCounter – licznik przerwań do odmierzania interwałów czasowych,
  • 1WireAutomatState – zmienna przechowująca aktualny stan automatu używanego przy pomiarze temperatury,
  • CurrentTemp – aktualnie zmierzona wartość temperatury,
  • SensorPresent – flaga sygnalizująca czy do mikrokontrolera przyłączony jest czujnik temperatury,
  • TempDataValid – flaga sygnalizująca ważność danych pomiarowych.

Kod: Zaznacz cały

SIGNAL ( TIMER1_OVF_vect )
{
  /*-------------------------------------------------------------------*/
  _1WireTimeDelay ++ ;
  if ( _1WireTimeDelay > _1WireTimeMaxDelay )
  {
    _1WireEvent = 1 ;
    _1WireTimeDelay = 0 ;
  } /* if */ ;
  TimeCounter ++ ;
  if ( TimeCounter > TimeIntervalCounterLimit )
  {
    TimeCounter = 0 ;
    TimeEvent = TRUE ;
  } /* if */ ;
} /* TIMER1_OVF_vect */

Funkcja do obsługi przerwań od zegara/licznika 1. Jej zadaniem jest generowanie sygnałów (poprzez ustawienie odpowiednich flag) do pomiaru temperatury oraz „konsumpcji” danych pomiarowych. Oczywiście można zrealizować to jako jedno zdarzenie, tu jest implementacja pozwalająca na niezależne (w innym tempie) pomiary temperatury oraz wykorzystanie tych danych pomiarowych.

Kod: Zaznacz cały

void _1WireDelay ( uint16_t DelayTime )
{
  uint16_t StartTimer ;
  uint16_t CurrTimer ;
  /*-------------------------------------------------------------------*/
  StartTimer = TCNT1 ;
  for ( ; ; )
  {
    CurrTimer = TCNT1 ;
    if ( ( CurrTimer - StartTimer ) >= DelayTime )
      break ;
  } /* for */ ;
} /* _1WireDelay */

Funkcja do odmierzania dużych interwałów czasowych. Jej działanie opiera się na sprzętowo inkrementowanym rejestrze licznika/zegara 1. Parametr wywołania DelayTime jest wyrażony w liczbie „tick'ów” licznika (częstotliwość taktująca mikrokontroler z uwzględnieniem preskalera). Algorytm opiera się na znajdowaniu różnicy pomiędzy aktualnym stanem licznika zegara z jego stanem w momencie wejścia do funkcji (obliczanie różnicy daje poprawny wynik nawet w sytuacji, gdy w międzyczasie nastąpiło przepełnienie stanu licznika). Ze względu na duże wartości parametru wywołania (przykładowo do odmierzenia 480 μs wartość parametru wyrażona jest w tysiącach) do odmierzania jest używany licznik 16-bitowy.

Kod: Zaznacz cały

static uint8_t _1WireTempSensorReset ( void )
{
  uint8_t PortData ;
  /*-------------------------------------------------------------------*/
  Set_1WireAsOutput ;
  Set_1WireLow ;
  _1WireDelay ( TimerDelay480us ) ;
  Set_1WireAsInput ;
  _1WireDelay60us ( ) ;
  PortData = Get_1WirePin ;
  _1WireDelay ( TimerDelay150us ) ;
  return ( PortData ) ;
} /* _1WireTempSensorReset */
Funkcja do resetowania czujnika temperatury. Operacja ta wymaga określonego wysterowania magistrali 1 wire. Wynikiem funkcji jest informacja logiczna (tak/nie), czy na magistrali 1 wire fizycznie znajduje się czujnik (dokładnie, wynikiem funkcji jest stan na magistrali 1 wire, czyli 0 oznacza, że czujnik jest obecny). Jej działanie odzwierciedla rysunek 3.
1w_ilu03.PNG
Sprowadza się to do:
  • przełączenia wyprowadzenia, gdzie przyłączona jest magistrala 1 wire do stanu „wyjście”,
  • wymuszenia stanu logicznego zera,
  • odczekanie 480 μs.
  • Elementy na rysunku 3 oznaczone kolorem.
  • przełączenia wyprowadzenia, gdzie przyłączona jest magistrala 1 wire do stanu „wejście”, co jednocześnie wprowadza stan pasywny na magistrali 1 wire,
  • odczekania interwału czasowego trochę większego od 60 μs, by znaleźć się w obszarze(rysunek 3),
  • odczytania stanu magistrali 1 wire (jeżeli jest przyłączony czujnik, to on wymusi stan logicznego zera, jeżeli czujnika nie ma, to .... nikt nie wymusi),
  • odczekania resztę czasu.

Kod: Zaznacz cały

static void _1WireTempSensorBitWrite ( uint8_t BitData )
{
  /*-------------------------------------------------------------------*/
  cli ( ) ;
  Set_1WireAsOutput ;
  Set_1WireLow ;
  _1WireDelay15us ( ) ;
  if ( BitData )
  {
    Set_1WireAsInput ;
  } /* if */ ;
  _1WireDelay60us ( ) ;
  Set_1WireAsInput ;
  sei ( ) ;
  _1WireDelay1us ( ) ;
} /* _1WireTempSensorBitWrite */

Funkcja do zapisu sekwencji sterującej do czujnika temperatury. Jej działanie odpowiada wymogom opisanym w odpowiedniej dokumentacji.
1w_ilu04.PNG
Wykonanie tych operacji realizowane jest przy zablokowanych przerwaniach by nie zaburzać pomiaru czasu dla małych interwałów czasowych. Sprowadza się to do:
  • wysterowania pinu, do którego przyłączony jest czujnik jako wyjścia,
  • wymuszenia stanu logicznego zera,
  • odczekania 10 μs czasu,
  • w przypadku gdy na magistralę 1 wite zapisywana jest logiczna jedynka, należy „odpuścić dla drutu 1 wire” (przełączyć pin sterujący na wejście, co spowoduje wymuszenie napięcia na magistrali jedynie przez rezystor),
  • w przeciwnym wypadku, gdy na magistralę zapisywane jest logiczne 0, wymuszony dotychczas stan jest przedłużany,
  • po odpowiednim wysterowaniu stanu logicznego na magistrali 1 wire realizowane jest odczekanie reszty czasu przeznaczonego na transmisję pojedynczego bitu na magistralę 1 wire,
  • odczekanie czasu 1 μs czasu jako odstęp między poszczególnymi bitami.

Kod: Zaznacz cały

static uint8_t _1WireTempSensorBitRead ( void )
{
  uint8_t PortData ;
  /*-------------------------------------------------------------------*/
  cli ( ) ;
  Set_1WireAsOutput ;
  Set_1WireLow ;
  _1WireDelay1us ( ) ;
  Set_1WireAsInput ;
  _1WireDelay15us ( ) ;
  PortData = Get_1WirePin ;
  sei ( ) ;
  _1WireDelay45us ( ) ;
  if ( PortData )
    return ( 1 ) ;
  else
    return ( 0 ) ;
} /* _1WireTempSensorBitRead */
Funkcja do odczytu danych z magistrali 1 wire. Jej działanie odpowiada wymogom opisanym w odpowiedniej dokumentacji.
1w_ilu05.PNG
Wykonanie tych operacji realizowane jest przy zablokowanych przerwaniach by nie zaburzać pomiaru czasu dla małych interwałów. Sprowadza się to do:
  • wysterowania pinu, do którego przyłączony jest czujnik jako wyjścia,
  • wymuszenia stanu logicznego zera,
  • odczekania 1 μs czasu,
  • przełączenia pinu, do którego przyłączony jest czujnik jako wejście do mikrokontrolera,
  • odczekanie kolejnych 15 μs czasu,
  • odczytanie stanu z magistrali 1 wire,
  • odczekanie reszty czasu przeznaczonego na odczyt pojedynczego bitu.

Kod: Zaznacz cały

static void _1WireTempSensorWrite ( uint8_t Data )
{
  uint8_t Loop ;
  /*-------------------------------------------------------------------*/
  for ( Loop = 0 ; Loop < 8 ; Loop ++ )
  {
    _1WireTempSensorBitWrite ( Data & 0x01 ) ;
    Data = Data >> 1 ;
  } /* for */ ;
  _1WireDelay5us ( ) ;
} /* _1WireTempSensorWrite */

Funkcja do zapisu bajtu na magistralę 1 wire jako złożenie ciągu zapisów pojedynczych bitów. Bity zapisywane są zaczynając od pozycji najmniej znaczącej.

Kod: Zaznacz cały

static uint8_t _1WireTempSensorRead ( void )
{
  uint8_t Data ;
  uint8_t Loop ;
  /*-------------------------------------------------------------------*/
  Data = 0 ;
  for ( Loop = 0 ; Loop < 8 ; Loop ++ )
  {
    Data = Data >> 1 ;
    if ( _1WireTempSensorBitRead ( ) )
      Data |= 0x80 ;
  } /* if */ ;
  return ( Data ) ;
} /* _1WireTempSensorRead */

Funkcja do odczytu bajtu z magistrali 1 wire jako złożenie ciągu odczytów pojedynczych bitów. Odczytane bity zapisywane są w lokalnej zmiennej zaczynając od pozycji najmniej znaczącej.
Pokazane operacje pokazują obsługę termometru na magistrali 1 wire. Wszelkie inne szczegóły są zawarte w przykładowym programie.
DS19B20.zip

Po zaprogramowaniu mikrokontrolera można za pomocą programu HyperTerminal przechwycić strumień przychodzących danych do pliku na dysku.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Wróć do „Inne mikroklocki, również peryferyjne”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 5 gości