Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Motorcycle Timer Device for “Dead Red” law

Iskweldog

New member
Joined
Nov 7, 2024
Messages
2
I’m an avid motorcyclist based in Illinois, and I recently came across your work at HackMakeMod. I’m reaching out because I have a specific need that I think your expertise could help with.

In Illinois, there’s a law allowing motorcyclists to proceed through a red light if they’ve been stopped for over 120 seconds, due to the common issue of traffic signals failing to detect motorcycles. To help with this, I’ve been looking for a small, straightforward device: a timer that could count to 120 seconds and be easily mounted to my motorcycle’s handlebars.

Ideally, I’d like this device to have a single button to initiate the countdown, with a design that works even when wearing bulky gloves. A power source via USB or battery would be perfect, along with an on/off switch.

Given your background in creating practical, DIY-friendly electronics, I thought you might be able to help design or suggest a solution. Let me know if this is something you’d be interested in discussing!
 
This sounds like a great project, especially with Illinois’ red-light law for motorcyclists. A simple handlebar-mounted timer with a big glove-friendly button, USB or battery power, and an on/off switch...

Here’s how I’d approach it:​

Parts:
  • Microcontroller (like an Arduino Nano or ESP32) for the countdown.
  • Big Button that’s easy to press with gloves.
  • Display to show the timer.
  • Power Supply – either rechargeable battery or USB.

Design Considerations:
  • Weatherproofing for outdoor durability, like a small, sealed case.
  • Mounting with a clamp or maybe a custom 3D-printed bracket.

Coding:
  • We’d set up simple code to start a 120-second countdown with a button press, resetting after the time runs out.

Quick Questions to Fine-Tune This:​

  1. Are you wanting something you can build yourself?
  2. What skills are you interested in using or learning? 3D printing, soldering, coding?
  3. Power preference: would USB work, or would you rather run it on 3 AAA batteries?
  4. how big do you want it?
  5. Could you share a pic of where you’d want it mounted?
Once I have these details, I can help with specific part suggestions and assembly steps.
 
Q: Are you wanting something you can build yourself?
A: Yes, something I can build or anyone with access to basic electronics skills can build for themselves.

Q: What skills are you interested in using or learning? 3D printing, soldering, coding?
A: I have basic 3D printing skills and soldering skills. I need help with electronics design and coding.

Q: Power preference: would USB work, or would you rather run it on 3 AAA batteries?
A: I think USB works best since most modern bikes have a USB port to charge phones, navigation devices, etc. Additionally, this would allow it to turn on/off with the motorcycle.

Q: How big do you want it?
A: Size is less relevant as long as it fits on the handlebars and doesn't interfere with the motorcycle's operation.

Q: Could you share a pic of where you’d want it mounted?
A: Every motorcycle is different but for my bike, a 2024 Triumph Speet Triple 765 RS, I'm thinking I want it clamped on the left handlebar, which I've measured to be 22mm.
street-triple-765-rs-cockpit.jpg
 
I wrote some code and made a wiring diagram. I tested everything and it works great! Let me know if you need anything else!
StopLightTimer-Diagram-01.png


This code controls a 120-second countdown timer displayed on an OLED screen, designed for use on a motorcycle. Here’s a summary of its functionality:
  1. Button-Controlled Countdown: When the button is pressed, it starts or restarts a 120-second countdown, showing the time remaining in large digits on the OLED display.
  2. Idle Animation: While waiting, the display shows a "breathing" circle animation that grows and shrinks to indicate the timer is ready to use.
  3. Countdown Completion: Once the countdown reaches zero, the code displays a flashing "GO!" logo (provided as a bitmap image) for 10 seconds, alerting the rider that the waiting time has passed.
  4. Loop Structure: The code continuously checks for button presses, updates the countdown every second, and handles the flashing "GO!" animation if the countdown has finished.
  5. Debouncing and Flash Timing: Debounce logic ensures reliable button presses, and timing intervals manage the countdown and flashing animations accurately.

C++:
// HackMakeMod StopLight Timer
// 120-second countdown timer for motorcyclists at traffic lights that don’t detect bikes.
// After pressing a button, the timer counts down, flashing "GO!" when 120 seconds have passed

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold24pt7b.h> // Include thicker font

