Jared Chinchilla Sanchez
Published © GPL3+

Rollsee

2 in 1, avoid your objects to roll over or even your car! Grab your own measure tool with you!

IntermediateShowcase (no instructions)5 hours36
Rollsee

Things used in this project

Hardware components

Arduino micro PRO (HID)
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1
Sunfounder ws2812b
×1
Resistor 10k ohm
Resistor 10k ohm
×1
SparkFun Triple Axis Accelerometer Breakout - ADXL345
SparkFun Triple Axis Accelerometer Breakout - ADXL345
×1
Buzzer
Buzzer
×1
Resistor 100 ohm
Resistor 100 ohm
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Drill / Driver, Cordless
Drill / Driver, Cordless
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Cable Cutter, 200mm
Cable Cutter, 200mm
Driver Bit, Phillips
Driver Bit, Phillips

Story

Read more

Schematics

Rollsee_diagram

diagram_image

Code

Rollsee_main

Arduino
Main code to measure disntance, check box orientation, control leds and buzzer and change modes
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
#include <FastLED.h>

// ---------- LEDs ----------
#define NUM_LEDS 8
#define LED_PIN 5
CRGB leds[NUM_LEDS];

// ---------- Accelerometer ----------
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);

// ---------- Ultrasonic ----------
#define TRIG_PIN 8
#define ECHO_PIN 7
long lastValidDistance = -1;

// ---------- IO ----------
#define BUZZER 9
#define BUTTON 10   // keep wiring consistent with your hardware (INPUT used)

// ---------- Modes ----------
enum Mode { ORIENTATION_MODE, DISTANCE_MODE };
Mode currentMode = ORIENTATION_MODE;

// ---------- Button / hold progress ----------
bool pressActive = false;
unsigned long buttonPressStart = 0;
int lastSecondShown = 0;
bool modeToggledThisPress = false;

// ---------- Alarm (non-blocking) ----------
bool alarmActive = false;
unsigned long alarmLastToggle = 0;
bool alarmState = false;
const unsigned long ALARM_INTERVAL = 200; // ms

// ---------- Misc timing ----------
unsigned long lastDistanceUpdate = 0;

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

  pinMode(BUZZER, OUTPUT);
  pinMode(BUTTON, INPUT);    // keep as INPUT to match your wiring (if you use pull-up change to INPUT_PULLUP and invert logic)
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
  FastLED.clear();
  FastLED.show();

  if (!accel.begin()) {
    Serial.println("ADXL345 not detected");
    while (1);
  }
  accel.setRange(ADXL345_RANGE_16_G);

  Serial.println("System Ready");
}

// main loop — button handler runs first so progress always updates promptly
void loop() {
  handleButtonProgress();

  // If the button is actively being held, the progress LEDs take precedence.
  // We skip normal mode behavior while showing the progress.
  if (pressActive) return;

  if (currentMode == ORIENTATION_MODE) {
    orientationMode();
  } else {
    distanceMode();
  }
}

// ---------------- Button + progress logic ----------------
void handleButtonProgress() {
  bool pressed = digitalRead(BUTTON);

  if (pressed && !pressActive) {
    // start a new hold
    pressActive = true;
    buttonPressStart = millis();
    lastSecondShown = 0;
    modeToggledThisPress = false;
    // immediately show 0-> no leds until first second passes
    FastLED.clear();
    FastLED.show();
    return;
  }

  if (!pressed && pressActive) {
    // button released — clear progress display and reset
    pressActive = false;
    lastSecondShown = 0;
    modeToggledThisPress = false;
    FastLED.clear();
    FastLED.show();
    Serial.print("Button held for (ms): ");
    Serial.println(millis() - buttonPressStart);
    return;
  }

  if (pressed && pressActive) {
    unsigned long elapsed = (millis() - buttonPressStart) / 1000; // whole seconds
    if (elapsed > lastSecondShown && elapsed <= NUM_LEDS) {
      // show progress for each newly reached second
      lastSecondShown = elapsed;
      showProgressLeds(elapsed);
      Serial.print("Hold seconds: ");
      Serial.println(elapsed);
    }

    // If we reached 8 seconds (NUM_LEDS) toggle mode once
    if (elapsed >= NUM_LEDS && !modeToggledThisPress) {
      // give a tiny audible confirmation
      tone(BUZZER, 1200);
      delay(120);
      noTone(BUZZER);

      toggleMode();
      modeToggledThisPress = true;
    }
  }
}

void showProgressLeds(int seconds) {
  // Lights LEDs 0..seconds-1 green
  FastLED.clear();
  for (int i = 0; i < seconds && i < NUM_LEDS; i++) {
    leds[i] = CRGB::Green;
  }
  FastLED.show();
}

// ---------------- Mode switching ----------------
void toggleMode() {
  // clear outputs and switch
  noTone(BUZZER);
  FastLED.clear();
  FastLED.show();

  currentMode = (currentMode == ORIENTATION_MODE) ? DISTANCE_MODE : ORIENTATION_MODE;
  Serial.print("Switched mode to: ");
  Serial.println(currentMode == ORIENTATION_MODE ? "ORIENTATION" : "DISTANCE");
  // small debounce pause so the button release doesn't immediately retrigger things
  delay(200);
}

// ---------------- Orientation mode (non-blocking alarm) ----------------
void orientationMode() {
  sensors_event_t event;
  accel.getEvent(&event);
  float X = event.acceleration.x;
  float Y = event.acceleration.y;

  // tilt thresholds (same as your original)
  if (abs(X) > 9 || abs(Y) > 8) {
    alarmActive = true;
  } else {
    alarmActive = false;
    // ensure outputs are off when not alarming
    noTone(BUZZER);
    FastLED.clear();
    FastLED.show();
  }

  // Non-blocking blink/tone while alarmActive
  if (alarmActive) {
    unsigned long now = millis();
    if (now - alarmLastToggle >= ALARM_INTERVAL) {
      alarmLastToggle = now;
      alarmState = !alarmState;
      if (alarmState) {
        tone(BUZZER, 900);
        fill_solid(leds, NUM_LEDS, CRGB::Red);
        FastLED.show();
      } else {
        noTone(BUZZER);
        FastLED.clear();
        FastLED.show();
      }
    }
  }
}

// ---------------- Distance mode ----------------
void distanceMode() {
  // Rate-limit distance updates
  if (millis() - lastDistanceUpdate < 60) return;
  lastDistanceUpdate = millis();

  long distance = readDistanceCM();

  // Ignore invalid readings
  if (distance < 0 || distance > 80) return;

  // Hysteresis to avoid jitter
  if (lastValidDistance != -1 && abs(distance - lastValidDistance) < 2) return;
  lastValidDistance = distance;

  // ---- 1 LED per 10 cm ----
  int ledsOn = distance / 10;
  ledsOn = constrain(ledsOn, 0, NUM_LEDS);

  FastLED.clear();
  for (int i = 0; i < ledsOn; i++) {
    leds[i] = CRGB::Red;
  }
  FastLED.show();

  Serial.print("Distance: ");
  Serial.print(distance);
  Serial.print(" cm | LEDs: ");
  Serial.println(ledsOn);
}

// ---------------- Ultrasonic helper ----------------
long readDistanceCM() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(5);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH, 30000); // 30 ms timeout
  if (duration == 0) return -1;  // NO ECHO

  return duration * 0.034 / 2;
}

Credits

Jared Chinchilla Sanchez
1 project • 2 followers
I work at a hotel as a receptionist and maintenance, since a meet Arduino in a feria of universities I started to investigate it.

Comments