(3 Player) Lazer Pointer Mirror Shooter Game, Using TCS3472 Color Sensor's and Multiplexer

by belletje in Circuits > Arduino

72 Views, 0 Favorites, 0 Comments

(3 Player) Lazer Pointer Mirror Shooter Game, Using TCS3472 Color Sensor's and Multiplexer

final layout.JPG
Capture.JPG
20240630_194236.jpg

I made this as a school project; it is a simple game for 3 players, with each player having their own color.

I liked the idea of shooting lasers via mirrors at targets.

The main goal of the game is to get all lamps on all 3 sides to display your color.

It is possible to make a DIY version using only cardboard, tape, and aluminum foil if you have the electronic parts for the Arduino.

The game works best and looks coolest in a dark room.

Supplies

20240627_193102.jpg
20240630_194354.2.jpg
20240326_172305.jpg
  • 3 x RGB LEDs
  • 9 x 3.3 ohm resistors
  • 3 x color sensors
  • 1 x multiplexer
  • 1 x step motor
  • 1 x Arduino
  • Old telephone cord for wiring
  • Soldering tin and pin board for making connections between wires
  • 1 x red laser pointer
  • 1 x green laser pointer
  • 1 x blue laser pointer
  • Cardboard or thin plastic (Stanley knife or scissors)
  • Tape
  • Aluminum foil

Sketch

scketch.JPG

In my initial sketches, I decided to make the lasers handheld and not controlled by the Arduino due to the limited number of outputs available. Additionally, this approach makes the project more interactive and fun.

Make Box

sides.JPG
box.JPG

With of each side: 30 cm

height: 15 cm

Make Mirror Hexagon

WhatsApp Image 2024-03-26 at 10.45.33 PM (2).jpeg

Height: 8cm

sides: 3cm

Wiring RGB Lights/ Soldering

solderen.JPG

Make wiring for lights around 1.5 the width of the box

Coding Servo Motors and Lights

This was my initial code to check if changing the light colors would work and if the motor could rotate randomly.

#include <Servo.h>

Servo myServo; // Create a servo object to control the servo motor
const int servoPin = 0; // D0 pin for the servo motor

void setup() {
  // Initialize servo motor
  myServo.attach(servoPin); // Attach the servo to pin D0
 
  // Initialize RGB LED pins
  for (int i = 1; i <= 9; i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW); // Ensure all LEDs are off at start
  }
}

void loop() {
  // Toggle between red, green, and blue for each RGB LED with a delay of 0.5 second
  toggleRGB(1, 1); // Red for first RGB LED
  toggleRGB(4, 1); // Red for second RGB LED
  toggleRGB(7, 1); // Red for third RGB LED
  delay(500);
 
  toggleRGB(1, 2); // Green for first RGB LED
  toggleRGB(4, 2); // Green for second RGB LED
  toggleRGB(7, 2); // Green for third RGB LED
  delay(500);
 
  toggleRGB(1, 3); // Blue for first RGB LED
  toggleRGB(4, 3); // Blue for second RGB LED
  toggleRGB(7, 3); // Blue for third RGB LED
  delay(500);

  // Move the servo motor randomly
  moveRandom();
  delay(500); // Adjust delay as needed
}

void toggleRGB(int led, int color) {
  // Turn off all LEDs
  digitalWrite(led, LOW); // r
  digitalWrite(led + 1, LOW); // g
  digitalWrite(led + 2, LOW); // b

  // Depending on the color value passed, turn on the corresponding color for the specified LED
  switch (color) {
    case 1: // Red
      digitalWrite(led, HIGH); // r
      break;
    case 2: // Green
      digitalWrite(led + 1, HIGH); // g
      break;
    case 3: // Blue
      digitalWrite(led + 2, HIGH); // b
      break;
  }
}

void moveRandom() {
  int currentPosition = myServo.read(); // Get the current position of the servo
  int newPosition = random(0, 180); // Generate a random position between 0 and 180 degrees
  int speed = random(1, 4); // Generate a random speed between 1 and 3 (adjust as needed)

  // Determine the direction (increment or decrement) to reach the new position
  int direction = (newPosition > currentPosition) ? 1 : -1;

  // Gradually move the servo to the new position with the specified speed
  for (int pos = currentPosition; pos != newPosition; pos += direction) {
    myServo.write(pos); // Move the servo to the current position
    delay(speed * 10); // Wait for the specified speed (adjust as needed)
  }

  myServo.write(newPosition); // Set the final position after reaching the new position
}

Wiring Sensors

wiring.JPG

Coding Sensors

First, I researched how the color sensor works and how it communicates the detected color to the Arduino.