// Define OLED display dimensions
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Initialize the OLED display using I2C
// SDA: Connect to D2 (GPIO4)
// SCL: Connect to D1 (GPIO5)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Button pin (you can change this to any available GPIO pin)
const int buttonPin = D7; // GPIO13

// Countdown variables
const int countdownSeconds = 120; // Counts down from 120 seconds
int remainingSeconds = countdownSeconds;
bool countdownActive = false;

// "GO!" display variables
bool goActive = false;
unsigned long goStartTime = 0;
unsigned long goFlashInterval = 200; // Flash every 200ms
unsigned long lastGoFlashTime = 0;
bool goVisible = true; // To toggle the visibility of "GO!"

// Timing variables
unsigned long previousMillis = 0;
const long interval = 1000; // 1 second

// Breathing circle variables
int circleRadius = 15;
int circleRadiusMin = 10;
int circleRadiusMax = 20;
int circleRadiusStep = 1;
bool circleGrowing = true;
unsigned long previousBreathMillis = 0;
const long breathInterval = 50; // Update every 50ms

// Button state variables
bool buttonState = HIGH;         // Current state of the button
bool lastButtonState = HIGH;     // Previous state of the button
unsigned long lastDebounceTime = 0;  // The last time the button state changed
const unsigned long debounceDelay = 50; // Debounce time in milliseconds

void setup() {
  // Initialize serial communication (optional)
  Serial.begin(115200);

  // Initialize I2C with custom pins
  Wire.begin(0, 2);       // SDA on GPIO 0, SCL on GPIO 2
  pinMode(D5, OUTPUT);    // Set GPIO pin D5 as an output
  digitalWrite(D5, LOW);  // Set D5 to LOW, simulating GND for the button

  // Initialize button input with internal pull-up resistor
  pinMode(buttonPin, INPUT_PULLUP);

  // Initialize the OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

  // Set maximum brightness
  display.ssd1306_command(SSD1306_SETCONTRAST);
  display.ssd1306_command(0xFF);

  // Clear the buffer
  display.clearDisplay();
  display.display();
}

void loop() {
  // Read the button state
  int reading = digitalRead(buttonPin);

  // Check for button state change (debounce)
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  // If the button state has been stable for debounceDelay, take it as the actual state
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // If the button state has changed
    if (reading != buttonState) {
      buttonState = reading;

      // If the button is pressed (LOW because of internal pull-up)
      if (buttonState == LOW) {
        // Start/restart the countdown
        countdownActive = true;
        goActive = false; // Stop "GO!" display if it's active
        remainingSeconds = countdownSeconds;
        previousMillis = millis();
        displayCountdown();
      }
    }
  }

  // Save the current reading for the next loop
  lastButtonState = reading;

  if (countdownActive) {
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      if (remainingSeconds > 1) {
        remainingSeconds--;
        displayCountdown();
      } else {
        countdownActive = false;
        goActive = true;
        goStartTime = millis();
        lastGoFlashTime = millis();
        goVisible = true;
      }
    }
  } else if (goActive) {
    // Handle "GO!" flashing
    unsigned long currentMillis = millis();

    if (currentMillis - goStartTime < 10000) { // Display "GO!" for 10 seconds
      if (currentMillis - lastGoFlashTime >= goFlashInterval) {
        lastGoFlashTime = currentMillis;
        goVisible = !goVisible; // Toggle visibility

        display.clearDisplay();

        if (goVisible) {
          // Display "GO!"
          display.setFont(&FreeSansBold24pt7b); // Set to thicker font
          display.setTextSize(1); // Adjust text size for this font
          display.setTextColor(SSD1306_WHITE);

          // Calculate position to center the text
          String goStr = "GO!";
          int16_t x1, y1;
          uint16_t w, h;
          display.getTextBounds(goStr, 0, 0, &x1, &y1, &w, &h);
          int x = (SCREEN_WIDTH - w) / 2;
          int y = (SCREEN_HEIGHT - h) / 2 + 35;

          display.setCursor(x, y);
          display.print(goStr);
        }

        display.display();
      }
    } else {
      // "GO!" display time is over
      goActive = false;
      display.clearDisplay();
      display.display();
    }
  } else {
    // Display breathing circle
    unsigned long currentBreathMillis = millis();
    if (currentBreathMillis - previousBreathMillis >= breathInterval) {
      previousBreathMillis = currentBreathMillis;
      updateBreathingCircle();
    }
  }
}

