Moduł dekodera sygnału z bezprzewodowego czujnika temperatury.

czujnik

Mając kilka wolnych chwil postanowiłem skonstruować odbiornik do bezprzewodowego czujnika stacji pogodowej Oregon Scientific RMR203HG. Stacje tej marki są ogólnie dostępne, jeśli chodzi o wrażenia z użytkowania – działa bez zarzutu. Mój model wyposażony jest w pomiar temperatury i wilgotności wewnętrznej, pomiar tych samych parametrów z max 3 czujników zewnętrznych oraz zegar synchronizowany radiowo sygnałem DCF77. Dołączony czujnik posiada symbol THGN132N.

Moduł odbiera dane z bezprzewodowego czujnika i wysyła je w sformatowanej formie poprzez UART.

W Internecie można bez większych problemów znaleźć dokument z  opisem protokołu (Oregon Scientific RF Protocol). Jest to protokół autorski. Zanim przejdę do szczegółowego opisu transmisji danych,  dwa słowa o warstwie fizycznej.

Pod względem fizycznym transmisja odbywa się w paśmie ISM 433,92MHz z modulacją OOK (zwanej niekiedy CW) – czyli bity informacji są kodowane jako impulsy emisji fali (stała częstotliwość i amplituda). Impulsy i przerwy między nimi wyznaczają dwie wartości logiczne. Do odbioru zastosowałem moduł receivera dla częstotliwości 433MHZ, który bez problemu można dostać na znanym portalu aukcyjnym lub w Chinach. Odbiornik taki posiada kilka pinów, są to zasilanie +5V, masa, wejście antenowe i wyjście danych. To ostatnie działa na prostej zasadzie. Stan wysoki jest stanem spoczynkowym i występuje, kiedy nie jest odbierany sygnał radiowy. Kiedy sygnał jest odbierany linia przyjmuje stan niski. Sygnał z tego wyjścia może być już bezpośrednio dekodowany przez mikrokontroler. Dla zasilania +5V stan wysoki na wyjściu ma nieco ponad 4V.

Oregon stosuje 3 znane wersje protokołu, mój czujnik używa wersji v2.1 i dla takiej wersji moduł został skonstruowany. W zasadzie nie ma przeszkód aby moduł dekodował pozostałe wersje protokołu, jednak nie dysponując takimi czujnikami, nie byłbym w stanie wykonać stosowych testów.

Protokół w wersji 2.1 wykorzystuje kodowanie Manchester, praktycznie obowiązkowy dodatek w większości asynchronicznych protokołów radiowych. Dodatkowo każdy bit przesyłany jest dwukrotnie, przy czym za drugim razem przesyłany jest w wersji zanegowanej. Pojawia się tutaj pewna dowolność interpretacji stanów, tzn. przyjęcie za zakodowanie „1” przejścia z 0 na 1 lub odwrotnie może zostać skompensowane przyjęciem pierwszego bitu za zanegowany lub drugiego. Dodatkowo jeszcze sygnał jest negowany przez odbiornik (wyłączone radio daje stan wysoki „1”) – zatem sama interpretacja sygnału jest dość dowolna o ile ostatecznie na wyjściu bity przyjmą taką polaryzację, aby rezultat był poprawny. Cała ramka przesylana jest dwukrotnie. Czujnik zaczyna nadawać około 15sekund po włożeniu baterii, pary ramek są przesyłane następnie  co około 40 sekund.

Patrząc na poziomie wyższym, kiedy zakodowane dane zostaną złożone w ciąg zdekodowanych wartości binarnych z właściwą polaryzjacją, mamy komunikat składający się z:

  1. Preambuły – 16 naprzemiennie przesyłanych 0 i 1
  2. Półbajtu (nibble) synchronizującego odpowiadającego wartości ASCII znaku  ‚A’
  3. Ramki danych
  4. Postambuły – dwa półbajty nieznanego przeznaczenia.

Ramka danych różni się z zależności od czujnika. W ogólności składa się z półbajtów kodujących wartości w kodzie BCD. Dla czujnika THGN132N kolejne półbajty wyglądają tak:

0..3 identyfikator czujnika w tym przypadku wartość 0x1D20,

4 numer kanał. Stacja może odbierać dane z 3 czujników na 3 różnych kanałach.

5..6 losowy kod ustawiany po każdym resecie czujnika.

7 znacznik słabej baterii

8..10 Temperatura (najmniej znacząca cyfra oznacza dziesiąte części stopnia.)

11 Znak temperatury – 0 dla dodatnich

12..13 Wilgotność w %

14 Nieznane przeznaczenie

15..16 Suma kontrolna – prosta suma poprzednich półbajtów.

17..18 Postambuła – przeznaczenie nieznane.

Moduł został wykonany na specjalnie zaprojektowanej płytce o wymiarach 3x5cm. Znajduje się na niej oczywiście odbiornik, mikrokontroler Atmega8, stabilizator 3,3V (LD 1117A), dioda (sygnalizująca zasilanie) dwie zworki i dzielnik napięcia. W standardowym układzie układ zasilany jest napięciem 5V. Napięcie to zasila bezpośrednio odbiornik, mikrokontoroler zasilany jest z 3,3V. Wyjście odbiornika podłączone jest do mikrokontrolera poprzez dzielnik obniżający napięcie, aby było bezpieczne dla układu. Moduł posiada czteropinowe wyprowadzenie: +5V, RXD, TXD, GND – służące do zasilania oraz będące wyjściem danych w standardzie UART. Dzięki zasilaniu 3,3V układ może bez  problemu działać z platformami ARM (friendlyARM, RaspberryPi). Wspomniane zworki pozwalają na przełączenie zasilania mikrokontrolera na 5V i rozłączenie dzielnika od masy umożliwijaąc pracę całości na 5V.

Wyprowadzenie jest zgodne z wyprowadzeniem UART (+5V, Rxd, Txd, Gnd) na płytce mini2440 friendlyARM, aczkolwiek oczywiście nic nie stoi na przeszkodzie aby moduł podłączyć do innych platform.

Jak widać, istnieje również możliwość przesyłania danch do modułu – aktualnie nie jest to zaimplementowane w module, jednak w przypadku zaistnienia takiej potrzeby pozostaje tylko kwestia napisania odpowiednich procedur.

Wyprowadzona została także większość wolnych pinów w postaci goldpinów, umożliwia to programowanie procesora w układzie i rozbudowa o inne funkcjonalności.

Wyjście odbiornika zostało podłączone do pinu PD2 (INT0). Z powodu tak banalnego układu – nie załączam schematu.

Krótka analiza kodu

Działanie programu opiera się o pomiar czasu pomiędzy zmianami stanu pinu PD2. Pin ten ustawiony jest jako przerwanie zewnętrzne wyzwalane każą zmianą stanu.

Procedura przerwania:

ISR(INT0_vect )
{
 if bit_is_set(PIND, 2) lastbit = 1;
 else lastbit = 0;
 PORTB |= 0b00000001;
}

powoduje odczyt stanu wejścia i przepisanie go do zmiennej lastbit oraz ustawienie PB0, działającego jako ICP1 i wywołanie w ten sposób obsługi przerwania przechwycenia wartości Timera1. Wejście ICP1 może zostać wyzwolone w zależności od ustawienia rejestrów zboczem rosnącym lub opadającym. W momencie kiedy niezbędne jest wyzwolenie obydwoma zboczami można zastosować przełączanie po każdym przerwaniu zbocza wyzwalającego, albo wyzwalać je programowo (przerwanie zostanie wyzwolone również kiedy pin skonfigurowany jest jako wyjście) w sposób zastosowany w moim rozwiązaniu.

Dekodowanie pakietu danych opiera się na budowie automatu, którego stan przechowywany jest w dedykowanej zmiennej enumeracyjnej:

enum global_state
{
 IDLE = 0,
 PREAMBLE_DETECTED = 1,
 SYNC_AWAITING = 2,
 SYNC_RECEIVING = 3,
 DATA_RECEIVING = 4
} STATE_DECODER;

Stan idle jest stanem podstawowym oczekiwania na pakiet. Stan preamble_detected znalazł sie w kodzie, ale ostatecznie nie jest wykorzystany. Sync_awaiting ustawiany jest po odebraniu części preambuły – 17 bitów. Sync_receiving trwa w czasie odbierania półbajtu synchronizującego. Po odebraniu półbajtu ustawiany jest data_receiving, liczone są półbajty i zapisywane do specjalnego bufora.

Funckja retrurn_to_idle powoduje zresetowanie wszystkich liczników i stanu w momencie wykrycia nieoczekiwanej wartości w czasie dekodowania. Układ jest dzięki temu gotowy do próby odbioru kolejnej ramki:

void return_to_idle()
{
 STATE_DECODER = IDLE;
 preamble_cntr = 0;
 bit_cntr = 0;
 bit_cntr2 = 0;
 nibble_cntr = 0;
 nibble = 0;
}

Pora zatem przyjrzeć się obsłudze przerwania przechwytującego Timera1:

ISR(TIMER1_CAPT_vect )
{
PORTB &= 0b11111110;
period =   ICR1 - ICR1tmp;
ICR1tmp = ICR1;

 if(period > 800 && period < 1200)
 { 
  if (STATE_DECODER == IDLE ) preamble_cntr++;
  if (preamble_cntr>17 && STATE_DECODER == IDLE) {STATE_DECODER = SYNC_AWAITING; }

if (STATE_DECODER == SYNC_RECEIVING || STATE_DECODER == DATA_RECEIVING)
  {
  bit_cntr+= 2;
  if(bit_cntr == 4)
   bit_received();
   if (bit_cntr2 == 4)
   nibble_received();
  }
 }
 else if(period > 330 && period < 600)
 { 
 if (STATE_DECODER == SYNC_AWAITING) STATE_DECODER = SYNC_RECEIVING;

 if (STATE_DECODER == SYNC_RECEIVING || STATE_DECODER == DATA_RECEIVING)
 {
 bit_cntr++;
  if(bit_cntr == 4)
  {
   return_to_idle();
  }
 }
 }

Przede wszystkim zerowana jest wartość PB0 w celu umożliwienia poprawnej obsługi kolejnego przerwania. Następnie jest obliczany czas pomiędzy zmianami stanu. Licznik pracuje z częstotliwością około 1MHz (około ponieważ pracuje na wewnętrznym oscylatorze RC), równą pracy mikrokontrolera. Zatem wartości zmiennej period odpowiadają liczbie mikrosekund. Tutaj uwaga – w kolejnej wersji programu warto zdefiniować te wartości jako stałe, uzależniając je od stałej F_CPU. dla celów szybkiego uruchomienia układu taki zapis pozostał. Przedział wartości pochodzi z faktu, że okresy odpowiadające bitom różnią się w zależności od wartości bitu (w ogólności częstotliwość transmisji wynosi 1024Hz, ale stany niskie i wysokie mają nieco inną długość). Okres wartości długiej odpowiada przesłaniu dwóch sąsiadujących ze sobą identycznych wartości, okres krótszy pojedynczej. Ponieważ jest to Manchester – inne długości trwania jednego stanu są niedopuszczalne i powodują uznanie odczytu za niepoprawny (powrót do stanu spoczynkowego).

Poszczególne przejścia między stanami są raczej łatwe do analizy, nie będę tutaj opisywał ich szczegółowo – po wykryciu preambuły i półbitu synchronizującego ukłąd przechodzi do odbioru danych. Wykorzystane są tutaj dwie zmienne globalne: bit_cntr – odpowiada za wyznaczenie odebranego bitu. Każdy bit danych podczas przesyłania trwa 4 krótkie okresy – czyli 4 bity występujące w niedekodowanym sygnale (bit wysyłany dwukrotnie, bit w kodowaniu Manchester kodowany jest dwoma bitami sygnału). Odebranie długiego sygnału oczywiście zwiększa licznik o 2 – sytuacja wystąpienia maks. 2 identycznych stanów po sobie jest oczywiście prawidłowa. Po odebraniu zakodowanego bitu uruchamiana jest odpowiednia funkcja opisana poniżej. Jeśli dokonana zostanie dokładniejsza analiza przebiegu sygnału, co polecam jako ćwiczenie, okaże się, że w każdym bicie danych wystepuje w środku długi okres. Jest to cecha, którą można wygodnie użyć do synchronizacji i określenia wartości bitu. Jednocześnie wystąpienie w tym miejscu krótkiego okresu odbierane jest jako błąd. Tutaj też uwaga  – synchronizacja jest przesunięta o 1/4 bitu (1 krótki okres) do przodu z powodu, że niekoniecznie wystąpi zmiana stanu między bitami, ale zawsze w środku bitu – cecha Manchestera.

Zmienna bit_cntr2 liczy bity w półbajcie.

Funkcja:

bit_received()
{
  if(lastbit) 
  {
  }
  else 
  {
  nibble |= 0b10000000;
  }
  nibble = nibble>>1;
  bit_cntr = 0;
  bit_cntr2++;
}

jest uruchamiana po odebraniu bitu, wpisuje do zmiennej nibble odpowiednią wartość (transmisja LSB -> MSB).

Po odebraniu kompletu bitów w półbicie:

nibble_received()
{
 bit_cntr2 = 0;
// USARTWriteChar('0'+ nibble_cntr);
 nibble = nibble>>3;
  if (STATE_DECODER == SYNC_RECEIVING)
  {
   if (nibble != 0x0A) return_to_idle();
   else 
   STATE_DECODER = DATA_RECEIVING;

  }

  if (STATE_DECODER == DATA_RECEIVING)
  {
  message[nibble_cntr] = nibble;
  nibble_cntr++;
  if (nibble_cntr == 20) {send_temp();return_to_idle();}
  }

}

wartość ta wpisywana jest w odpowiednie miejsce w buforze. W przypadku półbitu synchronizacji – sprawdzana jest jego wartość i w przypadku niezgodności następuje wyzerowanie automatu. Cały komunikat jest wysyłany przez interfejs UART osobną fukcją, której opis jest zbędny.

W  ten sposób dekodowany jest cały komunikat. Układ działa zgodnie z oczekiwaniami – pakiety odbierane są poprawnie. Praktyka wykazuje, ze manieco mniejszą czułość od fabrycznego odbiornika – prawdopodobnie wymaga dopracowania konstrukcji anteny. Nie zdarzają się ramki odebrane źle lub błędy sumy kontrolnej – błędy w transmisji, jeśli występują, powodują nieodebranie całej ramki.

Sam moduł jest bazą do zastosowania w rozmaitych projektach,w których zastosowanie może mieć bezprzewodowy czujnik temepratury i wilgotności. Możliwa jest modyfiukacj ainterfejsu, komunikacja dwustronna, rejestrowanie pomiarów. Możliwości zależą tylko od potrzeb i inwencji projektanta.

czujnik mini

Do pobrania cały kod (pochodzi z projektu w AVR Studio 4).

Ten wpis został opublikowany w kategorii Projekty. Dodaj zakładkę do bezpośredniego odnośnika.

Jedna odpowiedź na „Moduł dekodera sygnału z bezprzewodowego czujnika temperatury.

  1. LA72 pisze:

    Witam.
    Ciekawy projekt.
    Jak wygląda plik „global.h”.

Możliwość komentowania jest wyłączona.