Racing wheel for RC car

It’s every boys dream: sitting behind the wheel of a real race car. Unfortunately, money (or the lack of) seems to be the main cause of not making this dream come true. So as many boys do, RC cars become the next best thing. But holding a remote in your hand, just isn’t the same thing. So here is the next best thing.

Race car

The idea

The idea is simple enough: find an old gaming wheel, preferably with force feedback and pedals, and make it work with a RC car.
And as a cherry on top, maybe even a camera and goggles, to give that extra sense of (virtual) reality.

Required knowledge

Since this is a slightly advanced project, there are a few things you should understand the basics of.

  • nRF24L01 module (SPI)
    In this project, an example of a 2-way communication is provided so you can test your setup easily. Used library: TMRh20 / RF24
  • using registers (PIND instead of digitalRead)
    There’s a tutorial about registers on Arduino available here
  • controlling servos using the <Servo.h> library
    There’s a “quicky” available here
  • multiplexing with x*y arrays (4*4 in this projects)
    There are some great tutorials available at PCB Heaven and Instructables Circuits.
  • using negative logic (True = 0V/GND; False = +5V)

Choosing the components

Coming up with the idea is the easy part. Building it AND making it work, may be a tiny bit more challenging. So the first thing to do: breaking the problem into little pieces.

  • Deciding which steering wheel to use and reverse engineer it (figuring out how it works)
  • Finding a way to measure vibration on the RC car
  • Sending data to the RC car, and reading data from the RC car
  • Controlling the servo and speed controller of the RC car
  • Optionally: vision from the car
  • Putting the pieces together

Steering wheel

In this project, the “Logitech Formula Vibration Feedback Wheel” is used, which is a USB gaming device. It’s rated for 5VDC with a 500mA maximum current consumption.
Why: simply because it was laying around. But basically, any gaming device should be usable, and is very likely similarly built.

Logitech racing Wheel
Logitech Formula Vibration Feedback Wheel

Measuring vibration

A cheap way to measure vibration is by using accelerometers. Since the steering wheel has 2 vibration motors, a light one and a heavy one that can be controlled individually, it makes sense to use a 3-axis acceleration sensor (X, Y, Z).

Initially the LIS3DH was purchased, but it was dead on arrival, so no testing has been done so far. At the moment, this step is skipped.

LIS3DH

The LIS3DH is a 3-axis accelerometer and can communicate over SPI as well as I2C.

In the meantime, the ADXL345 (3-axis accelerometer) arrived, and it looks to be working properly. It can communicate over I2C.

Sending and receiving data

The communication between the steering wheel and the car must be able to go both directions, if force feedback is a requirement. If not, a single way communication suffices.

A possible way to do this, is by using two nRF24L01 modules. These are 2.4GHz modules that can transmit (Tx) as well as receive (Rx). They operate at 3.3V and interface over SPI.
Note: For testing purposes, the 100m range is sufficient, but for using it in the field, the antenna version (nRF24L01 + PA + LNA) with a range of about 800m is preferred.

nRF24L01 (PA / LNA)
nRF24L01 VS nRF24L01 + PA + LNA

The nRF24L01 + PA + LNA are still in backorder. Hopefully they will arrive soon.

Controller

Since all the components work at 5V and/or 3.3V DC, and interface over SPI or I2C, the Arduino boards are a good choice here: Widely available, low cost and easy to use.

For the transmitter (steering wheel), the Arduino Uno has just enough I/O.

For the receiver (RC car), the Arduino Micro or Nano can be used, or if you are using a large RC car, the Uno will do fine as well.

Arduino Uno
Arduino Uno (rev. 3)

Vision from the car

This part is optional but may be the easiest one. Mounting a camera on the car and using some VR goggles, makes you believe you’re actually sitting in the car. There are two ways to go:

  • Get a camera and goggle set from the store,
    which is very likely going to be the cheap and stable solution
  • Build it yourself with, for example, a Raspberry Pi, Pi cam and some RF modules

In this project, the camera part will not be covered.

Reverse engineering the wheel

Maybe the hardest part in this project (and any others) is figuring out how “stuff” works that was built by some else, and no manuals are available. The way to go: look, speculate, measure and test, try again, repeat process.
Simply put: Reverse Engineering

Before giving you a complex walk through, the final result is the schematics you can download below:

Logitech racing wheel schematics
Logitech racing wheel schematics

The schematics for the reverse engineering of the Logitech wheel can be downloaded below

You may have noticed that there are too many buttons and too few inputs on an Arduino Uno. There are a few simple ways to solve this by either

  1. use less buttons
  2. use an Arduino Mega board with many inputs
  3. use multiplexing

Since this wheel is already build for multiplexing, option 3 seems like the most sensible one. It also comes with a few very nice advantages:

  • No cutting connectors is needed
  • No soldering in the steering wheel is required
  • No changes on the PCB boards are necessary (no cutting or bridging PCB lanes)
  • Easily reverted to its original state

That’s right, by using the multiplex method, the original state can be preserved, allowing to easily revert to the a USB gaming device. So unless something gets blown up during testing, nothing will be permanently destroyed or altered.

Parts of the steering wheel

Basically, the steering wheel is built out of several parts, assembled by using cables with connectors, all connected to one USB main board. It is this main board that will be replaced with the Arduino board.

The connections to this board are

  • USB cable (which can simple be removed)
  • Steering wheel (left/right direction)
  • Buttons on the steering wheel (largest connector)
  • Pedals (brake and throttle via DB9 connector)
Connectors of the steering wheel
Connectors of the steering wheel

Pedal connector

Potentiometers pedals

The pedals (brake and throttle) are two potentiometers with common power supply (+5VDC) and ground (GND).

WireSignal
RedGND
GreenThrottle
WhiteBrake
Yellow / Black+5V

Steer connector

Potentiometer steering wheel

The steering wheel (turn left or right) is also a potentiometer.

WireSignal
RedGND
WhiteSteer left / right
Black +5VDC

Buttons connector

The buttons are placed in a matrix (for multiplexing), and can be read by using 4 digital inputs and 4 digital outputs. (Note that the pad is just 4 buttons.)
The vibration button needs a separate digital input and the corresponding LED a digital output.
The vibration motors in the steering wheel, need 1 digital output each.

