czwartek, 31 marca 2011

Podczerwień: RC-5 - Algorytm odbioru danych


Autor: Dondu

Artykuł jest częścią cyklu: Podczerwień - Transmisja danych


Aby poprawnie zdekodować sygnał odebrany w standardzie RC-5, niezbędne jest określenie poprawnego algorytmu.

Często jest tak, że ze względu na to, iż transmisja powinna przebiegać "w tle" programu głównego nie przeszkadzając mu zbytnio, należy tak opracować algorytm, by jego realizacja trwała jak najkrócej nawet kosztem zwiększonej objętości kodu wynikowego. Taki też algorytm znajdziesz poniżej.

Aby zrozumieć poprawnie działanie poniżej opisanego algorytmu, powinieneś znać i rozumieć treść dwóch artykułów:


Wstęp

Jak już ustaliliśmy w poprzednim artykule potrzebujemy licznik czasu (timer) oraz przerwanie.

Można oczywiście do rozwiązania podejść na wiele sposobów. W zależności od mikrokontrolera i posiadanych przez niego timerów można wykorzystać jeden z tych bardziej rozbudowanych posiadających na przykład tryb Input Capture lub podobny. Mamy wtedy zarówno przerwanie jak i timer w jednym, i tylko jeden pin zajęty, co jest oczywiście zaletą takiego rozwiązania.

Jednakże z reguły taki timer ma bezcenne dla projektanta funkcjonalności i przeznaczenie takiego "wypasionego" timera na bardzo prostą czynność dekodowania sygnału RC-5, może być niemożliwe lub utrudniać wykonanie pozostałych funkcjonalności projektowanego urządzenia.

Dlatego dobrym rozwiązaniem jest zastosowanie przerwań zewnętrznych z reguły oznaczanych jako INT oraz najprostszego timera.

Zastosowanie przerwania INT ma jeszcze jedną kolosalną zaletę. Z reguły taki pin wybudza mikrokontroler z najgłębszego trybu snu.

Pozwala to opracować urządzenie, które w trakcie czuwania zużywa najmniejszą możliwą ilość energii, a jednocześnie może zostać wybudzone za pomocą pilota.

Z drugiej strony przerwań INT jest z reguły mało i możesz mieć je wykorzystane. W takiej sytuacji w niektórych mikrokontrolerach, zamiast przerwania INT można wykorzystać przerwanie od zmiany stanu pinu portu (tzw. Pin Change Interrupt). W takim przypadku mamy jeszcze większe oszczędności i większą elastyczność implementacji naszej komunikacji w podczerwieni. Jednakże w tym przypadku poniższy algorytm może wymagać niewielkich modyfikacji, ponieważ przerwania od zmiany stanu pinu mogą nie mieć możliwości sprzętowego rozróżniania zbocza narastającego od opadającego - wszystko zależy od zastosowanego mikrokontrolera.


Co zrobić jeżeli nie mam wolnych przerwań INT, ani Pin Change?

Jeżeli nie mamy żadnej z powyższych możliwości, to możemy się posiłkować nawet wykorzystaniem komparatora i przerwań z niego pochodzących. Z reguły komparator ma możliwość wyboru wykrywanego zbocza co ułatwia implementację. Przykład takiego rozwiązania zaprezentujemy na stronie.



Algorytm

Poniżej przedstawiam jeden z możliwych wariantów algorytmu poprawnie dekodujący sygnał w standardzie RC-5.
Algorytm jest napisany w taki sposób, by łatwo było zrozumieć zasadę jego działania, a przede wszystkim metody dekodowania. Algorytm ten można śmiało zoptymalizować do postaci znacznie krótszej.

Analizując algorytm krok po kroku zobaczysz jak odbieramy pierwszy bit startu, drugi i kolejne.

Sygnał podawany jest na pin przerwania zewnętrznego, którego reakcja na początku jest ustawiona na zbocze opadające zgodnie z wykresem ramki na wyjściu odbiornika:



Dekodując sygnał na podstawie kolejnych przerwań:

Za każdym przerwaniem zmieniamy reakcję przerwania na przeciwną (zbocze opadające, na zbocze narastające lub odwrotnie), tak aby łapać wszystkie następujące po sobie zbocza.

Zmianę zbocza na przeciwne powinniśmy wykonać na samym początku funkcji przerwania, by żadnego nie przegapić.

