Kurs AVR-GCC Dekoder protokołu RC5
Pilot na podczerwień to prosty i tani sposób na sterowanie bezprzewodowe urządzeniami z bliskiej odległości. W tym artykule opisałem jak wykonać odbiornik współpracujący ze standardowym pilotem RC5. Funkcję dekodera protokołu RC5 będzie oczywiście pełnił mikrokontroler atmega - napiszemy taki program w języku C. Odbiornik można wykorzystać do budowy różnych urządzeń sterowanych pilotem.
Scalony odbiornik podczerwieni SFH5110-36
Obok pilota RC5 i mikrokontrolera AVR ATMEGA będzie potrzebny scalony odbiornik podczerwieni SFH5110-36, układ ten jest łatwo dostępny i kosztuje ok 3zł.
Rysunek poniżej wyjaśnia sposób działania układu SFH5110-36. Odbiornik SFH5110-36 posiada trzy wyprowadzenia: 1-wyjście cyfrowe, 2-masa, 3-zasilanie(5V) . Normalnie na wyjściu odbiornika SFH5110-36 jest wysoki stan napięcia, gdy odbiornik zostanie oświetlony pilotem, na wyjściu pojawi się stan niski. Pilot świeci diodami IR impulsowo, z częstotliwością ok 36kHz, gdyż odbiornik SFH5110-36 reaguje tylko na oświetlenie impulsowe o częstotliwości 36kHz, eliminuje to wpływ zakłóceń z innych źródeł światła.
Scalony odbiornik podczerwieni podobny do SFH5110-36 można wyciągnąć ze zużytego TV lub innego sprzętu. Przed wylutowaniem odbiornika należy koniecznie rozpoznać na płycie do których wyprowadzeń układu przyłączone jest zasilanie, a które jest wyjściem danych.
Pilot RC5
Chociaż w każdym porządnym domu znajdzie się kilka pilotów na podczerwień, to jest całkiem prawdopodobne, że żaden nie działa zgodnie z protokołem Philips RC5. Ale to żaden problem. Nowiutkiego pilota RC5 kupisz na allegro w cenie ok 5..7zł. Ja posłużę tu się pilotem od starego telewizora, fotka poniżej, pilot ten ma w środku układ scalony SAA3010P.
Protokół zdalnego sterowania RC5
Wciśnięcie przycisku w pilocie RC5 skutkuje wysłaniem ramki danych, która składa się z czternastu bitów danych. Najpierw idą dwa bity startowe, oba zawsze mają wartości 1. Następny jest bit kontrolny toggle, bit ten zmienia swoją wartość na przeciwną, za każdym razem, kiedy naciśnięty jest któryś przycisk w pilocie. W przypadku, gdy któryś przycisk zostanie przytrzymany dłużej, pilot będzie wysyłał identyczne ramki danych(bez zmiany bitu toggle) - aż do chili zwolnienia przycisku. Kolejne pięć bitów sys5..sys0 to adres systemu (urządzenia), dla TV adres ma wartość 0. Pozostałe sześć bitów cmd4..cmd0, zawierają numer przycisku.
W protokole RC5 wykorzystuje się kodowanie bitów Manchaster. Bit o wartości 1 kodowany jest jako przejście poziomu sygnału z niskiego na wysoki, a bit o wartości 0, jako przejście z poziomu wysokiego na niski. W obu przypadkach zmiana poziomu sygnału następuje w połowie czasu trwania bitu. Czas trwania każdego bitu wynosi ok 1.8 milisekundy.
Pilot RC5 przesyła kolejne ramki danych w odstępie czasu nie mniejszym niż okres trwania 50 bitów.
Przykładowy program dekodera RC5
Program będzie dekodował komendy pilota RC5 i prezentował wynik na siedmiu diodach LED. Sześć diod pokaże numer przycisku, a siódma - toggle bit. Toggle bit zmienia swój stan na przeciwny przy każdym wciśnięciu przycisku. Siedem diod LED przyłączyłem do portu D AVRa, a odbiornik podczerwieni SFH5110-36 do PB0.
W programie wykorzystano przerwanie wywoływane przepełnieniem timera0 - "Timer0 Overflow". Timer0 skonfigurowany jest tak, że przerwania występują co 32 mikrosekundy. Z każdym wystąpieniem przerwania inkrementowana jest globalna zmienna "timerL", a co 256 wystąpień przerwania, inkrementowana jest globalna zmienna "timerH". Zmienne "timerL" i "timerH" tworzą zegary, które użyjemy w programie do mierzenia czasu trwania impulsów i do wymierzania odcinków czasu.
Wykrywaniem i dekodowaniem komend rc5 zajmuje się funkcja "detect". Funkcja "detect" uruchamiana jest w głównej pętli programu, zwraca 12-bitowy kod komendy RC5, albo wartość -1, jeśli w okresie 131ms nie wykryje komendy lub wystąpi błąd.
Na początku funkcji "detect" program odczytuje w pętli stań wejścia uC, do którego podłączono odbiornik podczerwieni SFH5110-36, oczekując okresu ciszy trwającego co najmniej 3.5ms - cisza to stan wysoki napięcia na wyjściu SFH5110-36. Przypominam, że normalnie na wyjściu odbiornika SFH5110-36 jest wysoki stan napięcia, a gdy odbiornik oświetlany jest pilotem, to na wyjściu pojawia się stan niski. Jeśli w ciągu 131ms nie wystąpił okres ciszy trwający 3.5ms, to funkcja kończy działanie zwracając kod -1, oznaczający brak komendy. Następnie program oczekuje opadającego zbocza sygnału w połowie pierwszego bitu startowego
Po wykryciu bitu startowego mierzony jest czas trwania niskiego poziomu sygnału. Jeśli nie wykryto pierwszego bitu startowego w okresie 131ms, albo mierzony czas trwania poziomu niskie sygnału w pierwszym bicie startowym okazał się dłuższy niż 1.1ms, to funkcja kończy działanie zwracając kod oznaczający brak komendy. Dalej program oczekuje opadającego zbocza w środku drugiego bitu startowego. Funkcja wykorzystuje opadające lub rosnące zbocze sygnału w połowie każdego bitu do synchronizacji. Odczyt wartości kolejnego bitu ramki dokonuje się po okresie 3/4 czasu trwania bitu od momentu wykrycia zbocza w środku poprzedniego bitu. Odczytana wartość bitu zachowywana jest w zmiennej. Dalej, jeśli odczytano wartość bitu 1, to program oczekuje wystąpienia zbocza opadającego w środku bitu, a jeśli odczytano wartość bitu 0, zbocza narastającego. W momencie wykrycia krawędzi w środku bitu następuje wyzerowanie timera i cała akcja jest powtarzana dla kolejnego bitu. Jeśli program nie wykryje odpowiedniego zbocza w okresie 5/4 czasu trwania bitu od momentu poprzedniej synchronizacji, to funkcja kończy działanie z błędem.
A oto nasz programik, całość w jednym pliku.
//--------------------------------------------------------------- // Plik "main.c" // // KURS AVR-GCC (abxyz.bplaced.net) // // Dekoder RC5 // // (schemat i opis działania w artykule) // testowanie na atmega8 (8MHz) //--------------------------------------------------------------- #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> // Odbiornik podczerwieni SFH5110 przyłączona do portu PB0 #define RC5_IN (PINB & (1<<0)) // typedef unsigned char u8; typedef unsigned int uint; // Zmienne globalne pełnią rolę programowych zegarów // napędzanych przerwaniem TIMER0_OVF volatile u8 timerL; volatile u8 timerH; //--------------------------------------------------------------- // Funkcja konfiguruje i uruchamia Timer0 // oraz włącza przerwanie od przepełnienia timera, // przerwanie powinno występować co 32us. //--------------------------------------------------------------- void init_rc5() { //atmega8 TCCR0 = (1<<CS00); // włącza Timer0 TIMSK = (1<<TOIE0); // włącza przerwanie "Timer0 Overflow" /* //atmega88 TCCR0B = (1<<CS00); TIMSK0 = (1<<TOIE0); */ // Zezwala na przerwania sei(); } //--------------------------------------------------------------- // Procedura obsługi przerwania Timer0 Overflow" //--------------------------------------------------------------- ISR(TIMER0_OVF_vect) { volatile static u8 inttemp; // zmienna timerL zwiększa się co 32us timerL++; // zmienna timerH zwiększa się co 8.192ms (32us*256) inttemp++; if(!inttemp ) timerH++; } //--------------------------------------------------------------- // Funkcja wykrywa i dekoduje komendę pilota RC5 //--------------------------------------------------------------- uint detect() { u8 temp; u8 ref1; u8 ref2; u8 bitcnt; uint command; timerH = 0; timerL = 0; // Czeka na okres ciszy na linii wejścia uC trwający 3.5ms // Jeśli nie wykryje takiego okresu ciszy w ciągu 131ms, // to kończy działanie funkcji z błędem while( timerL<110) { if(timerH>=16) return command = -1; if(!RC5_IN) timerL = 0; } // Czeka na pierwszy bit startowy. // Jeśli nie wykryje bitu startowego w ciągu 131ms, // to kończy działanie funkcji z błędem while(RC5_IN) if(timerH>=16) return command = -1 ; // Pomiar czasu trwani niskiego poziom syganłu // w pierwszym bicie startowym. // Jeśli nie wykryje rosnącego zbocza sygnału w ciągu // 1ms, to kończy działanie funkcji z błędem timerL = 0; while(!RC5_IN) if(timerL>34) return command = -1; // temp = timerL; timerL = 0; // ref1 - oblicza 3/4 czasu trwania bitu ref1 =temp+(temp>>1); // ref2 - oblicza 5/4 czasu trwania bitu ref2 =(temp<<1)+(temp>>1); // Oczekuje na zbocze opadające sygnału w środku drugiego // bitu startowego. // Jeśli nie wykryje zbocza w ciągu 3/4 czasu trwania // bitu, to kończy działanie funkcji z błędem while(RC5_IN) if(timerL > ref1) return command = -1; // W momencie wykrycia zbocza sygnału, synchronizuje // zmieną timerL dla próbkowania bitu toggle timerL = 0; // Odczytuje dekoduje pozostałe 12 bitów polecenia rc5 for(bitcnt=0, command = 0; bitcnt <12; bitcnt++) { // Czeka 3/4 czasu trwania bitu od momentu wykrycia // zbocza sygnału w połowie poprzedniego bitu while(timerL < ref1) {}; // Próbkuje - odczytuje port we uC if(!RC5_IN) { // Jeśli odczytano 0, zapamiętuje w zmiennej // "command" bit o wartości 0 command <<= 1 ; // Oczekuje na zbocze rosnące sygnału w środku bitu. // Jeśli nie wykryje zbocza w ciągu 5/4 czasu trwania // bitu, to kończy działanie funkcji z błędem while(!RC5_IN) if(timerL > ref2) return command = -1; } else { // Jeśli odczytano 1, zapamiętuje w zmiennej // "command" bit o wartości 1 command = (command <<1 ) | 0x01; // Oczekuje na zbocze opadające sygnału w środku bitu. // Jeśli nie wykryje zbocza w ciągu 5/4 czasu trwania // bitu, to kończy działanie funkcji z błędem while(RC5_IN) if(timerL > ref2) return command = -1; } // W momencie wykrycia zbocza sygnału, synchronizuje // zmieną timerL dla próbkowania kolejnego bitu timerL = 0; } // Zwraca kod polecenia rc5 // bity 0..5 numer przycisku // bity 6..10 kod systemu(urządzenia) // bit 11 toggle bit return command; } //--------------------------------------------------------------- // GLÓWNA FUNKCJA PROGRAMU //--------------------------------------------------------------- int main(void) { // uint cmd; u8 out; // Porty PD0..PD6 wyjściami - diody LED DDRD = 0x7f; PORTD = 0x00; // uruchamia Timer0 i przerwanie init_rc5(); while (1) { // Wykrywa i dekoduje polecenie pilota RC5 cmd = detect(); // Jeśli odebrano komendę if(cmd != -1) { // Na sześciu diodach LED pokaże numer polecenia rc5, // a na siódmej LED - toggle bit out = (cmd & (1<<11)) >> 5; out |= cmd & 0x3f; PORTD = (out); } } return 0; }
Ważne! Program przeznaczony jest dla uC atmega8 taktowanego zegarem 8MHz. Jeśli mikrokontroler ma pracować z częstotliwością inną niż 8MHz, to program będzie wymagał drobnego dopasowania. Jeśli masz z tym problem, pytaj na forum.
Katalog z plikami projektu można pobrać klikając w link dekoder_rc5.zip. Kod wynikowy programu ma wielkość 400 bajtów.
Przykładowy program pilota RC5
Jeśliby ktoś chciał samodzielnie zbudować pilota RC5, to dołączam przykładowy programik do uruchomienia na atmega8. Jak napisano wcześniej, pilot świeci diodami IR impulsowo, z częstotliwością ok 36kHz, gdyż odbiornik SFH5110-36 reaguje tylko na oświetlenie impulsowe o częstotliwości 36kHz, eliminuje to wpływ zakłóceń z innych źródeł światła. W programie pilota użyto układ czasowy Timmer2 uC atmega8 do generowania przebiegu prostokątnego o częstotliwości 36kHz. Ponieważ dotąd jeszcze w naszym kursie nie przerabialiśmy układów czasowych AVRów, więc postaram się krótko wyjaśnić co i jak. Tajmer2 jest ośmiobitowym rejestrem działającym jak licznik, w naszym przykładzie tajmer2 zlicza cykle zegara taktującego mikrokontroler - liczy od zera w górę. Timer2 został skonfigurowany do pracy w trybie CTC(Clear Timer on Compare Match). Czyli w momencie, gdy zawartość tajmera zrówna się z zawartością rejestru OCR2, timer jest zerowany, a stan wyjścia na wyprowadzeniu OC2(w atemega8 - PB3) zmienia się na przeciwny. W wyniku otrzymujemy na wyjściu OC2 sygnał prostokątny, którego częstotliwość zależy od liczby zapisanej w rejestrze OCR2 i od częstotliwości zegara taktującego mikrokontroler. Diodę nadawczą IR należy przyłączyć do wyprowadzenia OC2 AVRa, najlepiej poprzez jakiś tranzystor wzmacniający prąd, aby wystarczając mocno świeciła.
Działanie programu pilota jest zupełnie proste: Funkcja "send_cmd" w pętli wysyła ramkę danych protokołu rc5 o długości 14 bitów. Jeśli wysyłany bit danych ma wartość 1, to przez pierwszą połowę czasu trwania bitu tajmer2 jest wyłączony i dioda IR nie świeci; a w drugiej połowie czasu trwania bitu tajmer2 pracuje - dioda IR świeci. Jeśli wysyłany bit ma wartość 0, to w pierwszej połowie bitu tajmer2 pracuje - dioda IR świeci, w drugiej połowie bitu tajmer2 stoi- dioda IR nie świeci.
Jeszcze taka uwaga. Przykłady w naszym kursie są maksymalnie uproszczone. Ale ponieważ pilot zasilany jest z baterii, więc należałoby zoptymalizować program i całą konstrukcję pilota w kierunku minimalizacji zużycia prądu.
/*************************************************************** Plik "main.c" KURS AVR-GCC (abxyz.bplaced.net) nadajnik RC5 (schemat i opis działania w artykule) testowanie na atmega8 (8MHz) *****************************************************************/ #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> typedef unsigned char u8; typedef unsigned int uint; //--------------------------------------------------------------- // Funkcja wysyła komendę RC5 //--------------------------------------------------------------- void send_cmd(uint cmd) { uint m; // dołącza 2 bity startowe cmd |= (3<<12); for(m=(1<<13); m>0; m>>=1) { if(cmd & m)// jeśli bit o wartości 1 { // Dioda nadawcza nie świeci TCCR2= (1<<FOC2)|(1<<COM21)|(1<<CS20); _delay_ms(0.89); // czas trwania połowy bitu // świeci TCCR2= (1<<FOC2)|(1<<WGM21)|(1<<COM20)|(1<<CS20); _delay_ms(0.89); } else // jeśli bit o wartości 0 { // Dioda nadawcza świeci TCCR2= (1<<FOC2)|(1<<WGM21)|(1<<COM20)|(1<<CS20); _delay_ms(0.89); // Nie świeci TCCR2= (1<<FOC2)|(1<<COM21)|(1<<CS20); _delay_ms(0.89); } } TCCR2= (1<<FOC2)|(1<<COM21)|(1<<CS20); // wyłączamy LED i timer2 TCCR2= 0; } //--------------------------------------------------------------- // GLÓWNA FUNKCJA PROGRAMU //--------------------------------------------------------------- int main(void) { uint i,cmd; u8 toggle = 0 ; // PB3(OC2) wyjście - dioda nadawcza podczerwieni DDRB = (1<<PB3); // Timer2, CTC mode, no prescaling ,Toggle OC2 on compare match // TCCR2= (1<<WGM21)|(1<<COM20)|(1<<CS20); // OC2 - 36 KHz OCR2 = 110; while(1) // Wysyła po kolei wszystkie komendy for(i=0; i<64; i++) { // do kolejnej komendy dostawia toggle bit cmd = i|((toggle & 0x01)<<11); toggle++; // Wysyła komendę send_cmd(cmd); _delay_ms(89); // 50 bitów "ciszy" // Komendy wysyłamy sobie co sekundę _delay_ms(1000); } return 0; }
Podobnie jak program dekodera program pilota przeznaczony jest dla uC atmega8 taktowanego zegarem 8MHz. Jeśli mikrokontroler ma pracować z częstotliwością inną niż 8MHz, to program będzie wymagał małego dopasowania.
Katalog z plikami projektu można pobrać klikając w link pilot_rc5.zip.
Pisząc program dekodera RC5 opierałem się na atmelowskiej nocie aplikacyjnej AVR410 doc1473.pdf.
Dekoder protokołu RC5 - ciąg dalszy
Ostatnio ktoś na forum wytknął, że program dekodera RC5 marnuje czas procesora testując w pętlach stan linii wejścia mikrokontrolera. W odpowiedzi zamieszczam tu drugą wersję programu dekodera RC5, w której wykorzystano przerwanie zewnętrzne mikrokontrolera. W tej wersji całość kodu zajmująca się odczytem komend RC5 umieszczona została w procedurze obsługi przerwania INT0, zaś główna pętla programu jest pusta, może wykonywać inne zadanie.
Program dekodera przeznaczony jest dla uC ATMEGA taktowanego zegarem 8MHz. Wyjście odbiornika podczerwieni należy przyłączyć do portu PD2(INT0), a osiem diod LED do portu B. Na sześciu diodach LED wyświetla się numer komendy RC5, a ósma dioda LED pokazuje stan bitu toggle; bit ten zmienia swoją wartość na przeciwną, za każdym razem, kiedy naciśnięty jest któryś przycisk w pilocie. Oprócz przerwania zewnętrznego INT0, program korzysta z Timera0, do pomiaru szerokości impulsów. Dlatego, jeśli mikrokontroler ma pracować z częstotliwością inną niż 8MHz, to program będzie wymagał małego dopasowania.
Katalog z plikami źródłowymi projektu można pobrać klikając w link dekoder_rc5_v2.zip.
Katalog zawiera także plik Makefile, który należy dostosować do posiadanego "sprzętu", tzn. wybrać typ AVRa, częstotliwość pracy mikrokontrolera, programator. Najlepiej skompilować program otwierając projekt w edytorzy "Programers's Notepad" dostarczonym wraz z pakietem "WinAVR"; jeśli nie wiesz jak, to polecam artykuł "Szybki start z WinAVR"
Programy na tej stronie są tylko przykładami do artykułu, celowo pisane prosto i krótko choć niekoniecznie w optymalny sposób - bo doby przykład, to krótki przykład:)