Sunday, February 22, 2015

Left turn ahead

The a great thing about computer science is that usually there is more than one way to accomplish a goal.  This is true for even the simplest of goals, such as turning left.  Today we'll look at different solutions to turn Boe-bot 90 degree left.

Solution 1: Time based turning
This solution relies on a predetermined time value for how long to keep the drive servos moving in order to turn 90 degrees.  Using the Parallax libraries, the code for this solution is short and straight forward.  Success!

Wait, why didn't this work as well on carpet as hardwood?  Why did Boe-bot do a 70 degree turn when the battery was running low?

This approach has a couple drawbacks.  Since you have to determine how long the servo should spin at design time a number of factors will impact the run time need such as the specific servo power, remaining battery power and surface friction.  Many of these factors aren't known as design and will change over time.  Clearly this method introduces inaccuracy into our navigation!

Usage note on servo.h: Don't confuse servo_speed with servo_set. Using servo_speed to start and stop the servos will quickly chew through the available cogs leaving you pondering what went wrong unless you are inspecting the function return values for errors.

#include "simpletools.h"
#include "servo.h"


static int LEFT_MOTOR = 14;
static int RIGHT_MOTOR = 15;

void stop()
{
    servo_set(LEFT_MOTOR, 1500);
    servo_set(RIGHT_MOTOR, 1500);
}

void pre_determined_rotation_left_turn()
{
    servo_set(LEFT_MOTOR, 1400);
    servo_set(RIGHT_MOTOR, 1400);
    pause(520);
    stop();
}

int main()
{
     pre_determined_rotation_left_turn();

}

Solution 2: Let's get some feedback
In the last post I introduced the compass module. Let's use the compass module to know the direction we started at and get feedback as we turn.  In this solution, we turn a small increment and then check our position and repeat until we are at our goal.  It's the robotic version of asking "are we there yet".  

The real world isn't as precise as a float, so we can't directly compare our current heading to the desired heading to know if we are there yet.  In fact, it can be a real challenge to work with floats in general.  To step side this challenge, we introduce a delta value and accept success if our heading is within +/- the delta value from the desired heading.

This approach provides greater assurances of accuracy within a range.  A trade off of this approach is that the many small steps produce a stuttering effect while turning.  This translates into slower turning speed than a continuous approach.      

#include "simpletools.h" // Include simple tools
#include "servo.h"
#include "compass3d.h" // Include compass3d header
#include "simplei2c.h"

//PIN ASSIGNMENTS//
static int COMPASS_SCL = 10;
static int COMPASS_SDA = 11;
static int LEFT_MOTOR = 14;
static int RIGHT_MOTOR = 15;

float getCompassHeading(i2c *bus)
{
  int x, y, z; // Declare x, y, & z axis variables

  compass_read(bus, &x, &y, &z); // Compass vals -> variables

  int *px, *py, *pz;

  px = &x;
  py = &y;
  pz = &z;

  *px = x;
  *py = y;
  *pz = z;

  float heading = atan2(x, y);
  if(heading < 0)
  {
     heading += 2.0 * 3.14;
  }

  float headingDegrees = heading * 180/3.14;

  return headingDegrees;
}

void stop_servo()
{
  servo_set(LEFT_MOTOR, 1500);
  servo_set(RIGHT_MOTOR, 1500);
}

void go_left(int duration)
{
  servo_set(LEFT_MOTOR, 1400);
  servo_set(RIGHT_MOTOR, 1400);
  pause(duration);
  stop_servo();
}

void turn_left_using_compass()
{
  i2c *bus = i2c_newbus(COMPASS_SDA, COMPASS_SCL, 0); // New I2C bus SCL=P10, SDA=P11
  compass_init(bus);

  float newHeading = getCompassHeading(bus) + 90;
  if(newHeading > 360)
  {
      newHeading = newHeading - 360;
  }

  printf("previous heading: %f\n",getCompassHeading(bus));
  float tempHeading = getCompassHeading(bus);
  static float delta = 3;
  int keepTurning = 0;
  printf("turning left to heading: %f\n",newHeading);
  printf("margin of error: %f to ",(newHeading - delta));
  printf("%f\n", (newHeading + delta));

  while( keepTurning == 0)
  {
     go_left(40);
     tempHeading = getCompassHeading(bus);
     if( tempHeading > (newHeading - delta) && ( tempHeading < (newHeading + delta) ) )
     {
       printf("now at heading: %f\n",tempHeading);
       //within margin of error, stop turning
       keepTurning = 1;
     }
  }
  print("left turn complete\n");
  stop_servo();
}

int main()
{
  turn_left_using_compass();
  servo_stop();

}

