Physical Computing Final – Racing Simulator With Adafruit Bluefruit

by jackmcclelland1 in Circuits > Electronics

160 Views, 0 Favorites, 0 Comments

Physical Computing Final – Racing Simulator With Adafruit Bluefruit

Physical Computing Final – Racing Simulator with Adafruit Bluefruit
Screen Shot 2022-05-13 at 10.49.21 AM.png

Hi! My name's Jack and I'm a senior at Boston College studying Computer Science and Finance. This semester, I'm taking Physical Computing with Professor Gallaugher. We've been following a flipped classroom model and I made this project utilizing Professor Gallaugher's Circuit Python YouTube tutorials.

For my Final Project, I made an auto racing simulator that lights up different colors as you turn a steering wheel, and gives you the option to press a button that controls a speedometer (which plays sounds as you get to the minimum and maximum speed).

This project utilizes an Adafruit Circuit Playground Bluefruit running CircuitPython as well as a 30-pixel LED light strip to display colors and augment the lights that are already flashing on the Bluefruit's LED pixel display. There's a servo as well that is controlled by an external button, and a speaker that's connected to the Bluefruit. Most of the wiring is connected via alligator clips. Finally, the Arduino is connected to an external power bank so that the setup can run on its own power (standalone, doesn't need to be connected to a laptop).

I wanted to build this project because I'm a huge fan of Formula 1. In my projects for the course so far it's been fun to build projects that overlap with my passions outside of school, and this is an example of that. The drivers in Formula 1 all practice on racing simulators because it's a very easy sport to simulate (you can simulate almost everything about racing conditions except maybe the g-forces). I thought it would be cool to build a simulator where when you turn a wheel, you get some visual / audio feedback. I wanted to push myself on the coding part a little bit and run multiple sensors and outputs at the same time. In the end, the project involved code to control the pixel display on the CPB and the LED light strip using the CPB's accelerometer as well as the servo and speaker based on button clicks.

If you have any questions, don't hesitate to reach out and I'll try my best to answer – I'm jack.m.mcclelland@gmail.com.


Supplies

materials.JPG