#include <Wire.h>
#include "Adafruit_TCS34725.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

/* Example code for the Adafruit TCS34725 breakout library */

/* Connect SCL    to analog 5
   Connect SDA    to analog 4
   Connect VDD    to 3.3V DC
   Connect GROUND to common ground */

/* Initialise with default values (int time = 2.4ms, gain = 1x) */
// Adafruit_TCS34725 tcs = Adafruit_TCS34725();

/* Initialise with specific int time and gain values */
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_614MS, TCS34725_GAIN_1X);

void setup(void) {
  Serial.begin(9600);

  if (tcs.begin()) {
    Serial.println("Found sense");
  } else {
    Serial.println("No TCS34725 found ... check your connections");
    while (1);
  }

  // Now we're ready to get readings!
}

void loop(void) {
  uint16_t r, g, b, c, colorTemp, lux;

  tcs.getRawData(&r, &g, &b, &c);
  // colorTemp = tcs.calculateColorTemperature(r, g, b);
  colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
  lux = tcs.calculateLux(r, g, b);

  Serial.print(WHITE);
  Serial.println("ColorSense");

  Serial.print("Lux:");
  Serial.println(lux, DEC);

  Serial.print("RED:");
  Serial.println(r, DEC);

  Serial.print("GREEN:");
  Serial.println(g, DEC);

  Serial.print("BLUE:");
  Serial.println(b, DEC);
  }

I encountered an unexpected problem while trying to get three TCS34725 color sensors to work on the same Arduino. These sensors can only be read on analog pins 4 and 5, which created a challenge since all three sensors have the same I2C address (0x29). This meant they couldn't be connected in parallel directly.

After extensive research, I discovered that using a multiplexer could solve this issue. A multiplexer can receive multiple analog inputs and assign different addresses to them, allowing each sensor to be read separately.

The multiplexer I used created the following addresses for the corresponding pins:

  • 0x70 -> SD0/SC0
  • 0x71 -> SD1/SC1
  • 0x72 -> SD2/SC2
  • 0x73 -> SD3/SC3
  • 0x74 -> SD4/SC4
  • 0x75 -> SD5/SC5
  • 0x76 -> SD6/SC6
  • 0x77 -> SD7/SC7

