nRF24 / RF24Network

OSI Layer 3 Networking for nRF24L01(+) and nRF52x on Arduino and Raspberry Pi
https://nrf24.github.io/RF24Network/
GNU General Public License v2.0
358 stars 163 forks source link

Any frequency hopping or anti-interference features? #72

Closed NicksonYap closed 8 years ago

NicksonYap commented 8 years ago

I learnt that CD/RPD register on the NRF24L01/NRF24L01+ can aid frequency hopping algorithms.

I'm trying to create a bidirectional link for my DIY RC remote and DIY receiver on my quadcopter (could be using MultiWii/CleanFlight/APM flight controller).

I made RC remote using NRF24L01+ module (the most common black version with PCB antenna) and TMRh20 RF24 library, The receiver integrated with MultiWii firmware (Arduino Pro mini + NRF24 module). The program was simple, it used fixed channel (CH 100) and made sure that in case of temporary pin disconnection, the program can reinitialize and receive data from the NRF24 module.

It had reception issues when I fly about 60 degrees, 2 meters above me. It will loose reception and crash. I was flying around residential area, about 30m wide playground with houses at all corners. I tried both 250kbps and 2Mbps. So I assumed it was low TX power and bad antenna. (is the range supposed to be that poor? I used capacitors and red LED to drop 5V to around 3.3V as power supply)

This time I bought a shielded 100mW NRF24L01P+PA (plus variant with power amplifier) with 3db whip antenna. And I learnt that I was flying around residential area and WiFi interferes with the receiver. (hopefully this will make a difference when it comes to communication range...)

I would like to develop frequency hopping algorithm for my remote control and receiver at the quadcopter-end, unless TMRh20 has already done it :)

I guess I will start with the "scanner" example and select frequencies which has less increment of the CD/ RPD bit. (am I right? anyone know a good ref for frequency hoppy /anti-jamming? xD)

Maybe later on add antenna tracking based on ping and packet loss (using highly directional antenna) (any tips? )

Is the RF24 or RF24Network library more suitable for this application? What's needed is range, latency isn't a big issue (at most 100ms I guess, could improve later) and connection reliability.

Avamander commented 8 years ago

RF24 is definitely more suitable if you wish to use only two radios as optimized as possible. I can't help with the frequency hopping unfortunately.

TMRh20 commented 8 years ago

Unfortunately, I won't be too much help either, since this isn't something I've experimented with to a large degree. I have seen others experimenting with channel hopping etc, encountering a number of issues with data corruption and apparent instability. Some have said problems are due to delays required for things to stabilize between channel changes. As an example, changing the delay times in the scanner example results in apparent changes to the results, so the example is a result of quick best-guess analysis.

Unfortunately, my quads have remained grounded this entire season, so I've not done too much with RF24 control in that scenario, but my best suggestion for this type of control is simple brute force tactics. Two simple ways of overcoming interfering signals are by overpowering them or transmitting in between the interfering signals, so I would disable auto-ack, and use high-speed multicast transmissions to overcome interference and/or transmission issues as much as possible. This can potentially help to maintain a high-frame rate (you want at least 30 control signals/second) even when a lot of data is being lost. The receiver can use a cheap RF24 module, while the transmitter would use a more powerful unit.

Another tactic that I've not experimented with much is using dual transmitters on separate frequencies/channels. Theoretically, two of these modules in close proximity would likely interfere, but if the timing of the transmissions etc. is managed, it may work well, and could provide a better/simpler result than channel hopping if it works.

Its hard to say whether RF24 or RF24Network would be 'better', but in general RF24 is used for simple device-to-device applications. It would likely be a good idea to implement a small header and pay attention to proper radio addressing etc if using RF24 directly. RF24Network is simpler to implement, but requires more overhead for buffering, routing etc.

NicksonYap commented 8 years ago

I'm able to scan all channels (0 - 125) and in around 2.5ms. (not very fast, but acceptable?) What I did was user direct port manipulation (digitalWrite takes 7us) and removed delayMicroseconds(5); everytime CE or CSN is toggled.