All materials:

  • Circuits and wiring materials:
  • Adafruit Circuit Playground Bluefruit (CPB), you can swap this out for other similar boards
  • External button (try to get something that's big and easy to press)
  • Servo (if you can get one where the arm is detachable that's fantastic)
  • LED light strip (the one shown here has 30 LEDs, which has implications for calculations in the code)
  • Speaker with aux out cord (you'll clip alligator clips to the tip and sleeve)
  • Alligator clips (2 for the button, 2 for the speaker, and potentially 3 for the servo)
  • External battery pack or power bank
  • USB 2.0 to Micro USB cable to connect external battery pack to Arduino
  • Any other alligator clips or wires you need to connect all the above
  • Frame and building materials:
  • Frame, steering wheel, and speedometer needle are cut from 1/8" dark blue acryllic
  • Tape is used to secure elements together

Lasercut and Assemble the Frame ✂️

Illustrator.png

First, design and lasercut the materials for the frame. I designed this in Adobe Illustrator and went to my university's Maker Space to cut this out (they were super nice and helpful).

The frame here is made out of 1/8" dark purple acrylic. If I were to do this project again I would make sure the etchings are visible. You can't really tell in the picture or videos but I tried to engrave a heads-up display to make the project look a little cooler (it looks like a few dials / speedometers next to each other). I also engraved some text including my name on the bottom right that you also can't really see. The acrylic is so hard that it's hard to see this show up, and also I think the engrave settings on the lasercutter were a bit off. That's okay though – this wasn't the main point of the project.

Make sure you have a hole in the middle that you can use to run wires to the back from the steering wheel, and also attach an axle that you can mount the steering wheel on (I was going to get to this but didn't have enough time unfortunately). Also make sure you have a hole for the speedometer needle, and holes for any buttons you want to add at the bottom. Finally, there should also be a hole for the speaker. Remember to cut out the steering wheel and speedometer needle of course!

Once you have the frame all good to go, it's time to write the code that will run on the circuitry!

Contact me (email at the top and bottom of this Instructable) if you'd like for me to send you the Adobe Illustrator file I used to cut this design!

Write the Code 💻

Code.png

Now it's time to write the CircuitPython code that will run on the Arduino Nano RP2040.

Let's briefly walk through it line-by-line:

First, add your import statements for the libraries you'll need for the project.

import board, neopixel, time, random, digitalio, adafruit_lis3dh, busio, pwmio
from rainbowio import colorwheel
from audiopwmio import PWMAudioOut as AudioOut
from audiocore import WaveFile
from adafruit_motor import servo
from adafruit_debouncer import Debouncer

Next, set up your colors:

from adafruit_led_animation.color import (AMBER, AQUA, BLACK, BLUE, CYAN, GOLD, GREEN,
    JADE, MAGENTA, OLD_LACE, ORANGE, PINK, PURPLE, RED, TEAL, WHITE, YELLOW, RAINBOW)

Now, set up your pixels (on the CPB) and strip (for the external LED strip):

pixels_pin = board.NEOPIXEL
lights_on_pixels = 10
pixels = neopixel.NeoPixel(pixels_pin, lights_on_pixels, brightness = 0.5, auto_write = True)

strip_pin = board.A1
lights_on_strip = 30
strip = neopixel.NeoPixel(strip_pin, lights_on_strip, brightness=0.5, auto_write=True)

Set up your accelerometer:

i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
accelerometer = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19, int1=int1)
accelerometer.range = adafruit_lis3dh.RANGE_8_G

Set up your servo:

pwm = pwmio.PWMOut(board.A2, frequency=50)
servo_1 = servo.Servo(pwm, max_pulse = 2500)

Set up your button:

button_speed_input = digitalio.DigitalInOut(board.A4)
button_speed_input.switch_to_input(pull = digitalio.Pull.UP)
button_speed = Debouncer(button_speed_input)

Set up your speaker:

speaker = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
speaker.direction = digitalio.Direction.OUTPUT
speaker.value = True
audio = AudioOut(board.SPEAKER)

Now create a sound function that will play sound to your speaker when audio files are passed in:

path = "sounds/"
def play_sound(filename):
    with open(path + filename, "rb") as wave_file:
        wave = WaveFile(wave_file)
        audio.play(wave)
        while audio.playing:
            pass

Remember to change the first line there (path = "sounds/") if your sounds are located in a folder that is not called sounds (adjust accordingly). Next, outside of your while True loop add some miscellaneous setup code:

pixels.fill(BLACK)
servo_1.angle = 180
servoangle = 180
servo_direction = -1
last_sound = 2

Here, a few things are happening:

  1. You're starting by turning all lights off
  2. Then you're resetting the servo from whatever angle it was at to 180 degrees, far left if it's facing up
  3. We initialize a separate variable called servoangle, see how it works in the main loop below
  4. Servo direction is another important variable to keep track of (for switching direction once at end)
  5. Finally, keep track of the last sound played, we have two different sounds to play on the speaker

Now that everything's set up, let's get to the main part of the code, explained below:

while True:
    x, y, z = accelerometer.acceleration
    # print((x, y, z))
    time.sleep(0.1)
    angle = (x + 9) * 1.67
    if angle < 0:
        angle = 0
    if angle > 30:
        angle = 30
    color = round(angle * (255/30))
    # print(color)
    strip.fill(colorwheel(color))
    pixels.fill(colorwheel(color))


    button_speed.update()
    if button_speed.fell:
        if servoangle < 0:
            servoangle = 0
        if servoangle > 180:
            servoangle = 180


        servoangle = servoangle + (servo_direction * 45)
        if servoangle == 0 or servoangle == 180:
            servo_direction = servo_direction * -1
            print("Reversing direction")
            if last_sound == 1:
                play_sound("2.wav")
                last_sound = 2
            else:
                play_sound("1.wav")
                last_sound = 1


        print("Moving from {} to {}".format(servo_1.angle, servoangle))
        servo_1.angle = servoangle

while True runs everything in this loop during the program's execution in perpetuity. A few things are happening:

  • We start by checking that the accelerometer readings are working, make an "angle" variable that takes in the x axis part of the accelerometer data (for detecting when the wheel is turned). The math is set because there are 30 strips on the LED light / 180 degrees, and 30 / 180 = 0.16666667, so that's where the 1.67 comes from, and the readings are mainly from -9 to 9 so that's where that part comes from. For the color part, the max value of a hex code is 255 and there are 30 LEDs on the light strip so that's where the 30 / 255 comes from. Then fill the pixels on the CPB and the lights on the strip with the color value.
  • When the button is pressed, see if the servo angle exceeds 180 or drops below 0 (the angle's a bit glitchy sometimes so this is important to make sure it doesn't crash). I wanted each button press to increase / decrease the speedometer in noticeable amounts, so maybe 4 button presses pushes the needle all the way one way (108 / 4 = 45 degrees, so that's where that part comes from). There's a bit of code to detect if you're at one side, in which case it reverses direction.
  • Finally, a print statement for debugging and angle setting.

Let me know if you have any questions about the code! Here it is in full:

# IMPORTS SETUP


# Standard imports
import board, neopixel, time, random, digitalio, adafruit_lis3dh, busio, pwmio
# For lights and colors
from rainbowio import colorwheel
# For playing sound
from audiopwmio import PWMAudioOut as AudioOut
from audiocore import WaveFile
# For servos
from adafruit_motor import servo
# For buttons
from adafruit_debouncer import Debouncer


# LIGHTS SETUP


# Colors setup
from adafruit_led_animation.color import (
    AMBER,
    AQUA,
    BLACK,
    BLUE,
    CYAN,
    GOLD,
    GREEN,
    JADE,
    MAGENTA,
    OLD_LACE,
    ORANGE,
    PINK,
    PURPLE,
    RED,
    TEAL,
    WHITE,
    YELLOW,
    RAINBOW
)


# Pixels setup
pixels_pin = board.NEOPIXEL
lights_on_pixels = 10
pixels = neopixel.NeoPixel(pixels_pin, lights_on_pixels, brightness = 0.5, auto_write = True)


# Strip setup
strip_pin = board.A1
lights_on_strip = 30
strip = neopixel.NeoPixel(strip_pin, lights_on_strip, brightness=0.5, auto_write=True)


# ACCELEROMETER SETUP
i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
accelerometer = adafruit_lis3dh.LIS3DH_I2C(i2c, address=0x19, int1=int1)
accelerometer.range = adafruit_lis3dh.RANGE_8_G


# SERVO SETUP
pwm = pwmio.PWMOut(board.A2, frequency=50)
servo_1 = servo.Servo(pwm, max_pulse = 2500)


# BUTTON SETUP
button_speed_input = digitalio.DigitalInOut(board.A4)
button_speed_input.switch_to_input(pull = digitalio.Pull.UP)
button_speed = Debouncer(button_speed_input)


# SOUND SETUP
# Speaker setup
speaker = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
speaker.direction = digitalio.Direction.OUTPUT
speaker.value = True
audio = AudioOut(board.SPEAKER)


# Playing sound
path = "sounds/"
def play_sound(filename):
    with open(path + filename, "rb") as wave_file:
        wave = WaveFile(wave_file)
        audio.play(wave)
        while audio.playing:
            pass


# Done with setup, here's the code:


pixels.fill(BLACK)
servo_1.angle = 180
servoangle = 180
servo_direction = -1
last_sound = 2


while True:
    x, y, z = accelerometer.acceleration
    # print((x, y, z))
    time.sleep(0.1)
    angle = (x + 9) * 1.67
    if angle < 0:
        angle = 0
    if angle > 30:
        angle = 30
    color = round(angle * (255/30))
    # print(color)
    strip.fill(colorwheel(color))
    pixels.fill(colorwheel(color))


    button_speed.update()
    if button_speed.fell:
        if servoangle < 0:
            servoangle = 0
        if servoangle > 180:
            servoangle = 180


        servoangle = servoangle + (servo_direction * 45)
        if servoangle == 0 or servoangle == 180:
            servo_direction = servo_direction * -1
            print("Reversing direction")
            if last_sound == 1:
                play_sound("2.wav")
                last_sound = 2
            else:
                play_sound("1.wav")
                last_sound = 1


        print("Moving from {} to {}".format(servo_1.angle, servoangle))
        servo_1.angle = servoangle

Downloads

Assemble the Circuitry ⚡️

1 (lights).JPG
2 (button).JPG
3 (pot).JPG
4 (pot needle).JPG
5 (speaker).JPG

Time to assemble the circuitry!

First, connect the CPB to the LED light strip, which should come in with its own built-in alligator clips (if not, use your own). These should be connected to power, ground, and A1.

Next, hook up your button. You'll need alligator clips for this. This should be connected to SCL / A4 or SDA / A5.

Then, connect your servo. This should be hooked up to power, ground, and A2. I used a triple alligator clip that connected to the servo wiring quite nicely and made it easy to clip to the CPB. At this point if you want you can pull the fulcrum off the end of the servo, slide the servo into the hole you made for the speedometer needle in the frame, and reattach the fulcrum then tape the needle to it.

Next, connect the speaker. Connect the tip and sleeve of the audio out jack to alligator clips (audio / power and ground on the board respectively).

Tape everything together! Tape the button to one of the holes, tape the servo to the frame, and tape the speaker into the hole you made for that too. If you have an axle to attach the steering wheel to that you can slide through the hole in the board for this then now is the time to do so.

Finally, use the USB to Micro USB cable to plug the Arduino into a portable charger so your setup has power and can run. That's the last thing you have to do! Start turning the wheel and pressing the button to make sure everything works and you don't need to adjust the placement / taping of anything.

All Done! Congrats 🎉

Walkthrough 2 &ndash;&amp;nbsp;Racing Sim (PhysComp Final)

With that, you're finished with this project! Congrats. Again, let me know if you have any questions at jack.m.mcclelland@gmail.com.

Happy building!