Projects

Kids' Basics - Emergency Vehicle Lights

Dan Koch

Issue 64, November 2022

This article includes additional downloadable resources.
Please log in to access.

Log in

We drastically expand on the red and blue flashing lights from Issue 19 with the use of Arduino.

All the way back in Kids' Basics for Issue 19, we made a set of red and blue flashing lights. They were designed to go with a choice of three printed artworks of emergency vehicles, or be built into a physical toy if you were up to the task of modifying it. However, that project was based around the NE555 timer IC and was just one red and one blue flashing light, with one light on, then the other.

For decades, flashing lights on emergency vehicles were round, with a light globe fixed in the middle of a reflector that rotated around it, focusing the light into one beam. This means the light is concentrated and therefore can be seen from further away, but also gives the flash necessary for the lights to stand out, because light can only be seen while the reflector is facing you. As time went by there were variations of this: Tow trucks in particular often had a bar of many lights in one clear coloured housing, each with their own reflectors.

These reflectors were motor-driven, sometimes directly but often with a belt. That meant that the lights on either side of a police car, for example, did their own thing. The motors were never exactly the same so the number of rotations per minute was slightly different even for lights from the same manufacturer. Watching two lights next to each other, you would see the flashes opposite each other, then slowly closer together until they were flashing at the same time, then gradually back to opposite.

LEDs changed all that. Not only are the lights now much lower in electrical power demands for the same brightness as filament bulbs, but there are no moving parts: The LEDs are directional and focused, and are just turned off and on, so there is no rotating reflector. This means more reliability but also means flash patterns can be more complex and attention-grabbing. Further, the lights can be controlled from one source, so the flash rate can stay the same for each light in the group.

That's the kind of light we are going to make in this project: A set of eight lights, with four red and four blue, with drive transistors for each so that the LEDs do not overload the Arduino when they're all on. Then, we will write some code that turns the LEDs on and off in different patterns, just like you see on real emergency vehicles. By the way, if you don’t want red and blue lights, you could make them all yellow for example, like the ones found on roadside work vehicles, electricity authority services trucks, and so on.

The build itself is super-simple this month, with very few steps. It's the code where things really happen this time. The build also stays on the breadboard. If you want the lights in a real toy, we'll direct you to previous issues which you can access for free, to see step-by-step how that is done.

SOME HELPFUL HINTS

We encourage you to read all the way to the end of the article before you build. Not only will you then have a better feel for the overall picture as you build, but we sometimes discuss options or alternatives that you will need to have decided on. You will need some basic hand tools for most builds. Small long-nosed pliers and flush-cut side cutters meant for electronics are the main ones. Materials like tape or glue are mentioned in the steps, too. We always produce a tools materials list if you have to go shopping, but anything that is lying around in most homes is just stated in the steps.

As always with Kids' Basics, we avoid soldering to make the build more accessible to more people, but having an adult around can still be helpful. You won't need any particular skills besides being able to identify components at a basic level, and even then, we help as you go along. If, for example, you don't already know what a resistor is, you'll probably be able to work it out from the photos and description in each step.

We do provide a schematic or circuit diagram but this is just helpful if you already know how to read one. Don’t stress if you have never learned, but take the chance to compare the digital drawing of the breadboard layout (which we call a 'Fritzing' after the company that makes the software) to the schematic and see if you can work some things out. You can make this project from the Fritzing and photos alone. You might also like to check out our Breadboarding Basics from Issue 15.

The Build:

