Skip to content

Commit 05b27e4

Browse files
committed
Add vel/acc example, and update offset calculation
Add velocity and acceleration example Offset computations got moved onto the OTOS, no longer performed in this library
1 parent e221e8d commit 05b27e4

File tree

5 files changed

+185
-224
lines changed

5 files changed

+185
-224
lines changed

examples/Example3_Calibration/Example3_Calibration.ino

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ void setup()
3030
// calibrateImu(), you can specify the number of samples to take and whether
3131
// to wait until the calibration is complete. If no parameters are provided,
3232
// it will take 255 samples and wait until done; each sample takes about
33-
// 1.2ms, so about 306ms total
33+
// 2.4ms, so about 612ms total
3434
myOtos.calibrateImu();
35-
// myOtos.calibrateImu(255, true);
35+
// myOtos.calibrateImu(255, true); // Or specify samples and whether to wait
3636

3737
// Here we can set the linear and angular scalars, which can compensate for
3838
// scaling issues with the sensor measurements. Note that as of firmware

examples/Example4_SetOffsetAndPosition/Example4_SetOffsetAndPosition.ino

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,12 @@ void setup()
3737
// Reset the tracking algorithm, making the sensor report it's at the origin
3838
myOtos.resetTracking();
3939

40-
// The OTOS sensor itself does not know about the offset defined above, it
41-
// only tracks its own location; the offset is applied by the library when
42-
// getPosition() is called. After resetting the tracking, the sensor will
43-
// report that it's at the origin, but getPosition() will return the inverse
44-
// of the offset pose. This can be resolved by calling setPosition(), which
45-
// take into account the offset and set the sensor's location accordingly.
46-
// Here we set the location to the origin, but any location is valid within
47-
// the sensor's tracking range.
48-
otos_pose2d_t newPose = {0, 0, 0};
49-
myOtos.setPosition(newPose);
40+
// After resetting the tracking, the OTOS will report that the robot is at
41+
// the origin. If you know the starting coordinates of the robot, or have
42+
// another source of location information (eg. vision odometry), you can set
43+
// the OTOS location to match and it will continue to track from there.
44+
otos_pose2d_t currentPosition = {0, 0, 0};
45+
myOtos.setPosition(currentPosition);
5046
}
5147

5248
void loop()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "SparkFun_Qwiic_OTOS_Arduino_Library.h"
2+
#include "Wire.h"
3+
4+
// Create an Optical Tracking Odometry Sensor object
5+
QwiicOTOS myOtos;
6+
7+
void setup()
8+
{
9+
// Start serial
10+
Serial.begin(115200);
11+
Serial.println("Qwiic OTOS Example 5 - Velocity and Acceleration");
12+
13+
Wire.begin();
14+
15+
Wire.setClock(100000);
16+
17+
// Attempt to begin the sensor
18+
while (myOtos.begin() == false)
19+
{
20+
Serial.println("OTOS not connected, check your wiring and I2C address!");
21+
delay(1000);
22+
}
23+
24+
Serial.println("OTOS connected!");
25+
26+
// Reset the tracking algorithm, making the sensor report it's at the origin
27+
myOtos.resetTracking();
28+
}
29+
30+
void loop()
31+
{
32+
// Create structs for position, velocity, and acceleration
33+
otos_pose2d_t pos;
34+
otos_pose2d_t vel;
35+
otos_pose2d_t acc;
36+
37+
// These values can be read individually like so:
38+
myOtos.getPosition(pos);
39+
myOtos.getVelocity(vel);
40+
myOtos.getAccerlation(acc);
41+
42+
// Or all at once with the following:
43+
// myOtos.getPosVelAcc(pos, vel, acc);
44+
45+
// Print measurements
46+
Serial.printf("Pos: X: %.3f\tY: %.3f\tH: %.3f\n", pos.x, pos.y, pos.h);
47+
Serial.printf("Vel: X: %.3f\tY: %.3f\tH: %.3f\n", vel.x, vel.y, vel.h);
48+
Serial.printf("Acc: X: %.3f\tY: %.3f\tH: %.3f\n", acc.x, acc.y, acc.h);
49+
Serial.println();
50+
51+
// Wait a bit so we don't spam the serial port
52+
delay(500);
53+
}

src/sfeQwiicOtos.cpp

Lines changed: 61 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@ sfeQwiicOtos::sfeQwiicOtos()
55
// Set communication bus to nullptr
66
_commBus = nullptr;
77

8-
// Reset offset pose and its inverse to zero
9-
_offsetPose.x = 0;
10-
_offsetPose.y = 0;
11-
_offsetPose.h = 0;
12-
_offsetPoseInv.x = 0;
13-
_offsetPoseInv.y = 0;
14-
_offsetPoseInv.h = 0;
15-
168
// Set default units to inches and degrees
179
_linearUnit = kOtosLinearUnitInches;
1810
_angularUnit = kOtosAngularUnitDegrees;
@@ -196,76 +188,36 @@ sfeTkError_t sfeQwiicOtos::setAngularScalar(float scalar)
196188
return kSTkErrFail;
197189

