diff --git a/README.md b/README.md index 4487f9c..ee00703 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,62 @@ -SparkFun Optical Tracking Odometry Sensor Arduino Library -======================================== +![SparkFun Optical Tracking Odometry Sensor - PAA5160E1](docs/images/gh-banner-2025-arduino-OTOS.png "SparkFun Optical Tracking Odometry Sensor - PAA5160E1Sensor") -
SparkFun Optical Tracking Odometry Sensor - PAA5160E1 (Qwiic)
+# SparkFun Optical Tracking Odometry Sensor - PAA5160E1 +Arduino Library for the SparkFun Optical Tracking Odometry Sensor - PAA5160E1 Sensor -[*SparkFun Optical Tracking Odometry Sensor [SEN-24904]*](https://www.sparkfun.com/products/24904) +![License](https://img.shields.io/github/license/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library) +![Release](https://img.shields.io/github/v/release/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library) +![Release Date](https://img.shields.io/github/release-date/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library) +![Documentation - build](https://img.shields.io/github/actions/workflow/status/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library/build-deploy-ghpages.yml?label=doc%20build) +![Compile - Test](https://img.shields.io/github/actions/workflow/status/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library/compile-sketch.yml?label=compile%20test) +![GitHub issues](https://img.shields.io/github/issues/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library) -Arduino Library for the SparkFun Optical Tracking Odometry Sensor -Repository Contents -------------------- +The [SparkFun Optical Tracking Odometry Sensor - PAA5160E1 (SEN-24904)](https://www.sparkfun.com/sparkfun-optical-tracking-odometry-sensor-paa5160e1-qwiic.html) empowers you to elevate your robot's navigation capabilities with exceptional precision and streamlined integration. This compact, all-in-one sensor leverages the power of the PAA5160E1 chip from PixArt Imaging Inc., delivering accurate dual-axis motion data across various hard floor surfaces. But that's not all! This sensor boasts a powerful built-in 6-axis Inertial Measurement Unit (IMU) and an onboard microcontroller that performs real-time sensor fusion and tracking algorithms. -* **/documents** - Data sheets, additional product information -* **/examples** - Example code -* **/src** - Source code +Looking for the board that matches this library - pick up a [SparkFun Optical Tracking Odometry Sensor - PAA5160E1 (SEN-24904)](https://www.sparkfun.com/sparkfun-optical-tracking-odometry-sensor-paa5160e1-qwiic.html) at www.sparkfun.com. -Documentation --------------- -* **[GitHub Repo](https://github.com/sparkfun/SparkFun_Optical_Tracking_Odometry_Sensor)** - Hardware repository for the SparkFun Optical Tracking Odometry Sensor -* **[Hookup Guide](https://docs.sparkfun.com/SparkFun_Optical_Tracking_Odometry_Sensor)** - Hookup Guide for the SparkFun Optical Tracking Odometry Sensor +## Functionality -License Information -------------------- +This library provides an interface that enables the following functionality when a SparkFun Optical Tracking Odometry Sensor - PAA5160E1 Sensors: -This product is _**open source**_! +* ***TODO*** +* + + +## General Use + +***TODO*** + +## Examples + +The following examples are provided with the library + +- [Basic Readings](examples/Example1_BasicReadings/Example1_BasicReadings.ino) - This example demonstrates how to read the position and heading from the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). +- [Set Units](examples/Example2_SetUnits/Example2_SetUnits.ino) - This example demonstrates how to change the units of the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS).. +- [Calibration](examples/Example3_Calibration/Example3_Calibration.ino) - This example demonstrates how to calibrate the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS).. +- [Set Offset and Position](examples/Example4_SetOffsetAndPosition/Example4_SetOffsetAndPosition.ino) - This example demonstrates how to set the offset and position of the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). +-[Velocity and Acceleration](examples/Example5_VelocityAndAcceleration/Example5_VelocityAndAcceleration.ino) - This example demonstrates how to read the velocity and acceleration from the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). +- [Standard Deviation](examples/Example6_StandardDeviation/Example6_StandardDeviation.ino) - This example demonstrates how to read the standard deviation of the measurements of the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). +- [Get Version](examples/Example7_GetVersion/Example7_GetVersion.ino) - This example demonstrates how to get the hardware and firmware version numbers from the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). +- [Self Test](examples/Example8_SelfTest/Example8_SelfTest.ino) - This example demonstrates how to perform a self test of the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). + +## Documentation + +The full API and use documentation for this library is provided [here](https://docs.sparkfun.com/SparkFun_Qwiic_OTOS_Arduino_Library/). For a quick reference, the main methods available in the library are listed [here](https://docs.sparkfun.com/SparkFun_Qwiic_OTOS_Arduino_Library/class_qwiic_o_t_o_s.html). + +Curious about the hardware this board works with - visit the SparkFun Optical Tracking Odometry Sensor [hardware repository](https://github.com/sparkfun/SparkFun_Optical_Tracking_Odometry_Sensor). + +The ***Hookup Guide*** for the SparkFun Optical Tracking Odometry Sensor is available [here](https://docs.sparkfun.com/SparkFun_Optical_Tracking_Odometry_Sensor). + +## License Information + +This product is ***open source***! + +This product is licensed using the [MIT Open Source License](https://opensource.org/license/mit). -Please review the LICENSE.md file for license information. -If you have any questions or concerns on licensing, please contact technical support on our [SparkFun forums](https://forum.sparkfun.com/viewforum.php?f=152). -Distributed as-is; no warranty is given. -- Your friends at SparkFun. -__ diff --git a/docs/doxygen/doxygen-config b/docs/doxygen/doxygen-config index d9cbfb3..9c1ada0 100644 --- a/docs/doxygen/doxygen-config +++ b/docs/doxygen/doxygen-config @@ -171,7 +171,7 @@ ALWAYS_DETAILED_SEC = YES # operators of the base classes will not be shown. # The default value is: NO. -INLINE_INHERITED_MEMB = NO +INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the diff --git a/docs/images/gh-banner-2025-arduino-OTOS.png b/docs/images/gh-banner-2025-arduino-OTOS.png new file mode 100644 index 0000000..93c30ba Binary files /dev/null and b/docs/images/gh-banner-2025-arduino-OTOS.png differ diff --git a/examples/Example8_SelfTest/Example8_SelfTest.ino b/examples/Example8_SelfTest/Example8_SelfTest.ino index e066c09..34d9af9 100644 --- a/examples/Example8_SelfTest/Example8_SelfTest.ino +++ b/examples/Example8_SelfTest/Example8_SelfTest.ino @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: MIT - + Copyright (c) 2024 SparkFun Electronics */ @@ -41,10 +41,10 @@ void setup() Serial.println("The OTOS must be stationary during the self test!"); // Perform the self test - sfeTkError_t result = myOtos.selfTest(); - + sfTkError_t result = myOtos.selfTest(); + // Check if the self test passed - if(result == kSTkErrOk) + if (result == ksfTkErrOk) { Serial.println("Self test passed!"); } diff --git a/library.properties b/library.properties index 2e2354f..2959bc0 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=SparkFun Qwiic OTOS Arduino Library -version=1.0.2 +version=1.1.0 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=A library to use the SparkFun Qwiic Optical Tracking Odometry Sensor -paragraph= +paragraph=The SparkFun Qwiic Optical Tracking Odometry Sensor empowers you to elevate a robots navigation capabilities with precision and streamlined integration. category=Sensors url=https://github.com/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library architectures=* -depends=SparkFun Toolkit (<1.0.0) +depends=SparkFun Toolkit (>=1.0.0) diff --git a/src/SparkFun_Qwiic_OTOS_Arduino_Library.h b/src/SparkFun_Qwiic_OTOS_Arduino_Library.h index b661ac0..fe485a8 100644 --- a/src/SparkFun_Qwiic_OTOS_Arduino_Library.h +++ b/src/SparkFun_Qwiic_OTOS_Arduino_Library.h @@ -1,8 +1,27 @@ -/* - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics -*/ +/** + * @file SparkFun_Qwiic_OTOS_Arduino_Library.h + * @brief Arduino wrapper for the SparkFun Qwiic OTOS sensor driver + * @details This file provides an Arduino-compatible interface to the SparkFun Qwiic + * Optical Tracking Odometry Sensor (OTOS). It wraps the platform-agnostic + * C++ driver to provide familiar Arduino conventions and simplified usage. + * + * Features: + * - Simple Arduino-style begin() function + * - Default Wire interface support + * - Compatible with all Arduino platforms + * - Inherits all functionality from base C++ driver + * - Automatic I2C bus configuration + * + * @author SparkFun Electronics + * @date February 2024 + * @copyright Copyright (c) 2024-2025, SparkFun Electronics Inc. + * + * SPDX-License-Identifier: MIT + * + * @see https://github.com/sparkfun/SparkFun_Qwiic_OTOS_Arduino_Library + */ + +// ...existing code... /******************************************************************************* SparkFun_Qwiic_OTOS_Arduino_Library.h - Arduino wrapper of the C++ driver @@ -13,14 +32,16 @@ *******************************************************************************/ #pragma once - +// clang-format off #include "Arduino.h" -#include "sfeQwiicOtos.h" +#include +#include "sfTk/sfDevOTOS.h" #include +// clang-format on /// @brief Arduino class for the SparkFun Qwiic Optical Tracking Odometry Sensor /// (OTOS) -class QwiicOTOS : public sfeQwiicOtos +class QwiicOTOS : public sfDevOTOS { public: /// @brief Begins the Qwiic OTOS and verifies it is connected @@ -32,7 +53,7 @@ class QwiicOTOS : public sfeQwiicOtos _theI2CBus.init(wirePort, kDefaultAddress); // Begin the sensor - return sfeQwiicOtos::begin(&_theI2CBus) == kSTkErrOk; + return sfDevOTOS::begin(&_theI2CBus) == ksfTkErrOk; } protected: @@ -42,5 +63,5 @@ class QwiicOTOS : public sfeQwiicOtos } private: - sfeTkArdI2C _theI2CBus; + sfTkArdI2C _theI2CBus; }; diff --git a/src/sfeQwiicOtos.cpp b/src/sfTk/sfDevOTOS.cpp similarity index 52% rename from src/sfeQwiicOtos.cpp rename to src/sfTk/sfDevOTOS.cpp index ffcbca6..cea11e7 100644 --- a/src/sfeQwiicOtos.cpp +++ b/src/sfTk/sfDevOTOS.cpp @@ -1,32 +1,49 @@ -/* - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics -*/ +/** + * @file sfDevOTOS.cpp + * @brief Implementation of the SparkFun Qwiic OTOS driver class + * @details This file contains the implementation of methods for the sfDevOTOS class, + * which provides a C++ interface for the SparkFun Qwiic Optical Tracking + * Odometry Sensor (OTOS). + * + * Implementation Features: + * - Platform-agnostic I2C communication using SparkFun Toolkit + * - Unit conversion between metric/imperial and radians/degrees + * - Register-level device interaction + * - Data conversion between raw register values and floating point numbers + * - Error handling and validation + * + * @author SparkFun Electronics + * @date February 2024 + * @copyright Copyright (c) 2024-2025, SparkFun Electronics Inc. This project is released under the MIT License. + * + * SPDX-License-Identifier: MIT + * + * @see https://github.com/sparkfun/SparkFun_Toolkit + */ /******************************************************************************* - sfeQwiicOtos.h - C++ driver implementation for the SparkFun Qwiic Optical + sfDevOTOS.h - C++ driver implementation for the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS). *******************************************************************************/ -#include "sfeQwiicOtos.h" +#include "sfDevOTOS.h" -sfeQwiicOtos::sfeQwiicOtos() +sfDevOTOS::sfDevOTOS() : _commBus{nullptr}, _linearUnit{kSfeOtosLinearUnitInches}, _angularUnit{kSfeOtosAngularUnitDegrees}, _meterToUnit{kMeterToInch}, _radToUnit{kRadianToDegree} { // Nothing to do here! } -sfeTkError_t sfeQwiicOtos::begin(sfeTkII2C *commBus) +sfTkError_t sfDevOTOS::begin(sfTkII2C *commBus) { // Nullptr check if (commBus == nullptr) - return kSTkErrFail; + return ksfTkErrFail; // Check the device address if (commBus->address() != kDefaultAddress) - return kSTkErrFail; + return ksfTkErrFail; // Set bus pointer _commBus = commBus; @@ -35,107 +52,107 @@ sfeTkError_t sfeQwiicOtos::begin(sfeTkII2C *commBus) return isConnected(); } -sfeTkError_t sfeQwiicOtos::isConnected() +sfTkError_t sfDevOTOS::isConnected() { // First ping the device address - sfeTkError_t err = _commBus->ping(); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->ping(); + if (err != ksfTkErrOk) return err; - + // Read the product ID uint8_t prodId; - err = _commBus->readRegisterByte(kRegProductId, prodId); - if(err != kSTkErrOk) + err = _commBus->readRegister(kRegProductId, prodId); + if (err != ksfTkErrOk) return err; // Check if the product ID is correct - if(prodId != kProductId) - return kSTkErrFail; + if (prodId != kProductId) + return ksfTkErrFail; // Everything checks out, we must be connected! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::getVersionInfo(sfe_otos_version_t &hwVersion, sfe_otos_version_t &fwVersion) +sfTkError_t sfDevOTOS::getVersionInfo(sfe_otos_version_t &hwVersion, sfe_otos_version_t &fwVersion) { // Read hardware and firmware version registers uint8_t rawData[2]; size_t readBytes; - sfeTkError_t err = _commBus->readRegisterRegion(kRegHwVersion, rawData, 2, readBytes); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->readRegister(kRegHwVersion, rawData, sizeof(rawData), readBytes); + if (err != ksfTkErrOk) return err; // Check if we read the correct number of bytes - if(readBytes != 2) - return kSTkErrFail; + if (readBytes != 2) + return ksfTkErrFail; // Store the version info hwVersion.value = rawData[0]; fwVersion.value = rawData[1]; // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::selfTest() +sfTkError_t sfDevOTOS::selfTest() { // Write the self-test register to start the test sfe_otos_self_test_config_t selfTest; selfTest.start = 1; - sfeTkError_t err = _commBus->writeRegisterByte(kRegSelfTest, selfTest.value); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->writeRegister(kRegSelfTest, selfTest.value); + if (err != ksfTkErrOk) return err; // Loop until self-test is done, should only take ~20ms as of firmware v1.0 - for(int i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) { // Give a short delay between reads delayMs(5); // Read the self-test register - err = _commBus->readRegisterByte(kRegSelfTest, selfTest.value); - if(err != kSTkErrOk) + err = _commBus->readRegister(kRegSelfTest, selfTest.value); + if (err != ksfTkErrOk) return err; - + // Check if the self-test is done - if(selfTest.inProgress == 0) + if (selfTest.inProgress == 0) { break; } } // Check if the self-test passed - return (selfTest.pass == 1) ? kSTkErrOk : kSTkErrFail; + return (selfTest.pass == 1) ? ksfTkErrOk : ksfTkErrFail; } -sfeTkError_t sfeQwiicOtos::calibrateImu(uint8_t numSamples, bool waitUntilDone) +sfTkError_t sfDevOTOS::calibrateImu(uint8_t numSamples, bool waitUntilDone) { // Write the number of samples to the device - sfeTkError_t err = _commBus->writeRegisterByte(kRegImuCalib, numSamples); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->writeRegister(kRegImuCalib, numSamples); + if (err != ksfTkErrOk) return err; - + // Wait 1 sample period (2.4ms) to ensure the register updates delayMs(3); - + // Do we need to wait until the calibration finishes? - if(!waitUntilDone) - return kSTkErrOk; - + if (!waitUntilDone) + return ksfTkErrOk; + // Wait for the calibration to finish, which is indicated by the IMU // calibration register reading zero, or until we reach the maximum number // of read attempts - for(uint8_t numAttempts = numSamples; numAttempts > 0; numAttempts--) + for (uint8_t numAttempts = numSamples; numAttempts > 0; numAttempts--) { // Read the gryo calibration register value uint8_t calibrationValue; - err = _commBus->readRegisterByte(kRegImuCalib, calibrationValue); - if(err != kSTkErrOk) + err = _commBus->readRegister(kRegImuCalib, calibrationValue); + if (err != ksfTkErrOk) return err; - + // Check if calibration is done - if(calibrationValue == 0) - return kSTkErrOk; + if (calibrationValue == 0) + return ksfTkErrOk; // Give a short delay between reads. As of firmware v1.0, samples take // 2.4ms each, so 3ms should guarantee the next sample is done. This @@ -144,42 +161,42 @@ sfeTkError_t sfeQwiicOtos::calibrateImu(uint8_t numSamples, bool waitUntilDone) } // Max number of attempts reached, calibration failed - return kSTkErrFail; + return ksfTkErrFail; } -sfeTkError_t sfeQwiicOtos::getImuCalibrationProgress(uint8_t &numSamples) +sfTkError_t sfDevOTOS::getImuCalibrationProgress(uint8_t &numSamples) { // Read the IMU calibration register - return _commBus->readRegisterByte(kRegImuCalib, numSamples); + return _commBus->readRegister(kRegImuCalib, numSamples); } -sfe_otos_linear_unit_t sfeQwiicOtos::getLinearUnit() +sfe_otos_linear_unit_t sfDevOTOS::getLinearUnit() { return _linearUnit; } -void sfeQwiicOtos::setLinearUnit(sfe_otos_linear_unit_t unit) +void sfDevOTOS::setLinearUnit(sfe_otos_linear_unit_t unit) { // Check if this unit is already set - if(unit == _linearUnit) + if (unit == _linearUnit) return; // Store new unit _linearUnit = unit; - + // Compute conversion factor to new units _meterToUnit = (unit == kSfeOtosLinearUnitMeters) ? 1.0f : kMeterToInch; } -sfe_otos_angular_unit_t sfeQwiicOtos::getAngularUnit() +sfe_otos_angular_unit_t sfDevOTOS::getAngularUnit() { return _angularUnit; } -void sfeQwiicOtos::setAngularUnit(sfe_otos_angular_unit_t unit) +void sfDevOTOS::setAngularUnit(sfe_otos_angular_unit_t unit) { // Check if this unit is already set - if(unit == _angularUnit) + if (unit == _angularUnit) return; // Store new unit @@ -189,142 +206,142 @@ void sfeQwiicOtos::setAngularUnit(sfe_otos_angular_unit_t unit) _radToUnit = (unit == kSfeOtosAngularUnitRadians) ? 1.0f : kRadianToDegree; } -sfeTkError_t sfeQwiicOtos::getLinearScalar(float &scalar) +sfTkError_t sfDevOTOS::getLinearScalar(float &scalar) { // Read the linear scalar from the device uint8_t rawScalar; - sfeTkError_t err = _commBus->readRegisterByte(kRegScalarLinear, rawScalar); - if(err != kSTkErrOk) - return kSTkErrFail; + sfTkError_t err = _commBus->readRegister(kRegScalarLinear, rawScalar); + if (err != ksfTkErrOk) + return ksfTkErrFail; // Convert to float, multiples of 0.1% scalar = (((int8_t)rawScalar) * 0.001f) + 1.0f; // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::setLinearScalar(float scalar) +sfTkError_t sfDevOTOS::setLinearScalar(float scalar) { // Check if the scalar is out of bounds - if(scalar < kMinScalar || scalar > kMaxScalar) - return kSTkErrFail; - + if (scalar < kMinScalar || scalar > kMaxScalar) + return ksfTkErrFail; + // Convert to integer, multiples of 0.1% (+0.5 to round instead of truncate) uint8_t rawScalar = (int8_t)((scalar - 1.0f) * 1000 + 0.5f); // Write the scalar to the device - return _commBus->writeRegisterByte(kRegScalarLinear, rawScalar); + return _commBus->writeRegister(kRegScalarLinear, rawScalar); } -sfeTkError_t sfeQwiicOtos::getAngularScalar(float &scalar) +sfTkError_t sfDevOTOS::getAngularScalar(float &scalar) { // Read the angular scalar from the device uint8_t rawScalar; - sfeTkError_t err = _commBus->readRegisterByte(kRegScalarAngular, rawScalar); - if(err != kSTkErrOk) - return kSTkErrFail; + sfTkError_t err = _commBus->readRegister(kRegScalarAngular, rawScalar); + if (err != ksfTkErrOk) + return ksfTkErrFail; // Convert to float, multiples of 0.1% scalar = (((int8_t)rawScalar) * 0.001f) + 1.0f; // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::setAngularScalar(float scalar) +sfTkError_t sfDevOTOS::setAngularScalar(float scalar) { // Check if the scalar is out of bounds - if(scalar < kMinScalar || scalar > kMaxScalar) - return kSTkErrFail; - + if (scalar < kMinScalar || scalar > kMaxScalar) + return ksfTkErrFail; + // Convert to integer, multiples of 0.1% (+0.5 to round instead of truncate) uint8_t rawScalar = (int8_t)((scalar - 1.0f) * 1000 + 0.5f); // Write the scalar to the device - return _commBus->writeRegisterByte(kRegScalarAngular, rawScalar); + return _commBus->writeRegister(kRegScalarAngular, rawScalar); } -sfeTkError_t sfeQwiicOtos::resetTracking() +sfTkError_t sfDevOTOS::resetTracking() { // Set tracking reset bit - return _commBus->writeRegisterByte(kRegReset, 0x01); + return _commBus->writeRegister(kRegReset, (uint8_t)0x01); } -sfeTkError_t sfeQwiicOtos::getSignalProcessConfig(sfe_otos_signal_process_config_t &config) +sfTkError_t sfDevOTOS::getSignalProcessConfig(sfe_otos_signal_process_config_t &config) { // Read the signal process register - return _commBus->readRegisterByte(kRegSignalProcess, config.value); + return _commBus->readRegister(kRegSignalProcess, config.value); } -sfeTkError_t sfeQwiicOtos::setSignalProcessConfig(sfe_otos_signal_process_config_t &config) +sfTkError_t sfDevOTOS::setSignalProcessConfig(sfe_otos_signal_process_config_t &config) { // Write the signal process register - return _commBus->writeRegisterByte(kRegSignalProcess, config.value); + return _commBus->writeRegister(kRegSignalProcess, config.value); } -sfeTkError_t sfeQwiicOtos::getStatus(sfe_otos_status_t &status) +sfTkError_t sfDevOTOS::getStatus(sfe_otos_status_t &status) { - return _commBus->readRegisterByte(kRegStatus, status.value); + return _commBus->readRegister(kRegStatus, status.value); } -sfeTkError_t sfeQwiicOtos::getOffset(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getOffset(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegOffXL, pose, kInt16ToMeter, kInt16ToRad); } -sfeTkError_t sfeQwiicOtos::setOffset(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::setOffset(sfe_otos_pose2d_t &pose) { return writePoseRegs(kRegOffXL, pose, kMeterToInt16, kRadToInt16); } -sfeTkError_t sfeQwiicOtos::getPosition(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getPosition(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegPosXL, pose, kInt16ToMeter, kInt16ToRad); } -sfeTkError_t sfeQwiicOtos::setPosition(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::setPosition(sfe_otos_pose2d_t &pose) { return writePoseRegs(kRegPosXL, pose, kMeterToInt16, kRadToInt16); } -sfeTkError_t sfeQwiicOtos::getVelocity(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getVelocity(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegVelXL, pose, kInt16ToMps, kInt16ToRps); } -sfeTkError_t sfeQwiicOtos::getAcceleration(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getAcceleration(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegAccXL, pose, kInt16ToMpss, kInt16ToRpss); } -sfeTkError_t sfeQwiicOtos::getPositionStdDev(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getPositionStdDev(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegPosStdXL, pose, kInt16ToMeter, kInt16ToRad); } -sfeTkError_t sfeQwiicOtos::getVelocityStdDev(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getVelocityStdDev(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegVelStdXL, pose, kInt16ToMps, kInt16ToRps); } -sfeTkError_t sfeQwiicOtos::getAccelerationStdDev(sfe_otos_pose2d_t &pose) +sfTkError_t sfDevOTOS::getAccelerationStdDev(sfe_otos_pose2d_t &pose) { return readPoseRegs(kRegAccStdXL, pose, kInt16ToMpss, kInt16ToRpss); } -sfeTkError_t sfeQwiicOtos::getPosVelAcc(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc) +sfTkError_t sfDevOTOS::getPosVelAcc(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc) { // Read all pose registers uint8_t rawData[18]; size_t bytesRead; - sfeTkError_t err = _commBus->readRegisterRegion(kRegPosXL, rawData, 18, bytesRead); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->readRegister(kRegPosXL, rawData, 18, bytesRead); + if (err != ksfTkErrOk) return err; // Check if we read the correct number of bytes - if(bytesRead != 18) - return kSTkErrFail; + if (bytesRead != 18) + return ksfTkErrFail; // Convert raw data to pose units regsToPose(rawData, pos, kInt16ToMeter, kInt16ToRad); @@ -332,21 +349,21 @@ sfeTkError_t sfeQwiicOtos::getPosVelAcc(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_ regsToPose(rawData + 12, acc, kInt16ToMpss, kInt16ToRpss); // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::getPosVelAccStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc) +sfTkError_t sfDevOTOS::getPosVelAccStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc) { // Read all pose registers uint8_t rawData[18]; size_t bytesRead; - sfeTkError_t err = _commBus->readRegisterRegion(kRegPosStdXL, rawData, 18, bytesRead); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->readRegister(kRegPosStdXL, rawData, 18, bytesRead); + if (err != ksfTkErrOk) return err; // Check if we read the correct number of bytes - if(bytesRead != 18) - return kSTkErrFail; + if (bytesRead != 18) + return ksfTkErrFail; // Convert raw data to pose units regsToPose(rawData, pos, kInt16ToMeter, kInt16ToRad); @@ -354,22 +371,23 @@ sfeTkError_t sfeQwiicOtos::getPosVelAccStdDev(sfe_otos_pose2d_t &pos, sfe_otos_p regsToPose(rawData + 12, acc, kInt16ToMpss, kInt16ToRpss); // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::getPosVelAccAndStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc, - sfe_otos_pose2d_t &posStdDev, sfe_otos_pose2d_t &velStdDev, sfe_otos_pose2d_t &accStdDev) +sfTkError_t sfDevOTOS::getPosVelAccAndStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc, + sfe_otos_pose2d_t &posStdDev, sfe_otos_pose2d_t &velStdDev, + sfe_otos_pose2d_t &accStdDev) { // Read all pose registers uint8_t rawData[36]; size_t bytesRead; - sfeTkError_t err = _commBus->readRegisterRegion(kRegPosXL, rawData, 36, bytesRead); - if(err != kSTkErrOk) + sfTkError_t err = _commBus->readRegister(kRegPosXL, rawData, 36, bytesRead); + if (err != ksfTkErrOk) return err; // Check if we read the correct number of bytes - if(bytesRead != 36) - return kSTkErrFail; + if (bytesRead != 36) + return ksfTkErrFail; // Convert raw data to pose units regsToPose(rawData, pos, kInt16ToMeter, kInt16ToRad); @@ -380,40 +398,40 @@ sfeTkError_t sfeQwiicOtos::getPosVelAccAndStdDev(sfe_otos_pose2d_t &pos, sfe_oto regsToPose(rawData + 30, accStdDev, kInt16ToMpss, kInt16ToRpss); // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::readPoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH) +sfTkError_t sfDevOTOS::readPoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH) { size_t bytesRead; uint8_t rawData[6]; // Attempt to read the raw pose data - sfeTkError_t err = _commBus->readRegisterRegion(reg, rawData, 6, bytesRead); - if (err != kSTkErrOk) + sfTkError_t err = _commBus->readRegister(reg, rawData, 6, bytesRead); + if (err != ksfTkErrOk) return err; // Check if we read the correct number of bytes if (bytesRead != 6) - return kSTkErrFail; + return ksfTkErrFail; regsToPose(rawData, pose, rawToXY, rawToH); // Done! - return kSTkErrOk; + return ksfTkErrOk; } -sfeTkError_t sfeQwiicOtos::writePoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw) +sfTkError_t sfDevOTOS::writePoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw) { // Store raw data in a temporary buffer uint8_t rawData[6]; poseToRegs(rawData, pose, xyToRaw, hToRaw); // Write the raw data to the device - return _commBus->writeRegisterRegion(reg, rawData, 6); + return _commBus->writeRegister(reg, rawData, 6); } -void sfeQwiicOtos::regsToPose(uint8_t *rawData, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH) +void sfDevOTOS::regsToPose(uint8_t *rawData, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH) { // Store raw data int16_t rawX = (rawData[1] << 8) | rawData[0]; @@ -426,13 +444,13 @@ void sfeQwiicOtos::regsToPose(uint8_t *rawData, sfe_otos_pose2d_t &pose, float r pose.h = rawH * rawToH * _radToUnit; } -void sfeQwiicOtos::poseToRegs(uint8_t *rawData, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw) +void sfDevOTOS::poseToRegs(uint8_t *rawData, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw) { // Convert pose units to raw data int16_t rawX = pose.x * xyToRaw / _meterToUnit; int16_t rawY = pose.y * xyToRaw / _meterToUnit; int16_t rawH = pose.h * hToRaw / _radToUnit; - + // Store raw data in buffer rawData[0] = rawX & 0xFF; rawData[1] = (rawX >> 8) & 0xFF; diff --git a/src/sfeQwiicOtos.h b/src/sfTk/sfDevOTOS.h similarity index 85% rename from src/sfeQwiicOtos.h rename to src/sfTk/sfDevOTOS.h index 1db2c40..5a982ef 100644 --- a/src/sfeQwiicOtos.h +++ b/src/sfTk/sfDevOTOS.h @@ -1,29 +1,41 @@ -/* - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics -*/ - -/******************************************************************************* - sfeQwiicOtos.h - C++ driver for the SparkFun Qwiic Optical Tracking Odometry - Sensor (OTOS). - - This file implements the driver as a C++ class. The driver uses the SparkFun - Toolkit library for I2C communication, which enables this driver to be - platform agnostic: - https://github.com/sparkfun/SparkFun_Toolkit -*******************************************************************************/ - +/** + * @file sfDevOTOS.h + * @brief Driver for the SparkFun Qwiic Optical Tracking Odometry Sensor (OTOS) + * @details This file implements a C++ driver class for interfacing with the OTOS sensor. + * The driver uses the SparkFun Toolkit library for I2C communication, + * making it platform agnostic. + * + * Features: + * - 2D pose tracking (position, velocity, acceleration) + * - Configurable linear and angular units + * - Built-in calibration and self-test functions + * - Adjustable signal processing parameters + * - Support for sensor offset compensation + * - Standard deviation measurements for tracking accuracy + * + * @author SparkFun Electronics + * @date February 2024 + * @copyright Copyright (c) 2024-2025, SparkFun Electronics Inc. This project is released under the MIT License. + * + * SPDX-License-Identifier: MIT + * + * @see https://github.com/sparkfun/SparkFun_Toolkit + */ #pragma once -#include "SparkFun_Toolkit.h" #include #include +// include the sparkfun toolkit headers +#include + +// Bus interfaces +#include + /// @struct sfe_otos_pose2d_t /// @brief 2D pose structure, including x and y coordinates and heading angle /// @note Although pose is traditionally used for position and orientation, this -/// structure is also used for velocity and accleration by the OTOS driver +/// structure is also used for velocity and acceleration by the OTOS driver typedef struct { /// @brief X value @@ -109,20 +121,20 @@ typedef union { { /// @brief Write 1 to start the self test uint8_t start : 1; - + /// @brief Returns 1 while the self test is in progress uint8_t inProgress : 1; - + /// @brief Returns 1 if the self test passed uint8_t pass : 1; - + /// @brief Returns 1 if the self test failed uint8_t fail : 1; - + /// @brief Reserved bits, do not use uint8_t reserved : 4; }; - + /// @brief Raw register value uint8_t value; } sfe_otos_self_test_config_t; @@ -158,30 +170,30 @@ typedef union { /// Includes methods to communicate with the sensor, such as getting the tracked /// location, configuring the sensor, etc. This class is a base class that must /// be derived to implement the delay function and I2C communication bus. -class sfeQwiicOtos +class sfDevOTOS { public: /// @brief Default constructor, only initializes member variables - sfeQwiicOtos(); + sfDevOTOS(); /// @brief Begins the Qwiic OTOS and verifies it is connected /// @param commBus I2C bus to use for communication /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t begin(sfeTkII2C *commBus = nullptr); + sfTkError_t begin(sfTkII2C *commBus = nullptr); /// @brief Checks if the device is connected /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t isConnected(); + sfTkError_t isConnected(); /// @brief Gets the hardware and firmware version numbers from the OTOS /// @param hwVersion Hardware version number /// @param fwVersion Firmware version number /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getVersionInfo(sfe_otos_version_t &hwVersion, sfe_otos_version_t &fwVersion); + sfTkError_t getVersionInfo(sfe_otos_version_t &hwVersion, sfe_otos_version_t &fwVersion); /// @brief Performs a self test of the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t selfTest(); + sfTkError_t selfTest(); /// @brief Calibrates the IMU on the OTOS, which removes the accelerometer /// and gyroscope offsets @@ -190,13 +202,13 @@ class sfeQwiicOtos /// @param waitUntilDone Whether to wait until the calibration is complete. /// Set false to calibrate asynchronously, see getImuCalibrationProgress() /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t calibrateImu(uint8_t numSamples = 255, bool waitUntilDone = true); + sfTkError_t calibrateImu(uint8_t numSamples = 255, bool waitUntilDone = true); /// @brief Gets the progress of the IMU calibration. Used for asynchronous /// calibration with calibrateImu() /// @param numSamples Number of samples remaining for calibration /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getImuCalibrationProgress(uint8_t &numSamples); + sfTkError_t getImuCalibrationProgress(uint8_t &numSamples); /// @brief Gets the linear unit used by all methods using a pose /// @return Linear unit @@ -217,63 +229,63 @@ class sfeQwiicOtos /// @brief Gets the linear scalar used by the OTOS /// @param scalar Linear scalar /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getLinearScalar(float &scalar); + sfTkError_t getLinearScalar(float &scalar); /// @brief Sets the linear scalar used by the OTOS. Can be used to /// compensate for scaling issues with the sensor measurements /// @param scalar Linear scalar, must be between 0.872 and 1.127 /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t setLinearScalar(float scalar); + sfTkError_t setLinearScalar(float scalar); /// @brief Gets the angular scalar used by the OTOS /// @param scalar Angular scalar /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getAngularScalar(float &scalar); + sfTkError_t getAngularScalar(float &scalar); /// @brief Sets the angular scalar used by the OTOS. Can be used to /// compensate for scaling issues with the sensor measurements /// @param scalar Angular scalar, must be between 0.872 and 1.127 /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t setAngularScalar(float scalar); + sfTkError_t setAngularScalar(float scalar); /// @brief Resets the tracking algorithm, which resets the position to the /// origin, but can also be used to recover from some rare tracking errors /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t resetTracking(); + sfTkError_t resetTracking(); /// @brief Gets the signal processing configuration from the OTOS /// @param config Signal processing configuration /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getSignalProcessConfig(sfe_otos_signal_process_config_t &config); + sfTkError_t getSignalProcessConfig(sfe_otos_signal_process_config_t &config); /// @brief Sets the signal processing configuration on the OTOS. This is /// primarily useful for creating and testing a new lookup table calibration /// @param config Signal processing configuration /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t setSignalProcessConfig(sfe_otos_signal_process_config_t &config); + sfTkError_t setSignalProcessConfig(sfe_otos_signal_process_config_t &config); /// @brief Gets the status register from the OTOS, which includes warnings /// and errors reported by the sensor /// @param status Status register value /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getStatus(sfe_otos_status_t &status); + sfTkError_t getStatus(sfe_otos_status_t &status); /// @brief Gets the offset of the OTOS /// @param pose Offset of the sensor relative to the center of the robot /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getOffset(sfe_otos_pose2d_t &pose); + sfTkError_t getOffset(sfe_otos_pose2d_t &pose); /// @brief Sets the offset of the OTOS. This is useful if your sensor is /// mounted off-center from a robot. Rather than returning the position of /// the sensor, the OTOS will return the position of the robot /// @param pose Offset of the sensor relative to the center of the robot /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t setOffset(sfe_otos_pose2d_t &pose); + sfTkError_t setOffset(sfe_otos_pose2d_t &pose); /// @brief Gets the position measured by the OTOS /// @param pose Position measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getPosition(sfe_otos_pose2d_t &pose); + sfTkError_t getPosition(sfe_otos_pose2d_t &pose); /// @brief Sets the position measured by the OTOS. This is useful if your /// robot does not start at the origin, or you have another source of @@ -281,17 +293,17 @@ class sfeQwiicOtos /// tracking from this position /// @param pose New position for the OTOS to track from /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t setPosition(sfe_otos_pose2d_t &pose); + sfTkError_t setPosition(sfe_otos_pose2d_t &pose); /// @brief Gets the velocity measured by the OTOS /// @param pose Velocity measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getVelocity(sfe_otos_pose2d_t &pose); + sfTkError_t getVelocity(sfe_otos_pose2d_t &pose); /// @brief Gets the acceleration measured by the OTOS /// @param pose Acceleration measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getAcceleration(sfe_otos_pose2d_t &pose); + sfTkError_t getAcceleration(sfe_otos_pose2d_t &pose); /// @brief Gets the standard deviation of the measured position /// @param pose Standard deviation of the position measured by the OTOS @@ -299,7 +311,7 @@ class sfeQwiicOtos /// @note These values are just the square root of the diagonal elements of /// the covariance matrices of the Kalman filters used in the firmware, so /// they are just statistical quantities and do not represent actual error! - sfeTkError_t getPositionStdDev(sfe_otos_pose2d_t &pose); + sfTkError_t getPositionStdDev(sfe_otos_pose2d_t &pose); /// @brief Gets the standard deviation of the measured velocity /// @param pose Standard deviation of the velocity measured by the OTOS @@ -307,7 +319,7 @@ class sfeQwiicOtos /// @note These values are just the square root of the diagonal elements of /// the covariance matrices of the Kalman filters used in the firmware, so /// they are just statistical quantities and do not represent actual error! - sfeTkError_t getVelocityStdDev(sfe_otos_pose2d_t &pose); + sfTkError_t getVelocityStdDev(sfe_otos_pose2d_t &pose); /// @brief Gets the standard deviation of the measured acceleration /// @param pose Standard deviation of the acceleration measured by the OTOS @@ -315,7 +327,7 @@ class sfeQwiicOtos /// @note These values are just the square root of the diagonal elements of /// the covariance matrices of the Kalman filters used in the firmware, so /// they are just statistical quantities and do not represent actual error! - sfeTkError_t getAccelerationStdDev(sfe_otos_pose2d_t &pose); + sfTkError_t getAccelerationStdDev(sfe_otos_pose2d_t &pose); /// @brief Gets the position, velocity, and acceleration measured by the /// OTOS in a single burst read @@ -323,7 +335,7 @@ class sfeQwiicOtos /// @param vel Velocity measured by the OTOS /// @param acc Acceleration measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getPosVelAcc(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc); + sfTkError_t getPosVelAcc(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc); /// @brief Gets the standard deviation of the measured position, velocity, /// and acceleration in a single burst read @@ -331,7 +343,7 @@ class sfeQwiicOtos /// @param vel Standard deviation of the velocity measured by the OTOS /// @param acc Standard deviation of the acceleration measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getPosVelAccStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc); + sfTkError_t getPosVelAccStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc); /// @brief Gets the position, velocity, acceleration, and standard deviation /// of each in a single burst read @@ -342,8 +354,9 @@ class sfeQwiicOtos /// @param velStdDev Standard deviation of the velocity measured by the OTOS /// @param accStdDev Standard deviation of the acceleration measured by the OTOS /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t getPosVelAccAndStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc, - sfe_otos_pose2d_t &posStdDev, sfe_otos_pose2d_t &velStdDev, sfe_otos_pose2d_t &accStdDev); + sfTkError_t getPosVelAccAndStdDev(sfe_otos_pose2d_t &pos, sfe_otos_pose2d_t &vel, sfe_otos_pose2d_t &acc, + sfe_otos_pose2d_t &posStdDev, sfe_otos_pose2d_t &velStdDev, + sfe_otos_pose2d_t &accStdDev); /// @brief Default I2C addresses of the Qwiic OTOS static constexpr uint8_t kDefaultAddress = 0x17; @@ -360,10 +373,10 @@ class sfeQwiicOtos virtual void delayMs(uint32_t ms) = 0; // Function to read raw pose registers and convert to specified units - sfeTkError_t readPoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH); + sfTkError_t readPoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH); // Function to write raw pose registers and convert from specified units - sfeTkError_t writePoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw); + sfTkError_t writePoseRegs(uint8_t reg, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw); // Function to convert raw pose registers to a pose structure void regsToPose(uint8_t *rawData, sfe_otos_pose2d_t &pose, float rawToXY, float rawToH); @@ -372,7 +385,7 @@ class sfeQwiicOtos void poseToRegs(uint8_t *rawData, sfe_otos_pose2d_t &pose, float xyToRaw, float hToRaw); // I2C bus to use for communication - sfeTkII2C *_commBus; + sfTkII2C *_commBus; // Units to be used by the public pose functions. Everything uses meters and // radians internally, so this just determines what conversion factor is