3D Printed RC Transmitter

by BasementMaker in Circuits > Remote Control

4206 Views, 114 Favorites, 0 Comments

3D Printed RC Transmitter

Assembeled_workbench_background.jpg

Like most hobby 3d printing and electronics nerds, I have a very large list of projects I want to pursue that seems to be growing daily. So many, in fact, that I won't be able to accomplish them in one lifetime. When digging through this long list, I have noticed that many of these projects have several things in common: they will all require some form of teleoperation. Fortunately, that places a nice constraint on my priorities. I can't go about building anything before I have a tool to control it. Thus arises the foundations for this instructable.

I have designed an RC transmitter or two in the past, but as I learn more through experience, I keep returning to this project. With a continuous revision process in mind, this controller version incorporates a few helpful features. It is relatively easy to assemble and disassemble (while being fun to do so), so replacing parts is simple. The different components are standalone modules with QWIIC (I2C) daisy chaining, so they can be added, removed, or upgraded with very minimal code modification. The internal layout is several pieces, which makes it easy to redesign just a single bracket to work with a new component and cuts down on 3D print time for new parts. All of these features make this controller an excellent tool for developing your next project.

About halfway through the design phase of this project, I was approached by Playing With Fusion, who was interested in using my project to showcase some features of their newest microcontroller, the R3aktor SAMD21 Cortex M0 Dev Board. In turn, I was able to use their resources to learn PCB design, further simplifying the integration of different controller components. Many of the components for this project can be purchased from Playing With Fusion's website, playingwithfusion.com. As a disclosure, I am now an employee of Playing With Fusion.

This project will take you through a detailed description of how you can 3D print, build, and program your own radio controller to interface with any past, current, or future project on your list. For an alternative format, these instructions can be viewed here.

Supplies

fastner_boxes_workbench.jpg

Hardware

  1. (x4) m2x8 pan head screws
  2. (x4) m2 nuts
  3. (x14) m3x8 pan head screws
  4. (x10) m3x12 pan head screws
  5. (x24) m3 nuts
  6. (x8) m3x20 hex standoffs
  7. (x14) m3x16 countersunk screws
  8. (x2) m3x20 countersunk screws

Electronics

  1. (x1) PWF R3aktor M0 Logger (Link)
  2. (x2) PWF IFB-40001 I2C Encoders (Link)
  3. (x2) PWF IFB-40002 I2C Joysticks (Link)
  4. (x1) PWF IFB-40003 I2C Buttons (Link)
  5. (x2) PWF IFB-40004 I2C Switches (Link)
  6. (x1) PWF IFB-40005 Power Switch (Link)
  7. (x1) NRF24L01+PA+LNA RF Module
  8. (x1) 10uF Ceramic capacitor
  9. (x1) 1s 3.7v 2000mAh Battery
  10. (x7) QWIIC Cable (Link)
  11. (x1) male-to-male JST cable
  12. (x7) male-to-male pin header jumper cables
  13. (x1) row of 12 angled pin headers
  14. (x1) row of 16 angled pin headers

3D Print Parts

Printables Page.jpg
  • (x1) Top
  • (x1) Outer Wall
  • (x1) Bottom
  • (x4) Button
  • (x1) Middle Button
  • (x2) Encoder Knob
  • (x1) Radio Enclosure
  • (x1) Button Bracket
  • (x1) Battery Enclosure
  • (x1) Joystick Bracket - right
  • (x1) Joystick Bracket - left
  • (x1) Encoder Bracket
  • (x1) Power Switch Bracket
  • (x1) Toggle Switch Bracket - right
  • (x1) Toggle Switch Bracket - left
  • (x4) Small Spacer
  • (x1) Switch Spacer - right
  • (x1) Switch Spacer - left
  • (x1) Lower Power Switch Spacer
  • (x1) Upper Power Switch Spacer
  • (x1) Lower Button Bracket Spacer
  • (x1) Upper Button Bracket Spacer - right
  • (x1) Upper Button Bracket Spacer - left
  • (x16) Standoff


These files can be downloaded from Printables here.

Solder PCB Components

rc_img_01.jpg
rc_img_02.jpg
rc_img_03.jpg
rc_img_04.jpg
rc_img_05.jpg
rc_img_06.jpg