Where to turn for future improvements

  • A hybrid approach.  Using the compass to take small steps provides greater accuracy at the cost of slower physical turning performance.  Storing the sum of the steps the first time the function is run could be used to determine how big of a "long step" to make.  Moving this from a design time to run time decision.  Subsequent turns could then take that "long step" and then "small steps" as needed near the goal.
  • Wheel encoders!  Additional feedback from these sensors would enable precise turning of the wheels.     



Saturday, November 8, 2014

Steps towards navigation and mapping

Mapping your environment, remembering where you've been and what you've seen, is fundamental to how humans and many animals survive.  For robots, mapping capabilities  increase the range of tasks that can be accomplished and the level of autonomy.

To accomplish this on the Bebot platform, I added a compass module.  With an ultrasonic PING and PWM servo drive motors, the robot will be able to determine its heading, detect obstacles and measure how far it has traveled.  Ultimately this will lead to Bebot producing and using a map.  Producing a map while exploring an environment is known as SLAM.



This image shows how to wire the compass on the breadboard.  The compass module needs four connections (pin 10 - SDA, pin 11 - SCL, 5V and GND).  For easy identification, I used yellow wires for data, red for power and black for ground.




Sunday, January 5, 2014

One ping, and one ping only

While bouncing off the walls with whiskers is a lot of fun, for the sake of my walls it is time to find another method of obstacle avoidance that involves less blunt force trauma.  

There are many ways to detect obstacles without touching them including infra red LEDs, ultra sonic, lasers and cameras to name a few common types.  Each has benefits and trade offs choosing the right choice will depend on the use case.

Today I am testing out the Ping ultrasonic distance sensor to see how it performs.  Hopefully it will be suitable as the primary obstacle detection sensor for Boe-bot.  

Ultra sonic distance detection works on the principle of sending out high frequency pulses and listening for the echo of these pulse off objects.  The longer the time it takes for the echo to return from an object, the farther away it is.  Since the speed of the wave is assumed to be constant (and it pretty much is), using the formula Rate*Time=Distance, you can solve for distance.  Simple in theory but as we'll see the devil is in the details.  

Test Setup

The ping sensor was mounted on Boe-bot's breadboard area.  For the experiment, I removed the whiskers.  

Since this is a test of distance measurement and object detection, having an easy and repeatable way to measure distances is important to collecting good data.  Surprisingly, a quilting board laid out with an inch wide grid pattern from the craft store met my needs.  With this setup I was able to test distances of up to 60 inches.  Putting the setup on a table with several feet of room around it gave me confidence that I wouldn't get noise in my data.  



The tests
A cardboard box (shown in the image above) with the face perpendicular to Boe-bot and no horizontal offset showed accurate distance measurement throughout the entire test range (5 to 60 inches).  


Actual DistancePing MeasuredDifference
6059.840.26%
5050.000.00%
4039.760.59%
3030.71-2.36%
2020.08-0.39%
1010.24-2.36%
55.12-2.36%

The story was very different once the box was angled.  Turning the cardboard box to face Boe-bot at a 45 degree angle resulted in a failure to detect the box from any distance!  The Ping sensor was able to detect the box once it was less than 30 degrees.  

The next test was to see how object shape impacted detection.  A cylinder with a six inch diameter was used to if the round face would impact accuracy.    


Actual DistancePing MeasuredDifference
6061.02-1.71%
5050.79-1.57%
4040.94-2.36%
3030.71-2.36%
2020.47-2.36%
1010.24-2.36%
55.51-10.24%

The next step is to see how well the Ping performs detecting objects off of the central axis.  To test this, I took an object and moved it in one inch increments from directly in front of the Ping until it was no longer detected.  The following graphic (yellow lines) shows the limits of where Ping was able to detect objects.  


In the real world, Boe-bot face could multiple objects at the same time.  This presents a unique challenge since the Ping sensor only returns a single value with each measurement.  I tested this by placing three objects in front of Boe-bot, each of which the Ping sensor was able to detect individually.  A large object at 40 inches, a medium one at 30 inches and small object at 20 inches.  In this scenario, the Ping sensor detected the medium object at 31 inches.  I then removed the small object and moved the large object to 20 inches, this resulted in a measurement of 20 inches.  


Not every object Boe-bot will come into contact with (or hopefully not come into contact with) is a regularly shaped object.  The final test is a crumpled ball of aluminum foil.  With a variety of faces, it was like some kind of magical top.  The aluminum foil ball would appear and disappear depending on the angle.


Next steps
That is a lot of data!  The next article will dig into analyzing these findings and how the Ping can be applied to helping Boe-bot navigate.


Sunday, September 15, 2013

Symphony in C

One of the challenges with a custom language like Spin is that a lot of the support mechanisms available to standard languages are not available.  

The great news is that Parallax has released a C/C++ compiler and IDE for the Propeller chip!    A set of libraries is under development by Parallax to provide support for many of the sensors, servos and accessories where there are Spin libraries.  

