Tag Archives: nrf24l01

New product Monday: pi3hat

I’ve now got the last custom board from the quad A1 up in the mjbots store for sale, the mjbots pi3 hat for $129.

This board breaks out 4x 5Mbps CAN-FD ports, 1 low speed CAN port, a 1kHz IMU and a port for a nrf24l01. Despite its name, it works just fine with the Rasbperry Pi 4 in addition to the 3b+ I have tested with mostly to date. I also have a new user-space library for interfacing with it that I will document in some upcoming posts. That library makes it pretty easy to use in a variety of applications.

Finally, as is customary with these boards, I made a video “getting started” guide:

Updated quad pi3 hat

I made a number of tweaks to the quad A1’s raspberry pi hat to get it ready for production, resulting in r4.1 of the board:


None of the changes were particularly big, but each has some value:

  • The correct switch mode regulator is installed.
  • The auxiliary CAN transceiver was switched to one that supports a larger common mode voltage.  This will allow it to be connected to the power distribution board without smoking.
  • Each of the STM32s now has some GPIO pins connected directly to GPIOs on the raspberry PI primarily to be used for interrupts.
  • Pin headers expose a few gpio pins from each STM32 for interfacing with random external things.
  • The NRF radio module changed orientation and has improved power filtering.
  • I added a microphone to the auxiliary STM32.  The goal is to eventually be able to use that to synchronize external video with onboard data collected during operation more easily.

I’ll bring this up in a future post!

Spread spectrum integration

I’ve been developing a new bi-directional spread spectrum radio to command and control the mjbots quad robot.  Here I’ll describe my first integration of the protocol into the robot.

To complete that integration, I took the library I had designed for the nrfusb, and ported it to run on the auxiliary controller of the pi3 hat.  This controller also controls the IMU and an auxiliary CAN-FD bus.  It is connected to one of the SPI buses on the raspberry pi.  Here, it was just a matter of exposing an appropriate SPI protocol that would allow the raspberry pi to receive and transmit packets.

Slightly unfortunately, this version of the pi3hat does not have interrupt lines for any of the stm32s.  Thus, I created a multiplexed status register that the rpi can use to check which of the CAN, IMU, or RF has data pending.  Then I slapped together a few registers which allowed configuring the ID and reading and writing slots and their priorities.

Then I refactored things around on the raspberry pi side so that one core would keep busy polling for one of those things to become available.  So far, for the things which access SPI, I’ve been putting them locked to an isolcpu cpu to get improved SPI timing.  Eventually, once I have interrupt lines, I might consolidate all of these down to a single core.  That, plus defining an initial mapping between the controls and slots resulted in:

Finally, I created a very simple GL gui application which connects to an nrfusb and a joystick.  It uses Dear ImGui to render a few widgets and glfw to window and read the joystick.


While I was at it, I finally updated my joystick UI to make gait selection a bit faster, and got the robot to do a better job of switching out of the walk gait.  Thus the following video showing all of that hooked together.

Spread spectrum implementation

With a protocol design in hand, the next step was to go and implement it.  My goal was to produce a library which would work on the nrfusb, and also on the auxiliary stm32g4 on the mjbots pi3 hat.  In this first implementation pass however, I only worked with the nrfusb as both transmitter and receiver.

While developing this, I had more than my share of “huh” moments working from the datasheet and with the components.  To begin with, the initial nrf24l01+ modules I got were all Chinese clone ones.  While I was having problems getting auto acknowledgement to work, I discovered that the clones at a minimum were not compatible with genuine Nordic devices.  Thus I reworked genuine parts into the modules I had:


A Nordic clone about to be removed

That didn’t solve any of my immediate problems, but the subsequent modules I got all had genuine chips so it was useful that they all were compatible.

The other more annoying problems are somewhat obvious in hindsight.  For a transmitter to be able to successfully receive an automatic acknowledgment from a receiver, not only does the ID need to be configured in the appropriate RX_ADDR register, but EN_RXADDR also needs to have the correct bit set.  I had assumed that was only required for slave devices as there was no mention of it in any of the Enhanced Shockburst flow charts or setup procedures for transmitters or auto acknowledgment.

The second annoyance, was that when in receiver mode, switching channels seems to kinda work a little bit for some channels even with CE held high, but to be reliable you have to pull CE low and put the unit in standby mode while changing channels.

With those problems (and some others) resolved, I have a reliable bidirectional link that is ultimately tweakable.  Next I’ll integrate this into the quad A1 to actually control the robot and monitor its telemetry.


Spread spectrum protocol design

Last time I discussed the rationale for building a custom control and telemetry solution.  Here I’ll describe the protocol design a little bit, before discussing the implementation in a future post.

Frame design and frequency hopping

The basic idea is that the transmitter sends a frame to the receiver every 20ms, and each frame is sent on a different radio frequency.  A set of frequencies and their order is generated pseudo-randomly based on a “key” that the transmitter and receiver each share ahead of time.  The receiver replies on the same frequency with its telemetry.  Then the transmitter and receiver each switch to the next frequency in the list to get ready for the next frame.

Frequency selection uses a similar scheme as to DSMX.  The “key” is a 32 bit transmitter ID.  From it a simple pseudo-random number generator is used to generate candidate channels from 125 of the nrf24l01+’s available frequencies.  Channels are discarded if they repeat and they are also discarded to maintain roughly equal distribution across the available frequencies of the radio.  That is accomplished by grouping the frequencies into 4 separate “bands”, and enforcing a limit of how many frequencies can be chosen from each band.

The end result is that given an ID, the transmitter and receiver agree on ordered set of 23 frequencies to cycle between which do not repeat and cover most of the available spectrum.