The result is printed in ASCII greyscale. In screenshot below, I start the scanner, then after a while I turn on my 2.4GHz RC remote next to the scanner then turn it off. (scanner shows that the channels are "noisy" when I turn on my remote)

screenshot from 2015-12-31 17 06 46


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

#define CEpin 7
#define CSNpin 8

RF24 radio(7, 8);

const uint8_t num_channels = 126;

//
// Setup
//
char ASCIIgreyscale[10] = {' ', '.', ':', '-', '=', '+' , '*' , '#' , '%' , '@'};

void setup(void)
{
  //
  // Print preamble
  //

  Serial.begin(115200);
  printf_begin();
  Serial.println(F("\n\rRF24/examples/scanner/"));

  //
  // Setup and configure rf radio
  //

  radio.begin();              
  radio.setPALevel(RF24_PA_MAX);
  radio.setDataRate(RF24_1MBPS);

  //1MHz bandwidth on 1MHz channel resolution. 
  //At 2Mbps, channel has 2MHz bandwidth which 
  //makes scanner low resolution (adjacent channels likely to show same results)

  radio.setAutoAck(0);                     

  radio.stopListening();

  radio.printDetails();
  // Print out header, high then low digit
  int i = 0;
  while ( i < num_channels )
  {
    printf("%x", i >> 4);
    ++i;
  }
  Serial.println();
  i = 0;
  while ( i < num_channels )
  {
    printf("%x", i & 0xf);
    ++i;
  }
  Serial.println();
}

//
// Loop
//

byte averagingStrength = 10; //increase if not sensitive enough
byte RPDvalue[num_channels];

void loop(void)
{
  // Clear measurement values
  memset(RPDvalue, 0, sizeof(RPDvalue));

  // Scan all channels num_reps times
int microsTaken = 0;
  for (byte j = 0; j < averagingStrength; j++) {
  int startMicros = micros();
    for (byte i = 0; i < num_channels; i++) {
      writeRegister(RF_CH, i);
      startRX();

      delayMicroseconds(4*32); 
      //128us, any lower value will cause RPD to fail (zeros returned)
      // (data sheet states 130us for PLL to stabilise)
      //delayMicroseconds() only 4us resolution, so valeus are in multiples of 4us

      if ( readRegister(RPD) ) {
        ++RPDvalue[i];
      }

      stopRX();
    }
    microsTaken += micros() - startMicros; //calculate time taken and then accumulate them
  }
microsTaken = microsTaken/averagingStrength; //divide  to obtain average

  for (byte i = 0; i < num_channels ; i++) {
    if (RPDvalue[i] < 10) {
      Serial.write(ASCIIgreyscale[RPDvalue[i]]);
    } else {
      Serial.write(ASCIIgreyscale[9]);
    }
  }
  // Print out channel measurements, clamped to a single hex digit

Serial.print("|\t");
// '|' is so that we can see the boundary of the last channel
// since normally the last few channels are "quiet"
// and tend to result in zero RPD
Serial.print(microsTaken);
  Serial.print("us");
  Serial.println();
}

void beginTransaction() {
  PORTB &= ~(1 << PB0);
}

void endTransaction() {
  PORTB |= (1 << PB0);
}

void ceHIGH() {
  PORTD |= (1 << PD7);
}

void ceLOW() {
  PORTD &= ~(1 << PD7);
}

uint8_t writeRegister(uint8_t reg, uint8_t value) {
  uint8_t status;
  beginTransaction();
  status = _SPI.transfer( W_REGISTER | ( REGISTER_MASK & reg ) );
  _SPI.transfer(value);
  endTransaction();
  return status;
}

uint8_t readRegister(uint8_t reg) {
  beginTransaction();
  _SPI.transfer( R_REGISTER | ( REGISTER_MASK & reg ) );
  uint8_t result = _SPI.transfer(0xFF);
  endTransaction();
  return result;
}

void startRX() {
  writeRegister(CONFIG, readRegister(CONFIG) | _BV(PRIM_RX));
  // writeRegister(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) );
  ceHIGH();
}

