8CH Transmitter & Receiver using Arduino Nano & NRF24L01 + PA
8CH Transmitter & Receiver using Arduino Nano & NRF24L01 + PA
This project creates a professional 8-channel wireless transmitter and receiver system using Arduino Nano and NRF24L01+PA modules. The system features custom PCBs, failsafe mechanisms, BLDC motor control with safety features, and supports multiple servo outputs for RC applications.
System Overview
Key Features:
- Professional PCBs: Custom designed transmitter and receiver boards
- Long Range: NRF24L01+PA+LNA modules with 500m+ range capability
- 8 Channels: 4 analog joystick axes + 2 switches + 2 potentiometers
- Failsafe Protection: Automatic neutral position on signal loss
- BLDC Motor Control: Safety arming sequence with throttle limit
- Visual Feedback: LED indicators and buzzer alerts
- Compact Design: Integrated Arduino Nano and power management
Materials Required
Core Components:
- 2x Arduino Nano - Buy Here
- 2x NRF Module NRF24L01+PA+LNA - Buy Here
- 2x NRF Adapter - Buy Here
- Mini Rocker Switch - Buy Here
- Mini Toggle Switch 3-Pin - Buy Here
- 50k Potentiometer 3-pin - Buy Here
- Buzzer - Buy Here
- 5mm Red LED - Buy Here
- 330 Ohm Resistor - Buy Here
- 470µF Capacitor - Buy Here
- 0.1µF Ceramic Capacitor - Buy Here
- Mini SPDT Slide Switch - Buy Here
- Male & Female Headers - Buy Here
- 7.4V Battery - Buy Here
For Testing (Optional):
- 11.1V 2200mAh 3S 80C LiPo Battery - Buy Here
- Brushless Motor with 30A ESC - Buy Here
- Servo Motor SG90 - Buy Here
- Servo MG995R - Buy Here
- Servo MG90 - Buy Here
PCB Gerber Files
Download the custom PCB design files for both transmitter and receiver:
Download Gerber FilesManufacture your PCBs using JLCPCB's professional service:
Order PCBs NowChannel Configuration
CH1 & CH2
Left Joystick X/Y
CH3 & CH4
Right Joystick X/Y
CH5 & CH6
Toggle Switches
CH7 & CH8
Potentiometers
Step-by-Step Assembly
Transmitter Assembly:
- Solder all headers and connectors to the transmitter PCB
- Install Arduino Nano in the designated socket
- Mount NRF24L01 adapter and connect the module
- Install joystick modules and connect to analog pins
- Solder toggle switches with proper pull-up resistors
- Install potentiometers for CH7 and CH8
- Connect buzzer and LED with current-limiting resistor
- Add power switch and battery connector
- Install 470µF capacitor near power input for stability
Receiver Assembly:
- Solder all servo output headers (CH1-CH8)
- Install Arduino Nano in the designated socket
- Mount NRF24L01 adapter and connect the module
- Add power input connector and smoothing capacitors
- Install status LED for connection indication
- Add optional voltage regulator if using higher voltage batteries
- Test all servo outputs with a multimeter
Testing & Calibration:
- Upload transmitter code to the first Arduino Nano
- Upload receiver code to the second Arduino Nano
- Power both units and check NRF24L01 module status LEDs
- Test each channel using servo tester or multimeter
- Calibrate joystick center points in the code if needed
- Test failsafe by disconnecting power from transmitter
- Verify BLDC safety arming sequence
- Test range in open area with obstacles
⚠️ Safety Warning:
BLDC MOTORS CAN BE DANGEROUS! Always test with propeller removed. Ensure failsafe is working properly before connecting to aircraft or vehicles. Keep fingers and loose clothing away from rotating parts.
🎥 Watch the video above for complete assembly and demonstration of the 8CH Transmitter & Receiver system.
Arduino Code
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
// Define NRF24L01 pins
#define CE_PIN 7
#define CSN_PIN 8
// Define channel pins
#define SWITCH_1 2
#define SWITCH_2 3
#define POT_1 A4
#define POT_2 A5
#define JOY_L_X A2
#define JOY_L_Y A3
#define JOY_R_X A0
#define JOY_R_Y A1
#define BUZZER_PIN 9
// Create radio object
RF24 radio(CE_PIN, CSN_PIN);
// Define the data structure to be transmitted
struct DataPacket {
uint16_t ch1; // Left Joystick X
uint16_t ch2; // Left Joystick Y
uint16_t ch3; // Right Joystick X
uint16_t ch4; // Right Joystick Y
uint16_t ch5; // Switch 1
uint16_t ch6; // Switch 2
uint16_t ch7; // Potentiometer 1
uint16_t ch8; // Potentiometer 2
uint8_t connectionStatus; // Connection status flag
};
DataPacket txData;
// Address for communication
const byte address[6] = "00001";
// Connection monitoring variables
unsigned long lastConnectionTime = 0;
bool connectionLost = false;
void setup() {
Serial.begin(9600);
// Initialize pins
pinMode(SWITCH_1, INPUT_PULLUP);
pinMode(SWITCH_2, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// Initialize radio
if (!radio.begin()) {
Serial.println("Radio initialization failed!");
while (1);
}
radio.setPALevel(RF24_PA_MAX);
radio.setDataRate(RF24_250KBPS);
radio.setChannel(100);
radio.openWritingPipe(address);
radio.stopListening();
// Initialize data structure
memset(&txData, 0, sizeof(txData));
Serial.println("Transmitter Initialized");
}
void readControls() {
// Read joysticks (map from 0-1023 to 1000-2000 like RC standard)
txData.ch1 = map(analogRead(JOY_L_X), 0, 1023, 1000, 2000);
txData.ch2 = map(analogRead(JOY_L_Y), 0, 1023, 1000, 2000);
txData.ch3 = map(analogRead(JOY_R_X), 0, 1023, 1000, 2000);
txData.ch4 = map(analogRead(JOY_R_Y), 0, 1023, 1000, 2000);
// Read switches (invert because INPUT_PULLUP gives HIGH when open)
txData.ch5 = digitalRead(SWITCH_1) ? 1000 : 2000;
txData.ch6 = digitalRead(SWITCH_2) ? 1000 : 2000;
// Read potentiometers
txData.ch7 = map(analogRead(POT_1), 0, 1023, 1000, 2000);
txData.ch8 = map(analogRead(POT_2), 0, 1023, 1000, 2000);
// Add connection status
txData.connectionStatus = 1;
}
void connectionBeep() {
// Beep pattern: two short beeps
digitalWrite(BUZZER_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
delay(50);
digitalWrite(BUZZER_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
}
void loop() {
static unsigned long lastBeepTime = 0;
static unsigned long lastTxTime = 0;
readControls();
// Try to send data
bool txSuccess = radio.write(&txData, sizeof(txData));
if (txSuccess) {
lastConnectionTime = millis();
connectionLost = false;
digitalWrite(BUZZER_PIN, LOW); // Ensure buzzer is off when connected
}
// Check connection status
if (millis() - lastConnectionTime > 1000) { // 1 second timeout
connectionLost = true;
}
// Handle beeping when connection is lost
if (connectionLost) {
if (millis() - lastBeepTime > 2000) { // Beep every 2 seconds
connectionBeep();
lastBeepTime = millis();
}
}
// Transmission rate control (approx 50Hz)
unsigned long currentMillis = millis();
if (currentMillis - lastTxTime < 20) {
delay(20 - (currentMillis - lastTxTime));
}
lastTxTime = millis();
// Debug output (optional)
if (millis() % 1000 < 20) {
Serial.print("CH1:");
Serial.print(txData.ch1);
Serial.print(" CH2:");
Serial.print(txData.ch2);
Serial.print(" Status:");
Serial.println(connectionLost ? "Lost" : "Connected");
}
}
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>
// Define NRF24L01 pins
#define CE_PIN A3
#define CSN_PIN A2
// Define output pins for channels
#define CH1_PIN 2 // Left Joystick X -> BLDC (ESC)
#define CH2_PIN 3 // Left Joystick Y -> Servo
#define CH3_PIN 4 // Right Joystick X -> Servo
#define CH4_PIN 5 // Right Joystick Y -> Servo
#define CH5_PIN 6 // Switch 1 -> Servo
#define CH6_PIN 9 // Switch 2 -> Servo
#define CH7_PIN A0 // Potentiometer 1 -> Servo
#define CH8_PIN A1 // Potentiometer 2 -> Servo
// Create radio object
RF24 radio(CE_PIN, CSN_PIN);
// Create servo objects
Servo ch1_servo; // Will be used for BLDC ESC
Servo ch2_servo;
Servo ch3_servo;
Servo ch4_servo;
Servo ch5_servo;
Servo ch6_servo;
Servo ch7_servo;
Servo ch8_servo;
// Define the data structure to be received
struct DataPacket {
uint16_t ch1; // Left Joystick X
uint16_t ch2; // Left Joystick Y
uint16_t ch3; // Right Joystick X
uint16_t ch4; // Right Joystick Y
uint16_t ch5; // Switch 1
uint16_t ch6; // Switch 2
uint16_t ch7; // Potentiometer 1
uint16_t ch8; // Potentiometer 2
uint8_t connectionStatus; // Connection status flag
};
DataPacket rxData;
// Address for communication
const byte address[6] = "00001";
// Connection monitoring variables
unsigned long lastPacketTime = 0;
bool connectionLost = true;
// Safety variables for BLDC
uint16_t lastCh1Value = 1500; // Neutral position for BLDC
bool bldcArmed = false;
unsigned long bldcArmTime = 0;
void setup() {
Serial.begin(9600);
// Attach servos to pins
ch1_servo.attach(CH1_PIN); // BLDC ESC on CH1
ch2_servo.attach(CH2_PIN);
ch3_servo.attach(CH3_PIN);
ch4_servo.attach(CH4_PIN);
ch5_servo.attach(CH5_PIN);
ch6_servo.attach(CH6_PIN);
ch7_servo.attach(CH7_PIN);
ch8_servo.attach(CH8_PIN);
// Initialize all outputs to neutral position (1500µs)
ch1_servo.writeMicroseconds(1500); // Critical for BLDC safety
ch2_servo.writeMicroseconds(1500);
ch3_servo.writeMicroseconds(1500);
ch4_servo.writeMicroseconds(1500);
ch5_servo.writeMicroseconds(1500);
ch6_servo.writeMicroseconds(1500);
ch7_servo.writeMicroseconds(1500);
ch8_servo.writeMicroseconds(1500);
// Initialize radio
if (!radio.begin()) {
Serial.println("Radio initialization failed!");
while (1);
}
radio.setPALevel(RF24_PA_MAX);
radio.setDataRate(RF24_250KBPS);
radio.setChannel(100);
radio.openReadingPipe(0, address);
radio.startListening();
// BLDC safety initialization
delay(1000); // Wait for ESC to initialize
ch1_servo.writeMicroseconds(1000); // Send minimum throttle
delay(2000); // Wait for ESC to recognize
ch1_servo.writeMicroseconds(1500); // Back to neutral
Serial.println("Receiver Initialized - Waiting for connection...");
}
void processBLDCSafety(uint16_t ch1Value) {
// BLDC/ESC Safety logic
static uint16_t safeThrottle = 1000;
if (connectionLost) {
// If connection lost, set to minimum throttle
safeThrottle = 1000;
bldcArmed = false;
} else {
// Arm sequence: throttle must go to minimum first
if (!bldcArmed) {
if (ch1Value <= 1100) { // Throttle at minimum
bldcArmed = true;
bldcArmTime = millis();
safeThrottle = 1000;
} else {
safeThrottle = 1000; // Keep at minimum until armed
}
} else {
// After arming, apply throttle with limits
if (millis() - bldcArmTime < 2000) {
// Slow start for 2 seconds after arming
safeThrottle = constrain(ch1Value, 1000, 1200);
} else {
// Normal operation with full range
safeThrottle = constrain(ch1Value, 1000, 2000);
}
}
}
// Apply the safe throttle value
ch1_servo.writeMicroseconds(safeThrottle);
}
void updateOutputs() {
// Update all channels independently
// Channel 1: BLDC with safety processing
processBLDCSafety(rxData.ch1);
// Channel 2-8: Servos with constraints
ch2_servo.writeMicroseconds(constrain(rxData.ch2, 1000, 2000));
ch3_servo.writeMicroseconds(constrain(rxData.ch3, 1000, 2000));
ch4_servo.writeMicroseconds(constrain(rxData.ch4, 1000, 2000));
ch5_servo.writeMicroseconds(constrain(rxData.ch5, 1000, 2000));
ch6_servo.writeMicroseconds(constrain(rxData.ch6, 1000, 2000));
ch7_servo.writeMicroseconds(constrain(rxData.ch7, 1000, 2000));
ch8_servo.writeMicroseconds(constrain(rxData.ch8, 1000, 2000));
}
void loop() {
// Check for incoming data
if (radio.available()) {
radio.read(&rxData, sizeof(rxData));
lastPacketTime = millis();
if (connectionLost) {
connectionLost = false;
Serial.println("Connection established!");
}
// Update outputs
updateOutputs();
// Optional debug output
static unsigned long lastPrintTime = 0;
if (millis() - lastPrintTime > 1000) {
Serial.print("CH1:");
Serial.print(rxData.ch1);
Serial.print(" CH2:");
Serial.print(rxData.ch2);
Serial.print(" CH7:");
Serial.print(rxData.ch7);
Serial.print(" Connected:");
Serial.println(rxData.connectionStatus);
lastPrintTime = millis();
}
}
// Check for connection timeout (2 seconds)
if (millis() - lastPacketTime > 2000 && !connectionLost) {
connectionLost = true;
Serial.println("Connection lost! Entering failsafe mode.");
// Failsafe: set all channels to neutral/safe positions
ch1_servo.writeMicroseconds(1000); // BLDC to minimum throttle
ch2_servo.writeMicroseconds(1500);
ch3_servo.writeMicroseconds(1500);
ch4_servo.writeMicroseconds(1500);
ch5_servo.writeMicroseconds(1500);
ch6_servo.writeMicroseconds(1500);
ch7_servo.writeMicroseconds(1500);
ch8_servo.writeMicroseconds(1500);
bldcArmed = false; // Disarm BLDC
}
}
Code Features:
- 50Hz Update Rate: Smooth control with minimal latency
- Connection Monitoring: Buzzer alerts for signal loss
- BLDC Safety: Arming sequence prevents accidental throttle
- Failsafe Protection: Auto-neutral on signal loss
- Standard PWM: 1000-2000µs pulse width for compatibility
- Error Handling: Robust connection recovery
Wiring Connections
Transmitter (TX) Connections:
| Component | Arduino Nano Pin | Connection Details |
|---|---|---|
| NRF24L01 CE | D7 | Direct connection |
| NRF24L01 CSN | D8 | Direct connection |
| Left Joystick X | A2 | VRx pin |
| Left Joystick Y | A3 | VRy pin |
| Right Joystick X | A0 | VRx pin |
| Right Joystick Y | A1 | VRy pin |
| Toggle Switch 1 | D2 | Middle pin with INPUT_PULLUP |
| Toggle Switch 2 | D3 | Middle pin with INPUT_PULLUP |
| Potentiometer 1 | A4 | Middle pin |
| Potentiometer 2 | A5 | Middle pin |
| Buzzer + LED | D9 | Through 330Ω resistor |
Receiver (RX) Connections:
| Channel | Arduino Nano Pin | Output Type |
|---|---|---|
| CH1 (ESC/BLDC) | D2 | Servo PWM (1000-2000µs) |
| CH2 (Servo) | D3 | Servo PWM (1000-2000µs) |
| CH3 (Servo) | D4 | Servo PWM (1000-2000µs) |
| CH4 (Servo) | D5 | Servo PWM (1000-2000µs) |
| CH5 (Servo) | D6 | Servo PWM (1000-2000µs) |
| CH6 (Servo) | D9 | Servo PWM (1000-2000µs) |
| CH7 (Servo) | A0 | Servo PWM (1000-2000µs) |
| CH8 (Servo) | A1 | Servo PWM (1000-2000µs) |
| NRF24L01 CE | A3 | Direct connection |
| NRF24L01 CSN | A2 | Direct connection |
Applications
✈️ Drones & Quadcopters
Complete flight control system
🏎️ RC Cars & Boats
Vehicle control with multiple channels
🤖 Robotics
Wireless robot control
🎮 Custom Controllers
Game development interfaces
🔧 Note: This system is compatible with most RC receivers and ESCs. The NRF24L01+PA+LNA module provides significantly better range than standard NRF24L01 modules. Always test in open areas and maintain line of sight for optimal performance.
Comments
Post a Comment