Пульт управления RC лодкой с двухканальной связью NRF24L01- Chat GPT

Добрый день. Понадобилось сделать свой пульт управления и приемник для лодки. Есть готовый проект на двух NRF24L01 (How To Make 6-Channel Radio Control. Range 2000m+ – RC Models, DIY Hobby Elektronics, Arduino projects, RC Airplanes) , все работает, я проверил, но мне нужно чтобы модуль NRF24L01 который используется в качестве приемника и стоит на борту лодки, мог бы отправлять значение напряжения бортового аккумулятора на пульт. На пульте допустим стоит OLED экран и выводит это напряжение. Это нужно чтобы не потерять лодку из-за разряда АКБ. Сам я в программировании мало что понимаю, но я целый день просидел с ChatGPT и он мне выдал код, который по его мнению должен работать, но он не работает и даже джойстик перестал работать. Не подскажите в чем проблема?

Код для приемника на лодке:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
  byte throttle;
  byte pitch;  
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  int voltage; // Добавлено: поле для напряжения
};

Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
const uint64_t pipeOut = 0xE8E8F0F0E1LL;
RF24 radio(9, 10); 

void ResetData() {
  data.throttle = 0;
  data.pitch = 127;
  data.yaw = 127;
  data.aux1 = 0;                                              
  data.aux2 = 0;
  data.voltage = 0; // Добавлено: сброс значения напряжения
}

void setup() {
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);
                                                           
  ResetData();
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening(); // Начинаем с режима прослушивания
}

unsigned long lastRecvTime = 0;

void recvData() {
  while ( radio.available() ) {
    radio.read(&data, sizeof(Signal));
    lastRecvTime = millis();
  }
}

void loop() {
  recvData();
  unsigned long now = millis();
  if ( now - lastRecvTime > 1000 ) {
    ResetData();
  }

  data.voltage = analogRead(A0); // Добавлено: чтение напряжения с порта A0

  ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
  ch_width_2 = map(data.pitch, 0, 255, 1000, 2000); 
  ch_width_3 = map(data.throttle, 0, 255, 1000, 2000); 
  ch_width_4 = map(data.yaw, 0, 255, 1000, 2000); 
  ch_width_5 = map(data.aux1, 0, 1, 1000, 2000); 
  ch_width_6 = map(data.aux2, 0, 1, 1000, 2000); 

  ch1.writeMicroseconds(ch_width_1);
  ch2.writeMicroseconds(ch_width_2);
  ch3.writeMicroseconds(ch_width_3);
  ch4.writeMicroseconds(ch_width_4);
  ch5.writeMicroseconds(ch_width_5);
  ch6.writeMicroseconds(ch_width_6); 

  radio.stopListening(); // Останавливаем прослушивание
  radio.write(&data, sizeof(Signal)); // Отправляем данные напряжения
  radio.startListening(); // Снова начинаем прослушивание
}

А это код для пульта с экраном OLED

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

const uint64_t pipeIn = 0xE8E8F0F0E1LL; // NOTE: The address in the Transmitter and Receiver code must be the same "0xE8E8F0F0E1LL" 
const uint64_t pipeOut = 0xABCDABCD71LL; // NOTE: The address in the Transmitter and Receiver code must be the same "0xABCDABCD71LL" 
RF24 radio(9, 10); // select CE,CSN pin

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
};

struct Voltage {
  int voltage;
};

Signal data;
Voltage voltageData;

void ResetData() 
{
  data.throttle = 0;                  
  data.pitch = 127;
  data.roll = 127;
  data.yaw = 127;
  data.aux1 = 0;                       
  data.aux2 = 0;
  voltageData.voltage = 0; // Добавлено: сброс значения напряжения
}

void setup() {
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS); // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX); // Output power is set for maximum range
  radio.startListening(); // Start the radio comunication for Transmitter 

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Voltage:");
  display.display();
}

