“M-my lord, it-it’s impossible to locate the ship. It’s out of our range.” – Nute Gunray
We recently moved into a new house and as always the first problem to address was finding the optimal place to park cars in the garage. Ideally, we wanted to park the cars as close to the garage door as possible, leaving the maximum free space between the front of the car and the back wall of the garage.
I was discussing various solutions with some friends and the common consensus was that the old “ball on a string” technique was the preferred solution to this problem. For those of you that haven’t seen this archaic practice, it involves hanging a tennis ball from a string suspended from the ceiling of the garage. Once the car has been moved to the optimal position, the length of the string is permanently adjusted so that the tennis ball is just touching the front windscreen of the car. As such, any time you drive into the garage you simply stop when the tennis ball touches the windscreen. Archaic indeed…
That simply would not do. I thought to myself, “Self, we need to solve this problem with gadgets.” We can’t have tennis balls on strings detracting from the pristine order that is the garage.
I decided to go with an ultrasonic transducer, which is basically a sound-based range finder. I was considering a laser/light source originally, but research led me to believe that this can have a bit of trouble with cars at short range. Luckily for me though, the sound based ultrasonic transducer would do the trick.
In the interest of time, as I needed to get the device assembled quickly, I opted for a pre-made ultrasonic transducer circuit rather than build one myself. I will probably build my own some day for the exercise, but time has been a bit short lately.
This is the product that I purchased – http://parallax.com/product/28015.
The circuit is fairly simple, it has a +5V pin, a ground pin, and a signal pin. You simply generate a pulse on the signal pin, and then measure the pulse width of the returning pulse to determine the distance. This device is apparently suitable for the 2cm to 3m range, which was fine for my needs.
I decided that I wanted the range to be displayed as a series of super-bright LEDs. I built 6 LEDs into the circuit, 5 red and 1 green, arranged as Red Red Red Red Green Red. I could have used multi-colour LEDs to do this, but I liked this approach better. The LEDs were purchased from http://www.ledsales.com.au.
This is how the LEDs would respond to the car range, as the car gets closer, more red lights are turned on. Once the car is in the optimal range, the red lights are turned off and the green light turned on. If the car goes too far (too close to the wall), then the green light is turned off and the final red light is turned on.
_ _ _ _ _ _ = Car greater than 3m from wall. r _ _ _ _ _ = Car 3m from wall. r r _ _ _ _ = Car 2m from wall. r r r _ _ _ = Car 1.5m from wall. r r r r _ _ = Car 1m from wall. _ _ _ _ g _ = Car 45cm from wall. _ _ _ _ _ r = Car 35cm from wall. |
This is the circuit that I constructed.
I used the standard Arduino Uno Rev 3 board, as I didn’t need network capability.
This is the basic control code. I’ve kept the code simple to only address the logic required for this function. The actual device that I built uses an RF receiver to pickup garage door state messages from my garage door controller circuit. I do this so that I can turn the range finder circuit off when the garage door is closed, and then turn the circuit on when the garage door opens.
/***************************************************************/ /* */ /* Garage Controller Range v2.0 */ /* */ /***************************************************************/ #include <SPI.h> #define SF(x) String(F(x)) #define CF(x) String(F(x)).c_str() #define RANGE_MIN 35 #define RANGE_MAX 39 #define RANGE_1 49 #define RANGE_2 64 #define RANGE_3 84 #define RANGE_4 200 int iPinRed1 = 3; int iPinRed2 = 4; int iPinRed3 = 5; int iPinRed4 = 6; int iPinGreen1 = 7; int iPinRed5 = 8; int iPinRange = 9; /***************************************************************/ /* Function: setup */ /***************************************************************/ void setup() { Serial.begin(9600); Serial.println(SF( "setup()" )); pinMode(iPinRed1, OUTPUT); pinMode(iPinRed2, OUTPUT); pinMode(iPinRed3, OUTPUT); pinMode(iPinRed4, OUTPUT); pinMode(iPinGreen1, OUTPUT); pinMode(iPinRed5, OUTPUT); pinMode(iPinRange, OUTPUT); } /***************************************************************/ /* Function: loop */ /***************************************************************/ void loop() { long lDuration = 0, lCM = 0; // 2 Microsecond Clear + 5 Microsecond Pulse pinMode(iPinRange, OUTPUT); digitalWrite(iPinRange, LOW); delayMicroseconds(2); digitalWrite(iPinRange, HIGH); delayMicroseconds(5); digitalWrite(iPinRange, LOW); // Get Return Pulse pinMode(iPinRange, INPUT); lDuration = pulseIn(iPinRange, HIGH); lCM = ConvertMSToCM(lDuration); Serial.println( "Distance: " + String(lCM) + " cm" ); // Too Close if (lCM < RANGE_MIN) { digitalWrite(iPinRed1, LOW); digitalWrite(iPinRed2, LOW); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, HIGH); } // Perfect else if (lCM >= RANGE_MIN & lCM < RANGE_MAX) { digitalWrite(iPinRed1, LOW); digitalWrite(iPinRed2, LOW); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, HIGH); digitalWrite(iPinRed5, LOW); } // Range 1 else if (lCM >= RANGE_MAX & lCM < RANGE_1) { digitalWrite(iPinRed1, HIGH); digitalWrite(iPinRed2, HIGH); digitalWrite(iPinRed3, HIGH); digitalWrite(iPinRed4, HIGH); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } // Range 2 else if (lCM >= RANGE_1 & lCM < RANGE_2) { digitalWrite(iPinRed1, HIGH); digitalWrite(iPinRed2, HIGH); digitalWrite(iPinRed3, HIGH); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } // Range 3 else if (lCM >= RANGE_2 & lCM < RANGE_3) { digitalWrite(iPinRed1, HIGH); digitalWrite(iPinRed2, HIGH); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } // Range 4 else if (lCM >= RANGE_3 & lCM < RANGE_4) { digitalWrite(iPinRed1, HIGH); digitalWrite(iPinRed2, LOW); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } // Range 5 else if (lCM >= RANGE_4) { digitalWrite(iPinRed1, LOW); digitalWrite(iPinRed2, LOW); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } else { digitalWrite(iPinRed1, LOW); digitalWrite(iPinRed2, LOW); digitalWrite(iPinRed3, LOW); digitalWrite(iPinRed4, LOW); digitalWrite(iPinGreen1, LOW); digitalWrite(iPinRed5, LOW); } delay(50); } /***************************************************************/ /* Function: ConvertMSToCM */ /***************************************************************/ long ConvertMSToCM( long lMicroSeconds) { // The speed of sound is 340 m/s or 29 microseconds per centimeter. // The ping travels out and back, so to find the distance of the // object we take half of the distance travelled. - Arduino Site return lMicroSeconds / 29 / 2; } |
There is one problem with the code above. Whilst it works, let’s assume that the car is parked exactly 34.5 cm from the wall. The range finder can oscillate between recording the location of the car as 34cm and 35cm. This means you will see the LEDs fluctuating between green (perfect), and red (too close). There’s several ways to address this, but one of them is to not use a single threshold to toggle between states. In the example above, RANGE_MIN (35cm) is used as the threshold between the “too close to the wall” state, and the “perfect range” state.
What we could do is create two thresholds. Once threshold is used as the range is decreasing, and one threshold is used as the range is increasing. Therefore when the car is 34 cm from the wall, the state changes to “too close”, but it won’t change back to “perfect” until the range is above 36cm. This way if the range is right on the threshold, it will stay in one of the states until the range changes significantly enough to move it back to the next state. Let me know if you need an example, and I can add a code snippet to that effect.
The last issue to solve is that of assembly. For me, I needed the range finder sitting at the level of the number plate/bumper bar. However I wanted the lights to be higher up the wall where the driver would be able to see them. I decided to place the range finder circuit in one assembly and the Arduino/lights in a different assembly. I then added a socket to each assembly and connected a cable between them completing the circuit. In hindsight, I should have placed the socket for the Arduino assembly on the bottom of the assembly, but what can you do…
I attached them both to the wall with the adhesive strips from the 3M hooks you can buy. You can also see a small green wire coming out of one side – that’s the antennae used for receiving the status of the garage door. I covered the circuit used for RF communications in this blog entry.
For more detail: Using an Arduino as a garage car parking sensor