Here is a link to the main Parallax page for using C with propeller.

Going forward any code that I create for Boe-bot will be developed in C.  

Thursday, September 5, 2013

Really simple Artificial Intelligence (Part 2)

This is part two of really simple AI for Boe-bot in Spin.  In this example, Boe-bot is setup with a speaker (Pin 3) and two contact whiskers (Pins 6 and 9).

The code below gives boe-bot the goal of moving forward and several pre-programmed choices for dealing with obstacles.  Boe-bot tries different maneuvers when it encounters an obstacle until a maneuver is successful in navigating the obstacle.    

A few basic programming hygiene comments about the code:

  • Name variables and constants.  Without names the code is less maintainable.  For example, WhiskerContact = 0 and NoWhiskerContact =1.  Without these names it would be very easy to reverse which value meant what!  To improve the below code, the values 0 and 255, which represent the minimum and maximum values that can be stored in a byte should be replaced with constants that reflect their purpose.
  • Even better, give clear meaningful names.  Take constants C6, C7 and G6.  As the author I know that they correspond to musical note frequencies.  Unfortunately, that required context and increases the learning curve for others.  Better names would be noteC6, noteC7 and noteG6.
  • Split code into small logical units.  This may seem obvious but is important as your program gets larger.  As a general rule, a function should be about 20 lines of code.  It will also help with reuse of code, as larger units of code are less likely to be reusable and much smaller units will not be too simple to capture meaningful functionality.    

Future ideas for this code.  While this code is functional, there are ways it could be enhanced.

  • The PickManeuver function has simple logic, selecting the highest value.  Using a statistical model could improve the learning process.  This would also be useful if Boe-bot was in a changing environment and a previously successful maneuver was no longer successful.  
  • Generate obstacle avoidance maneuvers at run time.  Currently these are hard coded to be successful options.   


OBJ

 system : "Propeller Board of Education"
 speaker : "PropBOE Square Wave"
 drive : "PropBOE-Bot Servo Drive"
 pst    : "Parallax Serial Terminal Plus"
 pin    : "Input Output Pins"   
 time : "Timing"  

CON
  C6 = 1046
  C7 = 2093
  G6 = 1568
  SpeakerPin = 3
  LeftWhisker = 6
  RightWhisker = 9
  LeftServoPin = 14
  RightServoPin = 15
  WhiskerContact = 0
  NoWhiskerContact = 1

DAT
  '                   left   right    time
  forward      long    100,    100,   1000
  backward     long   -100,   -100,    500
  pivot_R_45   long    100,   -100,    370   
  pivot_L_45   long   -100,    100,    370
  stop         long      0,      0,    500
  backupLeftLeft  long @backupLeftLeft, @backward, @pivot_L_45, @pivot_L_45, @stop, -1
  Right     long @Right, @pivot_R_45, @stop, -1
  Left      long @Left, @pivot_L_45, @stop, -1

VAR

  byte maneuverSuccessL_CntList[3]
  byte maneuverSuccessR_CntList[3]
  byte maneuverSuccessB_CntList[3]
  
PUB Main
  Startup
  LearnNavigation
  
PUB StartUp

  system.Clock(80_000_000)

  speaker.Out(SpeakerPin, 300, G6)
  speaker.Out(SpeakerPin, 300, G6)
  speaker.Out(SpeakerPin, 300, C6)

PUB LearnNavigation | maneuver, i
  pst.Str(String("Starting learn navigation program"))
  pst.NewLine

  bytefill(@maneuverSuccessL_CntList, 128, 3)
  bytefill(@maneuverSuccessR_CntList, 128, 3)
  bytefill(@maneuverSuccessB_CntList, 128, 3)
             
  repeat                                                                
      time.Pause(20)
      if pin.In(LeftWhisker) == WhiskerContact and pin.In(RightWhisker) == WhiskerContact
          maneuver := PickManeuver(@maneuverSuccessB_CntList)
          ExecManeuver(maneuver)
          if pin.In(LeftWhisker) == WhiskerContact and pin.In(RightWhisker) == WhiskerContact
            if(maneuverSuccessB_CntList[maneuver] > 0)
               maneuverSuccessB_CntList[maneuver]--
          else
            if(maneuverSuccessB_CntList[maneuver] < 255)
               maneuverSuccessB_CntList[maneuver]++
      elseif pin.In(LeftWhisker) == WhiskerContact
          maneuver := PickManeuver(@maneuverSuccessL_CntList)
          ExecManeuver(maneuver)
          if pin.In(LeftWhisker) == WhiskerContact
            if(maneuverSuccessL_CntList[maneuver] > 0)
               maneuverSuccessL_CntList[maneuver]--
          else
            if(maneuverSuccessL_CntList[maneuver] < 255)
               maneuverSuccessL_CntList[maneuver]++
      elseif pin.In(RightWhisker) == WhiskerContact
          maneuver := PickManeuver(@maneuverSuccessR_CntList)
          ExecManeuver(maneuver)
          if pin.In(RightWhisker) == WhiskerContact 
            if(maneuverSuccessR_CntList[maneuver] > 0)  
               maneuverSuccessR_CntList[maneuver]--
          else
            if(maneuverSuccessR_CntList[maneuver] < 255)
               maneuverSuccessR_CntList[maneuver]++
      else
        drive.wheels(100,100)  