int Border_Map(int val, int lower, int middle, int upper, bool reverse)
{
  val = constrain(val, lower, upper);
  if ( val < middle )
  val = map(val, lower, middle, 0, 128);
  else
  val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop() {
  data.roll = Border_Map( analogRead(A3), 0, 512, 1023, true ); // CH1   Note: "true" or "false" for signal direction
  data.pitch = Border_Map( analogRead(A0), 0, 512, 1023, true ); // CH2    
  data.throttle = Border_Map( analogRead(A2),0, 340, 570, true ); // CH3   Note: For Single side ESC | 
  // data.throttle = Border_Map( analogRead(A2),0, 512, 1023, true ); // CH3   Note: For Bidirectional ESC |
  data.yaw = Border_Map( analogRead(A1), 0, 512, 1023, false ); // CH4
  data.aux1 = digitalRead(0); // CH5
  data.aux2 = digitalRead(3); // CH6

  radio.stopListening(); // Останавливаем прослушивание
  radio.write(&data, sizeof(Signal)); // Отправляем данные
  radio.startListening(); // Снова начинаем прослушивание

  if (radio.available()) {
    radio.read(&voltageData, sizeof(Voltage)); // Принимаем данные

    display.clearDisplay();
    display.setCursor(0,0);
    display.println("Voltage:");
    display.println(voltageData.voltage); // Вывод значения напряжения на OLED экран
    display.display();
  }
}

Это код приемника из готового проекта который работает, но без вольтметра

//Receiver 

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {

byte throttle;
byte pitch;  
byte roll;
byte yaw;
byte aux1;
byte aux2;
    
};

Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
RF24 radio(9, 10); 

void ResetData()
{

data.throttle = 0;                                         // Define the inicial value of each data input.
data.pitch = 127;
data.yaw = 127;
data.aux1 = 0;                                              
data.aux2 = 0;
                                                    
}

void setup()
{
                                                           // Set the pins for each PWM signal 
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);
                                                           
  ResetData();                                             // Configure the NRF24 module 
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);                         // The lowest data rate value for more stable communication 
  radio.setPALevel(RF24_PA_MAX);                           // Output power is set for maximum 
  radio.startListening();                                  // Start the radio comunication for receiver

}

unsigned long lastRecvTime = 0;

void recvData()
{
while ( radio.available() ) {
radio.read(&data, sizeof(Signal));
lastRecvTime = millis();                                    // Receive the data 
}
}

void loop()
{
recvData();
unsigned long now = millis();
if ( now - lastRecvTime > 1000 ) {
ResetData();                                                // Signal lost.. Reset data
}

ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
ch_width_2 = map(data.pitch, 0, 255, 1000, 2000); 
ch_width_3 = map(data.throttle, 0, 255, 1000, 2000); 
ch_width_4 = map(data.yaw, 0, 255, 1000, 2000); 
ch_width_5 = map(data.aux1, 0, 1, 1000, 2000); 
ch_width_6 = map(data.aux2, 0, 1, 1000, 2000); 

ch1.writeMicroseconds(ch_width_1);                          // Write the PWM signal
ch2.writeMicroseconds(ch_width_2);
ch3.writeMicroseconds(ch_width_3);
ch4.writeMicroseconds(ch_width_4);
ch5.writeMicroseconds(ch_width_5);
ch6.writeMicroseconds(ch_width_6); 

}

А это код передатчика - пульта из готового проекта , который работает, но нет вольтметра

//Transmitter

  #include <SPI.h>
  #include <nRF24L01.h>
  #include <RF24.h>
  const uint64_t pipeOut = 0xABCDABCD71LL;         // NOTE: The address in the Transmitter and Receiver code must be the same "0xABCDABCD71LL" 
  RF24 radio(9, 10);                               // select CE,CSN pin

  struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  
};
  Signal data;
  void ResetData() 
{
  data.throttle = 0;                  
  data.pitch = 127;
  data.roll = 127;
  data.yaw = 127;
  data.aux1 = 0;                       
  data.aux2 = 0;

}
  void setup()
{
                                       // Configure the NRF24 module
  radio.begin();
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);    // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX);      // Output power is set for maximum range
  radio.stopListening();              // Start the radio comunication for Transmitter 
  ResetData();
 
}
                                      // Joystick center and its borders 
  int Border_Map(int val, int lower, int middle, int upper, bool reverse)
{
  val = constrain(val, lower, upper);
  if ( val < middle )
  val = map(val, lower, middle, 0, 128);
  else
  val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}
  void loop()
{                                  

  data.roll = Border_Map( analogRead(A3), 0, 512, 1023, true );        // CH1   Note: "true" or "false" for signal direction
  data.pitch = Border_Map( analogRead(A0), 0, 512, 1023, true );       // CH2    
  data.throttle = Border_Map( analogRead(A2),0, 340, 570, true );      // CH3   Note: For Single side ESC | 
  // data.throttle = Border_Map( analogRead(A2),0, 512, 1023, true );  // CH3   Note: For Bidirectional ESC |
  data.yaw = Border_Map( analogRead(A1), 0, 512, 1023, false );        // CH4
  data.aux1 = digitalRead(0);                                          // CH5
  data.aux2 = digitalRead(3);                                          // CH6

  radio.write(&data, sizeof(Signal));  
}

