Bike Light That Turns Off Automatically

by The_Blue_Lizard in Circuits > Arduino

128 Views, 0 Favorites, 0 Comments

Bike Light That Turns Off Automatically

IMG_20230427_135600.jpg

This is my first time working with Arduino so I wanted to take on a simple project that would teach me the basics while creating something fun and useful. At the time I was really frustrated with my broken bike light. I had already replaced it but it broke again in the same way within a few weeks. I decided to try to make my own bikelight, with the added functionality that it would turn off automatically (after a few minutes) if I myself forgot to do so.

As a safety measure I also added an LED on the handlebar that lets you know when the light turns off automatically. I wouldn't want the light to turn off while waiting for a traffic light without me noticing.

Supplies

Arduino Uno

Ultrasonic sensor

2x red LEDs

1x blue LED (or any color you like)

Momentary button

2x 220 ohm and 1x 10k ohm resistor

A bunch of Dupont Jumper wires, both male-male and male-female

2x +/- 1,5m wires (to transport a signal from the bike light all the way to the handlebar via the bike frame, I recommend measuring the length in advance or buying a longer wire than needed (2m is plenty) and cut it to be the right length)

experiment PCB

soldering equipment

9V battery

9V Battery Holder with DC Jack

USB-A to USB-B cable

Reflectors that can be attached to bike spokes.


Materials for a casing (I used an existing Bike Light casing and a shallow plastic box)

Waterproofing materials (small plastic tubes, rubber seal)

Making a Bike Light

IMG_20230302_114316.jpg
74f496c8-66f3-48fc-8a94-30b50f240452.png

I decided to start small and slowly add functionality in steps. The first step being a simple circuit where a button can turn an LED on and off, which is the basic functionality of a bike light. I did everything on breadboard at first, and a lot changed in the process of making the bike light, so if you just want to see the finished circuit with everything on it you can skip ahead to step 5, and you can find the finished code in step 3.


At this point I had barely any knowledge of Arduino, so I followed a tutorial by TheGeekPub Extras youtube channel for making the circuit and writing the code (You can find it all the way at the bottom of this instructable under Step 7: Credits). I connected the button to pin 4, and the LED to pin 11. I made one mistake, which I didn't know at the time, and used a 220 ohm resistor (I think, those color codes are still a little confusing to me) for the button instead of a 10k ohm one. The circuit still works, but sometimes the button thinks it's pressed twice. In the diagram I added above I fixed this mistake.


Code for this step:

  //ITTT project Bike Light (23-02-2023)
  //tutorial by : TheGeekPub.com


  //constants
  const int BUTTON_PIN = 4;
  const int LED_PIN = 11;


  //variables
  int ledState = LOW; //tracks current state of LED
  int lastButtonState; //previous state of the button
  int currentButtonState; //current state of button


void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);


  currentButtonState = digitalRead(BUTTON_PIN);
}


void loop() {
  lastButtonState = currentButtonState; //save last state
  currentButtonState = digitalRead(BUTTON_PIN); //read new state


  if(lastButtonState == HIGH && currentButtonState == LOW) {
    Serial.print("The button is pressed: " );


    //toggle state of LED
    if(ledState == LOW) {
      ledState = HIGH;
      Serial.println("Turning LED on");
    }
    else {
      ledState = LOW;
      Serial.println("Turning LED off");
    }


    //control LED according to the toggled state
    digitalWrite(LED_PIN, ledState);
    Serial.print(ledState);
  }
}

Adding the Ultrasonic Sensor

IMG_20230302_150919.jpg
67decb9e-e545-4d93-9dcd-c284314ac157.png

(In this step I made the unfortunate decision to change some wires around, so what was previously an orange wire leading from the button to pin 4 is now a green wire, sorry)


Next up I wanted to detect whether the bicycle was moving or not. I decided to use an ultrasonic sensor to detect whether the spokes of the bike were moving or still. For the time being I moved my hand in front of the sensor to test whether it and the code worked. In step 4 I will actually test in on the bicycle. Once again I followed a tutorial, this time by techexplorations.com.