// Function to display the countdown timer
void displayCountdown() {
  display.clearDisplay();

  // Set text properties
  display.setFont(&FreeSansBold24pt7b); // Set to thicker font
  display.setTextSize(1); // Adjust text size for this font
  display.setTextColor(SSD1306_WHITE);

  // Calculate position to center the text
  String numStr = String(remainingSeconds);
  int16_t x1, y1;
  uint16_t w, h;
  display.getTextBounds(numStr, 0, 0, &x1, &y1, &w, &h);
  int x = (SCREEN_WIDTH - w) / 2;
  int y = (SCREEN_HEIGHT - h) / 2 + 35;

  display.setCursor(x, y);
  display.print(numStr);

  display.display();
}

// Function to update the breathing circle
void updateBreathingCircle() {
  // Update circle radius
  if (circleGrowing) {
    circleRadius += circleRadiusStep;
    if (circleRadius >= circleRadiusMax) {
      circleRadius = circleRadiusMax;
      circleGrowing = false;
    }
  } else {
    circleRadius -= circleRadiusStep;
    if (circleRadius <= circleRadiusMin) {
      circleRadius = circleRadiusMin;
      circleGrowing = true;
    }
  }

  // Clear display
  display.clearDisplay();

  // Draw circle at center
  display.fillCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, circleRadius, SSD1306_WHITE);

  // Update display
  display.display();
}
 
Last edited:

An ATTINY85 Countdown Timer with WS2812b LEDs

So… I kept thinking about this and wondered if I could make the device a little simpler and better by using WS2812b LEDs. These LEDs are much brighter and could be seen easily in daylight situations. After tinkering around, I came up with a countdown timer that’s visually striking, highly functional, and easy to build with minimal components.

This project is USB powered and uses an ATTINY85 microcontroller and a ring of 12 WS2812b LEDs to create a configurable countdown timer with a sleek and intuitive design. Each LED represents a segment of time, lighting up and blinking during the countdown, then animating a colorful chase effect when the countdown ends.


Features

Brighter and Simpler:
By using WS2812b RGB LEDs we eliminate the need for a more expensive OLED display. They’re incredibly bright, making the timer usable even in well-lit environments.

Minimal Hardware:
  • ATTINY85 Microcontroller: A small, affordable microcontroller handles all the logic and works with 5V.
  • 12 WS2812b LEDs: Arranged in a circular ring to represent time visually.
  • Push Button: Allows you to start, stop, or reset the countdown at any time.
  • USB Power: Can be plugged into any available USB port for power.

Easy-to-Read Visuals:
  • Steady BLUE lit LEDs indicates that the device is "ready"
  • RED LEDs indicate the countdown process, blinking dynamically to show progress.
  • A smooth GREEN "chase" sequence marks the end of the countdown, cycling around the ring.

Configurable and Responsive:
  • The total countdown time is configurable in the code (default: 120 seconds).
  • A button press can interrupt the current state to start a new countdown at any time.

How It Works

IMG_9903.jpg

Idle Mode

When powered on, the LEDs glow blue to indicate the timer is idle and ready to start. Press the button, and the countdown begins.

Countdown

The timer divides the total countdown time into 12 equal segments, each represented by an LED. For example, with a 120-second countdown, each LED corresponds to 10 seconds.
  • LEDs start red.
  • Each LED blinks 10 times before turning off to signal the passage of time.
  • This repeats until all LEDs are off, marking the end of the countdown.

End Sequence

When the countdown finishes, the LEDs perform a green chase animation:
  • A "chasing" green light rotates around the ring.
  • Each LED lights up in turn, then fades smoothly, creating an eye-catching effect.
  • The chase runs for a configurable duration (default: 10 seconds).

Interrupt and Restart

You can press the button at any time to restart the countdown, even during the green chase animation. This interrupts the current state, resets the timer, and starts the countdown anew.
IMG_9925.jpg


Hardware Setup


Here’s what you’ll need:
  • ATTINY85 Microcontroller
  • 12 WS2812b LEDs (or a WS2812b LED ring)
  • 1 Push Button
  • 1 Capacitor (100 µF across power supply for stability)
  • Power Supply: USB or 5V DC source
  • Resistor: 120Ω - 220Ω (optional but recommended, for data line)