Parts Required:IDJaycarAltronicsPakronics
1 x Solderless Breadboard-PB8820P1002DF-FIT0096
1 x Packet Breadboard Wire Links-PB8850P1014ASS110990044
10 x Plug-to-Plug Jumper Wires *-WC6024P1022ADA1957
1 x Arduino-Compatible Uno-XC4410Z6240ARD-A000066
4 x 68Ω Resistors *R13 to R16RR0544R7530DF-FIT0119
4 x 100Ω Resistors *R5 to R8RR0548R7534DF-FIT0119
8 x 1kΩ Resistors *R1 to R4. R9 to R12RR0572R7558DF-FIT0119
8 x BC547 NPN Transistors *Q1 to Q8ZT2152Z1040-
4 x Red High-Brightness LEDs *LED1 to LED4ZD0154Z0863ADF-FIT0372
4 x Blue High-Brightness LEDs *LED5 top LED8ZD0180Z0869DF-FIT0372

Parts Required:

LEDs may be any colour of choice depending on end use. * Quantity required, may be sold in packs. ^ May not have the same dimensions as the part we used.

Step 1:

Place the breadboard in front of you, with the outer red (+) rail facing away from you and the outer blue (-) rail closest to you. Install two wire links, one joining the two red (+) rails and one joining the two blue (-) rails.

Step 2:

Insert eight BC547 NPN transistors with their flat sides facing you, in the lower set of rows. Install a wire link from each of the right-hand legs (the emitters) to the lower blue (-) rail.

Step 3:

Place eight 1kΩ resistors (BROWN BLACK BLACK BROWN SPACE BROWN), one from the middle leg of each transistor (the base) across the gap in the middle of the breadboard.

Step 4:

Install four red LEDs from the left-hand leg (the collector) of each transistor starting from the left-most one. Place the short negative (cathode) leg to the transistor and the long positive (anode) in a row on the other side of the gap.

Step 5:

Install four blue LEDs from the left-hand leg (the collector) of each transistor starting from the right-most one. Place the short negative (cathode) leg to the transistor and the long positive (anode) in a row on the other side of the gap.

Step 6:

Insert four 100Ω resistors (BROWN BLACK BLACK BLACK SPACE BROWN) from the upper red (+) rail to the long anode leg of each red LED. Add four 68Ω resistors (BLUE GREY BLACK GOLD SPACE BROWN) from the upper red (+) rail to the long anode leg of each blue LED.

Step 7:

Plug a pair of plug-to-plug jumper wires into the power rails, using a light colour for the red (+) positive rail and a dark colour for the blue (-) negative rail. Also plug in eight more, one to each 1kΩ resistor.

Step 8:

At the other end, plug the dark power rail wire into the GND connection on the Arduino, and the light power rail wire to the 5V connection. Plug the wires for the red LEDs into pins 2, 3, 4, and 5. Make sure they stay in order. Plug the wires for the blue LEDs into pins 8, 9, 10, and 11.

SETUP AND TESTING

Download the sketch from our website and open it into the Arduino IDE. Connect the Arduino using its USB cable to your computer, and use the Tools > Port menu to choose which COM port has your Arduino on it. Upload the code, and after a delay as the upload happens, you should see red and blue flashing lights. We have more detailed instructions for uploading code in Kids' Basics Issue 17.

If you do not get any light at all, start by checking the power connections between the Arduino and the breadboard. Make sure each is in its correct place at both ends. Also, make sure the green power light on the Arduino board is on to confirm the board is being powered at all.

If one or more lights are not working but others are, first check that the jumper wires are in the correct places both on the Arduino board and the breadboard, and that they connect through the 1kΩ resistor to the middle leg of the transistor. Then, carefully check the path from the power rail, through the resistor to the LED and then the transistor to ground. Compare it to a working one.

In the next section, we will detail the electronic workings of the circuit but also how the code works. We have headings so you can find the bits you need if you just want to change some code stuff.

HOW IT WORKS

We will cover the electronics first, then the code. It might sound silly at first, but the way we have written the code is actually the wrong way to do it! However, it still teaches some valuable lessons, and the 'right' way, using timers and arrays, is way beyond Kids' Basics coding. In fact, arrays are something even some established coders are not comfortable with.

THE ELECTRONICS

Electronically, the circuit is eight copies of the same thing: A transistor current driver so the LEDs can be run without overloading the Arduino. Each LED, at full brightness, can draw 20mA to 30mA depending on choice. The Arduino pin can supply 40mA, but the total load out of the Arduino for all its outputs together is not allowed to exceed 80mA. That means only four LEDs could be on at once. That does not happen very often with the flash patterns we wrote, but it could. Even the pattern where all four red LEDs are on, then all four blue LEDs, hits the 80mA limit, and that assumes the LEDs are drawing 20mA, not 30mA! So, it is safer to use a transistor driver.

A transistor is a device that uses a small current to control a bigger current. Voltages are important, but current is the main factor. The transistors used here are NPN types, in which current flows in the collector, across the transistor, and out the emitter. The base controls how much current flows. The base is quite a sensitive input, and it can be damaged. We have covered transistors before and need to give space to the code this time, but using a 1kΩ resistor on 5V, according to Ohm's Law, gives us a base current of 5mA (5V ÷ 1000Ω = 0.005A = 5mA). That's enough to fully turn on the transistor at the current we're drawing through it.

The transistor has a maximum current that can pass from the collector to the emitter, and from base to emitter, without damage. That's why we needed the 1kΩ resistor between the base and the Arduino pin. We also need one on our collector side. The LED we are driving does not current limit itself, so we need the resistor to protect both it and the transistor. Red LEDs have a voltage drop across them of around 1.7 to 2V. The transistor has a voltage drop of 0.7V across it. So, at worst our voltage drop is 2.7V (2 + 0.7). At best, it's 2.4V (1.7 + 0.7). The LEDs we had, and most of the high-brightness type, are 2V LEDs, so we calculated for 2.7V. We subtract this from the 5V supply, leaving 2.3V to be dropped across the resistor. We want 20mA to flow, which is 0.02A, so we use Ohm's Law again to find a resistor value: 2.3 ÷ 0.02 = 115Ω. Not all resistor kits have 115Ω in them, so we recalculated backwards with 100Ω resistors and found a current of 23mA. That's pretty close when most of the LEDs cope with 30mA!

We went through the same process for the blue LEDs, which usually have a 3V drop across them, and took the nearest common value from the available resistor kits to give 68Ω.

So, regardless of whether you are looking at a red or blue LED, when current flows from the Arduino pin to the base of the transistor and out the emitter, current flows from the positive supply rail, through the resistor and then the LED, through the transistor, and to ground, lighting the LED. When the Arduino pin is low, there is no base current and therefore the transistor is off.

Power to the board comes through the Arduino, and is supplied by the USB port. There is a limit to how much current the Arduino can pass from a USB port, which itself is limited to 500mA for older versions, but we're nowhere near either limit.

THE CODE

#define OUTER_LEFT_RED 2
#define INNER_LEFT_RED 3
#define INNER_RIGHT_RED 4
#define OUTER_RIGHT_RED 5
#define OUTER_LEFT_BLUE 8
#define INNER_LEFT_BLUE 9
#define INNER_RIGHT_BLUE 10
#define OUTER_RIGHT_BLUE 11

Starting at the very top of the code, you'll see #define eight times. This is used to tell the compiler that any time we use the phrase in capitals somewhere within the code, the compiler should insert the pin number that we put after the phrase, instead of the phrase itself. We do this because, while pin numbers are easy to type quickly, they're easy to mix up. Seeing the phrase OUTER_LEFT_RED somewhere in the code tells you that you're dealing with the outer left red LED in the bar of lights, without having to remember or look at the circuit board. These phrases are called 'macros' which are used frequently in Arduino sketches.

Even more importantly, it means that if you ever change the pin arrangement, you only need to change the pin number once, after the macro in #define. The bits in upper case in the #define staement are called 'macros' in Arduino. If you code with pin numbers and make a change, you have to find every single time that you have used that pin number throughout your code! At the end of the code are some functions we wrote but didn't use, so a car with these lights could indicate that traffic should merge when it is stopped on the road. So you can see the difference, we wrote this with pin numbers. Try changing a pin number, and see how hard it is to find every single time it was used!

