Introduction: UCL - Embedded - RMC/Autonomous Linetracking Car
In this school project I made a car that can be remote controlled and set in autonomous mode where it’s able to follow a black line. The joystick sends data via Bluetooth to the car, the controls consist of two potentiometers which control the speed and direction, two switches for mode selection and start/stop. On the joystick there is a display that gives a few information. In autonomous mode it uses three IR-sensors to track the line. In both manual and autonomous mode there’s an ultra-sonic sensor that prevents the car from driving into stuff.
Step 1: Video / Pictures
The steering in manual mode is very clunky, my original idea was to use one xy-joystick, but the output values were too random, that’s the reason why I’m using two potentiometers instead. A better alternative is to use two xy-joysticks and then only use one axis on both.
Step 2: Components List
In this project I’ve used Arduino mega’s instead of uno’s for convenience sake. With the mega’s I was able to use the serial monitor, and upload programs while the Bluetooth/serial communication was running. The arduino uno used in this project is only used to read the output from the ultrasonic sensor, this should be possible to do with an interrupt pin, a smart library or better code, but I wasn’t able to figure it out. To reduce the amount of components used you could also create an APP and replace the joystick.
2x Arduino
mega
1x Arduino uno
2x HC-05 bluetooth modul
2x breadboard
2x dc motor
1x l298n motor driver
1x Car chassis
2x wheels
1x Servo motor
3x IR obstacle sensor
1x ultrasonic sensor
1x LCD
2x potentiometer
2x slide switch
2x push button switch
Battery (7,2 V RC car battery preferable)
Wires
Resistances
- 2x 2k Ohm
- 2x 1k Ohm
Cable ties
Insulating tape
Step 3: Wiring Diagrams
Before wiring everything up, start with configuring the Bluetooth modules, this is described later in the guide.
When finished wiring everything up you must calibrate the IR sensors, you do that by adjusting the onboard potentiometer.
Step 4: Bluetooth Communication Setup
To configure the Bluetooth master/slave, you must upload an empty sketch to the Arduino and connect the Bluetooth module almost like in the sketch, but Rx to Rx, Tx to Tx and 5v on the EN pin on the Bluetooth module. NB you can’t upload the program if Rx and Tx is connected. When powering the Bluetooth module hold the switch above EN pin and the LED will start flashing every 2 seconds. Now open the serial monitor and chose “Both NL & CR” in the dropdown menu. Type in “AT”, if you receive OK you have connection. The first module we configure is the Slave Arduino, the commands we are going to use is “AT+UART?” to check the baud rate, in this project I’ve used 38400, if the baud rate is different type “AT+UART=38400,0,0”. Next command is “AT+ROLE?” which should be 0 for slave. Then “AT+ADDR?” to check the address of the module. Write the address down and change the semicolons to commas.
Change the Bluetooth module to the master module, power it up with the EN switch pressed like earlier. Change the role to 1, for master, check the baud rate, change if necessary. Use “AT+CMODE=0” for automatic connection to a partner. “AT+BIND= address of the slave” fx “AT+BIND=98d3,31,3058b0”.
Step 5: CAD Drawings
1. Ultra sonic sensor bracket
2. IR sensor bracket
3. Potentiometer bracket
I’ve 3D-printed three small brackets, one for the ultra-sonic sensor, this need a few adjustments, a holder for the potentiometers, and for the IR-sensors. The IR-sensors needs to be raised about 2 cm to work properly.
These drawings are not optimal for this project so I would recommend making your own.
Step 6: Flow Diagrams
Step 7: CODE - Car
<p>// Car arduino<br>// BY MB //////////////////////////////////////////////////////////////////// //************************Libraries*******************************\\ ////////////////////////////////////////////////////////////////////</p><p>#include <easytransfer.h> #include <servo.h></servo.h></easytransfer.h></p><p>//////////////////////////////////////////////////////////////////// //**********************Communication*****************************\\ ///////////////////////////////////////////////////////////////////</p><p>/*********Easy transfer*********\\</p><p>EasyTransfer ET; // receive joystick EasyTransfer ET1; // send joystick EasyTransfer ET2; // rec Ultra Sonic</p><p>struct RECEIVE_DATA_STRUCTURE { //put your variable definitions here for the data you want to receive //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t joystick_rx; int16_t joystick_ry; boolean joystick_switch; //pullup "always high" low on press unsigned int komTest = 0; boolean startSwitch;</p><p>};</p><p>//give a name to the group of data RECEIVE_DATA_STRUCTURE mydata_receive;</p><p>struct SEND_DATA_STRUCTURE { //put your variable definitions here for the data you want to receive //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t mode; int16_t startStop; int16_t ultraVal;</p><p>};</p><p>//give a name to the group of data SEND_DATA_STRUCTURE mydata_send;</p><p>struct RECEIVE_ULTRA_STRUCTURE { //put your variable definitions here for the data you want to receive //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t distance;</p><p>};</p><p>//give a name to the group of data RECEIVE_ULTRA_STRUCTURE ultra_receive;</p><p>//////////////////////////////////////////////////////////////////// //***************************Variables****************************\\ ////////////////////////////////////////////////////////////////////</p><p>Servo distServo; // Servo for ultrasonic sensor</p><p>unsigned long previousMillis = 0; // Send / receive delay const long interval = 20;</p><p>unsigned long serialPreviousMillis = 0; // PC serial monitor delay const long serialInterval = 200;</p><p>int motorRightSpeed; // Analog output for motors int motorLeftSpeed;</p><p>int menuMode = 0; // Menu 1 = RMC, Menu 0 = autonomous int startStop = 0; unsigned long switchPreviousMillis = 0; const long switchInterval = 500; // delay between switch reading</p><p>unsigned long servoPreviousMillis = 0; const long servoInterval = 10; // Interval between servo decr/incr int i = 0; // Servo speed PWM value int servoLoopFlag = 0; //Servo speed up/down flag int servoMax = 180; int servoMin = 80;</p><p>/*long duration; // Ultra sonic sensor travel time int distance; // calculated distance int trigState = LOW; //state of trigPin int ultraInterval = 1; // interval in milliseconds at which trigPin turns on int ultraPreviousMillis = 0; */ int filterUltra;</p><p>//////////////////////////////////////////////////////////////////// //*************************IO PIN LIST****************************\\ ////////////////////////////////////////////////////////////////////</p><p>const int motorLeftPinBackward = 2; const int motorLeftPinForward = 3; const int motorRightPinBackward = 4; const int motorRightPinForward = 5; const int servoPinDistanceSensor = 6; const int rightIRSensor = 7; const int leftIRSensor = 8; const int motorRightSpeedPin = 9; // Analog output const int motorLeftSpeedPin = 10; // Analog output //const int distTrigPin = 11; //const int distEchoPin = 12; const int midIRSensor = 13;</p><p>void setup() {</p><p> Serial.begin(9600); // PC Serial monitor Serial1.begin(38400); // Bluetooth com port Serial2.begin(9600); // Ultra sonic com port</p><p> ET.begin(details(mydata_receive), &Serial1); ET1.begin(details(mydata_send), &Serial1); ET2.begin(details(ultra_receive), &Serial2);</p><p> pinMode(motorRightPinForward, OUTPUT); pinMode(motorRightPinBackward, OUTPUT); pinMode(motorLeftPinForward, OUTPUT); pinMode(motorLeftPinBackward, OUTPUT); distServo.attach(servoPinDistanceSensor); pinMode(rightIRSensor, INPUT); pinMode(leftIRSensor, INPUT); pinMode(motorRightSpeedPin, OUTPUT); pinMode(motorLeftSpeedPin, OUTPUT); // pinMode(distTrigPin, OUTPUT); // pinMode(distEchoPin, INPUT); pinMode(midIRSensor, INPUT); //pinMode(ultraOverLimit, INPUT); }</p><p>void loop() {</p><p> ////////////////////////////////////////////////////////////////////////// //************************** SEND/RECEIVE DATA ************************** //////////////////////////////////////////////////////////////////////////</p><p> unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; if (ET.receiveData()) {</p><p> }</p><p> if (ET2.receiveData()) {</p><p> } mydata_send.mode = menuMode; mydata_send.startStop = startStop; mydata_send.ultraVal = filterUltra; //filterUltra = 20; ET1.sendData(); }</p><p> filterUltra = filter(ultra_receive.distance, filterUltra);</p><p> //////////////////////////////////////////////////////////////////// //************************** MENU MODE ***************************** ////////////////////////////////////////////////////////////////////</p><p> unsigned long switchCurrentMillis = millis();</p><p> if (mydata_receive.joystick_switch == LOW && menuMode == 0) //FLIP FLOP {</p><p> if (switchCurrentMillis - switchPreviousMillis >= switchInterval) { switchPreviousMillis = switchCurrentMillis; menuMode = 1; startStop = 0; //Serial.println(menuMode); } } if (mydata_receive.joystick_switch == LOW && menuMode == 1) {</p><p> if (switchCurrentMillis - switchPreviousMillis >= switchInterval) { switchPreviousMillis = switchCurrentMillis; menuMode = 0; startStop = 0; //Serial.println(menuMode); }</p><p> }</p><p> if (mydata_receive.startSwitch == LOW && startStop == 0) {</p><p> if (switchCurrentMillis - switchPreviousMillis >= switchInterval) { switchPreviousMillis = switchCurrentMillis; startStop = 1; //Serial.println(startStop); } } if (mydata_receive.startSwitch == LOW && startStop == 1) {</p><p> if (switchCurrentMillis - switchPreviousMillis >= switchInterval) { switchPreviousMillis = switchCurrentMillis; startStop = 0;</p><p> //Serial.println(startStop); }</p><p> } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //***********************************************************MOTOR CONTROL***********************************************************\\ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////</p><p> if ( startStop == 1) {</p><p> if (menuMode == 1) { // REMOTE CONTROL</p><p> if (mydata_receive.joystick_rx >= 520 && filterUltra > 10) // ************************** Forward ************************** {</p><p> motorForward();</p><p> if (mydata_receive.joystick_ry >= 520) // Motor speed forward right {</p><p> motorLeftSpeed = map(mydata_receive.joystick_rx, 520, 1023, 0, 255); // Forward 0-255 LEFT MOTOR SPEED motorRightSpeed = motorLeftSpeed - map(mydata_receive.joystick_ry, 520, 1023, 0, motorLeftSpeed); // Forward 0-255 RIGHT MOTOR SPEED</p><p> }</p><p> else if (mydata_receive.joystick_ry <= 480) // Motor speed forward left {</p><p> motorRightSpeed = map(mydata_receive.joystick_rx, 520, 1023, 0, 255); // Forward 0-255 RIGHT MOTOR SPEED motorLeftSpeed = motorRightSpeed - map(mydata_receive.joystick_ry, 480, 0, 0, motorRightSpeed); // Forward 0-255 LEFT MOTOR SPEED</p><p> } else // Motor speed straight forward { motorRightSpeed = map(mydata_receive.joystick_rx, 520, 1023, 0, 255); motorLeftSpeed = motorRightSpeed;</p><p> } }</p><p> else if (mydata_receive.joystick_rx <= 480) // ************************** Backwards **************************</p><p> {</p><p> motorBackward();</p><p> if (mydata_receive.joystick_ry >= 520) // Motor speed backward right {</p><p> motorLeftSpeed = map(mydata_receive.joystick_rx, 480, 0, 0, 255); // Backward 0-255 LEFT MOTOR SPEED motorRightSpeed = motorLeftSpeed - map(mydata_receive.joystick_ry, 520, 1023, 0, motorLeftSpeed); // Backward 0-255 RIGHT MOTOR SPEED</p><p> }</p><p> else if (mydata_receive.joystick_ry <= 480) // Motor speed backward left {</p><p> motorRightSpeed = map(mydata_receive.joystick_rx, 480, 0, 0, 255); // Backward 0-255 LEFT MOTOR SPEED motorLeftSpeed = motorRightSpeed - map(mydata_receive.joystick_ry, 480, 0, 0, motorRightSpeed); // Backward 0-255 RIGHT MOTOR SPEED</p><p> } else // Motor speed straight backward { motorRightSpeed = map(mydata_receive.joystick_rx, 480, 0, 0, 255); motorLeftSpeed = motorRightSpeed; } }</p><p> else if ( (mydata_receive.joystick_rx > 480 && mydata_receive.joystick_rx < 520) || filterUltra <= 10 ) //************************** BRAKE **************************</p><p> {</p><p> motorBrake();</p><p> }</p><p> }</p><p> if (menuMode == 0) { // Autonomous</p><p>int maxRight = 150; int maxLeft = 150; int minRight = 0; int minLeft = 0; int if (digitalRead(midIRSensor) == 1 && filterUltra > 10) { // go forward until right or left sensor is hit motorForward(); motorRightSpeed = maxRight; motorLeftSpeed = maxLeft; }</p><p> if (digitalRead(rightIRSensor) == 1 && filterUltra > 10) { // turn left until middle sensor is activated motorForward(); motorRightSpeed = minRight; motorLeftSpeed = maxLeft; }</p><p> if (digitalRead(leftIRSensor) == 1 && filterUltra > 10) { // turn right until middle sensor is activated motorForward();</p><p> motorRightSpeed = maxRight; motorLeftSpeed = minLeft; }</p><p> if (filterUltra <= 10) {</p><p> motorBrake();</p><p> }</p><p> } }</p><p> else { motorBrake(); }</p><p> analogWrite(motorRightSpeedPin, motorRightSpeed); analogWrite(motorLeftSpeedPin, motorLeftSpeed);</p><p> //////////////////////////////////////////////////////////////////////////////////////////// //************************** Servo control for ultra sonic sensor ************************** ////////////////////////////////////////////////////////////////////////////////////////////</p><p> unsigned long servoCurrentMillis = millis();</p><p> if ( startStop == 1) {</p><p> if ((servoCurrentMillis - servoPreviousMillis >= servoInterval) && i < servoMax && servoLoopFlag == 0) { servoPreviousMillis = servoCurrentMillis;</p><p> i++; if (i == servoMax) {</p><p> servoLoopFlag = 1 ;</p><p> } }</p><p> if ((servoCurrentMillis - servoPreviousMillis >= servoInterval) && i > servoMin && servoLoopFlag == 1) { servoPreviousMillis = servoCurrentMillis;</p><p> i--;</p><p> if (i == servoMin) {</p><p> servoLoopFlag = 0 ;</p><p> }</p><p> } }</p><p> distServo.write(i); // Analog write servo motor</p><p> //////////////////////////////////////////////////////////////////// //************************** TROUBLESHOOT ************************** ////////////////////////////////////////////////////////////////////</p><p> unsigned long serialCurrentMillis = millis(); if (serialCurrentMillis - serialPreviousMillis >= serialInterval) { serialPreviousMillis = serialCurrentMillis;</p><p> // Serial.println(mydata_receive.komTest); // Serial.println(menuMode); // Serial.println(mydata_receive.joystick_switch); //Serial.println(ultra_receive.distance); //Serial.println(startStop); // /*</p><p> // */ //************************** MOTOR CONTROL ************************** /* Serial.print("\t"); Serial.print("mydata_receive.joystick_rx "); Serial.print("\t"); Serial.print(mydata_receive.joystick_rx); Serial.print("\t"); Serial.print("mydata_receive.joystick_ry "); Serial.print("\t"); Serial.print(mydata_receive.joystick_ry); Serial.print("\t"); Serial.print("MotorHFrem "); Serial.print("\t"); Serial.print(bitRead(PORTD, motorRightPinForward)); Serial.print("\t"); Serial.print("MotorLFREM "); Serial.print("\t"); Serial.print(bitRead(PORTD, motorLeftPinForward)); Serial.print("\t"); Serial.print("MotorHTILBAGE "); Serial.print("\t"); Serial.print(bitRead(PORTD, motorRightPinBackward)); Serial.print("\t"); Serial.print("MotorLTILBAGE "); Serial.print("\t"); Serial.print(bitRead(PORTD, motorLeftPinBackward)); Serial.print("\t"); Serial.print("MotorL_HASTIGHED "); Serial.print("\t"); Serial.print(motorLeftSpeed); Serial.print("\t"); Serial.print("MotorH_HASTIGHED "); Serial.print("\t"); Serial.println(motorRightSpeed); Serial.print("\t"); */</p><p> //Serial.println(bitRead(PORTD, 12));</p><p> //Serial.println(mydata_receive.joystick_rx); Serial.print("Højre"); Serial.print(digitalRead(7)); Serial.print("\t"); Serial.print("Midt"); Serial.print(digitalRead(13)); Serial.print("\t"); Serial.print("Venstre"); Serial.println(digitalRead(8));</p><p> }</p><p> /* joystick_rx_scaled = map(mydata_receive.joystick_rx, 0, 1023, 0, 255); joystick_ry_scaled = map(mydata_receive.joystick_ry, 0, 1023, 0, 255);</p><p> distServo.write(0);</p><p> mydata_send.test = mydata_send.test + 1; */</p><p>}</p><p>//////////////////////////////////////////////////////////////////// //***********************Functions********************************\\ ////////////////////////////////////////////////////////////////////</p><p>void motorForward() { digitalWrite(motorRightPinForward, HIGH); digitalWrite(motorRightPinBackward, LOW); digitalWrite(motorLeftPinForward, HIGH); digitalWrite(motorLeftPinBackward, LOW);</p><p>}</p><p>void motorBackward() { digitalWrite(motorRightPinForward, LOW); digitalWrite(motorRightPinBackward, HIGH); digitalWrite(motorLeftPinForward, LOW); digitalWrite(motorLeftPinBackward, HIGH); }</p><p>void motorBrake() { digitalWrite(motorRightPinForward, LOW); digitalWrite(motorRightPinBackward, LOW); digitalWrite(motorLeftPinForward, LOW); digitalWrite(motorLeftPinBackward, LOW); motorRightSpeed = 0; motorLeftSpeed = 0;</p><p>}</p><p>double filter(double lengthOrig, double currentValue) { //Filter from https://github.com/XRobots/openDog/blob/master/Part8/Dog008/Dog008.ino double filter = 5; double lengthFiltered = (lengthOrig + (currentValue * filter)) / (filter + 1); return lengthFiltered; }</p>
Attachments
Step 8: CODE - Joystick
<p>// Joystick arduino<br>// BY MB</p><p>//*********Libraries*********\\</p><p>#include #include #include #include #include </p><p>//*********Communication*********\\ //*********Easy transfer*********\\</p><p>EasyTransfer ET; //send EasyTransfer ET1; //receive</p><p>struct SEND_DATA_STRUCTURE { //put your variable definitions here for the data you want to send //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t joystick_rx; int16_t joystick_ry; boolean joystick_switch; unsigned int komTest = 0; boolean startSwitch; };</p><p>//give a name to the group of data SEND_DATA_STRUCTURE mydata_send;</p><p>struct RECEIVE_DATA_STRUCTURE { //put your variable definitions here for the data you want to receive //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t mode; int16_t startStop; int16_t ultraVal;</p><p>};</p><p>//give a name to the group of data RECEIVE_DATA_STRUCTURE mydata_receive;</p><p>//*********Display*********\\</p><p>#define OLED_MOSI 22 #define OLED_CLK 23 #define OLED_DC 24 #define OLED_CS 25 #define OLED_RESET 26 Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);</p><p>//*********Variables*********\\</p><p>unsigned long previousMillis = 0; const long interval = 20;</p><p>int filterRX; int filterRY;</p><p>//*********IO LIST*********\\</p><p>const int joystickPinRx = 0; // Analog 0 const int joystickPinRy = 1; // Analog 1 const int joystickPinSwitch = 2; // Digital 2 const int startSwitchPin = 3;</p><p>void setup() { Serial.begin(9600); // PC Serial monitor Serial1.begin(38400); // Bluetooth com port</p><p> ET.begin(details(mydata_send), &Serial1); ET1.begin(details(mydata_receive), &Serial1);</p><p> pinMode(joystickPinSwitch, INPUT_PULLUP); pinMode(startSwitchPin, INPUT_PULLUP);</p><p> display.begin(SSD1306_SWITCHCAPVCC); display.display(); delay(1000); display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE);</p><p>}</p><p>void loop() {</p><p> unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis;</p><p> if (ET1.receiveData()) {</p><p> }</p><p> mydata_send.joystick_rx = analogRead(joystickPinRx); mydata_send.joystick_ry = analogRead(joystickPinRy); mydata_send.joystick_switch = digitalRead(joystickPinSwitch); mydata_send.startSwitch = digitalRead(startSwitchPin); mydata_send.komTest = mydata_send.komTest + 1; filterRX = filter(analogRead(joystickPinRx), filterRX); filterRY = filter(analogRead(joystickPinRy), filterRY);</p><p> //Serial.println(mydata_receive.ultraVal);</p><p> ET.sendData(); } display.clearDisplay(); display.setCursor(0, 0); display.print("Start: "); display.print(mydata_receive.startStop); display.print(" "); display.print("Mode: "); display.print(mydata_receive.mode); display.print("\n"); display.print("Hastighed: "); display.print(filterRX); display.print("\n"); display.print("Retning: "); display.print(filterRY); display.print("\n"); display.print("UltraVal: "); display.print(mydata_receive.ultraVal); display.print("\n"); display.display();</p><p> //Serial.println(mydata_receive.test); //Serial.println(digitalRead(2)); //Serial.println(digitalRead(3));</p><p>}</p><p>double filter(double lengthOrig, double currentValue) { //Filter from https://github.com/XRobots/openDog/blob/master/Part8/Dog008/Dog008.ino double filter = 5; double lengthFiltered = (lengthOrig + (currentValue * filter)) / (filter + 1); return lengthFiltered; }</p>
Attachments
Step 9: Code - Ultra-sonic Slave
<p>#include <br>EasyTransfer ET2; // send Ultra Sonic</p><p>struct SEND_ULTRA_STRUCTURE { //put your variable definitions here for the data you want to receive //THIS MUST BE EXACTLY THE SAME ON THE OTHER ARDUINO int16_t distance;</p><p>};</p><p>//give a name to the group of data SEND_ULTRA_STRUCTURE ultra_send;</p><p>int duration;</p><p>const int distTrigPin = 2; const int distEchoPin = 3;</p><p>void setup() { Serial.begin(9600); ET2.begin(details(ultra_send), &Serial);</p><p> pinMode(distTrigPin, OUTPUT); pinMode(distEchoPin, INPUT); </p><p>}</p><p>void loop() {</p><p> ////////////////////////////////////////////////////////////////////////// //************************** SEND DATA ************************** ////////////////////////////////////////////////////////////////////////// </p><p>ET2.sendData();</p><p> ////////////////////////////////////////////////////////////////////////// //************************** ULTRA SONIC ************************** //////////////////////////////////////////////////////////////////////////</p><p> digitalWrite(distTrigPin, LOW); //code from https://howtomechatronics.com/tutorials/arduino/ultrasonic-sensor-hc-sr04/ delayMicroseconds(2); digitalWrite(distTrigPin, HIGH); delayMicroseconds(10); digitalWrite(distTrigPin, LOW); duration = pulseIn(distEchoPin, HIGH); // Reads the distEchoPing, returns the sound wave travel time in microseconds ultra_send.distance = duration * 0.034 / 2; // Calculating the distance, (0.034 the speed of sound in air, /2, distance back and forth) }</p>
Attachments
Step 10: Improvements
- Better noise filtration
- Better CAD-drawings
- More 3D printed stuff
- Better steering
- Better use of functions in the code
- More permanent solutions
- APP-support
Step 11: Conclusion/reflection
In this project I’ve become better at programming Arduino, and learned about serial communication, how to set up Bluetooth communication, making CAD drawing and 3D print them. I’ve managed to create an autonomous car that can follow a line, but there is room for a lot of improvements. Overall it was a fun project and I’m satisfied with the result.
Comments
5 years ago
Cool robot design.