My idea was that the sensor would detect every few moments whether it sees a spoke or not. If it doesn't see a spoke for a long time (let's say 5 minutes), that probably means the bike is no longer moving, and the light can turn itself off. If it keeps seeing a spoke the bike is probably also standing still, but if it sees a spoke, then no spoke, and so on, the bike is moving and the lamp needs to stay on.

I encountered a few problems in this step. I documented these at the top of my code. The most important one was the following:

  • After adding in the code for the ultrasonic sensor, the button didn't always work anymore. I found out this was because of a delay in the code. While the code is waiting for the delay, none of the rest of the code is executed so the code for the button isn't either. I tried to fix this by adding an integer that would count up by one every frame, and use that instead of the delay to space out the times the ultrasonic sensor sends out a pulse. Unfortunately this doesn't work because Arduino doesn't have a set fps. I ended up finding a solution on Arduino's documentation site: A tutorial called Blink without Delay. It makes use of a function called millis(), that returns the number of milliseconds since the Arduino started running the code.

The code I wrote has some problems still. I will fix these in the next step.

Once again the circuit can be seen above, and the code right below here:

Code for this step:

  //ITTT project Bike light (23-02-2023)
  //light switch tutorial by : TheGeekPub.com
  //Ultrasonic sensor tutorial by: techexplorations.com
  //blink without delay tutorial by:docs.arduino.cc
 


    //PROBLEM1: sensor only reads distance when button is pressed (fix: move code for sensor to front)
    //PROBLEM2: arduino doesnt always receive input from button. --> This is caused by the delay
    //temporary solution: making the delay shorter
    //possible solution: remove delay and add int that counts to a certain amount (doesn't work because arduino doesnt have set fps)
    //solution: Blink without delay tutorial (millis() function)
    //PROBLEM SOLVED


    //CURRENT PROBLEM: arduino always says "spoke detected" when it detects anything, even when something is further than 10 cm away.
      //solution: its && not whatever weird thing I was doing, that meant or
    //SECOND PROBLEM: Button often thinks it is pressed twice


    //TO DO:
    //only let most of the program run when the led is on!!
    //sort everything into functions


  //constants
  const int BUTTON_PIN = 4; //light
  const int LED_PIN = 11;


  const int TRIG_PIN = 9; //ultrasonic sensor
  const int ECHO_PIN = 8;


  const long interval = 500; //interval at which to execute code for ultrasonic sensor
  const long Spokeinterval = 10000;


  //variables
  int ledState = LOW; //tracks current state of LED
  int lastButtonState; //previous state of the button
  int currentButtonState; //current state of button


  unsigned long previousMillis = 0; //will store last time code for ultrasonic sensor was executed
  unsigned long previousSpokeMillis = 0;
  unsigned long previousNoSpokeMillis = 0;


void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);


  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);


  currentButtonState = digitalRead(BUTTON_PIN);
}