void stopRX() {
  ceLOW();
  writeRegister(CONFIG, ( readRegister(CONFIG) ) & ~_BV(PRIM_RX) );
  //writeRegister(EN_RXADDR, readRegister(EN_RXADDR) | _BV(ERX_P0)); // Enable RX on pipe0
}

scanner_fast.zip

NicksonYap commented 8 years ago

What do you mean by "transmitting in between the interfering signals"?

I understand that I should disable AutoAck, set to 2MBPS and try to transmit as frequent as possible to increase the probability of receiving the payload while interfering transmitter and switching frequencies that allow a short window of a clean channel.

I was thinking of using an NRF24 module to only listen/scan the channels and tell the receiver module and transmitter to switch to a certain channel. But I do not need a real robust frequency hopping since I think NRF24L01 is not suitable for it. Because I normally fly around residential area and my current remote (Flysky TH-9X) has bad reception when I fly near houses. It's only within a 100m I don't think transmission power is the issue since I replaced a 5V regulator in the TX that was supplying 4.7V.

Perhaps I only needed to scan channels once in a while, find channels that are not in use (channel "quiet" for at least 5 secs?)

I realize the NRF24 channel supports frequencies outside common WiFi channels (unclear with b/g/n stuff) NRF24 can go up to 2.525GHz while WiFi and my remote is only up to about 2.484GHz.

The header I'm thinking of is 1 byte that carries the payload ID (0 to 255) then resets back to 0 once it reaches 255. Only for the receiver to know how many packets it has lost (measure signal quality and take necessary actions such as change channels/for the antenna tracker)

What is the header for if we're using brute force to transmit?

TMRh20 commented 8 years ago

Hmm, scanning all channels in 2.5ms? Hehe, very nice attempt, but that must be a typo.

Section 6.4 RPD measurements The status of RPD is correct when RX mode is enabled and after a wait time of Tstby2a +Tdelay_AGC= 130us + 40us.

170 us * 126 channels = 21,240us (21ms)

In practice, with the library, a wait time of 250us is used to provide what appear to be the most consistent results. The hard part with a scanner is actually verifying the results. A practical solution including data transmission and less channels is a bit more difficult, but not impossible.

"transmitting in between the interfering signals"?

Transmitter A: o-----o-----o-----o Transmitter B: o-o-o-o-o-o-o-o-o-o Transmitter B wins, basically as per your understanding.

The purpose of the header/counter is to help ensure the CRC is different between payloads. RF24Network uses a 16-bit (2 byte) counter in the header which seems to be optimal. See this example In the case of control signals which can repeat quite often, this is good to have.

Direct port manipulation is not something I would want to include in the library, since the idea is to use standard Arduino functions/libraries. It seems that there are very few scenarios where added delays of 5 or 7μs should make a significant difference, due to delays required by the radio, but I wouldn't necessarily be opposed to making changes that allow users to easily create custom ce() & csn() functions. Also, if using interrupts, delay() has no effect.

Story-time: The 5μs delay in CSN was added about a year ago, and was kind of one of those 'Ahha!' moments, when all of the radios on all of my devices started working more reliably. This is the result of extensive testing, which is demonstrated if you look over the history of library changes, so any arguments for removal have to be very convincing. These radios have been around for a fairly long time, with a fairly large user base, and I've personally tested most of the features in many scenarios.

NicksonYap commented 8 years ago

Nice catch! I made a mistake on the data type, "microsTaken" overflowed multiple times. It took around 22ms. That's.. kinda slow.

int microsTaken = 0;

changed to:

unsigned long microsTaken = 0;

I don't understand the sentence:

A practical solution including data transmission and less channels is a bit more difficult, but not impossible.

Proposed method to test the minimum channel switching for RPD