#WireButtonGroup
1BlackButton 12
Button 8
Button 4
Button 7
1
2
3
4
2BrownButton 10
Button 1
Button 3
Pad UP
1
2
3
4
3RedButton 11
Button 2
Pad DOWN
Pad LEFT
1
2
3
4
4OrangeButton 9
Shift UP
Shift DOWN
Pad RIGHT
1
2
3
4
5YellowMUX group 22
6GreenMUX group 11
7BlueMUX group 33
8PurpleMUX group 44
9GreyVibration buttonV
10WhiteLEDV
11Black Vibration func. common Vcc (+5V)V
12BrownLight vibratorV
13RedHeavy vibratorV
14OrangeVibration func. common ground (GND)V

To sum everything up that is required for full control

  • Power supply (+5VDC) and ground (GND)
  • 3 analogue inputs (brake + throttle + steer)
  • 7 digital outputs (4 for multiplex + 2 vibration motors + 1 LED)
  • 5 digital inputs (4 for multiplexing + 1 vibration button)

The power supply of 5VDC is best done via a separate power supply, since the Arduino Uno has a 5V power regulator that is not suited for controlling the 2 vibration motors. If you are not using the vibration motors, the 5V from the Arduino Uno suffices to power the steering wheel and LED.

Multiplexing the buttons

The steering wheel has 17 buttons in total, with 16 buttons in a 4×4 matrix. This means you wil need 4 inputs and 4 outputs to read all 16 buttons with just 8 I/O pins used, as illustrated in the schematic below.

Button matrix
Schematics for the button matrix
  • 4 outputs are used for selecting the button groups (mux)
    • Yellow (5)
    • Green (6)
    • Blue (7)
    • Purple (8)
  • 4 inputs are used for reading the button states (sense)
    • Black (1)
    • Brown (2)
    • Red (3)
    • Orange (4)

Since this system is build for negative logic, the output needs to be set to GND to select the group of buttons you want to read. The remaining outputs must be +5V. (see also pull-up resistors).

// Digital I/O
bool b[17];   // 0..16 = 17 elements, but [0] will not be used

// Setup
void setup()
{
  // NEGATIVE logic !
  pinMode(8, INPUT_PULLUP);  // Sense 1 - black (1)
  pinMode(9, INPUT_PULLUP);  // Sense 2 - brown (2)
  pinMode(10, INPUT_PULLUP); // Sense 3 - red (3)
  pinMode(11, INPUT_PULLUP); // Sense 4 - orange (4)

  pinMode(4, OUTPUT); // MUX 1 - yellow (5)
  pinMode(5, OUTPUT); // MUX 2 - green (6)
  pinMode(6, OUTPUT); // MUX 3 - blue (7)
  pinMode(7, OUTPUT); // MUX 4 - purple (8)

  Serial.begin(115200);
}

// Loop
void loop()
{
  // Read 16 inputs
  readButtons();  // read all multiplexed buttons

  for (int i=1; i<= 16; i++)
  {
    // NEGATIVE logic so invert !
    b[i] = not(b[i]);
  }

  if (b[1]) { Serial.println("Button 8"); }
  if (b[2]) { Serial.println("Button 1"); }
  if (b[3]) { Serial.println("Button 2"); }
  if (b[4]) { Serial.println("Shift UP"); }
  if (b[5]) { Serial.println("Button 12"); }
  if (b[6]) { Serial.println("Button 10"); }
  if (b[7]) { Serial.println("Button 11"); }
  if (b[8]) { Serial.println("Button 9"); }
  if (b[9]) { Serial.println("Button 4"); }
  if (b[10]) { Serial.println("Button 3 "); }
  if (b[11]) { Serial.println("Pad DOWN"); }
  if (b[12]) { Serial.println("Shift DOWN"); }
  if (b[13]) { Serial.println("Button 7"); }
  if (b[14]) { Serial.println("Pad UP"); }
  if (b[15]) { Serial.println("Pad LEFT"); }
  if (b[16]) { Serial.println("Pad RIGHT"); }

  delay(100); // For debugging purpose only
}

// Read buttons
void readButtons()
{
  // Read group 1
  digitalWrite(4, LOW); // NEGATIVE logic !
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  b[1] = digitalRead(8);  // Button 8 - yellow (5) + black (1)
  b[2] = digitalRead(9);  // Button 1 - yellow (5) + brown (2)
  b[3] = digitalRead(10); // Button 2 - yellow (5) + red (3)
  b[4] = digitalRead(11); // Shift UP - yellow (5) + orange (4)
  
  // Read group 2
  digitalWrite(4, HIGH);
  digitalWrite(5, LOW);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  b[5] = digitalRead(8);  // Button 12 - green (6) + black (1)
  b[6] = digitalRead(9);  // Button 10 - green (6) + brown (2)
  b[7] = digitalRead(10); // Button 11 - green (6) + red (3)
  b[8] = digitalRead(11); // Button 9 - green (6) + orange (4)
  
  // Read group 3
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, LOW);
  digitalWrite(7, HIGH);
  b[9] = digitalRead(8);   // Button 4 - blue (7) + black (1)
  b[10] = digitalRead(9);  // Button 3 - blue (7) + brown (2)
  b[11] = digitalRead(10); // Pad DOWN - blue (7) + red (3)
  b[12] = digitalRead(11); // Shift DOWN - blue (7) + orange (4)
  
  // Read group 4
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, LOW);
  b[13] = digitalRead(8);  // Button 7 - purple (8) + black (1)
  b[14] = digitalRead(9);  // Pad UP - purple (8) + brown (2)
  b[15] = digitalRead(10); // Pad LEFT - purple (8) + red (3)
  b[16] = digitalRead(11); // Pad RIGHT - purple (8) + orange (4)
}

Since digitalWrite(<pin>) and digitalRead(<pin>) is relatively slow, the use of registers is recommended. The function readButtons() will be replaced by

// Read buttons
void readButtons()
{
  for (int i=0; i<=3; i++)
  {
    // USE NEGATIVE LOGIC !!!
    if(i == 0)
    { PORTD = B11101111; }  // Select group 1
    else if(i == 1)
    { PORTD = B11011111; }  // Select group 2
    else if(i == 2)
    { PORTD = B10111111; }  // Select group 3
    else if(i == 3)
    { PORTD = B01111111; }  // Select group 4

    delayMicroseconds(3);   // 1µs is too fast, 2µs seems OK for the UNO
    deMultiplex(i);         // de-Multiplex the data
  }
}