проблема в генераторе кода

если вы имели ввиду ИИ, то это очевидно, что проблема в нем - ChatGPT что-то сделал неправильно или недоделал, но это не важно, так как мне нужно понять где проблема в коде, чтобы заставить работать с функцией вольтметра. Здесь я уперся в скудные возможности Chat GPT, он не может ничего сделать, хотя за день вариантов было миллион.

В том что вы взялись за дело, в котором ничего не понимаете.

Сразу скажу - вы зря проговорились, что использовали чатГПТ. Вряд ли кто-то станет править код после ИИ. Если вы получили код от робота, то его и просите, чтобы он вам его исправил.

3 лайка

Взять самую дешёвую аппаратуру Флайскай, контроллер APM 2.5 -2.8 c обвязкой и будет счастье, к нему вяжется практически всё, сделал для приятелей три комплекта, довольны. Правда получается сейчас не столь бюджетно

родилась мысль: ии - костыль, а не протез :wink:

код особо не смотрел, трубы назначены правильно, для начала таких экспериментов надо разжиться оригинальными модулями, которых практически ни у кого нет, он где-то рублей 500 стоит, а всем хочется за сущие копейки

Ох уж эти разработчики ИИ, опять обосрались, и не сделали правильно, так что бы все твои проблемы бесплатно решились, а тебе не надо было ничего уметь.

2 лайка

Есть у меня Radiolink AT10II там и большой цветной экран и в связке с miniPIX экран выводит не только напряжение, но и координаты и дистанцию, но не устраивает меня этот неудобный кирпич на шее.
Мне захотелось иметь маленький пульт, так как с ним намного удобнее лазить по заваленным берегам озер без особого риска разбить\утопить. Джойстики для мини пульта я взял от пульта комнатного квадрокоптера, так что пульт реально получится микроскопическим. AT10 я свой уже топил, разбирал прям на берегу, сушил от коррозии. Вот в концепцию маленького пультика и вписывается OLED с напряжением борта. Я правда думал зайти еще дальше и взять с порта телеметрии данные дистанции или еще чего и также их вывести на OLED, но это видимо будет еще сложнее. Сам я технарь, могу руками сделать хорошо, токарный и чпу станки мне в помощь только как инструмент + еще я и перфекционист, но вот писать код для меня непосильная задача, не математический у меня склад ума, уже много лет пытаюсь осилить ардуино, получается только светодиодиками моргать…

для начала надо видимо хорошенько изучить даташит на этот модуль, к примеру в проекте сканера он используется, с NRF24 работа напрямую, но только на приём
PS кстати у Тараниса есть компактный пульт но цена не гуманная

Я использую модули с али 2G4M27D. Я конечно не уверен что это оригинал, но во всяком случае штатный скетч пульта управления на этих модулях работает нормально. Еще я сделал свой тестовый скетч также на двух ардуино нано и двух таких модулях 2G4M27D, где одной ардуино измеряется напряжение, передается по радио и принимается другой ардуиной и выводится на OLED 0.96. Это тоже работает хорошо. Так что по идее модули рабочие, раз отдельно все работает.
Я уверен, что у меня проблема со скетчами заключается в том, что там должна быть двухсторонняя связь, то есть модули должны быть правильно настроены на прием и передачу, чтобы работало и управление сервами с джойстиков и при этом в ответ приходило напряжение с того конца.

сначала замаркируй передачу на одном, оставь приём и на другом наоборот, отлаживаешь передачу в одну сторону, потом делаешь наоборот. передача в другую сторону…после этого можно будет о чём то думать

Вот еще компактная и по сути бюджетная аппаратура, но ТТХ не знаю

Я вот всю жизнь шел к этому и обзавелся постепенно 3D принтером, самодельным ЧПУ, токарным ЧПУ и уже считаю неспортивным покупать готовое, благо этого много было у меня, да и цены кусаются. А сделать что-то самим, имея по сути лабораторию дома это уже вопрос чести ))