/* const int OuterLeftRed = 2;
   const int InnerLeftRed = 3;
   const int InnerRightRed = 4;
   const int OuterRightRed = 5;
   const int OuterLeftBlue = 8;
   const int InnerLeftBlue = 9;
   const int InnerRightBlue = 10;
   const int OuterRightBlue = 11; */

Under that, you'll see 'const int NameOfLight = #, where NameOfLight is replaced by the description of each LED and # is the pin number. This is another way of telling which pin is what, and is commonly used. However, it creates a variable and it is part of the code uploaded to the Arduino, not just a compiler instruction. This takes space, and in bigger programs, that matters. The main downside of using #define statements, however, is that they can produce bugs that are difficult to track down. For example, #define EG_VARIABLE 10; will cause semicolon errors to be thrown throughout your code. Using a 'const' variable will be compiled and thus error-checked like any other piece of code. It's up to you to decide which method to use.

Also in this section, you can see /* before the group of code lines, and */ after it. Most coders are familiar with comments, where everything written after // is ignored by the compiler until the next line. /* … */ is just a way to comment out whole sections. Everything in between the two symbols is ignored.

void setup() {
//this code runs once:
  pinMode(OUTER_LEFT_RED, OUTPUT);
  pinMode(INNER_LEFT_RED, OUTPUT);
/* OMITTED FOR SPACE */
  digitalWrite(INNER_LEFT_BLUE, LOW);
  digitalWrite(INNER_RIGHT_BLUE, LOW);
  digitalWrite(OUTER_RIGHT_BLUE, LOW);

In the 'void setup' section, we have set the pins to be outputs. They are usually outputs by default but making sure is sound practice. We also start by writing each pin low, just once. This makes sure all pins start as outputs turned off. We have also included but commented out the same code but with pin numbers for 'digitalWrite', so those who wish to use pin numbers instead of labels can just comment and uncomment the sections.

void loop() {
  // this code runs repeatedly:
  threeFlashes();
  slowFlash();
  threeFlashes();
  scanSame();
  threeFlashes();
  fastFlash();
  threeFlashes();
  scanOpposite();
  threeFlashes();
  inOutSame();
  threeFlashes();
  bounce();
  threeFlashes();
  inOutOpposite();

Then, we have the main loop. This is just a list of functions in the order we want to call them. We start with 'threeFlashes', which we use in between every other function. The loop calls each function in turn, then goes back to the beginning.

Now, we have more interesting stuff going on in the loops. Each function from here down is a flash pattern. Let's start with 'void threeFlashes'. There is a commented (//) line describing each what the pattern looks like, then a set of lines turning all outputs low, just in case a previous loop left one or more high. This is the same for all functions we wrote. We cut some out here for space but have a look at the code in your compiler.

void threeFlashes() {
 //all red flash three times fast,
 //then all blue flash three times fast
  digitalWrite(OUTER_LEFT_RED, LOW);
/* Plus the other digitalWrites we didn't show */
 for (int c = 0; c <=9; c++) {
  for (int i = 0; i <= 2; i++) {
   digitalWrite(OUTER_LEFT_RED, HIGH);
   digitalWrite(INNER_LEFT_RED, HIGH);
   digitalWrite(INNER_RIGHT_RED, HIGH);
   digitalWrite(OUTER_RIGHT_RED, HIGH);
   delay(60);
   digitalWrite(OUTER_LEFT_RED, LOW);
   digitalWrite(INNER_LEFT_RED, LOW);
   digitalWrite(INNER_RIGHT_RED, LOW);
   digitalWrite(OUTER_RIGHT_RED, LOW);
   delay(60);
 }
      delay(60);    
  for (int i = 0; i <= 2; i++) {
   digitalWrite(OUTER_LEFT_BLUE, HIGH);
   digitalWrite(INNER_LEFT_BLUE, HIGH);
   digitalWrite(INNER_RIGHT_BLUE, HIGH);
   digitalWrite(OUTER_RIGHT_BLUE, HIGH);
   delay(60);
   digitalWrite(OUTER_LEFT_BLUE, LOW);
   digitalWrite(INNER_LEFT_BLUE, LOW);
   digitalWrite(INNER_RIGHT_BLUE, LOW);
   digitalWrite(OUTER_RIGHT_BLUE, LOW);
   delay(60);
 }
     delay(60);
}
}

Now, we have a 'for loop'. For loops are common code blocks which repeatedly do a certain thing when a certain condition is met, then move on. We actually have nested for loops here, which is one inside another. The first, for (int c = 0; c <=9; c++) { runs whatever is after { ten times. 'int c = 0' sets up an integer, a type of variable that we can write a number to, calls it 'c', and sets it to 0 to start with. Then, we have the condition. This is what the code checks against to see if it has finished the for loop. We have 'c <= 9', which means 'if c is less than or equal to 9'. After the semicolon, we have the 'increment'. 'c++' means 'c' is increased by 1 every time the loop is run. Because it starts at 0 and runs before c becomes 1, this totals ten times (0, 1, 2, 3, 4, 5, 6, 7, 8, 9).

Immediately under this is another for loop. 'for (int i = 0; i <= 2; i++) {'sets up another integer, 'i', and makes it 0 to start. Then, the condition is set to 'if i is less than or equal to 2'. Then, the increment is set to increase i by 1 every time the loop runs. Under this is the digitalWrites which turn on the LEDs. We turn on the four red LEDs, then delay for 60 milliseconds because delay is always in milliseconds, then turn all four red LEDs off again. We wait another 60ms then go back to the beginning of the loop when we reach the } that goes with this nested for loop. When we go back to the top, i = 0 becomes i = 1. This happens within the Arduino while it is running, thanks to the i++ statement. You never see this in the typed code, because that's the starting point. The Arduino does the work, and counts 0, 1, 2, then exits the loop. It has just flashed the red lights three times very fast.

As soon as that for loop is finished, the code moves to whatever is under it. In this case, it's another for loop. However, it turns the blue lights on. Other than that, it's the same. When this for loop has run three times, it exits. However, there is nothing else there except a } which sends the program back up to the very first for loop, the one counting to ten. c = 0 now becomes c = 1. So, all up, the lights flash three times red, then three times, blue, ten times in total.

Every other function we wrote is some variation on this theme. They all use for loops. Some are nested, some are not. fastFlash and slowFlash, for example, are just a single for loop because the lights only flash once each per run of the loop.

We have used delay() a lot in this code. We use it to control how long a light is on, and how long it is off. We use it to add slight pauses between loops in some cases, too. However, delay() is a problem within code in many cases. The challenge with delay is that it stops almost everything, besides some advanced functions like PWM and Interrupts. There can be no button press inputs, no other loops, no timers, nothing. Code does not have to be very complex before this becomes a problem. We chose it for a very good reason, however.

The solution is to use millis(), which simply looks at the number of milliseconds since the Arduino started running. Using simple 'if' statements with millis() is a handy way of juggling multiple tasks at the same time on an Arduino. It does not block like delay() and so you can turn outputs on and off, sense button presses, and so on, while a time based on millis() is still running. However, it is more complex to use on its own and becomes really complex when used to turn a group of lights on and off like this. That is because the correct way to do this is with an array. This is a grid of 0 and 1 values which correspond to the outputs and what values you want them to be. Working with them is complicated and definitely beyond kids' basics.

If you want to see how they work, or how scary they can be to a new or less experienced coder, check out the LEGO lighting project in Issue 44. In that project, arrays were used to make the emergency lights for some LEGO fire trucks. We did this because we had separate flash patterns for the roof light bars, grille lights, and side lights. Different arrays were working on different on and off times side by side, which you cannot do without timers and arrays. delay() definitely won't do it!

Not only is delay() a clunky command with limited real-world use, the code as a whole is bulky when compared to other methods. However, we did these things because it is really easy for newer coders to expand on, and it is very easy to see and follow. In a code as operationally simple as this, it isn't a problem, either, because it is ok to have everything stop while a light is on or off and take up a lot of space to do not much. If it gets you to a working piece of code when a more complex but more correct method would be too confronting to learn, then it's done its job.

IN THE REAL WORLD

There are almost as many variations of emergency vehicle lights as there are emergency vehicles. The arrangement and flash patterns we came up with here are based on viewing videos on the internet, and our own observations, of many different vehicles but are not a copy of one vehicle at all. We are based in NSW so most of the vehicles we see up close are NSW vehicles. That is, except in major fire seasons and emergencies where everyone helps everyone out, then we see vehicles from all states. The reverse applies to other states, so most people are most familiar with their own state's vehicles but have likely seen interstate help on occasion. That means, there is definitely no 'right' and 'wrong' when it comes to patterns and light arrangements.

Up until quite recently, NSW Fire and Rescue trucks had lights which appeared to be independent of each other. The standard Scania Pumper that most stations have has two roof lights at the front, two at the back, two grille lights, and a scattering of others. Watching these for a while suggests they are not coordinated with each other. This makes wiring much easier when fitting out a vehicle, as you only need to run power to all lights with their own internal flash driver circuits.

However, the lights on NSW Police Force cars do give the impression of being centrally controlled. Highway Patrol cars in particular seem to have some very carefully planned light patterns on the roof bar at least. In Victoria, we have seen video of Metropolitan Fire Brigade (now Fire Rescue Victoria) trucks which do appear to have coordinated light clusters with some sort of central control. The patterns of flashes do seem to match between the different light fittings. More recently, we have seen some NSW Fire and Rescue trucks with light clusters that are made with addressable LEDs and which change colour, making for some very complex flash patterns.

All of that can also be said for Ambulance services across the different states. We definitely want to acknowledge our Ambos! We also want to acknowledge our international readers, too. Emergency Services in different parts of different countries all have their own variations on the way warning lights are operated and constructed, to suit local conditions and local ideas.

So, while we have come up with a bunch of patterns, you might want to do things completely differently depending on where you live and which kind of emergency vehicle you are most interested in.

WHERE TO NEXT

While the lights are fun on a breadboard, they're much more fun on a real toy. Whether you're using red and blue emergency lights or construction style amber lights, you can use this circuit to drive lights mounted in a toy or model vehicle. That means modifying the toy, which is not something we can provide instructions for here. Every one is different! A model is a little easier because you can plan ahead before you assemble your model and run wires beforehand. With a toy, you will have to carefully disassemble it then look for ways to run wires. It will also involve drilling into the plastic lights that are there, so you really need an adult for help.

However, the rest of the process is fairly easy. You'll need to add jumper wires to the LEDs to make them reach off the board. This can be done with the plug-to-socket version of the same wires we use between the Arduino board and the breadboard. In fact, some builders will already have a kit with plug-to-plug like we used in the build, plug-to-socket like we need for the LEDs, and socket-to-socket, in one packet. All you need to do is use a pair of wires, one for the positive anode long leg of each LED, and one for the negative cathode short leg of each LED. Put the LED legs in the socket end, and the plug end into the breadboard where the LED was.

For step by step instructions, including how to make sure the LEDs stay in the sockets, check our Kids' Basics Issue 18. You might need more length, as these wires are often 150mm (15cm) long, but you can get longer ones in most of the same shops. You can also power the Arduino and lights with a small USB power bank so it is self-contained and your toy has no wire hanging out.