jgromes / RadioLib

Universal wireless communication library for embedded devices
https://jgromes.github.io/RadioLib/
MIT License
1.54k stars 384 forks source link

Connecting two radio modules #423

Closed MrTalon63 closed 2 years ago

MrTalon63 commented 2 years ago

Hello, For my next project, I need full-duplex transmission so I thought about getting two SX1278 radio modules and connecting them to HSPI and VSPI interfaces on ESP32. My question is if it's possible to tell the second constructor what pins it should use for SPI?

MrTalon63 commented 2 years ago

A small addition, I tried it with some information from another issue but with no luck.

My code:

#include <RadioLib.h>
#include <SPI.h>

SPIClass hspi (HSPI);
SPIClass vspi (VSPI);

SPISettings spiset (2000000, MSBFIRST, SPI_MODE0);

// SX1278 has the following connections:
// NSS pin:   10
// DIO0 pin:  2
// RESET pin: 9
// DIO1 pin:  3
SX1278 radio = new Module(4, 2, 15, 5, vspi, spiset);
SX1278 radio1 = new Module(27, 26, 25, 33, hspi, spiset);

void setup() {
  Serial.begin(115200);

  Serial.print(F("[SX1278] Initializing 1 ... "));
  int state = radio.beginFSK();
  if (state == ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while (true);
  }

  state = radio.setFrequency(433.5);
  state = radio.setBitRate(8.0);
  state = radio.setFrequencyDeviation(50.0);
  state = radio.setRxBandwidth(250.0);
  state = radio.setOutputPower(10.0);
  state = radio.setCurrentLimit(100);
  if (state != ERR_NONE) {
    Serial.print(F("Unable to set configuration, code "));
    Serial.println(state);
    while (true);
  }

   Serial.print(F("[SX1278] Initializing 2 ... "));
  int state1 = radio1.beginFSK();
  if (state1 == ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while (true);
  }

  state1 = radio.setFrequency(433.5);
  state1 = radio.setBitRate(8.0);
  state1 = radio.setFrequencyDeviation(50.0);
  state1 = radio.setRxBandwidth(250.0);
  state1 = radio.setOutputPower(10.0);
  state1 = radio.setCurrentLimit(100);
  if (state1 != ERR_NONE) {
    Serial.print(F("Unable to set configuration, code "));
    Serial.println(state1);
    while (true);
  }
}

void loop() {

  String str;

  int state1 = radio.receive(str);
  if (state1 == ERR_NONE) {
    Serial.println(F("[SX1278] Received packet!"));
    Serial.print(F("[SX1278] Data:\t"));
    Serial.println(str);
  } else if (state1 == ERR_RX_TIMEOUT) {
    Serial.println(F("[SX1278] Timed out while waiting for packet!"));
  } else {
    Serial.println(F("[SX1278] Failed to receive packet, code "));
    Serial.println(state1);
  }
  delay(1000);

  // FSK modem can use the same transmit/receive methods
  // as the LoRa modem, even their interrupt-driven versions
  // NOTE: FSK modem maximum packet length is 63 bytes!

  int state = radio.transmit("Wojtek cwel Wojtek cwel Wojtek cwel Wojtek cwel");

  if (state == ERR_NONE) {
    Serial.println(F("[SX1278] Packet transmitted successfully!"));
  } else if (state == ERR_PACKET_TOO_LONG) {
    Serial.println(F("[SX1278] Packet too long!"));
  } else if (state == ERR_TX_TIMEOUT) {
    Serial.println(F("[SX1278] Timed out while transmitting!"));
  } else {
    Serial.println(F("[SX1278] Failed to transmit packet, code "));
    Serial.println(state);
  }
  delay(5000); 
}
jgromes commented 2 years ago

In principle, this is possible. However, it's definitely not going ot be as simple as calling receive and then transmit.

For example, you have defined two radio objects, but are only using one in the entire sketch. You also have to handle transmission and reception in parallel, which means you definitely cannot use the (blocking) methods transmit and receive. Your code first receives in blocking mode, times out (because it doesn't receive anything), then switches to transmission on the same radio. That's half duplex. You have to use interrupt-driven methods here, but that's only the beginning.

To even just approach full duplex, you will also have to process the data in parallel, or something close to it. So that means multithreading, probably on FreeRTOS. This will lead to issues like priority, accessing shared resources (the radios) from multiple threads, etc. - that's a challenge to handle, even for experienced programmers.

I would suggest to re-examine your project. What is the justification for the full-duplex requirement? Maybe half duplex with some flow control logic would be sufficient, while being a lot easier to implement.

MrTalon63 commented 2 years ago

I wanted to create a homemade RC controller for other projects that would suffice my requirements and I wanted to get full-duplex for instant command execution at the vehicle side like steering etc. Now as I think about it, it may be much simpler to receive commands between moments sending telemetry back to the ground station as it would happen every second and will take around 50ms.

Either way, thank's for pointing that out as I didn't think how hard it would really be. And being on the topic of FreeRTOS, I already was planning to use ESP32s dual-core architecture to handle communication on one core and data collection and steering on another one while storing values in global variables.

jgromes commented 2 years ago

I don't think you will be able to achieve latencies low enough to make it feasible as a remote control for e.g. a plane or a model car, you might need something like 10-30 ms or better, from the moment you pull the control lever to the actuator moving. If it's more you'll probably start to notice the "lag".

communication on one core and data collection and steering on another one while storing values in global variables

The I leave you with a bit of a "homework" to consider the following:

  1. what if both threads (cores or RTOS tasks - doesn't matter at this stage) simultaneously decide to write to the global variable?
  2. what if the variable is more than one word (e.g. a double), so that writing or reading takes more than one clock cycle, and one thread decides to write to it while the other one is reading?

Speaking from experience, writing thread-safe code is not easy. Writing thread-safe code for critical applications (RC control definitely is that) is haaaaaaaaard ;)

MrTalon63 commented 2 years ago

Thank you very much for homework portion of your comment as I didn't thought about those problems, also I'm not a very good person at writing code especially for microcontrollers (web backend is more of my thing) so I didn't knew how low level things like clock cycles work on doubles and so on.