1. Overview
In this project, you will learn how to control a 6DOF robotic arm using a PS5 DualSense controller, a Raspberry Pi 5, and an Arduino Nano.
The PS5 controller connects to the Raspberry Pi 5 through Bluetooth. The Raspberry Pi reads the joystick and button values using Python, then sends simple command numbers to the Arduino Nano through USB serial communication. The Arduino Nano receives those command numbers and moves the correct servo on the robotic arm.
So the full control path looks like this:
PS5 Controller → Raspberry Pi 5 → Arduino Nano → Servo Robotic ArmThis project is a good starting point if you want to understand how gamepad teleoperation works in robotics. Instead of controlling the arm from a phone app or directly from Arduino buttons, you will control it with a proper game controller, just like a small robotic manipulation system.
In this version, the focus is only on the robotic arm. Later, the same setup can be extended to control a robot car using an L298N motor driver. That means the PS5 controller can eventually control both the robotic arm and the mobile base.
2. ThingsBefore starting, gather the main parts and tools.
Hardware componentsYou will need:
Raspberry Pi 5
- Raspberry Pi 5
Arduino Nano
- Arduino Nano
PS5 DualSense controller
- PS5 DualSense controller
6DOF aluminium servo robotic arm
- 6DOF aluminium servo robotic arm
Arduino Nano servo expansion board or servo shield
- Arduino Nano servo expansion board or servo shield
6 servo motors
- 6 servo motors
External servo power supply
- External servo power supply
USB cable for Arduino Nano
- USB cable for Arduino Nano
Raspberry Pi 5 power supply
- Raspberry Pi 5 power supply
microSD card with Raspberry Pi OS
- microSD card with Raspberry Pi OS
Jumper wires where necessary
- Jumper wires where necessary
You will also need:
Raspberry Pi OS
- Raspberry Pi OS
Python 3
- Python 3
Pygame
- Pygame
PySerial
- PySerial
Arduino IDE
- Arduino IDE
Arduino Servo library
- Arduino Servo library
Visual Studio Code
- Visual Studio Code
VS Code Remote - SSH extension
- VS Code Remote - SSH extension
GitHub for code hosting
- GitHub for code hosting
Hackster.io or Instructables for documentation
- Hackster.io or Instructables for documentation
For the next version, where the arm will be mounted on a robot car, you may also need:
Arduino Uno
- Arduino Uno
L298N motor driver
- L298N motor driver
Robot car chassis
- Robot car chassis
DC motors
- DC motors
Motor battery
- Motor battery
Extra USB cable or serial connection
- Extra USB cable or serial connection
I started this project because I wanted a more practical and interesting way to control my robotic arm.
The robotic arm already had servos, brackets, a gripper, and an Arduino Nano servo shield. It could move, but I did not want to control it in a basic way. I wanted something closer to how real robotic systems are controlled — using a joystick or gamepad.
Since I had a PS5 controller and a Raspberry Pi 5, the idea was to use the PS5 controller as the main input device. The Raspberry Pi would read the controller, then send movement commands to the Arduino Nano. The Arduino Nano would then handle the servo movement.
I looked at older projects where Xbox controllers were used with Raspberry Pi and Arduino. Those projects helped me understand the basic idea, but my own setup was different. I was using a PS5 controller, Raspberry Pi 5, and Arduino Nano.
So instead of copying everything directly, I rebuilt the process step by step.
First, I set up the Raspberry Pi 5 in headless mode. Then I used VS Code with the Remote - SSH extension to connect to the Pi. This made the workflow much easier because I could edit the Python files directly from VS Code while still running the code on the Raspberry Pi.
After that, I paired the PS5 controller with the Raspberry Pi through Bluetooth.
Once the controller was working, I connected the Arduino Nano to the Raspberry Pi using USB. The Nano appeared as /dev/ttyUSB0, which confirmed that the Raspberry Pi could see it.
The robotic arm’s original Arduino code was designed to receive commands through an HC-05 Bluetooth module. For this project, that was not needed anymore, because the Raspberry Pi was already handling the Bluetooth connection from the PS5 controller. So I changed the Arduino code to receive commands directly from USB serial.
When I sent the first test command from the Raspberry Pi and one servo moved, that confirmed the main control chain was working.
From there, I mapped the PS5 controller buttons, mapped the Arduino command numbers, tested the movement of each servo, and finally wrote a stable Python control script.
4. System OverviewThe system has four main parts:
1. PS5 DualSense controller
2. Raspberry Pi 5
3. Arduino Nano
4. 6DOF robotic armThe PS5 controller does not connect directly to the Arduino. Instead, it connects to the Raspberry Pi through Bluetooth.
The Raspberry Pi runs a Python script. That script reads the PS5 controller input, decides which command should be sent, and sends the command to the Arduino Nano over USB serial.
The Arduino Nano receives the command and moves the correct servo.
Here is the basic system flow:
PS5 DualSense Controller
↓ Bluetooth
Raspberry Pi 5
↓ USB Serial
Arduino Nano
↓ Servo Signals
6DOF Robotic ArmThis setup works well because the Raspberry Pi is better for reading Bluetooth controller input and running Python logic, while the Arduino Nano is better for generating stable servo control signals.
In simple terms:
Raspberry Pi = brain and controller reader
Arduino Nano = servo driver
PS5 controller = human input device
Robotic arm = output system5. Hardware ArchitectureThe hardware is divided into two important parts:
1. Signal/control connection
2. Power connectionThe signal connection is responsible for sending commands. The power connection is responsible for supplying enough current to the servos.
The Raspberry Pi 5 is powered through its USB-C power supply. The Arduino Nano is connected to the Raspberry Pi through a USB cable. This USB cable is used for serial communication and also powers the logic side of the Arduino Nano.
However, do not power the servos from the Raspberry Pi or from the Nano USB line. Servo motors can draw a lot of current, especially when many joints move or when the arm is carrying load. If you power the servos from the wrong source, the arm may shake, reset, or behave strangely.
The correct power idea is:
Raspberry Pi 5 power supply → Raspberry Pi only
Raspberry Pi USB port → Arduino Nano logic and serial communication
External servo power supply → Servo shield and servo motorsThe hardware connection looks like this:
Raspberry Pi 5 USB port
↓
Arduino Nano USB port
↓
Servo expansion board
↓
6 servo motorsThe external power supply should feed the servo power rail on the servo board. Make sure the servo power supply is suitable for your servos.
6. Setting up the Robotic Arm KitStart by assembling the robotic arm carefully.
This project uses a 6DOF aluminium servo robotic arm. The arm has several servo joints and a two-finger gripper. Your own arm may look slightly different, but the idea is the same: each servo controls one joint or part of the arm.
When building the arm, don’t rush the mechanical part. A wrongly aligned servo horn can make the arm move badly or hit its mechanical limit too early.
Here are a few useful tips:
Fix the servo horns carefully.
- Fix the servo horns carefully.
Do not force any joint by hand.
- Do not force any joint by hand.
Keep the arm near a neutral position before testing.
- Keep the arm near a neutral position before testing.
Make sure wires are not trapped between brackets.
- Make sure wires are not trapped between brackets.
Do not overtighten rotating joints.
- Do not overtighten rotating joints.
Make sure the gripper can open and close freely.
- Make sure the gripper can open and close freely.
Check each joint gently before applying full power.
- Check each joint gently before applying full power.
After assembling the arm, the next thing is to know what each command does. This is very important because the Arduino code uses command numbers, but you need to know which command moves which servo.
With the arm facing me, this was the command mapping I got:
18 = Servo 5 right
19 = Servo 5 left
20 = Servo 3 up
21 = Servo 3 down
22 = Servo 6 gripper close
23 = Servo 6 gripper open
24 = Servo 2 up
25 = Servo 2 down
26 = Servo 1 base left
27 = Servo 1 base right
16 = Servo 4 up
17 = Servo 4 downYour own servo direction may differ depending on how the arm was assembled, so it is always good to test one command at a time.
7. Setting up the Arduino NanoThe Arduino Nano controls the servos.
In the original robotic arm setup, the Arduino code was designed to receive commands from an HC-05 Bluetooth module using SoftwareSerial.
For this project, we do not need the HC-05 module. The PS5 controller already connects to the Raspberry Pi through Bluetooth, so the Raspberry Pi will send commands to the Arduino Nano through USB.
That means we need to change the Arduino input method from Bluetooth serial to normal USB serial.
The old idea looked like this:
#include <SoftwareSerial.h>
SoftwareSerial Bluetooth(3, 2);
Bluetooth.begin(9600);
dataIn = Bluetooth.read();For this project, change it to:
Serial.begin(9600);
dataIn = Serial.read();So anywhere the old code uses:
Bluetooth.available()
Bluetooth.read()replace it with:
Serial.available()
Serial.read()This allows the Arduino Nano to receive command bytes directly from the Raspberry Pi through USB.
After editing the code, upload it to the Arduino Nano using the Arduino IDE.
If you are using an Arduino Nano clone, you may need to select:
Board: Arduino Nano
Processor: ATmega328P (Old Bootloader)After uploading the sketch, disconnect the Nano from your laptop and connect it to the Raspberry Pi 5.
8. Setting up VS Code for Raspberry Pi 5For this project, VS Code is the main working environment.
Instead of opening a separate terminal tool and jumping between different windows, you can use VS Code to connect directly to the Raspberry Pi, edit your files, open the Pi terminal, and run the project from one place.
Install VS CodeInstall Visual Studio Code on your laptop.
After installing VS Code, open it and install the Remote - SSH extension.
In VS Code:
Extensions → Search “Remote - SSH” → InstallThis extension allows VS Code to connect to the Raspberry Pi through SSH.
Prepare the Raspberry Pi for headless useThe Raspberry Pi 5 was used in headless mode. That means I did not need a monitor or keyboard connected to the Pi during normal use.
I prepared the microSD card using Raspberry Pi Imager. During the setup, I enabled SSH and configured the Wi-Fi.
The important settings were:
Hostname: raspberrypi
Username: pi
SSH: Enabled
SSH authentication: Password authentication
Wi-Fi: ConfiguredAfter inserting the SD card into the Raspberry Pi and powering it on, wait a few minutes for it to boot.
Connect VS Code to the Raspberry PiIn VS Code, press:
Ctrl + Shift + PThen search for:
Remote-SSH: Connect to HostWhen VS Code asks for the SSH address, enter:
pi@raspberrypi.localIf it asks for the platform, select:
LinuxThen enter your Raspberry Pi password.
Once the connection is successful, VS Code will open a new window connected to the Raspberry Pi.
You can confirm you are inside the Raspberry Pi by opening the VS Code terminal:
Terminal → New TerminalThe terminal should show something like:
pi@raspberrypi:~ $That means your VS Code terminal is running directly on the Raspberry Pi.
Open the project folderInside the VS Code terminal connected to the Pi, create and open the project folder:
mkdir -p ~/ps5_arm
cd ~/ps5_armIn VS Code, you can also open the folder through:
File → Open Folder → /home/pi/ps5_armNow all the Python files can be created and edited directly inside VS Code.
Update the Raspberry PiIn the VS Code terminal, run:
sudo apt update
sudo apt full-upgrade -yThen install the required packages:
sudo apt install -y python3-pip python3-venv python3-serial python3-pygame joystick evtest bluetooth bluez gitThese packages are needed because:
python3-serial → allows Python to talk to Arduino through serial
python3-pygame → reads the PS5 controller
joystick → provides jstest for controller testing
bluetooth/bluez → handles Bluetooth pairing
git → useful for version controlFrom this point, all project commands should be run inside the VS Code terminal connected to the Raspberry Pi.
9. Pairing the PS5 ControllerNow pair the PS5 controller with the Raspberry Pi.
In the VS Code terminal connected to the Raspberry Pi, check that Bluetooth is running:
sudo systemctl status bluetoothIf it is active and running, continue.
Open the Bluetooth control tool:
bluetoothctlInside bluetoothctl, run:
power on
agent on
default-agent
scan onNow put the PS5 controller in pairing mode. Hold:
PS button + Create buttonHold both until the controller light starts blinking.
When the controller appears in the Bluetooth scan, pair and connect it:
pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
quitReplace XX:XX:XX:XX:XX:XX with the MAC address shown on your screen.
After pairing, check the joystick device:
ls /dev/input/js*In this project, the PS5 controller appeared as:
/dev/input/js0Then test it:
jstest /dev/input/js0Move the sticks and press the buttons. If the numbers change, the controller is working.
10. Mapping the PS5 Gamepad ControlsDo not assume the PS5 button numbers. Always test them.
The controller mapping can vary depending on how Linux reads the controller. So before writing the final control script, map every stick and button.
You can create a mapping script in VS Code.
Inside your /home/pi/ps5_arm folder, create a file named:
map_ps5.pyUse that script to identify the button and axis numbers.
In this project, the confirmed PS5 mapping was:
Left stick X = Axis 0
Left stick Y = Axis 1
Right stick X = Axis 3
Right stick Y = Axis 4The direction values were:
Left stick left = Axis 0 negative
Left stick right = Axis 0 positive
Left stick up = Axis 1 negative
Left stick down = Axis 1 positive
Right stick left = Axis 3 negative
Right stick right = Axis 3 positive
Right stick up = Axis 4 negative
Right stick down = Axis 4 positiveThe button mapping was:
X / Cross = Button 0
Circle = Button 1
Triangle = Button 2
Square = Button 3
L1 = Button 4
R1 = Button 5
L2 = Button 6 and Axis 2
R2 = Button 7 and Axis 5
PS button = Button 10
Left stick press / L3 = Button 11
Right stick press / R3 = Button 12
Touchpad click = No input detectedThe D-pad was mapped as HAT 0:
D-pad up = (0, 1)
D-pad down = (0, -1)
D-pad left = (-1, 0)
D-pad right = (1, 0)
Released = (0, 0)This mapping is what made the final control script accurate.
11. Testing Raspberry Pi to Arduino Serial CommunicationBefore joining the PS5 controller and the robotic arm together, first test if the Raspberry Pi can talk to the Arduino Nano.
Connect the Arduino Nano to the Raspberry Pi using USB.
In the VS Code terminal connected to the Raspberry Pi, run:
python3 -m serial.tools.list_portsThe output showed:
/dev/ttyAMA10
/dev/ttyUSB0
2 ports foundThe important one is:
/dev/ttyUSB0That is the Arduino Nano.
Now create a small test script in VS Code to send one command to the Nano.
Create a file named:
arduino_test.pyUse this basic test:
import serial
import time
PORT = "/dev/ttyUSB0"
BAUD = 9600
ser = serial.Serial(PORT, BAUD, timeout=1)
time.sleep(2)
ser.write(bytes([19]))
time.sleep(0.5)
ser.write(bytes([0]))
ser.close()The important part is this:
ser.write(bytes([19]))Do not send:
ser.write(b"19")bytes([19]) sends the actual command number 19.
b"19" sends the text characters 1 and 9, which is not what the Arduino expects.
When the servo moved during this test, that confirmed the Raspberry Pi to Arduino Nano communication was working.
12. Raising Your Arm: PS5 Control of the Robotic ArmNow that the controller and Arduino serial communication are working separately, it is time to combine them.
The Raspberry Pi Python script will:
1. Read the PS5 controller
2. Check which button or joystick is active
3. Convert that input into an Arduino command number
4. Send the command to the Arduino Nano through USB serial
5. Stop the arm when no command is activeIn VS Code, create the final Python file:
ps5_to_arm_final.pyThis is the main file that joins the PS5 controller, Raspberry Pi, Arduino Nano, and robotic arm together.
At first, direct control worked, but I noticed one issue: when buttons were pressed too fast, the input could fluctuate. That can make a robotic arm jerk or behave unpredictably.
To make the movement more stable, the final script includes:
Deadzone
Debounce
Command hold
Rate limiting
Emergency stopThe deadzone ignores small joystick noise.
The debounce helps prevent quick button flicker.
The command hold makes sure the command remains stable for a short time.
The rate limit prevents the Raspberry Pi from sending commands too fast.
The emergency stop sends command 0 to stop the arm.
This made the control much better and safer.
13. Final Control LayoutAfter testing different control layouts, I preferred putting the base servo on L1 and R1.
This made the base easier to control, especially when it approached its movement limit.
The final control layout is:
L1 / R1 = Servo 1 base left/right
Left stick left/right = Servo 4 up/down
Left stick up/down = Servo 2 up/down
Right stick left/right = Servo 5 left/right
Right stick up/down = Servo 3 up/down
X / Circle = Gripper open/close
Triangle = Emergency stop
CTRL + C = Stop program safelyThe command mapping behind the layout is:
L1 → Base left → Command 26
R1 → Base right → Command 27
Left stick left/right → Servo 4 → Commands 16 / 17
Left stick up/down → Servo 2 → Commands 24 / 25
Right stick left/right → Servo 5 → Commands 19 / 18
Right stick up/down → Servo 3 → Commands 20 / 21
X → Gripper open → Command 23
Circle → Gripper close → Command 22
Triangle → Stop → Command 0This control layout is not compulsory. You can change it to fit your own hand preference, but this version worked well for my setup.
14. The Nitty-GrittyThe Python script sends only one movement command at a time.
That is intentional.
If the script tries to move too many joints at once, the arm may become harder to control, and the power demand may increase. For a first version, one command at a time is safer and easier to debug.
The deadzone is set like this:
DEADZONE = 0.45This means the joystick must move beyond 0.45 or below -0.45 before the script accepts it as a real movement.
The debounce time is:
DEBOUNCE_TIME = 0.10That means the input must remain stable for about 100 milliseconds before the command is accepted.
The send interval is:
SEND_INTERVAL = 0.10This means the Raspberry Pi sends commands every 100 milliseconds instead of flooding the Arduino Nano.
The Arduino command system is simple:
Command number sent from Pi → Arduino reads it → Arduino moves servoFor example:
26 = base left
27 = base right
22 = gripper close
23 = gripper openThis method is simple, but it is also flexible. Later, if you add a car base, you can create another set of command numbers for the car.
15. Troubleshooting and Lessons LearnedHere are some of the issues I faced and how they were solved.
VS Code Remote SSH setupThe smoothest workflow was using VS Code with Remote - SSH. Once VS Code connected to the Raspberry Pi, I could edit files and run commands inside the same environment.
The important thing is to confirm that the VS Code terminal is actually connected to the Raspberry Pi.
You should see:
pi@raspberrypi:~ $If you see that, you are running commands directly on the Pi.
Raspberry Pi password issueSince the Pi was used headless, SSH had to be enabled from Raspberry Pi Imager. The username and password also had to be set correctly during flashing.
Finding the Arduino NanoThe Arduino Nano appeared as:
/dev/ttyUSB0That became the serial port used in the Python script.
PS5 controller mappingThe PS5 controller mapping was not guessed. Each button and joystick axis was tested manually.
Fast button fluctuationFast button presses caused some fluctuation. This was handled with debounce, command hold, and rate limiting.
Servo limit issueWhen the base servo reached around 180 degrees, it sometimes stopped responding as expected. A better future improvement is to clamp the servo position directly in the Arduino code so it never goes outside its safe range.
Servo powerThe servos must use an external power supply. Do not power them directly from the Raspberry Pi or Nano USB port.
16. Future Upgrade: Mounting the Arm on an L298N Robot CarThe next upgrade is to mount the robotic arm on a robot car chassis.
The car can be controlled using:
Arduino Uno
L298N motor driver
DC motors
Motor battery
Robot chassisThe same PS5 controller can control both the robotic arm and the car.
The future system can look like this:
PS5 Controller
↓ Bluetooth
Raspberry Pi 5
↓ USB Serial 1
Arduino Nano → Robotic Arm
Raspberry Pi 5
↓ USB Serial 2
Arduino Uno → L298N Motor Driver → Robot CarThe Raspberry Pi will act as the main controller. It will read the PS5 controller and decide whether the command should go to the robotic arm or to the car.
A simple future control idea is:
Joysticks = robotic arm
D-pad = car movement
L2 / R2 = speed control
Triangle = emergency stop
Square = switch modeAnother option is to use one mode for the arm and another mode for the car. That will make the control cleaner and prevent command clashes.
17. Some Useful ReferencesThese references are useful if you want to understand or extend the project:
Raspberry Pi documentation
VS Code Remote - SSH documentation
Pygame joystick documentation
Python PySerial documentation
Arduino Servo library documentation
Linux joystick input tools
Hackster.io robotic arm controller examples
GitHub project repositoryOlder Xbox controller robot arm projects helped with the general idea of using a Raspberry Pi as a bridge between a gamepad and Arduino.
This project builds on that idea, but adapts it for:
PS5 controller
Raspberry Pi 5
Arduino Nano
USB serial communication
6DOF aluminium robotic arm
VS Code Remote SSH workflow18. SchematicsMain system block diagramPS5 DualSense Controller
↓ Bluetooth
Raspberry Pi 5
↓ USB Serial
Arduino Nano
↓ Servo signals
Servo Expansion Board
↓
6DOF Robotic ArmPower diagramRaspberry Pi 5 power supply
↓
Raspberry Pi 5
Raspberry Pi USB port
↓
Arduino Nano logic and serial communication
External servo power supply
↓
Servo expansion board
↓
Servo motorsVS Code workflow diagramLaptop running VS Code
↓ Remote - SSH
Raspberry Pi 5
↓ Python scripts
Arduino Nano
↓ Servo commands
Robotic ArmFuture car expansion diagramPS5 Controller
↓ Bluetooth
Raspberry Pi 5
↓ USB Serial
Arduino Nano → Robotic Arm
Raspberry Pi 5
↓ USB Serial
Arduino Uno → L298N → DC motors19. CodeThe code is divided into two main parts.
Raspberry Pi Python codeFile:
raspberry-pi/ps5_to_arm_final.pyThis script reads the PS5 controller, applies deadzone and debounce, then sends command numbers to the Arduino Nano through /dev/ttyUSB0.
It handles:
PS5 joystick input
PS5 button input
Emergency stop
Serial communication
Command stabilityArduino Nano codeFile:
arduino/24G6_fixed_USB_SERIAL.inoThis Arduino sketch receives command numbers through USB serial and moves the correct servo.
It replaces the old HC-05 Bluetooth input with USB serial input.
Testing scriptsThese test scripts are helpful:
ps5_test.py
map_ps5.py
arduino_test.py
map_arm.pyUse ps5_test.py to confirm that Python can read the controller.
Use map_ps5.py to find the real button and axis numbers.
Use arduino_test.py to confirm that the Raspberry Pi can send serial commands to the Arduino Nano.
Use map_arm.py to test each Arduino command and see which servo moves.
All Python files can be created, edited, and run directly from VS Code after connecting to the Raspberry Pi through Remote - SSH.
20. CreditsThis project was built and tested by Ademosu Oluwatobi using a Raspberry Pi 5, Arduino Nano, PS5 DualSense controller, and a 6DOF aluminium servo robotic arm.
The idea was inspired by earlier gamepad-controlled robotic arm projects, especially projects that used a Raspberry Pi as a bridge between a controller and Arduino.
This version was adapted and rebuilt for:
PS5 DualSense controller
Raspberry Pi 5
Arduino Nano
USB serial control
6DOF servo robotic arm
VS Code Remote SSH workflowIt also serves as the foundation for a future mobile robotic arm project using an L298N robot car base.
21. CommentsThis project proves that a PS5 controller can be used to control a robotic arm through a Raspberry Pi and Arduino Nano.
The most important milestone is that the full control chain works:
PS5 Controller → Raspberry Pi 5 → Arduino Nano → Robotic ArmVS Code made the workflow cleaner because the Raspberry Pi could be controlled remotely, while the code could still be edited like a normal development project.
There is still room to improve the project.
The next improvement is to mount the arm on a robot car and control both the car and arm with the same PS5 controller.
Another improvement is to modify the Arduino servo code so each servo angle is safely clamped between its minimum and maximum range. That will make the arm more predictable and safer near its movement limits.
For now, this version is a solid working base for PS5-controlled robotic arm teleoperation.











Comments