Pico Force Feedback Steering Wheel

by argentinamm86 in Circuits > Electronics

222 Views, 3 Favorites, 0 Comments

Pico Force Feedback Steering Wheel

assembled1.jpg

The goal of this project was to make a force feedback, configurable, USB steering wheel based on the Raspberry Pi pico. It was designed and assembled by 3 of my friends and I, and we made it as our end of year project for school on 2024.

Supplies

  1. 2 double sided copper boards
  2. 1kg PLA
  3. 1 LM358
  4. 2 Raspberry pi picos
  5. 12 Pulsadores smd
  6. 6 3mm leds
  7. 12 5050 white leds
  8. 5 4x1 molex connectors
  9. 6 3x1 molex connectors
  10. 4 2x1 molex connectors
  11. 2 5x2 IDC connectors
  12. 6A 12v power supply
  13. 3 IRLZ44N MOSFETs
  14. 3 BC547 SOT23 BJTs
  15. 2 DC motors (we took them from a broken drill)
  16. 5 Rotary Encoders
  17. 2 2 pin screw terminals
  18. 1 4 pin screw terminal
  19. 2 Limit switches
  20. 1 16x2 I2C LCD
  21. 12 M4 screws and nuts
  22. 14 M2.5 screws and heat inserts
  23. 1 metal rod that will be used as the axis
  24. A piece of wood
  25. Black spray paint
  26. Acrylic paints
  27. Spray lacquer
  28. An acrylic sheet
  29. Epoxy glue
  30. Soldering tools
  31. Pliers
  32. Snippers
  33. Hot glue
  34. Sandpaper, 60 and 120 grit
  35. Various 0603 resistors
  36. 100nF 0603 capacitors
  37. Wires and flat cable
  38. More general purpose tools (Screwdrivers, etc)
  39. Testing equipment for debugging (DSO, Multimeter, Lab PSU, AFG), they're optional, but nice to have if you have access to them

The Project

A brief note:

Something that will be a common thing during this whole instructable is odd choices, there are many things that could have been different, more efficient or easier. The reason for this is that, as this was a school project, we had some guidelines to follow, as well as some budget constraints, we didn't want this to be too expensive, so we used a lot of things that we already had and based the design on them.


Now, a more detailed explanation of what we wanted to make:

The idea was to build an f1 style steering wheel that could be recognized as an HID device by a computer, allowing the user to assign the axis and buttons to whatever function they'd like, as well as having some kind of force feedback which as you'll see, won't work as expected.


On the hardware side:

To register the movement of the wheel we chose to use an accelerometer rather than a potentiometer because we wanted to have more range of motion, less wear, and because we had to have a certain amount of sensors as per school requirements.

A 4 by 3 button matrix was used for the user inputs and for some of the wheel specific functions that we'll talk about in a bit, paired with 5 rotary encoders. 4 of them were user configurable, and one was supposed to control the brightness of the backlight, something that ended up not working for a reason we deemed not really important to investigate, as the deadline was approaching and we still needed to make the core functions work. And for the paddle shifters we used two limit switches and magnets to get a nice satisfying click.

For the force feedback we first decided to use a stepper motor, but after some testing we found that it wouldn't work, for reasons that are too long to explain without this becoming really boring, maybe I'll make a more in-depth instructable with all of our mistakes, why they happened and how we fixed them. But the important thing is that we settled for a two dc motor setup, coupled with a set of reduction gears to achieve the maximum possible torque without giving up too much speed. The reason behind the two motors was that changing the motor direction as fast as we needed to would be bad for the motor, so one motor for each direction. We also used a shunt resistor to measure the current used by the motors to get an idea of how much torque was being produced, also to cap the current draw to something safe for our power supply and control circuits.

The display was meant to display game data, like rpms, time deltas, brake bias, and all those fun things, but we ran out of time to code that, and using an lcd to display that much things isn't really convenient, so in the end we made it display the angle reading calculated from the accelerometer, which button was pressed, and the 3 extra functions of which I'll talk as soon as I finish talking about the hardware part of this.

We used some leds that were supposed to display game data as well, in the end they were used to show the user if a function was activated or deactivated.

And lastly, we had to use two pi's because one didn't have enough GPIO to handle all of the hardware. One was inside the wheel, controlling the user inputs (except the limit switches, but I'll get into that in the code section because it's kind of strange), the commands being sent to the computer the display and the leds. The other one handled the current measuring, torque calculation, led brightness and motor control.


Software side:

The code for this can be split into 4 main sections, the first one is the "normal" mode, in which the inputs made by the user were sent to the computer, and the display showed the angle and what buttons were pressed. The other 3 modes were considered extra functions:

Calibration mode: The whole purpose of this mode was to zero out the wheel angle, you entered this mode pressing one of the buttons for 5 seconds, the leds blinked and the display showed a text telling you you entered this mode. To set the zero all the you had to do was put the wheel in the neutral position, and exit calibration mode. To leave calibration mode you had to press the same button used for entering for 5 seconds, the leds blinked, and the new zero was set.

