How to build the ultimate bicycle safety light system
How to build the ultimate bicycle safety light system
This project started off innocently enough; I just wanted to add one permanent light to my bicycle for when I ride at night. The idea snowballed from there and I now have a super fancy bicycle safety light system using an Arduino strobe controller shield, which I designed just for this purpose.
So here’s the skinny on what my bicycle now has:
- Six light modules
- Five light channels
- A strobe controller module
- Some batteries to power the thing
The strobe controller module is a PCB shield I designed and got manufactured specifically for this project, and is actually the first PCB I have designed. I am very pleased with how it turned out. Here are the features of the strobe controller:
- Up to eight output channels
- Up to 600mA output per channel
- Uses an Arduino nano or Arduino micro
- Uses 6-20v input
- Twelve possible modes with the 4x3 keypad (or more if you are handy with the code)
With as much time and effort as I have spent on this project, it seems a little silly for it to just be me and my bike with one light system, so I am writing this Comment/How-to in the hopes that you guys can make one of your own! I am selling kits so can build your own (info is in the last page). This project turned out to be somewhat lengthy with a lot of steps, so here's what you can expect to see in this Comment/How-to:
- Designing the lights
- Assembling the lights
- Installing the lights
- Designing the strobe controller shield
- Assembling the strobe controller
- Installing the strobe controller
- Designing the software
And as a bit of a teaser, here's what the lights look like on the finished product:
While my original idea consisted mainly of a single red light to the rear, my final design had five lighting channels controlling six light modules:
- Front center
- Rear center
- Front left
- Front right
- Rear left
- Rear right
The channels I decided to run are:
- Left (L)
- Right (R)
- Center (C)
- Blue (B)
- Rear red (RR)
Now, you'll notice that I have six modules but five channels, so what gives? There's a little bit of overlap. The blue (B) and center (C) channels run the center modules identically on the front and the rear, the rear red (RR) channel is wired to both the left and right rear modules, and lastly, the left and right channels are used for the left front and rear, and the right front and rear, respectively. In other words:
- Front Center: 6 white LEDs (C), 1 blue LED (B)
- Rear Center: 6 red (C), 1 blue (B)
- Front Left: 4 amber (L)
- Front Right: 4 amber (R)
- Rear Left: 4 amber (L), 2 red (RR)
- Rear Right: 4 amber (R), 2 red (RR)
It helps to look at the picture.
Now is a good time to address something that I'm sure plenty of you have noticed: there are blue lights on my safety strobe system. Isn't that impersonating an officer or something?
Good question. I decided to feature blue lights on the front and rear because I find the color blue to be especially attention-grabbing at night. However, because I don't want any trouble with the law, I am only using one blue LED on the front and one on the rear, compared with 14 other LEDs on the front and 18 others on the rear. Furthermore, I have made only sparing use of the blue lights in the various strobe patterns. I am comfortable using this light, but if you have reservations about the colors, I recommend simply omitting blue from your project. See the last step for the reactions I have noted from police.
Light design, electrical
I used red, amber, blue, and white LEDs for the light modules. The red and amber LEDs have a recommended current of 30mA, while the blue and white are recommended to take 20mA.
I am using two li-ion cells in series to power this project because that gives me a nice voltage range: 8.4v when full, down to ~6v when empty. The Arduino nano has a minimum input of 6v, so that happens to work out nicely.
I decided to pair up the amber and red LEDs in series to get a total forward voltage of 4.4v, but to leave the blue and white LEDs as single diodes for a forward voltage of just 3.3v. If I put two blue in series, the forward voltage would be 6.6v for the pair, which would get very dim with low batteries.
For the red/amber, I calculate that I need a ~150 ohm resistor in series with the pair to get around 30mA with a full battery. 8.4v - 4.4v = 4v across the resistor, and ohms law (V = IR, R = V/I) says that to get 30mA from 4v, I need 4 / 0.030 = 133.3 ohms. I used the nearest common value of 120 ohms.
For the white/blue, I need ~250 ohms. 8.4v - 3.3v = 5v. 5v / 0.020 A = ~250 ohms.
I decided that I was going to use a common ground in my light modules, meaning that I would provide a switched positive signal to each unit/channel. I designed the strobe controller module to provide just this.
The specific LEDs I used are:
- Two in series for amber and red
- Always test the lights BEFORE you cast them permanently in a block of plastic...
- Stick the LEDs in holes punched in the rubber
- grounds are connected
- Seal the bottom end, then pour the resin
- Peeling this off was as satisfying as it looks
To build the light modules, I soldered the diodes/resistors together, and wired all the LED/resistor sets, then jammed everything in a mold.
For the left and right modules, I used a piece of rubber hose as the mold, and for the center modules, I used a 3/4" PVC cap.
For mounting the side modules, I added a piece of 1/8" steel rod with a kink inside of the plastic. For the center modules, I just cast an M6 bolt in the resin, sticking out the back.
After wiring the lights and installing them in the mold, I poured casting resin over the lights, resistors, wires and all, then set it in the sun to cure.
The resin I used is called Solarez, and I like how it contains a UV-activated catalyst so there's no fiddling with mixing anything: just pour it and set it in the sun. The can claims it will cure in 3 minutes with sunlight, but I found it to be more like a whole afternoon. Nonetheless, it cured very hard and I am pleased with the results.
Here are some videos of me assembling the center modules
Light installation, front
- Convenient mounting holes
- Convenient mounting hole users
- Mounting hole for M6 bolt on light module
- Convenient holes conveniently used
- All mounted
- These wires will sit behind the light
- Mounting bolt
- Light goes here
- This won't hurt a bit...
- I know they're ugly. I should get a TIG already
- One sheet metal screw
- Post for rigidity
- Hide wires under grip tape
- Feed wires through rubber
- Hidden wires!
Mounting the lights is the part of this project that will vary the most between one bike and the next, but for completeness, here's how I mounted the lights on my bicycle.
For the front center module, all I needed was a simple triangular piece of sheet metal with 3 holes - two line up with the handlebar clamp, and the last one serves to receive the bolt on the center light module.
The front left/right modules were a little more complicated. After contemplating how to put the lights on my "ram's horn" handlebars without getting in my way, I decided to mount them sticking out from the very top front of the plastic brake actuator bodies. I used a washer in conjunction with the mounting rod so that a single screw could clamp the modules without letting them spin. The pictures are really the best way to describe this.
Light mounting, rear
- Side view
- Top view
- Add a plate (normal to this view, horizontal when mounted)
- Seat tube goes here
- Add reinforcement
- Rubber for protection
- Bolt for clamping purposes
- Needs some threads
- More ugly welding
- Connect grounds
- Quick clean up on the grinder and a coat of paint
- Metric hardware is better than SAE
- Big bulky wire bundle
On the rear, I no place to mount the lights without some sort of a clamp-on fixture. I didn't take pictures as I built it, so my "artistic" rendering will have to suffice. It is made with some 16ga sheet metal. The fixture clamps onto the seat tube with a piece of rubber hose as extra friction and scratch protector. The rear center module just bolts straight onto the fixture, and the rear left/right modules got an M5 bolt welded onto their rods so they could bolt straight on also.
Once the lights bolted into their appropriate holes, the wires were connected where appropriate (grounds were consolidated, as well as the two RR light leads), and a bit of tape was added to protect the wires where they go over sharp edges. The wire bundle was bulkier than I anticipated, but that's just what I get for choosing five lighting channels.
When I first started this project, I was considering a single light on the rear, perhaps made to blink using a 555 timer. The 555 train of thought led me to consider stringing two timers together to get an intermittent strobe. Then I thought about stringing together three... and I realized that the hardware was getting very complicated very fast, because around the same time started considering adding more independent channels with their own patterns. And if I was going this far in complexity, why not throw in a couple different modes, just because I can?
If you think a good option to simplify this would be an Arduino, you and I are in agreement. I decided to use the Arduino nano due to its tiny size and low price. It uses the same atmega328p chip and is functionally identical to the Arduino uno, except that it uses miniaturized components, so it is very small. The nano has been discontinued by the Arduino brand, but you can still buy it directly from the company which designed it, Gravitech. Alternatively, generic/knockoff nanos go for about $5 on eBay (and that's what I'm using).
The Arduino micro will also work in the shield, because it has a backwards compatible pinout. Just make sure to plug it into the header sockets so that the correct pins line up; it will have two extra pins on each side, which will just hang out, not connected or anything.
The lack of real estate on the Arduino nano makes it obvious that I needed a shield to interface between the Arduino and the light modules. The nano can source 5v from the output pins, but with a maximum of 40mA, while I wanted to use a little more than that for each channel. So the shield needs to step up that output.
Also,there is still the question of how to change modes. I have seen matrix keypads used in Arduino projects before, so, without any knowledge of how to implement one, I decided to use a 4x3 membrane switch, which has 7 wires (one for each row and one for each column). On the next page I'll discuss how I interfaced between the keypad and the Arduino.
Hardware design, schematics
- Amplifier unit
The transistor switch works like this: a high signal (5v) comes from the digital pin through the base of the NPN transistor. This turns on the NPN and sinks current from the base of the PNP transistor. The result is that the PNP transistor is turned on and allows current to flow to the lights, with a much higher current than the Arduino can provide. The PNP transistor I used is a pn2907 which has an absolute maximum current of 800mA. Derating this to about 600mA should keep the PNP transistor below its maximum power dissipation. Still, that's a huge increase over the 40mA from the Arduino.
Now, as far as reading input from the keypad, I found out that there are basically two ways to read input from a matrix switch: digital and analog. The digital method requires a digital pin for each wire (for a 4x3, that’s seven pins). At the time I designed the PCB, I hadn't realized that the Arduino's analog pins can be used as fully functional digital pins, so, feeling a shortage of digital pins (14 - 8 = only 6), I implemented the analog, one wire method, which essentially puts an array of resistors on the keypad such that each key generates a unique voltage. You can read more about it here.(and the next PCB design will use the digital method).
Note: Something nice about the keypad interface is that it can work with any size of matrix keypad, as long as it's 4x3 or smaller. If you wanted a smaller keypad, you could always go with the 4x1; the only downside is that the software maps one mode to one button, so you would only have four modes with a 4x1 keypad (unless you tweaked the software).
Anyway, on to building the circuit!
PCB design and ordering
I have to give a huge shout out to Dangerous Prototypes for making my boards. They had (by far) the best price I found in weeks of searching for a supplier. I got my boards about 6 days after I submitted the order using express shipping. They are the same people as "dirty pcbs" and "seeed studio". They have some top notch DIY stuff and I recommend giving them a look.
Once I knew what I needed my circuits to do and how I wanted to build them, all that I needed to sort out were the traces for each circuit element. There were eight amplifier units for eight channels and one voltage divider for the keypad. I compared some software options, and ultimately used fritzing, which is free but powerful. It has a schematic, breadboard, and PCB view, with a "rat's nest wire" feature that makes it really easy to lay out the traces after creating the schematic. The people at fritzing do offer a PCB manufacturing service, but I preferred the price/quantity that Dangerous Prototypes offered.
One cool thing I noticed about fritzing was that, while searching for a specific feature, I would find years-old forum posts asking about this feature or that, and the developers would say "we don't have that feature yet." But when I searched a little further, I found that they had actually added the feature since then. So they have an active and interactive development team.
I spent a lot of time tweaking this board until I was happy with it, and went though about 7 major revisions over the course of a week or so. So that you can replicate this project, I am selling the PCBs alone or in a kit. Info is in the last step.
So what goes where?
- Q1-Q8 are a 2n2222 or 2n3904 transistor (NPN ebc)
- Q9-Q16 are pn2907 or 2n3906 (PNP ebc) (but the 3906 has lower current capacity)
- R1-R8 are 100ohm
- R9-R16 and R20-R22 are 1k ohm
- R17-R19 are 4.7k ohm
- J1-J7 are 1935161 or similar screw terminals, 5mm pitch
- C1, an optional filter capacitor to help shield the Arduino from noise. It should be at least double your battery voltage and ~1000μF
- Arduino has header sockets, not shown in the photos
- keypad connector has header pins
R1-R16 are installed flat against the board, while R17-R22 are installed in the "stand up" orientation. I recommend installing the resistors first, the header pins/sockets, then the transistors, then the remaining components.
The Arduino needs to be soldered into the strobe controller or installed using header sockets. I went with the header socket method, but that was after taking pictures. The Arduino connections will be sketchy if you just put it in the board with no assistance.
Put it in a box
- New box, needs some holes
- Center the PCB
- mark a hole
- hidden Mark
- 1mm holes
- Remove PCB posts
- Smooth underside
- Place standoffs
- #2 sheet metal screw
Once you have the PCB assembled, and unless you live in the Mojave Desert and like the components to be hanging loose, you'll need to put everything in a water-resistant case. I found that a Hammond 1591XXC fits the board nicely, and it just happens to carry an IP54 rating.
Use the board as a template to mark the four hole locations on the lid of the box, then drill them with a 1/16" or 1mm drill bit. Use a chisel to remove the PCB mounting posts from the lid, and use 6mm spacers on #2 sheet metal screws to mount the board.
Use #2 sheet metal screws to hold the board down, on top of the spacers.
Add a battery holder
- Case lid screw hole doing double duty
- Other lid screw hole doing double duty, as well
- Battery wire hole
- battery wire hole, in use
- Battery positive - center
PCB positive - side
- Connect to PCB. Watch the polarity!
- Wires coming through the hole
- Cable gland
- Longer lid screws to accommodate the battery holder
With the PCB mounted in the lid of the case, let's add the battery holder. We will use the case lid screws to mount the battery holder, except that we need slightly longer ones than are included with the case.
In the battery holder, drill two holes through the base of one cell cavity that align with the case lid mounting holes.
In the lid, drill one hole under the battery holder for the wires to go through. In the case box, drill a hole for the switch to mount. This switch could go anywhere according to your preference, but I decided to put it on the rear of the box - the side which will have the keypad ribbon.
Feed the battery wires through the hole and extend the positive lead a few inches to reach the switch on the other side of the box. Then add a wire from the switch to the positive input on the board. The ground lead from the battery holder goes straight to any ground connection.
Holes for wires and switch
- Switch in place
- Cable gland in place
- Place in place
- PCB in place
- Battery cells out of place
The power switch needs a hole to mount, and we also need a way to pass the wires through the box.
For the switch,use a 1/4" drill.
For the wire hole, use a 1/2" bit to accommodate the cable gland (it's a weird name, I know). There is a nut that goes on the back side, so don't put the hole too close to the edge.
Velcro mounting straps
- 10" double side straps
- Holes predrilled into the center of the PCB posts
- Melt two small holes through the straps to accommodate the screws
- Soft side up
- Almost ready to hang on the bike
Who doesn't like velcro? The strobe controller box hangs from the top tube using a couple hook and loop straps, and the straps are attached to the box using plain ol' sheet metal screws.
Drill 1/8" holes through the PCB posts in the box, so that there's a little more "meat" for the screws to grab. Use a soldering iron or awl to punch holes through the two velcro straps, then screw them onto the box, soft side up (for the bicycle paint).
Wire the lights
- I found it easier to feed the wires through the nut first
- Mess of wires wired appropriately.
- RTV blob. Yes that's the technical term.
- Squeeze the RTV in between the individual wires
- Slide the RTV section back into the gland. Tighten the nut.
- Lid and battery mount screw (other one is not visible)
- I've been waiting a long time to peel this off...
- Hey, doesn't look too shabby
You should have your lights mounted and the wires run to the rough location of the Strobe Controller box, plus at least 6" of extra wire. Run the wires through the cable gland, then connect the individual wires into their appropriate slots. The strobe controller provides 5 ground slots and 8 channel outputs (I only used 5).
Normally, a cable gland is used only with a smooth, jacketed wire, and it seals effectively on the jacket, but I am using a bunch of individual wires. To keep this water tight, I backed out the wire bundle just a little bit, added some silicone RTV all through the bundle, then slid everything back into the gland. While you have out the RTV, go ahead and glob on a bit to the battery wires where they enter the box.
Aside from being waterproof, the rubber gland provides a nice anti-stress clamp on the wires. Don't go crazy when tightening it - very finger tight is all it needs, or lightly snug with a wrench.
Before you close up the box, load the software on the Arduino and install it in its socket. Speaking of software...
Software design, under the hood
First off, a big shout out to TimothyBeenefor doing the heavy lifting in terms of developing the code for this project.
When I started designing this strobe controller, I was indeed familiar with the basic "blink" sketch, but in terms of making multiple lights flash simultaneously, each with their own patterns, I was not so well prepared. No matter, this was a learning opportunity! TimothyBeene and I wound up using the "millis()" method outlined here, modified to be a little more "modular."
First, the patterns are defined in an integer array, one for each channel, as millisecond values of times that the channel should stay in its state. The state is determined by the sign of the interval: positive values are "on" times, and negative values are "off" times. The lists are terminated with a zero because they are arbitrarily long arrays; there is no built in terminating character for an array like this, so the program will read intervals until it gets to the zero that we put at the end. This is kind of like the "\0" in a string. These patterns are in a separate file to make the code a little neater.
Each mode has its own interval array (pattern) for each channel used in that mode, and the mode accesses the individual interval arrays through an interval array array (sort of a two dimensional array), such as modeX_data.
Because there are one pattern for one channel, up to 8 channels per mode, and 12 modes, there are up to 96 individual patterns. If each pattern were 50 intervals long (a high estimate), there could possibly be 4800 int values being stored to keep track of the patterns. At 2 bytes per int, that's 9.6KB. This doesn't seem like a big number to anyone who didn't start programming in the '70s, but it is too big to store in the atmega328's ram, which maxes out at 2KB. Fortunately, the onboard flash is 32KB, so we can force these variables into the flash using the word PROGMEM. Variables declared with PROGMEM cannot be changed after burn-in, but that's a-ok for this application.
Anyway, now that we have all the patterns taken care of, we need to do some actual flashing of lights. When the program starts, or when a key is pressed, a particular mode object is created, called with the appropriate array directing it to its patterns and the pins used by that mode. The mode object will create a channel object for each pin listed in the mode call. Each channel then handles one pattern array.
The actual work of flashing the lights happens in the channel object. The channel grabs the first interval in the pattern and checks the sign. If the sign is positive, it sets the pin HIGH, and if the sign is negative, it sets the pin LOW. The channel records the system time (from millis()) that the channel state was set, and every time that the thread loops back around, it checks to see if the difference between nowand the last time is greater than the absolute value of the current interval, and if it is, the channel will read the next value, check its sign, and do the same thing again. Once the last interval is reached, the channel loops back and starts with the first interval in the pattern.
All of the channels are operating simultaneously, of course, so the thread loops through all of the channels, checking if enough time has elapsed to advance the interval.
Software design, customization
So let's say you build this, but you just don't really like the patterns, or the pin assignments. We wrote the code with the intent that it could be customized fairly easily, hopefully in a way that is at least a little intuitive.
Let's recap from the last step. I'll discuss just mode 0 for simplicity. Each channel is pointed at an array of millisecond intervals, mode0_1 for the first pattern. Positive values are "on" and negative values are "off," comma separated, and the list must end with a zero for the last value. Each channel is assigned a pin number in the array mode0_pins (the first value is the pin number for the first pattern, second value is pin number for second pattern, etc).
Nothing says that the patterns must be synchronous, but if you want your pattern to stay in sync, you need to make sure that the total period of each pattern matches. Otherwise your patterns will become offset within a mode. A great example of this is the difference between mode 1 and mode 4. They are very similar, both having one "off" time and one "on" time, but pattern 1 has the same period for all lights, while 4 has the same "on" times but slightly different "off" times. You can see a big difference between the two patterns in the demo video below (they are mapped to 2 and 5 on the keypad).
The shortest interval that seems to still work nicely is about 5 milliseconds. If you include any intervals much shorter than this, they won't look right, and won't stay synchronized.
This project took about 7 weeks from the first idea to the finished product (I spent plenty of time waiting on shipping from China), and I learned a whole lot in the process. I have gotten plenty of looks, nods, smiles and waves with the lights flashing, so I think it accomplishes the goal: attract drivers' attention. However, I have almost been run over once while running these lights, which goes to reinforce a rule of bicycling: no matter what you do, you have to keep an eye on cars.
I should note that the headlight on this setup, just six regular white LEDs, is not bright enough to illuminate the road ahead of me, but that isn't the point; it's supposed to grab attention. I still use a very bright headlamp on my helmet, and I also make a habit of using it to grab the attention of drivers who I don't think have noticed me.
In terms of the reaction from local law enforcement, I have noted the reactions from police in various municipalities, including some pretty small towns. At night, with all the lights blazing, I have ridden near different police officers who did nothing more than give a disinterested stare. If I were to wager a guess, they are probably just happy that I have lights to grab drivers' attention.
When I was designing this strobe controller system, I had the intention to develop it to the point I could sell it as a DIY kit. I intentionally purchased more PCBs than I needed (I only needed one) so that I could sell them to fellow makers in the hope that other bicycles (and other vehicles, signs, or whatever other applications) would become a little more attention-grabbing with the addition of a Strobe Controller. Information about buying this kit is in this forum post.
I am looking forward to your feedback in the comments. How could I improve? How does it look on your bicycle? What would you change when building it?
Thanks for reading!
Future work and improvements
In the course of building this project and writing this Comment/How-to, there have been many small changes that I would implement if I were to build it again.
- The mount for the lights on the rear is kind of big and wobbly. It should really be trimmed down a lot smaller.
- The side light modules should be mounted on a smaller diameter rod, perhaps a bicycle spoke. Furthermore, integration of a spring would make the lights resilient against breakage when the bike falls over.
- The strobe controller V1.1 PCB will no longer use the "oneWireKeypad," but rather the fully digital "standard" method of reading keys. I will probably also use mosfets for switching the lights. I think I will also include a way for the arduino to detect the battery voltage and switch to a low power pattern automatically when the batteries get low, as I have found that the arduino will run well below the rated 6v input, and the lights just get dim.
Acknowledgments, Credits, and Bibliography