Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : gamepad-controlled PicoMite robot
Author | Message | ||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
At times it is difficult to let something go ... I wanted to remote-control my robot with a gamepad controller, but not with a wired one. I got a Sony PS4 controller but this uses Bluetooth, unfortunately. With the cable, it works - that is, it is recognized by PicoMiteUSB - but walking a robot on a leash is besides the point ... I thought just adding vendor and device ID of my trusted wireless gamepad to the PicoMiteUSB firmware might do the trick. Well, my controller can produce at least 4 vendor/device ID combinations and almost as many data formats. In PS3 mode, it is not recognized by the firmware, probably because the connection between dongle and controller takes too long to negotiate; MMBasic "looses interest" and disconnects the controller. Therefore, I decided to see if I can add a controller type (here, XBox/Android) to the firmware ... |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
For this, I needed to compile the firmware; how I did this is documented here: README.md There is probably an easier way, but these instructions worked for me. It was fun, playing with `USBKeyboard.c`, exploring what different controllers generate in terms of data. I managed to add a controller type (`XBOX`) that the modified release candidate (5.09.00R3) can recognize and handle. It has the ID 131 (`XBOX`) and understands a data protocol that provides 9 bytes/package (the controller manual says this was an Android format, whatever that means). But it is nice and compact. Here is the code that decodes a package: if(len == 9) { if(report[0]&0x10)b|=0x0400; // Button y/triangle if(report[0]&0x02)b|=0x0800; // Button b/circle if(report[0]&0x08)b|=0x1000; // Button x/square if(report[0]&0x01)b|=0x2000; // Button a/cross if(report[1]&0x08)b|=0x0002; // Button start -> start? if(report[1]&0x04)b|=0x0008; // Button home -> xbox/PS? if(report[1]&0x10)b|=0x0004; // Button select -> back/share? if(report[0]&0x80)b|=0x0001; // Button R/R1 if(report[0]&0x40)b|=0x0010; // Button L/L1 if(report[2] == 0x4)b|=0x20; // Button down cursor if(report[2] == 0x2)b|=0x40; // Button right cursor if(report[2] == 0x0)b|=0x80; // Button up cursor if(report[2] == 0x6)b|=0x100; // Button left cursor nunstruct[n].ax=report[3]; nunstruct[n].ay=report[4]; nunstruct[n].Z=report[5]; nunstruct[n].C=report[6]; nunstruct[n].L=report[8]; nunstruct[n].R=report[7]; } If you are interested, the modified `USBKeyboard.c` is here. Note that the modifications (additions) to Peter's code are marked: // ===>>> ... // <<<=== I am aware that the next change Peter does to the firmware will ``kill`` my modifications, but then I don't want to run the robot remote-controlled forever - at some point, I hope it becomes more autonomous. |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
A small Basic program to test gamepads (I tested it with the PS4 controller as well) you can find here: gamepad_test.bas ' Test - Gamepad ' Option Explicit Option Base 0 Option Default Float Dim integer running = 1, ch Dim string s$ ' Gamepad-related variables ' Types: ' 0=not in use, 1=keyboard, 2=mouse, 128=ps4, 129=ps3, 130=SNES/Generic, 131=Xbox Dim integer gpad.ch = 0, gpad.type = 0, gpad.changed = 0 Dim integer gpad.data(6), gpad._intDetect = 0 initGamePad 131 If gpad.ch = 0 Then Print "No gamepad found" End EndIf Do While running ' Update gamepad data and if new, print values out If updateGamepad() Then Math V_print gpad.data() checkGamepad EndIf ' Check for escape, which aborts program ch = Asc(LCase$(Inkey$)) If ch = 27 Then running = 0 Pause 20 Loop End ' ---------------------------------------------------------------------------- Sub initGamepad _type ' Look for gamepad `_type` and initialize it, if available Local integer i, j gpad.ch = 0 gpad.type = 0 Math Set 0, gpad.data() For i=1 To 4 j = MM.Info(USB i) If j = _type Then Print "Gamepad type="+Str$(_type)+" found." gpad.ch = i gpad.type = _type Device gamepad interrupt enable i, _cb_gamepad Exit Sub EndIf Next End Sub Function updateGamepad() ' Keeps gamepad data up to date (call frequently) Static integer v(6) If gpad.type < 128 Then updateGamepad = 0 : Exit Sub : EndIf ' Read gamepad sticks etc gpad.data(0) = DEVICE(gamepad gpad.ch, lx) gpad.data(1) = DEVICE(gamepad gpad.ch, ly) gpad.data(2) = DEVICE(gamepad gpad.ch, rx) gpad.data(3) = DEVICE(gamepad gpad.ch, ry) gpad.data(4) = DEVICE(gamepad gpad.ch, l) gpad.data(5) = DEVICE(gamepad gpad.ch, r) ' Check if any stick value as substantially changed Math C_Sub v(), gpad.data(), v() : v(6) = 0 gpad.changed = gpad._intDetect Or Abs(Math(Sum v())) > 2 gpad._intDetect = 0 updateGamepad = gpad.changed Math Scale gpad.data(), 1, v() End Function ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ' &H0001 := R1 ' &H0002 := Options/Start ' &H0004 := PS/Home ' &H0008 := Share/Select ' &H0010 := L1 ' &H0020 := pad, down ' &H0040 := pad, right ' &H0080 := pad, up ' &H0100 := pad, left ' &H0200 := R2 pressed, value in `gpad.data(5)` ' &H0400 := Triangle ' &H0800 := Circle ' &H1000 := Square ' &H2000 := Cross ' &H4000 := L2 pressed, value in `gpad.data(4)` ' &H8000 := touchpad pressed Sub checkGamepad Local string s$ = "" If gpad.data(6) And &H0400 Then s$ = s$ +"Triangle " If gpad.data(6) And &H1000 Then s$ = s$ +"Square " If gpad.data(6) And &H0800 Then s$ = s$ +"Circle " If gpad.data(6) And &H2000 Then s$ = s$ +"Cross " If gpad.data(6) And &H0010 Then s$ = s$ +"L1 " If gpad.data(6) And &H0001 Then s$ = s$ +"R1 " If gpad.data(6) And &H0002 Then s$ = s$ +"Start " If gpad.data(6) And &H4000 Then s$ = s$ +"L2 ("+Str$(gpad.data(4))+") " If gpad.data(6) And &H0200 Then s$ = s$ +"L2 ("+Str$(gpad.data(5))+") " If gpad.data(6) And &H0008 Then s$ = s$ +"Share " If gpad.data(6) And &H0004 Then s$ = s$ +"PS " If gpad.data(6) And &H0100 Then s$ = s$ +"< " If gpad.data(6) And &H0040 Then s$ = s$ +"> " If gpad.data(6) And &H0080 Then s$ = s$ +"^ " If gpad.data(6) And &H0020 Then s$ = s$ +"v " If gpad.data(6) And &H8000 Then s$ = s$ +"Touchpad " Print "&B"+Bin$(gpad.data(6), 32)"" Print s$ End Sub ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sub _cb_gamepad ' Interrupt callback for gamepad If gpad.type < 128 Then Exit Sub gpad.data(6) = DEVICE(gamepad gpad.ch, b) gpad._intDetect = 1 End Sub ' ---------------------------------------------------------------------------- |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
The robot is one of my long-long-term projects (one can see that from the dust on some parts ... ). The mechanical setup was inspired by the remote-controlled Arduino-based vorpal robot, but with one more joint per leg added. Its 18 servo motors are controlled by a Maestro 18, which receives commands from one of two Pico (W) microcontrollers. They both run MMBasic, one -- the server -- controls the gait and generates the leg movements, the second one -- the client -- currently receives the gamepad input and talks to the server via a serial connection. Eventually, the client will control the robot autonomously. Other features are: - 8-channel servo-load sensing - BNO055-based 9-DOF IMU - two WS2812 LEDs for signalling the robot's state - a TFT display (not mounted here) - a buzzer - separate logic and servo batteries with voltage monitoring - hardware handshaking between the Picos - a potentiometer for mode selection (not yet used) Originally, I developed MicroPython code for it but gave up at some point, because it did too much in the background (e.g., garbage collection) and that kept messing up the servo update timing as well as the communication between the two "brains" ... Newer MicroPython releases may be better with that now, but I was wondering if a solution with MMBasic running on "bare metal" on a Pico would be better. And indeed, it runs very well and reliable, the communication between the Picos is a dream. And if the timing is too slow, I just overclock it more Finally, here's a video, I'll document the hardware and the MMBasic code soon in the repository - in case someone is interested. |
||||
matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 9111 |
Thomas Great stuff - I'll add the xbox code to the standard build if that is OK? Note there is a bug in TinyUSB 0.16 that I found and that has now been fixed so you want to download the latest from the github. Please could you provide details of the controller you use that works wirelessly and how to configure it. Thanks Peter |
||||
lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3150 |
Terrific work. Very impressive. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4226 |
Wauw, controlling 18servos to create movements if quite impressive. Volhout PicomiteVGA PETSCII ROBOTS |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Thank you! Of course, you can add it, however, I am not sure how useful that is for others. I bought it 4 years ago to work with my Windows PC, which is does well. It is from a company called EasySMX (PS3 Controller, EasySMX 2,4G Wireless Gamepad, Joysticks Dual Vibration TURBO) and it can work in different modes: Note that it is not really "XBox" - I gave it that name in the code before I figured out what it really is. Depending on the mode, it returns different vendor/device ID combinations and data packages: "Android", vendor ID: 0x11c0, product ID: 0x5500 "X-input", vendor ID: 0x045e, product ID: 0x028e "D-input, emulation", vendor ID: 0x11c1, product ID: 0x9101 "PS3", vendor ID: 0x054c, product ID: 0x09cc When connected to a Windows 11 PC, all modes are recognized. For the PicoMiteUSB, "PS3" would have been the obvious choice, by just adding another device ID to your `is_sony_ds3()` function, but for some reason that did now work. This is the controllers default mode. When it is started, it behaves like a PS3 controller. But then changes mode, if no PlayStation responds (I guess). This is probably why the PicoMiteUSB firmware was not able to stay connected. "D-input, emulation" works but generates very long (27 byte) data packages that contain all relevant data (some redundantly). "Android" generates nicely compact 9-byte data packages, providing data about almost all buttons and joy sticks that your firmware supports, therefore I opted for this format. To cut a long story short, I think it would be great to add another type of controller/gamepad, but I am not sure which format. Maybe that "Android" format, that I mislabelled in my code as XBOX. However, I don't know how common that is. As to the wireless: I expect that Gamepads that use a special dongle (like the one here) should generally work - if their IDs/data format are known and they make it through the "negotiation phase". All newer wireless gamepads seem to rely on Bluetooth and are therefore out of the question. I will try your newest firmware version with the TinyUSB 0.16 bug fix to see if my controller now stays connected if I add its device code. Thanks for all your fantastic work on the PicoMite and Co! Best Thomas |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Thank you! |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Thank you! |
||||
stanleyella Guru Joined: 25/06/2022 Location: United KingdomPosts: 2127 |
impressive! I never finished mine, it's not easy |
||||
javavi Senior Member Joined: 01/10/2023 Location: UkrainePosts: 213 |
How can I make the Xbox360 gamepad work too? |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Hi Peter, I pulled all changes in the PicoMite and TinyUSB repositories this morning. No problems building and my code with the gamepad changes still worked. I also added the PS3 vendor/product IDs my gamepad generates in PS3 mode, however, it's still not recognized. But as I said above, I think this is due to the weirdness of my gamepad trying to negotiate the best mode of operation. Best Thomas Edited 2024-04-08 02:07 by karlelch |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Hi Javavi, as detailed above, I don't have an Xbox 360 gamepad. What you would need to do is to find out the vendor/product IDs and the format of the data the gamepad it supplies. I can give you here only pointers how I did it for my strange gamepad. 1) I wrote a short Python script for the PC that looks up all connected USB devices, waits for you to plug a new device in (i.e. the gamepad), then checks which device is new, and prints vendor/product ID and such. import hid def print_dev(d): print(f"vendorID={hex(d['vendor_id'])}, productID={hex(d['product_id'])} ", end="") print(f"manufacturer=`{d['manufacturer_string']}`, product=`{d['product_string']}`") # Get a list of all connected HID devices devices = hid.enumerate() print("Device entries found:") for i, dev in enumerate(devices): print(f"{i:3}: ", end="") print_dev(dev) # Wait for the user to plug in the new device input("Connect new device and press Enter") print("New device(s):") # Get list again and find differences devices_new = hid.enumerate() n_new = 0 # Iterate over the devices and retrieve the vendor and product IDs for dev_new in devices_new: found = False for dev in devices: if found := dev_new["path"] == dev["path"]: break if not found: print(f"{n_new:3}: ", end="") print_dev(dev_new) n_new += 1 if n_new == 0: print("No new devices found") 2) My modified `USBKeyboard.c` for the PicoMiteUSB firmware contains a section, where new gamepad IDs can be added for testing: // ===>>> #define TE_TEST 0 #if(TE_TEST) // Add your IDs here for testing #define ANDROID 131 #define TE_V_ID 0x11c0 #define TE_P_ID 0x5500 #else // EasySMX Wireless, u, Android mode (u) #define ANDROID 131 #define TE_V_ID 0x11c0 #define TE_P_ID 0x5500 #endif If `TE_TEST` is set to 1, then the code uses the IDs given in the first branch of the `#if()` directive and prints out the data from the gamepad as soon as it is connected to the Pico. (Of course, this requires re-building the firmware). The result in Tera Term looks something like this: 9 0_0 1_0 2_F 3_80 4_80 5_80 6_80 7_0 8_0 9 0_0 1_0 2_F 3_80 4_80 5_80 6_80 7_0 8_0 9 0_0 1_0 2_F 3_80 4_80 5_80 6_80 7_0 8_0 9 0_0 1_0 2_F 3_80 4_80 5_80 6_80 7_0 8_0 9 0_0 1_0 2_F 3_80 4_80 5_80 6_80 7_0 8_0 With the first number being the number of data bytes to follow (here 9), and the following `x_y` pairs the byte index (`x`) and the value (`y`). When you then move the joystick of your gamepad or press a key, you should see how the values are changing. This way you find out, which control is reflected in which data byte and how the message is to be decoded. Note that the Pico is only printing the data, it does not populate the MMBasic-specific gamepad variables in this test mode. 3) Based on that, one can then add decoding code to `USBKeyboard.c`. Of course, this cannot be a general solution - everyone adding their favorite gamepad. But maybe having one commonly used class of gamepads (e.g. XBox) in addition to PS3/PS4 would be useful for many. Best Thomas |
||||
karlelch Senior Member Joined: 30/10/2014 Location: GermanyPosts: 172 |
Burned out servo ... surgery required ... |
||||
Print this page |