void deMultiplex(int i)
{
  // Read register B
  byte inputs_reg_B = PINB  ;

  // De-multiplex
  b[(4*i)+1] = bool(inputs_reg_B >> 0 & B0001); // Mask bit 0 (D8)
  b[(4*i)+2] = bool(inputs_reg_B >> 1 & B0001); // Mask bit 1 (D9)
  b[(4*i)+3] = bool(inputs_reg_B >> 2 & B0001); // Mask bit 2 (D10)
  b[(4*i)+4] = bool(inputs_reg_B >> 3 & B0001); // Mask bit 3 (D11)
}

Looking at the timing diagram

Timing diagram multiplexer
Timing diagram multiplexer

At point G1, the “select group 1” (yellow wire) signal is set. Only signal read 2 (brown channel) is active, which means button 1 must be pressed.
At point G2 and G4, the “select group 2 and 4” (green and purple wire) signal is set, but no read signals are active there.
At point G3, the “select group 3” (blue wire) signal is set, and now both read 1 (black wire) and read 2 (brown wire) is active, meaning buttons 4 and 3 are pressed.
So in this cycle of about 12µs buttons 1, 3 and 4 were pressed. This means the status of 16 buttons were read in 12µs, with only 8 I/O pins.

Measuring G forces

LIS3DH dead on arrival.
This part is currently paused.

Bidirectional communication

The code below will send and receive data in both directions.
For details, see the comments in de the code.
Don’t forget to set the nodeID!

int nodeID = 1;  // Transmitter 1 (can also receive)
int nodeID = 2;  // Receiver 2 (can also transmit)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

/*
  PIN  | nRF24L01 | UNO
  -----+----------+------
  GND  | 1        | GND
  3V3  | 2        | 3V3   (No 5V on the NRF24L10 !!!)
  CE   | 3        | 9
  CSN  | 4        | 10    (Also referred to as SCN / SS)
  SCK  | 5        | 13
  MOSI | 6        | 11
  MISO | 7        | 12
  IRQ  | 8        | -
*/
RF24 radio(9, 10);       // nRF24L01 (CE, CSN)
const byte address[][6] = {"node1", "node2" };    // Address
int nodeID = 2;                                   // set 1 for first node, set 2 for second node

// For the NRF24L01, the max/ buffer size is 32 bytes;
struct ST_RfData
{
  byte Data1;     // Byte 1
  byte Data2;     // Byte 2
};

// Declare a datablock (struct) of the type
ST_RfData sendST;     // Data block for sending data
ST_RfData recST;      // Data block for receiving data

unsigned long tmrTimeout;
unsigned long sendInterval = 20;        // Send every <sendInterval> ms
unsigned long lastIntervalMillis = 0;   // Holder for the interval timer

void setup()
{
  Serial.begin(115200);
  Serial.println("--- Serial monitor started ---");

  // Wireless comms.
  radio.begin();                        // Start the radio
  radio.setChannel(123);                // Select channel
  radio.setAutoAck(false);              // Disable autoACK

  // Must be opposite adresses
  if (nodeID == 1) {
    radio.openWritingPipe(address[1]);    // Start address for sending data on TX side
    radio.openReadingPipe(1, address[0]); // Start address for receiving data on TX side
  }
  else {  // just accept any number other than 1
    radio.openWritingPipe(address[0]);    // Start address for sending data on RX side
    radio.openReadingPipe(1, address[1]); // Start address for receiving data on RX side
  }

  // radio.setDataRate(RF24_250KBPS);   // LOW speed data transfer, should improve range
  radio.setDataRate(RF24_1MBPS);        // Medium speed will also reduce range slightly
  // radio.setDataRate(RF24_2MBPS);     // High speed will further reduce range

  radio.setPALevel(RF24_PA_MIN);        // LOWEST power, but decrease range (-18dBm)
  // radio.setPALevel(RF24_PA_LOW);     // LOW power (-12dBm)
  // radio.setPALevel(RF24_PA_HIGH);    // HIGH power (-6dBM)
  // radio.setPALevel(RF24_PA_MAX);     // MAXIMUM power, for long range (0dBm)
}


void loop()
{
  // Constant poll for receiving data
  receiveData();

  // Set random numbers
  sendST.Data1 = random(16, 255);
  sendST.Data2 = random(16, 255);

  // Send every <sendInterval> ms
  if ( lastIntervalMillis + sendInterval <= millis())
  {
    // Sending data
    if (sendData())
    { Serial.print("Sending data SUCCESS ---> "); }
    else
    { Serial.print("Sending data FAILED (for some reason) ---> "); }
    lastIntervalMillis = millis();
  }

  // Timeout ?
  if (tmrTimeout + 1000 < millis())
  {
    Serial.println("Timeout - No data received for <timeout> time");
  }
  else
  {
    // Look what's inside the data block
    Serial.print(sendST.Data1, HEX);
    Serial.print(" - ");
    Serial.print(sendST.Data2, HEX);
    Serial.print(" --- ");
    Serial.print(recST.Data1, HEX);
    Serial.print(" - ");
    Serial.print(recST.Data2, HEX);
    Serial.print(" --- ");
    Serial.print(String(millis() - tmrTimeout));
    Serial.println();
  }
}


void receiveData()
{
  delay(5);                   // small delay is required for proper comms
  radio.startListening();     // RX mode
  if (radio.available())
  {
    tmrTimeout = millis();      // Reset the timeout timer
    while (radio.available()) {
      radio.read(&recST, sizeof(ST_RfData));   // Read the whole data and store it into the 'data' structure
    }
  }
}


bool sendData()
{
  // Sending data
  delay(5);                   // small delay is required for proper comms
  radio.stopListening();      // TX mode
  if (radio.write(&sendST, sizeof(ST_RfData)))   // Send the data block (structure) to the receiver
  { return true; }
  else
  { return false; }
}

Controlling RC car

Using Arduino servo library

To control the servo and speed controller of the RC car, the standard servo library (<Servo.h>) will do the job.

For the steering wheel, values 0-90 will turn the steering servo left, 90 will be centre and 90-180 will turn right.
Note: Depending on the model used, the directions may be swapped.

For the brake and throttle, it depends on the controller that is used, but in general any value from 0-90 will brake or reverse the car, 90 is neutral and 90-180 is drive forward.
Note: Again depending on the model, brake/reverse and forward may be swapped.

Tip: It’s always a good idea to first test your code on a servo simulation. In the example image below, the Pi camera mount with 2 servos is used for this.

