Kursy • Poradniki • Inspirujące DIY • Forum
RFM12B i przerwania
Aby umożliwić programowi wykonywanie innych czynności w czasie nadawania powinniśmy poszukać możliwości sprawdzania stanu układu RFM12B bez konieczności ciągłego kontrolowania sygnału linii MISO.
Taką możliwość daje nam linia nIRQ – przyjmuje ona stan niski, gdy RFM zasygnalizuje różne zdarzenia wewnętrzne – nas interesować będą 2 z nich:
- odebranie 8 bitów (ich ilość konfigurowaliśmy, o czym wspominałem wcześniej)
- możliwość nadawania (zapisania do bufora nadawczego) kolejnego bajta z danymi.
W związku z tym podpinamy linię nIRQ do dowolnego pinu przerwania zewnętrznego, które skonfigurujemy na wyzwalanie przy stanie niskim. W przykładzie użyłem przerwania INT0 ulokowanego na pinie PD2 układu ATMega644p.
Linia nIRQ pozostanie w stanie niskim do momentu odczytu statusu układu, w związku z czym plan naszego działania będzie następujący:
- Ustawić parametry nadawania/odbioru, aktywować w procesorze przerwanie
- Po wystąpieniu przerwania odczytać status i sprawdzić, czy przerwanie wywołało interesujące nas zdarzenie
- Jeśli tak to wykonujemy działania dotyczące nadania/odbioru kolejnego bajta danych
- Po zakończeniu odbioru/nadawania wyłączamy przerwanie i wyłączamy układ RFM12B
Parę słów o transmisji
Musimy zacząć zastanawiać się nad organizacją transmisji – na razie przesyłaliśmy 1 bajt danych – co jednak, jeśli będziemy chcieli nadać inną ilość danych? I w dodatku różną przy różnych transmisjach? W takiej sytuacji warto zdefiniować sobie sposób w jaki będziemy kodować dane – u nas:
- Przyjmiemy, że dane przesyłać będziemy tylko w kodowaniu ASCII
- Pozostałe znaki niedrukowane wykorzystamy do sterowania programem
- Dodatkowo ustalmy, że znak o wartości 0x03 będzie oznaczał koniec transmisji
Tak więc nadawana ramka będzie mieć postać:
Zaczynamy
Na początku musimy zdefiniować funkcje wyłączającej układ RFM12B i dezaktywujące przerwanie:
1 2 3 4 5 6 7 8 |
void Rfm_stop(void){ //wyłączamy przerwanie RFM_INT_MASK &= ~(1<<RFM_INT_NUM); //wyłączamy nadajnik i odbiornik Rfm_xmit(POWER|DIS_CLKO); //kasujemy ewentualne przerwania Rfm_xmit(STATUS_READ); } |
Nie wymaga ona dodatkowego komentarza, w związku z czym zajmijmy się teraz funkcjami inicjalizacji nadawania i odbioru.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void Rfm_tx_start(void){ //informuję program w jakim stanie jest układ RFM rfm_state=RFM_TX; //włączamy nadajnik Rfm_xmit(STATUS_READ); Rfm_xmit(POWER|EN_TRANSMISSION|EN_SYNTH|EN_OSC|DIS_CLKO); //ustawiam wskaźnik nadawania na początek rfm_tx_h=0; //aktywuję przerwanie RFM_INT_MASK |= (1<<RFM_INT_NUM); } |
Najpierw ustawiamy w odpowiedniej zmiennej informację, że RFM będzie nadawał. Następnie, tak jak poprzednio, aktywujemy nadajnik, wcześniej resetując ewentualne przerwania, które wystąpiły przed wywołaniem tej funkcji. Kolejnym krokiem jest ustawienie wskaźnika aktualnie nadawanego bajta na początek bufora i na samym końcu aktywujemy przerwanie.
Zwrócę jeszcze uwagę na to, że w funkcji inicjalizacji włączamy również podciąganie linii przerwania.
1 |
RFM_INT_PORT |= (1<<RFM_INT_PORT_NUM);//włączenie podciągania na linii przerwania |
Bardzo podobnie wygląda funkcja inicjalizująca odbiór:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void Rfm_rx_prepare(void){ //informuję program w jakim stanie jest układ RFM rfm_state=RFM_RX; //włączamy odbiornik Rfm_xmit(POWER|EN_RX|EN_BASEBAND|EN_OSC|DIS_CLKO); _delay_ms(5); //para komend powodująca w efekcie działania reset synchronizacji odbiornika Rfm_xmit(FIFO_RST|FIFO_IT_8|HS_RST_DIS); Rfm_xmit(FIFO_RST|FIFO_IT_8|EN_AFT_SYNC|HS_RST_DIS); //odczytuję status, aby na pewno została zwolniona linia przerwania Rfm_xmit(STATUS_READ); //ustawiam wskaźnik odczytu na początek rfm_rx_h=0; //aktywuję przerwanie RFM_INT_MASK |= (1<<RFM_INT_NUM); } |
Podobnie zapisujemy w odpowiedniej zmiennej informację o tym, że teraz RFM jest skonfigurowany do odbioru danych, po czym włączamy odbiornik, resetujemy synchronizację. Po tym upewniamy się, że linia nIRQ nie wygeneruje przerwania resetując ewentualne zdarzenia, które wystąpiły przed wywołaniem tej funkcji. Następnie ustawiamy wskaźnik odbioru na początek bufora i aktywujemy przerwanie.
Ponadto definiujemy trzy funkcje:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
rfm_state_t Rfm_state_check(void){ return rfm_state;//sprawdzamy stan układu } void Rfm_rx_get(uint8_t* buffer, uint8_t* num){ for(uint8_t i=0;i<rfm_rx_h;i++){//kopiujemy zawartość bufora do wskazanego bufora w programie (*(buffer+i))=RFM_RX_BUF[i]; } *num=rfm_rx_h; } void Rfm_tx_set(uint8_t* buffer, uint8_t num, uint8_t sync){ RFM_TX_BUF[0]=0xAA;//stały bajt synchronizacji RFM_TX_BUF[1]=0x2D;//stały bajt synchronizacji RFM_TX_BUF[2]=sync;//definiowany bajt synchronizacji for(uint8_t i=0;i<num;i++){ RFM_TX_BUF[i+3]=(*(buffer+i)); } RFM_TX_BUF[num+3]=0x03;//bajt kończący ramkę RFM_TX_BUF[num+4]=0xAA;//bajt kończący transmisję(dummy byte) rfm_tx_t=num+4; } |
Pierwsza z nich służy do odczytu aktualnego stanu układu RFM, druga zaś kopiuje dane z bufora i liczbę odebranych bajtów danych do zmiennych w naszym programie.
Trzecia funkcja oprócz skopiowania danych z bufora programu do naszego bufora nadawczego i ustawienia liczby bajtów, które będą nadane dodaje do danych elementy stałe – czyli bajty synchronizacji (w tym ten definiowany) oraz bajty kończące transmisję.
Obsługa przerwania
Wreszcie możemy przyjrzeć się samej procedurze obsługi przerwania:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
ISR(INT0_vect){ //odczytuję status, aby na pewno została zwolniona linia przerwania uint16_t status=Rfm_xmit(STATUS_READ); //sprawdzić czy to to przerwanie if(status&(M_FIFO_IT|M_TX_READY)){//jeśli to przerwanie zostało wygenerowane if(rfm_state==RFM_RX){//jeśli mamy odbierać dane uint8_t data=Rfm_xmit(FIFO_READ);//to odczytujemy bufor fifo if(data==0x03){//jeśli to znak zakończenia transmisji rfm_state=RFM_RXC;//to ustawiamy stan zakończenia odbioru //i wyłączamy nadajnik Rfm_stop(); }else if(rfm_rx_h<RFM_BUFFER_SIZE){//jeśli w czasie odbioru rozmiar bufora nie zostanie przekroczony RFM_RX_BUF[rfm_rx_h++]=data;//to zapisujemy dane }else{//w przeciwnym wypadku ustawimy odpowiedni stan i wyłączymy odbiornik rfm_state=RFM_RXOVF; Rfm_stop(); } }else if(rfm_state==RFM_TX){//jeśli mamy wysyłać dane if(rfm_tx_h<=rfm_tx_t){//jeśli jeszcze nie wysłaliśmy wszystkich bajtów Rfm_xmit(TX_WRITE|RFM_TX_BUF[rfm_tx_h++]);//to wysyłamy dany bajt i inkrementujemy wskaźnik }else{//w przeciwnym razie wyłączamy nadajnik i sygnalizujemy zakończenie nadawania rfm_state=RFM_TXC; Rfm_stop(); } } }else{//jeśli to inne przerwanie związane z nadawaniem to podejmujemy stosowne działania if(rfm_state==RFM_TX&&status&M_TX_OVF){ rfm_state = RFM_TXOVF; Rfm_stop(); }else if(rfm_state==RFM_RX&&status&M_FIFO_OVF){ rfm_state = RFM_RXOVF; Rfm_stop(); } }//innych przerwań nie obsługujemy } |
Na samym początku rzecz jasna sprawdzamy, czy linia nIRQ przyjęła stan niski z powodu zdarzenia, które nas interesuje. W przeciwnym wypadku sprawdzamy jeszcze czy zdarzenie to nie jest spowodowane błędami nadajnika lub odbiornika.
Jeśli odpowiedź na pierwsze pytanie jest twierdząca to w zależności od tego czy nadajemy czy odbieramy podejmujemy odpowiednie działania.
W czasie odbioru:
- Odczytujemy odebrany bajt danych
- Jeśli jest to bajt kończący transmisję to sygnalizujemy zakończenie odbioru i wyłączamy odbiornik
- W przeciwnym razie zapisujemy odebrany bajt do bufora, o ile ten nie został przepełniony
- W przypadku przepełnienia bufora także sygnalizujemy taką sytuację i wyłączamy RFM
W czasie nadawania:
- Jeśli nie nadaliśmy wszystkich danych to wysyłamy kolejny bajt
- Po nadaniu wszystkich bajtów z bufora wyłączamy nadajnik i sygnalizujemy zakończenie nadawania
Teraz program nadajnika znacznie się uprościł:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
Rfm_spi_init();//inicjalizacja magistrali SPI Rfm_init();//wstępna konfiguracja układu RFM12B sei();//włączamy gobalny system przerwań. DDRB &= ~((1<<PB0)|(1<<PB1));//konfiguruję piny PB0-1 jako wejście PORTB |= (1<<PB0)|(1<<PB1);//i włączam podciąganie uint8_t data; while(1){ //sprawdzam przyciski i ustawiam odpowiednie bity w bajcie do nadania data = 0; if(!(PINB&(1<<PB0))){ data |= 1; } if(!(PINB&(1<<PB1))){ data |= 2; } if(data==0)continue;//jeśli nic nie wciśnięto to rozpoczynamy pętlę od początku sprintf(bufor,"%02X",data); Rfm_tx_set((uint8_t*)bufor,2,0xD4); Rfm_tx_start(); while(Rfm_state_check()==RFM_TX); _delay_ms(100); } |
Po standardowej inicjalizacji w pętli głównej postępujemy jak poprzednio, z tym, że dane do nadania kodujemy w zapisie heksadecymalnym i nadajemy jako 2 bajty w kodzie ASCII, po czym czekamy dopóki trwa nadawania. Normalnie zamiast tej pustej pętli while moglibyśmy wykonywać zupełnie inne zadania i jedynie w razie konieczności nadawania sprawdzać, czy poprzednia transmisja została ukończona lub nie wystąpiły błędy.
Podobnie dużo prostszy będzie program odbierający dane:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Rfm_spi_init();//inicjalizacja magistrali SPI uart_init(9600);//inicjalizacja USART sei();//włączamy przerwania do obsługi uart uart_puts("\r\n\r\nRFM12B - RECEIVER\r\n");//wyświetlamy powitanie Rfm_init();//inicjalizujemy układ RFM12B Rfm_xmit(SYNC_PATTERN|0xD4); //ustawiamy programowalny bajt synchronizacji na wartość 0xD4 //wykorzystamy tę funkcjonalność do adresowania wielu układów Rfm_rx_prepare(); while(1){ uint8_t tmp; switch(Rfm_state_check()){ case RFM_RXC: Rfm_rx_get(rx_buf,&tmp); Rfm_rx_prepare(); uart_puts("DATA: "); for(int i=0;i<tmp;i++){ sprintf(bufor,"%c",rx_buf[i]); uart_puts(bufor); } uart_puts("\r\n"); break; case RFM_RXOVF: Rfm_rx_prepare(); uart_puts("BUFFER OVF\r\n"); break; default:break; } } |
Tu także po standardowych przygotowaniach dokonujemy inicjalizacji odbioru danych. Dalej w pętli głównej sprawdzamy stan mechanizmu odbiorczego, po wykryciu zakończonego odbioru odczytujemy dane i przygotowujemy się do odbioru następnej paczki danych, po czym wyświetlamy odebrane dane. W przypadku błędu jedynie re-inicjalizujemy mechanizm odbiorczy.
Proste? Mam nadzieję, ze tak, bo zaraz przechodzimy do czegoś trudniejszego!
Drugi stopień wtajemniczenia…
…czyli o zakłóceniach i zmienia kierunku transmisji informacji.
Jak już zapewne zauważyliście układ RFM12B może albo nadawać albo odbierać dane – stanowi to pewne ograniczenie – teraz w razie konieczności wymiany danych w obu kierunkach musimy odpowiednio sterować „kierunkiem pracy” komunikujących się modułów, tak aby jeden z nich był nadajnikiem, a drugi odbiornikiem.
Najlepiej wyznaczyć wtedy jeden z układów jako nadrzędny, który będzie prosił inne układy o informacje, a te będą nadawały tylko i wyłącznie po otrzymaniu takiej prośby.
Kolejną kwestią, którą musimy się zająć są zakłócenia. Dotychczas nie sprawdzaliśmy czy dane, które otrzymaliśmy są prawidłowe, czy też zmienione w wyniku zakłóceń.
W związku z tym warto wprowadzić jakiś mechanizm zabezpieczający – w naszym przykładzie zastosuję generowanie sumy kontrolnej crc8, która będzie przesyłana na końcu ramki z danymi. Do wyliczanej sumy kontrolnej wplotę też adres układu, gdyż on także może ulec przekłamaniu, a dzięki temu będziemy mogli odrzucić ramkę z przekłamanym adresem (oczywiście istnieje prawdopodobieństwo tak niefortunnego zbiegu okoliczności, w którym odpowiednio przekłamana zostanie też dana kontrolna crc8, jednak prawdopodobieństwo takiego zdarzenia jest znikome).
Przykład ten jest już dosyć rozbudowany, ale jak poprzednio niemalże każda linijka została skomentowana, w związku z tym serdecznie zachęcam do jego samodzielnej analizy. Odpowiednie pliki projektów znajdują się w załączniku.
Przykład ten wymaga dodatkowego podpięcia na układzie SLAVE termometru DS18B20 do pinu PD7 wraz z odpowiednim rezystorem podciągającym.
Dodatkowo możemy wykonać dwa układy slave i zaobserwować jak sprawdza się możliwość adresowania kilku urządzeń pracujących na tym samym kanale.
W robotyce
Na koniec pora na garść inspiracji gdzie w robotyce można wykorzystać taką komunikację:
- Wydawanie poleceń robotowi (np.: start/stop)
- Monitorowanie pracy robota
- Kalibracja sensorów
- Komunikacja między kilkoma robotami
- i wiele innych
Mam nadzieję, ze artykuły te nieco przybliżyły Wam komunikację radiową i pozwolą na sprawne stosowanie modułów RFM12B w robotyce. W komentarzach czekam na Wasze pomysły, jak wykorzystać taką komunikację w robocie.
Powodzenia!
To nie koniec, sprawdź również
Przeczytaj powiązane artykuły oraz aktualnie popularne wpisy lub losuj inny artykuł »
bezprzewodowa, komunikacja, moduł, RFM12B, SPI
Trwa ładowanie komentarzy...