Solder the 10uF ceramic capacitor to the positive and negative terminals of the radio module. Keep the form factor as small as possible so the radio module will fit in the 3D printed enclosure.

Solder the angled pin headers to the R3aktor board.

Gather all the Playing with Fusion PCBs and their components. Solder each component onto each board. Take special care with the power switch PCB and the toggle switch PCBs so that the components are as close to square to the PCB as possible.

I2C Address Selection

rc_img_adr_sel.jpg
cuttable_jumper_sel.jpg

The I2C bus requires each device to have a unique address. The firmware on each board handles this mostly, but a small change is needed to allow identical devices to coexist on the same bus.

Each PCB has a small "cuttable jumper" labeled ADR. Each Identical device needs to have a different configuration of jumpers selected. For example, joystick A has both jumpers 0 and 1 intact, and joystick B has jumper 0 cut and jumper 1 intact. See the above image.

This can be achieved by scraping one or both copper pads with a sharp hobby knife until the parallel pads become electrically disconnected. Using a multimeter to check these selectors for continuity may be helpful if you encounter errors during the programming phase of this project.

Assembly

rc_img_07.jpg
rc_img_08.jpg
rc_img_09.jpg

Gather 4 M2x8 pan head screws, 4 M2 nuts, the R3aktor, and the bottom. Place the R3aktor onto the raised cylinders in the center of the bottom. You may need to slide the USB-C port into the USB-C slot first, and then fit the R3aktor into place. Use the M2 hardware to secure the R3ktor to the bottom.

Assembly

rc_img_10.jpg
rc_img_11.jpg

Assemble the button PCB to the button bracket using 2 m3x8 screws and 2 m3 nuts. Make sure the button PCB goes onto the right side of the bracket.

Assembly

rc_img_12.jpg
rc_img_13.jpg

Assemble the joystick PCBs to the joystick bracket using 8 m3x12 screws and 8 m3 nuts. The QWIIC connectors should point away from the wall of the joystick bracket.

Assembly

rc_img_14.jpg
rc_img_15.jpg

Assemble the encoder PCBs to the encoder bracket using 8 m3x8 screws and 8 m3 nuts.


Assembly

rc_img_16.jpg
rc_img_17.jpg

Assemble the switch PCBs to the switch brackets using 4 m3x8 screws and 4 m3 nuts.


Assembly

rc_img_18.jpg
rc_img_19.jpg

Assemble the power switch PCBs to the power switch bracket using 2 m3x12 screws and 2 m3 nuts.


Assembly

rc_img_20.jpg
rc_img_21.jpg
rc_img_22.jpg

Using 6 m3x16 countersunk screws, 2 m3x20 countersunk screws and 8 m3x20 hex standoffs, fasten the 3D printed standoffs to the 3D printed top of the controller. Use the longer screws for the upper-middle holes. Ensure the standoffs are all aligned properly in the square slots on the underside of the 3D printed top.


Assembly

rc_img_23.jpg
rc_img_24.jpg

Insert the encoder knobs into the encoder knob holes.


Assembly

rc_img_25.jpg
rc_img_26.jpg
rc_img_27.jpg

Place the radio module in its place. Fit the radio module enclosure around it.


Assembly

rc_img_28.jpg
rc_img_29.jpg

Slide two small spacers onto the standoffs to match the height of the radio module enclosure.


Assembly

rc_img_30.jpg
rc_img_31.jpg

Place the assembled joystick modules on the standoffs.


Assembly

rc_img_32.jpg
rc_img_33.jpg

Add the remaining two small spacers on the outside standoffs on top of the joystick mount. Place the encoder mount assembly and place it on the center standoffs. The encoder knobs will need to slide into the 3D-printed encoder knobs placed earlier. As the encoder mount assembly is slid into place, rotate the 3D-printed encoder knobs until the metal encoder knobs find their place.


Assembly

rc_img_34.jpg

Use 3 QWIIC cables to connect each one of the interface boards. The left joystick is connected to the left encoder is connected to the right encoder is connected to the right joystick.


Assembly

rc_img_35.jpg
rc_img_36.jpg

Slide four more 3D-printed standoffs onto the hex standoffs. After this, slide the switch mount assemblies onto the standoffs. The QWIIC connectors on the PCBs should be pointed upwards (away from the 3D-printed top).


Assembly

rc_img_37.jpg