Wiring the servo simulation
Wiring the servo simulation

Servo wire colour standards

The wire colours for servos and electronics in RC are standardised. The center wire is always the Vcc, left and right are ground (0V) and signal.
Most used colour codes are

BrandGNDVccSignal
Graupner / JRBrownRedOrange
Futaba / RobbeBlackRedWhite
Multiplex / HitecBlackRedYellow
-unknown-BlackRedBlue
Servo colour codes
Servo colour codes

Putting it together

Vibration button and LED

Before putting it all together, there is one thing that hasn’t been addressed yet: The vibration button and LED. It’s important the vibration mode can be turned on and off by pushing the vibration button, using the LED to indicate if vibration mode is on or off, and without interfering with the rest of the code (creating delays).

To handle buttons properly, a class is written to de-bounce a button and detect rising and falling edges, without the use of any delaying functions.
In this project, the rising edge detection is used for toggling vibration mode on or off, by pressing the button for about 0.5s.

You can find the code for the button class below in section Complete Code
Below is an example of how to use this class.

#include "class_button.h";

BUTTON btnVibration = BUTTON(); // Vibration button

void setup()
{
  pinMode(8, INPUT_PULLUP); // Pin 8 connected to vibration button
  Serial.begin(115200);
}

void loop()
{
  btnVibration.state = not(bool(PINB & B00000001));  // NEGATIVE logic !! -> invert for class
  // Fast way way for:   digitalRead(<pinButton>);
  btnVibration.TON_delay = 2000;	// Holding for 2000ms is considered high
  btnVibration.TOF_delay = 1000;	// Releasing for 1000ms is considered low

  btnVibration.refresh();

  if (btnVibration.RE)
  { Serial.println("Rising Edge detected"); }	// Can be used to toggle mode or LED (on / off)
  if (btnVibration.FE)
  { Serial.println("Falling Edge detected"); }
/*
  if (btnVibration.Q)
  { Serial.println("Button: Pressed"); }
  if (!btnVibration.Q)
  { Serial.println("Button: Released"); }
*/
  delay(50);	// only for debugging purposes
}

Grand finale

Wiring the transmitter

On the transmitter side (steering wheel), the Arduino Uno will be connected to the steering wheel and a nRF24L01.

Wiring transmitter schematics
Wiring transmitter schematics

The complete electrical schematics for the transmitter can be downloaded below

A small prototype board for the Arduino Nano (v3), nRF24L01, a LM7805 and the connectors for the steering wheel could look like this

Transmitter prototype PCB
Transmitter prototype PCB

And the final result. If you’re wondering what the resistors are for; the Nano didn’t do very well with the internal pull-up resistors, so I added them afterwards. It’s a big improvement.

Steering wheel wiring

The 4*4 button matrix of the steering wheel will be connected to pins 0..7. Pin 8 will be used for the vibration button and A3, A4 and A5 will be used as digital outputs to control the vibration LED, light vibration motor and heavy vibration motor.
A0, A1 and A2 are used as analogue inputs and are connected to the pot-meters of the steering wheel and pedals.
An array in the code will be used to store the button states.

Mux | Sense | Array | Button     | Wire colors
----+-------+-------+-------------------------------------
-   | -     | b[0]  | -          |
D4  | D0    | b[1]  | Button 8   | yellow (5) + black (1)
D4  | D1    | b[2]  | Button 1   | yellow (5) + brown (2)
D4  | D2    | b[3]  | Button 2   | yellow (5) + red (3)
D4  | D3    | b[4]  | Shift UP   | yellow (5) + orange (4)
D5  | D0    | b[5]  | Button 12  | green (6) + black (1)
D5  | D1    | b[6]  | Button 10  | green (6) + brown (2)
D5  | D2    | b[7]  | Button 11  | green (6) + red (3)
D5  | D3    | b[8]  | Button 9   | green (6) + orange (4)
D6  | D0    | b[9]  | Button 4   | blue (7) + black (1)
D6  | D1    | b[10] | Button 3   | blue (7) + brown (2)
D6  | D2    | b[11] | Pad DOWN   | blue (7) + red (3)
D6  | D3    | b[12] | Shift DOWN | blue (7) + orange (4)
D7  | D0    | b[13] | Button 7   | purple (8) + black (1)
D7  | D1    | b[14] | Pad UP     | purple (8) + brown (2)
D7  | D2    | b[15] | Pad LEFT   | purple (8) + red (3)
D7  | D3    | b[16] | Pad RIGHT  | purple (8) + orange (4)

Pin | Function              | Wire color
----+-----------------------+-------------------------------------
D8  | Vibration button      | orange (14) + grey (9)
A3  | Vibration LED         | black (11) + white (10)
A4  | Ligth vibr. motor     | brown (12) + black (11) + orange (14)
A5  | Heavy vibr. motor     | red (13) + black (11) + orange (14)
                            | (14 = GND | 11 = +5V)
A0  | Steering wheel        |
A1  | Brake pedal           | white
A2  | Gas pedal (throttle)  | green

In the prototype stage, this is how it looks

Wiring Arduino Uno to steering wheel
Wiring Arduino Uno to steering wheel
nRF24L01 wiring

The RF module (nRF24L01) will be wired to the SPI pins of the Arduino Uno as listed below. MOSI, MISO and SCK are fixed at pins 11, 12 and 13. CE and CSN (same as SS or Slave Select) are configurable on the Arduino Uno side, and are set to pins 9 and 10.

SignalnRF24L01Arduino (Uno)Note
GND1GND
3V323V3No 5V on the NRF24L10 !!!
CE39
CSN410Also referred to as SCN / SS
SCK513
MOSI611
MISO712
IRQ8
nRF24L01 Pinout
nRF24L01 Pinout

Wiring the receiver

On the receiver side (RC car), the RF module (nRF24L01) will be connected to the Arduino board, as well as the servo and speed controller of the RC car.

nRF24L01 wiring

The nRF24L01 module will be connected to the SPI pins of the Arduino board.

  • When using an Arduino Uno or Nano, the SPI pins MOSI, MISO and SCK are fixed at pins 11, 12 and 13. CE and CSN can be configured freely, and are for this project set on pins 9 and 10.
  • If you prefer an Arduino Micro, it has separate SPI pins, so pins 11, 12 and 13 remain free.