198190
// Convert to raw integer, multiples of 0.1%
199-
int8_t rawScalar = (scalar - 1.0f) * 1000;
191+
uint8_t rawScalar = (int8_t)((scalar - 1.0f) * 1000);
200192

201193
// Write the scalar to the device
202-
return _commBus->writeRegisterByte(kOtosRegScalarH, (uint8_t)rawScalar);
194+
return _commBus->writeRegisterByte(kOtosRegScalarH, rawScalar);
203195
}
204196

205-
void sfeQwiicOtos::setOffset(otos_pose2d_t &pose)
197+
sfeTkError_t sfeQwiicOtos::resetTracking()
206198
{
207-
// Copy the pose to the offset
208-
_offsetPose = pose;
209-
210-
// Convert to meters and radians
211-
_offsetPose.x /= _meterToUnit;
212-
_offsetPose.y /= _meterToUnit;
213-
_offsetPose.h /= _radToUnit;
214-
215-
// Compute the inverse of the offset
216-
_offsetPoseInv = invertPose(_offsetPose);
199+
// Set tracking reset bit
200+
return _commBus->writeRegisterByte(kOtosRegReset, 0x01);
217201
}
218202

219-
void sfeQwiicOtos::getOffset(otos_pose2d_t &pose)
203+
sfeTkError_t sfeQwiicOtos::getOffset(otos_pose2d_t &pose)
220204
{
221-
// Copy the offset to the pose
222-
pose = _offsetPose;
223-
224-
// Convert to current units
225-
pose.x *= _meterToUnit;
226-
pose.y *= _meterToUnit;
227-
pose.h *= _radToUnit;
205+
return readPoseRegs(kOtosRegOffXL, pose, kInt16ToMeter, kInt16ToRad);
228206
}
229207

230-
sfeTkError_t sfeQwiicOtos::resetTracking()
208+
sfeTkError_t sfeQwiicOtos::setOffset(otos_pose2d_t &pose)
231209
{
232-
// Set tracking reset bit
233-
return _commBus->writeRegisterByte(kOtosRegReset, 0x01);
210+
return writePoseRegs(kOtosRegOffXL, pose, kMeterToInt16, kRadToInt16);
234211
}
235212

236213
sfeTkError_t sfeQwiicOtos::getPosition(otos_pose2d_t &pose)
237214
{
238-
// Read the raw pose data
239-
otos_pose2d_t sensorPose;
240-
sfeTkError_t err = readPoseRegs(kOtosRegPosXL, sensorPose, kInt16ToMeter, kInt16ToRad);
241-
if(err != kSTkErrOk)
242-
return err;
243-
244-
// Apply the offset
245-
pose = transformPose(sensorPose, _offsetPoseInv);
246-
247-
// Convert to current units
248-
pose.x *= _meterToUnit;
249-
pose.y *= _meterToUnit;
250-
pose.h *= _radToUnit;
251-
252-
// Done!
253-
return kSTkErrOk;
215+
return readPoseRegs(kOtosRegPosXL, pose, kInt16ToMeter, kInt16ToRad);
254216
}
255217

256218
sfeTkError_t sfeQwiicOtos::setPosition(otos_pose2d_t &pose)
257219
{
258-
otos_pose2d_t sensorPose;
259-
260-
// Convert to meters and radians
261-
sensorPose.x = pose.x / _meterToUnit;
262-
sensorPose.y = pose.y / _meterToUnit;
263-
sensorPose.h = pose.h / _radToUnit;
264-
265-
// Apply the offset
266-
sensorPose = transformPose(sensorPose, _offsetPose);
267-
268-
return writePoseRegs(kOtosRegPosXL, sensorPose, kMeterToInt16, kRadToInt16);
220+
return writePoseRegs(kOtosRegPosXL, pose, kMeterToInt16, kRadToInt16);
269221
}
270222

271223
sfeTkError_t sfeQwiicOtos::getVelocity(otos_pose2d_t &pose)
@@ -288,156 +240,83 @@ sfeTkError_t sfeQwiicOtos::setAccerlation(otos_pose2d_t &pose)
288240
return writePoseRegs(kOtosRegAccXL, pose, kMeterToInt16, kRpssToInt16);
289241
}
290242

