How to use a PS3 Gamepad with MicroPython on the EV3 brick

Anton

Updated on:

Micropython On LEGO MINDSTORMS EV3

MicroPython is the new kid on the block for programming the LEGO MINDSTORMS EV3 Intelligent Brick. It’s lightning fast because it’s so lightweight. Python 3 takes up to 20 seconds to start and is a little overkill for just driving a robot around. MicroPython starts in mere seconds. I will assume you are set up with VS Code, ev3dev and MicroPython.

EV3 MicroPython program for using the bluetooth gampad

Let’s start with the complete program for reading the PS3 gamepad stick positions in MicroPython. Below I will and dissect how it works below it. You can just go to VS Code, connect to your EV3 brick and paste this code in the main.py file. Press F5 to run it. If the gamepad is connected you will be able to steer a simple tank-like robot.

#!/usr/bin/env pybricks-micropython

from pybricks import ev3brick as brick
from pybricks.ev3devices import (Motor, TouchSensor, ColorSensor,
                                 InfraredSensor, UltrasonicSensor, GyroSensor)
from pybricks.parameters import (Port, Stop, Direction, Button, Color,
                                 SoundFile, ImageFile, Align)
from pybricks.tools import print, wait, StopWatch
from pybricks.robotics import DriveBase

import struct

# Declare motors 
left_motor = Motor(Port.B)
right_motor = Motor(Port.C)

# Initialize variables. 
# Assuming sticks are in the middle when starting.
right_stick_x = 124
right_stick_y = 124

# A helper function for converting stick values (0 - 255)
# to more usable numbers (-100 - 100)
def scale(val, src, dst):
    """
    Scale the given value from the scale of src to the scale of dst.
 
    val: float or int
    src: tuple
    dst: tuple
 
    example: print(scale(99, (0.0, 99.0), (-1.0, +1.0)))
    """
    return (float(val - src[0]) / (src[1] - src[0])) * (dst[1] - dst[0]) + dst[0]


# Find the PS3 Gamepad:
# /dev/input/event3 is the usual file handler for the gamepad.
# look at contents of /proc/bus/input/devices if it doesn't work.
infile_path = "/dev/input/event3"

# open file in binary mode
in_file = open(infile_path, "rb")

# Read from the file
# long int, long int, unsigned short, unsigned short, unsigned int
FORMAT = 'llHHI'    
EVENT_SIZE = struct.calcsize(FORMAT)
event = in_file.read(EVENT_SIZE)

while event:
    (tv_sec, tv_usec, ev_type, code, value) = struct.unpack(FORMAT, event)
    if ev_type == 3 and code == 3:
        right_stick_x = value
    if ev_type == 3 and code == 4:
        right_stick_y = value

    # Scale stick positions to -100,100
    forward = scale(right_stick_y, (0,255), (100,-100))
    left = scale(right_stick_x, (0,255), (100,-100))

    # Set motor voltages. If we're steering left, the left motor
    # must run backwards so it has a -left component
    # It has a forward component for going forward too. 
    left_motor.dc(forward - left)
    right_motor.dc(forward + left)

    # Finally, read another event
    event = in_file.read(EVENT_SIZE)

in_file.close()

Connecting more sticks and buttons

If you want to connect more buttons and sticks to motor functions, just listen for different event types and codes in the main while loop. Have those events trigger different function on your robots. Below is an overview of all event types and codes. Derek Segesdy was so kind to fix the errors in my overview and share his results.

How the PS3 gamepad program works in MicroPython

Device input without evdev

In python 3 there’s a handy library called evdev which connects to the Linux device events system. There’s no such thing in MicroPython. We’re lucky however that Debian Linux uses a file based access system for usb devices. So we can just read a file and decode the bytes inside with python struct.

After connecting the PS3 gamepad, you could open an SSH terminal to your EV3 brick and check which file handler is for your gamepad. On my brick it looked like this:

robot@ev3dev:~$ cat /proc/bus/input/devices
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="LEGO MINDSTORMS EV3 Speaker"
P: Phys=
S: Sysfs=/devices/platform/sound/input/input0
U: Uniq=
H: Handlers=kbd event0

[...]

