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 1000m+ 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.
Arduino Code
#include#include #include // 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#include #include #include // 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