243+
sfeTkError_t sfeQwiicOtos::getPosVelAcc(otos_pose2d_t &pos, otos_pose2d_t &vel, otos_pose2d_t &acc)
244+
{
245+
// Read all three pose registers
246+
uint8_t rawData[18];
247+
size_t bytesRead = 0;
248+
sfeTkError_t err = _commBus->readRegisterRegion(kOtosRegPosXL, rawData, 18, bytesRead);
249+
if(err != kSTkErrOk)
250+
return err;
251+
252+
// Check if we read the correct number of bytes
253+
if(bytesRead != 18)
254+
return kSTkErrFail;
255+
256+
// Convert raw data to pose units
257+
regsToPose(rawData, pos, kInt16ToMeter, kInt16ToRad);
258+
regsToPose(rawData + 6, vel, kInt16ToMeter, kInt16ToRps);
259+
regsToPose(rawData + 12, acc, kInt16ToMeter, kInt16ToRpss);
260+
261+
// Done!
262+
return kSTkErrOk;
263+
}
264+
291265
sfeTkError_t sfeQwiicOtos::readPoseRegs(uint8_t reg, otos_pose2d_t &pose, float rawToXY, float rawToH)
292266
{
293267
size_t bytesRead = 0;
294268
uint8_t rawData[6];
295269

296270
// Attempt to read the raw pose data
297271
sfeTkError_t err = _commBus->readRegisterRegion(reg, rawData, 6, bytesRead);
298-
299-
// Check whether the read was successful
300272
if (err != kSTkErrOk)
301273
return err;
302274

303-
// Store raw data
304-
int16_t rawX = (rawData[1] << 8) | rawData[0];
305-
int16_t rawY = (rawData[3] << 8) | rawData[2];
306-
int16_t rawH = (rawData[5] << 8) | rawData[4];
275+
// Check if we read the correct number of bytes
276+
if (bytesRead != 6)
277+
return kSTkErrFail;
307278

308-
// Store in pose
309-
pose.x = rawX * rawToXY;
310-
pose.y = rawY * rawToXY;
311-
pose.h = rawH * rawToH;
279+
regsToPose(rawData, pose, rawToXY, rawToH);
312280

313281
// Done!
314282
return kSTkErrOk;
315283
}
316284

317285
sfeTkError_t sfeQwiicOtos::writePoseRegs(uint8_t reg, otos_pose2d_t &pose, float xyToRaw, float hToRaw)
318286
{
319-
// Convert pose units to raw data
320-
int16_t rawX = pose.x * xyToRaw;
321-
int16_t rawY = pose.y * xyToRaw;
322-
int16_t rawH = pose.h * hToRaw;
323-
324287
// Store raw data in a temporary buffer
325-
uint8_t rawData[8];
326-
rawData[0] = rawX & 0xFF;
327-
rawData[1] = (rawX >> 8) & 0xFF;
328-
rawData[2] = rawY & 0xFF;
329-
rawData[3] = (rawY >> 8) & 0xFF;
330-
rawData[4] = rawH & 0xFF;
331-
rawData[5] = (rawH >> 8) & 0xFF;
288+
uint8_t rawData[6];
289+
poseToRegs(rawData, pose, xyToRaw, hToRaw);
332290

333291
// Write the raw data to the device
334292
return _commBus->writeRegisterRegion(reg, rawData, 6);
335293
}
336294