В общем, ИИ я послал куда подальше в виду бесполезности и разбираюсь со скетчем сам сравнивая строки кода с рабочим проектом пульта RC. Закомментировал настройки труб так чтобы пульт работал в одну сторону от джойстика и джойстик стал управлять сервой, правда только в том случае если я закомментировал три строки в конце кода.

  // radio.stopListening(); // Останавливаем прослушивание
  // radio.write(&data, sizeof(Signal)); // Отправляем данные напряжения
  // radio.startListening(); // Снова начинаем прослушивание

В итоге скетч приемника пока такой:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  int voltage; // Добавлено: поле для напряжения
};

Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
//const uint64_t pipeOut = 0xE8E8F0F0E1LL;
RF24 radio(9, 10); 

void ResetData() {
  data.throttle = 0;
  data.pitch = 127;
  data.yaw = 127;
  data.aux1 = 0;
  data.aux2 = 0;
  data.voltage = 0; // Добавлено: сброс значения напряжения
}

void setup() {
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);

  ResetData();
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  //radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening(); // Начинаем с режима прослушивания
}

unsigned long lastRecvTime = 0;

void recvData() {
  while ( radio.available() ) {
    radio.read(&data, sizeof(Signal));
    lastRecvTime = millis();
  }
}

void loop() {
  recvData();
  unsigned long now = millis();
  if ( now - lastRecvTime > 1000 ) {
    ResetData();
  }

  data.voltage = analogRead(A0); // Добавлено: чтение напряжения с порта A0

  ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
  ch_width_2 = map(data.pitch, 0, 255, 1000, 2000); 
  ch_width_3 = map(data.throttle, 0, 255, 1000, 2000); 
  ch_width_4 = map(data.yaw, 0, 255, 1000, 2000); 
  ch_width_5 = map(data.aux1, 0, 1, 1000, 2000); 
  ch_width_6 = map(data.aux2, 0, 1, 1000, 2000); 

  ch1.writeMicroseconds(ch_width_1);
  ch2.writeMicroseconds(ch_width_2);
  ch3.writeMicroseconds(ch_width_3);
  ch4.writeMicroseconds(ch_width_4);
  ch5.writeMicroseconds(ch_width_5);
  ch6.writeMicroseconds(ch_width_6); 

  // radio.stopListening(); // Останавливаем прослушивание
  // radio.write(&data, sizeof(Signal)); // Отправляем данные напряжения
  // radio.startListening(); // Снова начинаем прослушивание
}

передатчик

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

//const uint64_t pipeIn = 0xE8E8F0F0E1LL; // NOTE: The address in the Transmitter and Receiver code must be the same "0xE8E8F0F0E1LL" 
const uint64_t pipeOut = 0xABCDABCD71LL; // NOTE: The address in the Transmitter and Receiver code must be the same "0xABCDABCD71LL" 
RF24 radio(9, 10); // select CE,CSN pin

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  int voltage; // Добавлено: поле для напряжения
};

Signal data;

void ResetData() 
{
  data.throttle = 0;                  
  data.pitch = 127;
  data.roll = 127;
  data.yaw = 127;
  data.aux1 = 0;                       
  data.aux2 = 0;
  data.voltage = 0; // Добавлено: сброс значения напряжения
}

void setup() {
  radio.begin();
  //radio.openReadingPipe(1,pipeIn);
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS); // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX); // Output power is set for maximum range
  radio.stopListening();              // Start the radio comunication for Transmitter 
  ResetData(); 

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Voltage:");
  display.display();
}