SignalnRF24L01Arduino
Uno/Nano
Arduino
Micro
Note
GND1GNDGND
3V323V33V3No 5V on the NRF24L10 !!!
CE399
CSN41010Also referred to as SCN / SS
SCK513SCK
MOSI611MOSI
MISO712MISO
IRQ8
RC car wiring

If you are using a RC car on batteries, very likely the speed controllers has a BEC that outputs 4.8V – 5VDC. This power output can be used to power the Arduino (use Vin is possible) and other servos.

If you are using a speed controller without BEC or a fuel powered car where the speed is controlled with another servo, an extra battery (and voltage regulation) may be required. You can use the Vin pin of the Arduino up to 20VDC, and use the +5V pin of the Arduino to power the servo, but in this case, do make sure the servos are not drawing too much power.

Wiring Arduino Uno to RC car
Wiring Arduino Uno to RC car

Final word of caution

Make sure that ALL ground signals (GND of the servo’s, GND of the Arduino, GND of the nRF24L01) are connected together. Failing to do so may cause in unstable behaviour or even damage of some electronics components.
This is a general rule in electronics, and thus is valid for the receiver as well as the transmitter.

Final code

The final code consists of 4 files

  1. rf_structs.h
    Sender as well as receiver need the same definition on the data blocks. Therefore, this part of the code was placed in a separate file, so only 1 file needs to be updated if the data block structure changes.
  2. class_button.h
    This class can be used to de-bounce a button and detect rising and falling edges with a just few lines of code, without causing big delays.
  3. RC_Transmitter.ino
    This is the code for the transmitter or steering wheel.
    It uses both class_button.h and rf_structs.h
  4. Rc_Receiver.ino
    This is the code for the receiver or RC car.
    It also uses rf_structs.h
Included libraries
  • Servo.h (default in Arduino)
  • SPI.h (default Arduino)
  • RF24.h (TMRh20 ; download here)
  • nRF24L01.h (TMRh20 ; download here)
Download complete code
// For the NRF24L01, the max/ buffer size is 32 bytes;
struct ST_RfControl
{
  byte Steer;           // Byte 1
  byte Throttle;
  byte vibrationMode;   // Byte 3
};

struct ST_RfFeedback
{
  byte Xaxis;	// Byte 1
  byte Yaxis;
  byte Zaxis;	// Byte 2
};
/*************************************************************************
 *  To be used as a local class
 ************************************************************************/
class BUTTON
{
  private:
    unsigned long storeMillis = millis();
    unsigned long tmrTON = millis();
    unsigned long tmrTOF = millis();
    bool REpassed = false;  // Remember Rising Edge
    bool FEpassed = false;  // Remember Falling Edge

  public:
    bool state = false;     // State of the button to check
    int TON_delay = 1000;   // Debounce/Delay time for ON/Rising Trigger detection
    int TOF_delay = 1000;   // Debounce/Delay time for OFF/Falling Trigger detection
    bool Q = false;         // Debounce/Delay time passed and button still high
    bool RE = false;        // Rising Edge detected (remains high for only 1 cycle)
    int FE = false;         // Falling Edge detected (remains high for only 1 cycle)

    // Call this every cycle
    void refresh()
    {
      // First check the timers
      if (!this->state)
      {  this->tmrTON = millis(); }
      else  // (this->state)
      {  this->tmrTOF = millis(); }

      // How long is the button PRESSED ?
      if (this->state && (this->tmrTON + this->TON_delay < millis()))
      {
        // Button was continuously pressed for TON_delay time
        this->Q = true;           // Set Q to HIGH state
        this->FE = false;         // Falling Edge low (small change it's still high)
        this->FEpassed = false;   // Falling Edge detection help var low

        // Rising Edge detected ?
        if(!this->REpassed)
        {
          this->RE = true;        // Not yet -> Set Rising Edge detected HIGH
          this->REpassed = true;  // Help var for Rising Edge
        }
        else
        {
          this->RE = false;       // Already detected -> Set Rising Edge detected LOW
        }
      }

      // How long is the button RELEASED ?
      if (!this->state && (this->tmrTOF + this->TOF_delay < millis()))
      {
        // Button was continuously released for TOF_delay time
        this->Q = false;          // Set Q to LOW state
        this->RE = false;         // Rising Edge low (small change it's still high)
        this->REpassed = false;   // Rising Edge detection help var low

        // Falling Edge detected ?
        if(!this->FEpassed)
        {
          this->FE = true;        // Not yet -> Set Falling Edge detected HIGH
          this->FEpassed = true;  // Help var for Falling Edge
        }
        else
        {
          this->FE = false;       // Already detected -> Set Falling Edge detected LOW
        }
      }
    };
};
/*
    Logitech Steering Wheel for RC cars with nRF24L01 radio
    =======================================================

    Steering wheel:
      Logitech Formula Vibration Feedback Wheel
      M/N:E-UK/12
      P/N: 863248-0000

    Written for:
      Transmitter: Arduino UNO (R3) + nRF24L01 module
      Receiver: Arduino UNO (R3) + nRF24L01 module + optional accellerometers

    Purpose:
      Controlling RC car/boat with Logitech Steering Wheel

    Code steps:
      1) All buttons from the steering wheel are read (via multiplexing) -> See code comments for wiring
         Reading is done via registers for high speed reads
      2) Steering Wheel, Gas pedal, Brake Pedal are read in 10bit analog inputs A0..A2 (0..1023)
         and converted to byte servo signal (0..180 [with 90° at the center])
      3) Data is transmitted via nRF24L01 module to RC vahicle
      
      4) Optional:
         Data receiced from RC vehicle over nRF24L01 communication:
         This is datat from the accelerometers from the RC vehicle and can be used to simulate vibration
         of the vehicle by the 2 vibration motors (1 small + 1 large) in the Steering Wheel
         Vibration mode can be toggled by pressing the vibration button for 1s

    License/Copyright:
      Code is written by Arashi Projects (2019) and is free to use
 */


/*************************************************************************
 *  nRF24L01
*************************************************************************/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

/*
  *RF = nRF24L01
  
  PIN | RF* | UNO | 
  ----+-----+-----+-------------------------------------
  GND |  1  | GND | 
  3V3 |  2  | 3V3 | (No 5V on the NRF24L10 !!!)
  CE  |  3  | 9   | 
  CSN |  4  | 10  | (also referred to as SCN or SS)
  SCK |  5  | 13  | 
  MOSI|  6  | 11  | 
  MISO|  7  | 12  | 
  IRQ |  8  | -   | 
*/
RF24 radio(9, 10);                                // nRF24L01 (CE, SS)
const byte address[][6] = {"node1", "node2" };    // Addresses
int nodeID = 1;                                   // set 1 for TX, set 2 for RX