void loop() {
  lastButtonState = currentButtonState; //save last state
  currentButtonState = digitalRead(BUTTON_PIN); //read new state


  unsigned long currentMillis = millis(); //amount of milliseconds that the code has been running for


  if (currentMillis - previousMillis >= interval) { //interval in which the code below  is executed, execute after interval amount of milliseconds
    previousMillis = currentMillis; //save last time code was executed


  //ultrasonic sensor
        long duration, distance; //make 2 variabeles
      digitalWrite(TRIG_PIN, LOW);
      delayMicroseconds(2);
      digitalWrite(TRIG_PIN, HIGH);
      delayMicroseconds(10);
      digitalWrite(TRIG_PIN, LOW);
      duration = pulseIn(ECHO_PIN, HIGH);
      distance = (duration/2) / 29.1;


      if (distance >=0 && distance <= 200) {//(distance >= 200 || distance <= 0) {
        Serial.print(distance);
        Serial.println("cm");


        if (distance >= 2 && distance <= 10) {
          Serial.println("spoke!");
          previousSpokeMillis = currentMillis;
        }
        else {
          Serial.println("No spoke");
          previousNoSpokeMillis = currentMillis;
        }
      }
      if (distance >= 200 || distance <= 0) {
        Serial.println("Out of range");
      }
    }
 
  //If Spokeinterval amount of ms pass without seeing spoke (or if it keeps seeing a spoke)
  if (currentMillis - previousSpokeMillis >= Spokeinterval || currentMillis - previousNoSpokeMillis >= Spokeinterval) {
    Serial.println("No movement detected, turning off");
   
    //send signal to LED to turn off
    ledState = LOW;
    Serial.println("Turning LED off");
    delay(10000); //also stop the millisecond timer? and reset it when the button is pressed? only let most of the program run when the led is on?
    //stop sensor from checking distance
    //activate secondary led for 3 seconds to indicate lamp turning off
  }


  if(lastButtonState == HIGH && currentButtonState == LOW) {
    Serial.print("The button is pressed: " );


    //toggle state of LED
    if(ledState == LOW) {
      ledState = HIGH;
      Serial.println("Turning LED on");
    }
    else {
      ledState = LOW;
      Serial.println("Turning LED off");
    }


    //control LED according to the toggled state
    digitalWrite(LED_PIN, ledState);
    Serial.print(ledState);
  }
}

Finishing Up Code

40fbc3ed-ed41-4de3-9df9-188709fabb72.png


The bulk of the code is now done, but there are a few things that could be improved:

  • The code for the ultrasonic sensor was always running, even when the LED was off, so I added an if statement so the code only runs when the LED is on.
  • I added in the secondary (blue) LED and wrote a few lines of code so that it would activate for three seconds before the bike light turns itself off. I plan on placing this LED on the handlebar, so it can let the cyclist know that the light has turned off automatically.
  • The code was getting pretty cluttered, so I tried to sort everything in functions.


  • Another problem I encountered is that when I tried to turn the lamp back on after it had turned off automatically, it would be turned off again immediately. This is because when you turn off the light, even though the light doesn't appear to do anything anymore, the code is still running. This means the millisecond timer is still going up. It took me a while to wrap my head around this one for some reason, but I ended up creating another variable that stores the last time the LED was activated (previousActivationMillis), detracted that from the current time to see how long it had been since the lamp had been turned on, and used that to check how long it had been since a spoke was last detected. (Is this getting confusing? My head sure is starting to smoke a little) Anyway, I reccomend to just look at the code below, if there is something you don't understand you can always post a comment and I'll try to explain it better. Also, if you know a better way of doing something please let me know! I have a lot to learn still :)


The Final Code (woah!):

  //ITTT project Bike Light (23-04-2023)


  //CREDITS:
  //light switch tutorial by : TheGeekPub.com
  //Ultrasonic sensor tutorial by: techexplorations.com
  //blink without delay tutorial by: docs.arduino.cc


//constants
  const int BUTTON_PIN = 12; //light
  const int LED_PIN = 11;
  const int SIGNAL_LED_PIN = 2; //light on handlebar of bicycle


  const int TRIG_PIN = 9; //ultrasonic sensor
  const int ECHO_PIN = 8;


  const long interval = 250; //interval at which to check whether spokes are moving
  const long SpokeInterval = 10000; //time after which the lamp turns itself off if no movement is detected


  //variables
  int ledState = LOW; //tracks current state of LED
  int lastButtonState; //previous state of the button
  int currentButtonState; //current state of button


  unsigned long previousMillis = 0; //will store last time code was executed
  unsigned long previousSpokeMillis = 0;
  unsigned long previousNoSpokeMillis = 0;
  unsigned long currentMillis = 0;


void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  pinMode(SIGNAL_LED_PIN, OUTPUT);


  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);


  currentButtonState = digitalRead(BUTTON_PIN);
}