int Border_Map(int val, int lower, int middle, int upper, bool reverse)
{
  val = constrain(val, lower, upper);
  if ( val < middle )
  val = map(val, lower, middle, 0, 128);
  else
  val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop() {
  data.roll = Border_Map( analogRead(A3), 0, 512, 1023, true ); // CH1   Note: "true" or "false" for signal direction
  data.pitch = Border_Map( analogRead(A0), 0, 512, 1023, true ); // CH2    
  data.throttle = Border_Map( analogRead(A2),0, 340, 570, true ); // CH3   Note: For Single side ESC | 
  // data.throttle = Border_Map( analogRead(A2),0, 512, 1023, true ); // CH3   Note: For Bidirectional ESC |
  data.yaw = Border_Map( analogRead(A1), 0, 512, 1023, false ); // CH4
  data.aux1 = digitalRead(0); // CH5
  data.aux2 = digitalRead(3); // CH6

  radio.stopListening(); // Останавливаем прослушивание
  radio.write(&data, sizeof(Signal)); // Отправляем данные
  radio.startListening(); // Снова начинаем прослушивание

  if (radio.available()) {
    radio.read(&data, sizeof(Signal)); // Принимаем данные

    display.clearDisplay();
    display.setCursor(0,0);
    display.println("Voltage:");
    display.println(data.voltage); // Вывод значения напряжения на OLED экран
    display.display();
  }
}

Из даташита:

7.2 Расширенный обзор ShockBurst ™
Усовершенствованный ShockBurst ™ использует ShockBurst ™ для автоматической обработки пакетов и синхронизации. Во время передачи
ShockBurst ™ собирает пакет и синхронизирует биты в пакете данных для передачи. Во
время приема ShockBurst ™ постоянно ищет действительный адрес в демодулированном сигнале. Когда ShockBurst ™ находит действительный адрес, он обрабатывает остальную часть пакета и проверяет его с помощью CRC. Если пакет
действителен, полезная нагрузка перемещается в свободный слот в RX FIFOs. Вся высокоскоростная обработка битов и синхронизация контролируются ShockBurst ™ .
Усовершенствованная функция ShockBurst ™ обеспечивает автоматическую обработку пакетных транзакций для простого внедрения
надежного двунаправленного канала передачи данных. Расширенная пакетная транзакция ShockBurst ™ представляет собой обмен
пакетами между двумя приемопередатчиками, при этом один приемопередатчик выступает в качестве основного приемника (PRX), а другой приемопередатчик выступает в качестве основного передатчика (PTX). Расширенная пакетная транзакция ShockBurst ™ всегда
инициируется передачей пакета от PTX, транзакция завершается, когда PTX получил
пакет подтверждения (ACK-пакет) от PRX. PRX может присоединять пользовательские данные к ACK-пакету
, обеспечивая двунаправленный канал передачи данных.
Автоматическая обработка пакетных транзакций работает следующим образом:

  1. Вы начинаете транзакцию с передачи пакета данных из PTX в PRX. Улучшенный
    ShockBurst ™ автоматически переводит PTX в режим приема для ожидания пакета подтверждения.
  2. Если пакет получен PRX, Enhanced ShockBurst ™ автоматически собирает и
    передает пакет подтверждения (ACK-пакет) в PTX, прежде чем вернуться в режим приема.
  3. Если PTX не получает пакет подтверждения немедленно, Enhanced ShockBurst ™ автоматически
    повторно передает исходный пакет данных после программируемой задержки и переводит PTX в
    режим приема для ожидания пакета подтверждения.
    В Enhanced ShockBurst ™ можно настроить такие параметры, как максимальное количество повторных передач и задержка от одной передачи до следующей повторной передачи. Вся автоматическая обработка выполняется без
    участия микроконтроллера.

А здесь примеры, как это можно использовать

Скетч приёмника будет выглядеть приблизительно так:
(данные для обратного канала надеюсь сможешь сам подготовить)

//Receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
};

int ackData[2] = {109, -4000}; // the two values to be sent to the master
bool newData = false;


Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
RF24 radio(9, 10);

void ResetData(){
  data.throttle = 0;                                         // Define the inicial value of each data input.
  data.pitch = 127;
  data.yaw = 127;
  data.aux1 = 0;
  data.aux2 = 0;
}


void setup(){
  // Set the pins for each PWM signal
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);

  ResetData();                        // Configure the NRF24 module
  radio.begin();
  radio.openReadingPipe(1, pipeIn);
  radio.setChannel(100);
  radio.setAutoAck(true);
  radio.setDataRate(RF24_250KBPS);    // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX);      // Output power is set for maximum
  radio.startListening();             // Start the radio comunication for receiver
  radio.enableAckPayload();
                                      // pre-load data (здесь бы выполнить процедуру получения
                                      // данных для обратного канала)     
  radio.writeAckPayload(1, &ackData, sizeof(ackData));   
}

unsigned long lastRecvTime = 0;

void recvData(){
  if ( radio.available() ) {
    radio.read(&data, sizeof(Signal));
    lastRecvTime = millis();         // Receive the data
    updateReplyData();
    newData = true;
  }
}