PUB PickManeuver(maneuverSuccessList) : highScoreIndex | highScore, i          

        highScore := 0
        highScoreIndex := 0
        'use highest ranked option
        
        repeat i from 0 to 2
          if((byte[maneuverSuccessList][i]) > highScore)
              highScore := byte[maneuverSuccessList][i]
              highScoreIndex := i

        return highScoreIndex  

PUB ExecManeuver(maneuver)
  case maneuver
    0: drive.Sequence(@Left)
    1: drive.Sequence(@Right) 
    2: drive.Sequence(@backupLeftLeft)
              

Wednesday, September 4, 2013

Really simple Artificial Intelligence

Artificial Intelligence (AI) is a very broad field.  I could not possible do the entire topic justice in a blog post but at least I can give a brief introduction to the field and break down what I see as the most common misconception.  

The field of AI seeks to create systems that find solutions were previously unknown to the program creator (not take over the world and use us as batteries).  One branch of AI is machine learning, where programs learn based on inputs and then can act on future inputs to achieve a goal.  

Today, we are going to break down the misconception that artificial intelligence programming has to be very complex.  The diagram below illustrates a conceptual goal based navigation for Boe-bot that I created.  The goal we've given Boe-bot is to go forward.  That is it!  

This wouldn't be much of a goal if Boe-bot were in an environment with no walls, like on a treadmill or an infinitely large surface.   Since there are walls in Boe-bot's actual environment, it needs to know it has driven into a wall and take a corrective action in order to meet its goal (of driving forward).  To help with this, Boe-bot has two inputs -whiskers that are attached to switches.  When a switch is closed, we know Boe-bot is touching a wall or obstacle.  

In this first code iteration, Boe-bot has been armed with three action choices, turn left, turn right or backup and turn right.  We want Boe-bot to learn which action will allow Boe-bot to accomplish the goal of driving forward.  Initially, Boe-bot gives each action an equal likelihood of success for the three combinations of input stimuli (left whisker touching, right whisker touching or both).  Through trial and error Boe-bot will learn which actions are more success at responding to certain input stimuli.

For example, if the left whisker is touching a wall, it is unlikely that turning left will free Boe-bot from the wall.  Using the algorithm outlined in the diagram, if Boe-bot initially did select turn left in this example, the result would be to reduce the success rating for this action choice.  When Boe-bot chooses an action the second time, it will choose a different action.  Assuming that action is successful, Boe-bot will increase the success factor for this action stimuli pair.   


In part two of this article, I'll share my code that implements the above algorithm and perhaps also share a video of Boe-bot learning in action!  

In future articles, I'll explore how we can make this more interesting, through a richer selection algorithm and have Boe-bot generate actions.

Monday, September 2, 2013

What good is a robotics blog, without some robots?


I’ve been using the Parallax Boe-Bot platform with a Propeller microprocessor on a Propeller Board of Education.  My initial impressions of both the chassis and electronics are positive:

Chassis
This platform is relatively low cost (bare chassis can be had for around $80 USD), its compact size and lots of attachment points.  Mostly metal, it can take the beating that figuring out how to detect the edge of a table can dish out!  One wish I do have is that it doesn’t have wheel encoders, so accurate navigation can be a real challenge. 

Propulsion comes from two servo motors, so it simplifies the overall electronic complexity.  This also has the added benefit of making the robot move at a reasonable speed, so that head on collisions don't inflict any damage (except for my ego - where did my obstacle avoidance design go wrong)! 

Propeller and Board of Education

Multicore, check.  SD card interface, check.  Breadboard, check.  The propeller microcontroller and board of education offer a lot of expansion options, so you won’t quickly run out room to experiment.  Parallax provides robust documentation for propeller and a free IDE.  The IDE supports two languages, Spin a high level language and assembly language for those times when speed matters.

The 64K of onboard RAM may sound like nothing compared to the gigabytes on most computers.  For me it has been a huge adjustment.  With that little space, things that you rarely had to consider in software development become much more important.  For example, not only do programs have to be shorter, the variables in them also occupy that same 64K.  It is like asking J R R Martin to write a haiku.  

Boe-bot with whiskers