PlayingWithFusion.com Home

0 Items
View Cart

Document: 1210
Posted: June 9, 2024
Modified: June 9, 2024

Home > Tech Article Categories > FRC and Robotics > 3D Printed RC Transmitter

3D Printed RC Transmitter

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 many types of hobbyist electronics projects.

Preperation

Gather the materials:

  • Hardware
    • (x4) m2x8 pan head screws
    • (x4) m2 nuts
    • (x14) m3x8 pan head screws
    • (x10) m3x12 pan head screws
    • (x24) m3 nuts
    • (x8) m3x20 hex standoffs
    • (x14) m3x16 countersunk screws
    • (x2) m3x20 countersunk screws
  • Electronics
    • (x1) PWF R3aktor M0 Logger
    • (x2) PWF IFB-40001 I2C Encoders
    • (x2) PWF IFB-40002 I2C Joysticks
    • (x1) PWF IFB-40003 I2C Buttons
    • (x2) PWF IFB-40004 I2C Switches
    • (x1) PWF IFB-40005 Power Switch
    • (x1) NRF24L01+PA+LNA RF Module
    • (x1) 10uF Ceramic capacitor
    • (x1) 1s 3.7v 2000mAh Battery
    • (x7) QWIIC Cable
    • (x1) male to male JST cable
    • (x7) male to male pin header jumper cables
    • (x1) row of 12 angled pin headers
    • (x1) row of 16 angled pin headers

Print the parts (download link for all parts):

  • (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

Solder the PCB componets:

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.

Assembly

Step 1:

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.


Step 2:

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.


Step 3:

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.


Step 4:

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


Step 5:

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


Step 6:

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


Step 7:

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.


Step 8:

Insert the encoder knobs into the encoder knob holes.


Step 9:

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


Step 10:

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


Step 11:

Place the assembeled joystick modules on the standoffs.


Step 12:

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.


Step 13:

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.


Step 14:

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).


Step 15:

Add 3 more QUIIC 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.


Step 16:

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.


Step 17:

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 controller’s top, like in the above image.


Step 18:

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.


Step 19:

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


Step 20:

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.


Step 21:

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.


Step 22:

Set the rechargable 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.


Step 23:

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.


Step 24:

Make the remaining electrical connections. Plug the other end of 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


Step 25:

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


Step 26:

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.


Done

Congradulations! The controller is now ready for programming.