void updateReplyData() {
    ackData[0] -= 1;
    ackData[1] -= 1;
    if (ackData[0] < 100) {
        ackData[0] = 109;
    }
    if (ackData[1] < -4009) {
        ackData[1] = -4000;
    }
    radio.writeAckPayload(1, &ackData, sizeof(ackData)); // load the payload for the next time
}


void loop(){
  recvData();
//  unsigned long now = millis();
  if ( millis() - lastRecvTime > 5000 ) {
    ResetData();                     // Signal lost.. Reset data
  }
  
  if (newData == true) {
  ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
  ch_width_2 = map(data.pitch, 0, 255, 1000, 2000);
  ch_width_3 = map(data.throttle, 0, 255, 1000, 2000);
  ch_width_4 = map(data.yaw, 0, 255, 1000, 2000);
  ch_width_5 = map(data.aux1, 0, 1, 1000, 2000);
  ch_width_6 = map(data.aux2, 0, 1, 1000, 2000);

  ch1.writeMicroseconds(ch_width_1); // Write the PWM signal
  ch2.writeMicroseconds(ch_width_2);
  ch3.writeMicroseconds(ch_width_3);
  ch4.writeMicroseconds(ch_width_4);
  ch5.writeMicroseconds(ch_width_5);
  ch6.writeMicroseconds(ch_width_6);

  newData = false;
  }
}

Что-то здесь вообще дремучий лес для меня, попробовал под этот алгоритм переделать передатчик, пусть пока без отправки напряжения, но даже джойстик завести не удалось… сериал монитор отрабатывает движение джойстиков но дальше никуда не идет…

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

const uint64_t pipeOut = 0xABCDABCD71LL; // NOTE: The address in the Transmitter and Receiver code must be the same "0xABCDABCD71LL" 
RF24 radio(9, 10); // select CE,CSN pin

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
};

Signal data;

void ResetData() {
  data.throttle = 0;                  
  data.pitch = 127;
  data.roll = 127;
  data.yaw = 127;
  data.aux1 = 0;                       
  data.aux2 = 0;
}

void setup() {
  // Configure the NRF24 module
  radio.begin();
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(true); // 
  radio.setDataRate(RF24_250KBPS); // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX); // Output power is set for maximum range
  radio.stopListening(); // Start the radio comunication for Transmitter 
  ResetData();

  Serial.begin(9600); 
}

// Joystick center and its borders 
int Border_Map(int val, int lower, int middle, int upper, bool reverse) {
  val = constrain(val, lower, upper);
  if ( val < middle )
    val = map(val, lower, middle, 0, 128);
  else
    val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop() {                                  
  data.roll = Border_Map( analogRead(A3), 0, 512, 1023, true ); // CH1   Note: "true" or "false" for signal direction
  data.pitch = Border_Map( analogRead(A0), 0, 512, 1023, true ); // CH2    
  data.throttle = Border_Map( analogRead(A2),0, 340, 570, true ); // CH3   Note: For Single side ESC | 
  // data.throttle = Border_Map( analogRead(A2),0, 512, 1023, true ); // CH3   Note: For Bidirectional ESC |
  data.yaw = Border_Map( analogRead(A1), 0, 512, 1023, false ); // CH4
  data.aux1 = digitalRead(0); // CH5
  data.aux2 = digitalRead(3); // CH6

  radio.write(&data, sizeof(Signal));  


  Serial.print("Roll: ");
  Serial.println(data.roll);
  Serial.print("Pitch: ");
  Serial.println(data.pitch);
  Serial.print("Throttle: ");
  Serial.println(data.throttle);
  Serial.print("Yaw: ");
  Serial.println(data.yaw);
  Serial.print("Aux1: ");
  Serial.println(data.aux1);
  Serial.print("Aux2: ");
  Serial.println(data.aux2);
}

На данный момент с приемника пытаюсь отправить в таком виде radio.writeAckPayload(1, &data, sizeof(Signal));

Включил сериал мониторы на обоих скетчах и вижу что на приемнике напряжение считывается корректно и выводится в монитор. Вот в передатчике с джойстиком и экраном отображает нулевое напряжение - т.е. не приходит напряжение… Как вычислить что неправильно - не пойму. Тут и отладить на каком шаге и в каком скетче не работает не получается из-за радио…

Приемник

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
  byte throttle;
  byte pitch;  
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  byte voltage;  // новое поле для напряжения
};

Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
const uint64_t pipeOut = 0xE8E8F0F0E1LL; // новая труба для передачи данных
RF24 radio(9, 10); 