Timer jest ustawiony na ciągłe zliczanie z określoną częstotliwością zegara, pozwalającą na poprawne odmierzanie istotnych dla nas czasów. Za każdym przerwaniem timer służy do odczytania czasu jaki upłynął od poprzedniego przerwania, a następnie jest zerowany, by liczył czas od aktualnego przerwania.

Śledząc po kolei zbocza sprawdzamy czasy.

Jeżeli czas, który upłynął od poprzedniego zbocza nie jest zgodny ze standardem, przerywamy odbiór ramki, rozpoczynając procedurę od nowa.

Po to, by łatwiej było zrozumieć algorytm podzielony jest on na osobne fragmenty dekodujące:
  1. pierwszy bit startu,
  2. drugi bit startu,
  3. pozostałe bity,
  4. wykrywanie ostatniego bitu i zapis danych do zmiennych globalnych,
  5. ustawianie odbioru od początku, gdy wystąpił błąd lub zakończono odbiór ramki.

Gdy wszystkie bity ramki zostaną prawidłowo odebrane zapisujemy dane do zmiennych (nr urządzenia, komendę oraz stan bitu toggle) i czekamy na nową ramkę. Tutaj w zależności od projektu należy albo zastosować bufor odebranych danych, albo nie pozwolić na odbiór następnej ramki dopóki poprzednie dane nie zostaną odebrane przez program główny.

Algorytm przedstawiony jest w postaci pseudokodu i jest dokładnie opisany więc nie powinieneś mieć kłopotów z jego zrozumieniem.

Na bazie tego algorytmu w następnym artykule przećwiczysz działający przykład. Istotnym jest, byś rozumiał algorytm, zanim przejdziesz dalej.

//funkcja zerująca timer oraz flagę jego przepełnienia
zeruj_timer_i_flage_przepelnienia()
{
  resetuj_preskaler_timera
  zeruj_timer
  zgaś_flagę_przepełnienia_timera
}


//---------------------------------------------------------------------------


