Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:p2p-nrf24l01

Point-to-point spoj s nRF24L01

Zadání projektu

Cílem projektu je, jak je již z názvu patrné, vytvořit experimentální rádiový spoj v pásmu 2,4 GHz pomocí páru transceiverů. Spoj bude moci obousměrně komunikovat a měřit parametry spojení jako RTT, BER apod. Cílem projektu je také vyzkoušet, jaký vliv bude mít na tyto parametry vzdálenost mezi transceivery.

Hardware a vývojová platforma

Pro projekt byla použita hardwarová platforma FRDM od společnosti Freescale (NXP) s procesorem KL25Z s jádrem Cortex M0+: FRDM-KL25Z. Jako transceivery byly použity moduly nRF24L01+ od společnosti Nordic Semiconductor s integrovanou anténou. Moduly jsou velice kompaktní a je možné si na nich nastavit kmitočet v rozsahu 2400 - 2483 MHz, dále také datovou rychlost a výkon vysílače. Distributoři tvrdí, že dosah transceiveru je až 100 metrů ve volném prostoru. Vývojová deska a její pinout je uveden na obrázku níže. Pod ním je na obrázku samotný modul nRF24L01+. Na třetím obrázku je uvedeno blokové schéma modulu nRF24L01.

Modul je s vývojovou deskou spojen pomocí vodičů přes sběrnici SPI. K desce bylo nutno připájet piny, aby bylo možné obě zařízení propojit. Pro komunikaci bylo zvoleno SPI na pinech PTD1 - PTD3, viz obrázek. Zde se však objevila drobná vada, na pinu PTD1 je signál SCK sběrnice SPI, avšak zároveň také ovládání modré LED. To způsobuje svit modré LED při připojení SPI, signál SCK prakticky funguje jako PWM pro modrou LED. Tento designový krok mi přijde přinejmenším velmi nelogický. Řešení by spočívalo v připojení modulu na jinou sběrnici SPI, avšak kvůli tomu by se musely zbytečně pájet další piny, což je pouze kvůli kosmetické vadě zbytečné. Svit modré LED tak indikuje činnost SPI.

Původně bylo pro vývoj firmwaru použito prostředí Freescale Kinetis, avšak po jednom dni neúspěšné práce byl pro zachování pevných nervů zvolen mbed, což nakonec přineslo řadu výhod. Asi největší z nich spočívala v již hotové knihovně, která se snadno do projektu importovala. Odkaz na ni je zde: https://developer.mbed.org/cookbook/nRF24L01-wireless-transceiver.

Vývoj firmwaru

Po propojení obou zařízení následoval samotný vývoj firmwaru. Jedno zařízení funguje primárně jako vysílač, který odešle zprávu směrem k přijímači, jenž na ni poté odpovídá. Vysílač následně počítá RTT, BER, počet ACK a NACK. Přijímač vypisuje přijaté a odeslané zprávy a také jejich pořadové číslo. V následujícím kódu jsou ukázány základní funkce pro blikání LED, které indikují odeslání a přijetí zprávy (červená odeslání, zelená přijetí), výpočet BER a automatické odesílání zpráv. U blikání LED je nutné použít neblokující čekání, aby nebyl ovlivněn parametr RTT. Krátké blokující čekání je v programu použito dvakrát, a to z toho důvodu aby při rychlém odeslání a příjmu neblikly obě LED zaráz a výsledná barva RGB LED by pak byla bílá. Navíc z důvodu trvalého svitu modré LED není výsledná barva červená a zelená, ale purpurová a azurová.

//Functions for LED blinks
void led_red_flip() {
    rled = !rled;
    rled_ticker.detach();
 
}
 
void led_green_flip() {
    gled = !gled;
    gled_ticker.detach();    
}

Bitová chybovost se počítá tak, že se pomocí funkce XOR porovnává přijatá zpráva s očekávanou zprávou a pokud není výsledek nulový, počet chyb se zvýší podle počtu nenulových (chybných) bitů.

//Function for BER calculation
void ber_calculate(char recData[]) {
    float ber = 0;
    char sent_string[] = "ACK\r"; //Compared string
 
    all_bits += (strlen(recData) *8); //Increase the size if incoming bits
 
    for (uint8_t i=0; i<= strlen(recData); i++) {
        recData[i] ^= sent_string[i]; //XOR received data with expected string
 
        //If there was no error, all bits must be zero
        while(recData[i] !=0) {
            //If there was an error, increase number of error bits and shift by one
            if (recData[i] & 1) { 
                error_bits++;
                recData[i] >>= 1;
            }
        }
 
 
    }
    ber =  (((float)error_bits)/all_bits) *100; //BER calculation
    pc.printf("BER: %3.4f %%\n", ber);
 
}

