Skip to content

Latest commit

 

History

History
180 lines (115 loc) · 6.19 KB

Navigator.md

File metadata and controls

180 lines (115 loc) · 6.19 KB

#Navigator

One of the core components of NavBot is the Navigator. Its job is to encapsulate the localization information of the robot, i.e. where in the world it is.

For this implementation I am only considering a two dimensional world as the bot will mostly likely only travel around my office floor or some other flat surface.

At some later point if we want the bot to travel over general terrain we'd need to factor in elevation too.

But for now the navigator tracks the following data:

  • Position (x,y) - The x and y location of the bot from its center, in millimeters, from some origin (0,0)
  • Heading (h) - The orientation of the bot in degrees from North about its center.
  • Speed (v) - The speed of motion, in millimeters per second, in the current heading from its center.
  • Turn Rate - The degrees per second the bot is turning about its center.

image

##Implementation

I've implemented the Navigator as it's own C++ class and tried to make it as modular as possible so it can be deployed to any application requiring localization.

For simplicity it uses floating point math. However, as I also want to be able to change over to fixed point math, Navigator abstracts out the some basic data types:

//----------------------------------------
// Base Types
//----------------------------------------

typedef float       nvCoord;        // millimeters
typedef float       nvDegrees;      // degrees 
typedef float       nvRadians;      // radians 
typedef nvDegrees   nvHeading;      // degrees from North
typedef float       nvRate;         // change per second
typedef float       nvDistance;     // millimeters
typedef uint32_t    nvTime;         // time in milliseconds

And as you can see from the comments we are using millimeters, degrees and milliseconds as the units of measurement.

Everything is floating point except time, which is an unsigned 32 bit integer.

Each type has an "nv" prefix to avoid possible name clashes with other libraries or code.

To make the code more readable, and to facilitate possible conversion to fixed point at a later date, Navigator provides some helper macros:

//----------------------------------------
// Helper Macros
//----------------------------------------

#define nvMM(D)         ((nvDistance)(D))
#define nvMETERS(D)     ((nvDistance)((D)*1000))
#define nvMS(MS)        ((nvTime)(MS))
#define nvSECONDS(S)    ((nvTime)((S)*1000))
#define nvDEGREES(D)    ((nvDegrees)(D))
#define nvRADIANSS(R)   ((nvRadians)(R))

#define nvNORTH         nvDEGREES(0)
#define nvNORTHEAST     nvDEGREES(45)
#define nvEAST          nvDEGREES(90)
#define nvSOUTHEAST     nvDEGREES(135)
#define nvSOUTH         nvDEGREES(180)
#define nvSOUTHWEST     nvDEGREES(225)
#define nvWEST          nvDEGREES(270)
#define nvNORTHWEST     nvDEGREES(315)

Navigator also uses the following data abstractions:

//----------------------------------------
// nvPosition
//----------------------------------------

struct nvPosition
{
	nvCoord     x;              // mm from origin
	nvCoord     y;              // mm from origin
};

//----------------------------------------
// nvPose
//----------------------------------------

struct nvPose
{
	nvPosition  position;       // mm from (0, 0)
	nvHeading   heading;        // degrees from North
};

##Using Navigator

To use Navigator we first need to create an instance of the class:

#include "Navigator.h"

Navigator navigator;

Then in setup() we need to initialize the instance:

// Navigator defines
#define WHEEL_BASE      nvMM(83.5)
#define WHEEL_DIAMETER  nvMM(35.4)
#define TICKS_PER_REV   1204

setup()
{
    :
    :
	 // set up navigation
	 navigator.InitEncoder( WHEEL_DIAMETER, WHEEL_BASE, TICKS_PER_REV );

	:
	:
}

This particular implementation uses dead reckoning via wheel encoders so we pass in the nominal wheel diameter, wheelbase distance and the number of encoder ticks per revolution.

image

With this information the Navigator can calculate the number of millimeters travelled per encoder tick by taking the circumference of the wheels and dividing it by the number of encoder ticks.

By default the Navigator assumes the bot is at location (0, 0) and heading (0). If we want to set a different starting location we use these two functions:

navigator.SetStartPosition( nvMM(200), nvMM(300));
navigator.SetStartHeading( nvEAST );

or

nvPose pose;

pose.position.x = nvMM(200);
pose.position.y = nvMM(300);
pose.heading = nvEAST;

navigator.SetStartPose( pose );

To enable Navigator to track the movement of the robot we need first call its Reset() method and then regularly call the UpdateTicks() method.

So first call Reset():

navigator.Reset( millis() );

passing in the current time in milliseconds. This will initialize the navigator and set its location to the starting position and heading.

After Reset() we must regularly call UpdateTicks() thus:

navigator.UpdateTicks( lticks, rticks, millis() );

passing in the number of encoder ticks for the left and right wheels since we last called UpdateTicks() and/or Reset(), and the current time in milliseconds.

Using the tick counts and time the navigator can calculate changes in the bot's location.

Navigator will recalculate changes in position every 10 ms by default. You can change the minimum interval by calling SetMinInterval():

navigator.SetMinInterval( nvMS(50) );

Once the minimum time interval has elapsed it calculates the new location of the bot based on the tick counts and the amount of time elapsed.

At any time we can query this information from the navigator by using various getter methods:

nvPose  pose = navigator.Pose();

Serial.print("Heading: ");
Serial.println( pose.heading );

Serial.print("Position: (");
Serial.print( pose.position.x );
Serial.print(", ");
Serial.print( pose.position.y );
Serial.println(")");

Serial.print("Speed: ");
Serial.println( navigator.Speed() );

Serial.print("Turn Rate: ");
Serial.println( navigator.TurnRate() );