Re: VFH algorithm
Thanks for the feedback. Having a lot of fun building it, not for military…personal fun!
I’ll give you the high level ideas to how I did it, there are many ways to do it.
By experimentation, I discovered that sonar signals bounce off walls at relatively small angles to the perpendicular of an oncoming wall. I settled on having sonars every 30 degrees around 3 sides of the bot…4 sides would be better for force field but time to cycle through all sonars slows down with more sensors. Even at 30 degrees, the bot can still run into objects at particular angles and shapes that bounce sound away from bot (like wedges). Still, nothing is perfect, and 30 degrees would still fit on my bot using cheap SRF04’s for about $5 a piece. I originally wanted to use Maxbotix sensors at $55 but since I didn’t know if the whole idea was going to work well, I decided to try it on the cheap sensors first.
Next, I started with the NewPing library. There is a 15 sensor version that allows you to run a bunch of sonars without delays…crucial for this situation. To use this lib, you supply code for the oneSensorCycle function. Basically, I cycle through the readings and hand the values over to my force service, then call the force service to do some processing, then the obstacle service.
void oneSensorCycle()
{
for (int i = 0; i < SONAR_NUM; i++)
{
UpdateForceDistance(i+1, distance[i]);
}
ForceService();
ObstacleService();
if(_SonarArrayOutputSwitch)
{
for (int i = 0; i < SONAR_NUM; i++)
{
Serial.print(i+1);
Serial.print("=");
Serial.print(distance[i]);
Serial.print("in ");
}
Serial.println();
}
}
Next the Force Service. This service is called one time for every cycle of the sonars. Kind of dangerous if you are driving in close proximity at any significant speed. Each vector is configured in the SetupForce function. In my case, one attractive force (the goal of where I want to go), and 9 repulsive forces, one for each sonar. I kept the force code separate from the sonar code intentionally so I could add behaviors later like “move towards light”, “move towards other bots”, “move towards high ground”. The idea being, I might want to add attractive or repulsive forces in the future that were not sonar related. I also setup the ability to modify the direction of a force. This is because as the robot moves around, the direction of its goal will change rapidly relative to the bot. Some code somewhere will need to update this heading regularly. Here’s the code…yes I realize I could rewrite this with one struct array and I plan to…I only just got this working a week ago…this if proof of concept stuff.
// On Off Switch for Service
boolean _ForceServiceSwitch = false;
//Output Switch for Service
boolean _ForceOutputSwitch = false;
//Constants
const int NUM_FORCES = 10; //Number of forces in array
const int DISTANCE_THRESHOLD = 3; //Only readings above this value will be considered, all readings below will be ignored.
const int DISTANCE_FLOOR = 5; //All readings < this value will be set to this value
const int DISTANCE_CEILING = 36; //All readings above this amount will be considered to be same as DISTANCE_MAX and exert zero force.
const int DISTANCE_MAX = 255; //Do not change this value;
const long DEFAULT_ATTRACTIVE_FORCE_MAG = 100000;
const long DEFAULT_REPULSIVE_FORCE_MAG = -100000;
const long DEFAULT_REPULSIVE_FORCE_MAG2 = -50000;
const long DEFAULT_ATTRACTIVE_FORCE_DISTANCE = 14;
//Force Distance
int _ForceDistance[NUM_FORCES];
//Force Angles in Radians
int _ForceAngleDegrees[NUM_FORCES];
double _ForceAngleRadians[NUM_FORCES];
//Force X and Y Adjustment Factors
double _ForceAdjX[NUM_FORCES];
double _ForceAdjY[NUM_FORCES];
//Force Factors
int _Force[NUM_FORCES];
//Force Factor X and Y Components
int _ForceX[NUM_FORCES];
int _ForceY[NUM_FORCES];
//Force Switches - To Turn Each Force On or Off
boolean _ForceSwitch[NUM_FORCES];
int _ForceXSum = 0;
int _ForceYSum = 0;
//Force Magnitude, Positive for Attractive, Negative for Repulsive
long _ForceMagnitude[NUM_FORCES];
double _NetForceRadians;
int _NetForceDegrees;
int _NetForceMagnitude;
void SetupForce(boolean pIsServiceOn, boolean pShowOutput)
{
_ForceServiceSwitch = pIsServiceOn;
_ForceOutputSwitch = pShowOutput;
//Configure Attractive Forces - Angle will be updated later by compass or drive module in main loop
ConfigureForce(0, ON, 0, DEFAULT_ATTRACTIVE_FORCE_MAG, DEFAULT_ATTRACTIVE_FORCE_DISTANCE);
//Configure Repulsive Forces - Distances will be updated later by sonar module in main loop
ConfigureForce(1, ON, 0, DEFAULT_REPULSIVE_FORCE_MAG, DISTANCE_MAX);
ConfigureForce(2, ON, -30, DEFAULT_REPULSIVE_FORCE_MAG, DISTANCE_MAX);
ConfigureForce(3, ON, 30, DEFAULT_REPULSIVE_FORCE_MAG, DISTANCE_MAX);
ConfigureForce(4, ON, -60, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
ConfigureForce(5, ON, 60, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
ConfigureForce(6, ON, -90, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
ConfigureForce(7, ON, 90, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
ConfigureForce(8, ON, -120, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
ConfigureForce(9, ON, 120, DEFAULT_REPULSIVE_FORCE_MAG2, DISTANCE_MAX);
}
void ForceService()
{
if(_ForceServiceSwitch)
{
CalculateNetForce();
if(_ForceOutputSwitch)
{
ShowForceOutput();
}
}
else
{
setForceHeading(-1);
}
}
void SwitchForce(int pForceNumber, boolean pOnOffSwitch)
{
_ForceSwitch[pForceNumber] = pOnOffSwitch;
}
void ConfigureForce(int pForceNumber, boolean pOnOffSwitch, signed int pForceAngle, signed long pForceMagnitude, int pDistance)
{
_ForceSwitch[pForceNumber] = pOnOffSwitch;
_ForceAngleDegrees[pForceNumber] = pForceAngle;
_ForceAngleRadians[pForceNumber] = getRadiansFromDegrees(pForceAngle);
_ForceAdjX[pForceNumber] = sin(_ForceAngleRadians[pForceNumber]);
_ForceAdjY[pForceNumber] = cos(_ForceAngleRadians[pForceNumber]);
_ForceMagnitude[pForceNumber] = pForceMagnitude;
UpdateForceDistance(pForceNumber, pDistance);
}
void UpdateForceDistance(int pForceNumber, int pForceDistance)
{
//Serial.println(“Force Updated.”);
//Only Look at readings that are over threshold
if(pForceDistance > DISTANCE_THRESHOLD)
{
if(pForceDistance < DISTANCE_FLOOR)
{
_ForceDistance[pForceNumber] = DISTANCE_FLOOR;
}
else if(pForceDistance > DISTANCE_CEILING)
{
_ForceDistance[pForceNumber] = DISTANCE_MAX;
}
else
{
_ForceDistance[pForceNumber] = pForceDistance;
}
//Calculate Force
_Force[pForceNumber] = _ForceMagnitude[pForceNumber] / (pow(_ForceDistance[pForceNumber], 2));
//Calculate X and Y components of force
_ForceX[pForceNumber] = (int)(_Force[pForceNumber] * _ForceAdjX[pForceNumber]);
_ForceY[pForceNumber] = (int)(_Force[pForceNumber] * _ForceAdjY[pForceNumber]);
}
}
void UpdateForceAngle(int pForceNumber, int pForceAngle)
{
_ForceAngleDegrees[pForceNumber] = pForceAngle;
_ForceAngleRadians[pForceNumber] = getRadiansFromDegrees(pForceAngle);
_ForceAdjX[pForceNumber] = sin(_ForceAngleRadians[pForceNumber]);
_ForceAdjY[pForceNumber] = cos(_ForceAngleRadians[pForceNumber]);
if(_ForceOutputSwitch)
{
Serial.print("ForceNum: “);
Serial.print((int)pForceNumber);
Serial.print(” Updated Angle: ");
Serial.println((int)_ForceAngleDegrees[pForceNumber]);
}
}
double getRadiansFromDegrees(int pDegrees)
{
return (double)(pDegrees * 0.0174532925);
}
int getDegreesFromRadians(float pRadians)
{
return (int)(pRadians * 57.2957795);
}
int getNetForceDiff()
{
return _NetForceDegrees;
}
void CalculateNetForce()
{
_ForceXSum = 0;
_ForceYSum = 0;
for(int MyForceNum = 0; MyForceNum < NUM_FORCES; MyForceNum++)
{
if(_ForceSwitch[MyForceNum])
{
_ForceXSum = _ForceXSum + _ForceX[MyForceNum];
_ForceYSum = _ForceYSum + _ForceY[MyForceNum];
}
}
_NetForceRadians = atan2(_ForceXSum, _ForceYSum);
_NetForceDegrees = getDegreesFromRadians(_NetForceRadians);
_NetForceMagnitude = sqrt(pow(_ForceXSum,2) + pow(_ForceYSum,2));
//Update Force Heading with Heading Service
setForceHeading(getNewHeading(getHeading(), _NetForceDegrees));
}
The next service is key to make this work…the ObstacleService. The problem is, sonar readings are unreliable. You get crazy readings that are small, or crazy large when an object is actually close. You need filters. What I did was count the number of times in a row that a value in each vector (each sonar) was below 3 different ranges. If it ever got to 3, I considered the ruing to be broken. Imagine the bot has 3 rings of defense around it, lets say at 18in, 12in, and 6in. Although for me, they are ovals and not circular, I am more concerned with obstacles in front than to the sides. I didn’t include source for the obstacle service but I might when I feel a little better about it. But below is the basic ideas.
The idea is:
1. If no rings are broken, drive to where you want to drive.
2. Start making steering adjustments according to the ForceService if the first ring is broken, and start slowing down to buy more time for sonar cycles.
3. Slow down further if 2nd ring is broken, allow for sharper steering adjustments.
4. Stop if the innermost ring is broken and take some evasive action, back up, run a contingency mission, whatever.
My ObstacleService tells my ThrottleService what the MaxSpeed should be and what the heading should be, and the ThrottleService takes care of how much juice to send to each motor based on the difference between actual heading and desired heading.
Hope this helps.