Introduction to Robotics

Team

Simon Opelt (0355763), Philipp Aumayr (0356068), Jürgen Holzleitner (0056645)

Line Following Robot

Construction

Our robot has a 3 wheel design with 2 wheels attached to two motors and one wheel for stabilization. Turning is achieved by running the motors at different rotation speeds. One Light Sensor at the front of the car measuring.

Software

The goal of our robot algorithm is to follow a thick black line on the ground. Since we have only a single light sensor available for this project, we follow the edge of the line instead of the line itself.

At the beginning of the algorithm we try to find the minimum and maximum light levels by turning around a full circle. The 360-degree turn is not measured accurately but based on tested values (This does not have any influence on the rest of the algorithm, since it tries finding the first edge by turning in the other direction). This calibration step is especially important with light sensors as the minimum and maximum gray level values vary from hour to hour.

By averaging the maximum and minimum values we determine the target gray value which should be measured if the sensor is above the left edge of the line (seen from driving direction).

For driving we have two different speed values, one for turning and one for driving a straight line. The light value is measured as often as possible. Depending on the difference between the target gray value and the measured sensor value, we adjust the steering factor accordingly.

Based on the difference we calculate a basis move speed and add / subtract the turn speed which results in different speeds for each motor. The ratio between the move speed and the turn speed is defined by the absolute difference between the measured value and the target value.

All of the speed values are calculated relative to a maximum speed value, which can be adjusted according to the environmental constraints.

Mounting the sensor not immediately above the edge of the line allowed the sensor to see more of the line which made it react more smoothly.

Issues

    • Slippy floor makes it difficult to drive accurately.

    • The sensor was mounted too far from the turning axle of the driving wheels, and we therefore got fewer samples than required for optimal steering.

    • Minimum and maximum (linear interpolation) calculation had to be solved by fixed-point arithmetic.

Conclusio

Even tough we had to fight with these issues we mastered winning the competition far ahead of the rest and could almost keep up with the RCX sets which have the advantage of using two light sensors and a very accurate and powerful chain-drive with lots of grip.

Source Code

path.nxc#include "NXCDefs.h"void RotateLeft();void StopRotating();task main(){ // enable sensor light SetSensorLight(IN_1); int maxlight, minlight; maxlight = 0; minlight = 100; int maxspeed = 75; RotateLeft(); for(int i = 0; i < 210; i++) { int x = Sensor(IN_1); if(x > maxlight) maxlight = x; if(x < minlight) minlight = x; NumOut(0, LCD_LINE1, minlight); NumOut(0, LCD_LINE2, maxlight); Wait(5); } StopRotating(); int nTotalDiff=maxlight-minlight; int target = (minlight + maxlight) / 2; while(true) { int sensval = Sensor(IN_1); long diff = ((target - sensval)*1000)/nTotalDiff; long absDiff=diff; if (absDiff<0) absDiff=-absDiff; int movSpeed = (((500-absDiff)*maxspeed)/500)); int turnSpeed = ((diff*maxspeed)/1000) ; int nSpeedA=( movSpeed - turnSpeed ); int nSpeedC=( movSpeed + turnSpeed ); NumOut(0, LCD_LINE3, nSpeedA); NumOut(0, LCD_LINE4, nSpeedC); NumOut(0, LCD_LINE5, target); NumOut(0, LCD_LINE6, diff); if(nSpeedA > 0) OnRev(OUT_A, nSpeedA); else OnFwd(OUT_A, -nSpeedA); if(nSpeedC > 0) OnRev(OUT_C, nSpeedC); else OnFwd(OUT_C, -nSpeedC); }}void RotateLeft(){ OnFwd(OUT_A, 100); OnRev(OUT_C, 100);}void StopRotating(){ Off(OUT_AC);}

Parallel Parking

Introduction

Our project is about parallel parking a vehicle with a car-like steering mechanism. For this we used the standard NXT parts including an ultra-sonic rangefinder, 3 motors (one for driving, one for steering and one for sweeping the ultra-sound sensor in future development) and a touch sensor to detect the maximum steering amount.

Construction

We started construction by modularizing the problem into two separate parts: The rear section providing the drive system (forward and backward) and the front section responsible for steering left and right.

Beginning with the rear part we immediately recognized a problem. Car-like steering requires differential rotation on the rear-wheels of the car. Fortunately the older RCX development set provided a differential gear and the problem was solved in compact fashion.

Next we focused on the development of the steering mechanism. A typical car steering mechanism rotates the wheels around individual axle bearing points at the center of each wheel. We solved this by using the standard Lego parallel axis steering approach. To enable our steering mechanism to auto-calibrate itself we mounted a touch sensor that gets triggered immediately before the steering would be blocked by the mechanical limit.

We first tried a direct steering by a motor but the stepping resolution was too low and inaccurate to provide sufficiently detailed steering. We then applied a simple gearing to achieve a higher motor-rotation vs. steering ratio.

The next step would have been a direct connection of the rear and the front part, which would have resulted in quite a long vehicle. Thanks to a hint by our instructor, Zoltan Istenes, we came up with a solution where the rear part was mounted vertically instead of horizontally. This reduced the length of the vehicle enormously, making parallel parking easier.

Additionally, the vertical mount of the rear part provided a solid basis for mounting the NXT Brick and stabilizing the vehicle as a whole.

The final task was to place the ultrasonic sensor. A good position for accurate, fail-safe, reliable measurement requires free line of sight, a central position concerning horizontal as well as vertical alignment.

Setting up the working environment

From the lessons learned during the first project, we tried to get Bluetooth reprogramming of the device working. This would have allowed us to reprogram the Brick at the test-site without having to connect the Brick to the Notebook via USB.

While we were not able to get reprogramming working reliably (without having to repair every time, and without crashing the IDE every other time) we have found out the following facts:

    • Firmware version 1.4 included some patches to the Bluetooth connectivity.

    • In the BrixCC Root installation folder a file called NXT.dat caches a list of known NXT devices which prevents detection of Bluetooth enabled bricks. Simply deleting the file and restarting the IDE causes BrickCC to rescan and rebuild the file.

    • In case BrixCC cannot find the device or multiple devices are available, one can specify the MAC address of the device using the following syntax: BTH::NXT::00:16:53:03:3c:71 (BTH::NXT::<MAC>).

Since Bluetooth reprogramming required a firmware upgrade but didn't resolve all issues, we decided to downgrade to firmware version 1.3 due to other bugs found in version 1.4. The first time we tried flashing the firmware using the BrixCC we ended up with a non-responding NXT device. After a first shock, we reset the device and reprogrammed the brick using firmware version 1.3 and Lego's proprietary software in order to continue our time-critical mission.

We also considered using leJOS in combination with the Eclipse IDE development plugin. We found out that the plugin and the leJOS firmware works perfectly with the older RCX brick, but NXT support is still preliminary and under heavy development.

Software

The software we developed for parallel parking our vehicle is separated into 4 distinct parts:

    • Setup and Calibration

    • Searching for a parking lot big enough for our car

    • A fixed parking routine

    • Staying in track by driving at a fixed distance to the side of the road

Setup and Calibration

At the beginning of our algorithm the steering is calibrated. This is done by turning the steering motor until the steering-touch-sensor is triggered. Then the current motor rotation count is retrieved, and the motor is turned on into the other direction until the touch sensor is triggered again. This gives us the total range of the steering motor without breaking any hardware. To center the wheels we turn again half-way towards the other direction. This has proven to be accurate enough for our purposes.


void CalibrateSteering(){ OnRev(OUT_A, 75); while(SensorBoolean(S4) == 0); Off(OUT_A); int leftValue = MotorRotationCount(OUT_A); NumOut(0, LCD_LINE1, MotorRotationCount(OUT_A)); OnFwd(OUT_A, 75); Wait(500); while(SensorBoolean(S4) == 0); Off(OUT_A); int rot = (MotorRotationCount(OUT_A) - leftValue) / 2; NumOut(0, LCD_LINE1, rot); ResetRotationCount(OUT_A); RotateMotor(OUT_A, 75, -rot);}Searching for a parking lot big enough for our carWhile driving a long the side of the wall, our Algorithm constantly measures the distance to the side of the road. If the distance to the wall is greater than the width of our car, we found a parking lot. After this we continue driving passed the parking lot, till either the width is not big enough any more or we have reached the length our algorithm requires for parking. The length is measured by the difference in motor rotation counts.int FindAParkingLot(int &totalDistance int &distToWall){ if (distToWall<=0) { distToWall = SensorUS(S1); while(distToWall == 0 || distToWall == 255) distToWall = SensorUS(S1); } NumOut(0, LCD_LINE2, distToWall); int curVal = distToWall; int readVal = 0; int startC = MotorRotationCount(OUT_C); OnRev(OUT_C, 50); doSteering = true; while(distToWall + CAR_WIDTH > curVal) { readVal = SensorUS(S1); if(readVal != 255) curVal = readVal; bool bSteer = false; int nDistance=curVal-distToWall; if (nDistance<CAR_WIDTH && nDistance>-CAR_WIDTH) { if(curVal < distToWall-1) { targetHeading = MIN(70, -nDistance * 30); bSteer = true; } if(curVal > distToWall+1) { targetHeading = MAX(-40, -nDistance * 10); bSteer = true; } if(!bSteer) targetHeading = 0; } if(totalDistance + (MotorRotationCount(OUT_C) - startC) >= 12000) { totalDistance = 12000; return 0; } NumOut(0, LCD_LINE1, curVal); } doSteering = false; TextOut(0, LCD_LINE4, "Found Lot"); totalDistance += MotorRotationCount(OUT_C); int startDist = MotorRotationCount(OUT_C); int rotationCount = 0; while(distToWall + CAR_WIDTH < curVal && rotationCount < MIN_LOT_LENGTH) { readVal = SensorUS(S1); if(readVal != 255) curVal = readVal; rotationCount = -(MotorRotationCount(OUT_C) - startDist); NumOut(0, LCD_LINE1, curVal); } Off(OUT_C); NumOut(0, LCD_LINE3, rotationCount); totalDistance += rotationCount; if(rotationCount >= MIN_LOT_LENGTH) return true; return false;}We repeat the parking lot searching routine until we have found a parking lot big enough or have travelled to far and no parking lot can be found.bool FindGoodParkingLot(){ int totalDistance = 0; int nDistToWall=15; while(totalDistance < 6000) { if(FindAParkingLot(totalDistance, nDistToWall)) return true; } TextOut(0, LCD_LINE4, "No Parking Lot"); return false;}A Fixed parking routineThe fixed parking lot routine is straight forward and follows simple rules learned in driving school./* Motor Control */void MoveForward(int nTime){ OnRev(OUT_C, 75); Wait(nTime);}void SteerLeft(int nPercent){ RotateMotor(OUT_A, 75, (200*nPercent)/100);}void SteerRight(int nPercent){ RotateMotor(OUT_A, 75, -(200*nPercent)/100);}void DoPrimitiveParking(){ TextOut(0, LCD_LINE4, "Start parking"); RotateMotor(OUT_C, 50, -450); SteerRight(100); start DoBeeb; RotateMotor(OUT_C, 50, 500); SteerLeft(100); RotateMotor(OUT_C, 50, 150); SteerLeft(100); RotateMotor(OUT_C, 50, 500); SteerRight(100); RotateMotor(OUT_C, 50, -100);}In order to comply with federal laws we have to make sure pedestrians around the vehicle are warned of the parallel parking truck and we therefore implemented a routine beeping at 440 Hz once per second. This had to be done in its own task in order to remain accurate.task DoBeeb(){ for (int n=0; n<9; ++n) { TextOut(0, LCD_LINE5, "BEEEP"); PlayTone(440, 500); Wait(1000); }}All of the 3 above source code parts make up our main routine for the first working parking algorithm.task main(){ SetSensorType(S1, SENSOR_TYPE_LOWSPEED); SetSensor(S4, SENSOR_TOUCH); CalibrateSteering(); start AdjustSteering; if(FindGoodParkingLot()) { DoPrimitiveParking(); } else { TextOut(0, LCD_LINE5, "NO PARKING"); } Wait(2000);}Staying in track by driving at a fixed distance to the side of the roadThe first problem with parallel driving is that steering occurs all the time. Our first implementation of the steering algorithm used blocking calls for steering which resulted in a low number of measurements per second. We therefore had to move the steering code into its own task to allow ongoing ultra-sonic range measurements while steering.Our parking lot searching algorithm is extended with code which keeps the car at a constant distance to the wall while trying to drive as parallel as possible.This can be acheived by constantly measuring and correcting the steering according to the distance: If the measured distance is smaller than the required distance we steer away from the wall, if it is greater we steer towards the wall. If the distance is within a small threshold we do not adjust our steering at all.Since the steering is within its own task, we have one shared variable to activate steering and another one to pass the direction where to steer to. doSteering = true; while(distToWall + CAR_WIDTH > curVal) { readVal = SensorUS(S1); if(readVal != 255) curVal = readVal; bool bSteer = false; int nDistance=curVal-distToWall; if (nDistance<CAR_WIDTH && nDistance>-CAR_WIDTH) { if(curVal < distToWall-1) { targetHeading = MIN(70, -nDistance * 30); bSteer = true; } if(curVal > distToWall+1) { targetHeading = MAX(-40, -nDistance * 10); bSteer = true; } if(!bSteer) targetHeading = 0; } if(totalDistance + (MotorRotationCount(OUT_C) - startC) >= 12000) { totalDistance = 12000; return 0; } NumOut(0, LCD_LINE1, curVal); } doSteering = false;The source code for the steering task is straight forward and does not require further explanation.task AdjustSteering(){ doSteering = false; int initHeading = MotorRotationCount(OUT_A); while(true) { if(doSteering) { int curHeading = MotorRotationCount(OUT_A) - initHeading; ClearScreen(); TextOut(0, LCD_LINE6, "steering"); NumOut(0, LCD_LINE7, targetHeading); NumOut(0, LCD_LINE8, curHeading); int diff = targetHeading - curHeading; if(diff < -5) { OnRev(OUT_A, 30); while((MotorRotationCount(OUT_A) - initHeading) > targetHeading ); Off(OUT_A); } if(diff > 5) { OnFwd(OUT_A, 30); while((MotorRotationCount(OUT_A) - initHeading) < targetHeading ); Off(OUT_A); } } Wait(20); }}