/*************************************************************************
 *  Project
*************************************************************************/

// Digital I/O
bool b[17];   // 0..16 = 17 elements, but [0] will not be used

// Include button class
#include "class_button.h";
BUTTON btnVibration = BUTTON(); // Vibration button


// Analog I/O
struct ST_CONTROLLER
{
  int rawMin = 0;
  int rawMax = 1023;
  int rawVal = 511;
  int scaledVal = 90;    // rescaled values
};
ST_CONTROLLER Steer;
ST_CONTROLLER Brake;
ST_CONTROLLER Throttle;


// Include the structs for RF communication
#include "rf_structs.h";
ST_RfControl rfControlData;      // Control data to send to RC
ST_RfFeedback rfFeedbackData;  // Feedback data received from RC

// System vars
bool vibrationMode = 0;   // Vibration mode on or off
byte Throttle_Value;      // Calculated servo position for throttle/brake
unsigned long tmrTimeout;
unsigned long sendInterval = 10;  // send data every <sendInter> ms
unsigned long lastIntMillis = 0;

/*
  Read/Write I/O via registers is MUCH faster than digitalRead/diigtalWrite
  
  DDRx:  pinmode
  PINx:  DigitalRead
  PORTx: DigitalWrite

  B: Digital pins 8..13 (+ crystal)
  C: Analog pins 0..5 / 0..7 (mini)
  D: Digital pins 0..7 (0 and 1 are sreials pins !)
*/


/*
  Mux | Sense | Array | Button       Wire colors
  ----+-------+-------+-------------------------------------
  -   | -     | b[0]  | -
  D4  | D0    | b[1]  | Button 8     yellow (5) + black (1)
  D4  | D1    | b[2]  | Button 1     yellow (5) + brown (2)
  D4  | D2    | b[3]  | Button 2     yellow (5) + red (3)
  D4  | D3    | b[4]  | Shift UP     yellow (5) + orange (4)
  D5  | D0    | b[5]  | Button 12    green (6) + black (1)
  D5  | D1    | b[6]  | Button 10    green (6) + brown (2)
  D5  | D2    | b[7]  | Button 11    green (6) + red (3)
  D5  | D3    | b[8]  | Button 9     green (6) + orange (4)
  D6  | D0    | b[9]  | Button 4     blue (7) + black (1)
  D6  | D1    | b[10] | Button 3     blue (7) + brown (2)
  D6  | D2    | b[11] | Pad DOWN     blue (7) + red (3)
  D6  | D3    | b[12] | Shift DOWN   blue (7) + orange (4)
  D7  | D0    | b[13] | Button 7     purple (8) + black (1)
  D7  | D1    | b[14] | Pad UP       purple (8) + brown (2)
  D7  | D2    | b[15] | Pad LEFT     purple (8) + red (3)
  D7  | D3    | b[16] | Pad RIGHT    purple (8) + orange (4)

  Pin | Function              | Wire color
  ----+-----------------------+-------------------------------------
  D8  | Vibration button      | orange (14) + grey (9)
  A3  | Vibration LED         | black (11) + white (10)
  A4  | Ligth vibr. motor     | brown (12) + black (11) + orange (14)
  A5  | Heavy vibr. motor     | red (13) + black (11) + orange (14)
                              | (14 = GND | 11 = +5V)
  A0  | Steering wheel        |
  A1  | Brake pedal           | white
  A2  | Gas pedal (throttle)  | green

  PIN  | nRF24L01 | Arduino Uno
  -----+----------+--------------------------------------
  GND  | 1        | GND
  3V3  | 2        | 3V3   (No 5V on the NRF24L10 !!!)
  CE   | 3        | 9 
  CSN  | 4        | 10    (Also referred to as SCN / SS)
  SCK  | 5        | 13
  MOSI | 6        | 11
  MISO | 7        | 12
  IRQ  | 8        | -
*/



/*************************************************************************
 *  SETUP
*************************************************************************/
void setup()
{
  // Setup pin modes
  pinMode(0, INPUT_PULLUP); // Sense 1
  pinMode(1, INPUT_PULLUP); // Sense 2
  pinMode(2, INPUT_PULLUP); // Sense 3
  pinMode(3, INPUT_PULLUP); // Sense 4

  pinMode(4, OUTPUT); // MUX 1
  pinMode(5, OUTPUT); // MUX 2
  pinMode(6, OUTPUT); // MUX 3
  pinMode(7, OUTPUT); // MUX 4

  pinMode(8, INPUT_PULLUP); // Vibration button

  // Keep A0..A2 as analog inputs (steer, brake, throttle)
  pinMode(A3, OUTPUT);  // Vibration LED
  pinMode(A4, OUTPUT);  // Light vibration motor
  pinMode(A5, OUTPUT);  // Heavy vibration motor

  // LED control for vibration mode
  vibrationMode = 0;    // Turn vibration mode off
  digitalWrite(A3, 1);  // Turn vibration LED off (NEGATIVE logic!)

  // Wireless comms.
  radio.begin();                        // Start the radio
  radio.setChannel(123);                // Select channel
  radio.setAutoAck(false);              // Disable autoACK

  // Must be opposite adresses
  if (nodeID == 1) {
    radio.openWritingPipe(address[1]);    // Start address for sending data on TX side
    radio.openReadingPipe(1, address[0]); // Start address for receiving data on TX side
  }
  else {  // just accept any number other than 1
    radio.openWritingPipe(address[0]);    // Start address for sending data on RX side
    radio.openReadingPipe(1, address[1]); // Start address for receiving data on RX side
  }
  

  // radio.setDataRate(RF24_250KBPS);   // LOW speed data transfer, should improve range
  radio.setDataRate(RF24_1MBPS);        // Medium speed will also reduce range slightly
  // radio.setDataRate(RF24_2MBPS);     // High speed will further reduce range
 
  radio.setPALevel(RF24_PA_MIN);        // LOWEST power, but decrease range (-18dBm)
  // radio.setPALevel(RF24_PA_LOW);     // LOW power (-12dBm)
  // radio.setPALevel(RF24_PA_HIGH);    // HIGH power (-6dBM)
  // radio.setPALevel(RF24_PA_MAX);     // MAXIMUM power, for long range (0dBm)

  
  // Calibrated values for min. and max. of analog inputs (raw values)
  Steer.rawMin = 29;
  Steer.rawMax = 974;

  Brake.rawMin = 300;
  Brake.rawMax = 885;

  Throttle.rawMin = 320;
  Throttle.rawMax = 785;
 
  // INIT time holders
  tmrTimeout = millis();
  lastIntMillis = millis();
}