I: Bus=0005 Vendor=054c Product=0268 Version=8100
N: Name="PLAYSTATION(R)3 Controller"
P: Phys=a0:e6:f8:60:30:81
S: Sysfs=/devices/platform/soc@1c00000/serial8250.2/tty/ttyS2/hci0/hci0:1/0005:054C:0268.0001/input/input2
U: Uniq=00:06:f7:89:3f:81
H: Handlers=event3
B: PROP=0
B: EV=20001b
B: KEY=f 0 0 0 0 0 0 0 7fdb0000 0 0 0 0 0 0 0 0 0
B: ABS=3f
B: MSC=10
B: FF=1 7030000 0 0

Near the end of the file you can see the file handler for the playstation controller. It contains a few bits and bytes with encoded event values. I use python struct to unpack those bytes and make some sense of them.

Controlling the robot from the main loop without Threading

In my regular python 3 code I used threading to have a neat 60Hz motor control loop. The MicroPython program on the other hand waits for an event from the gamepad to pop up. These events come fairly often, so you get the feeling of real-time. However it is probably not suited for a closed control loop. Such loop would be needed with a rack gear and 4-link steering mechanism. In MicroPython there are coroutines and asyncio. They are tough to program with for beginners. So that’s stuff for another article.

Like this article? Help us make more!

Your support matters

We appreciate your support on Patreon and YouTube! Small things count.

Become a Patron

Don't miss a thing

Subscribe below to get an email when we publish a new article.

Share this with someone who needs to know

