Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Bastiaan Slee
Published © GPL3+

Arduboy Projector

Cover your wall with an Arduboy game!

IntermediateFull instructions provided10 hours5,883

Things used in this project

Hardware components

Arduboy Compatible
Arduboy Compatible
×1
Arduino Micro
Arduino Micro
×1
Aldis Slide Projector
×1
Magnetometer, Three Axis
Magnetometer, Three Axis
Magnetometer for the Steering Wheel Controller
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
Various tactile switches for both controllers
×1
LED (generic)
LED (generic)
RGB 5mm and Blue 3.5mm for both controllers
×1

Software apps and online services

Arduino IDE
Arduino IDE
Programming environment for Arduino
Fusion
Autodesk Fusion
3D Design the cases
Autodesk Eagle
Designing the PCB

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Snapmaker 2.0 A350
3D printer (printing cases) and CNC router (creating the PCB)

Story

Read more

Custom parts and enclosures

Base Station 3D model

Autodesk Fusion 360 design file

Steering Wheel Controller 3D model

Autodesk Fusion 360 design file

Slide Holder 3D model

Autodesk Fusion 360 design file

Power Block 3D model

Autodesk Fusion 360 design file

Schematics

Base Station PCB board design

Autodesk Eagle design file

Base Station PCB schematics

Autodesk Eagle design file

Code

Steering Wheel Controller

Arduino
This code will use a magnetometer to "sense" if you are turning the wheel left or right. It will then virtually press the buttons for Arduboy control
#include <Wire.h>    // Library for I2C communication
#include <LSM303.h>  // Library for the compass

LSM303 compass;
LSM303::vector<int16_t> neutral_min = {32767, 32767, 32767}, neutral_max = {-32768, -32768, -32768};
LSM303::vector<int16_t> left_min = {32767, 32767, 32767}, left_max = {-32768, -32768, -32768};
LSM303::vector<int16_t> right_min = {32767, 32767, 32767}, right_max = {-32768, -32768, -32768};
char report[80];

const byte LED_right = 9; // For testing: the RX LED has defined Arduino pin 17
const byte LED_left = 10;  // For testing: the TX LED has defined Arduino pin 30
const byte OUTPUT_right = 18;  // A0
const byte OUTPUT_left = 23;  // A5

int LastPos = 0;    // -1 = LEFT   0 = NEUTRAL   1 = RIGHT
int CurrPos = 0;    // -1 = LEFT   0 = NEUTRAL   1 = RIGHT
int LastPosTimer = 0;   // Timer for LastPos

int NrOfCalibrationSamples = 512;
int CalibrationWidening = 50;
int TimeOutForButtonReset = 10;
int TimeOutForLedReset = 50;
int TimeOutForTimerReset = 100;


