Двунаправленная отправка данных:
Модуль может работать либо в режиме передатчика, либо в режиме приёмника, но передатчик способен запрашивать, а приёмник отправлять пакет подтверждения приёма данных. Происходит это по протоколу Enhanced Shockburst примерно так: передатчик отправляет данные приёмнику с запросом ответа, приёмник получает данные, проверяет их корректность (сверяет CRC), и если всё верно, то отвечает передатчику «Ок! я все получил, спасибо.». А если приёмник не ответил передатчику, то передатчик отправляет данные приёмнику повторно, пока не исчерпает заданное количество попыток отправки данных.
Так вот в ответе приёмника могут присутствовать и те данные которые Вы хотите отправить передатчику. Правда эти данные уже не будут проходить проверку, и они должны быть сформированы на стороне приёмника до того как он получит данные от передатчика. Но такой тип связи позволит приёмнику возвращать, а передатчику принимать, данные без изменения режимов радиопередач на обоих устройствах, приёмник остаётся приёмником, а передатчик передатчиком.
#include <SPI.h> // Подключаем библиотеку для работы с шиной SPI.
#include <nRF24L01.h> // Подключаем файл настроек из библиотеки RF24.
#include <RF24.h> // Подключаем библиотеку для работы с nRF24L01+.
RF24 radio(7, 10); // Создаём объект radio для работы с библиотекой RF24, указывая номера выводов модуля (CE, SS).
int myData[5]; // Объявляем массив для приёма и хранения данных (до 32 байт включительно).
int ackData[5]; // Объявляем массив для передачи данных в пакете подтверждения приёма (до 32 байт включительно).
//
void setup(){ //
radio.begin(); // Инициируем работу nRF24L01+
radio.setChannel (27); // Указываем канал передачи данных (от 0 до 125), 27 - значит передача данных осуществляется на частоте 2,427 ГГц.
radio.setDataRate (RF24_1MBPS); // Указываем скорость передачи данных (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS), RF24_1MBPS - 1Мбит/сек.
radio.setPALevel (RF24_PA_MAX); // Указываем мощность передатчика (RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_HIGH=-6dBm, RF24_PA_MAX=0dBm).
radio.enableAckPayload(); // Указываем что в пакетах подтверждения приёма есть блок с пользовательскими данными.
// radio.enableDynamicPayloads(); // Разрешить динамически изменяемый размер блока данных на всех трубах.
radio.openReadingPipe (1, 0xAABBCCDD11LL); // Открываем 1 трубу с адресом 0xAABBCCDD11, для приема данных.
radio.startListening (); // Включаем приемник, начинаем прослушивать открытые трубы.
radio.writeAckPayload (1, &ackData, sizeof(ackData) ); // Помещаем данные всего массива ackData в буфер FIFO. Как только будут получены любые данные от передатчика на 1 трубе, то данные из буфера FIFO будут отправлены этому передатчику вместе с пакетом подтверждения приёма его данных.
} // В модуле имеется 3 буфера FIFO, значит в них одновременно может находиться до трёх разных или одинаковых данных для ответа по одной или разным трубам.
// После отправки данных из буфера FIFO к передатчику, соответствующий буфер очищается и способен принять новые данные для отправки.
void loop(){ //
if(radio.available()){ // Если в буфере приёма имеются принятые данные от передатчика, то ...
radio.read ( &myData, sizeof(myData) ); // Читаем данные из буфера приёма в массив myData указывая сколько всего байт может поместиться в массив.
radio.writeAckPayload (1, &ackData, sizeof(ackData) ); // Помещаем данные всего массива ackData в буфер FIFO для их отправки на следующее получение данных от передатчика на 1 трубе.
} // Если все 3 буфера FIFO уже заполнены, то функция writeAckPayload() будет проигнорирована.
} // Так как в данном скетче данные в буфер помещаются только после получения данных от передатчика, значит один из буферов был только что очищен и заполнение всех 3 буферов в данном скетче невозможно.
Обратите внимание на то, что ответ приёмника ackData сначала помещается в буфер FIFO функцией writeAckPayload(), а отправляется он аппаратно, при получении данных от передатчика, но ещё до того как функция available() вернёт true.
#include <SPI.h> // Подключаем библиотеку для работы с шиной SPI.
#include <nRF24L01.h> // Подключаем файл настроек из библиотеки RF24.
#include <RF24.h> // Подключаем библиотеку для работы с nRF24L01+.
RF24 radio(7, 10); // Создаём объект radio для работы с библиотекой RF24, указывая номера выводов модуля (CE, SS)
int myData[5]; // Объявляем массив для хранения и передачи данных.
int ackData[5]; // Объявляем массив для получения данных из пакета подтверждения приёма (до 32 байт включительно).
//
void setup(){ //
radio.begin (); // Инициируем работу модуля nRF24L01+.
radio.setChannel (27); // Указываем канал передачи данных (от 0 до 125), 27 - значит передача данных осуществляется на частоте 2,427 ГГц.
radio.setDataRate (RF24_1MBPS); // Указываем скорость передачи данных (RF24_250KBPS, RF24_1MBPS, RF24_2MBPS), RF24_1MBPS - 1Мбит/сек.
radio.setPALevel (RF24_PA_MAX); // Указываем мощность передатчика (RF24_PA_MIN=-18dBm, RF24_PA_LOW=-12dBm, RF24_PA_HIGH=-6dBm, RF24_PA_MAX=0dBm).
radio.enableAckPayload(); // Указываем что в пакетах подтверждения приёма есть блок с пользовательскими данными.
// radio.enableDynamicPayloads(); // Разрешить динамически изменяемый размер блока данных на всех трубах.
radio.openWritingPipe (0xAABBCCDD11LL); // Открываем трубу с адресом 0xAABBCCDD11 для передачи данных (передатчик может одновременно вещать только по одной трубе).
} //
//
void loop(){ //
radio.write(&myData, sizeof(myData)); // Отправляем данные из массива myData указывая сколько байт массива мы хотим отправить.
if( radio.isAckPayloadAvailable() ){ // Если в буфере имеются принятые данные из пакета подтверждения приёма, то ...
radio.read(&ackData, sizeof(ackData)); // Читаем данные из буфера в массив ackData указывая сколько всего байт может поместиться в массив.
} //
delay(50); // Устанавливаем задержку на 50 мс. В этом скетче нет смысла слать данные чаще.
} //
В этих скетчах одному модулю назначена роль приемника, а другому передатчика, но не смотря на это связь организована двухсторонняя, без изменения ролей модулей в радиопередаче.
Стоит отметить несколько особенностей данной реализации связи:
- На стороне передатчика, вместо функции isAckPayloadAvailable() можно использовать функцию available(), она будет работать точно так же.
- Блоки данных в пакетах подтверждения приёма имеют динамически изменяемый размер, он используется по умолчанию для отправки ответов по трубам 0 и 1, так что если данные приемника нужно отправить передатчику по трубам 2-5 (где по умолчанию используются статичный размер ответных данных), то необходимо раскомментировать функцию enableDynamicPayloads() в обоих скетчах.
- Если на стороне передатчика не читать данные из буферов, то буферы заполнятся и перестанут принимать пакеты подтверждения приёма, тогда функция write() будет всегда возвращать false, хотя данные будут и отправляться, и приниматься приемниками.
- Если на стороне приемника формировать ответы для нескольких передатчиков (а каждый из них должен использовать свою трубу), то нужно учитывать тот факт что у модуля для этих целей имеется всего 3 буфера, которые могут хранить до трех ответов для труб с разными или одинаковыми номерами. Если буферы заполнены, то функция writeAckPayload() будет проигнорирована. Например, если в буферах уже есть ответы для труб с номерами 1,2 и 3, новые ответы записать не получится, а пришли данные от передатчика по трубе с номером 0, то он естественно не получит ответ.