void loop() {
  lastButtonState = currentButtonState; //save last state
  currentButtonState = digitalRead(BUTTON_PIN); //read new state


  currentMillis = millis(); //amount of milliseconds that the code has been running for


  //check if button is pressed
  if(lastButtonState == HIGH && currentButtonState == LOW) {
    Serial.print("The button is pressed: " );


    //toggle state of LED
    if(ledState == LOW) {
      ledState = HIGH;
      Serial.println("Turning LED on");
      previousSpokeMillis = currentMillis;
      previousNoSpokeMillis = currentMillis;
    }
    else {
      ledState = LOW;
      Serial.println("Turning LED off");
    }


    //control LED according to the toggled state
    digitalWrite(LED_PIN, ledState);
    Serial.print(ledState);
  }


  //only exectute the following code if the LED is ON:
  if(ledState == HIGH) {
    //activate ultrasonic sensor
    UltraSonicSensor();
   
    //If SpokeInterval amount of time passes without seeing spoke (or if it keeps seeing a spoke) turn off the LED
    if (currentMillis - previousSpokeMillis >= SpokeInterval || currentMillis - previousNoSpokeMillis >= SpokeInterval) {
      TurnOffLight();
    }
  }
}


void UltraSonicSensor() {
  if (currentMillis - previousMillis >= interval) { //interval in which the code below  is executed, execute after interval amount of milliseconds
    previousMillis = currentMillis; //save last time code was executed


    long duration, distance; //create 2 variabeles
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    duration = pulseIn(ECHO_PIN, HIGH);
    distance = (duration/2) / 29.1;


    if (distance >=0 && distance <= 200) {
      Serial.print(distance);
      Serial.println("cm");


      if (distance >= 2 && distance <= 7) {
        Serial.println("spoke!");
        previousSpokeMillis = currentMillis;
      }
      else {
        Serial.println("No spoke");
        previousNoSpokeMillis = currentMillis;
      }
      }
    if (distance >= 200 || distance <= 0) {
      Serial.println("Out of range");
      previousNoSpokeMillis = currentMillis;
    }
  }
}


void TurnOffLight() {
  Serial.print("No movement detected for ");
  Serial.print(SpokeInterval / 1000);
  Serial.println(" sec");
 
  //activate secondary led for 3 seconds to indicate lamp turning off
  digitalWrite(SIGNAL_LED_PIN, HIGH);
  delay(3000); //Blue LED is on for 3 seconds to alert cyclist that light is turning off, however, the cyclist can not turn the light on or off during this time period
  digitalWrite(SIGNAL_LED_PIN, LOW);


  //send signal to LED to turn off
  ledState = LOW;
  digitalWrite(LED_PIN, ledState);
  Serial.println("Turning LED off!");
}

*note that the time after which the lamp turns itself off is set to 10000ms = 10sec for testing purposed. If you want to change this to a few minutes (which is reccomended when actually using the project as a bike light) you can set the SpokeInterval to a different amount of milliseconds.

Testing the Light on a Bike

IMG_20230427_135600.jpg
IMG_20230427_140357.jpg
IMG_20230427_140344.jpg
Arduino Bike Light - Test 1
Arduino Bike Light - Test 2

Now that the code is finished, it's time to test the project on an actual bike.

I mostly wanted to test whether the ultrasonic sensor would detect the spokes. It didn't, so I taped a piece of cardboard to the spokes and checked whether it would detect that, which it also didn't sometimes (this can be seen in the first video). I then changed some of the timings of the sensor so it would send a pulse out more often. This seems to have fixed it. In the second video I swapped the piece of cardboard for reflectors that can be fastened to the spokes of a bike. You can see that the sensor is now working as intended, writing "spoke!" in the Serial Monitor every time it detects one of the reflectors.

Finishing Touches

Arduino Bike Light - Test 3
f8e713c2-1211-4678-b017-bae6b938737e.png
IMG_20230520_132340.jpg
IMG_20230520_132934.jpg
IMG_20230520_134754.jpg
IMG_20230520_134805.jpg
IMG_20230520_134812.jpg

