I've been playing Assetto Corsa with my Xbox 360 controller. Unfortunately, steering with the analogue stick is very unwieldy, and I don't have the space for a wheel setup. I tried to think of ways I could shoehorn a better steering mechanism into the controller, when it occurred to me that I could use the whole controller as a steering wheel.
The analogue stick has two potentiometers. One measures vertical movement, and one measures horizontal movement. It puts 1.6V through each and measures the voltage produced at the wiper to determine how much the stick has moved. This means it's possible to control the stick movement by feeding a particular voltage to the wiper pin. (more information here: https://drstrangevolt.blogspot.com/2012/09/an-analog-hack-of-xbox-360-controller-part1.html)
This mod uses an Arduino to calculate the angle from accelerometer readings and convert it to analogue stick movement via a DAC. Therefore, it should work with any game that uses the analogue stick as input.
Tools:
Materials:
There are seven screws you have to remove. Six of them are obvious, but the seventh is behind a sticker. I assume removing it voids your warranty, so proceed at your own risk. A lot of guides say you need a Torx screwdriver, but mine are crosshead, so check your controller.
After that, carefully pry off the back cover. If you pry off the front the buttons will spill out and probably go all over the room. Lift it from the bottom. Then unplug the two vibration motors. (the one with the small weight should be on the left, and the one with the big weight on the right) Take the PCB out and remove the rubber caps on the analogue sticks. They simply pull off.
The next thing is to remove the left analogue stick so it doesn't interfere with our input, but the left trigger mechanism is in the way. In order to remove it, you have to desolder the three pins from the potentiometer from the front of the board, then unclip the mechanism from the PCB.
Next, desolder the 14 pins holding the left analogue stick. Then pull the stick off.
You'll notice there's quite a lot of clearance between the back of the PCB and the case. This makes it possible to put all the hardware in the case without removing anything.
I only realised later, but this would be a good time to desolder the reset button on the Arduino. If you don't, it'll press on the back of the case and cause the project to stop working if you tighten one of the screws too much when reassembling it.
I glued a thin piece of card to the back of each PCB to insulate it, then glued that to the controller's PCB. I was reluctant to use glue, but couldn't think of a better way to do it.
The positions in the image is the best combination I could find. The Arduino is on the left, with the edge with the reset button flush against the piece of plastic from the right trigger mechanism, with the other side under the wire and with the corner as close to the white connector as possible. There's a slight bulge in the case, but I couldn't find a better place to put it.
The accelerometer is to the right of the wire. It should be as flat and as straight as possible, otherwise you might have to write some code later to compensate for the offset. Note that there are some protruding pieces of plastic on the back of the case that you have to be careful to avoid. I've found that you can put something sticky and colourful, like lipstick, on the protruding pieces of plastic then put the back cover on to see where it leaves marks.
The DAC(s) go in the bottom left hand corner. There's enough clearance here to stack two DACs, one on top of the other, if you want to control both axes. You don't need to glue them down. They'll stay where they are with just the soldered connections. If you're sticking card between them make sure you cut the card so as to leave SCL, SDA, VCC and GND accessible, because you'll be accessing them from both sides.
If you do use two DACs, don't forget to switch the address jumper and disable the pull up resistors on one of them, as described here: https://learn.sparkfun.com/tutorials/mcp4725-digital-to-analog-converter-hookup-guide
Now you have to connect everything. VCC, GND, SDA and SCL from all 2/3 devices should be connected to VCC, GND, A4 and A5 on the Arduino, respectively. The DACs are the trickiest part. If you have two, you have to connect them together, while leaving somewhere you can connect the power and lines to the accelerometer, while keeping the OUT wires separate.
The OUT pin on the DAC should be connected to the pin on the controller's PCB that used to be for the middle horizontal potentiometer pin for the analogue stick. That is, where the analogue stick was, there's a row of three pins at the top. Connect it to the middle one. If you have another DAC connect it to the vertical potentiometer pin (the row to the left) in the same way. You won't be able to get to the pins from the back when the trigger is replaced, so you have to run a wire to the front of the board. There's a circular plastic "wall" around the analogue stick area, but fortunately there's a convenient gap in it that you can put wires through. Make sure the wires don't get in the way of the screw post on the front part of the case.
My original plan was to power the Arduino with the 5V from the USB cable connected to the RAW pin, but when I tried it, it didn't work. The Arduino didn't run anything, and both the Arduino and the controller turned off after a few seconds. However, I discovered that there's a steady 3.3V output from two pins on the front of the board near the black peripheral socket, presumably for powering peripherals. It works with both VCC and RAW, but I chose VCC because it's already the right voltage and because it allows me to solder it to the VCC wire on the DAC that's already near the bottom of the board and save on wires.
Be aware that there are a lot of plastic parts protruding from the case that you have to work around, but if you glue the wires in place, you only have to worry about them once.
All of this is difficult to describe with words, so I've included pictures and a crude diagram.
Now you have to program the Arduino. This requires moving the USB cable on the controller so you can access the serial pins on the Arduino. I've included the code I used. It requires the Adafruit MCP4725 library, which can be found here: https://github.com/adafruit/Adafruit_MCP4725
As is, the code allows you to go through the entire range of motion of the analogue stick evenly by moving the controller 90 degrees to the left to 90 degrees to the right, and keep it in the middle by holding it flat.
It gets the angle of the controller by calculating the inverse tangent of the X axis g-force divided by the Z axis g-force. This means it works if the controller is vertical, flat, or any angle in between. (more information here: https://www.digikey.com/en/articles/techzone/2011/may/using-an-accelerometer-for-inclination-sensing)
It works on my controller, but other controllers might require different voltages, putting it out of alignment. I think the best way to find the voltage range is with trial and error. Many games will show you a slider for the analogue stick movement, but the most accurate way I've found to determine movement is with jstest on Linux. (https://wiki.archlinux.org/index.php/Gamepad#Joystick_API) It gives you a number between -32,767 and 32,767 rather than a graphic, so you know exactly where the stick is. Plug in both the controller and the Arduino USB to serial adaptor, load jstest and try different DAC values until you reach the top and bottom of the range, and make a note of each. For me it was 1,593 - 382.
Of particular interest is line 36:
dacvalue = (controllerangle + 2.5617859169446084418) / 0.0025942135867793503208 + 0.5;
It's not immediately obvious what it does. Simply, it takes the angle of the controller (measured in radians and between ~1.57 and ~-1.57) and converts it to a value between 1,593 and 382 for the DAC. If you have a different DAC range, you will need to change that line.
The line can be written as:
dacvalue = (controllerangle + <S>) / <D> + 0.5;
With <S> and <D> being the numbers you need to change. <D> is equal to the range of the controller angle (pi) divided by the total range of DAC values. (the top of the range minus the bottom of the range) This gets you as far as changing the voltage, although the results will be outside the range you want. That's why you need <S>. <S> is equal to <D> multiplied by the bottom of the range plus half the range of motion of the controller. (pi / 2) Adding half the range of motion makes sure it's not a negative number, and adding <D> multiplied by the bottom of the range makes sure it's synchronised with the range you want.
When converting the decimals to an integer, C++ doesn't round. It instead cuts off the decimal, so 9.9 becomes 9. Adding 0.5 at the end makes sure anything above a half goes to the next integer, so it does round.
Once you've uploaded your program, make sure it works with jstest.
Put the controller back together in the same way you took it apart, minus the left analogue stick. It should work now. I find there's no noticeable delay and it's a lot better than using the analogue stick. Because it uses an accelerometer, it is affected by sudden movements, but you have to go out of your way to notice it.
There are some improvements that could be made. These include: