Hello guys, i am actually doing a robot that has to pass over a cable. I have a sensor that detects the cable. can you suggest me ideas of how to make the robot move along the cable path. The sensor outputs (from analogRead)about 150-200 bits when on the cable and about 80-90 bits when not on the cable. Is PID control the only option or is can there me a simpler control method?
The sensor outputs about 150-200 bits
What sensor? Bits of what?
Is PID control the only option
No.
can there me a simpler control method?
Of course.
One thing to consider, though, is just how important it is to stay close to the cable, and how fast you want to go. If 3 meters a week is good, and staying within 9 parsecs of the cable is acceptable, then PID certainly isn't needed.
If 3 meters a second is needed, and you need to stay within 9 nanometers of the cable, nothing but PID will work.
Im sorry, not really bits. but the values that are obtained from analogRead. And the speed is very slow about 0.5 to 1 metre per minute. Even if the robot's movement zig-zags, it doesnt matter as long as it sticks to the path.
I drafted this piece of code just to ask if the principle would work? Or could you suggest me ways of improving the same.
int sensor_read = analogRead(A0);
forward (); //function to move forward
delay (1000);
sensor_read_new = analogRead(A0);
if (sensor_read > sensor_read_new ){ // by a certain percentage
Left(); // turn left function
delay(200);
forward ();
delay(200);
sensor_read_new = analogRead(A0);
if (sensor_read > sensor_read_new ){ // much less than above
Right(); // turn right
delay(200);
};
sensor_read_new = analogRead(A0);
else if (sensor_read == sensor_read_new ) {// approximately equal to
forward(); // move forward
};
Can you put the sensor on a swinging arm in front of the robot? Then you can take several readings across the swing and determine where the cable is much more precisely.
Back to the zig-zagging fixed-sensor idea, what about taking a series of readings when moving forwards in small steps? Then you can identify a peak where the cable is or find an increasing or decreasing trend. If you find that it's decreasing, reverse along your track and try to pick up the cable again.
MorganS:
Can you put the sensor on a swinging arm in front of the robot? Then you can take several readings across the swing and determine where the cable is much more precisely.
Not really, i have already built the robot, and the sensor is at the back. Would that create a problem. Actually i started with using three identical sensors, but it is just now that i am noticing that the voltage variations are too close.
MorganS:
Back to the zig-zagging fixed-sensor idea, what about taking a series of readings when moving forwards in small steps? Then you can identify a peak where the cable is or find an increasing or decreasing trend. If you find that it's decreasing, reverse along your track and try to pick up the cable again.
Taking a number of readings and finding the path, this is exactly what i have in mind, i have written a part of the code (above) could you check it, if it might work.
How fast does the sensor reading tail off when the cable is no longer fully under it?
I'm wondering if your robot is moving slowly enough relative to the frequency of the arduino so that any tail off can trigger a turn of varying amounts to keep it focused.
That said I suppose with only the 1 sensor you don't know whether the cable has moved to the right or the left of the sensor, with 2 sensors you could get this vector.
tammytam:
How fast does the sensor reading tail off when the cable is no longer fully under it?I'm wondering if your robot is moving slowly enough relative to the frequency of the arduino so that any tail off can trigger a turn of varying amounts to keep it focused.
That said I suppose with only the 1 sensor you don't know whether the cable has moved to the right or the left of the sensor, with 2 sensors you could get this vector.
tail off?
the range of the sensor is very small. so if it gets out of the cable by 5 cm, a significant change in output will result.. from about 150 to 80 (analogRead values).
the problem will be whether to turn right or left? I believe in this case i have to do it by trial and error? in case for e.g left doesn't work then right.
A lot of this is going to end up manually tuned. You don't want the thing taking big turns and driving away from the cable at every opportunity but you also don't want the thing to miss a bend in the cable. You will have to set it up where there are a few parameters that are adjustable - the distance driving forwards and the angle to turn each time. Then adjust once you have the real robot following a test cable.
Your comments in the pseudocode "much less than above" and so on need to be quantified. This kinds of comparisons indicate to me that it needs more memory than just one copy of sensor_read. I'm thinking that it needs to remember at least 3 readings and make decisions based on those last 3 readings.
In the code below, assume the command "take reading" will push all the previous readings down by one step and add a new reading at the top. So reading[0] is always the most recent, reading[1] is the previous one and reading[2] is the oldest.
loop {
takeReading()
forward()
takeReading()
if(reading[0] < reading[1]) {
//we are moving away from the cable
reverse()
turnLeft()
}
}
This will be inefficient but it could work. It will start at a point and search out a star pattern around the point, always coming back to that point if it doesn't find a better reading. But a right-hand bend in the cable will result in the robot reversing direction and coming back the way it started.
loop {
takeReading()
forward()
takeReading()
if(reading[0] < reading[1]) {
//we are moving away from the cable
reverse()
turnLeft()
forward()
takeReading()
if(reading[0] < reading[1]) {
//left turn was the wrong turn
reverse()
turnRight()
turnRight()
}
}
}
This seems to be a better idea. If it's on the line but the signal gets weaker, it will scan left and right and then resume moving in the original direction. It should be workable with imprecise mechanics - it doesn't have to turn exactly the same distance left as it goes right, although you do expect that a left followed by two rights will result in at least some right turn effect and forward+reverse doesn't drift too far from the starting position.
It's amazing what bugs you find in your code one second after hitting "post." The above code won't do what I said - if the signal gets weaker then it will keep feeling out left and right but won't move forward from that position.
If the signal is likely to weaken by 50 points when moving a long way away but might only change by 5 points when a speck of dirt is holding the robot fractionally higher off the cable then the less-than comparisons in the above code should use a threshold - we don't care if it gets just a little weaker for a single forwards movement of 5cm.
MorganS:
This seems to be a better idea. If it's on the line but the signal gets weaker, it will scan left and right and then resume moving in the original direction. It should be workable with imprecise mechanics - it doesn't have to turn exactly the same distance left as it goes right, although you do expect that a left followed by two rights will result in at least some right turn effect and forward+reverse doesn't drift too far from the starting position.
Yeah, this is actually a big concern.
i saw this algorithm on the net for line following IR using a single sensor.
Once the start button was pressed, the robot would go forwards until the sensor saw black. -Once the sensor saw black, the robot would continue to go forwards, while turning left gradually. -Whenever the sensor saw white (i.e. the robot was leaving the line), the robot would turn to the right until the sensor found black again. -It would continue the second and third steps until the stop button was pressed.
find attached the sensor data for on cable and off cable.
That last algorithm would work, but for left turns where the cable turned tighter than the "gradual" left turn of the robot, would result in an almost full circle of right turn before the line was found again.
But there has to be some trade off by using a single sensor.
tammytam:
That last algorithm would work, but for left turns where the cable turned tighter than the "gradual" left turn of the robot, would result in an almost full circle of right turn before the line was found again.But there has to be some trade off by using a single sensor.
i can make sure that there are no such harsh turns. i just have to prove that it can actually locate the cable and then follow it. i will try writing a program for the set of data and the last algorithm.
I have tried coding the sequence, but there seems to be a hysteresis problem with the sensing circuit. The problem i am having is that the integer value changes from about 0 to 70 when approaching the cable, but when it goes away from it, it does not go back to zero but gets to as low about 50. I do not know if there is an issue with the circuit but is there a way to counter this in the program.
// Initialize Motor Driver Pins
int motorENA = 6;
int motorIN1 = 46;
int motorIN2 = 48;
int motorIN3 = 50;
int motorIN4 = 52;
int motorENB = 7;
int ant_L_0;
int ant_M_0;
int ant_R_0;
int avg_0;
int ant_max=0;
void setup() {
// put your setup code here, to run once:
pinMode(motorENA, OUTPUT);
pinMode(motorIN1, OUTPUT);
pinMode(motorIN2, OUTPUT);
pinMode(motorIN3, OUTPUT);
pinMode(motorIN4, OUTPUT);
pinMode(motorENB, OUTPUT);
pinMode(ant_LED, OUTPUT);
ant_L_0 = analogRead(A8);
ant_M_0 = analogRead(A9);
ant_R_0 = analogRead(A10);
// avg_0 = ant_avg();
Serial.begin(9600);
}
void loop() {
start: //Pointer - goto start; can be used to restart program when required
int avg_new;
int avg = ant_avg();
if (avg < 25) {
digitalWrite(ant_LED, LOW);
Serial.print("avg:");
Serial.println(avg);
sTop();
delay(100);
goto start;
}
else if (avg >= 25) {
digitalWrite(ant_LED, HIGH);
Serial.println("Cable Found");
Serial.print("avg:");
Serial.println(avg);
forward_left();
delay (200);
avg_new = ant_avg();
if ((avg < avg_new) && (ant_max < avg_new)) {
ant_max = avg_new;
Serial.print("ant_max:");
Serial.println(ant_max);
};
Serial.print("ant_max:");
Serial.println(ant_max);
Serial.print("avg_new:");
Serial.println(avg_new);
if ((( ant_max - avg_new) >= 10)&& (( ant_max - avg_new) < 25)){
rIght();
delay (100);
//forward_left();
//delay (100);
}
else if (( ant_max - avg_new) >= 25){
sTop();
delay (20000);
};
};
}
//Locomotion Functions
void reverse()
{
analogWrite(motorENA, 255); // right wheel
analogWrite(motorENB, 255); //left wheel
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, LOW);
digitalWrite(motorIN3, LOW);
digitalWrite(motorIN4, HIGH);
delay(50);
};
void forward_left() // slightly to the left
{
analogWrite(motorENA, 180); // right wheel
analogWrite(motorENB, 150); //left wheel
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void forward()
{
analogWrite(motorENA, 255);
analogWrite(motorENB, 255);
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void rIght()
{
analogWrite(motorENA, 150);
analogWrite(motorENB, 150);
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, LOW);
digitalWrite(motorIN4, HIGH);
delay(50);
};
void lEft()
{
analogWrite(motorENA, 150);
analogWrite(motorENB, 150);
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, LOW);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void sTop()
{
analogWrite(motorENA, 255);
analogWrite(motorENB, 255);
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, HIGH);
delay(50);
};
int ant_avg() { //averaging the 3 sensors minus initial value when cable is off - callibrating
int ant_L_new = analogRead(A8);
int ant_M_new = analogRead(A9);
int ant_R_new = analogRead(A10);
int ant_L = ant_L_new - ant_L_0;
int ant_M = ant_M_new - ant_M_0;
int ant_R = ant_R_new - ant_R_0;
int ant_avg = ((ant_L + ant_M + ant_R) / 3);
return ant_avg;
};
can anyone help me out. I have to ensure that the robots stays on the cable, that is about the maximum value obtained from the sensor and stop soon as it leaves the cable.
The goto
is totally unnecessary. The rest of the main loop code is inside the else
so it won't get executed and the next iteration of the loop will start at the top.
It might be a good idea to move the main variables outside the loop as globals or declare them as static
because the value of the local variables will be lost when the loop ends.
Capital letters in functionNames() are used to make it easier to read the individual words used to make the name. sTop() is not a clever name for a function. motorsStop() or motionStop() might be a better name. Or just call it stop() although sometimes this will get you in trouble with keywords or other things declared elsewhere in the Arduino core. You can't have a function called while(), for example.
OK, get to the algoritm, batman!
It looks like you're intentionally driving in curves. That's not going to make it easy to reacquire the cable when its lost. Then you've got lots of big delays in the code. This makes it impossible to look at the sensor while it's driving along. In fact, it looks like rather than just move forward a specified amount for each call to forward_left(), it keeps driving along in that direction until after you've calculated that it's the wrong direction.
Start small, with small movements. Move. Stop. Sample. Decide.
MorganS:
Thegoto
is totally unnecessary. The rest of the main loop code is inside theelse
so it won't get executed and the next iteration of the loop will start at the top.It might be a good idea to move the main variables outside the loop as globals or declare them as
static
because the value of the local variables will be lost when the loop ends.Capital letters in functionNames() are used to make it easier to read the individual words used to make the name. sTop() is not a clever name for a function. motorsStop() or motionStop() might be a better name. Or just call it stop() although sometimes this will get you in trouble with keywords or other things declared elsewhere in the Arduino core. You can't have a function called while(), for example.
Ok, i'll modify it.
MorganS:
It looks like you're intentionally driving in curves. That's not going to make it easy to reacquire the cable when its lost. Then you've got lots of big delays in the code. This makes it impossible to look at the sensor while it's driving along. In fact, it looks like rather than just move forward a specified amount for each call to forward_left(), it keeps driving along in that direction until after you've calculated that it's the wrong direction.Start small, with small movements. Move. Stop. Sample. Decide.
Actually moving in a curve is being a problem in itself, as once it is lost, it cannot seem to locate the cable back and moves completely away from the cable. Is there a better solution? for a single sensor robot?
The thing abut delays. what is the minimum delay should i use for stable movements, about 50 or 100?
int ant_LED = 47;
int battery_full = 49;
// Initialize Motor Driver Pins
int motorENA = 6;
int motorIN1 = 46;
int motorIN2 = 48;
int motorIN3 = 50;
int motorIN4 = 52;
int motorENB = 7;
int ant_L_0;
int ant_M_0;
int ant_R_0;
int avg_0;
static int avg_new;
static int avg;
static int ant_max=0;
void setup() {
// put your setup code here, to run once:
pinMode(motorENA, OUTPUT);
pinMode(motorIN1, OUTPUT);
pinMode(motorIN2, OUTPUT);
pinMode(motorIN3, OUTPUT);
pinMode(motorIN4, OUTPUT);
pinMode(motorENB, OUTPUT);
pinMode(ant_LED, OUTPUT);
ant_L_0 = analogRead(A8);
ant_M_0 = analogRead(A9);
ant_R_0 = analogRead(A10);
// avg_0 = ant_avg();
Serial.begin(9600);
}
void loop() {
start: //Pointer - goto start; can be used to restart program when required
avg = ant_avg(); // Store sensor value in avg
if (avg < 10) { //Check if cable is nearby
digitalWrite(ant_LED, LOW);
Serial.print("avg:");
Serial.println(avg);
motionStop();
//delay(50);
//goto start; Unecessary at this point
}
else if (avg >= 25) { //If cable is nearby, the avg value should be above this threshold
digitalWrite(ant_LED, HIGH);
Serial.println("Cable Found");
Serial.print("avg:");
Serial.println(avg);
forward_left(); //Move forward with slight inclination on the left side
delay (100);
avg_new = ant_avg(); //Check the Sensor Value once again
if ((avg < avg_new) && (ant_max < avg_new)) { //If new sensor value is greater than previous value and maximum value obtained
ant_max = avg_new; // Change maximum value to the greater value
Serial.print("ant_max:");
Serial.println(ant_max);
};
Serial.print("ant_max:");
Serial.println(ant_max);
Serial.print("avg_new:");
Serial.println(avg_new);
if ((( avg_new - avg) >= 5) && (( ant_max - avg_new) < 25)) { //If new value>previous value and no large deviation
rIght(); //means robot has deviated too far in the left
//delay (50);
//forward_left();
//delay (100);
}
else if (( ant_max - avg_new) >= 25) { //Check if there was any large deviation in the new value.
motionStop(); //large deviation means cable is either offline or ended.
// delay (20000);
};
};
}
//Locomotion Functions
void reverse()
{
analogWrite(motorENA, 255); // right wheel
analogWrite(motorENB, 255); //left wheel
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, LOW);
digitalWrite(motorIN3, LOW);
digitalWrite(motorIN4, HIGH);
delay(50);
};
void forward_left() // slightly to the left
{
analogWrite(motorENA, 180); // right wheel
analogWrite(motorENB, 150); //left wheel
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void forward()
{
analogWrite(motorENA, 255);
analogWrite(motorENB, 255);
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void rIght()
{
analogWrite(motorENA, 150);
analogWrite(motorENB, 150);
digitalWrite(motorIN1, LOW);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, LOW);
digitalWrite(motorIN4, HIGH);
delay(50);
};
void lEft()
{
analogWrite(motorENA, 150);
analogWrite(motorENB, 150);
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, LOW);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, LOW);
delay(50);
};
void motionStop()
{
analogWrite(motorENA, 255);
analogWrite(motorENB, 255);
digitalWrite(motorIN1, HIGH);
digitalWrite(motorIN2, HIGH);
digitalWrite(motorIN3, HIGH);
digitalWrite(motorIN4, HIGH);
delay(50);
};
int ant_avg() {
int ant_L_new = analogRead(A8);
int ant_M_new = analogRead(A9);
int ant_R_new = analogRead(A10);
int ant_L = ant_L_new - ant_L_0;
int ant_M = ant_M_new - ant_M_0;
int ant_R = ant_R_new - ant_R_0;
int ant_avg = ((ant_L + ant_M + ant_R) / 3);
return ant_avg;
};
I believe there is a problem with this code, can someone help me out. It moves slightly then just stops.
My guess is that it's lost the cable, and now each loop the avg is below the threshold so it just runs motionStop().
You have a fair bit of console spam there, add some more showing what its current state is before it moves then post the console here, be easier to pick through the logic.
tammytam:
My guess is that it's lost the cable, and now each loop the avg is below the threshold so it just runs motionStop().
Yeah. Actually what i did was remove the initial values that the circuit was giving when there no cable. What i notice is that when the cable is detected, the value jumps to greater than 30 to a maximum of 80. The values are not the same all the time. Plus the values on activating the cable, goes from about 0 (<10) and jumps to above 30 to about 80 gradually (I believe it should stop for a while once it detects the cable and wait for it to go to a maximum) . However when the cable is removed, the values jump back to about 50 and not to zero. Might there be a bug in the part about reading the sensor values?
i do not know why there is a hysteresis now as the circuit was tested on an oscilloscope and was fine.
tammytam:
You have a fair bit of console spam there, add some more showing what its current state is before it moves then post the console here, be easier to pick through the logic.
Console spam?
Console Spam = your serial data.
So you've tested the sensor separately and it has a reasonable response to the desired conditions? Then what's different when it's mounted on the robot? Is it picking up interference from the robot's own motors? Try to have it stop all motors before taking a reading.
Is it something else on the robot that's doing it? Maybe the sensor should shielded or moved to a different place on the robot.
You could write code to mitigate some of the hysteresis, like a drop of 5 points is considered as significant as a rise of 50 points, but what if it drops 5 and then approaches the cable again? Does it go up 5 or 50?