void ResetData() {
  data.throttle = 0;                                         
  data.pitch = 127;
  data.yaw = 127;
  data.aux1 = 0;                                              
  data.aux2 = 0;
  data.voltage = 0;  // инициализация напряжения нулем
}

void setup() {
  Serial.begin(9600); // начинаем серийное соединение
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);
                                                           
  ResetData();                                             
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(true); // включаем autoAck
  radio.setDataRate(RF24_250KBPS);                         
  radio.setPALevel(RF24_PA_MAX);                           
  radio.startListening();                                  
}

unsigned long lastRecvTime = 0;

void recvData() {
  while ( radio.available() ) {
    radio.read(&data, sizeof(Signal));
    lastRecvTime = millis();                                    
  }
}

void loop() {
  recvData();
  unsigned long now = millis();
  if ( now - lastRecvTime > 1000 ) {
    ResetData();                                                
  }

  // Считывание напряжения с вывода A0 и сохранение его в data.voltage
  int sensorValue = analogRead(A0);
  float voltage = sensorValue * (5.0 / 1023.0);
  data.voltage = voltage * 10;  // умножаем на 10, чтобы сохранить одну десятичную дробь

  // Передача данных обратно передатчику
  radio.writeAckPayload(1, &data, sizeof(Signal));

  // Вывод данных на серийный порт
  Serial.print("Voltage: ");
  Serial.println(data.voltage / 10.0);  // делим на 10, чтобы вернуться к исходному значению

  ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
  ch_width_2 = map(data.pitch, 0, 255, 1000, 2000); 
  ch_width_3 = map(data.throttle, 0, 255, 1000, 2000); 
  ch_width_4 = map(data.yaw, 0, 255, 1000, 2000); 
  ch_width_5 = map(data.aux1, 0, 1, 1000, 2000); 
  ch_width_6 = map(data.aux2, 0, 1, 1000, 2000); 

  ch1.writeMicroseconds(ch_width_1);                          
  ch2.writeMicroseconds(ch_width_2);
  ch3.writeMicroseconds(ch_width_3);
  ch4.writeMicroseconds(ch_width_4);
  ch5.writeMicroseconds(ch_width_5);
  ch6.writeMicroseconds(ch_width_6); 
}

передатчик

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Adafruit_SSD1306.h>  // библиотека для работы с OLED экраном

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

const uint64_t pipeIn = 0xE8E8F0F0E1LL; // новая труба для приема данных
const uint64_t pipeOut = 0xABCDABCD71LL;         
RF24 radio(9, 10);                               

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
  byte voltage;  // новое поле для напряжения
};

Signal data;

void ResetData() {
  data.throttle = 0;                  
  data.pitch = 127;
  data.roll = 127;
  data.yaw = 127;
  data.aux1 = 0;                       
  data.aux2 = 0;
  data.voltage = 0;  // инициализация напряжения нулем
}

void setup() {
  Serial.begin(9600); // начинаем серийное соединение
  radio.begin();
  radio.openReadingPipe(1,pipeIn);
  radio.openWritingPipe(pipeOut);
  radio.setChannel(100);
  radio.setAutoAck(true); // включаем autoAck
  radio.setDataRate(RF24_250KBPS);    
  radio.setPALevel(RF24_PA_MAX);      
  radio.stopListening();              
  ResetData();

  // Инициализация OLED экрана
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Voltage:");
  display.display();
}

int Border_Map(int val, int lower, int middle, int upper, bool reverse) {
  val = constrain(val, lower, upper);
  if ( val < middle )
    val = map(val, lower, middle, 0, 128);
  else
    val = map(val, middle, upper, 128, 255);
  return ( reverse ? 255 - val : val );
}

