DIY - Universal RC Joystick: receiver
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
Components
1x Arduino Nano (or another Arduino module) $3.18 | |
1x Mini-breadboard
| |
1x NRF24L01 (or NRF24L01+ or NRF24L01+PA+LNA) $2.32 | |
1x NRF24L01 adapter $1.07 | |
Dupont wires $1.61 |
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:
RF24 official library: Download here
AirControl official library: Download here
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 <https://www.hibit.dev>
// -------------------------------------------------
#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
#define INTERVAL_MS_SIGNAL_LOST 1000
#define INTERVAL_MS_SIGNAL_RETRY 250
// 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) {
Serial.begin(115200);
}
const byte address[6] = "00001";
radio.begin();
//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
radio.setPayloadSize(sizeof(air_control));
//Act as receiver
radio.openReadingPipe(0, address);
radio.startListening();
}
void loop()
{
if (radio.available() > 0) {
radio.read(&airControl, sizeof(air_control));
if (strcmp(airControl.key, SECURITY_KEY) != 0) {
invalidConnection(); // Skip if security code is not used
} else {
if (DEBUG) {
debug(airControl);
}
// TODO
// 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) {
lostConnection();
}
}
void invalidConnection()
{
if (DEBUG) {
Serial.println("Data received but security key is invalid!");
delay(INTERVAL_MS_SIGNAL_RETRY);
}
}
void lostConnection()
{
if (DEBUG) {
Serial.println("Connection lost, preventing unwanted behavior!");
delay(INTERVAL_MS_SIGNAL_RETRY);
}
}
Note: acting against invalid and/or lost signals depends on each project and must be implemented there.
Security
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.
Testing
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.
As you can see, buttons state is represented with one digit within brackets and variable values with 3 digits.
Conclusion
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.
Credits
Official GitHub: https://github.com/hibit-dev/aircontrol
Official nRF24 GitHub: https://github.com/nRF24
0 Comments