I found an example (https://www.hackster.io/sherwinchiu89/multiplexing-6-i2c-tcs34725-color-sensors-2a7272) which demonstrated how to use a multiplexer with TCS34725 sensors. Although my sensors and Arduino were different, the example provided valuable insights into addressing the sensors via the multiplexer.

Adapting the code required considerable trial and error. Debugging was challenging because errors could stem from the code, the sensors, or the wiring. Despite these difficulties, I managed to get the setup working by carefully adjusting the code and ensuring all connections were correct.

Taping Everything Together

taping.JPG

I initially put some black tape over the lights from the color sensor to prevent interference with the laser colors. Later, I replaced the tape with black nail polish for a more permanent solution.

Prototype Full Code

My first not completely bug free semi working code (prototype):

//servo
#include <Servo.h>
Servo myServo; // Create a servo object to control the servo motor
const int servoPin = 0; // D0 pin for the servo motor

// colour sensor
#include <Wire.h>
#include "Adafruit_TCS34725.h"
#define MUX_ADDR 0x70 // Multiplexer address
#define SENSORS_COUNT 3 // Number of sensors connected to the multiplexer
Adafruit_TCS34725 tcs; // Create an object for TCS34725 sensor

//#define THRESHOLD 5000 // Adjust the threshold value as needed
int redThreshold = 500;
int greenThreshold = 500;
int blueThreshold = 500;

int player1_color = 0;
int player2_color = 0;
int player3_color = 0;

void setup() {
  // servo motor
  myServo.attach(servoPin); // Attach the servo to pin D0
 
  // rgb let lights
  pinMode(1, OUTPUT); // Pin connected to the red LED of the first RGB LED //D1=Red D2=Geen D3=Blue
  pinMode(4, OUTPUT); // Pin connected to the red LED of the second RGB LED //D4=Red D5=Geen D6=Blue
  pinMode(7, OUTPUT); // Pin connected to the red LED of the third RGB LED //D7=Red D8=Geen D9=Blue
 
  // colour sensors
  // Wire.begin(); // Initialize I2C communication
  // // Initialize the TCS34725 sensor
  // if (!tcs.begin()) {
  //   Serial.println("No TCS34725 found ... check your connections");
  //   while (1);
  // }
}

void loop() {

  // // Iterate through each sensor connected to the multiplexer
  // for (int sensorNum = 0; sensorNum < 3; sensorNum++) {
  //   // Select the sensor using the multiplexer
  //   chooseBus(sensorNum);
   
  //   // Read data from the selected sensor
  //   uint16_t r, g, b, c;
  //   tcs.getRawData(&r, &g, &b, &c);

  //   // Print the RGB values of the selected sensor
  //   Serial.print("Sensor ");
  //   Serial.println(sensorNum + 1); // Sensor numbering starts from 1
  //   Serial.print("RED: ");
  //   Serial.println(r);
  //   Serial.print("GREEN: ");
  //   Serial.println(g);
  //   Serial.print("BLUE: ");
  //   Serial.println(b);
  //   Serial.println();
   
  //   checkColorThreshold(r, g, b, redThreshold, greenThreshold, blueThreshold, sensorNum + 1);
  // }

  //Toggle between red, green, and blue for each RGB LED with a delay of 1 second
  toggleRGB(1, 1); // Red for first RGB LED
  toggleRGB(4, 1); // Red for second RGB LED
  toggleRGB(7, 1); // Red for third RGB LED
  delay(500);
 
  toggleRGB(1, 2); // Green for first RGB LED
  toggleRGB(4, 2); // Green for second RGB LED
  toggleRGB(7, 2); // Green for third RGB LED
  delay(500);
 
  toggleRGB(1, 3); // Blue for first RGB LED
  toggleRGB(4, 3); // Blue for second RGB LED
  toggleRGB(7, 3); // Blue for third RGB LED
  delay(500);

  moveRandom();
 
  //delay(500); // Wait before moving again
}
void chooseBus(int busNum) {
  Wire.beginTransmission(MUX_ADDR);
  Wire.write(1 << (busNum + 2)); // Set the appropriate bit for selecting the sensor channel
  Wire.endTransmission();
}

void toggleRGB(int led, int color)
{
  // Turn off all LEDs
  // Turn off all LEDs

  digitalWrite(led, LOW); // r
  digitalWrite(led + 1, LOW); // g
  digitalWrite(led + 2, LOW); // b

  //digitalWrite(led, LOW); // Turn off the specified LED
  //delay(10);
  // Depending on the color value passed, turn on the corresponding color for the specified LED
 
  switch(color)
  {
    case 1: // Red
      digitalWrite(led, HIGH);// r
      // digitalWrite(led + 1, LOW); // g
      // digitalWrite(led + 2, LOW); // b
      break;
    case 2: // Green
      digitalWrite(led + 1, HIGH); // The green pin is one higher than the red pin
      // digitalWrite(led, LOW);// r
      // digitalWrite(led + 1, HIGH); // g
      // digitalWrite(led + 2, LOW); // b
      break;
    case 3: // Blue
      digitalWrite(led + 2, HIGH); // The blue pin is two higher than the red pin
      // digitalWrite(led, LOW);// r
      // digitalWrite(led + 1, LOW); // g
      // digitalWrite(led + 2, HIGH); // b
      break;
    default:
      break;
  }
}

void moveRandom() {
  int currentPosition = myServo.read(); // Get the current position of the servo
  int newPosition = random(0, 180); // Generate a random position between 0 and 180 degrees
  int speed = random(1, 4); // Generate a random speed between 1 and 3 (adjust as needed)

  // Determine the direction (increment or decrement) to reach the new position
  int direction = (newPosition > currentPosition) ? 1 : -1;

  // Gradually move the servo to the new position with the specified speed
  for (int pos = currentPosition; pos != newPosition; pos += direction) {
    myServo.write(pos); // Move the servo to the current position
    delay(speed * 10); // Wait for the specified speed (adjust as needed)
  }

  myServo.write(newPosition); // Set the final position after reaching the new position
}

void checkColorThreshold(uint16_t r, uint16_t g, uint16_t b, int redThreshold, int greenThreshold, int blueThreshold, int sensorNum)
{
  // // Check if the red value is above its threshold
  // if (r > redThreshold) {
  //   Serial.println("Red is above threshold.");
  // }

  // // Check if the green value is above its threshold
  // if (g > greenThreshold) {
  //   Serial.println("Green is above threshold.");
  // }

  // // Check if the blue value is above its threshold
  // if (b > blueThreshold) {
  //   Serial.println("Blue is above threshold.");
  // }

  // Determine which color is highest above its threshold
  if (r > redThreshold && r >= g && r >= b) {
    Serial.println("Red is highest above its threshold.");
    player_colors(1, sensorNum);
  } else if (g > greenThreshold && g >= r && g >= b) {
    Serial.println("Green is highest above its threshold.");
    player_colors(2, sensorNum);
  } else if (b > blueThreshold && b >= r && b >= g) {
    Serial.println("Blue is highest above its threshold.");
    player_colors(3, sensorNum);
  }
}

void player_colors(int color,int sensorNum)
{
  if (sensorNum = 1)
  {
    player1_color = color;
    toggleRGB(1, color);
  }
   if (sensorNum = 2)
  {
    player2_color = color;
    toggleRGB(4, color);
  }
  if (sensorNum = 3)
  {
    player3_color = color;
    toggleRGB(7, color);
  }
}

The main issue with this code was that is suddenly did not want to work anymore, the color thresholds were not right, and the lamps appeared to be constantly active on certain pins.

Prototype and Coding Errors

20240328_100504.2.jpg
code and errors.JPG
all items.JPG

These are some persistent bugs I encountered. The primary issue appeared to be related to the wiring of the color sensors, which were not being recognized through the multiplexer. A lot later, it became evident that the sensors could only be connected to 3.3V instead of 5V to function correctly.

Fixing Error's and Wiring

F9Q5K3ELY1KAR8H.jpg
arduino updated cabels.JPG
sensors updated cables.JPG

After disassembling and reassembling the setup, I addressed issues with the lights frequently disconnecting during testing. I found a more stable method to connect them securely to the Arduino.

Additionally, I revised the code to adjust the way color thresholds were processed. Previously, the red color detection was not functioning correctly because it was the first condition checked, even when the red value exceeded the threshold.

Full Game

20240630_194315.jpg
FS0F1WLLY1KATPO.jpg
FS3YRIWLY1KATNU.jpg
20240630_194251.2.jpg

I double-checked all connections for accuracy and opted for a neater finish by replacing cardboard with cut plastic. The cardboard cutting steps from earlier can also be used for this, mind that I now put the wiring of the lamps to the inside of the box for a cleaner look, enhancing both the game's appearance and playability. Additionally, I designed the walls to be movable and standalone, ensuring they do not disrupt the wiring below.

Now the sensors are also placed lower/beneath the laser locations to encourage pointing downward with lasers to make it saver and to reduce visible wires.

The game turned out to be surprisingly enjoyable and challenging. Despite the mirrors rotating slowly, hitting the sensors posed a significant challenge. The game can be played for extended periods because once you secure your color, others can still take over.

It offers a great test of hand-eye coordination in a completely novel way.

Working Code

This is my latest code, and it's fully operational now. You can adjust the thresholds to accommodate the ambient light conditions in the room. If the lamps illuminate before you shoot a laser at them, simply adjust the thresholds accordingly.

#include <Servo.h>
#include <Wire.h>
#include "Adafruit_TCS34725.h"

// Servo motor
Servo myServo;
const int servoPin = 9; // Connect the servo to digital pin 9

// Color sensors
Adafruit_TCS34725 tcs[] = {
  Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_614MS, TCS34725_GAIN_1X),
  Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_614MS, TCS34725_GAIN_1X),
  Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_614MS, TCS34725_GAIN_1X)
};
byte gammatable[256];