void setup()
{
  Serial.begin(9600);
  //while(!Serial);

  // Set the LED pins to OUTPUT
  pinMode(LED_right, OUTPUT);
  pinMode(LED_left, OUTPUT);

  // Define the BUTTON pins, we do a trick :)
  // OUTPUT defaults to LOW, which is a button press
  // INPUT defaults to floating state, which is covered by the pullup on the Arduboy side
  pinMode(OUTPUT_right, INPUT);   // do not press the button anymore
  pinMode(OUTPUT_left, INPUT);   // do not press the button anymore


  Wire.begin();  // Arduino Micro default I2C pins: SDA = 2 and SCL = 3
  compass.init();
  compass.enableDefault();

  delay(200);



  // CALIBRATE NEUTRAL

  // blink both LEDs to alert for calibration
  for (int i=0; i<=5; i++){
    digitalWrite(LED_left, HIGH);   // turn the LED on
    digitalWrite(LED_right, HIGH);   // turn the LED on
    delay(100);
    digitalWrite(LED_left, LOW);    // turn the LED off
    digitalWrite(LED_right, LOW);    // turn the LED off
    delay(100);
  }

  // Steady both LEDs while calibrating
  digitalWrite(LED_left, HIGH);   // turn the LED on
  digitalWrite(LED_right, HIGH);   // turn the LED on

  // Get standard neutral position
  for (int i=0; i <= NrOfCalibrationSamples; i++){
    compass.read();
    
    neutral_min.x = min(neutral_min.x, compass.m.x);
    neutral_min.y = min(neutral_min.y, compass.m.y);
    neutral_min.z = min(neutral_min.z, compass.m.z);
    
    neutral_max.x = max(neutral_max.x, compass.m.x);
    neutral_max.y = max(neutral_max.y, compass.m.y);
    neutral_max.z = max(neutral_max.z, compass.m.z);
    
    delay(10);
  }

  // Off for both LEDs as calibration is complete
  digitalWrite(LED_left, LOW);    // turn the LED off
  digitalWrite(LED_right, LOW);    // turn the LED off

  // Update the readings with a little extra range
  neutral_min.x = neutral_min.x - CalibrationWidening;
  neutral_min.y = neutral_min.y - CalibrationWidening;
  neutral_min.z = neutral_min.z - CalibrationWidening;
  neutral_max.x = neutral_max.x + CalibrationWidening;
  neutral_max.y = neutral_max.y + CalibrationWidening;
  neutral_max.z = neutral_max.z + CalibrationWidening;

  snprintf(report, sizeof(report), "NEUTRAL = min: {%+6d, %+6d, %+6d}    max: {%+6d, %+6d, %+6d}",
    neutral_min.x, neutral_min.y, neutral_min.z,
    neutral_max.x, neutral_max.y, neutral_max.z);
  Serial.println(report);

  delay(300);




  // CALIBRATE LEFT

  // blink LEFT LED to alert for calibration
  for (int i=0; i<=5; i++){
    digitalWrite(LED_left, HIGH);   // turn the LED on
    delay(100);
    digitalWrite(LED_left, LOW);    // turn the LED off
    delay(100);
  }

  // Steady LEFT LED while calibrating
  digitalWrite(LED_left, HIGH);   // turn the LED on

  // Get standard left position
  for (int i=0; i <= NrOfCalibrationSamples; i++){
    compass.read();
    
    left_min.x = min(left_min.x, compass.m.x);
    left_min.y = min(left_min.y, compass.m.y);
    left_min.z = min(left_min.z, compass.m.z);
    
    left_max.x = max(left_max.x, compass.m.x);
    left_max.y = max(left_max.y, compass.m.y);
    left_max.z = max(left_max.z, compass.m.z);
    
    delay(10);
  }

  // turn off LEFT LED as calibration is complete
  digitalWrite(LED_left, LOW);    // turn the LED off

  // Update the readings with a little extra range
  left_min.x = left_min.x - CalibrationWidening;
  left_min.y = left_min.y - CalibrationWidening;
  left_min.z = left_min.z - CalibrationWidening;
  left_max.x = left_max.x + CalibrationWidening;
  left_max.y = left_max.y + CalibrationWidening;
  left_max.z = left_max.z + CalibrationWidening;

  snprintf(report, sizeof(report), "LEFT = min: {%+6d, %+6d, %+6d}    max: {%+6d, %+6d, %+6d}",
    left_min.x, left_min.y, left_min.z,
    left_max.x, left_max.y, left_max.z);
  Serial.println(report);

  delay(300);



  // CALIBRATE RIGHT

  // blink RIGHT LED to alert for calibration
  for (int i=0; i<=5; i++){
    digitalWrite(LED_right, HIGH);   // turn the LED on
    delay(100);
    digitalWrite(LED_right, LOW);    // turn the LED off
    delay(100);
  }

  // Steady RIGHT LED while calibrating
  digitalWrite(LED_right, HIGH);   // turn the LED on

  // Get standard right position
  for (int i=0; i <= NrOfCalibrationSamples; i++){
    compass.read();
    
    right_min.x = min(right_min.x, compass.m.x);
    right_min.y = min(right_min.y, compass.m.y);
    right_min.z = min(right_min.z, compass.m.z);
    
    right_max.x = max(right_max.x, compass.m.x);
    right_max.y = max(right_max.y, compass.m.y);
    right_max.z = max(right_max.z, compass.m.z);
    
    delay(10);
  }

  // turn off RIGHT LED as calibration is complete
  digitalWrite(LED_right, LOW);    // turn the LED off

  // Update the readings with a little extra range
  right_min.x = right_min.x - CalibrationWidening;
  right_min.y = right_min.y - CalibrationWidening;
  right_min.z = right_min.z - CalibrationWidening;
  right_max.x = right_max.x + CalibrationWidening;
  right_max.y = right_max.y + CalibrationWidening;
  right_max.z = right_max.z + CalibrationWidening;

  snprintf(report, sizeof(report), "RIGHT = min: {%+6d, %+6d, %+6d}    max: {%+6d, %+6d, %+6d}",
    right_min.x, right_min.y, right_min.z,
    right_max.x, right_max.y, right_max.z);
  Serial.println(report);

  delay(300);



  // CALIBRATION COMPLETE

  // Steady both LEDs
  digitalWrite(LED_left, HIGH);   // turn the LED on
  digitalWrite(LED_right, HIGH);   // turn the LED on
  delay(300);

  // Off for both LEDs
  digitalWrite(LED_left, LOW);    // turn the LED off
  digitalWrite(LED_right, LOW);    // turn the LED off
  delay(300);

}