Configuration mode: This mode was supposed to let you pick how much force the motors generated by modifying the current target. Entering this mode was done in the same way as entering calibration mode, and in the same way that was used to enter memory mode. The display showed the target, and to change it you had to use one of the encoders. The new target would be used as soon as you exited configuration mode.

Memory mode: This is a tricky one because we also ran out of time to code it, but it was supposed to let you tell a program on the computer to start and stop logging the position data and what buttons were pressed so that later you could select the option to replay the data. To do so the desktop app would send the logs to one of the pico's, the pico ignored all new user inputs (except the one to stop replaying data), all it did was move the wheel to whatever position the app sent and send the command for a button press whenever necessary. This mode was supposed to be used for the project presentation, because it was about 5 hours and we didn't want to be playing for 5 hours straight to show people how it worked, the idea was to have it replay data and basically play on its own while we explained everything and answered questions.

The communication between the pi's was done via UART, and the communication with the PC was done both by USB using the TinyUSB library, and another UART for the data logging.

Last but not least, we made the app to log the data, and to also show the angle of the wheel and the buttons pressed via console. This last part was purely a requirement made by our school, so we didn't put the focus on it and we ended up not using it for the exhibition.

I'll go more in depth on the logic in the code part, but everything was programmed in C.

3D Model and 3D Print

Screenshot 2025-03-11 183613.png
v1.17.jpg
v1.18.jpg
v1.19.jpg
v1.20.jpg
v1.21.jpg

For the 3d model we took inspiration and measurements from the Sim Lab Mercedes-AMG wheel, we downloaded some images, imported them into AutoCad, scaled it so that the radius matched the one mentioned on the website, drew the general outline, and then made adjustments to fit our own design requirements, like the screw holes, and the nut holders. After the drawing was done we moved to Rhino8 to make the 3d model, getting the wheel to be both accurate to what we wanted it to look like and to be suitable for 3d printed was very hard, but to make this part not too long, we extruded the outer surface, we changed it from a polysurface to a Sub-D in order to have a more ergonomic shape, and not one full of ugly fillets or sharp edges, then we made the inside hollow with the shell tool, lastly we made the holes for the buttons, and added the nut holders, screw holders, encoder holders, and the display cover, this part was made separately because we couldn't get it to stay as a solid polysurface if we made it directly on the wheel, plus it would later be useful to hide the glue marks. The model was cut into 4 main pieces, 2 halves for the back and 2 for the front in order to be able to print it with our 3d printer. Then there were some other parts, like the paddle shifters, the display cover, the axis and motor holders, and lastly the gears.

All the renders you can see here were made on keyshot 11.

Everything was printed on a Prusa i3-MK2, with regular PLA and 5% infill except the gears that were printed with 30% infill, as they needed to bear a lot of forces. The only parts printed with supports were the main body parts, and they were a pain to remove.

I've uploaded everything to printables, but i do not recommend printing the motor parts, and please read the final thoughts, as there i explain an error we made on the design.

Post Processing

after sand.jpg
Pre sand.jpg

After the main body parts were printed, and the supports were removed, we added heat inserts to hold the pcb, and the nuts to hold the two parts together. We also wanted to get a smooth surface on everything a user could touch, so we did a lot of sanding, starting with 60 grit to remove the most uneven layer lines on the overhangs, and then 120 grit to finish. Next we glued the two front parts and the display cover together using epoxy based glue, and painted the body black with spray paint, and added a protective layer of lacquer. And painted the buttons with acrylic paint, plus a layer of lacquer for a shiny look.

Lastly, the buttons didn't fit comfortably in the holes they were supposed to go into, so we made the holes bigger.

Circuits!

This part wasn't necessarily hard, we just made a lot of mistakes along the way, the biggest ones were getting footprints wrong, and not checking that our amplifier circuit could send 12v straight into the analog pin on the pico, which resulted in a burnt pico, and with a pcb in the trash.

We made 4 pcbs, 3 for the main body, and one for the motor, MPU, limit switches, and current sensing.

The main board held the pico that handled the comms with the PC, the I2C header for the display, the connectors for the peripheral boards and encoders, the connector for communication with the motor board, the backlight leds, the indicator leds, and 6 of the buttons, with their respective debouncing capacitors.

The peripheral boards contained 3 buttons each, plus two backlight leds and debouncing capacitors, as well as a connector for the main board. We made them this way because otherwise they wouldn't have been able to fit inside the wheel.

The motor board housed the 2nd pico, the shunt resistor, the amplifier for the current sensing that converted the ~1.41Vpeak to 3.3Vpeak to get a more accurate reading with the pico's ADC, screw terminals for the motors and power supply, the connectors for the MPU and limit switches, the connector for the main board, the MOSFETs and BJTs for the motors and leds, and a last minute hacked on snubber network.

Every board was deigned with Altium, and as a requirement, we had to make them ourselves, so that keeps 4 layers out of the picture, both the main board and motor board were 2 layer pcbs, and the peripheral boards were 1 layer pcbs.