/*************************************************************************
 *  LOOP
*************************************************************************/
void loop()
{
  // Constantly poll for receiving data
  receiveData();  

  
  // Read 16 inputs
  readButtons();  // read all multiplexed buttons


  // Handle button for setting vibrating mode ON/OFF
  btnVibration.state = not(bool(PINB & B00000001));  // Button uses negative logic
  btnVibration.TON_delay = 750;                      // Button needs to be pressed for at least 750ms
  btnVibration.TOF_delay = 100;                      // Button needs to be released for at least 100ms
  btnVibration.refresh();
  if (btnVibration.RE)  // Rising Edge detected
  {
    vibrationMode = not(vibrationMode);     // Toggle vibration mode
    digitalWrite(A3, not(vibrationMode));   // Set/Reset LED (negative logic)
  }


  // If vibration mode is ON
  if (vibrationMode)
  {
    // For now, simulate vibration mode
    if(rfFeedbackData.Xaxis > 250 or rfFeedbackData.Zaxis > 250) { digitalWrite(A4, 1); }
    else { digitalWrite(A4, 0); }
    if(rfFeedbackData.Yaxis > 250 or rfFeedbackData.Zaxis > 250) { digitalWrite(A5, 1); }
    else { digitalWrite(A5, 0); }
  }
  else
  {
    // No vibration mode -> turn off vibration motors
    digitalWrite(A4, 0);
    digitalWrite(A5, 0);
  }


  // Read analog values
  Steer.rawVal = analogRead(A0);
  Brake.rawVal = analogRead(A1);
  Throttle.rawVal = analogRead(A2);
  // Rescale for servo signals
  Steer.scaledVal = constrain(map(Steer.rawVal, Steer.rawMin, Steer.rawMax, 0,180), 0, 180);  // Steer 0..180, with 90 at the center
  Brake.scaledVal = constrain(map(Brake.rawVal, Brake.rawMin, Brake.rawMax, 90, 0), 0, 90); // Brake 90..0
  Throttle.scaledVal = constrain(map(Throttle.rawVal, Throttle.rawMin, Throttle.rawMax, 90, 180), 90, 180);     // Throttle 90..180


  /*
   *  Throttle value=
   *  90..180 if throttle pedal is pushed and NOT brake (90)
   *  0..90 if brake pedal is pushed (ignore throttle)
   */
  if(Brake.scaledVal >= 90)
  { Throttle_Value = (byte)Throttle.scaledVal; }
  else
  { Throttle_Value = (byte)Brake.scaledVal; } 

  // Set datablock ready for sending
  rfControlData.Steer = Steer.scaledVal;
  rfControlData.Throttle = Throttle_Value;
  if(vibrationMode) { rfControlData.vibrationMode = 180 ;} else { rfControlData.vibrationMode = 0 ; }


  // Time to send ?
  if ( lastIntMillis + sendInterval <= millis())
  {
    // Send the data block (structure) to the receiver
    sendData();
    lastIntMillis = millis();
  }
}



/*************************************************************************
 *  Send and Receive data via nRF24L01 modules
*************************************************************************/
void receiveData()
{
  delay(5);                   // small delay is required for proper comms
  radio.startListening();     // RX mode
  if (radio.available())
  {
    tmrTimeout = millis();      // Reset the timeout timer
    while (radio.available()) {
      radio.read(&rfFeedbackData, sizeof(ST_RfFeedback));   // Read the whole data and store it into the 'data' structure
    }
  }
}



void sendData()
{
  // Sending data
  delay(5);                   // small delay is required for proper comms
  radio.stopListening();      // TX mode
  radio.write(&rfControlData, sizeof(ST_RfControl));   // Send the data block (structure) to the receiver
}


/*************************************************************************
 *  DE-MULTIPLEXER
*************************************************************************/
void readButtons()
{
  for (int i=0; i<=3; i++)
  {
    // USE NEGATIVE LOGIC !!!
    if(i == 0)
    { PORTD = B11101111; }
    else if(i == 1)
    { PORTD = B11011111; }
    else if(i == 2)
    { PORTD = B10111111; }
    else if(i == 3)
    { PORTD = B01111111; }
  
    delayMicroseconds(3);   // 1µs is too fast, 2µs seems OK for the UNO
    deMultiplex(i);         // de-Multiplex the data
  }
}

void deMultiplex(int i)
{
  // read register D
  byte inputs_reg_D = PIND;

  // De-multiplex
  b[(4*i)+1] = bool(inputs_reg_D >> 0 & B0001);  // Mask bit 0 (D0)
  b[(4*i)+2] = bool(inputs_reg_D >> 1 & B0001);  // Mask bit 1 (D1)
  b[(4*i)+3] = bool(inputs_reg_D >> 2 & B0001);  // Mask bit 2 (D2)
  b[(4*i)+4] = bool(inputs_reg_D >> 3 & B0001);  // Mask bit 3 (D3)
}

// EOF
/*************************************************************************
    Servo control
*************************************************************************/
#include <Servo.h>;
Servo servoSteer;
Servo servoThrottle;

/*
  Use external 5V power supply for servo +4.8V
  Use ground for servo 0V
  Use pin 3 for steer
  Use pin 4 for throttle
 */

/*************************************************************************
    nRF24L01
*************************************************************************/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

/*
  *RF = nRF24L01
  
  PIN | RF* | UNO | 
  ----+-----+-----+-------------------------------------
  GND |  1  | GND | 
  3V3 |  2  | 3V3 | (No 5V on the NRF24L10 !!!)
  CE  |  3  | 9   | 
  CSN |  4  | 10  | (also referred to as SCN or SS)
  SCK |  5  | 13  | 
  MOSI|  6  | 11  | 
  MISO|  7  | 12  | 
  IRQ |  8  | -   | 
*/
RF24 radio(9, 10);                                // nRF24L01 (CE, SS)
const byte address[][6] = {"node1", "node2" };    // Addresses
int nodeID = 2;                                   // set 1 for TX, set 2 for RX