337-
otos_pose2d_t sfeQwiicOtos::transformPose(otos_pose2d_t &pose12, otos_pose2d_t &pose23)
338-
{
339-
// We're going to compute this transformation as if the provided poses are
340-
// homogeneous transformation matrices. For the 2D case, the transformation
341-
// matrix is a 3x3 matrix T that looks like this:
342-
//
343-
// [R11 R12 x] [cos(h) -sin(h) x]
344-
// T = [R d] = [R21 R22 y] = [sin(h) cos(h) y]
345-
// [0 1] [0 0 1] [0 0 1]
346-
//
347-
// Where h is the heading and (x, y) is the translation from one frame to
348-
// another. If we know the transformation from frame 1 to frame 2 (T12), and
349-
// from frame 2 to frame 3 (T23), we can compute the transformation from
350-
// frame 1 to frame 3 (T13) by multiplying the transformation matrices:
351-
//
352-
// T13 = T12 * T23
353-
//
354-
// Which expands to:
355-
//
356-
// [cos(h13) -sin(h13) x13] [cos(h12) -sin(h12) x12] [cos(h23) -sin(h23) x23]
357-
// [sin(h13) cos(h13) y13] = [sin(h12) cos(h12) y12] * [sin(h23) cos(h23) y23]
358-
// [0 0 1 ] [0 0 1 ] [0 0 1 ]
359-
//
360-
// For the x13 and y13 components, this expands to:
361-
//
362-
// x13 = x23 * cos(h12) - y23 * sin(h12) + x12
363-
// y13 = x23 * sin(h12) + y23 * cos(h12) + y12
364-
//
365-
// The upper left 2x2 corner is the resulting rotation matrix, where each
366-
// component depends only on h13. The expansion for each element is one of:
367-
//
368-
// cos(h13) = cos(h12) * cos(h23) - sin(h12) * sin(h23)
369-
// sin(h13) = sin(h12) * cos(h23) + cos(h12) * sin(h23)
370-
//
371-
// Instead of computing the inverse trigonometric functions, we can notice
372-
// that these are just the angle addition identities, so we can simplify by
373-
// just adding the angles:
374-
//
375-
// h1 = h2 + h3
376-
377-
// Create struct to hold output
378-
otos_pose2d_t pose13;
379-
380-
// Pre-compute sin and cos
381-
float cosh12 = cosf(pose12.h);
382-
float sinh12 = sinf(pose12.h);
383-
384-
// Compute transformation following equations above
385-
pose13.x = pose23.x * cosh12 - pose23.y * sinh12 + pose12.x;
386-
pose13.y = pose23.x * sinh12 + pose23.y * cosh12 + pose12.y;
387-
pose13.h = pose23.h + pose12.h;
388-
389-
// Wrap heading to +/- pi with fmod
390-
pose13.h = wrapAngle(pose13.h);
391-
392-
// Done!
393-
return pose13;
394-
}
395-
396-
otos_pose2d_t sfeQwiicOtos::invertPose(otos_pose2d_t &pose)
295+
void sfeQwiicOtos::regsToPose(uint8_t *rawData, otos_pose2d_t &pose, float rawToXY, float rawToH)
397296
{
398-
// Similar to transformPose(), we're going to compute this transformation as
399-
// if the provided pose is a homogeneous transformation matrix T. The
400-
// inverse of T is given by:
401-
//
402-
// [ cos(h) sin(h) -x*cos(h)-y*sin(h)]
403-
// T^-1 = [R' d'] = [RT -RT*d] = [-sin(h) cos(h) x*sin(h)-y*cos(h)]
404-
// [0 1 ] [0 1 ] [0 0 1 ]
405-
//
406-
// Where R' and d' are the inverse rotation matrix and position vector, and
407-
// RT is the transpose of the rotation matrix. Solving for the inverse
408-
// position vector, we get:
409-
//
410-
// x' = -x * cos(h) - y * sin(h)
411-
// y' = x * sin(h) - y * cos(h)
412-
//
413-
// The expansion for the inverse rotation matrix elements are one of:
414-
//
415-
// cos(h') = cos(h)
416-
// sin(h') = -sin(h)
417-
//
418-
// Instead of computing the inverse trigonometric functions, we can notice
419-
// that h' and h must be opposite angles, so we can simplify:
420-
//
421-
// h' = -h
422-
423-
// Create struct to hold output
424-
otos_pose2d_t poseInv;
425-
426-
// Pre-compute sin and cos
427-
float cosh = cosf(pose.h);
428-
float sinh = sinf(pose.h);
429-
430-
// Compute inverse following equations above
431-
poseInv.x = -pose.x * cosh - pose.y * sinh;
432-
poseInv.y = pose.x * sinh - pose.y * cosh;
433-
poseInv.h = -pose.h;
297+
// Store raw data
298+
int16_t rawX = (rawData[1] << 8) | rawData[0];
299+
int16_t rawY = (rawData[3] << 8) | rawData[2];
300+
int16_t rawH = (rawData[5] << 8) | rawData[4];
434301

435-
// Done!
436-
return poseInv;
302+
// Store in pose and convert to units
303+
pose.x = rawX * rawToXY * _meterToUnit;
304+
pose.y = rawY * rawToXY * _meterToUnit;
305+
pose.h = rawH * rawToH * _radToUnit;
437306
}
438307

439-
float sfeQwiicOtos::wrapAngle(float angle, otos_angular_unit_t unit)
308+
void sfeQwiicOtos::poseToRegs(uint8_t *rawData, otos_pose2d_t &pose, float xyToRaw, float hToRaw)
440309
{
441-
float bound = (unit == kOtosAngularUnitRadians) ? M_PI : 180.0;
442-
return fmodf(angle + bound, 2 * bound) - bound;
310+
// Convert pose units to raw data
311+
int16_t rawX = pose.x * xyToRaw / _meterToUnit;
312+
int16_t rawY = pose.y * xyToRaw / _meterToUnit;
313+
int16_t rawH = pose.h * hToRaw / _radToUnit;
314+
315+
// Store raw data in buffer
316+
rawData[0] = rawX & 0xFF;
317+
rawData[1] = (rawX >> 8) & 0xFF;
318+
rawData[2] = rawY & 0xFF;
319+
rawData[3] = (rawY >> 8) & 0xFF;
320+
rawData[4] = rawH & 0xFF;
321+
rawData[5] = (rawH >> 8) & 0xFF;
443322
}

0 commit comments

Comments
 (0)