void loop()
{
  compass.read();  // Read the compass data

  /*
  // Print data to Serial Monitor
  snprintf(report, sizeof(report), "M: %6d %6d %6d", compass.m.x, compass.m.y, compass.m.z);
  Serial.println(report);
  */

  /*
  // Send data to Serial Plotter
  Serial.print(compass.m.x);
  Serial.print("\t");
  Serial.print(compass.m.y);
  Serial.print("\t");
  Serial.println(compass.m.z);
  */

  // Find if the compass Magnetometer is in one of our direction ranges
  if( compass.m.x > right_min.x && compass.m.x < right_max.x &&
      compass.m.y > right_min.y && compass.m.y < right_max.y &&
      compass.m.z > right_min.z && compass.m.z < right_max.z ) {
      CurrPos = 1;
    }
  else if( compass.m.x > left_min.x && compass.m.x < left_max.x &&
      compass.m.y > left_min.y && compass.m.y < left_max.y &&
      compass.m.z > left_min.z && compass.m.z < left_max.z ) {
      CurrPos = -1;
    }
  else if( compass.m.x > neutral_min.x && compass.m.x < neutral_max.x &&
      compass.m.y > neutral_min.y && compass.m.y < neutral_max.y &&
      compass.m.z > neutral_min.z && compass.m.z < neutral_max.z ) {
      CurrPos = 0;
    }

  // Find if the last position is different from current position. If yes, turn on the LED and virtually press the button
  if ( LastPos != CurrPos) {
      digitalWrite(LED_right, LOW);   // turn the LED off
      digitalWrite(LED_left, LOW);   // turn the LED off
      pinMode(OUTPUT_right, INPUT);   // do not press the button anymore
      pinMode(OUTPUT_left, INPUT);   // do not press the button anymore

      if (CurrPos == -1) {
          Serial.println("LEFT");
          digitalWrite(LED_left, HIGH);   // turn the LED on
          pinMode(OUTPUT_left, OUTPUT);   // press the button
        } 
      if (CurrPos == 1) {
          Serial.println("RIGHT");
          digitalWrite(LED_right, HIGH);   // turn the LED on
          pinMode(OUTPUT_right, OUTPUT);   // press the button
        } 
      LastPos = CurrPos;  // Position to LEFT or RIGHT
      LastPosTimer = 0;  // Reset timer to 0
    }

  // If in the same direction, loop for turning the press off, turning the LED off, and turning back to neutral
  else {
    if (LastPosTimer == TimeOutForButtonReset && CurrPos == -1) {
        pinMode(OUTPUT_left, INPUT);   // do not press the button anymore
      }
    if (LastPosTimer == TimeOutForButtonReset && CurrPos == 1) {
        pinMode(OUTPUT_right, INPUT);   // do not press the button anymore
      }

    if (LastPosTimer == TimeOutForLedReset && CurrPos == -1) {
        digitalWrite(LED_left, LOW);   // turn the LED off
      }
    if (LastPosTimer == TimeOutForLedReset && CurrPos == 1) {
        digitalWrite(LED_right, LOW);   // turn the LED off
      }
      
    if (LastPosTimer == TimeOutForTimerReset) {
        LastPosTimer = 0;  // Reset timer to 0
        LastPos = 0;   // Position to NEUTRAL, such that it triggers next time
      }
    else {
        LastPosTimer++;  // Add 1 to timer
      }
  }
  
  delay(10);  // Next loop in 10 ms
}

Credits

Bastiaan Slee

Bastiaan Slee

5 projects • 34 followers
Tinkerer in the field of Home Automation, with the main goal: fun! Using Raspberry Pi, Arduino (+clones), LoRaWAN, NodeRed, 3D Printing

Comments