Add 3 more QWIIC cables to connect the left switch PCB to the left joystick PCB and the right joystick PCB to the right switch PCB. The third cable should be plugged into the right switch PCB. The other end will be plugged in later.


Assembly

rc_img_38.jpg
rc_img_39.jpg

Slide the two mirrored switch spacers onto the 3D-printed standoffs. These are the last parts that need to go on this end of the controller, so only a small sliver of the standoff should be raised above the top surface of the spacers.


Assembly

rc_img_40.jpg
rc_img_41.jpg

On the opposite side of the controller, place 3 spacers into their proper places on the 3D printed standoffs. Ensure the contour of the spacers matches the contour of the edge shape of the controller’s top, like in the above image.


Assembly

rc_img_42.jpg
rc_img_43.jpg
rc_img_44.jpg

Place the 3D-printed buttons into the button holes on the top. Add a QWIIC cable into the QWIIC connector on the button PCB, leaving the other end unconnected for now.


Assembly

rc_img_45.jpg

Place the last four standoffs onto the remaining hex standoffs. Also, plug the unconnected QWIIC cable from the switch PCB into the QWIIC connector on the button PCB.


Assembly

rc_img_46.jpg
rc_img_47.jpg

Slide the switch mount assembly into place. It may require some maneuvering to get the switch to fit into the switch hole on the top. Once it is in place, add the two 3D-printed spacers.


Assembly

rc_img_48.jpg
rc_img_49.jpg

Slide the battery enclosure into the last open place on the standoffs. Plug one end of the JST cable into the terminal on the right side of the switch.


Assembly

rc_img_50.jpg

Set the rechargeable battery into the battery enclosure. Feed the battery cable through an opening in the enclosure and plug it into the left terminal of the switch.

Assembly

rc_img_51.jpg
rc_img_52.jpg

Utilize the flexibility of the outer wall to place it around the controller. It helps if the toggle switch levers are placed in their respective holes first, and then the other side is slid into place.


Assembly

rc_img_53.jpg
rc_img_radio_schematic.jpg

Make the remaining electrical connections. Plug the other end of the JST connector into the R3aktor battery JST terminal. Connect each pin of the radio module to its respective pin on the R3aktor (see the above connection diagram).

  • R3aktor -> NRF24L01
  • GND -> GND
  • 3V3 -> VCC
  • D09 -> CE
  • D10 -> CSN
  • SCK -> SCK
  • MOSI -> MOSI
  • MISO -> MISO


Assembly

rc_img_54.jpg

Plug the loose end of the last QWIIC cable into the R3aktor board.


Assembly

rc_img_55.jpg

Fit the 3D-printed bottom into place. Make sure all wires are stored safely inside the controller and are not pinched. Use 8 M3x16 countersunk screws to fasten the bottom into place.


Assembly Complete

rc_img_56.jpg

Congratulations! The controller is now ready for programming.

Controller Programming

Controller Programming Step.jpg

Connect the controller to a computer using a USB-C cable. Download and open the RC Transmitter code in the Arduino IDE. If you have not already, follow the instructions for setting up the Arduino IDE to work with the R3aktor board linked here. Once the code is opened, click on the RC_Transmitter.ino tab. Upload this code to the controller.


/***************************************************************************
* File Name: RC_Transmitter.ino
* Processor/Platform: PwFusion R3aktor M0 (tested)
* Development Environment: Arduino 2.1.1
*
* Designed to collect information from Playing with Fusion I2C interface boards
* and write them to a NRF24L01 radio.
*
* Copyright � 2023 Playing With Fusion, Inc.
* SOFTWARE LICENSE AGREEMENT: This code is released under the MIT License.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
* **************************************************************************
* REVISION HISTORY:
* Author     Date     Comments
* N. Johnson    2023Sep30   Original version
*
* Playing With Fusion, Inc. invests time and resources developing open-source
* code. Please support Playing With Fusion and continued open-source
* development by buying products from Playing With Fusion!
***************************************************************************/


// Include the radio module libraries
#include <RF24.h>
#include <RF24_config.h>
#include <nRF24L01.h>
#include <printf.h>


// Include the PwFusion I2C Interface board libraries
#include <PwFusion_I2C_Toggle_Arduino_Library.h>
#include <PwFusion_I2C_Joystick_Arduino_Library.h>
#include <PwFusion_I2C_Encoder_Arduino_Library.h>
#include <PwFusion_I2C_Buttons_Arduino_Library.h>