void loop() {                                  
  data.roll = Border_Map( analogRead(A3), 0, 512, 1023, true );        
  data.pitch = Border_Map( analogRead(A0), 0, 512, 1023, true );       
  data.throttle = Border_Map( analogRead(A2),0, 340, 570, true );      
  data.yaw = Border_Map( analogRead(A1), 0, 512, 1023, false );        
  data.aux1 = digitalRead(0);                                          
  data.aux2 = digitalRead(3);                                          

  radio.write(&data, sizeof(Signal));  

  // Прием данных обратно от приемника
  radio.startListening();
  if (radio.available()) {
    radio.read(&data, sizeof(Signal));
  }
  radio.stopListening();

  // Вывод данных на серийный порт
  Serial.print("Voltage: ");
  Serial.println(data.voltage / 10.0);  // делим на 10, чтобы вернуться к исходному значению

  // Вывод напряжения на OLED экран
  display.setCursor(0,10);
  display.println(data.voltage / 10.0);  // делим на 10, чтобы вернуться к исходному значению
  display.display();
}

ты хочешь всё и сразу, добейся для начала чтобы АСК заработал, просто настрой передатчик на обработку, но данные не обрабатывай пока и смотри, работает приёмник или нет

На скорости 250кбод не работает автоподтверждение. Либо 1Мбод ставить, либо без подтверждений, только вручную переключать и передавать. Не знаю зачем так сделано, но это в даташите есть.

Значит надо поставить скорость мегабит

RX

//Receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
  byte throttle;
  byte pitch;
  byte roll;
  byte yaw;
  byte aux1;
  byte aux2;
};

int ackData[2] = {109, -4000}; // the two values to be sent to the master
bool newData = false;


Signal data;

const uint64_t pipeIn = 0xABCDABCD71LL;
RF24 radio(9, 10);

void ResetData() {
  data.throttle = 0;                                         // Define the inicial value of each data input.
  data.pitch = 127;
  data.yaw = 127;
  data.aux1 = 0;
  data.aux2 = 0;
}


void setup() {
  // Set the pins for each PWM signal
  ch1.attach(2);
  ch2.attach(3);
  ch3.attach(4);
  ch4.attach(5);
  ch5.attach(6);
  ch6.attach(7);
  
  // получить данные с датчиков для канала телеметрии (4 байта)
  ackData[0] = analogRead(A0); // с датчика подключенного к А0
  ackData[1] = analogRead(A1); // с датчика подключенного к А1


  ResetData();                        // Configure the NRF24 module
  radio.begin();
  radio.openReadingPipe(1, pipeIn);
  radio.setChannel(100);
  radio.setAutoAck(true);
  radio.setDataRate(RF24_1MBPS);    // The lowest data rate value for more stable communication
  radio.setPALevel(RF24_PA_MAX);      // Output power is set for maximum
  radio.startListening();             // Start the radio comunication for receiver
  radio.enableAckPayload();
  // pre-load data (здесь бы выполнить процедуру получения
  // данных для обратного канала)
  radio.writeAckPayload(1, &ackData, sizeof(ackData));
}

unsigned long lastRecvTime = 0;

void recvData() {
  if ( radio.available() ) {
    radio.read(&data, sizeof(Signal));
    lastRecvTime = millis();         // Receive the data
    updateReplyData();
    newData = true;
  }
}

void updateReplyData() {
  ackData[0] = analogRead(A0); // с датчика подключенного к А0
  ackData[1] = analogRead(A1); // с датчика подключенного к А1
  radio.writeAckPayload(1, &ackData, sizeof(ackData)); // load the payload for the next time
}


void loop() {
  recvData();
  //  unsigned long now = millis();
  if ( millis() - lastRecvTime > 5000 ) {
    ResetData();                     // Signal lost.. Reset data
  }

  if (newData == true) {
    ch_width_1 = map(data.roll, 0, 255, 1000, 2000);
    ch_width_2 = map(data.pitch, 0, 255, 1000, 2000);
    ch_width_3 = map(data.throttle, 0, 255, 1000, 2000);
    ch_width_4 = map(data.yaw, 0, 255, 1000, 2000);
    ch_width_5 = map(data.aux1, 0, 1, 1000, 2000);
    ch_width_6 = map(data.aux2, 0, 1, 1000, 2000);

    ch1.writeMicroseconds(ch_width_1); // Write the PWM signal
    ch2.writeMicroseconds(ch_width_2);
    ch3.writeMicroseconds(ch_width_3);
    ch4.writeMicroseconds(ch_width_4);
    ch5.writeMicroseconds(ch_width_5);
    ch6.writeMicroseconds(ch_width_6);

    newData = false;
  }
}