Connections:
  • Pin D0 (PB0): Data line for WS2812b LEDs
  • Pin D1 (PB1): Push Button (with internal pull-up enabled)
  • VCC and GND: Power and ground connections to LEDs and ATTINY85.

ATTINY85 Arduino Pin Mapping​

ATTINY-85-01.png
RESET/D5 (Pin 1)
  • Digital I/O (D5): Can be used as a digital input/output pin if the reset functionality is disabled (not recommended for most applications).
  • Functionality: Reset, when active low.
PB3 (Pin 2) - D3
  • Analog Input: A3
  • Digital I/O (D3): Can be used as a standard digital input/output.
  • Functions: MOSI when used in SPI communications, OC1A (Output Compare Match A for Timer/Counter1).
PB4 (Pin 3) - D4
  • Analog Input: A2
  • Digital I/O (D4): Standard digital input/output capabilities.
  • Functions: MISO in SPI mode, OC1B (Output Compare Match B for Timer/Counter1).
GND (Pin 4)
  • Functionality: Ground.
PB0 (Pin 5) - D0
  • Digital I/O (D0): General-purpose digital input/output.
  • Functions: AIN0 (Analog Comparator Input 0), OC0A (Output Compare Match A for Timer/Counter0).
PB1 (Pin 6) - D1
  • Digital I/O (D1): General-purpose digital input/output.
  • Functions: AIN1 (Analog Comparator Input 1), OC0B (Output Compare Match B for Timer/Counter0), INT0 (External Interrupt 0).
PB2 (Pin 7) - D2
  • Analog Input: A1
  • Digital I/O (D2): Standard digital input/output.
  • Functions: SCK/USCK for SPI/I²C, ADC1 (Analog to Digital Converter Input 1), T0 (Timer/Counter0 external counter input).
VCC (Pin 8)
  • Functionality: Power supply input (+5V or +3.3V).


Wire diagram​

StopLightTimer-Diagram-LED-Version.png

The Code

This project uses the Adafruit NeoPixel library for controlling the WS2812b LEDs and is optimized for the ATTINY85’s limited resources.
Here’s the full code (commented for clarity):

C++:
/*
  ATTINY85 "Dead Red" Countdown Timer with WS2812b LEDs
  Created by HackMakeMod

  This code runs a countdown timer using an ATTINY85 and 12 WS2812b LEDs.
  LEDs show time passing (red), end with a green chase animation, and reset with a button.

  Pins:
  - Pin 0 (PB0): WS2812b LED data line
  - Pin 1 (PB1): Push button input

  Customization:
  - COUNTDOWN_TIME: Total countdown duration (default: 120s)
  - CHASE_DELAY & FADE_RATE: Green chase animation settings
*/

#include <Adafruit_NeoPixel.h>

// Pin Definitions
#define LED_PIN 0    // WS2812B LED data pin
#define BUTTON_PIN 1 // Button input pin

// Configuration
#define NUM_LEDS 12
#define COUNTDOWN_TIME 120000 // Total countdown time in milliseconds
#define LED_ON_TIME (COUNTDOWN_TIME / NUM_LEDS) // Time each LED is active
#define BLINKS_PER_LED 10 // Number of blinks per LED
#define CHASE_DELAY 20     // Green chase delay in milliseconds
#define FADE_RATE 1       // Rate at which green LEDs fade lower is slower
#define END_BLINK_TIME 10000 // Total green chase time in milliseconds

// LED States
#define IDLE_COLOR 0x0000FF   // Blue
#define ACTIVE_COLOR 0xFF0000 // Red

Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

unsigned long lastButtonPress = 0;
unsigned long countdownStartTime = 0;
int currentLED = -1; // -1 means idle state
bool isCounting = false;
bool isEnding = false;

// Variables for the end countdown state
unsigned long endStart = 0;
unsigned long lastChaseUpdate = 0;
int chaseCurrentLED = 0;
uint8_t chaseBrightness[NUM_LEDS] = {0};

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  strip.begin();
  strip.show(); // Initialize all LEDs to off

  setIdleState();
}

void loop() {
  checkButton();
  if (isCounting) {
    updateCountdown();
  } else if (isEnding) {
    handleEndCountdown();
  }
}

