DIY - Universal RC Joystick: receiver

  • avatar
  • 1 Like
  • 10 mins read

Wireless communication implies having a transmitter, in our case the joystick, and a receiver. We must build a simple circuit with NRF24L01 wiring that will act as listener for our joystick. The NRF24L01 module strictly needs 3.3V but the logic pins are 5V tolerant. That why we recommend to use the NRF24L01 adapter which acts as regulator, keep the voltage stable, apply filtering and reduce noises.

Below you will find all links to the project posts.

AriControl project

DIY - Universal RC Joystick: concept

DIY - Universal RC Joystick: controls

DIY - Universal RC Joystick: mounting

DIY - Universal RC Joystick: code

DIY - Universal RC Joystick: receiver

DIY - Universal RC Joystick



1x Arduino Nano (or another Arduino module)


Buy now


1x Mini-breadboard


Buy now


1x NRF24L01 (or NRF24L01+ or NRF24L01+PA+LNA)


Buy now


1x NRF24L01 adapter


Buy now


Dupont wires


Buy now

Wiring schema

The NRF24L01 module communicates with the Arduino using SPI protocol. That means that the MOSI, MISO and SCK pins must be connected to their corresponding pins on the microcontroller (another Arduino Nano):

  • MOSI: Arduino Nano D11

  • MISO: Arduino Nano D12

  • SCK: Arduino Nano D13

  • CE and CSN are connected to Arduino Nano D9 and D10 respectively (other pins can be used)

Note: pin D10 is a special pin, it must be set as OUTPUT for the Arduino Nano to operate as a SPI master.

Installing Arduino libraries

To interact with NRF24L01 module we make use of existing libraries. The library provides an interface to communicate with the module saving us a lot of time. Another advantage is robust code base tested and improved by the community during years. We recommend to make use of the library and avoid implementing everything from scratch.

Interaction with AirControl is simplified too. We have created a library that works for the transmitter as well as the receiver. Both libraries can be downloaded from our official repository:

To import a library, open the Arduino IDE, go to Sketch > Include Library > Add .ZIP Library and select the library file downloaded from our GitHub repository.


Then you can simply use include statement:

#include "RF24.h"
#include "nRF24L01.h"
#include "AirControl_v1.h"

AirControl library

AirControl library defines structures for the joystick and control elements available on it. It aggregates small structs that describe each control element and allows to know in every moment the current state of each element.

struct button {
byte pressed = 0;

struct toggle {
byte on = 0;

struct potentiometer {
byte level = 0;

struct analog {
short x, y;

button button;

//Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct air_control {
char key[10] = "hibit";

struct {
analog left, right;
} analogs;

struct {
toggle upper, lower;
} toggles;

struct {
struct {
button upper, lower;
} left, right;
} buttons;

struct {
potentiometer left, right;
} potentiometers;

The library also provides a debug function that outputs the current state of the controller. We will see it later in the testing section.

Receiver Arduino code

We will be listening for the AirControl struct and validating incoming signals. The connection will be considered lost after INTERVAL_MS_SIGNAL_LOST milliseconds without incoming message.

  • setup() function initiates the NRF24L01 module as receiver with the provided configuration.

  • loop() function takes care of listening for the payload and handling it.

  • invalidConnection() function handles unknown connections to prevent unwanted behavior.

  • lostConnection() function handles lost connections to prevent unwanted behavior.

// -------------------------------------------------
// Copyright (c) 2022 HiBit <>
// -------------------------------------------------

#include "SPI.h"
#include "RF24.h"
#include "nRF24L01.h"
#include "AirControl.h"

// NRF24 pins
#define RF24_CE_PIN 9
#define RF24_CSN_PIN 10 //PIN 10 must be output to work as SPI master

// Time intervals for connection

// Debug & security
#define DEBUG false
#define SECURITY_KEY "hibit"

// Air Control joystick
air_control airControl;

// NRF24L01
RF24 radio(RF24_CE_PIN, RF24_CSN_PIN);

// Latest signal time
unsigned long lastSignalMillis = 0;

void setup()
if (DEBUG) {

const byte address[6] = "00001";


//Append ACK packet from the receiving radio back to the transmitting radio
radio.setAutoAck(false); //(true|false)
//Set the transmission datarate
radio.setDataRate(RF24_250KBPS); //(RF24_250KBPS|RF24_1MBPS|RF24_2MBPS)
//Greater level = more consumption = longer distance
radio.setPALevel(RF24_PA_LOW); //(RF24_PA_MIN|RF24_PA_LOW|RF24_PA_HIGH|RF24_PA_MAX)
//Default value is the maximum 32 bytes
//Act as receiver
radio.openReadingPipe(0, address);

void loop()
if (radio.available() > 0) {, sizeof(air_control));

if (strcmp(airControl.key, SECURITY_KEY) != 0) {
invalidConnection(); // Skip if security code is not used
} else {
if (DEBUG) {

// Available values

// (byte) airControl.analogs.left.x
// (byte) airControl.analogs.left.y
// (byte) airControl.analogs.left.button.pressed

// (byte) airControl.analogs.right.x
// (byte) airControl.analogs.right.y
// (byte) airControl.analogs.right.button.pressed

// (byte) airControl.toggles.upper.on
// (byte) airControl.toggles.lower.on

// (byte) airControl.buttons.left.upper.pressed
// (byte) airControl.buttons.left.lower.pressed
// (byte) airControl.buttons.right.upper.pressed
// (byte) airControl.buttons.right.lower.pressed

// (byte) airControl.potentiometers.left.level
// (byte) airControl.potentiometers.right.level

lastSignalMillis = millis();

if (lastSignalMillis != 0 && millis() - lastSignalMillis > INTERVAL_MS_SIGNAL_LOST) {

void invalidConnection()
if (DEBUG) {
Serial.println("Data received but security key is invalid!");


void lostConnection()
if (DEBUG) {
Serial.println("Connection lost, preventing unwanted behavior!");


Note: acting against invalid and/or lost signals depends on each project and must be implemented there.


As part of communication security we have added a security key, defined in SECURITY_KEY constant. It's attached every time the transmitter generates a payload. On the receiver side there is a key guard to make sure incoming key is valid and avoid executing our code in case it's not.


Enabling debug constant in the main code will output AirControl status via the serial monitor every 100ms.

#define DEBUG true

The output is a graphical representation of the joystick. Push and switch buttons are represented with 1 (on) and 0 (off). In case of potentiometers and analog joystick axes it will be a number between 0 and 255. Debug function also provides some useful information as set security key and used buffer bytes. Just as reminder, NRF24L01 buffer is limited to 32 bytes.

AirControl testing output

As you can see, buttons state is represented with one digit within brackets and variable values with 3 digits.


Using libraries and predefined structures make things uncomplicated. We clearly reused transmitter's code and avoided writing same things again and again. Prepared debug function provides a real-time output of the state for each control element, that simplifies the verification of the incoming payload.


Official GitHub:

Official nRF24 GitHub:

 Join Our Monthly Newsletter

Get the latest news and popular articles to your inbox every month

We never send SPAM nor unsolicited emails


Leave a Reply

Your email address will not be published.

Replying to the message: View original

Hey visitor! Unlock access to featured articles, remove ads and much more - it's free.