FUNKCJA_OBSLUGI_PRZERWANIA()
{

  //Czy poprzednio odebrane dane zostały wykorzystane?
  if(poprzednie dane niewykorzystane) wyjdz_z_tej_funkcji

  //Na samym początku zmieniamy wykrywanie zbocza przeciwnego do
  //poprzedniego. Robimy to po to, by nie umknęło nam żadne przerwanie,
  //które może wystąpić w trakcie wykonywania niniejszej funkcji przerwania.
  zmien_zbocze_na_przeciwne();

  //W zależności, który bit jest aktualnie dekodowany
  switch(numer_bitu) {

    //--- bit startowy nr 1 ------------------------------------

  case 1:

    //Wykryto pierwsze zbocze opadające.
    //Sprawdzamy, czy przerwa w sygnale była wystarczająco długa
    if(przerwa wystaczajaco dluga) {

      //Przerwa między kolejnymi ramkami była wystarczająca,
      //by mieć pewność, że rozpoczynamy odbiór nowej ramki.
      zeruj_timer_i_flage_przepelnienia();
      numer_bitu=2;
      dane_temp = 0;
      polbit_licznik = 0;
      //kończymy czekając na zbocze narastające drugiego bitu startu

    } else {
      //Zbyt krótka przerwa w sygnale pomiędzy ramkami
      //dlatego tę ramkę danych musimy przeczekać.

      //Tutaj nie trzeba nic wykonywać, ponieważ aktualnie numer_bitu
      //jest równy 1 co oznacza, że warunek na kończu funkcji
      //przerwania ustawi stan początkowy
    }
    break;


    //--- bit startowy nr 2 ------------------------------------

  case 2:

    //sprawdzamy, czy czas połowy bitu jest zgodny z parametrami
    if(
      wystąpiło przepełnienie timera
      lub półbit za krótki
      lub półbit za długi
      )
    {
      //Wykryto błąd w sygnale.
      //Przerywamy dekodowanie tej ramki rozpoczynamy od nowa
      numer_bitu = 1;
      break;  //break, by nie sprawdzał końcowego if(numer_bitu > 14)

    } else {

      //Czas jest z zakresu półbitów

      //Które zbocze wywołało przerwanie?
      if(zbocze opadające) {
        //Przerwanie wywołało zbocze opadające bitu startowego nr 2
        //co oznacza, że drugi bit startu odebrany prawidłowo
        zeruj_timer_i_flage_przepelnienia();
        numer_bitu++;
      } else {
        //wykryto pierwsze zbocze narastające bitu startowego nr 2
        zeruj_timer_i_flage_przepelnienia();
      }
    }
    break;


    //--- pozostałe bity --------------------------------------

  default:

    //Tutaj odbieramy pozostałe bity ramki

    //W zależności jaki czas upłynął od ostatniego zbocza (przerwania)
    //Czy czas wykracza poza brzegowe parametry (min i max)
    if(
      wystąpiło przepełnienie timera
      lub czas krótszy niż minimalny dla półbitu
      lub czas dłuższy niż maksymalny dla całego bitu
      )
    {
      //Wykryto błąd w sygnale.
      //Przerywamy dekodowanie tej ramki rozpoczynamy od nowa
      numer_bitu = 1;
      break;  //break, by nie sprawdzał końcowego if(numer_bitu > 14)

      //Czy minął czas półbitu?
    } else if(
             czas równy lub krótszy niż maksymalny dla półbitu
             )
    {
      //Upłynął czas równy połowie bitu

      //sprawdzamy, czy to druga połowa bitu
      if(polbit_licznik == 1) {

        //Tak to druga połówka aktualnie dekodowanego bitu
        //i jesteśmy aktualnie w połowie czasu odbieranego
        //bitu. Jest to moment, w którym ustalamy wartość
        //odebranego bitu na podstawie kierunku zbocza.

        //Przesuń dane o jeden bit w lewo, by zrobić miejsce
        //na odebrany bit
        dane_temp << 1

        //Jeżeli przerwanie wywołało zbocze opadające
        //oznacza to, że odebraliśmy jedynkę więc ją dodajemy
        if(było zbocze opadające) {
          dane_temp |= 1;
        }

        //zeruj licznik półbitów
        polbit_licznik = 0;

        //zwiększ licznik odebranych bitów
        numer_bitu++;

      } else {

        //To pierwsza połowa dekodowanego bitu, czyli jesteśmy
        //aktualnie na początku czasu przesyłanego bitu.

        //Ustawiamy licznik półbitów
        polbit_licznik = 1;

      }

      //w obu przypadkach półbitów
      zeruj_timer_i_flage_przepelnienia();

    } else {

      //Upłynął czas całego bitu i jesteśmy aktualnie w połowie
      //odbieranego bitu. Jest to moment, w którym ustalamy wartość
      //odebranego bitu na podstawie kierunku zbocza.

      //Przesuń rejestr odbiorczy o jeden bit w lewo by zrobić
      //miejsce na odebrany bit
      dane_temp << 1

      //Jeżeli przerwanie wywołało zbocze opadające
      //oznacza to, że odebraliśmy jedynkę więc ją dodajemy
      if(było zbocze opadające) {
        dane_temp |= 1;
      }

      zeruj_timer_i_flage_przepelnienia();

      //zwiększ licznik bitów
      numer_bitu++;
    }

    //--- czy to już ostatni bit? --------------------------------

    if(numer_bitu > 14) {

      //Tak, odebrano ostatni bit zapisz dane do zmiennych globalnych

      //1-Dane są na sześciu najmłodszych bitach
      dane_odebrane = dane_temp[komenda];

      //2-Nr urządzenia (adres) i bit toggle
      status = dane_temp[toggle bit oraz nr urządzenia];

      //przygotuj się do odbioru nowej ramki
      numer_bitu = 1;

      //koniec odbioru ramki ... uff nareszcie :-)
    }
    break;
  }

  //--- Błąd lub koniec transmisji -------------------------------

  //Jeżeli na końcu funkcji przerwania numer bitu ma wartość 1
  //to oznacza, że zarządano przerwania dekodowania z powodu błędu
  //lub poprawnego zakończenia dekodowania
  if(numer_bitu == 1) {
    //ustawiamy stan początkowy
    zeruj_timer_i_flage_przepelnienia();
    ustaw_zbocze_opadajace();
    polbit_licznik = 0;
  }

}




2 komentarze:

  1. Witam. Chciałbym się zapytać odnośnie fizyki, tego jak gdyby czasu trwania impulsu. Na rysunku jest pokazane, że jedynka binarna jest zboczem opadającym, a zero jest zboczem narastającym. Skąd mam wiedzieć ile trwa zero, a ile trwa jedynka. Jest to jakaś przypisana wartość czasowa-energia dla 1 i 0. Pozdro

    OdpowiedzUsuń