Controller Programming

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. Once the code is opened, click on the RC_Transmitter.ino tab. Upload this code to the controller.

  1.  
  2. /***************************************************************************
  3. * File Name: RC_Transmitter.ino
  4. * Processor/Platform: PwFusion R3aktor M0 (tested)
  5. * Development Environment: Arduino 2.1.1
  6. *
  7. * Designed to collect information from Playing with Fusion I2C interface boards
  8. * and write them to a NRF24L01 radio.
  9. *
  10. * Copyright ? 2023 Playing With Fusion, Inc.
  11. * SOFTWARE LICENSE AGREEMENT: This code is released under the MIT License.
  12. *
  13. * Permission is hereby granted, free of charge, to any person obtaining a
  14. * copy of this software and associated documentation files (the "Software"),
  15. * to deal in the Software without restriction, including without limitation
  16. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  17. * and/or sell copies of the Software, and to permit persons to whom the
  18. * Software is furnished to do so, subject to the following conditions:
  19. *
  20. * The above copyright notice and this permission notice shall be included in
  21. * all copies or substantial portions of the Software.
  22. *
  23. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  28. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  29. * DEALINGS IN THE SOFTWARE.
  30. * **************************************************************************
  31. * REVISION HISTORY:
  32. * Author        Date        Comments
  33. * N. Johnson    2023Sep30   Original version
  34. *
  35. * Playing With Fusion, Inc. invests time and resources developing open-source
  36. * code. Please support Playing With Fusion and continued open-source
  37. * development by buying products from Playing With Fusion!
  38. ***************************************************************************/
  39.  
  40.  
  41. // Include the radio module libraries
  42. #include <RF24.h>
  43. #include <RF24_config.h>
  44. #include <nRF24L01.h>
  45. #include <printf.h>
  46.  
  47.  
  48. // Include the PwFusion I2C Interface board libraries
  49. #include <PwFusion_I2C_Toggle_Arduino_Library.h>
  50. #include <PwFusion_I2C_Joystick_Arduino_Library.h>
  51. #include <PwFusion_I2C_Encoder_Arduino_Library.h>
  52. #include <PwFusion_I2C_Buttons_Arduino_Library.h>
  53.  
  54.  
  55. // Define the radio pins
  56. #define CE_PIN    9
  57. #define CSN_PIN   10
  58.  
  59.  
  60. // Address definitions
  61. uint8_t ADR_ENC_A = 0x01;
  62. uint8_t ADR_ENC_B = 0x02;
  63.  
  64.  
  65. uint8_t ADR_JOY_A = 0x03;
  66. uint8_t ADR_JOY_B = 0x04;
  67.  
  68.  
  69. uint8_t ADR_BTN_B = 0x05;
  70. uint8_t ADR_BTN_A = 0x06;
  71.  
  72.  
  73. uint8_t ADR_SW_A = 0x07;
  74. uint8_t ADR_SW_B = 0x08;
  75.  
  76.  
  77. // Create new instances of the objects for each I2C board
  78. Joystick joyA;
  79. Joystick joyB;
  80. Encoder encA;
  81. Encoder encB;
  82. Buttons btnA;
  83. Buttons btnB;
  84. Switch swA;
  85. Switch swB;
  86.  
  87.  
  88. // Define the address for the radio
  89. const uint64_t pipe = 0x01;
  90.  
  91.  
  92. // Define an array for storing and sending data
  93. uint8_t data[13];
  94.  
  95.  
  96. // Initialize the radio object
  97. RF24 radio(CE_PIN, CSN_PIN);
  98.  
  99.  
  100. void setup() {
  101.   Serial.begin(9600);
  102.  
  103.  
  104.   // Initialize each I2C interface board object
  105.   joyA.begin(ADR_JOY_A);
  106.   joyB.begin(ADR_JOY_B);
  107.   encA.begin(ADR_ENC_A);
  108.   encB.begin(ADR_ENC_B);
  109.   btnA.begin(ADR_BTN_A);
  110.   btnB.begin(ADR_BTN_B);
  111.   swA.begin(ADR_SW_A);
  112.   swB.begin(ADR_SW_B);
  113.  
  114.  
  115.   // Start the radio
  116.   radio.begin();
  117.   radio.openWritingPipe(pipe);
  118. }
  119.  
  120.  
  121. void loop() {
  122.  
  123.  
  124.   // Store each piece of data from the I2C boards in the array
  125.   data[0] = joyA.getVRX();
  126.   data[1] = joyA.getVRY();
  127.   data[2] = joyA.getSW();
  128.  
  129.  
  130.   data[3] = joyB.getVRX();
  131.   data[4] = joyB.getVRY();
  132.   data[5] = joyB.getSW();
  133.  
  134.  
  135.   data[6] = encA.getBtnState();
  136.   data[7] = encA.getCount();
  137.  
  138.  
  139.   data[8] = encB.getBtnState();
  140.   data[9] = encB.getCount();
  141.  
  142.  
  143.   data[10] = btnA.getBtn();  
  144.  
  145.   data[11] = swA.getState();
  146.  
  147.  
  148.   data[12] = swB.getState();
  149.  
  150.  
  151.   //Print out the values of the interface boards to the Serial Monitor
  152.   Serial.print("JOY_A x: ");
  153.   Serial.print(data[0]);
  154.   Serial.print(" | ");
  155.  
  156.  
  157.   Serial.print("JOY_A y: ");
  158.   Serial.print(data[1]);
  159.   Serial.print(" | ");
  160.  
  161.  
  162.   Serial.print("JOY_A sw: ");
  163.   Serial.print(data[2]);
  164.   Serial.print(" | ");
  165.  
  166.  
  167.   Serial.print("JOY_B x: ");
  168.   Serial.print(data[3]);
  169.   Serial.print(" | ");
  170.  
  171.  
  172.   Serial.print("JOY_B y: ");
  173.   Serial.print(data[4]);
  174.   Serial.print(" | ");
  175.  
  176.  
  177.   Serial.print("JOY_B sw: ");
  178.   Serial.print(data[5]);
  179.   Serial.print(" | ");
  180.  
  181.  
  182.   Serial.print("ENC_A sw: ");
  183.   Serial.print(data[6]);
  184.   Serial.print(" | ");
  185.  
  186.  
  187.   Serial.print("ENC_A count: ");
  188.   Serial.print(data[7]);
  189.   Serial.print(" | ");
  190.  
  191.  
  192.   Serial.print("ENC_B sw: ");
  193.   Serial.print(data[8]);
  194.   Serial.print(" | ");
  195.  
  196.  
  197.   Serial.print("ENC_B count: ");
  198.   Serial.print(data[9]);
  199.   Serial.print(" | ");
  200.  
  201.  
  202.   Serial.print("BTN_A btn: ");
  203.   Serial.print(data[10]);
  204.   Serial.print(" | ");
  205.  
  206.  
  207.   Serial.print("SW_A state: ");
  208.   Serial.print(data[11]);
  209.   Serial.print(" | ");
  210.  
  211.  
  212.   Serial.print("SW_B state: ");
  213.   Serial.print(data[12]);
  214.   Serial.print(" | ");
  215.  
  216.  
  217.   Serial.println();
  218.  
  219.  
  220.   // Write the array to the radio address.
  221.   radio.write(&data, sizeof(data));
  222.  
  223.  
  224. }
  225.  

Operating Instructions

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

Reciever Programming

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