// Define constants
#define MUX_ADDR 0x70 // Multiplexer address
#define SENSORS_COUNT 3 // Number of sensors connected to the multiplexer

#define RED_PIN1 2
#define GREEN_PIN1 3
#define BLUE_PIN1 4

#define RED_PIN2 5
#define GREEN_PIN2 6
#define BLUE_PIN2 7

#define RED_PIN3 10
#define GREEN_PIN3 11
#define BLUE_PIN3 12

// Color thresholds
#define RED_THRESHOLD 100
#define GREEN_THRESHOLD 100
#define BLUE_THRESHOLD 100

// Player colors
int playerColors[3] = {0, 0, 0}; // Array to store colors for each player

// Servo movement variables
int currentPosition = 90; // Start at mid-point
int targetPosition = 90;
unsigned long lastServoMoveTime = 0;
const int servoMoveInterval = 10; // Decrease milliseconds between servo moves (was 20)
const int servoStepSize = 5; // Increase the step size

void setup() {
 Serial.begin(9600);
 Serial.println("start setup");

 // Initialize RGB LED pins
 for (int i = 2; i <= 11; i++) {
  pinMode(i, OUTPUT);
  digitalWrite(i, LOW); // Ensure all LEDs are off at start
 }
 
 Serial.println("setup leds");

 Wire.begin();  
 for (int i = 0; i < 256; i++) {
   float x = i;
   x /= 255;
   x = pow(x, 2.5);
   x *= 255;
   gammatable[i] = x;
 }
 
 Serial.println("setup 1");
 initColorSensors();
 
 Serial.println("setup after initialisation color sensors");

 // Initialize the servo motor
 myServo.attach(servoPin);
 myServo.write(currentPosition); // Move servo to start position
 Serial.println("Servo attached and set to 90 degrees");
}

