Last Christmas I received the Elegoo car (Version 1) which is small car based on the Arduino platform. The car comes with an Arduino Uno, a sensor shield, and many sensors. Recently, I enhanced my car with four “augmentations”:
- automatic break system (pure software update)
- headlights
- undercar lights
- bluetooth remote control
I made a video about how I created these augmentations. In addition to the video, I want to give some more insights about this project on my website. You can find the video here:
https://www.youtube.com/watch?v=5rdbHKXZOQU
The goal of this little project was to get all ideas into a prototype state. Therefore, almost all created augmentations look very prototyp’ish. If I find some time and the augmentations turn out fun, I might continue this project with 3D-printed parts.
List of materials:
Arduino Elegoo car (Version 1) | |
Relais (headlights) | |
High power LEDs (headlights) | |
WS2812B LEDs (undercarlights) | |
Arduino Nano (bluetooth remote control) | |
Bluetooth module HC-05 (bluetooth remote control) | |
DIP switches (bluetooth remote control) | |
Joystick KY-23 (bluetooth remote control) | |
Rotary angle sensor(bluetooth remote control) |
Automatic break system:
The automatic break system is basically a pure software update. If the car is driving and an obstacle comes too close, it starts to break immedately. At the same time, a yellow LED will light up to show that the car just automatically stopped. Then a phase of about 7 seconds starts. In this phase, the car can be moved to a “safe position” without being interrupted by the automatic break mechanisms. This phase is indicated by the same LED which starts blinking. If the LED goes out, the car is in “normal” mode and will stop again if an obstacles comes close.
The Elegoo car comes already with an ultrasonic sensor which was is used to implement the automatic break system. Normally, in order to read the distance values from the sensors, the pulseIn-function of the Arduino platform is used. The drawback of the pulseIn-function is that it actively waits while observing the output of the ultrasonic sensor. Therefore, I implemented the distance measurements based on interrupt routines. In particular, my routines are called when the output signal of the ultrasonic sensor switches from HIGH to LOW or from LOW to HIGH. By using this approach, the Arduino does not need to actively wait for something and the saved time can be used for other things, such as handling the bluetooth communication.
Headlights:
For the headlights, so-called “power LEDs” are used. In contrast to conventional LEDs, they require a forward voltage between 6 and 7 Volt. As a result, they shine very bright. Luckily, the Elegoo car comes with a 9V battery unit. Therefore, using such “power LEDs” is not a problem at all. In order to separate the Arduino’s 5V circuit from the 9V circuit which powers the headlights, a relay breakout board is used. Such relay boards can be used with any of the Arduino’s digital or analog output pins. Moreover, the relay board has an LED which indicates whether the relay is in close or open state. This is especially useful for debugging a program or wiring layout.
Undercar lights:
Depending on the viewer, the undercar lights improve the look of the Elegoo car. For this task, WS2812B LED modules are used. Multiple WS2812B can be cascaded and then individually controlled. This makes them perfect for showing individually programmed lighting routines. In total, six WS2812B LEDs are added to the Elegoo car. The lighting routines were programmed with the FastLED library.
Bluetooth remote control:
The Elegoo car comes already with a Bluetooth HC-06 module. In order to communicate with this Bluetooth module, a Bluetooth HC-05 module was used. The difference between the HC-05 and HC-06 is that the HC-05 can be set to be either Master or Slave. On the contrary, the HC-06 can only be set to be Slave. Therefore, one requires a HC-05 module if a Bluetooth communication should be established with the existing HC-06 module.
The heart of the remote control is an Arduino Nano, since it is very small and fits perfectly on a breadboard. The Arduino Nano handles the Bluetooth communication and takes care of all the connected knobs, switches, and joysticks. I thought a lot about how to implement steering and acceleration. After some tests, I decided to use two joysticks placed on the left and right: The left joystick controls the steering and the right joystick controls the acceleration (speed and direction). This means, only the x-axis of the left joystick and only the y-axis of the right joystick is used. All in all I’m very happy with this decision. Controlling the car works like a charm.
Final augmented Arduino car prototype:
Wiring of Bluetooth Remote Control:
Wiring of Augmented Arduino Car:
Source code Augmented Arduino Car:
// (c) Michael Schoeffler 2017, http://www.mschoeffler.de // Remark: This code was written for a prototype project. Therefore, the code style is somewhat "prototyp'ish" ;) #include <IRremote.h> #include <Servo.h> //servo library #include <SoftwareSerial.h> #include "FastLED.h" // pins const int ultrasonic_echo = 2; const int ultrasonic_trig = A5; const int ultrasonic_led = A1; const int motorcontrol_IN1 = 9; const int servo_ctrl = 3; const int motorcontrol_IN2 = 4; const int motorcontrol_ENA = 5; const int motorcontrol_ENB = 6; const int motorcontrol_IN3 = 7; const int motorcontrol_IN4 = 8; const int motorcontrol_led = A0; const int headlights_relais = A2; const int undercarlights_data = 10; const int irremotecontrol_receiverpin = 11; const int bt_rx = 12; const int bt_tx = 13; #define undercarlights_LED_TYPE WS2812B #define undercarlights_COLOR_ORDER GRB #define undercarlights_NUM_LEDS 6 #define undercarlights_BRIGHTNESS 96 CRGB undercarlights_leds[undercarlights_NUM_LEDS]; unsigned long undercarlights_specialprog_delay = 750; unsigned long undercarlights_specialprog_lastcall = 0; unsigned long undercarlights_specialprog_docall = false; int motorcontrol_on = 0; int motorcontrol_x = 511; int motorcontrol_y = 511; // automatic break system led byte ultrasonic_state = 0; unsigned long ultrasonic_time = 0; // automatic break system measurement unsigned long ultrasonic_distance = 4096; volatile long ultrasonic_echo_start = 0; volatile long ultrasonic_echo_end = 0; // Codes for IR keypad const unsigned long CODE_IR_KEYPAD_UP = 0xFF629D; const unsigned long CODE_IR_KEYPAD_LEFT = 0xFF22DD; const unsigned long CODE_IR_KEYPAD_OK = 0xFF02FD; const unsigned long CODE_IR_KEYPAD_RIGHT = 0xFFC23D; const unsigned long CODE_IR_KEYPAD_DOWN = 0xFFA857; const unsigned long CODE_IR_KEYPAD_1 = 0xFF6897; const unsigned long CODE_IR_KEYPAD_2 = 0xFF9867; const unsigned long CODE_IR_KEYPAD_3 = 0xFFB04F; const unsigned long CODE_IR_KEYPAD_4 = 0xFF30CF; const unsigned long CODE_IR_KEYPAD_5 = 0xFF18E7; const unsigned long CODE_IR_KEYPAD_6 = 0xFF7A85; const unsigned long CODE_IR_KEYPAD_7 = 0xFF10EF; const unsigned long CODE_IR_KEYPAD_8 = 0xFF38C7; const unsigned long CODE_IR_KEYPAD_9 = 0xFF5AA5; const unsigned long CODE_IR_KEYPAD_0 = 0xFF4AB5; const unsigned long CODE_IR_KEYPAD_MUL = 0xFF42BD; const unsigned long CODE_IR_KEYPAD_SHARP = 0xFF52AD; Servo servo; // create servo object to control motor for ultrasonic sensor IRrecv irrecv(irremotecontrol_receiverpin); // initializes ir remote control SoftwareSerial BTserial(bt_rx, bt_tx); void movement_move(int speedLeft, int speedRight) { if (speedLeft > 255) { speedLeft = 255; } if (speedRight > 255) { speedRight = 255; } if (speedLeft < -255) { speedLeft = -255; } if (speedRight < -255) { speedRight = -255; } if (speedRight == 0) { analogWrite(motorcontrol_ENA, LOW); } else { analogWrite(motorcontrol_ENA, abs(speedRight)); } if (speedLeft == 0) { analogWrite(motorcontrol_ENB, LOW); } else { analogWrite(motorcontrol_ENB, abs(speedLeft)); } if (speedRight > 0) { digitalWrite(motorcontrol_IN1, LOW); digitalWrite(motorcontrol_IN2, HIGH); } else { digitalWrite(motorcontrol_IN1, HIGH); digitalWrite(motorcontrol_IN2, LOW); } if (speedLeft > 0) { digitalWrite(motorcontrol_IN3, LOW); digitalWrite(motorcontrol_IN4, HIGH); } else { digitalWrite(motorcontrol_IN3, HIGH); digitalWrite(motorcontrol_IN4, LOW); } } void movement_forward(byte speed) { analogWrite(motorcontrol_ENA, speed); analogWrite(motorcontrol_ENB, speed); digitalWrite(motorcontrol_IN1, LOW); digitalWrite(motorcontrol_IN2, HIGH); digitalWrite(motorcontrol_IN3, LOW); digitalWrite(motorcontrol_IN4, HIGH); Serial.println("movement_forward"); } void movement_back(byte speed) { analogWrite(motorcontrol_ENA, speed); analogWrite(motorcontrol_ENB, speed); digitalWrite(motorcontrol_IN1, HIGH); digitalWrite(motorcontrol_IN2, LOW); digitalWrite(motorcontrol_IN3, HIGH); digitalWrite(motorcontrol_IN4, LOW); Serial.println("movement_back"); } void movement_left(byte speed) { analogWrite(motorcontrol_ENA, speed); analogWrite(motorcontrol_ENB, speed); digitalWrite(motorcontrol_IN1, LOW); digitalWrite(motorcontrol_IN2, HIGH); digitalWrite(motorcontrol_IN3, HIGH); digitalWrite(motorcontrol_IN4, LOW); Serial.println("movement_left"); } void movement_right(byte speed) { analogWrite(motorcontrol_ENA, speed); analogWrite(motorcontrol_ENB, speed); digitalWrite(motorcontrol_IN1, HIGH); digitalWrite(motorcontrol_IN2, LOW); digitalWrite(motorcontrol_IN3, LOW); digitalWrite(motorcontrol_IN4, HIGH); Serial.println("movement_right"); } void movement_stop() { digitalWrite(motorcontrol_ENA, LOW); digitalWrite(motorcontrol_ENB, LOW); } void headlights_switchOff() { digitalWrite(headlights_relais, LOW); } void headlights_switchOn() { digitalWrite(headlights_relais, HIGH); } void echo_interrupt() { switch (digitalRead(ultrasonic_echo)) { case HIGH: ultrasonic_echo_end = 0; ultrasonic_echo_start = micros(); break; case LOW: if (ultrasonic_echo_end == 0) { ultrasonic_echo_end = micros(); long duration = ultrasonic_echo_end - ultrasonic_echo_start; long durationOneWay = duration / 2; // divided by two, since duration is a roundtrip signal // acoustic velocity of air at a temperature of 20°C => ~343.5 m/s // => 0.03435 cm/us ultrasonic_distance = durationOneWay * 0.03435; // distance in cm } break; } } void setup() { pinMode(motorcontrol_IN1, OUTPUT); pinMode(motorcontrol_IN2, OUTPUT); pinMode(motorcontrol_IN3, OUTPUT); pinMode(motorcontrol_IN4, OUTPUT); pinMode(motorcontrol_ENA, OUTPUT); pinMode(motorcontrol_ENB, OUTPUT); pinMode(irremotecontrol_receiverpin, INPUT); pinMode(headlights_relais, OUTPUT); pinMode(ultrasonic_trig, OUTPUT); pinMode(ultrasonic_echo, INPUT); pinMode(ultrasonic_led, OUTPUT); digitalWrite(ultrasonic_led, LOW); servo.attach(servo_ctrl);// attach servo on pin 3 to servo object irrecv.enableIRIn(); FastLED.addLeds<undercarlights_LED_TYPE,undercarlights_data,undercarlights_COLOR_ORDER>(undercarlights_leds, undercarlights_NUM_LEDS).setCorrection(TypicalLEDStrip); // initializes LED strip FastLED.setBrightness(undercarlights_BRIGHTNESS);// global brightness Serial.begin(9600); movement_stop(); headlights_switchOff(); showLightprogram(0); BTserial.begin(9600); attachInterrupt(digitalPinToInterrupt(ultrasonic_echo), echo_interrupt, CHANGE); } void showProgramShiftMultiPixel() { for (int i = undercarlights_NUM_LEDS-1; i > 0; --i) { undercarlights_leds[i] = undercarlights_leds[i-1]; } CRGB newPixel = CHSV(random8(),255,255); undercarlights_leds[0] = newPixel; FastLED.show(); } void showLightprogram(int undercarlights_program) { if (undercarlights_program == 0) { undercarlights_specialprog_docall = false; for (int i = 0; i < undercarlights_NUM_LEDS; ++i) { undercarlights_leds[i] = CRGB::Black; } FastLED.show(); return; } else if (undercarlights_program == 7) { undercarlights_specialprog_docall = true; return; } else { undercarlights_specialprog_docall = false; CRGB crgb; switch (undercarlights_program) { case 1: crgb = CRGB::Maroon; break; case 2: crgb = CRGB::Amethyst; break; case 3: crgb = CRGB::Azure; break; case 4: crgb = CRGB::Chartreuse; break; case 5: crgb = CRGB::CornflowerBlue; break; case 6: crgb = CRGB::Fuchsia; break; } for (int i = 0; i < undercarlights_NUM_LEDS; ++i) { undercarlights_leds[i] = crgb; } FastLED.show(); return; } } char buf[8]; int index_buf= 0; unsigned long ultrasonic_time_last_trigger = 0; void loop() { byte default_speed = 128; unsigned long time = millis(); if (ultrasonic_time_last_trigger + 50 > time) { digitalWrite(ultrasonic_trig, LOW); // turn off the trigger delayMicroseconds(3); digitalWrite(ultrasonic_trig, HIGH);// prepare to send "trigger" command to module delayMicroseconds(10); // wait for 10us (module sends signal only, if trigger had a HIGH signal for at least 10 us) digitalWrite(ultrasonic_trig, LOW); // module sends signal now ultrasonic_time_last_trigger = time; } // receive IR commands decode_results results; if (irrecv.decode(&results)) { unsigned long irCommand; irCommand = results.value; irrecv.resume(); switch(irCommand) { case CODE_IR_KEYPAD_UP: movement_forward(default_speed); break; case CODE_IR_KEYPAD_LEFT: movement_left(default_speed + 64); break; case CODE_IR_KEYPAD_RIGHT: movement_right(default_speed + 64); break; case CODE_IR_KEYPAD_DOWN: movement_back(default_speed); break; case CODE_IR_KEYPAD_OK: movement_stop(); break; case CODE_IR_KEYPAD_2: servo.write(90); break; case CODE_IR_KEYPAD_3: servo.write(10); break; case CODE_IR_KEYPAD_1: servo.write(170); break; case CODE_IR_KEYPAD_4: headlights_switchOff(); break; case CODE_IR_KEYPAD_5: headlights_switchOn(); break; } } // receive bluetooth commands while (BTserial.available()) { char c = BTserial.read(); if (index_buf == 0 && c != '<') { // wait for start sign continue; } else if (index_buf == 1 && c < 'a' || c > 'f') { index_buf = 0; continue; } buf[index_buf++] = c; if (c == '>') { // full frame received START_BYTE, SENSOR_ID_BYTE, VALUE[length:1-4], STOP_BYTE char sensorId = buf[1]; int value = atoi(buf+2); switch (sensorId) { case 'a': // servo { int value_servo = map(value, 1023, 0, 5, 175); servo.write(value_servo); break; } case 'b': // headlights { if (value == 0) { headlights_switchOff(); } else if (value == 1) { headlights_switchOn(); } break; } case 'c': // undercar lights { //Serial.println(value); showLightprogram(value); break; } case 'd': // undercar lights { motorcontrol_on = value; break; } case 'e': // undercar lights { motorcontrol_x = value; break; } case 'f': // undercar lights { motorcontrol_y = value; break; } } /* Serial.print("Bluetooth [RX]: "); Serial.print(sensorId); Serial.print(" - "); Serial.println(value); */ index_buf = 0; buf[0] = ' '; } } if (undercarlights_specialprog_docall) { unsigned long current_time = millis(); if (current_time > (undercarlights_specialprog_lastcall + undercarlights_specialprog_delay)) { showProgramShiftMultiPixel(); undercarlights_specialprog_lastcall = current_time; } } // ultrasonic unsigned long curtime = millis(); if (ultrasonic_state == 0) { if (ultrasonic_distance < 15) { digitalWrite(ultrasonic_led, HIGH); ultrasonic_state = 1; ultrasonic_time = curtime; movement_stop(); } } else if (ultrasonic_state > 0 && (ultrasonic_time + 250) < curtime) { ultrasonic_time = curtime; if (ultrasonic_state > 8) { digitalWrite(ultrasonic_led, ultrasonic_state%2); } ++ultrasonic_state; if (ultrasonic_state > 44) { ultrasonic_state = 0; } } // motorcontrol byte max_speed = 255; if (motorcontrol_on == 1) { digitalWrite(motorcontrol_led, HIGH); int speed = map(motorcontrol_y, 1023, 0, -max_speed, max_speed); if (speed < 25 && speed > -25 ) { speed = 0; }; int speed_left = speed; int speed_right = speed; if (motorcontrol_x >= 512) { // go right int value = motorcontrol_x - 512; // should be between 0 and 511 float modifier = map(value, 0, 511, 511, 0) / 511.0; speed_right = speed_right * modifier; } else { // go left int value = motorcontrol_x; // should be between 0 and 511 float modifier = value / 511.0; speed_left = speed_left * modifier; } if (ultrasonic_state == 0 || ultrasonic_state > 8) { movement_move(speed_left, speed_right); } } else { digitalWrite(motorcontrol_led, LOW); movement_stop(); } }
Source code Bluetooth remote control:
// (c) Michael Schoeffler 2017, http://www.mschoeffler.de // Remark: This code was written for a prototype project. Therefore, the code style is somewhat "prototyp'ish" ;) #include <SoftwareSerial.h> const int control_servo = A0; const int control_headlights = 2; const int control_undercarlights_a = 3; // DIP switch #a const int control_undercarlights_b = 4; // DIP switch #b const int control_undercarlights_c = 5; // DIP switch #c const int bt_rx = 12; const int bt_tx = 13; const int motorcontrol_x = A1; const int motorcontrol_y = A2; const int motorcontrol_on = 8; SoftwareSerial BTserial(bt_rx, bt_tx); // RX | TX char c = ' '; void setup() { pinMode(control_servo, INPUT); pinMode(control_headlights, INPUT_PULLUP); pinMode(control_undercarlights_a, INPUT_PULLUP); pinMode(control_undercarlights_b, INPUT_PULLUP); pinMode(control_undercarlights_c, INPUT_PULLUP); pinMode(motorcontrol_on, INPUT_PULLUP); pinMode(motorcontrol_x, INPUT); pinMode(motorcontrol_y, INPUT); Serial.begin(9600); BTserial.begin(9600); Serial.println("Remote control is ready"); } void writeFrame(char controlId, int value) { char buf[5]; BTserial.write("<"); BTserial.write(controlId); String str = String(value, DEC); str.toCharArray(buf, 5); BTserial.write(buf); BTserial.write(">"); } int control_servo_last_value = -32.768; int control_servo_last_epsilon = 16; int control_headlights_last_value = -32.768; int control_undercarlights_last_value = -32.768; int control_motorcontrol_x_last_value = -32.768; int control_motorcontrol_y_last_value = -32.768; int control_motorcontrol_on_last_value = -32.768; void loop() { int control_servo_value = analogRead(control_servo); if (abs(control_servo_value - control_servo_last_value) > control_servo_last_epsilon) { writeFrame('a', control_servo_value); } control_servo_last_value = control_servo_value; int control_headlights_value = !digitalRead(control_headlights); if (control_headlights_value != control_headlights_last_value) { writeFrame('b', control_headlights_value); } control_headlights_last_value = control_headlights_value; int control_undercarlights_value = !digitalRead(control_undercarlights_a) <<2 | !digitalRead(control_undercarlights_b) <<1 | !digitalRead(control_undercarlights_c); if (control_undercarlights_value != control_undercarlights_last_value) { writeFrame('c', control_undercarlights_value); } control_undercarlights_last_value = control_undercarlights_value; int control_motorcontrol_on_value = !digitalRead(motorcontrol_on); if (control_motorcontrol_on_value != control_motorcontrol_on_last_value) { writeFrame('d', control_motorcontrol_on_value); } control_motorcontrol_on_last_value = control_motorcontrol_on_value; int control_motorcontrol_x_value = analogRead(motorcontrol_x); if (control_motorcontrol_x_value != control_motorcontrol_x_last_value) { writeFrame('e', control_motorcontrol_x_value); } control_motorcontrol_x_last_value = control_motorcontrol_x_value; int control_motorcontrol_y_value = analogRead(motorcontrol_y); if (control_motorcontrol_y_value != control_motorcontrol_y_last_value) { writeFrame('f', control_motorcontrol_y_value); } control_motorcontrol_y_last_value = control_motorcontrol_y_value; delay(20); }