// Define the radio pins
#define CE_PIN    9
#define CSN_PIN   10


// Address definitions
uint8_t ADR_ENC_A = 0x01;
uint8_t ADR_ENC_B = 0x02;


uint8_t ADR_JOY_A = 0x03;
uint8_t ADR_JOY_B = 0x04;


uint8_t ADR_BTN_B = 0x05;
uint8_t ADR_BTN_A = 0x06;


uint8_t ADR_SW_A = 0x07;
uint8_t ADR_SW_B = 0x08;


// Create new instances of the objects for each I2C board
Joystick joyA;
Joystick joyB;
Encoder encA;
Encoder encB;
Buttons btnA;
Buttons btnB;
Switch swA;
Switch swB;


// Define the address for the radio
const uint64_t pipe = 0x01;


// Define an array for storing and sending data
uint8_t data[13];


// Initialize the radio object
RF24 radio(CE_PIN, CSN_PIN);


void setup() {
  Serial.begin(9600);


  // Initialize each I2C interface board object
  joyA.begin(ADR_JOY_A);
  joyB.begin(ADR_JOY_B);
  encA.begin(ADR_ENC_A);
  encB.begin(ADR_ENC_B);
  btnA.begin(ADR_BTN_A);
  btnB.begin(ADR_BTN_B);
  swA.begin(ADR_SW_A);
  swB.begin(ADR_SW_B);


  // Start the radio
  radio.begin();
  radio.openWritingPipe(pipe);
}


void loop() {


  // Store each piece of data from the I2C boards in the array
  data[0] = joyA.getVRX();
  data[1] = joyA.getVRY();
  data[2] = joyA.getSW();


  data[3] = joyB.getVRX();
  data[4] = joyB.getVRY();
  data[5] = joyB.getSW();


  data[6] = encA.getBtnState();
  data[7] = encA.getCount();


  data[8] = encB.getBtnState();
  data[9] = encB.getCount();


  data[10] = btnA.getBtn();  
  
  data[11] = swA.getState();


  data[12] = swB.getState();


  //Print out the values of the interface boards to the Serial Monitor
  Serial.print("JOY_A x: ");
  Serial.print(data[0]);
  Serial.print(" | ");


  Serial.print("JOY_A y: ");
  Serial.print(data[1]);
  Serial.print(" | ");


  Serial.print("JOY_A sw: ");
  Serial.print(data[2]);
  Serial.print(" | ");


  Serial.print("JOY_B x: ");
  Serial.print(data[3]);
  Serial.print(" | ");


  Serial.print("JOY_B y: ");
  Serial.print(data[4]);
  Serial.print(" | ");


  Serial.print("JOY_B sw: ");
  Serial.print(data[5]);
  Serial.print(" | ");


  Serial.print("ENC_A sw: ");
  Serial.print(data[6]);
  Serial.print(" | ");


  Serial.print("ENC_A count: ");
  Serial.print(data[7]);
  Serial.print(" | ");


  Serial.print("ENC_B sw: ");
  Serial.print(data[8]);
  Serial.print(" | ");


  Serial.print("ENC_B count: ");
  Serial.print(data[9]);
  Serial.print(" | ");


  Serial.print("BTN_A btn: ");
  Serial.print(data[10]);
  Serial.print(" | ");


  Serial.print("SW_A state: ");
  Serial.print(data[11]);
  Serial.print(" | ");


  Serial.print("SW_B state: ");
  Serial.print(data[12]);
  Serial.print(" | ");


  Serial.println();


  // Write the array to the radio address.
  radio.write(&data, sizeof(data));


}

Operation Instructions

Assembeled_workbench_background_power_switch.jpg

To turn the controller on, flip the power switch. A light should appear to indicate the controller is receiving power. When charging the controller, the power switch must be in the on position.


Receiver Programming

Reciever Images.jpg

The controller must talk to a receiver to interface with any project. All the receiver needs to consist of is a controller board (like a r3aktor or an Arduino) and an nrf24l01 radio module. This controller board will receive a packet of data from the controller and store it in an array. Each array value represents the output values from a component within the controller. If the given code (RC_Transmitter.ino) was used, the array is structured like the following list:

data[0] = joystick A x-axis
data[1] = joystick A y-axis
data[2] = joystick A button
data[3] = joystick B x-axis
data[4] = joystick B y-axis
data[5] = joystick B button
data[6] = encoder A button
data[7] = encoder A count
data[8] = encoder B button
data[9] = encoder B count
data[10] = buttons state
data[11] = switch A state
data[12] = switch B state

See the next step for an example of this receiver code.

Demo

3D Printed RC Transmitter Demo

There is no better way to demonstrate the functionality of this project than by seeing it working! The embedded video shows me controlling my current project. This project is still in the works (as evidenced by the wire ratsnets and the squealing gearbox), but still serves to show how simple it is to connect and how responsive the controller is.


The following code receives inputs from the transmitter and uses them to control the speeds and directions of drive motors.


// Include the required libraries for communication with the nrf module
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>


// CE and CSN pin definitions
#define CE_PIN    7
#define CSN_PIN   8


// Motor pin definitions
#define Ap1 3
#define Ap2 5
#define Bp1 6
#define Bp2 9
#define Cp1 2
#define Cp2 4


// Dead zone for input values
int D_ZONE = 2;


// Joystick position definitions
float joyAx = 0;
float joyAy = 0;
float joyBx = 0;
float joyBy = 0;

// Define the address for RF communication
const uint64_t pipe = 0x01;


// Define the package size to receive.
uint8_t data[13];  


// Define the new radio object
RF24 radio(CE_PIN, CSN_PIN);


void setup() {


  pinMode(Ap1, OUTPUT);
  pinMode(Ap2, OUTPUT);
  pinMode(Bp1, OUTPUT);
  pinMode(Bp2, OUTPUT);
  pinMode(Cp1, OUTPUT);
  pinMode(Cp2, OUTPUT);


  // Make sure all motors are off
  digitalWrite(Ap1, LOW);
  digitalWrite(Ap2, LOW);
  digitalWrite(Bp1, LOW);
  digitalWrite(Bp2, LOW);
  digitalWrite(Cp1, LOW);
  digitalWrite(Cp2, LOW);


  Serial.begin(9600);
  delay(1000);


  // Initialize the radio for recieving
  Serial.println("Nrf24L01 Receiver Starting");
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
}

void loop() {


  // This demonstraits how to recieve the inputs from the RC Transmitter.
  if ( radio.available() ) {
      // Read the data from the radio and store it in the data array
      radio.read(data, sizeof(data));


      // Uncomment to see controller values in the Serial Monitor
      // Print out each value from data to the Serial Monitor
      // for (int i = 0; i < 13; i++) {
      //   Serial.print(data[i]);
      //   Serial.print("\t");
      // }


      // Serial.println();


      // Retrieve, map, and assign the joystick position values from the data array
      joyAx = map(data[0], 0, 255, -255, 255);
      joyAy = map(data[1], 0, 255, -255, 255);
      joyBx = map(data[3], 0, 255, -255, 255);
      joyBy = map(data[4], 0, 255, -255, 255);


      // Functions to set motor speeds and directions
      set_motor_pwm(joyAy, Ap1, Ap2);
      set_motor_pwm(joyBy, Bp1, Bp2);
      set_motor(joyAx, Cp1, Cp2);
     


  } else {
    Serial.println("Transmitter unavailable");
  }
}


void set_motor_pwm(int pwm, int pin1, int pin2) {


  if (pwm < -2) {
    analogWrite(pin1, -1 * pwm);
    digitalWrite(pin2, LOW);


    Serial.println("Forward");


  } else if (pwm > 2) {
    digitalWrite(pin1, LOW);
    analogWrite(pin2, pwm);


    Serial.println("Put it in reverse, Terry!");


  } else {
    digitalWrite(pin1, LOW);
    digitalWrite(pin2, LOW);
  }


}


void set_motor(int in, int pin1, int pin2) {


  if (in < -10) {
    digitalWrite(pin2, LOW);
    digitalWrite(pin1, HIGH);
  } else if (in > 10) {
    digitalWrite(pin2, HIGH);
    digitalWrite(pin1, LOW);
  } else {
    digitalWrite(pin2, LOW);
    digitalWrite(pin1, LOW);
  }


}

Final Word

Thank you for taking the time to read through this instructable. I hope you were able to learn something or get a few new ideas for your next project.


Please leave a comment with any questions or suggestions! I will do my best to respond.