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:
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.
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.
RFM_INT_PORT |= (1<<RFM_INT_PORT_NUM);//włączenie podciągania na linii przerwania
Bardzo podobnie wygląda funkcja inicjalizująca odbiór:
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:
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:
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ł:
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:
Rfm_spi_init();//inicjalizacja magistrali SPI
uart_init(9600);//inicjalizacja USART
sei();//włączamy przerwania do obsługi uart
uart_puts("rnrnRFM12B - RECEIVERrn");//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("rn");
break;
case RFM_RXOVF:
Rfm_rx_prepare();
uart_puts("BUFFER OVFrn");
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!
Załączniki
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...