Coding

Coding.jpg

Ok, so this is the hardest part to explain for a lot of reasons, but mainly because it's a LOT of code, like way too much, but I'll try to make it quick.

First let's start with the main board code:

To make everything more manageable we separated functions into files, and then called them inside the main file. A lot of the functions were really standard code, like reading the matrix or the encoders, so i won't get too much into them, there's a lot of great tutorials that explain this in detail already (reading encoders, reading matrices). Something that isn't too standard is getting the angle of the wheel using the accelerometer, for that we use an equation that takes into account all the different accelerations, and gives us an estimate of the angle, it uses some constants and trig functions.

For the display we used I2C, nothing too weird, apart from the fact that we didn't have a library available to us, so we used the functions from the pico example, and then adapted all of our integers to strings so that they could be sent to the display with not much of a problem.

For the different operation modes we just stored the time the button was pressed, and the time when it was released, calculated the total pressed time, and if it was longer than 5 seconds, went into that specific mode, changing some variables to make the LCD display the correct data.

And probably the most difficult parts were the UART communication with the other pico, because it had to be fast, otherwise it could either block a user input from being registered, or sent wrong data to the other board, making its calculations wrong. To solve this we just made it transmit the data every 1mS.

Lastly, all we did was send the button states as a 32 bit unsigned int to the TinyUSB library, paired with the angle of the wheel mapped to a signed 16 bit int, and the library handled all the USB protocol.

All of this was massively oversimplified, if you want a more in depth understanding of the code and what does what I highly encourage you to read through it in the github repo //hyperlink. I made sure to ask github copilot to comment everything in a more understandable manner, as all of the old comments were made in a rush, while I still had the code fresh in my mind.


Now for the motor board the code was simpler, as it had to do less than the main board, it consists of:

A calculation of the current the motor is using based on the voltage reading on the resistor, simple as the current on a resistor is linear, so it isn't necessary to include the gain of the amplifier.

A PID controller that sets the duty cycle needed to reach the target current on each motor.

A simple if statement that takes the angle of the wheel and activates the motor that tries to move it in the opposite direction.

And lastly, it reads the limit switches and sends the state of them via UART as soon as they change.

Again, I skipped a lot of details and explanations, if you want me to explain more of the code feel free to ask and I'll make an instructable explaining it, or I'll explain it on the github repos.

Final Assembly

last days assembly.jpg
maqueta.jpg
assembled pcb.jpg
assembled pcb1.jpg
assembled pcb2.jpg
assembled pcb3.jpg
assembled.jpg

This is the most chaotic part of every project, when you try to integrate everything together and new problems arise. As you can see in the photos everything was a mess, because at this point we were a couple of days away from the deadline, we had to get everything working perfectly, and then prepare for the exhibition which was the day after the professors evaluated our work. We weren't even done with the code, so 2 of us were working on the code, while the other 2 were working on assembling, safe to say we kept working until around 3 AM, when we finally got the code to work we uploaded it to the pico, tested it one last time, and closed the wheel, after that uploading new code or touching anything on the inside would have been really hard. We screwed everything shut, secured the wheel to the axis, secured the gears, and put everything on a random piece of wood we had laying around, then we covered it with a piece of clear acrylic because while testing, one of the gears came flying out of the motor, and we didn't want a piece of plastic hitting someone if that happened again in the exhibition. When we had everything pretty much ready to go we went to sleep, the next day we took everything to school and waited for our turn to show the final thing to the professors. Once that was done, we corrected everything that we were told were problems, and celebrated that we were finally done.

Exhibition

expo1.jpg
expo2.jpg
expo3.jpg
2024 project

This is by far one of the best moments of every end of year project at our school, it's one day in which everyone shows their projects and people from anywhere can come in and look at what we made, ask questions and have a little fun.

This day we don't have to attend any classes, we come in after lunch and set up our stand, as you get to the last years you get more space, more time, and a nicer stand, because the projects are chosen by the students rather than proposed by the professors as in the first years of school.

For our stand we set up Forza Horizon on one laptop, a video showing the whole making of the project in the other laptop, we made some flyers highlighting the most important features, and we made an instruction manual that people could read scanning the QR code on the table or on the wheel.

As expected, a lot of people wanted to use the wheel, so there were some broken parts here and there, but nothing that couldn't get fixed on site. It was a really fun day, we got to explain a lot of what we do to students that were looking to get into our school, as well as meeting some alumnus that knew what we were talking in more technical terms, and people that just wanted to look at what we did but didn't have anything to do with the school

Final Thoughts: What Went Wrong

There were a lot of things we didn't like about the final product, but the worst ones were that first the motor did not work, because our power supply couldn't handle enough current, the motor used a lot more than what we anticipated, and we didn't test the motor until the day of the evaluation, so we couldn't change anything by then. Then another thing is that we didn't put any screws in the middle part, so if you looked at the wheel from above or below you could see all the cables which wasn't nice. But now we know better how to approach this year's project.