Následuje funkce pro automatické odesílání zpráv. Vysílač odesílá zprávu ping a přijímač poté odpovídá zprávou ACK. Odeslaná data se navíc vypisují do konzole.

//Function to send data automatically
void send() {
    //If the device is TX, send ping message
    if (tx_mode) {
        sprintf(txData, "ping\r");
        rtt_timer.start();
    }
    //If the device is RX, send ACK message
    else {
        sprintf(txData, "ACK\r");
    }
    //Print sent data and send it via nrf24l01
    pc.printf("\nSent: %s", txData);
    txDataCnt = 4;
    my_nrf24l01p.write( NRF24L01P_PIPE_P0, txData, txDataCnt );
    //Small blocking delay for LED blink
    if (!tx_mode) {
        wait_ms(200);
    }
    rled = 0; 
    rled_ticker.attach(&led_red_flip, 0.2);
}

Následuje výpis hlavní funkce main. Ta obstarává samotný příjem a odeslání zpráv, přičemž výsledky jsou poté vypisovány do konzole na PC. Z kódu je patrné, že některé bloky se vykonávají pouze pokud je zařízení nastaveno buď jako přijímač nebo vysílač. Je také možné si nastavit, zda budou zařízení odesílat zprávy v pravidelných časových intervalech nebo zda bude zvolen vstup z klávesnice. Proto je také pomocí registrů nRF24l01+ nastavena proměnná délka paketů, přičemž maximum je 32 bajtů (velikost FIFO paměti). V projektu je zvolena maximální velikost 16 bajtů. Zároveň pokud je zvolen vstup z klávesnice, nepočítá se parametr BER neboť příchozí zprávy mohou mít libovolný tvar, vypisují se pouze počty potvrzených a nepotvrzených zpráv. Odesílání zpráv v automatickém módu probíhá po nastavených časových intervalech. Při vstupu z klávesnice se odeslání provede po stisknutí tlačítka Enter nebo při maximální velikosti zprávy (16 znaků).

int main() {
    Timer send_timer; //Timer for sending automatic messages
    uint16_t lost_num = 0; //Number of lost packets
    uint16_t ack_num = 0; //Number of ACKs
    uint16_t rec_messages = 0;  //Number of received messages
    char rxData[TRANSFER_SIZE];
 
    my_nrf24l01p.powerUp();
    settings(); //Function for user menu 
 
    // Display the (default) setup of the nRF24L01+ chip
    pc.printf( "nRF24L01 Frequency    : %d MHz\n",  my_nrf24l01p.getRfFrequency() );
    pc.printf( "nRF24L01 Output power : %d dBm\n",  my_nrf24l01p.getRfOutputPower() );
    pc.printf( "nRF24L01 Data Rate    : %d kbps\n", my_nrf24l01p.getAirDataRate() );
    pc.printf( "nRF24L01 TX Address   : 0x%010llX\n", my_nrf24l01p.getTxAddress() );
    pc.printf( "nRF24L01 RX Address   : 0x%010llX\n", my_nrf24l01p.getRxAddress() );
    //If TX mode and automatic mode are chosen, print the set interval for messages 
    if (tx_mode) {
        pc.printf( "Message Interval      : %d seconds\n",  interval);
    }
    //If keyboard mode is set, print this message
    if (keyboard_mode) {
        pc.printf( "Type keys to test transfers:\r\n  (transfers are grouped into variable characters, maximum is a group of %d characters)\r\n", TRANSFER_SIZE );
    }
    //Set registers for dynamic payload
    my_nrf24l01p.setRegister(_NRF24L01P_REG_FEATURE, 0x04); 
    my_nrf24l01p.setRegister(_NRF24L01P_REG_DYNPD, 0x01);
    //Start up the nrf24L01
    my_nrf24l01p.enable();
    my_nrf24l01p.setReceiveMode();
 
    //Start the timer for automatic transmissions
    if (!keyboard_mode && tx_mode) {
        send_timer.start();
    }
 
 
    while (1) {
        //Send messages periodically
        if (!keyboard_mode && tx_mode) {
            if ((int) send_timer.read_ms() % (interval*1000) == 0) {
                send();    
            }
        }
        //If keyboard mode is activated, read user input
        if (keyboard_mode) {
            // If we've received anything over the host serial link...
            if ( pc.readable() ) {
                // ...add it to the transmit buffer
                txData[txDataCnt++] = pc.getc();
                // If the transmit buffer is full or Enter is pressed
                if ( txDataCnt >= sizeof( txData ) || txData[txDataCnt-1] == '\r') {
                    // Send the transmit buffer via the nRF24L01+
                    my_nrf24l01p.write( NRF24L01P_PIPE_P0, txData, txDataCnt );
                    pc.printf("\nSent: %s\n", txData);
                    //Start the RTT timer in TX Mode
                    if (tx_mode) {
                        rtt_timer.start();
                    }   
                    // Blink Red LED to indicate message sent
                    txDataCnt = 0;
                    rled = 0; 
                    rled_ticker.attach(&led_red_flip, 0.2);
                }
 
            }
        }
 
 
        // If we've received anything in the nRF24L01+...
        if ( my_nrf24l01p.readable() ) {
            //Stop RTT timer
            if (tx_mode) {
                rtt_timer.stop();
                wait_ms(200);
            }
            // ...read the data into the receive buffer
            my_nrf24l01p.read( NRF24L01P_PIPE_P0, rxData, sizeof( rxData ) );
            pc.printf("Received: %s", rxData);
            //Blink green led to indicate message received
            gled = 0;
            gled_ticker.attach(led_green_flip, 0.2);
            //Send automatic response in RX mode  
            if (!keyboard_mode && !tx_mode) {
                send();
            }
 
            if (tx_mode) {
                //If a message wasn't received between automatic transmissions, increase BER and NACKs 
                if ((int) rtt_timer.read() >= interval && !keyboard_mode) {
                    lost_num = ((int) rtt_timer.read()/interval);
                    error_bits += (strlen(rxData) * 8) * lost_num;
                    all_bits += (strlen(rxData) * 8) * lost_num;
                }
                ack_num++; //If message was received between retransmissions, increase number of ACKs
                pc.printf("RTT: %3.4f\n", rtt_timer.read()); //Print RTT
                pc.printf("Number of ACKs: %d\n", ack_num); //Number of ACKs
                pc.printf("Number of NACKs: %d\n", lost_num); //Number of NACKs
                rtt_timer.reset(); //Reset the RTT timer
                //Calculate BER, but only in automatic mode
                if (!keyboard_mode) {
                    ber_calculate(rxData);
                }
            //If RX mode is activated, diplay number of received messages
            } else {
                rec_messages++;
                pc.printf("Recieved message No.: %d\n\n", rec_messages);
            }
 
        }
 
    }
}

