Kursy • Poradniki • Inspirujące DIY • Forum
W artykule Różne zastosowania podczerwieni – przegląd rozwiązań wspominałem, że mam zamiar opublikować więcej materiałów. W tym tekście postaram się wykorzystać zebrane informacje w praktyce. W artykule zostanie wykorzystany oscyloskop stworzony w domowych warunkach, który może okazać się przydatny również do innych zastosowań.
W amatorskich konstrukcjach nieraz można spotkać się z zastosowaniem pilota. Dzięki niemu możemy uruchamiać swojego robota z pewnej odległości (przydatne na zawodach), przełączać tryby pracy albo po prostu zdalnie nim sterować. Praktycznie wszystkie tego typu konstrukcje korzystają z pilotów w standardzie RC5. Nic dziwnego, w końcu Atmel udostępnił gotowy program do odczytywania tego kodu.
Ostatnio, gdy sam miałem potrzebę zastosowania sterowania na pilota, mój wybór także padł na RC5. Poszedłem na giełdę i kupiłem za 2 zł pierwszy lepszy pilot Philipsa (wcześniej gdzieś przeczytałem, że większość pilotów tej firmy korzysta z RC5, a po co przepłacać i czekać na dostawę w internecie). Oczywiście nie trudno się domyślić, że zakupiony sprzęt nie działał tak, jak bym tego oczekiwał. Postanowiłem nie poddawać się i rozkodować nowo nabytego pilota. Wynikami swojej pracy dzielę się na łamach tego artykułu.
Jak rozpoznać standard nadawania?
Pierwszym krokiem było poznanie istniejących sposobów kodowania. Co nieco napisałem o nich w poprzednim artykule. Dowiedziałem się, że istnieją dwa główne sposoby kodowania -manchester i kodowanie długością impulsu.
Poza tym kody pilotów pracują na różnych częstotliwościach, takich jak:
- 36kHz
- 38kHz
- 40kHz
- 56kHz
Niektóre starsze rozwiązania w ogóle nie używają modulacji częstotliwościowej. Do odczytania zmodulowanego sygnału używane są scalone dekodery, takie jak popularne TSOPy. Różne modele tego układu zostały stworzone do odczytywania popularnych częstotliwości kodowania. Poniżej tabela modeli TSOP22xx Vishaya:
Należy pamiętać, że czujnik odbiera pewien zakres częstotliwości w otoczeniu nominalnej.
Zwykle jest to zjawisko negatywne, ponieważ możemy w ten sposób odbierać zakłócenia z innych pilotów. W tym jednak wypadku, jest to zjawisko pożądane, ponieważ łatwiej będzie nam odczytać ramkę danych z pilota, nie dysponując dokładnie tym modelem czujnika, co trzeba. Poniżej wykres z noty katalogowej tego samego czujnika TSOP22xx, obrazujący siłę generowanego sygnału przez poszczególne częstotliwości w odniesieniu do nominalnej:
Ważne jest odpowiednie filtrowanie zakłóceń w takim czujniku. Najlepiej postępować zgodnie z zaleceniami producenta. Poniżej schemat sugerowany przez producenta czujników TSOP:
Zaczynamy testy
Pierwszym zadaniem jest więc określenie, na jakiej częstotliwości działa nasz pilot. Możliwe, że jest na to łatwiejszy sposób, ja jednak wybrałem dość toporne rozwiązanie. Postanowiłem zbudować na płytce stykowej prosty układ z czujnikiem, mikrokontrolerem i kilkoma diodami:
Układ ten będzie potrzebny praktycznie do wszystkich testów. We wszystkich programach używam wewnętrznego rezonatora 4MHz. Przewidziałem w schemacie wyjście UARTa, co będzie później bardzo przydatne przy debugowaniu.
Napisałem prosty program, który ma zwiększać wartość wyświetlaną na LEDach za każdym razem, kiedy wykryje stan wysoki na wejściu czujnika. Liczyłem, że pilot będzie pracował na 36kHz, co okazało się zgodne z prawdą. W przeciwnym wypadku musiałbym testować czujniki na inne częstotliwości.
Duże koncerny posiadają wiele standardów kodowania i używają nieraz kilku częstotliwości, np. Sony używa czasem częstotliwości 40kHz. Może testowana przeze mnie próbka nie była reprezentatywna, ale kody wszystkich pilotów jakie miałem można było odczytać za pomocą czujnika TSOP2236.
Interpretacja ramki danych
Kiedy już wiemy, że dysponujemy czujnikiem odczytującym dane z pilota, możemy przejść do głównego punktu programu. Naszym zadaniem będzie rozszyfrowanie ramki danych z pilota oraz określenie kodu poszczególnych przycisków.
Prosty oscyloskop
Do śledzenia przebiegów generowanych przez czujnik najlepiej użyć oscyloskopu. Tutaj pojawia się poważny problem. Profesjonalne oscyloskopy są bardzo drogie i mało kto może sobie na nie pozwolić. Jednak rozwiązanie jest prostsze, a przede wszystkim tańsze niż mogłoby się wydawać. Jedyne czego potrzebujemy to... starego kabla od słuchawek. Poza tym musimy zainstalować program Soundcard Oscilloscope. Dzięki niemu możemy używać przetwornika analogowego-cyfrowego naszej karty dźwiękowej jako wejścia oscyloskopu, natomiast przetwornika cyfrowo-analogowego jako generatora przebiegów.
Program umożliwia również tworzenie wykresów XY oraz śledzenie przebiegów w dziedzinie częstotliwości. Kabel od słuchawek przecinamy w odległości kilkudziesięciu centymetrów od wtyczki tak, aby można go było łatwo podłączyć do komputera i płytki testowej. Następnie ręką rozdzielamy dwie żyły i nożykiem zdejmujemy z każdej z nich izolację.
W każdej żyle powinna się znajdować kolejna warstwa izolacji, zawierająca przewód sygnałowy oraz nieizolowany przewód masy.
Wtyczka natomiast zawiera wyprowadzenia zgodne z poniższym obrazkiem:
Na odsłonięte nożem przewody warto założyć drucik i rurkę termokurczliwą. W ten sposób otrzymamy prowizoryczną sondę oscyloskopu.
Przed podłączeniem sondy do komputera należy się upewnić, za pomocą miernika, czy masy układu i karty dźwiękowej się zgadzają oraz czy maksymalne napięcie na pewno nie przekracza 2V. Nieprzestrzeganie tych zasad może spowodować uszkodzenie karty dźwiękowej, a w ekstremalnych wypadkach nawet całego komputera.
Używanie oscyloskopu
Domyślnym wejściem naszego oscyloskopu jest gniazdo mikrofonu, natomiast wyjściem generatora jest gniazdo słuchawkowe. Karta dźwiękowa jest przystosowana do generowania/odbierania przebiegów o częstotliwościach z zakresu słyszalności ludzkiego ucha, czyli w zakresie około 20-22000 Hz. Obsługa programu jest bardzo prosta.
Poniżej okno główne:
Pokrętłami Amplitude i Time skalujemy osie, za pomocą wartości Offset przesuwamy wartość początkową przebiegu na osi Y dla poszczególnych kanałów. W ramce Trigger możemy ustawić tryby wyzwalania. Dostępne opcje to:
- Auto - przebieg jest wyświetlany cały czas na ekranie, nawet jeśli nie zostanie przekroczony próg wyzwalania.
- Normal - za każdym razem, gdy próg zostanie przekroczony, pojawia się nowy przebieg.
- Single - po wciśnięciu przycisku Run/Stop wyświetlany jest pierwszy przebieg po przekroczeniu progu wyzwalania.
Niżej można wybrać kanał, dla którego ustalamy warunki wyzwalania, rosnące lub opadające zbocze przebiegu (edge) oraz próg (threshold), jaki musi przekroczyć zbocze, aby zostało zarejestrowane przez oscyloskop.
W lewym dolnym rogu wykresu pojawia nam się przycisk Save, który zapisuje przebiegi zarówno w formie obrazka, jak i pliku csv. Dzięki temu dane z oscyloskopu można następnie analizować za pomocą przeznaczonych do tego programów.
Niektórzy mogą się dziwić, jak sprzętem odbierającym częstotliwości do 22kHz mamy zbadać sygnał o częstotliwości 36kHz.
Dlatego właśnie za pomocą oscyloskopu z karty dźwiękowej można również bez problemu zbadać przebiegi zmodulowane częstotliwością 56kHz.
Pomiary
Po podłączeniu czujnika zgodnie z zaleceniami producenta oraz poprowadzeniu wyjść OUT i GND do sondy oscyloskopu, mogłem rozpocząć badanie przebiegów. Oscyloskop ustawiłem w trybie single, a próg wyzwalania podniosłem na tyle, aby nie łapał śmieci. Poniżej przebieg dla klawisza "1":
Jedna rzecz nieco mnie zdziwiła. Otóż TSOP2236 domyślnie wskazuje stan wysoki, a do niskiego przechodzi kiedy otrzymuje daną. Z wykresu na oscyloskopie wynika natomiast, że jest odwrotnie. Po szybkim sprawdzeniu za pomocą miernika doszedłem do wniosku, że oscyloskop pokazuje odwrócone wartości.
Nie wiem, dlaczego tak się dzieje. Trzeba pamiętać, że to tylko prosta namiastka prawdziwego oscyloskopu, więc należy sobie jakoś radzić. W końcu czasy trwania poziomów logicznych są ok, a zmiana polaryzacji nie jest specjalnie kłopotliwa.
Dla porównania zapisałem przebiegi dla przycisków 2, 3, 4:
Interpretacja oscylogramów
Jak widzimy na przebiegach, każdy sygnał zaczyna się od dwóch długich impulsów. Następnie jest kilka dużo krótszych i kolejny długi. Do tego momentu każda ramka danych wygląda tak samo.
Różnią się dopiero ostatnią serią krótkich impulsów. Jeżeli porównamy te obserwacje ze znanymi kodami do pilotów, możemy dojść do wniosku, że początkowe długie sygnały służą do inicjalizacji. Następna seria krótkich impulsów to kod systemu, dlatego jest identyczny dla każdego przycisku.
Ostatnia seria krótkich impulsów, która jest różna dla każdego przycisku musi zawierać kody komend. Długości poszczególnych ramek różnią się od siebie, więc mamy do czynienia z kodowaniem za pomocą długości impulsu. Przyjrzyjmy się teraz seriom krótkim.
Jak widać, składają się z 9 stanów niskich (pamiętamy o wadzie oscyloskopu) o takiej samej długości oraz ośmiu stanów wysokich, których długość przybiera jedną z dwóch wartości. Nie wiemy, czy producent założył sobie dłuższy impuls jako 1 czy 0, ale tak naprawdę nas to nie obchodzi. Możemy przypisać te wartości po swojemu i odczytywać tak, jak nam wygodnie. Ja przyjąłem, że 0 to krótki sygnał, a 1 to długi.
Następnym krokiem jest poznanie dokładnych zależności czasowych. W tym celu posłużyłem się Matlabem, ale nada się do tego każdy program do obróbki danych, nawet zwykły Excel. Wyznaczyłem punkty początku i końca każdego rodzaju impulsu i obliczyłem czasy ich trwania. Operację powtórzyłem na kilku przebiegach aby mieć pewność, że nie popełniłem błędu. Następnie odczytałem kody każdego przycisku i zapisałem sobie wszystkie informacje o moim pilocie:
Częstotliwość modulacji: 36kHz
Ramka danych:
- bit startu 1: 8ms LOW
- bit startu 2: 4ms HIGH
- osiem bitów systemu
- bit przerwy: 4ms HIGH
- osiem bitów komendy
- koniec informacji: HIGH do otrzymania kolejnej ramki
Kodowanie bitów systemu i komendy:
- sygnał low: 422us
- sygnał high "0": 422us
- sygnał high "1": 1.6ms
Między ósmym bitem danych, a przerwą lub końcem występuje jeszcze jeden sygnał low 422us.
- kod systemu: 11000011
Lista komend:
- 0 - 11110000
- 1 - 01001000
- 2 - 01101000
- 3 - 10011000
- 4 - 00100000
- 5 - 00110000
- 6 - 00011010
- 7 - 01110000
- 8 - 10010000
- 9 - 00111000
- power - 00011000
- mute - 01011000
- prog - 01100000
- audio - 11101000
- sys - 01010000
- lnb - 10111000
- up - 11011000
- down - 11001000
- left - 01000000
- right - 00111001
- store - 11111000
- next - 10101000
- fav - 10001000
- hv - 11010000
- tone - 01111000
- radio - 00000000
- tvsat - 10100000
- lock - 00101000
Dla pewności postanowiłem jeszcze sprawdzić przebiegi za pomocą prostego programu, napisanego w asemblerze, wykorzystującego USART do kontroli poprawności odczytywanych danych. Jego zadaniem jest odczytanie stanu pinu w przerwaniu timera, dodanie go do bufora odczytu, a po uzbieraniu ośmiu bitów wysłanie ich do PC.
Co ciekawe, wcześniej próbowałem sprawdzać pin w pętli, a nie w przerwaniu lub używać przerwania zewnętrznego, a nie timera. Okazywało się wtedy, że wartość przy zmianie przez jakiś czas oscyluje, powodując efekt podobny do drgania styków przy switchach, tylko oczywiście trwający o wiele krócej.
Na szczęście sprawdzanie w przerwaniu timera co 64us całkowicie wyeliminowało ten problem, a na odczycie z terminala wyniki są zgodne z oczekiwaniami. Oto odczyt z terminala w kodzie szesnastkowym dla jednej ramki danych:
c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 07 ff ff ff ff ff ff ff 00 3f ff ff 00 ff ff fc 01 fc 03 f8 07 f0 07 f0 0f ff ff c0 3f ff ff 00 ff ff ff ff ff ff ff fc 03 f8 07 ff ff e0 1f c0 3f 80 7f ff ff 00 ff 00 fe 01 fc 03
Poszczególne fragmenty przebiegu z oscyloskopu można łatwo dopasować do odpowiadających im ciągów zer i jedynek na terminalu. Dzięki temu mam pewność, że mikrokontrolerowi uda się odczytać ramkę danych bez żadnych zakłóceń. Poniżej kod programu w asemblerze. Jeśli ktoś nie zna tego języka, to i tak nie powinien mieć problemu ze zrozumieniem dzięki komentarzom i dokumentacji Atmela:
.include "m16def.inc" ;definicje dla atmegi 16
;deklaracje nazw rejestrow pomocniczych
.def A = r16
.def licz = r17
.def buf = r18
;USART tylko transmisja, 9600baud 8bitów even parity
.EQU UCSRBconf = (1<<TXEN)
.EQU UCSRCconf = (1<<URSEL) | (1<<UCSZ0) | (1<<UCSZ1) | (1<<UPM1)
.EQU UCRRLconf = 12
;TIMER0 przerwanie ovf, preskaler 1
.EQU TCCR0conf = (1<<CS00)
.EQU TIMSKconf = (1<<TOIE0)
.CSEG
.ORG 0x00
jmp RESET ;skok do obslugi resetu
.ORG 0x012
jmp TIM0_OVF ;skok do obslugi przerwania T0
.ORG 0x100
RESET:
;inicjalizacja stosu
ldi A, high(RAMEND)
out sph, A
ldi A, low(RAMEND)
out spl, A
;pullup do wejscia TSOPa, domyslnie jako wejscie
ldi A, 0x04
out PORTD, A
;konfiguracja USART
ldi A, UCSRBconf
out UCSRB, A
ldi A, UCSRCconf
out UCSRC, A
ldi A, UCRRLconf
out UBRRL, A
clr A
out UBRRH, A
;konfiguracja T0
ldi A, TCCR0conf
out TCCR0, A
ldi A, TIMSKconf
out TIMSK, A
sei
;nieskonczona petla
PROGRAM:
jmp PROGRAM
;przerwanie od timera
TIM0_OVF:
lsl buf ;przesuniecie bufora w lewo
sbic PIND, 2 ;jezeli aktualnie na wejsciu jest 1
inc buf ;to zwieksz buf
inc licz ;zwieksz licznik bitow w buforze
cpi licz, 8 ;sprawdz czy bufor pelny
breq SEND ;jesli tak to wyslij przez uart
reti ;powrot z przerwania
SEND:
sbis UCSRA, UDRE ;petla dopoki bufor UART nie
rjmp SEND ;bedzie pusty po poprzedniej transmisji
mov A, buf ;a nastepnie skopiowanie buf do bufora UART
out UDR, A
clr licz ;i wyzerowanie licznika
reti
Program dekodujący
Teraz kolej na napisanie pełnego kodu do dekodera pilota. Tutaj po raz kolejny wybrałem asemblera z kilku powodów. Po pierwsze, będzie to prosta aplikacja, wykorzystująca głównie operacje na rejestrach, a takie pisze się w asemblerze bardzo przyjemnie.
Po drugie, będę mógł dokładnie prześledzić działanie programu w debuggerze AVR Studio 4 i szybciej znaleźć ewentualne błędy. Ponadto, podczas pisania mogę ściągnąć niektóre fragmenty z noty aplikacyjnej Atmela, dotyczącej RC5. Pod spodem gotowy kod z opisem w komentarzach:
.include "m16def.inc" ;definicje dla atmegi 16
;deklaracje nazw rejestrow
.DEF tmp = r16 ;rejestr tymczasowy
.DEF timerL = r17 ;trzy liczniki do obslugi przerwania T0
.DEF timerH = r18
.DEF timertemp = r19
.DEF system = r0 ;rejestr przechowujacy kod systemu
.DEF command = r1 ;rejestr przechowujacy kod komendy
.DEF stan = r20 ;pomocniczy rejestr przechowujacy aktualny stan PIN.D2
;konfiguracja timera T0
.EQU TIMSKconf = (1<<TOIE0)
.EQU TCCR0conf = (1<<CS00)
.CSEG
.ORG 0x00
jmp RESET ;skok do obslugi resetu
.ORG 0x12
jmp T0_OVF ;skok do obslugi przerwania T0
.ORG 0x100
;obsluga resetu - poczatek programu
RESET:
;inicjalizacja stosu
ldi tmp, low(RAMEND)
out spl, tmp
ldi tmp, high(RAMEND)
out sph, tmp
;pullup na PD2
ldi tmp, 0x04
out PORTD, tmp
;PORT C - wyswietla na linijce diod odczytany bajt, wyjscie, poczatkowe wartosci H
ser tmp
out DDRC, tmp
out PORTC, tmp
;konfiguracja timera
ldi tmp, TCCR0conf
out TCCR0, tmp
ldi tmp, TIMSKconf
out TIMSK, tmp
;aktywacja przerwan globalnych
sei
;poczatek funkcji dekodujacej, reset timerow
detect:
clr timerH
clr timertemp
detect1:
clr timerL
;sprawdzenie czy jest idle przez 7.5ms (timingi dla taktowania 4MHz)
idle:
cpi timerH, 8 ;sprawdzenie czy minelo 131ms
brsh detect ;jak nie to wracamy do poczatku
sbrs stan, 0 ;sprawdzenie stanu pinu
rjmp detect1
cpi timerL, 100 ;sprawdzenie czy stan sie utrzymuje przez 7.5ms
brlo idle ;jezeli nie to wracamy do detecta
;wykrycie poczatku bitu startu
clr timerH
start:
cpi timerH, 8
brsh detect ;jezeli startu nie ma przez 131ms to wracamy do poczatku
sbrc stan, 0 ;jezeli na pinie jest 1 to petla, jak nie to algorytm idzie dalej
rjmp start
clr timerL
;wykrycie stanu niskiego trwajacego 9.6ms
start1:
cpi timerL, 150
brsh detect ;jezeli 0 trwa dluzej niz 9.6ms albo krocej niz 4ms to error
sbrs stan, 0 ;jezeli nie ma 0 na PD2 to powrot do start1
rjmp start1
cpi timerL, 50
brlo detect
clr timerL
;wykrycie stanu wysokiego przez 4ms
start2:
cpi timerL, 100
brsh detect ;jezeli trwa dluzej niz 6ms albo krocej niz 2.5 to error
sbrc stan, 0
rjmp start2
cpi timerL, 20
brlo detect
;poczatek odczytywania systemu
system_init:
clr system
clr tmp
;kolejny bit systemu
sys_next:
clr timerL
;odczytanie sygnalu LOW
sys_low:
cpi timerL, 12
brsh detect ;jezeli dluzej niz 760us to od nowa
sbrs stan, 0 ;jezeli nie pojawia sie 0 to powrot do sys_low
rjmp sys_low
clr timerL ;jezeli pomyslnie odczytano low to zerujemy timer
;sygnal high i odczytanie "0" albo "1"
sys_high:
cpi timerL, 40
brsh detect
sbrc stan, 0 ;czekanie na "1" na PD2
rjmp sys_high
lsl system ;operacja bitowa system<<1
inc tmp ;zwiekszenie licznika ilosci odebranych bitow
cpi timerL, 15 ;jezeli dlugi high to wpisujemy 1 na ostatni bit systemu
brlo sys_end ;jezeli nie to zostanie zero
inc system
sys_end:
cpi tmp, 8 ; jezeli licznik mniejszy niz 8 to wracamy do odczytu kolejnego bitu
brlo sys_next
;odczytanie ostatniego stanu niskiego po odczytaniu systemu
sys_end2:
clr timerL
cpi timerL, 12
brsh detect ;jezeli dluzej niz 760us to od nowa
sbrs stan, 0
rjmp sys_end2
clr timerL
;do tego momentu na pewno dobrze dziala
;wykrycie stanu wysokiego przez 4ms
przerwa:
cpi timerL, 100
brsh detect ;jezeli trwa dluzej niz 6ms albo krocej niz 2.5 to error
sbrc stan, 0
rjmp przerwa
cpi timerL, 20
brlo detect
;odczytywanie command takie samo jak w wypadku stanu tylko z zamienionymi nazwami
command_init:
clr command
clr tmp
;kolejny bit systemu
com_next:
clr timerL
;----------------------
com_low:
cpi timerL, 12
brsh detect ;jezeli dluzej niz 760us to od nowa
sbrs stan, 0
rjmp com_low
clr timerL ;jezeli pomyslnie odczytano low to zerujemy timer
;sygnal high i odczytanie "0" albo "1"
com_high:
cpi timerL, 40
brsh koniec
sbrc stan, 0
rjmp com_high
lsl command
inc tmp
cpi timerL, 15 ;jezeli dlugi high to wpisujemy 1 na ostatni bit systemu
brlo com_end ;jezeli nie to zostanie zero
inc command
com_end:
cpi tmp, 8
brlo com_next
;odczytanie ostatniego stanu niskiego po odczytaniu systemu
com_end2:
clr timerL
cpi timerL, 12
brsh koniec ;jezeli dluzej niz 760us to od nowa
sbrs stan, 0
rjmp com_end2
clr timerL
;----------------------
;po poprawnym odczycie zwrocenie na linijke led wartosci command
out PORTC, command
;powrot do poczatku programu
koniec:
jmp detect
T0_OVF:
clr stan ;jezeli w czasie przerwania pd2 jest High to stan = 0xFF
sbic PIND, 2 ;w przeciwnym razie 0x00
ser stan
inc timerL ;zwiekszenie wartosci timerow
inc timertemp
brne t0_koniec ;jezeli timertemp==0 to zwiekszenie timerH
inc timerH ;timerH pracuje 256 razy wolniej niż timerL i timertemp
t0_koniec:
reti ;powrot z przerwania
Po napisaniu części odczytujących kolejne fragmenty ramki, warto dopisać linijki zapalające diody, w celu sprawdzenia, czy wszystko robimy poprawnie.
Łatwiej debugować na bieżąco, niż później męczyć się z szukaniem błędów. Mój program przeszedł właśnie taki proces debugowania, co pozwoliło mi dość szybko uporać się z nim.
Program działa bez zarzutu, wykrywa wszystkie kody przycisków dokładnie tak, jak w opisanej wyżej specyfikacji. Ma on jednak pewne wady. Mimo korzystania z przerwań, dalej używane jest również odpytywanie w pętli. Poza tym trudno byłoby używać go jako funkcji w innym programie, jeżeli każde odczytanie przycisku zajmowałoby kilkadziesiąt milisekund, podczas których procesor nie może się zajmować niczym innym.
W większych projektach asembler jednak nie jest tak poręczny i przydałoby się również dysponować kodem w C. Dlatego właśnie napisałem bibliotekę danego pilota w tym języku. Mogę jej teraz z powodzeniem używać jako fragment większego projektu.
Biblioteka w C
Wykonuje ona wszystkie operacje w przerwaniu. Mimo że przerwanie wydaje się bardzo rozbudowane, składa się z wielu instrukcji warunkowych i tak naprawdę każda możliwa ścieżka wykonuje się dość szybko.
Przerwanie jest zorganizowane jako zbiór stanów, w których aktualnie może się znajdować układ. Za każdym razem są w nim sprawdzane warunki przejścia do innego stanu i jeśli zostaną spełnione, przy następnym przerwaniu układ znajdzie się w kolejnym stanie. Oczywiście nie ma najmniejszego problemu, żeby przeskalować program dla innych wartości oscylatora.
/*
Biblioteka pilot.h zawiera:
-inicjalizacje odpowiednich portów i timera0
-przerwanie T0OVF
-zmienne potrzebne do transmisji
*/
//kolejne stany
#define IDLE 0
#define DSTART 1
#define START1 2
#define START2 3
#define SYSTEMLOW 4
#define SYSTEMHIGH 5
#define SYSTEMEND 6
#define PRZERWA 7
#define COMMANDLOW 8
#define COMMANDHIGH 9
#define KONIEC 10
//kody przyciskow
#define BUTTON_0 0b11110000
#define BUTTON_1 0b01001000
#define BUTTON_2 0b01101000
#define BUTTON_3 0b10011000
#define BUTTON_4 0b00100000
#define BUTTON_5 0b00110000
#define BUTTON_6 0b00011010
#define BUTTON_7 0b01110000
#define BUTTON_8 0b10010000
#define BUTTON_9 0b00111000
#define BUTTON_POWER 0b00011000
#define BUTTON_MUTE 0b01011000
#define BUTTON_PROG 0b01100000
#define BUTTON_AUDIO 0b11101000
#define BUTTON_SYS 0b01010000
#define BUTTON_LNB 0b10111000
#define BUTTON_UP 0b11011000
#define BUTTON_DOWN 0b11001000
#define BUTTON_LEFT 0b01000000
#define BUTTON_RIGHT 0b00111001
#define BUTTON_STORE 0b11111000
#define BUTTON_NEXT 0b10101000
#define BUTTON_FAV 0b10001000
#define BUTTON_HV 0b11010000
#define BUTTON_TONE 0b01111000
#define BUTTON_RADIO 0b00000000
#define BUTTON_TVSAT 0b10100000
#define BUTTON_LOCK 0b00101000
#define SYSTEM_CODE 0b11000011
volatile unsigned char system=0, command=0, system_new=0, command_new=0, czy_odczytano=0;
void PILOT_init(void)
{
TIMSK = (1<<TOIE0);
TCCR0 = (1<<CS00);
DDRD &= 0xFB; //PD3 input
PORTD |= (1<<3); //pullup na INT2
}
ISR(TIMER0_OVF_vect)
{
static unsigned char timerH=0, timerL=0, timertemp=0, stan=0, i=0;
timerL++;
timertemp++;
if(!timertemp) timerH++;
switch(stan)
{
case IDLE:
{
if(timerH<8)
{
if(PIND&0x04) {if(timerL<100) return; else {stan=DSTART; timerH=0; timerL=0; return;}}
else {timerL=0; return;}
}
else {timertemp=0; timerH=0; timerL=0; return;}
}
case DSTART:
{
if(timerH>8) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) return; else {timerL=0; stan = START1; return;}
}
}
case START1:
{
if(timerL>150) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04)
{
if(timerL<80) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else {timerL=0; stan = START2; return;}
}
else return;
}
}
case START2:
{
if(timerL>100) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) return;
else
{
if(timerL<40) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else {timerL=0; stan = SYSTEMLOW; system_new=0; i=0; return;}
}
}
}
case SYSTEMLOW:
{
if(timerL>12) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) {timerL=0; stan=SYSTEMHIGH; return;}
else return;
}
}
case SYSTEMHIGH:
{
if(timerL>40) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) return;
else
{
i++;
system_new = (system_new<<1);
if(timerL>15) system_new++;
timerL=0;
if(i<8) {stan=SYSTEMLOW; return;} else {timerL=0; stan=SYSTEMEND; return;}
}
}
}
case SYSTEMEND:
{
if(timerL>12) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) {timerL=0; stan=PRZERWA; return;}
else return;
}
}
case PRZERWA:
{
if(timerL>100) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) return;
else
{
if(timerL<40) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else {timerL=0; stan = COMMANDLOW; command_new=0; i=0; return;}
}
}
}
case COMMANDLOW:
{
if(timerL>12) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) {timerL=0; stan=COMMANDHIGH; return;}
else return;
}
}
case COMMANDHIGH:
{
if(timerL>40) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04) return;
else
{
i++;
command_new = (command_new<<1);
if(timerL>15) command_new++;
timerL=0;
if(i<8) {stan=COMMANDLOW; return;} else {timerL=0; stan=KONIEC; return;}
}
}
}
case KONIEC:
{
if(timerL>12) {timertemp=0; timerH=0; timerL=0; stan=IDLE; return;}
else
{
if(PIND&0x04)
{
timerL=0;
timerH=0;
timertemp=0;
command=command_new;
system=system_new;
czy_odczytano=1;
stan=IDLE;
return;
}
else return;
}
}
}
}
Podsumowanie
Jak widać, za pomocą prostych narzędzi i odrobiny wiedzy dotyczącej kodowania pilotów, udało mi się przystosować do własnych celów sygnał z nieznanego pilota.
Stosując podobne techniki można uporać się z każdym innym kodowaniem. Na zakończenie przeanalizujmy jeszcze przebieg z innego pilota do telewizora, jaki akurat miałem w domu. Nie będę już się w niego zagłębiać, ale proces dekodowania przebiegałby podobnie jak wyżej.
Na przebiegach widać wyraźnie część z kodem systemu, 20ms przerwy i kod komendy.
Każda z części zaczyna się dwoma sygnałami inicjalizującymi. Poza tym widzimy, że wszystkie przebiegi są tej samej długości, czyli mamy do czynienia z kodowaniem Manchester.
W kodzie systemu wszystkie bity mają tę samą wartość, natomiast w kodach komend dobrze widać, które bity mają inne wartości. Dekodując kod typu Manchester warto posiłkować się notą dotyczącą kodu RC5.
Innym sposobem jest odczytywanie połówek bitów, a następnie programowa zamiana ciągów 01 i 10 na odpowiednie wartości. Zostawiam to już jednak do samodzielnych eksperymentów.
To nie koniec, sprawdź również
Przeczytaj powiązane artykuły oraz aktualnie popularne wpisy lub losuj inny artykuł »
asm, C, osycloskop, philips, pilot, przerwanie, rc5
Trwa ładowanie komentarzy...