/*************************************************************************
    Project
*************************************************************************/

// Include the structs for RF communication
#include "rf_structs.h";
ST_RfControl rfControlData;      // Control data to send to RC
ST_RfFeedback rfFeedbackData;  // Feedback data received from RC

// System vars
unsigned long tmrTimeout;
unsigned long sendInterval = 50;  // send data every <sendInter> ms ; for feedback, 50ms suffices
unsigned long lastIntMillis = 0;

// If the default servo.write(angle°) sets a bad timing, use servo.writeMicrosoconds in stead!
bool bUseServoAngle = false;   // TRUE = servo.write(°) ; FASLSE = use servo.writeMicroseconds(µs)
int micosSteer = 1500;
int microsThrottle = 1500;



/*************************************************************************
    SETUP
*************************************************************************/
void setup()
{
  // Connect servo's to pins 3 and 4
  servoSteer.attach(3);
  servoThrottle.attach(4);

  if(bUseServoAngle)
  {
    servoSteer.write(90);      // INIT in center
    servoThrottle.write(90);   // INIT in center
  }
  else
  {
    servoSteer.writeMicroseconds(1500);     // INIT to center
    servoThrottle.writeMicroseconds(1500);  // INIT to center
  }
  
  
  // Wireless comms.
  radio.begin();                        // Start the radio
  radio.setChannel(123);                // Select channel
  radio.setAutoAck(false);              // Disable autoACK

  // Must be opposite adresses
  if (nodeID == 1) {
    radio.openWritingPipe(address[1]);    // Start address for sending data on TX side
    radio.openReadingPipe(1, address[0]); // Start address for receiving data on TX side
  }
  else {  // just accept any number other than 1
    radio.openWritingPipe(address[0]);    // Start address for sending data on RX side
    radio.openReadingPipe(1, address[1]); // Start address for receiving data on RX side
  }

  // radio.setDataRate(RF24_250KBPS);   // LOW speed data transfer, should improve range
  radio.setDataRate(RF24_1MBPS);        // Medium speed will also reduce range slightly
  // radio.setDataRate(RF24_2MBPS);     // High speed will further reduce range
 
  radio.setPALevel(RF24_PA_MIN);        // LOWEST power, but decrease range (-18dBm)
  // radio.setPALevel(RF24_PA_LOW);     // LOW power (-12dBm)
  // radio.setPALevel(RF24_PA_HIGH);    // HIGH power (-6dBM)
  // radio.setPALevel(RF24_PA_MAX);     // MAXIMUM power, for long range (0dBm)

  // Serial.begin(115200);  // DEBUG

  // INIT time holders
  tmrTimeout = millis();
  lastIntMillis = millis();
}




/*************************************************************************
    LOOP
*************************************************************************/
void loop()
{
  // Constantly poll for receiving data
  receiveData();  

  // Timeout reached -> go to safe mode
  if (tmrTimeout + 1000 < millis())
  {
    // Serial.println("Timeout, no data received - going into safe mode");
    rfControlData.Steer = 90;             // Steer center
    rfControlData.Throttle = 90;          // No throtthle, no brake
    rfControlData.vibrationMode = 0;      // No vibration mode
  }

     
  // Update servos (data is already in right format)
  if(bUseServoAngle)
  {
    servoSteer.write(rfControlData.Steer);
    servoThrottle.write(rfControlData.Throttle);   // controller will handle brake/reverse mode
  }
  else
  {
    // Rescale angle to custom timing
    micosSteer = constrain(map(rfControlData.Steer, 0, 180, 1090, 1900), 1090, 1900);        // 0..180, with 90 at the center = 1090 .. 1500 .. 1900
    microsThrottle = constrain(map(rfControlData.Throttle, 0, 180, 1090, 1900), 1090, 1900);
    servoSteer.writeMicroseconds(micosSteer);
    servoThrottle.writeMicroseconds(microsThrottle);
  }


  // Only measure and send when vibration mode is on
  if (rfControlData.vibrationMode)
  {
    // Get feedback data
    getFeedback();

    // Time to send ?
    if ( lastIntMillis + sendInterval <= millis())
    {
      // Send the data block (structure) to the receiver
      sendData();
      lastIntMillis = millis();
    }
  }
}



/*************************************************************************
 *  Gather feedback data
*************************************************************************/
void getFeedback()
{
  // Simulate feedback for vibration mode with incoming values from throttle, brake and steer
  if (rfControlData.Throttle > 160)
  { rfFeedbackData.Xaxis = 255; Serial.print("X"); }
  else
  { rfFeedbackData.Xaxis = 0; }

  if (rfControlData.Steer < 20 or rfControlData.Steer > 160)
  { rfFeedbackData.Yaxis = 255; Serial.print("Y"); }
  else
  { rfFeedbackData.Yaxis = 0; }
  
  if (rfControlData.Throttle < 20)  // braking
  { rfFeedbackData.Zaxis = 255; Serial.print("Z"); }
  else
  { rfFeedbackData.Zaxis = 0; }
}


/*************************************************************************
 *  Send and Receive data via nRF24L01 modules
*************************************************************************/
void receiveData()
{
  delay(5);                   // small delay is required for proper comms
  radio.startListening();     // RX mode
  if (radio.available())
  {
    tmrTimeout = millis();      // Reset the timeout timer
    while (radio.available()) {
      radio.read(&rfControlData, sizeof(ST_RfControl));   // Read the whole data and store it into the 'data' structure
    }
  }
}


void sendData()
{
  // Sending data
  delay(5);                   // small delay is required for proper comms
  radio.stopListening();      // TX mode
  radio.write(&rfFeedbackData, sizeof(ST_RfFeedback));   // Send the data block (structure) to the receiver
}

// EOF

References
Arduino Boards: Arduino Uno R3 – Arduino Nano V3 – Arduino Micro
Arduino Software: Arduino IDE
Arduino Playground: Library NewPing
Arduino forum: Arduino Micro Interrupts 6Simple nRF24L01 demo 
Arduino Reference: Servo libraryPort RegistersattachInterrupt()interrupt()noInterrrupt()pulseIn()pulseInLong()
Logitech
GitHub: High Speed NRF24L01 libraryRF24v1
How To Mechatronics: NRF24L01 Tutorial
Instructables Circuits: Bidirectional NRF24L01Muliplexing
Electronics Hobbyists: NRF24L01 Interfacing with Arduino
PCB Heaven: How a key matrix works