I method of verifying I have in mind is: Go to a field with low interference Set channel switching delay to a safe value, maybe 500us Scanner should show that all channels are quiet Then use another NRF24 module and transmit at a fixed channel as frequent as possible (for a more continuous reading) and run the scanner. Assuming the TX power is constant, vary the distance of the transmitter from the receiver to find out the distance where the RPD toggles between 1 to 0 (50% of the time) This is the -64dB threshold in terms of distance, I call it RPD distance threshold. Then lower down the channel switching delay while varying the receiver distance to see if the RPD distance threshold changes (since the AGC needs time to settle) and if RPD still toggles HIGH (reliablity). Any other ways to test the RPD reliability? What defines the RPD's reliability? I wouldn't mind if the RPD distance threshold changes, as long as it is consistent since we only need to compare with other channels. (I would vary the RX side coz I would stay with the receiver, easier to see the scanner readings, haha) The point where the delay is lowered until RPD turn unreliable is the minimum delay.

I haven't tried RF24Network, I'll refer it's source code later :)

About the 5us delay

What I do not understand about the 5us is that the SPI speed, DIV4 is way faster than than the 5us delay.

Assuming 5us delay means 10us period in each toggle, then it would make the toggling frequency 100KHz while SPI clock speed is 8MHz. Or simply, SPI period is only 0.125us. Seems like cabling isn't the issue. Note that delayMicroseconds has only 4us resolution. 5us delay could actually mean 8us delay or 4us. I'm not certain.

Is it NRF24's reaction time? but it's not mentioned in the datasheet. I'm using quality NRF24L01+ module. The 5us delay could be beneficial to clones? So far in my tests (channel switching and data stream), I find the 5us redundant and affects performance greatly as digitalWrite() itself is slow enough (slow not as in slew rate, but delay it takes to find which MUC port is associated to the Arduino pin number). There is a macro-based library called digitalWriteFast and seems to be outdated so I didn't try it. If you're using RPi, perhaps there should be a delay, maybe the I/O is toggled too fast. I'll do some tests using other modules and maybe probe with the scope.

Every time RF24 lib tries to communicate with the module, there is at least 7+5=12us delay whenever it begins or end the transmission.

TMRh20 commented 8 years ago

Disclaimer: Please keep in mind that I get many emails and requests from people claiming new things, but most often, they really have no idea what they are talking about. That being said, I generally refuse to discuss theoretical problems or issues, since I've actually done the testing and put in the work to verify my results (mostly) scientifically. My biggest problem is that I don't document every single thing, and I'm not going to, because that was never the goal.

Most importantly, thank you for actually backing up your concepts and ideas with code + testing. If you want things to change however, just prove there is a problem with code and examples. I will gladly change anything if I know there is a problem.

A practical solution including data transmission and less channels is a bit more difficult, but not impossible.

Means: Its easier to make a simple scanner than a full channel hopping solution with actual communication. Probably why I've never seen anybody complete it.

What defines the RPD's reliability?

I would say the reliability of the Received Power Detector is the ability to consistently & accurately detect the power level on any given frequency/channel. I only have a few weak neighbor wi-fi signals, so my testing was simply observing the consistency of results with the known channels, as well as using RF24 nodes to create activity. I put (maybe too much) emphasis on refusing to complicate simple things.

5us delay: tl;dr: In order to claim it redundant, you need to do much more extensive testing, over large time frames, with multiple devices.

All I can say for 100% sure, is test, test, test, using a number of devices, Uno, Duemilanove, Due and RPi, and real-life applications/scenarios, over a long period of time, and you should notice a marked difference in reliability of transmissions & hardware under relatively controlled conditions.

My observations, if you want to attempt to identify exactly wtf is going on:

  1. The nrf24l01+ is/has an MCU not completely unlike an Arduino. Sending SPI commands more than every 12us can actually have a negative impact in some cases. Easily test this by hammering an Arduino with SPI commands while it returns information and performs arithmetic.
  2. Whether it is due to some sort of capacitance issue or the length of wires, I am not certain, but SPI seems to function more reliably with the delay, before initiating the 4 or 8mhz signal.
Max-62 commented 7 years ago

If it still interests you, I would have created something that you will probably find interesting. https://github.com/Max-62/nRF24L01-Frequency-Hopping-FHSS Regards