void loop()
{
 for (int i = 0; i < SENSORS_COUNT; i++) {
  readColors(i);
 }

 // Toggle LEDs based on player colors
 for (int i = 0; i < SENSORS_COUNT; i++) {
  int redPin, greenPin, bluePin;
  switch (i) {
   case 0:
    redPin = RED_PIN1; greenPin = GREEN_PIN1; bluePin = BLUE_PIN1;
    break;
   case 1:
    redPin = RED_PIN2; greenPin = GREEN_PIN2; bluePin = BLUE_PIN2;
    break;
   case 2:
    redPin = RED_PIN3; greenPin = GREEN_PIN3; bluePin = BLUE_PIN3;
    break;
  }
  toggleRGB(redPin, greenPin, bluePin, playerColors[i]);
 }

 moveRandom(); // Move the servo motor randomly

 delay(1); // Adjust delay as needed
}

void initColorSensors() {
 Serial.println("Sensor initialization");
 for (int i = 0; i < SENSORS_COUNT; i++) {
  chooseBus(i);
  if (tcs[i].begin()) {
   Serial.print("Found sensor "); Serial.println(i + 1);
  } else {
   Serial.println("No Sensor Found");
   while (true);
  }
 }
}

void readColors(byte sensorNum) {
 chooseBus(sensorNum);
 uint16_t r, g, b, c;
 tcs[sensorNum].getRawData(&r, &g, &b, &c);
 
 Serial.print("Sensor "); Serial.print(sensorNum); Serial.println(":");
 Serial.print("R: "); Serial.print(r, DEC); Serial.print(" ");
 Serial.print("G: "); Serial.print(g, DEC); Serial.print(" ");
 Serial.print("B: "); Serial.print(b, DEC); Serial.print(" ");
 Serial.print("Clear: "); Serial.println(c);
 
 checkColorThreshold(r, g, b, c, sensorNum);
}

void chooseBus(uint8_t bus) {
 Wire.beginTransmission(MUX_ADDR);
 Wire.write(1 << bus);
 Wire.endTransmission();
}

void checkColorThreshold(uint16_t r, uint16_t g, uint16_t b, uint16_t c, int sensorNum) {
 if (r > RED_THRESHOLD && r > g && r > b) {
  Serial.print(sensorNum);
  Serial.println(": Red detected.");
  setPlayerColor(1, sensorNum);
 } else if (g > GREEN_THRESHOLD && g > r && g > b) {
  Serial.print(sensorNum);
  Serial.println(": Green detected.");
  setPlayerColor(2, sensorNum);
 } else if (b > BLUE_THRESHOLD && b > r && b > g) {
  Serial.print(sensorNum);
  Serial.println(": Blue detected.");
  setPlayerColor(3, sensorNum);
 }
}

void setPlayerColor(int color, int sensorNum) {
 if (sensorNum >= 0 && sensorNum < SENSORS_COUNT) {
  playerColors[sensorNum] = color;
 }
}

void toggleRGB(int redPin, int greenPin, int bluePin, int color) {
 // Turn off all LEDs
 digitalWrite(redPin, LOW);
 digitalWrite(greenPin, LOW);
 digitalWrite(bluePin, LOW);

 // Depending on the color value passed, turn on the corresponding color for the specified LED
 switch (color) {
  case 1:
   Serial.println("Turning on RED");
   digitalWrite(redPin, HIGH);
   break;
  case 2:
   Serial.println("Turning on GREEN");
   digitalWrite(greenPin, HIGH);
   break;
  case 3:
   Serial.println("Turning on BLUE");
   digitalWrite(bluePin, HIGH);
   break;
 }
}

void moveRandom() {
 unsigned long currentTime = millis();
 if (currentTime - lastServoMoveTime >= servoMoveInterval) {
  if (currentPosition == targetPosition) {
   // Choose a new random target position
   targetPosition = random(0, 180);
   Serial.print("New target position: ");
   Serial.println(targetPosition);
  } else {
   // Move the servo multiple steps towards the target position
   int step = min(servoStepSize, abs(targetPosition - currentPosition));
   currentPosition += (targetPosition > currentPosition) ? step : -step;
   myServo.write(currentPosition);
   Serial.print("Current position: ");
   Serial.println(currentPosition);
  }
  lastServoMoveTime = currentTime;
 }
}

Videos

Videos of a:

test (prototype testing)

play through (with 3 players)

unpacking (the insides)

all speed up since only less than 25 MB files are allowed...