- Introduction to dead reckoning
- Mathematical representation of dead reckoning
- Functional overview of the code
- How to build and test the code
- Room for improvements
- Bibliography
In navigation, dead reckoning is the process of calculating the current position of some moving object by using a previously determined position; and then incorporating a speed estimation, heading direction, and course over elapsed time. The corresponding term in biology, used to describe the processes by which animals update their estimates of position or heading, is path integration.1
In our case, we consider the CarriRo AD as being the robot in which we will implement dead reckoning:
As far as I know, the idea behind this robot is to have an autonomous moving cart that can handle heavy loads's transportation. A dead reckoning system would give this robot, the capability of knowing its current position in a 2D plane from an initially known position. That is an essential part when comming to implement navigation for autonomous robots.
In this use case, we have the following informations :
- The two rear wheels are driving wheels
- The two front wheels are swivel caster wheels
- We can get the odometry of the 4 wheels
- We can get the yaw rate of the whole robot
In order to estimate our position, we have two kinds of available information. The 4 wheel odometry and the yaw rate. In this case, we will use odometry to calculate the robot shift, and the gyrometer to calculate the angle shift. We could use odometry alone to extract both of these parameters, but the gyrometer should be more precise when extracting the yaw angle.
We will use an orthonormal coordinate system. At initialization, CarriRo AD 0 coordinate is at the coordinate system origin. It is important to note that our CarriRo AD origin is the center between the two rear wheels (for simplicity purposes). Finally, carriro is facing the positive X axis.
Fig.3 CarriRo representative illustration
Fig.4 CarriRo inital position in the plane
The angle θ is the angle between the direction of the robot and the x axis.
So at the beginning, our coordinates are the following ones :
- x = 0
- y = 0
- θ = 0
In this section, we will cover how the odometry and yaw rate can be used to extract the absolute robot position.2
First of all, we simplified the problem by taking into account only the two rear wheels because the front ones do not have any driving capacity.
The following image illustrates CarriRo's movement in the physical world.
Fig.5 CarriRo real robot movement representation
This trajectory can be decomposed in small curved segments as in the following image:
Fig.6 CarriRo segmented robot movement representation
If we focus on a single segment, we can extract the yaw angle and robot shift:
From the representation of our robot, we can extract the following set equations:
- dR = (R + 2RW)Δθ
- dL = R*Δθ
- d = (R + RW)*Δθ
With some basic math, we get to the following equation:
- d = (dR + dL)/2
In the last section, we just extracted the robot movement regarding the last segment. But now, we need to use this in order to know where our robot is in the absolute coordinates system. In order to do that, we need to convert the yaw angle and the position shift to an x and y coordinates and to an absolute angle between the x axis and the robot direction.
To simplify things, we imagine that the robot does a slight rotation on the spot before going straight. This allows us to use the d distance as if it was straight forward:
Fig.8 Robot shift to absolute coordinates
Legend :
- y: y axis
- x: x axis
- (xt-1, yt-1): last scan coordinates
- (xt, yt): new scan coordinates
- Δθ: yaw angle between last direction and new direction
- Δx: variation on x axis
- Δy: variation on y axis
- θt: absolute angle between x axis and the robot direction
- θt-1: absolute angle between x axis and the robot direction in the last scan
To update our absolute angle, we take the yaw angle and add it to the last angle:
- θt = Δθ + θt-1
With some trigonometry, we can extract Δx and Δy:
- Δx = d cos(θt)
- Δy = d sin(θt)
And our new absolute coordinates are:
- xt = xt-1 + Δx
- yt = yt-1 + Δy
- θt
In order to simulate the odometry and the yaw rate acquisition functions, I made a mockup library that had two functions: one for odometry and one for the gyrometer.
int gyrometerAcq(float &yawRate, uint32_t ×tamp);
int odometryAcq(std::array<float, 4> &odometry, uint32_t ×tamp);Note : For simplicity, we supposed that this library returns the odometry values between the last acq and the new one.
The output values are actually given by reference and the returned value corresponds to the success or failure of the function. Regarding the timestamp, it is referenced to 0 at boot.
The library consists of one class robotPosition that contains three public methods, eight private ones and one public attribute. To use this library, you first need to create an instance of the class. Then three functions can be used, one is for yaw angle update loop, the second one is for position update loop and the last one is to launch a multi threaded loop where you can choose the refresh rate of the yaw angle and odometry. The attribute is the global position of the robot in (x,y,θ) coordinates:
class robotPosition{
public:
std::array<float, COORDS_SIZE> coords = {0, 0, 0};
robotPosition(void);
~robotPosition(void);
void updateCoordsThreads(int gyroFreqHz, int odometryFreqHz);
void updateAngleLoop(int gyroFreqHz);
void updateXYLoop(int odometryFreqHz);
...Before building and testing your code, please make sure to have the following installs:
- g++
- gdb
- CppUTest3
And do the following configurations:
- Replace the
CPPUTEST_HOMEvalue in the make file with your absolute path to it
We have made three kinds of builds : a test build, an executable build and a library build.
In order to build them, you just need to go to Dead_reckoning_system and type:
make testsfor the unitary testsmake dead_reckoningfor the executablemake libraryfor the static library
Note: in the makefile there is a debug flag used to print some debug information. You can remove it for release.
For the moment, the treads in
void updateCoordsThreads(int gyroFreqHz, int odometryFreqHz);are completely unsafe. Actually they manipulate the same variable (std::array<float, COORDS_SIZE> coords;) and this could be very conflicting. In order to avoid this, we should add a mutex to ensure the correct lecture and update of the coords variable.
Currently our code gets the yawRate and odometry with different rates. But after getting these values, we only update the variable that is directly associated with it (if we get odometry, we update x and y but not yaw angle and vice versa). But if we take into account the speeds of x, y and yaw angle we could predict the current position of all the variables.
Concretely:
- When we get the odometry, we update the yaw angle with the last yaw rate.
- When we get the yaw rate, we update the x and y coordinates with their speed.
This could give our application better precision.
A big problem of dead reckoning is that it tends to shift over traveled distance. So the predictions that might be accurate after 10 meters of traveling, could be very wrong after 100m. So in order to maintain the good positioning of the robot it is useful to integrate a global position system. For outdoors applications GPS are really good, but as CarriRo is mostly used indoors, a local system (apriltags, signal triangulation, ...) should be used.
And in order to integrate a dead reckoning system and a global positioning input, a kalman filter could be used to filter out acquisition errors.
Footnotes
-
Wikipedia: https://en.wikipedia.org/wiki/Dead_reckoning ↩
-
A good explanation: https://www.youtube.com/watch?v=LrsTBWf6Wsc&t=1554s ↩
-
Can be downloaded here: http://cpputest.github.io/ ↩