Transmitter and receiver implementation

The transmitters job in this scheme is relatively straightforward.  It transmits one frame, waits a millisecond to see if a reply comes in, then switches frequencies and waits until the next 20ms boundary to send the next frame.

The receiver is slightly more complicated.  During an initial “synchronization” phase, it sits on one frequency for 20 periods waiting to receive a message.  If no message is received, then it advances to the next channel.  This ensures that even if one channel is completely unusable, eventually the transmitter and receiver will connect.

Once the receiver receives a packet and replies, it then enters the “locked” mode.  In this state, after each reception, the channel is changed awaiting the next frame.  If a frame is 10ms overdue, then it is assumed lost and the channel is switched anyway.  If some number of frames are dropped in a row, then the receiver re-enters the “synchronization” phase.

Mapping onto the nrf24l01+

The following parameters are used with the nrf24l01+:

  • 5 byte address field (the address is derived from the ID)
  • dynamic payload length
  • 2 byte CRC
  • no automatic retransmission
  • auto acknowledgment
  • data rate is configurable

Replies are handled using the nrf24l01+’s “Enhanced Shockburst” “auto acknowledgment” feature.  This ensures that the receiver replies to the transmitter within a few microseconds of receiving the transmitter’s packet, relieving the microcontroller of that timing requirement.

“slot” data scheduling

The final piece of this is how to package up the data.  I decided on letting each of the transmitter and receiver define up to 15 slots.  Each slot contains up to 15 bytes of data and can be configured, via a bitmask termed “priority”, to be sent in every frame, every other frame, or some more interesting division.  On the wire, the 32 byte RF packet is filled with slots based on the current frame.  Each slot gets a 1 byte header, with 4 bits denoting which slot is next, and 4 bits denoting the size.  The host is responsible for selecting the bitmasks and sizes such that no frame is over-allocated.

So, if the transmitter has 4 slots that look like:

Slot Size “Priority”
0 (data denoted as “A”) 8
1 (data denoted as “B”) 4
2 (data denoted as “C”) 5
3 (data denoted as “D”) 6

Then it would incorporate those slots into frames as follows.  Each frame is written in hex, with ABCD corresponding to the data from slot 0123 respectively:


It is up to the transmitter and receive to agree upon how data is allocated to slots and the semantics of data inside each slot.  For the quad A1 application, that is hard-coded ahead of time.

Comparison and references

Aside from bidirectional data, the RF portion is pretty similar to DSMX with only a few differences.  First, DSMX uses a Cypress CYRF6936, where I’m using a slightly more available nrf24l01+.  Second, the frequency selection is the same in theory, but has slightly different parameters in implementation.  Third, bidirectional transmission is achieved using a single RF IC and amplifier.

The slot data scheduler is unique, as DSMX only allows one type of data to be transmitted in the control direction – “transmitter channels”.

Next I’ll demonstrate my implementation by itself before any integration!


Spread spectrum RF control and telemetry

Now that I have both sides of the nrf24l01+ link covered, it was time to design a protocol to take advantage of them.

Design space

To recap, what I needed was a reliable means of commanding the robot and receiving telemetry, even in congested radio environments.  At competitions or events like Maker Faire, Robogames and such, the wireless environment is often totally trashed.  Hundreds of devices are operating in close proximity, across all spectrum bands, including plenty of things that probably aren’t licensed to be transmitting in the first place.  When we first built Super Mega Microbot, we used a custom protocol with a 5 GHz wifi transmitter as the physical layer and selected USB based dongles which allowed control over the physical layer.  USB proved problematic, and with national RF regulations, it is extremely challenging to find wifi devices which provide that level of control at the RF layer.  Also, even with full physical layer control, wifi is difficult to make work in a reliable manner as there is so much congestion in both the 2.4GHz and 5GHz bands and the channels are so wide.

What does usually work at these events, despite the extreme congestion, are standard hobby RC transmitters.  DSMX from Spektrum, is one of the more popular varieties.  It uses an off the shelf 2.4GHz RF IC, and then hops between frequencies every transmission based on a pseudorandom key shared between transmitter and receiver.  This enables many transmitters to share the same RF environment, and renders them extremely resistant to interference.

The biggest problem with DSMX for this application (and basically every other RC protocol), is that they are unidirectional.  At best, bidirectional solutions involve sticking two independent transmitter and receiver pairs in opposite directions.  This is despite the fact that most of the low level RF ICs actually support bidirectional communications natively.  Even then, the only supported telemetry forms are things specific to RC models.  Joint position feedback would need to be encoded in propeller RPM for instance!

Proposed design goals

The target features I’m looking to achieve with this protocol are:

  • Resistant to heavy RF interference
  • Bidirectional communication of arbitrary data
  • 50Hz update rate or higher
  • Multiple transmitters and receivers can operate in the same area without interfering or explicitly coordinating
  • Support data that is transmitted at different rates (i.e. voltage telemetry update can be low rate, but movement commands are high rate)
  • Control over source to add new features over time

Things I’m not necessarily trying to accomplish (yet):

  • Long range
  • A “binding” mode that uses RF to share the common psuedorandom key
  • A “stable” over the air protocol

Next up, a design which hopefully achieves these goals!


In order to bring up the final piece of the raspberry pi 3 hat, the nrf24l01+, I wanted a desktop development platform that would allow for system bringup and also be useful as a PC side transmitter.  Thus, the nrfusb:


Similar to the fdcanusb, it is just an STM32G474 on the USB bus, although this has a pin header for a common nrf24l01+ form factor daughterboard.

The next steps here are to get this working at all, then implement a spread spectrum bidirectional protocol for control and telemetry.