Na začátku funkce main se ještě volá funkce settings. Jejím úkolem je interakce s uživatelem. Pro lepší ovládání bylo vytvořeno jednoduché menu v konzolovém okně, které se zavolá vždy po spuštění programu. Na začátku si uživatel zvolí, zda bude zařízení pracovat primárně jako vysílač či přijímač. Poté je nastaven vstup z klávesnice nebo automatické odesílání zpráv. Pokud je zvoleno automatické odesílání, je na vysílači možné nastavit časový interval odesílání zpráv. Následně je na klávesnici zvolen kmitočet z povoleného rozsahu. Je třeba jej volit s ohledem na datovou rychlost. Poté je možné si vybrat z několika vysílacích výkonů, které modul umožňuje nastavit. Nakonec se nastaví samotná datová rychlost, je možné si vybrat ze tří možností: 250 kbit/s, 1 Mbit/s a 2 Mbit/s. Po nastavení správných parametrů mohou moduly komunikovat. Pokud je nějaká možnost zvolena špatně, zvolí se výchozí nastavení.

void settings() {
    uint16_t frequency;
    char input[]= "";
    char *ptr;
 
    pc2.printf("***Experimental 2.4 GHz Radio Link. Setup below: ***\n\n");
 
    pc2.printf("Select TX or RX Mode. TX calculates BER (only in automatic mode) and ACKs,\n RX shows number of incoming packets.:\n");
    pc2.printf("1: TX\n2: RX\n");
    //Select TX or RX mode
    switch (pc2.getc()) {
    case '1':
        tx_mode = true;
    break;
    case '2':
        tx_mode = false;
    break;
    default:
        tx_mode = true;
        pc2.printf("Wrong number! Default TX Mode was set");
        wait_ms(1000);
    break;
    }
    //Clear the console window
    pc2.puts("\e[2J\e[H");
 
    pc2.printf("Select Input. If automatic is set, the message is transmitted repeatedly\n in short intervals.:\n");
    pc2.printf("1: Keyboard\n2: Automatic\n");
    //Select keyboard input or automatic mode, if automatic mode is set and the device is in TX mode, select time interval
    switch (pc2.getc()) {
    case '1':
        keyboard_mode = true;
    break;
    case '2':
        keyboard_mode = false;
        if (tx_mode) {
            pc2.puts("\e[2J\e[H");
            pc2.printf("Please specify a time interval. The interval must be an integer number:\n");
            interval = pc2.getc() - '0';
        }
    break;
    default:
        keyboard_mode = true;
        pc2.printf("Wrong number! Default Automatic mode was set");
        wait_ms(1000);
    break;
    }
    //Clear the console window
    pc2.puts("\e[2J\e[H");
 
    pc2.printf("Type in the RF channel depending on the speed in the range of 2400 - 2525.\n The channel must be the same for RX and TX!: \n");
    //Type the number of desired RF channel in range
    pc2.gets(input, 5);
    frequency = (int) strtol(input, &ptr, 10);
    pc2.printf("Selected frequency: %d\n", frequency);
    if (frequency >= 2400 && frequency <=2525) {
        nrf24l01p.setRfFrequency(frequency);
    }
    //If wrong number was set, default frequency will be set
    else {
        pc2.printf("Wrong frequency! Default frequency was set (2402 MHz)\n");
    }
    //Clear the console window
    pc2.puts("\e[2J\e[H");
 
    pc2.printf("Select radio output power:\n");
    pc2.printf("1: 0 dB\n2: -6 dB\n3: -12 dB\n4: -18 dB\n");
    //Select the RF output power
    switch (pc2.getc()) {
    case '1':
        nrf24l01p.setRfOutputPower(NRF24L01P_TX_PWR_ZERO_DB);
    break;
    case '2':
        nrf24l01p.setRfOutputPower(NRF24L01P_TX_PWR_MINUS_6_DB);
    break;
    case '3':
        nrf24l01p.setRfOutputPower(NRF24L01P_TX_PWR_MINUS_12_DB);
    break;
    case '4':
        nrf24l01p.setRfOutputPower(NRF24L01P_TX_PWR_MINUS_18_DB);
    break;
    default:
        pc2.printf("Wrong number! Default value was set (0 dB)\n");
        wait_ms(1000);
    break;
    }
    //Clear the console window
    pc2.puts("\e[2J\e[H");
 
    pc2.printf("Set air data rate. The air data rate must be the same for RX and TX!:\n");
    pc2.printf("1: 250 KBps\n2: 1 MBps\n3: 2 MBps\n");
    //Set the air data rate
    switch (pc2.getc()) {
    case '1':
        nrf24l01p.setAirDataRate(NRF24L01P_DATARATE_250_KBPS);
    break;
    case '2':
        nrf24l01p.setAirDataRate(NRF24L01P_DATARATE_1_MBPS);
    break;
    case '3':
        nrf24l01p.setAirDataRate(NRF24L01P_DATARATE_2_MBPS);
    break;
    default:
        pc2.printf("Wrong number! Default value was set (1 MBps)\n");
        wait_ms(1000);
    break;
    }
    //Clear the console window
    pc2.puts("\e[2J\e[H");
}

Demonstrační video

Následuje video, které demonstruje funkčnost spoje. Na začátku videa je ukázáno nastavení modulu pomocí konzole. Po nastavení je ukázáno posílání zpráv při zadávání znaků z klávesnice. Nakonec je ukázáno odesílání automatických zpráv. K notebooku je připojen vysílač, který měří parametry spoje. Ve videu je demonstrováno, jakým způsobem se mění parametry spoje při zvyšující se vzdálenosti mezi moduly. Při opuštění místnosti s přijímačem již dochází k výpadku spojení a tedy i zpráv, což vede ke zvýšení BER. Je však nutno podotknout, že ve videu je nastaven výstupní výkon na -6 dBm a maximum modulu je 0 dBm, dosah by se tedy určitě o několik metrů zlepšil při zvýšení výkonu. Parametry spoje nastavené ve videu jsou následující:

  • Výstupní výkon transceiveru: -6 dBm
  • Frekvence: 2410 MHz
  • Datová rychlost: 2 Mbit/s
  • Interval automatických zpráv: 3 sekundy

Adresy obou zařízení jsou nastaveny programově a musí být stejné. Stejně tak je pevně nastaveno automatické potvrzování a jsou vypnuty automatické retransmise.

V konzolovém okně se občas objeví chybné znaky, není to však tím, že by během přenosu došlo k chybě. Chyba je nejspíše někde na straně konzole.

Závěr

Cílem projektu bylo naprogramovat rádiový spoj v pásmu 2,4 GHz. Výstupem projektu je plně funkční obousměrný spoj, který měří základní parametry přenosu. Spoj s uživatelem komunikuje prostřednictvím konzole na PC, kde se vypisují odeslané a přijaté zprávy a zároveň parametry přenosu a počet zpráv. V konzoli je také možné modul nastavit na požadované parametry pomocí jednoduchého menu. Zadání projektu je tedy tímto splněno.

Repozitář k celému projektu je uveden zde

2015/p2p-nrf24l01.txt · Poslední úprava: 2016/01/17 21:22 autor: Petr Sedláček