Issues

Although the algorithm itself is working it has shown to be quite error-prone due to the relative rotation of the car to the wall. The reason for this is that due to the nature of the steering a change in turn has a very long delay until showing an effective change in the measurement. This would have been quite easy to solve by using two ultra-sonic range finders at the front and the rear of the car, revealing the relative rotation of the car to the wall.

The rotation of the car also affects the measurement since the range finder is not always perpendicular to the wall and therefore measures a distance greater than the effective minimum distance to the wall. Measurements made not perpendicular to an object have also shown to be quite inaccurate.

During the development we have found out that the ResetMotorRotation count does not always reliably reset the motor rotation, and we there have to generally measure relative rotation counts. Changing the code was done quickly, but up to that point this bug had caused a lot of collateral damage.

Final Result

Source Code

parkobot.nxc


#include "NXCDefs.h"#define CAR_WIDTH 20 #define MIN_LOT_LENGTH 850 #define MIN(x, y) ((x < y) ? x : y) #define MAX(x, y) ((x < y) ? y : x) int targetHeading = 0;bool doSteering;task AdjustSteering(){ doSteering = false; int initHeading = MotorRotationCount(OUT_A); while(true) { if(doSteering) { int curHeading = MotorRotationCount(OUT_A) - initHeading; ClearScreen(); TextOut(0, LCD_LINE6, "steering"); NumOut(0, LCD_LINE7, targetHeading); NumOut(0, LCD_LINE8, curHeading); int diff = targetHeading - curHeading; if(diff < -5) { OnRev(OUT_A, 30); while((MotorRotationCount(OUT_A) - initHeading) > targetHeading ); Off(OUT_A); } if(diff > 5) { OnFwd(OUT_A, 30); while((MotorRotationCount(OUT_A) - initHeading) < targetHeading ); Off(OUT_A); } } Wait(20); }}/* Motor Control */void MoveForward(int nTime){ OnRev(OUT_C, 75); Wait(nTime);}void SteerLeft(int nPercent){ RotateMotor(OUT_A, 75, (200*nPercent)/100);}void SteerRight(int nPercent){ RotateMotor(OUT_A, 75, -(200*nPercent)/100);}void CalibrateSteering(){ OnRev(OUT_A, 75); while(SensorBoolean(S4) == 0); Off(OUT_A); int leftValue = MotorRotationCount(OUT_A); NumOut(0, LCD_LINE1, MotorRotationCount(OUT_A)); OnFwd(OUT_A, 75); Wait(500); while(SensorBoolean(S4) == 0); Off(OUT_A); int rot = (MotorRotationCount(OUT_A) - leftValue) / 2; NumOut(0, LCD_LINE1, rot); ResetRotationCount(OUT_A); RotateMotor(OUT_A, 75, -rot);}int FindAParkingLot(int &totalDistance int &distToWall){ if (distToWall<=0) { distToWall = SensorUS(S1); while(distToWall == 0 || distToWall == 255) distToWall = SensorUS(S1); } NumOut(0, LCD_LINE2, distToWall); int curVal = distToWall; int readVal = 0; int startC = MotorRotationCount(OUT_C); OnRev(OUT_C, 50); doSteering = true; while(distToWall + CAR_WIDTH > curVal) { readVal = SensorUS(S1); if(readVal != 255) curVal = readVal; bool bSteer = false; int nDistance=curVal-distToWall; if (nDistance<CAR_WIDTH && nDistance>-CAR_WIDTH) { if(curVal < distToWall-1) { targetHeading = MIN(70, -nDistance * 30); bSteer = true; } if(curVal > distToWall+1) { targetHeading = MAX(-40, -nDistance * 10); bSteer = true; } if(!bSteer) targetHeading = 0; } if(totalDistance + (MotorRotationCount(OUT_C) - startC) >= 12000) { totalDistance = 12000; return 0; } NumOut(0, LCD_LINE1, curVal); } doSteering = false; TextOut(0, LCD_LINE4, "Found Lot"); totalDistance += MotorRotationCount(OUT_C); int startDist = MotorRotationCount(OUT_C); int rotationCount = 0; while(distToWall + CAR_WIDTH < curVal && rotationCount < MIN_LOT_LENGTH) { readVal = SensorUS(S1); if(readVal != 255) curVal = readVal; rotationCount = -(MotorRotationCount(OUT_C) - startDist); NumOut(0, LCD_LINE1, curVal); } Off(OUT_C); NumOut(0, LCD_LINE3, rotationCount); totalDistance += rotationCount; if(rotationCount >= MIN_LOT_LENGTH) return true; return false;}bool FindGoodParkingLot(){ int totalDistance = 0; int nDistToWall=15; while(totalDistance < 6000) { if(FindAParkingLot(totalDistance, nDistToWall)) return true; } TextOut(0, LCD_LINE4, "No Parking Lot"); return false;}task DoBeeb(){ for (int n=0; n<9; ++n) { TextOut(0, LCD_LINE5, "BEEEP"); PlayTone(440, 500); Wait(1000); }}void DoPrimitiveParking(){ TextOut(0, LCD_LINE4, "Start parking"); RotateMotor(OUT_C, 50, -450); SteerRight(100); start DoBeeb; RotateMotor(OUT_C, 50, 500); SteerLeft(100); RotateMotor(OUT_C, 50, 150); SteerLeft(100); RotateMotor(OUT_C, 50, 500); SteerRight(100); RotateMotor(OUT_C, 50, -100);}task main(){ SetSensorType(S1, SENSOR_TYPE_LOWSPEED); SetSensor(S4, SENSOR_TOUCH); CalibrateSteering(); start AdjustSteering; if(FindGoodParkingLot()) { DoPrimitiveParking(); } else { TextOut(0, LCD_LINE5, "NO PARKING"); } Wait(2000);}