I unfortunately don't have any video's or pictures of the finished project, as it was a particularily rainy day when I had the project presentation and I didn't want anything to get water damage while cycling home, so I dismanteled the whole thing before realizing that I hadn't documented the final result. I do however have a video of testing the light after it was soldered. The video is slightly sped up because waiting ten seconds for the light to turn off gets boring fast. (Also, halfway through the wire connecting the blue LED comes loose, which is why it doesn't turn on at the end of the video, I fixed this immediately afterwards).

Like I said the video was taken after soldering, which is what I want to talk about in this step. So far all the circuits have been on breadboard. To finish up the project I needed to attach everything together properly using solder, and then build a casing to hold the arduino and the battery. For the actual bike light part I used the existing casing of my broken bike light.

Soldering

At this point I divided the circuit in three parts: I soldered the main part of the light with the button and the red LED onto an experiment PCB. I connected a few male-female Dupont wires together and connected those to the ultrasonic sensor and directly to the Arduino. And lastly I soldered the blue LED and a 220 ohm resistor to the 1,5m wires, connecting one to the ground line on the PCB, and one to pin 2 on the arduino.

Casing

For the casing I used a small plastic box that fit perfectly between my pannier rack and mud guard. If you don't have enough space there you can also put it on top of the pannier rack or underneath the saddle. I drilled two holes into the box for the wires to go through. One for the ultrasonic sensor and one for the PCB with the LED and the button, which the one wire going to the blue LED also goes through. At this point time was running out, so I have to admit that I attached most of the wires to the bike using tape and pieces of rope. Not proud of it, but it is the way it is. Like I said I used the existing casing of a broken bike light to cover the PCB and everything attached to it. Before soldering I sawed the PCB in two so it would fit into the casing. I also placed the button strategically so it would line up with the little rubber button on the casing. Unfortunately I couldn't reattach the back of the casing since the wires had to have a place to come out, which the casing didn't accomodate for. If I had had more time I think I would have drilled a hole in the back of the casing to let the wires through, or 3D printed an entire new backside for it.

Waterproofing

IMG_20230520_144557.jpg
IMG_20230520_131922.jpg
IMG_20230520_144619.jpg

This is a step that I unfortunately didn't get to myself. But it did want to include it in the instructable since it's pretty crucial for a bike light to be waterproof. I have a few idea's, but additional idea's/tips are very much welcome.

Like I said in the previous step, I could not reattach the back of the bike light casing, which meant that the back of the PCB was completely exposed to the elements. I already mentioned drilling a hole in the original casing and 3D printing an entire new casing as possible solutions. A third option could be to simply attach a plastic sheet to the front of the casing that covers the circuit.

The plastic box that I placed the arduino and the battery in is already pretty waterproof by itself, but the holes in the sides where the cables go through could probably be a little more waterproof if you filled it up with... something. I think rubber seal would work?

The last things to waterproof are the exposed parts of the wires. A fellow student had the genius idea to put the wires in tubes, which I tested and it works pretty well! At least for short wires. For the blue LED this probably wouldn't suffice however, it probably needs it's own small casing.


Thats it! Thank you for reading all the way up to this point. I learnt a whole lot throughout this project, and I feel like I now have some basic skills, like programming and soldering, that will enable me to do many more projects like this in the future. Whether you are a teacher that has to grade this project, or someone who wants to construct their own bike light, I hope you enjoyed the read!

Credits

Arduino: Use a Button to Toggle an LED

The circuit diagrams for this instructable were made with TinkerCad


Arduino: Use a Button to Toggle an LED tutorial: https://www.youtube.com/watch?v=VdB4GWeVkvY&t=181s

Ultrasonic sensor tutorial (step 2): https://techexplorations.com/guides/arduino/sensors/ultrasonic/

Blink without Delay: https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay