Ask Hackaday: Frequency Hopping On The NRF24l01+?

We’ve seen a lot of hacks with the nRF24l01+ 2.4 GHz radio modules. The tiny chips pack a lot of bang for the buck. Since the radios can switch frequencies relatively quickly, [Shubham Paul] decided to take advantage of this feature to make a rudimentary frequency-hopping communications channel.

The code is actually incredibly simple. Both the transmitter and receiver simply scan up and down over the defined channels. Because the clock speeds of any given pair of Arduinos are likely to be slightly different, it’s not a surprise that the radios eventually drift out of sync. Right now, as a quickie solution, [Shubham] is using a serial-port resynchronization: both are connected to the same computer, and he just tells them to get on the same channel. That’s not a horribly satisfying workaround. (But it’s a great start!)

Keeping two radios that are continually swapping channels in sync is no easy task, but it could possibly be made easier by taking advantage of the nRF’s acknowledge mode. If the delay between a sent acknowledge message and a received one were constant, these events (one on TX and one on RX) could be used to re-sync the two hopping cycles. All of this would probably require more temporal resolution than you’re going to get out of a microprocessor running Arduino code, but should be possible using hardware timers. But this is pure speculation. We briefly looked around and couldn’t find any working demos.

So Hackaday, how would you remotely sync two nRF24s on the cheap? Or is this a crazy idea? It might help to make transmissions more reliable in the face of 2.4 GHz band interference. Has anyone implemented their own frequency hopping scheme for the nRF24l01+?