23 thoughts on “How to use a PS3 Gamepad with MicroPython on the EV3 brick”

  1. Great Article, but what is the secret of connecting a PS4 controller. I managed to do a few months back, but cannot seem to repeat my success as I write this.

    Tried this code out with a PS4 directly connected and all hell broke loss. Wheels spinning at maximum power, no controls on the PS4 controller.

    Reply
    • I have no PS4 controller, so I’m afraid I can’t help you. You could use the micropython script from my other article and print the codes from the PS4 controller. Maybe you can figure out the differences?

      Reply
  2. OK, got the Sony PS4 Dual Shock paired, [You need to press playstation and sharing button and then go look on the brick for the connection] but still looking at pandemonium! There are three event areas in the devices. One for the touchpad, one for the motion and one for the controller. None of them work with this script.

    robot@ev3dev:/proc/bus/input$ more devices
    I: Bus=0000 Vendor=0000 Product=0000 Version=0000
    N: Name=”LEGO MINDSTORMS EV3 Speaker”
    P: Phys=
    S: Sysfs=/devices/platform/sound/input/input0
    U: Uniq=
    H: Handlers=kbd event0
    B: PROP=0
    B: EV=40001
    B: SND=6

    I: Bus=0019 Vendor=0001 Product=0001 Version=0100
    N: Name=”EV3 Brick Buttons”
    P: Phys=gpio-keys/input0
    S: Sysfs=/devices/platform/gpio_keys/input/input1
    U: Uniq=
    H: Handlers=kbd event1
    B: PROP=0
    B: EV=3
    B: KEY=1680 0 0 10004000

    I: Bus=0005 Vendor=054c Product=09cc Version=8100
    N: Name=”Wireless Controller Touchpad”
    P: Phys=40:bd:32:3e:56:97
    S: Sysfs=/devices/platform/soc@1c00000/serial8250.2/tty/ttyS2/hci0/hci0:1/0005:054C:09CC.0002/input/input6
    U: Uniq=dc:af:68:a5:ee:6e
    H: Handlers=event2
    B: PROP=5
    B: EV=b
    B: KEY=2420 0 10000 0 0 0 0 0 0 0 0
    B: ABS=2608000 0

    I: Bus=0005 Vendor=054c Product=09cc Version=8100
    N: Name=”Wireless Controller Motion Sensors”
    P: Phys=40:bd:32:3e:56:97
    S: Sysfs=/devices/platform/soc@1c00000/serial8250.2/tty/ttyS2/hci0/hci0:1/0005:054C:09CC.0002/input/input7
    U: Uniq=dc:af:68:a5:ee:6e
    H: Handlers=event3
    B: PROP=40
    B: EV=19
    B: ABS=3f
    B: MSC=20

    I: Bus=0005 Vendor=054c Product=09cc Version=8100
    N: Name=”Wireless Controller”
    P: Phys=40:bd:32:3e:56:97
    S: Sysfs=/devices/platform/soc@1c00000/serial8250.2/tty/ttyS2/hci0/hci0:1/0005:054C:09CC.0002/input/input5
    U: Uniq=dc:af:68:a5:ee:6e
    H: Handlers=event4
    B: PROP=0
    B: EV=20001b
    B: KEY=7fdb0000 0 0 0 0 0 0 0 0 0
    B: ABS=3003f
    B: MSC=10
    B: FF=1 7030000 0 0

    Reply
  3. Ok,

    Managed to extract these values?

    Reading event4 Left/Right Stick Center/Forward/Back/Left/Right many events
    RSC ev_type 3 code 4 returns 128/129 {centre}
    RSF ev_type 3 code 4 returns 0 {forward}
    RSB ev_type 3 code 4 returns 255 {back}

    RSC ev_type 3 code 3 returns 123/122 {centre}
    RSL ev_type 3 code 3 returns 0 {left}
    RSR ev_type 3 code 3 returns 255 {right}

    LSC ev_type 3 code 0 returns 130/141
    LSL ev_type 3 code 0 returns 0
    LSR ev_type 3 code 0 returns 255
    LSC ev_type 3 code 1 returns 128/129
    LSF ev_type 3 code 1 returns 0
    LSB evtype 3 code 1 returns 255

    Reply
  4. Bon,

    So this code produces forward/backward using the right stick.

    if ev_type == 3 and code == 1:
    right_stick_x = value
    if ev_type == 3 and code == 4:
    right_stick_y = value

    Your cpython version works better 🙂

    Reply
  5. Sorry and finally this gives me all for directions, but it isn’t very clean.

    if ev_type == 3 and code == 3:
    right_stick_x = value
    if ev_type == 3 and code == 1:
    right_stick_x = value
    if ev_type == 3 and code == 0:
    right_stick_y = value
    if ev_type == 3 and code == 4:
    right_stick_y = value

    Reply
    • So it works? Shouldn’t it be more like this?

      if ev_type == 3 and code == 3:
      LEFT_stick_x = value
      if ev_type == 3 and code == 1:
      right_stick_x = value
      if ev_type == 3 and code == 0:
      LEFT_stick_y = value
      if ev_type == 3 and code == 4:
      right_stick_y = value

      Reply
  6. Hi, thanks I got my 8BitDo working with this code also.
    One thing is how to I tell motor to just rotate 45 deg left and right ?
    right = scale(rPad_x, (0, 255), (100, -100))
    left = scale(rPad_x, (0, 255), (100, -100))
    main_motor.dc(right – left).
    main_motor.dc(right + left)

    So if I trun the right pad to at the end of the pad 0 motor only goes 45 deg

    Reply
  7. I received the following error when trying to execute the command:

    “Starting: brickrun –directory=”/home/robot/PS3_Controller_Redux” “/home/robot/PS3_Controller_Redux/main.py”
    Started.
    ———-
    Starting remote process failed: Failed to execute child process “/home/robot/PS3_Controller_Redux/main.py” (Permission denied)
    ———-
    Exited with error code 1.”

    Thoughts? I tried running the operation through VS with the EV3 plugged in.

    Reply
  8. Hi Anton,
    I followed your instruction to connect a PS3 controler and it seems to works fine. But I dont no what to do with this kind of error to controle a small tank program, and I tried several things but the messages still pops up:

    Traceback (most recent call last):
    File “/home/robot/ROBOT_EDUCATOR/main.py”, line 16, in
    NameError: name ‘ultrasonicSensor’ isn’t defined
    ———-
    Exited with error code 1.

    Reply
  9. Hey Anton I watched the video and have read over the code but I want to ask how I can make to additional motors move as well to create a type of crane action I would like to use the X button and O button if possible or the R2 and L2. Any advice on how I can figure out what codes i should add.

    Reply
    • I’m getting so many questions about this, I’ll dedicate an article to this situation specifically. The short answer is: check for the buttons event type, then find the codes for the buttons you want to use and wait for a value == 0 event to happen. Value == 0 happens when a button has been pressed and is then released. Or you could turn the crane motors on at value==1 and off again at value==0.

      Reply
  10. Hi, Anton.
    I have 8BitDo controller and it has been long time since dev ev3 so can you remind me how do I get the the events to terminal?
    cat /dev/input/event2 I get the events but is show the data )��a)��_a)�� >)��_ format ( binary ? ) So how do I get the format human readable format, thanks 🙂

    Reply

Leave a Reply

Item added to cart.
0 items - 0.00