The following code provides an example of a reciever. This code recieves the data from the transmitter and uses those values to set the speeds and directions of drive motors for a tracked base (still a work in progress).

  1.  
  2. // Include the required libraries for communication with the nrf module
  3. #include <SPI.h>
  4. #include <nRF24L01.h>
  5. #include <RF24.h>
  6.  
  7.  
  8. // CE and CSN pin definitions
  9. #define CE_PIN    7
  10. #define CSN_PIN   8
  11.  
  12.  
  13. // Motor pin definitions
  14. #define Ap1 3
  15. #define Ap2 5
  16. #define Bp1 6
  17. #define Bp2 9
  18. #define Cp1 2
  19. #define Cp2 4
  20.  
  21.  
  22. // Dead zone for input values
  23. int D_ZONE = 2;
  24.  
  25.  
  26. // Joystick position definitions
  27. float joyAx = 0;
  28. float joyAy = 0;
  29. float joyBx = 0;
  30. float joyBy = 0;
  31.  
  32. // Define the address for RF communication
  33. const uint64_t pipe = 0x01;
  34.  
  35.  
  36. // Define the package size to receive.
  37. uint8_t data[13];  
  38.  
  39.  
  40. // Define the new radio object
  41. RF24 radio(CE_PIN, CSN_PIN);
  42.  
  43.  
  44. void setup() {
  45.  
  46.  
  47.   pinMode(Ap1, OUTPUT);
  48.   pinMode(Ap2, OUTPUT);
  49.   pinMode(Bp1, OUTPUT);
  50.   pinMode(Bp2, OUTPUT);
  51.   pinMode(Cp1, OUTPUT);
  52.   pinMode(Cp2, OUTPUT);
  53.  
  54.  
  55.   // Make sure all motors are off
  56.   digitalWrite(Ap1, LOW);
  57.   digitalWrite(Ap2, LOW);
  58.   digitalWrite(Bp1, LOW);
  59.   digitalWrite(Bp2, LOW);
  60.   digitalWrite(Cp1, LOW);
  61.   digitalWrite(Cp2, LOW);
  62.  
  63.  
  64.   Serial.begin(9600);
  65.   delay(1000);
  66.  
  67.  
  68.   // Initialize the radio for recieving
  69.   Serial.println("Nrf24L01 Receiver Starting");
  70.   radio.begin();
  71.   radio.openReadingPipe(1,pipe);
  72.   radio.startListening();
  73. }
  74.  
  75. void loop() {
  76.  
  77.  
  78.   // This demonstraits how to recieve the inputs from the RC Transmitter.
  79.   if ( radio.available() ) {
  80.       // Read the data from the radio and store it in the data array
  81.       radio.read(data, sizeof(data));
  82.  
  83.  
  84.       // Uncomment to see controller values in the Serial Monitor
  85.       // Print out each value from data to the Serial Monitor
  86.       // for (int i = 0; i < 13; i++) {
  87.       //   Serial.print(data[i]);
  88.       //   Serial.print("\t");
  89.       // }
  90.  
  91.  
  92.       // Serial.println();
  93.  
  94.  
  95.       // Retrieve, map, and assign the joystick position values from the data array
  96.       joyAx = map(data[0], 0, 255, -255, 255);
  97.       joyAy = map(data[1], 0, 255, -255, 255);
  98.       joyBx = map(data[3], 0, 255, -255, 255);
  99.       joyBy = map(data[4], 0, 255, -255, 255);
  100.  
  101.  
  102.       // Functions to set motor speeds and directions
  103.       set_motor_pwm(joyAy, Ap1, Ap2);
  104.       set_motor_pwm(joyBy, Bp1, Bp2);
  105.       set_motor(joyAx, Cp1, Cp2);
  106.      
  107.  
  108.  
  109.   } else {
  110.     Serial.println("Transmitter unavailable");
  111.   }
  112. }
  113.  
  114.  
  115. void set_motor_pwm(int pwm, int pin1, int pin2) {
  116.  
  117.  
  118.   if (pwm < -2) {
  119.     analogWrite(pin1, -1 * pwm);
  120.     digitalWrite(pin2, LOW);
  121.  
  122.  
  123.     Serial.println("Forward");
  124.  
  125.  
  126.   } else if (pwm > 2) {
  127.     digitalWrite(pin1, LOW);
  128.     analogWrite(pin2, pwm);
  129.  
  130.  
  131.     Serial.println("Put it in reverse, Terry!");
  132.  
  133.  
  134.   } else {
  135.     digitalWrite(pin1, LOW);
  136.     digitalWrite(pin2, LOW);
  137.   }
  138.  
  139.  
  140. }
  141.  
  142.  
  143. void set_motor(int in, int pin1, int pin2) {
  144.  
  145.  
  146.   if (in < -10) {
  147.     digitalWrite(pin2, LOW);
  148.     digitalWrite(pin1, HIGH);
  149.   } else if (in > 10) {
  150.     digitalWrite(pin2, HIGH);
  151.     digitalWrite(pin1, LOW);
  152.   } else {
  153.     digitalWrite(pin2, LOW);
  154.     digitalWrite(pin1, LOW);
  155.   }
  156.  
  157.  
  158. }
  159.  

Related Products