// Detects button presses with debounce handling
void checkButton() {
  if (digitalRead(BUTTON_PIN) == LOW && millis() - lastButtonPress > 200) {
    lastButtonPress = millis();
    startCountdown(); // Restart countdown, stops green chase if active
  }
}

// Sets the idle state with blue LEDs
void setIdleState() {
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, IDLE_COLOR);
  }
  strip.show();
  isCounting = false;
  isEnding = false;
  currentLED = -1;
  resetEndCountdownState(); // Clear green chase variables
}

// Resets the countdown and sets LEDs to red
void resetCountdown() {
  countdownStartTime = millis();
  currentLED = 0;
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, ACTIVE_COLOR);
  }
  strip.show();
}

// Starts a new countdown
void startCountdown() {
  resetCountdown();
  isCounting = true;
  isEnding = false;
  resetEndCountdownState(); // Reset green chase state when starting a new countdown
}

// Updates the countdown and manages LED blinking
void updateCountdown() {
  unsigned long elapsed = millis() - countdownStartTime;

  if (elapsed >= COUNTDOWN_TIME) {
    isCounting = false;
    isEnding = true;
    return;
  }

  int newLED = elapsed / LED_ON_TIME;
  if (newLED > currentLED) {
    strip.setPixelColor(currentLED, 0); // Turn off the previous LED
    currentLED = newLED;
  }

  // Blink the active LED 10 times in its time slice
  unsigned long blinkDuration = LED_ON_TIME / (BLINKS_PER_LED * 2);
  unsigned long blinkPhase = (elapsed % LED_ON_TIME) / blinkDuration;

  if (blinkPhase % 2 == 0) {
    strip.setPixelColor(currentLED, ACTIVE_COLOR);
  } else {
    strip.setPixelColor(currentLED, 0); // Off during blink
  }
  strip.show();
}

// Handles the end-of-countdown green chase sequence
void handleEndCountdown() {
  unsigned long now = millis();

  if (endStart == 0) {
    endStart = now; // Initialize the green chase start time
  }

  if (now - lastChaseUpdate >= CHASE_DELAY) {
    chaseBrightness[chaseCurrentLED] = 255; // Set current LED to full brightness
    chaseCurrentLED = (chaseCurrentLED + 1) % NUM_LEDS; // Move to the next LED
    lastChaseUpdate = now;
  }

  // Update the brightness of each LED
  for (int i = 0; i < NUM_LEDS; i++) {
    if (chaseBrightness[i] > 0) {
      chaseBrightness[i] = max(0, chaseBrightness[i] - FADE_RATE); // Gradually fade
      strip.setPixelColor(i, strip.Color(0, chaseBrightness[i], 0)); // Set green color
    } else {
      strip.setPixelColor(i, 0); // Turn off LED if brightness reaches 0
    }
  }

  strip.show();

  // End the green chase after the designated time
  if (now - endStart >= END_BLINK_TIME) {
    setIdleState(); // Return to idle mode
  }
}

// Resets variables used for the green chase sequence
void resetEndCountdownState() {
  endStart = 0;
  lastChaseUpdate = 0;
  chaseCurrentLED = 0;
  memset(chaseBrightness, 0, sizeof(chaseBrightness)); // Clear brightness array
}
 
I’m an avid motorcyclist based in Illinois, and I recently came across your work at HackMakeMod. I’m reaching out because I have a specific need that I think your expertise could help with.

In Illinois, there’s a law allowing motorcyclists to proceed through a red light if they’ve been stopped for over 120 seconds, due to the common issue of traffic signals failing to detect motorcycles. To help with this, I’ve been looking for a small, straightforward device: a timer that could count to 120 seconds and be easily mounted to my motorcycle’s handlebars.

Ideally, I’d like this device to have a single button to initiate the countdown, with a design that works even when wearing bulky gloves. A power source via USB or battery would be perfect, along with an on/off switch.

Given your background in creating practical, DIY-friendly electronics, I thought you might be able to help design or suggest a solution. Let me know if this is something you’d be interested in discussing!
I think there are already several devices already made that would do the trick, kitchen timer, etc. However, if you want to make a timer to fit in a certain place or style. The timer could be as simple as a LED either going off or on after the 120 second timer or an display showing a count down. Here again there are several simple sketches already written to cut and paste. Nothing real technical. You never said if you want to make or to farm it out.
 
Back
Top