50 thoughts on “Ask Hackaday: Frequency Hopping On The NRF24l01+?

  1. Make each of them send a hop request to their counterpart after they received one… then you just have to find a way to make them decide who starts the first round and how to restart when sync is lost… maybe revert to previous channel if the other one hasn’t sent an ack to the request? Some fun experiments to make there…

      1. I think the key is that a module shouldn’t be allowed to hop if is has not received the hop request from its counterpart yet… last known good state will always be in sync, and you can send a hop request/ack in a ring fashion until hop is made.
        This can take a bit of thinking, but maybe you can have some hints on topologies like token ring and other circular data transfer like fibre channel etc…

  2. A phase lock loop would fix the drift issue. If you hopped frequency and the other side hasn’t yet, wait a bit longer before hopping the next hop. If the other side hops before you do, wait a bit less the next hop. With this you can even sync up initially, but it might take a bit of time.

    1. But how does the rx/tx side come to know whether the counter side has hopped or not ? waiting can’t be an option as “waiting time changes according to the clock speed [& latencies] of the processor which is always different and will always make you go out of sync so that’s why I am trying to hop with the auto- acknowledgement feature which is giving me a good sync time but not 100% perfect ( maybe 85 )

      1. Tx side ends message with hop or no-hop byte, which also encodes next channel number, after ACK both switch channel. Rx side sends “are you there?” message, and after ACK from tx side it waits for next message. This will add plenty of overhead, but it will keep both sides in sync…

          1. Start with something simple: know your sequence of channels, use fewer at first. RX always waits on same channel. TX transmits on each channel in order, even if no ACK is received. At some point TX will land and align with RX. At that time the RX marks its timer and knows when to expect next packet. It should be ready in RX mode soon.
            TX should transmit at precise intervals, use the millis() to know when such an interval passed, eg 100ms (start with something rather large, then reduce).

          2. If both ends know the hopping sequence, they can hop without successful exchange of a packet, helpful if a frequency in the sequence has particularly bad interference. If there’s a timing margin around the frequency hop, a receiver can be listening before a packet is expected, allowing it to get a packet if the transmitter is a bit early by it’s clock.

  3. I did it with other radios. It is quite easy. The first part you have to develop is clock synchronisation. Depending on how fast you want to hop, the delay between packets and how much guard time you can accept, this can be trivially simple (eg. Sync counter on rx) to complicated (real clock models, …). Then you make both nodes hop on the same time instant using this shared clock.

  4. There is always the “I’ve got a bigger hammer than you” approach – give them both GPS receivers and sync to satellite time. Make the current channel number the UNIX epoch modulo 16 and you don’t even have startup issues.

    Cost is another matter, of course…

  5. Those chips have multiple listening pipes. Just select the few frequencies you want to hop between, and listen on all of them — then just do the hopping on the transmitter side.

  6. It is definitely possible to do frequency hopping with the NRF
    However, the implementation in this article is too simplistic and does not take into account that some timings are not guaranteed by the radio. Simply put, the receiver might take more time to switch than the transmitter. A simple solution would be to make sure you send packet every x time, where x is sufficiently large to account for any random delays.

  7. One thing I do on frequency hopper designs is to use a very stable reference for the synthesizer and share the same reference with the MCU. Then the frequency error is correlated to the error between timers on the MCU on an RX/TX pair. Frequency error can be determined by the DC offset correction algorithm. This can help with sync. Still, the best way to keep sync is to have a master beacon that synchronizes the rest of the network. The dwell time can vary, but 20 milliseconds seems to be a good time. Look into slotted aloha protocol. Another thing that helps with FHSS sync is to make frame sync patterns for master->slave packets different than slave->master packets. This helps reduce slave trying to inspect packets from another slave.

    1. One last comment on this. If you are interested in implementing a 900 MHz hopper, then you should take a look at TI CC1200 as well. This part is light years ahead for this type of application. It is probably a bit more work than the NRF24l01 to get working, but it is worth the effort. No, I don’t work for TI.

  8. What about storing a 32 bit signed adjustment to the local time. Start it at zero. Send the low 16 bits of the time + adjustment, plus a 16 bit signed difference between adjusted time and the time received from the last packet. Use the difference to correct any mismatch in adjusted time towards zero (note: do not set to equal as you don’t know the time the message takes to arrive). Make the correction proportional to the difference.
    This results in both clocks disciplining each other towards a mutually agreed time. Bonus points for working out an average drift and automatically correcting for that in advance.

    1. This is only the case assuming no sync between TX and RX. Otherwise you just need to hop on both sides until you are lucky and during one packet TX and RX overlap. From then on you just hop together.

      1. Another method is to stop hopping on the slave side (assuming you have a short dwell time and master beacons begging of every hop). Wait for the master to come by. With a fast hopper, this usually happens several times a second. If you don’t see the master within a couple of channel sequence times (might be bad channel at the time), then try the next channel in the pattern. This works *much* better than trying to get lucky.

        1. I don’t see why hopping on the RX would make the system perform worse. In your case it can happen that, as you note, the receiver is on a bad channel. If the RX is also hopping with a different sequence than the TX this probability is much reduced, while not changing the hit rate. In my experience many radios use a linear RX hopping pattern with a duration approximately 2-3 times the slot duration when trying to sync.

  9. I just bought 20 of these to play around with this weekend. I’m planning on use DH to get a shared key I can feed into a PRESENT cipher in CTR mode for a stream of channels. The last channel of each 32 channel block will be used for synchronization. That way drift should put them out of sync for at most 31 packets.

  10. Look at the Bluetooth specification. They use a masters initial packet as anchor point and then accurate timers to make sure they keep in sync. Eventually they have a drift…so,you should use a transmission as anchor point every once in a while. Bluetooth spec. even starts with relaxed timing constraints and optimizes the link when possible by reducing tolerances as it is possible.

  11. Why not looking into existing protocols. There are many RC transmitter protocols with frequency hopping. There is an alternate firmware for some transmitter, which has code for the most of existing RC receiver. For some protocols you have only to add a transmitter module like these Nrf24l01+. Maybe you will find some ideas for a reliable frequency hopping code.

    I’m also using Nrf24l01+ for some of my robots and thinking of a frequency hopping protocol. At the moment I programmed a fix channel for every bot.

    1. I just found this document and got an idea.
      The way to find the transmitter sounds nice. Just wait a hole period of hopping through all frequencies listening on one channel and the try the next channel. If you receive some legal packet, you are in sync.

      To stay in sync, how about: the transmitter will send always 10 packets on one frequency and the hops to the next. Every packet contains a counter and if the receiver sees the 10th packet, it will also hop to the next frequency. For safety, you can set a timer, which guess about the next hop, to get sure, if you have some packet loss and don’t get the 10th packet.
      I think, this code will not be very complicated.

  12. Install an addressable time clock system in each unit. Make sure all clocks are set to same time up to the second. You could do this with a simple Arduino. At every hour on the 0 second, all units start a new hop sequence of hops (full restart) and each hop is timed every x seconds with the same Arduino accordingly. All units have the same hop pattern programmed in the Arduino. As long as the Arduino is stable and accurate (which it would be), the hops should be in sync. However, if the hop pattern is longer than an hour, your not going to get a lot of unique hops every hour (note: you could go longer than every hour too). But that’s more of a radio-interception security issue, which does not seem to be that critical in this case.

    It is also not critical that they are set to the same actual time of day like say from WWV or the Naval Observatory. Just as long as they all match or sync with the master unit’s time setting is just fine. And “master unit” may be a misnomer here as none is actually needed. I’m just calling it that for simplicity sake.

    You don’t have to wait for the next hour to sync up either. Just write a manually-initiated routine for the Arduino that says do a full restart at xx:xx:00 time. Then they do it again every hour from then on. Do this on all units that have already had their main clocks synced up.

  13. Have an Arduino designated as slave request the master send it’s micros() value on the slave’s startup. Then periodically request micros() from the master. Any clock drift will be clear in the relative changes to these numbers. By the 2nd exchange you can start to calculate how often the module designated as slave should increment or decrement a micros correction value. To avoid the 71 minute problem, add a rollover counter and use 64bits for the exchanged time. To avoid the 584 millennium problem, think a bit more inside the box. In the event that the change in the time from the master goes negative, assume a reboot and start the clock over but keep the correction factor. This method will not work as reliably for Arduinos that are exposed to extreme temperature changes as their clocks will not be consistent over temperature. In the case of extreme temperature changes, an additional short term correction may need to be layered on top of this correction. At startup, and if frequencies still get out of sync, recovery is just a matter of the master jumping at normal speed and the slave jumping much more quickly until they reach sync again. Out of sync could be defined as the normal jumps not recovering signal after going through all allowed frequencies. If the sync is not recovered within the square of the number of allowed frequencies, the other end should be assumed to be off and scanning should be continued passively to avoid cluttering the air uselessly.

  14. A lot of the replies here talk about synchronisation drift and methods of accurately clocking the transmitter and receiver – but its not necessary. If the transmitter sends a packet on each channel precisely every 5ms, then the receiver uses each packet received to restart an ‘expected next packet within…’ timer, say 6ms. If it doesnt see the next packet, it hops anyway and waits 5ms. And again if another is missed, thereby staying in sync. If several are missed then the receiver sits on the base channel until it gets a packet, from which point its sync’ed and starts hopping again. This is how my own NRF24L01 FHSS R/C project is configured and it works perfectly :-)

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.