07: Electronic output devices
Week 7 MVP for CubeControl: prove the scary part early. Physical cube enclosure, real sensor input, output you can see on screen. This became the path to our final Rubik's cube controller.
Assignment requirements
- Input + output + housing: IMU on a 3D-printed cube shell, game on my laptop as the output.
- Firmware: Adafruit MPU6050 sketch, read sensor, filter roll, print to Serial.
- Oscilloscope: probe the input side (IMU / I2C) since you can't clip a probe onto the game.
Goal
Test whether orientation from a cube-mounted IMU could drive something on my Mac by streaming values instead of just staring at Serial Monitor. I used Asphalt 9 as the output. Pitch, yaw, and roll from the cube were supposed to map to steering on screen. Was gonna use that as a basis for extending CubeControl later.
Steps taken
- 3D print a Rubik's cube shell for the electronics. I printed a really bad cube at first. Started with models online and tried to reverse engineer them. Timeline below.
Timelapse of the Rubik's cube MVP shell design and printing.
- Mount IMU + microcontroller inside the cube so tilt matches how you hold it.
- Firmware: Adafruit MPU6050 demo, complementary filter on roll, accel Y/Z over Serial.
- Host side: kept Serial Monitor closed, ran Python to read every line and map it to game input.
- Validate on screen: car in Asphalt 9 tracks cube orientation. Output this week was the display + game, not a motor.
- Oscilloscope: probed the input path, not the output, to see update rate and if anything runs on a fixed clock.

Firmware (MPU6050, Adafruit)
Started from the Adafruit MPU6050 basic demo. Sensor on I2C, complementary filter to blend accelerometer roll with gyro so it doesn't drift as bad. Printed accel Y/Z and filtered roll on Serial. That's what Python read.
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
Adafruit_MPU6050 mpu;
float roll = 0;
unsigned long lastTime = 0;
void setup(void) {
Serial.begin(115200);
if (!mpu.begin()) { /* halt */ }
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setGyroRange(MPU6050_RANGE_500_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
lastTime = micros();
}
void loop() {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
unsigned long now = micros();
float dt = (now - lastTime) / 1000000.0;
lastTime = now;
float accelRoll = atan2(a.acceleration.y, a.acceleration.z) * 180.0 / PI;
float gyroRollRate = g.gyro.x * 180.0 / PI;
float gyroRoll = roll + gyroRollRate * dt;
roll = 0.98 * gyroRoll + 0.02 * accelRoll;
Serial.print("Accel Roll: ");
Serial.print(accelRoll);
Serial.print(" | Filtered Roll: ");
Serial.println(roll);
Serial.print(a.acceleration.y);
Serial.print(" ");
Serial.println(a.acceleration.z);
}Bring-up used a small delay(10) in the loop while tuning. Later on hall-effect code I moved toward millis() instead of blocking delay.
Python streaming to Asphalt 9 (my childhood game)
Didn't leave Serial Monitor open, only one thing can own the port. Python read lines and turned roll / accel into keyboard input for Asphalt 9 on my Mac. Don't have that exact script saved anymore. Flow was always: read line, parse numbers, if past a threshold send left/right or whatever we mapped that day.
import math
import serial
PORT = "/dev/tty.usbmodem1101"
ser = serial.Serial(PORT, 115200, timeout=1)
while True:
line = ser.readline().decode(errors="ignore").strip()
if not line:
continue
if "Filtered Roll:" in line:
roll = float(line.split("Filtered Roll:")[1].strip())
else:
parts = line.split()
if len(parts) != 2:
continue
y, z = map(float, parts)
roll = math.degrees(math.atan2(y, z))
if roll > 15:
press_key("right")
elif roll < -15:
press_key("left")
else:
release_keys()When it worked, tilting the cube steered the car. Output for this week was the game reactin instead of a motor.
What tilt actually did
Mostly used roll for steering. Accel Y/Z was a raw check against the filtered value. Full pitch/yaw fusion would be next; for MVP, roll plus watching accel when I twisted the cube was enough to see the pipeline work.
Gripping the cube crooked couples axes. Same lesson as the final build, sensor has to sit right relative to the magnets and faces.
Link to final project
This week proved the cube could be an input and the computer could listen. CubeControl could use swipe sensing and maybe still include the IMU inside later.