diff --git a/CMakeLists.txt b/CMakeLists.txt index 09bec0a5f3a8d1e6bc1aed9a421b271a121b96f2..ecc81f3cda8b50cc39f6dd72bc454462aebbc9f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -463,6 +463,10 @@ sbs_target(test-vn100-serial stm32f407vg_stm32f4discovery) add_executable(test-vn100-spi src/tests/sensors/test-vn100-spi.cpp) sbs_target(test-vn100-spi stm32f407vg_stm32f4discovery) +add_executable(test-vn300 src/tests/sensors/test-vn300.cpp) +sbs_target(test-vn300 stm32f767zi_compute_unit) + + add_executable(test-lis2mdl src/tests/sensors/test-lis2mdl.cpp) sbs_target(test-lis2mdl stm32f429zi_stm32f4discovery) diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake index 5a53153f43b849cb58d00a2589228ea5ae847241..17c8f18bcfe3fe80f80c6e6e07ed3f828c25f72c 100644 --- a/cmake/boardcore.cmake +++ b/cmake/boardcore.cmake @@ -109,8 +109,10 @@ set(BOARDCORE_SRC ${BOARDCORE_PATH}/src/shared/sensors/SensorSampler.cpp ${BOARDCORE_PATH}/src/shared/sensors/UBXGPS/UBXGPSSerial.cpp ${BOARDCORE_PATH}/src/shared/sensors/UBXGPS/UBXGPSSpi.cpp - ${BOARDCORE_PATH}/src/shared/sensors/VN100/VN100Serial.cpp - ${BOARDCORE_PATH}/src/shared/sensors/VN100/VN100Spi.cpp + ${BOARDCORE_PATH}/src/shared/sensors/Vectornav/VNCommonSerial.cpp + ${BOARDCORE_PATH}/src/shared/sensors/Vectornav/VN100/VN100Serial.cpp + ${BOARDCORE_PATH}/src/shared/sensors/Vectornav/VN100/VN100Spi.cpp + ${BOARDCORE_PATH}/src/shared/sensors/Vectornav/VN300/VN300.cpp ${BOARDCORE_PATH}/src/shared/sensors/LIS2MDL/LIS2MDL.cpp ${BOARDCORE_PATH}/src/shared/sensors/LPS28DFW/LPS28DFW.cpp ${BOARDCORE_PATH}/src/shared/sensors/LPS22DF/LPS22DF.cpp diff --git a/src/shared/logger/LogTypes.h b/src/shared/logger/LogTypes.h index 0bed3aa6880acb8ff33e17047ba6484ad2b87230..98884a8dc501f617aba545be8ae4763b7e7d444c 100644 --- a/src/shared/logger/LogTypes.h +++ b/src/shared/logger/LogTypes.h @@ -61,8 +61,9 @@ #include <sensors/RotatedIMU/IMUData.h> #include <sensors/SensorData.h> #include <sensors/UBXGPS/UBXGPSData.h> -#include <sensors/VN100/VN100SerialData.h> -#include <sensors/VN100/VN100SpiData.h> +#include <sensors/Vectornav/VN100/VN100SerialData.h> +#include <sensors/Vectornav/VN100/VN100SpiData.h> +#include <sensors/Vectornav/VN300/VN300Data.h> #include <sensors/analog/AnalogLoadCellData.h> #include <sensors/analog/BatteryVoltageSensorData.h> #include <sensors/analog/Pitot/PitotData.h> @@ -173,6 +174,7 @@ void registerTypes(Deserializer& ds) ds.registerType<UBXGPSData>(); ds.registerType<VN100SerialData>(); ds.registerType<VN100SpiData>(); + ds.registerType<VN300Data>(); ds.registerType<VoltageData>(); ds.registerType<Xbee::XbeeStatus>(); } diff --git a/src/shared/sensors/VN100/VN100Serial.cpp b/src/shared/sensors/Vectornav/VN100/VN100Serial.cpp similarity index 100% rename from src/shared/sensors/VN100/VN100Serial.cpp rename to src/shared/sensors/Vectornav/VN100/VN100Serial.cpp diff --git a/src/shared/sensors/VN100/VN100Serial.h b/src/shared/sensors/Vectornav/VN100/VN100Serial.h similarity index 100% rename from src/shared/sensors/VN100/VN100Serial.h rename to src/shared/sensors/Vectornav/VN100/VN100Serial.h diff --git a/src/shared/sensors/VN100/VN100SerialData.h b/src/shared/sensors/Vectornav/VN100/VN100SerialData.h similarity index 100% rename from src/shared/sensors/VN100/VN100SerialData.h rename to src/shared/sensors/Vectornav/VN100/VN100SerialData.h diff --git a/src/shared/sensors/VN100/VN100Spi.cpp b/src/shared/sensors/Vectornav/VN100/VN100Spi.cpp similarity index 100% rename from src/shared/sensors/VN100/VN100Spi.cpp rename to src/shared/sensors/Vectornav/VN100/VN100Spi.cpp diff --git a/src/shared/sensors/VN100/VN100Spi.h b/src/shared/sensors/Vectornav/VN100/VN100Spi.h similarity index 100% rename from src/shared/sensors/VN100/VN100Spi.h rename to src/shared/sensors/Vectornav/VN100/VN100Spi.h diff --git a/src/shared/sensors/VN100/VN100SpiData.h b/src/shared/sensors/Vectornav/VN100/VN100SpiData.h similarity index 100% rename from src/shared/sensors/VN100/VN100SpiData.h rename to src/shared/sensors/Vectornav/VN100/VN100SpiData.h diff --git a/src/shared/sensors/VN100/VN100SpiDefs.h b/src/shared/sensors/Vectornav/VN100/VN100SpiDefs.h similarity index 100% rename from src/shared/sensors/VN100/VN100SpiDefs.h rename to src/shared/sensors/Vectornav/VN100/VN100SpiDefs.h diff --git a/src/shared/sensors/Vectornav/VN300/VN300.cpp b/src/shared/sensors/Vectornav/VN300/VN300.cpp new file mode 100755 index 0000000000000000000000000000000000000000..55feeebda4484a9e9dc8d6e77f541bfd8fb64a5b --- /dev/null +++ b/src/shared/sensors/Vectornav/VN300/VN300.cpp @@ -0,0 +1,468 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "VN300.h" + +#include <drivers/timer/TimestampTimer.h> +#include <utils/Debug.h> + +namespace Boardcore +{ + +VN300::VN300(USART& usart, int userBaudRate, + VN300Defs::SampleOptions sampleOption, CRCOptions crc, + std::chrono::milliseconds timeout, + const VN300Defs::AntennaPosition antPosA, + const VN300Defs::AntennaPosition antPosB, + const Eigen::Matrix3f rotMat) + : VNCommonSerial(usart, userBaudRate, "VN300", crc, timeout), + sampleOption(sampleOption), antPosA(antPosA), antPosB(antPosB), + rotMat(rotMat) +{ +} + +bool VN300::init() +{ + SensorErrors backup = lastError; + + // If already initialized + if (isInit) + { + lastError = SensorErrors::ALREADY_INIT; + LOG_WARN(logger, "Sensor VN300 already initialized"); + return false; + } + + // Allocate the pre loaded strings based on the user selected crc + if (crc == CRCOptions::CRC_ENABLE_16) + { + preSampleBin1 = "$VNBOM,1*749D\n"; + } + else + { + preSampleBin1 = "$VNBOM,1*45\n"; + } + + // Set the error to init fail and if the init process goes without problem + // i restore it to the last error + lastError = SensorErrors::INIT_FAIL; + + if (recvString.data() == NULL) + { + LOG_ERR(logger, "Unable to initialize the receive VN300 string"); + return false; + } + + configDefaultSerialPort(); + + if (!setCrc(false)) + { + LOG_ERR(logger, "Unable to set the vn300 user selected CRC"); + return false; + } + + if (!disableAsyncMessages(false)) + { + LOG_ERR(logger, "Unable to disable async messages from vn300"); + return false; + } + + if (!configUserSerialPort()) + { + LOG_ERR(logger, "Unable to config the user vn300 serial port"); + return false; + } + + if (!verifyModelNumber("VN-300T-CR")) + { + LOG_ERR(logger, "Error, model number not corresponding"); + lastError = INVALID_WHOAMI; + return false; + } + + if (!setBinaryOutput()) + { + LOG_ERR(logger, "Unable to set binary output register"); + return false; + } + + if (!setAntennaA(antPosA)) + { + LOG_ERR(logger, "Unable to set antenna A position"); + return false; + } + + if (!setCompassBaseline(antPosB)) + { + LOG_ERR(logger, "Unable to set compass baseline"); + return false; + } + + if (!setReferenceFrame(rotMat)) + { + LOG_ERR(logger, "Unable to set reference frame rotation"); + return false; + } + + // I need to repeat this in case of a non default + // serial port communication at the beginning + if (!setCrc(true)) + { + LOG_ERR(logger, "Unable to set the vn300 user selected CRC"); + return false; + } + + if (!disableAsyncMessages(true)) + { + LOG_ERR(logger, "Unable to disable async messages from vn300"); + return false; + } + + isInit = true; + + // All good i restore the actual last error + lastError = backup; + + return true; +} + +bool VN300::selfTest() { return true; } + +VN300Data VN300::sampleImpl() +{ + D(assert(isInit && "init() was not called")); + + // Reset any errors + lastError = SensorErrors::NO_ERRORS; + + if (sampleOption == VN300Defs::SampleOptions::FULL) + { + return sampleFull(); + } + else if (sampleOption == VN300Defs::SampleOptions::REDUCED) + { + return sampleReduced(); + } + else + { + // Sample option not implemented + lastError = SensorErrors::COMMAND_FAILED; + return lastSample; + } +} + +VN300Data VN300::sampleFull() +{ + VN300Data data; + VN300Defs::BinaryDataFull bindataFull; + + const uint64_t timestamp = TimestampTimer::getTimestamp(); + + bool sampleOutcome = + false; // True if a valid sample was retrieved from the sensor + bool validChecksum = false; + + sampleOutcome = + getBinaryOutput<VN300Defs::BinaryDataFull>(bindataFull, preSampleBin1); + if (!sampleOutcome) + { + lastError = NO_NEW_DATA; + } + + validChecksum = + crc == CRCOptions::CRC_NO || + calculateChecksum16(reinterpret_cast<uint8_t*>(&bindataFull), + sizeof(bindataFull)) == 0; + if (sampleOutcome && !validChecksum) + { + lastError = SensorErrors::BUS_FAULT; + } + + // Verify if the sample is valid + sampleOutcome = sampleOutcome && validChecksum; + + if (sampleOutcome) + { + buildBinaryDataFull(bindataFull, data, timestamp); + return data; + } + else + { + // Last error is already set + return lastSample; + } +} + +VN300Data VN300::sampleReduced() +{ + VN300Data data; + VN300Defs::BinaryDataReduced binDataReduced; + + const uint64_t timestamp = TimestampTimer::getTimestamp(); + + bool sampleOutcome = + false; // True if a valid sample was retrieved from the sensor + bool validChecksum = false; + + sampleOutcome = getBinaryOutput<VN300Defs::BinaryDataReduced>( + binDataReduced, preSampleBin1); + if (!sampleOutcome) + { + lastError = NO_NEW_DATA; + } + + validChecksum = + crc == CRCOptions::CRC_NO || + calculateChecksum16(reinterpret_cast<uint8_t*>(&binDataReduced), + sizeof(binDataReduced)) == 0; + if (sampleOutcome && !validChecksum) + { + lastError = SensorErrors::BUS_FAULT; + } + + // Verify if the sample is valid + sampleOutcome = sampleOutcome && validChecksum; + + if (sampleOutcome) + { + buildBinaryDataReduced(binDataReduced, data, timestamp); + return data; + } + else + { + // Last error is already set + return lastSample; + } +} + +void VN300::buildBinaryDataFull(const VN300Defs::BinaryDataFull& rawData, + VN300Data& data, const uint64_t timestamp) +{ + // Quaternion + data.quaternionTimestamp = timestamp; + data.quaternionW = rawData.quaternionW; + data.quaternionX = rawData.quaternionX; + data.quaternionY = rawData.quaternionY; + data.quaternionZ = rawData.quaternionZ; + + // Accelerometer + data.accelerationTimestamp = timestamp; + data.accelerationX = rawData.accelX; + data.accelerationY = rawData.accelY; + data.accelerationZ = rawData.accelZ; + + // Magnetometer + data.magneticFieldTimestamp = timestamp; + data.magneticFieldX = rawData.magneticX; + data.magneticFieldY = rawData.magneticY; + data.magneticFieldZ = rawData.magneticZ; + + // Gyroscope + data.angularSpeedTimestamp = timestamp; + data.angularSpeedX = rawData.angularX; + data.angularSpeedY = rawData.angularY; + data.angularSpeedZ = rawData.angularZ; + + // Gps + data.insTimestamp = timestamp; + data.gpsFix = rawData.gpsFix; + data.insStatus = rawData.insStatus; + data.yaw = rawData.yaw; + data.pitch = rawData.pitch; + data.roll = rawData.roll; + data.latitude = rawData.latitude; + data.longitude = rawData.longitude; + data.altitude = rawData.altitude; + data.velocityX = rawData.velocityX; + data.velocityY = rawData.velocityY; + data.velocityZ = rawData.velocityZ; +} + +void VN300::buildBinaryDataReduced(const VN300Defs::BinaryDataReduced& rawData, + VN300Data& data, const uint64_t timestamp) +{ + data = VN300Data(); + + // Gps + data.insTimestamp = timestamp; + data.latitude = rawData.latitude; + data.longitude = rawData.longitude; + data.altitude = rawData.altitude; + data.gpsFix = rawData.gpsFix; + + // Angular + data.angularSpeedTimestamp = timestamp; + data.angularSpeedX = rawData.angularX; + data.angularSpeedY = rawData.angularY; + data.angularSpeedZ = rawData.angularZ; + + // Yaw pith roll + data.yaw = rawData.yaw; + data.pitch = rawData.pitch; + data.roll = rawData.roll; + + // Quaternion + data.quaternionTimestamp = timestamp; + data.quaternionW = rawData.quaternionW; + data.quaternionX = rawData.quaternionX; + data.quaternionY = rawData.quaternionY; + data.quaternionZ = rawData.quaternionZ; +} + +bool VN300::setAntennaA(VN300Defs::AntennaPosition antPos) +{ + std::string command; + + command = fmt::format("{}{},{},{}", "VNWRG,57,", antPos.posX, antPos.posY, + antPos.posZ); + + usart.clearQueue(); + if (!sendStringCommand(command)) + { + return false; + } + + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + + if (checkErrorVN(recvString.data())) + return false; + + return true; +} + +bool VN300::setCompassBaseline(VN300Defs::AntennaPosition antPos) +{ + std::string command; + + command = fmt::format("{}{},{},{},{},{},{}", "VNWRG,93,", antPos.posX, + antPos.posY, antPos.posZ, antPos.uncX, antPos.uncY, + antPos.uncZ); + + usart.clearQueue(); + if (!sendStringCommand(command)) + { + return false; + } + + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + + if (checkErrorVN(recvString.data())) + return false; + + return true; +} + +bool VN300::setReferenceFrame(Eigen::Matrix3f rotMat) +{ + + std::string command = + fmt::format("{}{},{},{},{},{},{},{},{},{}", "VNWRG,26,", rotMat(0, 0), + rotMat(0, 1), rotMat(0, 2), rotMat(1, 0), rotMat(1, 1), + rotMat(1, 2), rotMat(2, 0), rotMat(2, 1), rotMat(2, 2)); + + usart.clearQueue(); + if (!sendStringCommand(command)) + { + return false; + } + + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + + if (checkErrorVN(recvString.data())) + return false; + + return true; +} + +bool VN300::setBinaryOutput() +{ + uint16_t outputGroup = 0, commonGroup = 0, gnssGroup = 0; + + switch (sampleOption) + { + case VN300Defs::SampleOptions::FULL: + outputGroup = + VN300Defs::BINARYGROUP_COMMON | VN300Defs::BINARYGROUP_GPS; + + commonGroup = VN300Defs::COMMONGROUP_YAWPITCHROLL | + VN300Defs::COMMONGROUP_QUATERNION | + VN300Defs::COMMONGROUP_ANGULARRATE | + VN300Defs::COMMONGROUP_VELOCITY | + VN300Defs::COMMONGROUP_ACCEL | + VN300Defs::COMMONGROUP_MAGPRES | + VN300Defs::COMMONGROUP_INSSTATUS; + + gnssGroup = VN300Defs::GPSGROUP_NUMSATS | VN300Defs::GPSGROUP_FIX | + VN300Defs::GPSGROUP_POSLLA; + break; + case VN300Defs::SampleOptions::REDUCED: + outputGroup = + VN300Defs::BINARYGROUP_COMMON | VN300Defs::BINARYGROUP_GPS; + commonGroup = VN300Defs::COMMONGROUP_YAWPITCHROLL | + VN300Defs::COMMONGROUP_QUATERNION | + VN300Defs::COMMONGROUP_ANGULARRATE; + gnssGroup = VN300Defs::GPSGROUP_FIX | VN300Defs::GPSGROUP_POSLLA; + + break; + } + + // "0" means that no asynchronous message is active + // "8" represents the divider at which the asynchronous message is sent with + // respect to the max rate + const char* const comp = "VNWRG,75,0,8"; + + std::string command = fmt::format("{},{},{:x},{:x}", comp, outputGroup, + commonGroup, gnssGroup); + + usart.clearQueue(); + + if (!sendStringCommand(command)) + { + return false; + } + + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + LOG_WARN(logger, "Unable to sample due to serial communication error"); + return false; + } + + if (checkErrorVN(recvString.data()) != 0) + { + LOG_WARN(logger, "Error while setting binary output: {}", + recvString.data()); + return false; + } + + return true; +} + +} // namespace Boardcore diff --git a/src/shared/sensors/Vectornav/VN300/VN300.h b/src/shared/sensors/Vectornav/VN300/VN300.h new file mode 100755 index 0000000000000000000000000000000000000000..9bf642020b9c6990dd3db4ec3303c9e3c0348846 --- /dev/null +++ b/src/shared/sensors/Vectornav/VN300/VN300.h @@ -0,0 +1,158 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <sensors/Sensor.h> +#include <sensors/Vectornav/VNCommonSerial.h> + +#include "VN300Data.h" +#include "VN300Defs.h" + +namespace Boardcore +{ + +/** + * @brief Driver class for VN300 IMU. + */ +class VN300 : public Sensor<VN300Data>, public VNCommonSerial +{ +public: + /** + * @brief Constructor. + * + * @param usart Serial bus used for the sensor. + * @param BaudRate different from the sensor's default [9600, 19200, 38400, + * 57600, 115200, 128000, 230400, 460800, 921600]. + * @param sampleOption The data packet we want to sample. + * @param crc Checksum option. + * @param timeout The maximum time that will be waited when reading from the + * sensor. + * @param antPosA Antenna position A. + * @param antPosB Antenna position B. + * @param rotMat Rotation matrix. + */ + VN300(USART& usart, int userBaudRate, VN300Defs::SampleOptions sampleOption, + CRCOptions crc, std::chrono::milliseconds timeout, + VN300Defs::AntennaPosition antPosA = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + VN300Defs::AntennaPosition antPosB = {1.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + Eigen::Matrix3f rotMat = Eigen::Matrix3f::Identity()); + + bool init() override; + + bool selfTest() override; + +protected: + /** + * @brief Sample action implementation. + */ + VN300Data sampleImpl() override; + +private: + /** + * @brief Sets the antenna A offset. + * + * @param antPos antenna position. + * + * @return True if operation succeeded. + */ + bool setAntennaA(VN300Defs::AntennaPosition antPos); + + /** + * @brief Sets the compass baseline, position offset of antenna B respect to + * antenna A. Uncertainty must be higher than actual measurement error, + * possibly twice as the error. + * All measures are in meters [m]. + * + * @param antPos antenna position. + * + * @return True if operation succeeded. + */ + bool setCompassBaseline(VN300Defs::AntennaPosition antPos); + + /** + * @brief Set the reference frame rotation of the sensor in order to have + * all the data on the desired reference frame. + * + * @param rotMat rotation matrix. + * + * @return True if operation succeeded. + */ + bool setReferenceFrame(Eigen::Matrix3f rotMat); + + /** + * @brief Set the binary output register. + * + * @return True if operation succeeded. + */ + bool setBinaryOutput(); + + /** + * @brief Utility function for sampling data from the sensor (FULL packet). + * + * @return The sampled data. + */ + VN300Data sampleFull(); + + /** + * @brief Utility function for sampling data from the sensor (Reduced + * packet). + * + * @return The sampled data. + */ + VN300Data sampleReduced(); + + /** + * @brief Build output data packet starting from raw binary data received + * from the sensor. + * + * @param rawData The raw data received from the sensor. + * @param data The structure that will contain the output. + * @param timestamp The timestamp of the extracted data. + */ + void buildBinaryDataFull(const VN300Defs::BinaryDataFull& rawData, + VN300Data& data, const uint64_t timestamp); + + /** + * @brief Build output data packet starting from raw binary data received + * from the sensor. + * + * @param rawData The raw data received from the sensor. + * @param data The structure that will contain the output. + * @param timestamp The timestamp of the extracted data. + */ + void buildBinaryDataReduced(const VN300Defs::BinaryDataReduced& rawData, + VN300Data& data, const uint64_t timestamp); + + VN300Defs::SampleOptions sampleOption; + + VN300Defs::AntennaPosition antPosA; + VN300Defs::AntennaPosition antPosB; + Eigen::Matrix3f rotMat; + + /** + * @brief Pre-elaborated binary output polling command. + */ + const char* preSampleBin1 = ""; +}; + +} // namespace Boardcore diff --git a/src/shared/sensors/Vectornav/VN300/VN300Data.h b/src/shared/sensors/Vectornav/VN300/VN300Data.h new file mode 100644 index 0000000000000000000000000000000000000000..538fecbfc5310079dee9c7ab4e7e83b002777b36 --- /dev/null +++ b/src/shared/sensors/Vectornav/VN300/VN300Data.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <sensors/SensorData.h> + +#include "VN300Defs.h" + +namespace Boardcore +{ + +/** + * @brief Data class for VN300. + * + * Units of measurement: + * - Magnetometer [Gauss] + * - Accelerometer [m/s^2] + * - Gyroscope [rad/s] + * - Velocity (NED) [m/s] + * - Temperature [°C] + * - Pressure [kPa] + * - Latitude [deg] + * - Longitude [deg] + * - Altitude [m] + */ +struct VN300Data : public QuaternionData, + public MagnetometerData, + public AccelerometerData, + public GyroscopeData, + public VN300Defs::INSData +{ + + // cppcheck-suppress uninitDerivedMemberVar + VN300Data() + : QuaternionData{0, 0.0, 0.0, 0.0, 0.0}, MagnetometerData{0, 0.0, 0.0, + 0.0}, + AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0}, + INSData{0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} + { + } + + // cppcheck-suppress uninitDerivedMemberVar + VN300Data(const QuaternionData& quat, const MagnetometerData& magData, + const AccelerometerData& accData, const GyroscopeData& gyro, + const INSData& ins) + : QuaternionData(quat), MagnetometerData(magData), + AccelerometerData(accData), GyroscopeData(gyro), INSData(ins) + { + } + + static std::string header() + { + return "quaternionTimestamp,quaternionX,quaternionY,quaternionZ," + "quaternionW,magneticFieldTimestamp," + "magneticFieldX,magneticFieldY,magneticFieldZ," + "accelerationTimestamp,accelerationX,accelerationY," + "accelerationZ,angularSpeedTimestamp,angularSpeedX," + "angularSpeedY,angularSpeedZ,insTimeStamp," + "insStatus,yaw,pitch,roll,latitude," + "longitude,altitude,nedVelX,nedVelY,nedVelZ\n"; + } + + void print(std::ostream& os) const + { + os << quaternionTimestamp << "," << quaternionX << "," << quaternionY + << "," << quaternionZ << "," << quaternionW << "," + << magneticFieldTimestamp << "," << magneticFieldX << "," + << magneticFieldY << "," << magneticFieldZ << "," + << accelerationTimestamp << "," << accelerationX << "," + << accelerationY << "," << accelerationZ << "," + << angularSpeedTimestamp << "," << angularSpeedX << "," + << angularSpeedY << "," << angularSpeedZ << "," << insTimestamp + << "," << insStatus << "," << yaw << "," << pitch << "," << roll + << "," << latitude << "," << longitude << "," << altitude << "," + << velocityX << "," << velocityY << "," << velocityZ << "\n"; + } +}; + +} // namespace Boardcore diff --git a/src/shared/sensors/Vectornav/VN300/VN300Defs.h b/src/shared/sensors/Vectornav/VN300/VN300Defs.h new file mode 100755 index 0000000000000000000000000000000000000000..b7037a26b36a03aabd3c6b51bd527b10b62b0cff --- /dev/null +++ b/src/shared/sensors/Vectornav/VN300/VN300Defs.h @@ -0,0 +1,289 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +namespace Boardcore +{ + +namespace VN300Defs +{ + +/** + * @brief The groups of binary data available from the sensor. + */ +enum BinaryGroup +{ + BINARYGROUP_COMMON = 0x01, ///< Common group. + BINARYGROUP_TIME = 0x02, ///< Time group. + BINARYGROUP_IMU = 0x04, ///< IMU group. + BINARYGROUP_GPS = 0x08, ///< GPS group. + BINARYGROUP_ATTITUDE = 0x10, ///< Attitude group. + BINARYGROUP_INS = 0x20, ///< INS group. + BINARYGROUP_GPS2 = 0x40 ///< GPS2 group. +}; + +/** + * @brief Values used to select data for the binary output from group 1 (common + * group). + */ +enum CommonGroup +{ + COMMONGROUP_NONE = 0x0000, ///< None. + COMMONGROUP_TIMESTARTUP = 0x0001, ///< Time since startup (nanoseconds). + COMMONGROUP_TIMEGPS = 0x0002, ///< Gps time. + COMMONGROUP_TIMESYNCIN = 0x0004, ///< Time since last sync in trigger. + COMMONGROUP_YAWPITCHROLL = 0x0008, ///< Yaw pitch roll. + COMMONGROUP_QUATERNION = 0x0010, ///< Quaternion. + COMMONGROUP_ANGULARRATE = 0x0020, ///< Angular Rate. + COMMONGROUP_POSITION = 0x0040, ///< Position. + COMMONGROUP_VELOCITY = 0x0080, ///< Velocity. + COMMONGROUP_ACCEL = 0x0100, ///< Acceleration compensated (body). + COMMONGROUP_IMU = 0x0200, ///< Uncompensated gyro and accelerometer. + COMMONGROUP_MAGPRES = + 0x0400, ///< Compensated magnetic, temperature and pressure. + COMMONGROUP_DELTATHETA = 0x0800, ///< Delta time, theta and velocity. + COMMONGROUP_INSSTATUS = 0x1000, ///< Ins status. + COMMONGROUP_SYNCINCNT = 0x2000, ///< SyncIn count. + COMMONGROUP_TIMEGPSPPS = 0x4000 ///< Time since last GpsPps trigger. +}; + +/** + * @brief Values used to select data for the binary output from group 2 (time + * group). + */ +enum TimeGroup +{ + TIMEGROUP_NONE = 0x0000, ///< None. + TIMEGROUP_TIMESTARTUP = 0x0001, ///< Time since startup (nanoseconds). + TIMEGROUP_TIMEGPS = 0x0002, ///< Gps time. + TIMEGROUP_GPSTOW = 0x0004, ///< Time since start of gps week. + TIMEGROUP_GPSWEEK = 0x0008, ///< Gps week. + TIMEGROUP_TIMESYNCIN = 0x0010, ///< Time since last sync in trigger. + TIMEGROUP_TIMEGPSPPS = 0x0020, ///< Time since last GpsPps trigger. + TIMEGROUP_TIMEUTC = 0x0040, ///< UTC time. + TIMEGROUP_SYNCINCNT = 0x0080, ///< Sync in trigger count. + TIMEGROUP_SYNCOUTCNT = 0x0100, ///< Sync out trigger count. + TIMEGROUP_TIMESTATUS = 0x0200 ///< Time valid status flag. +}; + +/** + * @brief Values used to select data for the binary output from group 3 (imu + * group). + */ +enum ImuGroup +{ + IMUGROUP_NONE = 0x0000, ///< None. + IMUGROUP_IMUSTATUS = 0x0001, ///< Imu status. + IMUGROUP_UNCOMPMAG = 0x0002, ///< Uncompensated magnetic. + IMUGROUP_UNCOMPACCEL = 0x0004, ///< Uncompensated accelerometer. + IMUGROUP_UNCOMPGYRO = 0x0008, ///< Uncompensated gyroscope. + IMUGROUP_TEMP = 0x0010, ///< Temperature. + IMUGROUP_PRES = 0x0020, ///< Pressure. + IMUGROUP_DELTATHETA = 0x0040, ///< Delta theta angles. + IMUGROUP_DELTAVEL = 0x0080, ///< Delta velocity. + IMUGROUP_MAG = 0x0100, ///< Compensated magnetometer. + IMUGROUP_ACCEL = 0x0200, ///< Compensated accelerometer. + IMUGROUP_ANGULARRATE = 0x0400, ///< Compensated gyroscope. +}; + +/** + * @brief Values used to select data for the binary output from group 4 + * and 7 (gps and gps2 group). + */ +enum GpsGroup +{ + GPSGROUP_NONE = 0x0000, ///< None. + GPSGROUP_UTC = 0x0001, ///< Gps UTC time. + GPSGROUP_TOW = 0x0002, ///< Gps time of week. + GPSGROUP_WEEK = 0x0004, ///< Gps week. + GPSGROUP_NUMSATS = 0x0008, ///< Number of tracked satellites. + GPSGROUP_FIX = 0x0010, ///< Gps fix. + GPSGROUP_POSLLA = + 0x0020, ///< Gps position (latitude, longitude, altitude). + GPSGROUP_POSECEF = 0x0040, ///< Gps position (ECEF). + GPSGROUP_VELNED = 0x0080, ///< Gps velocity (NED). + GPSGROUP_VELECEF = 0x0100, ///< Gps velocity (ECEF). + GPSGROUP_POSU = 0x0200, ///< Position uncertainty (NED). + GPSGROUP_VELU = 0x0400, ///< Velocity uncertainty. + GPSGROUP_TIMEU = 0x0800, ///< Time uncertainty. + GPSGROUP_TIMEINFO = 0x1000, ///< Gps time status and leap seconds. + GPSGROUP_DOP = 0x2000, ///< Dilution of precision values. +}; + +/** + * @brief Values used to select data for the binary output from group 5 + * (attitude group). + */ +enum AttitudeGroup +{ + ATTITUDEGROUP_NONE = 0x0000, ///< None. + ATTITUDEGROUP_VPESTATUS = 0x0001, ///< VpeStatus. + ATTITUDEGROUP_YAWPITCHROLL = 0x0002, ///< Yaw pitch roll. + ATTITUDEGROUP_QUATERNION = 0x0004, ///< Quaternion. + ATTITUDEGROUP_DCM = 0x0008, ///< Directional cosine matrix. + ATTITUDEGROUP_MAGNED = 0x0010, ///< Compensated magnetic (NED). + ATTITUDEGROUP_ACCELNED = 0x0020, ///< Compensated acceleration (NED). + ATTITUDEGROUP_LINEARACCELBODY = + 0x0040, ///< Compensated linear acceleration (no gravity). + ATTITUDEGROUP_LINEARACCELNED = + 0x0080, ///< Compensated linear acceleration (no gravity) (NED). + ATTITUDEGROUP_YPRU = 0x0100, ///< Yaw Pitch Roll uncertainty. +}; + +/** + * @brief Values used to select data for the binary output from group 6 (ins + * group). + */ +enum InsGroup +{ + INSGROUP_NONE = 0x0000, ///< None. + INSGROUP_INSSTATUS = 0x0001, ///< Status. + INSGROUP_POSLLA = 0x0002, ///< Position (latitude, longitude, altitude). + INSGROUP_POSECEF = 0x0004, ///< Position (ECEF). + INSGROUP_VELBODY = 0x0008, ///< Velocity (body). + INSGROUP_VELNED = 0x0010, ///< Velocity (NED). + INSGROUP_VELECEF = 0x0020, ///< Velocity (ECEF). + INSGROUP_MAGECEF = 0x0040, ///< Compensated magnetic (ECEF). + INSGROUP_ACCELECEF = 0x0080, ///< Compensated acceleration (ECEF). + INSGROUP_LINEARACCELECEF = + 0x0100, ///< Compensated linear acceleration (ECEF). + INSGROUP_POSU = 0x0200, ///< Position uncertainty. + INSGROUP_VELU = 0x0400, ///< Velocity uncertainty. +}; + +/** + * @brief Structure to handle antenna A position units [m] + */ +struct AntennaPosition +{ + float posX; // Relative position of GPS antenna (X-axis) + float posY; + float posZ; + float uncX; // Uncertainty in the X-axis position measurement + float uncY; + float uncZ; +}; + +/** + * @brief Structure to handle INS (inertial navigation system) data. + */ +struct INSData +{ + uint64_t insTimestamp; + uint16_t gpsFix; + uint16_t insStatus; + float yaw; + float pitch; + float roll; + float latitude; + float longitude; + float altitude; + float velocityX; // Estimated velocity (NED) in m/s + float velocityY; + float velocityZ; +}; + +/** + * @brief Sample options (data output packets) available. + */ +enum class SampleOptions : uint8_t +{ + FULL, ///< All data is sampled + REDUCED, ///< Only data needed for the reduced data packet +}; + +/** + * @brief Structure to handle binary message in case of full sampling (all + * available measurements are taken). + */ +struct __attribute__((packed)) BinaryDataFull +{ + uint8_t selectedGroups; // Indicates which groups are selected + uint16_t selectedFieldsCommonGroup; // Indicates which fields are selected + uint16_t selectedFieldsGnssGroup; // Indicates which fields are selected + float yaw; + float pitch; + float roll; + float quaternionX; + float quaternionY; + float quaternionZ; + float quaternionW; + float angularX; + float angularY; + float angularZ; + float velocityX; // Estimated velocity (NED) in m/s + float velocityY; + float velocityZ; + float accelX; + float accelY; + float accelZ; + float magneticX; + float magneticY; + float magneticZ; + float temperature; + float pressure; + uint16_t insStatus; // Status of the INS + uint8_t numsats; // Number of tracked gps satellites + uint8_t gpsFix; + double latitude; + double longitude; + double altitude; + uint16_t checksum; +}; + +/** + * @brief Structure to handle the reduced data packet. + * Selected data: + * - Common group: + * - Yaw pitch and roll + * - Quaternion + * - Angular rate + * - Gps1: + * - Fix + * - PosLla + */ +struct __attribute__((packed)) BinaryDataReduced +{ + uint8_t selectedGroups; // Indicates which groups are selected + uint16_t selectedFieldsCommonGroup; // Indicates which fields are selected + uint16_t selectedFieldsGnssGroup; // Indicates which fields are selected + float yaw; + float pitch; + float roll; + float quaternionX; + float quaternionY; + float quaternionZ; + float quaternionW; + float angularX; + float angularY; + float angularZ; + uint8_t gpsFix; + double latitude; + double longitude; + double altitude; + uint16_t checksum; +}; + +} // namespace VN300Defs + +} // namespace Boardcore diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.cpp b/src/shared/sensors/Vectornav/VNCommonSerial.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c4859c0b6cead5e27d26d70ce0b0b18d190fffc1 --- /dev/null +++ b/src/shared/sensors/Vectornav/VNCommonSerial.cpp @@ -0,0 +1,503 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "VNCommonSerial.h" + +#include <drivers/timer/TimestampTimer.h> +#include <utils/Debug.h> + +namespace Boardcore +{ + +VNCommonSerial::VNCommonSerial(USART &usart, int baudrate, + const char *sensorName, CRCOptions crc, + const std::chrono::milliseconds timeout) + : usart(usart), baudRate(baudrate), crc(crc), + logger(Logging::getLogger(sensorName)), maxTimeout(timeout) +{ +} + +VNCommonSerial::~VNCommonSerial() {} + +uint8_t VNCommonSerial::calculateChecksum8(const uint8_t *message, int length) +{ + int i; + uint8_t result = 0x00; + + // Iterate and XOR all of the elements + for (i = 0; i < length; i++) + { + //^ = XOR Operation + result ^= message[i]; + } + + return result; +} + +uint16_t VNCommonSerial::calculateChecksum16(const uint8_t *message, int length) +{ + int i; + uint16_t result = 0x0000; + + // Apply the datasheet definition of CRC16-CCITT + for (i = 0; i < length; i++) + { + result = (uint8_t)(result >> 8) | (result << 8); + result ^= message[i]; + result ^= (uint8_t)(result & 0xff) >> 4; + result ^= result << 12; + result ^= (result & 0x00ff) << 5; + } + + return result; +} + +bool VNCommonSerial::verifyChecksum(char *command, int length) +{ + int checksumOffset = 0; + + // I look for the checksum position + while (checksumOffset < length && command[checksumOffset] != '*') + { + checksumOffset++; + } + + if (checksumOffset == length) + { + // The command doesn't have any checksum + TRACE("No checksum in the command!\n"); + return false; + } + + // Check based on the user selected crc type + if (crc == CRCOptions::CRC_ENABLE_16) + { + if (length != checksumOffset + 5) // 4 hex chars + 1 of position + { + TRACE("16 bit Checksum wrong length: %d != %d --> %s\n", length, + checksumOffset + 5, command); + return false; + } + + // Calculate the checksum and verify (comparison between numerical + // checksum to avoid string bugs e.g 0856 != 865) + if (strtol(command + checksumOffset + 1, NULL, 16) != + calculateChecksum16((uint8_t *)(command + 1), checksumOffset - 1)) + { + TRACE("Different checksum: %s\n", command); + return false; + } + } + else if (crc == CRCOptions::CRC_ENABLE_8) + { + if (length != checksumOffset + 3) // 2 hex chars + 1 of position + { + TRACE("8 bit Checksum wrong length: %d != %d --> %s\n", length, + checksumOffset + 3, command); + return false; + } + + // Calculate the checksum and verify (comparison between numerical + // checksum to avoid string bugs e.g 0856 != 865) + if (strtol(command + checksumOffset + 1, NULL, 16) != + calculateChecksum8((uint8_t *)(command + 1), checksumOffset - 1)) + { + TRACE("Different checksum: %s\n", command); + return false; + } + } + + return true; +} + +bool VNCommonSerial::disableAsyncMessages(bool waitResponse) +{ + // Command string + std::string command = "VNWRG,06,00"; + + // Clear the receiving queue + usart.clearQueue(); + + if (!sendStringCommand(command)) + { + return false; + } + + if (waitResponse) + { + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + + if (checkErrorVN(recvString.data())) + { + return false; + } + } + + return true; +} + +bool VNCommonSerial::setCrc(bool waitResponse) +{ + // Command for the crc change + std::string command; + CRCOptions backup = crc; + + // Check what type of crc is selected + if (crc == CRCOptions::CRC_ENABLE_16) + { + // The 3 inside the command is the 16bit select. The others are default + // values + command = "VNWRG,30,0,0,0,0,3,0,1"; + } + else + { + // Even if the CRC is not enabled i put the 8 bit + // checksum because i need to know how many 'X' add at the end + // of every command sent + command = "VNWRG,30,0,0,0,0,1,0,1"; + } + + // I need to send the command in both crc because i don't know what type + // of crc is previously selected. So in order to get the command accepted + // i need to do it two times with different crc. + crc = CRCOptions::CRC_ENABLE_8; + + // Send the command + if (!sendStringCommand(command)) + { + return false; + } + + // Read the answer + if (waitResponse) + { + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + } + + crc = CRCOptions::CRC_ENABLE_16; + + // Send the command + if (!sendStringCommand(command)) + { + return false; + } + + // Read the answer + if (waitResponse) + { + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + return false; + } + } + + // Restore the crc + crc = backup; + + return true; +} + +void VNCommonSerial::configDefaultSerialPort() +{ + // Initial default settings + usart.setBaudrate(DEFAULT_BAUDRATE); +} + +bool VNCommonSerial::configUserSerialPort() +{ + std::string command; + + // I format the command to change baud rate + command = fmt::format("{}{}", "VNWRG,5,", baudRate); + + // I can send the command + if (!sendStringCommand(command)) + { + return false; + } + + // I can open the serial with user's baud rate + usart.setBaudrate(baudRate); + + // Check correct serial init + return true; +} + +bool VNCommonSerial::verifyModelNumber(const char *expectedModelNumber) +{ + // The model number starts from the 10th position + const int modelNumberOffset = 10; + + // Clear the receiving queue + usart.clearQueue(); + + if (!sendStringCommand("VNRRG,01")) + { + LOG_WARN(logger, "Unable to send string command"); + return false; + } + + miosix::Thread::sleep(100); + + if (!recvStringCommand(recvString.data(), recvStringMaxDimension)) + { + LOG_WARN(logger, "Unable to receive string command"); + return false; + } + + if (strncmp(expectedModelNumber, recvString.data() + modelNumberOffset, + strlen(expectedModelNumber)) != 0) + { + LOG_ERR(logger, "Model number not corresponding: {} != {}", + recvString.data(), expectedModelNumber); + return false; + } + + if (!verifyChecksum(recvString.data(), recvStringLength)) + { + LOG_ERR(logger, "Checksum verification failed: {}", recvString.data()); + return false; + } + + return true; +} + +QuaternionData VNCommonSerial::sampleQuaternion() +{ + unsigned int indexStart = 0; + char *nextNumber; + QuaternionData data; + + // Look for the second ',' in the string + // I can avoid the string control because it has already been done in + // sampleImpl + for (int i = 0; i < 2; i++) + { + while (indexStart < recvStringLength && recvString[indexStart] != ',') + { + indexStart++; + } + indexStart++; + } + + // Parse the data + data.quaternionTimestamp = TimestampTimer::getTimestamp(); + data.quaternionX = strtod(recvString.data() + indexStart + 1, &nextNumber); + data.quaternionY = strtod(nextNumber + 1, &nextNumber); + data.quaternionZ = strtod(nextNumber + 1, &nextNumber); + data.quaternionW = strtod(nextNumber + 1, NULL); + + return data; +} + +MagnetometerData VNCommonSerial::sampleMagnetometer() +{ + unsigned int indexStart = 0; + char *nextNumber; + MagnetometerData data; + + // Look for the sixth ',' in the string + // I can avoid the string control because it has already been done in + // sampleImpl + for (int i = 0; i < 6; i++) + { + while (indexStart < recvStringLength && recvString[indexStart] != ',') + { + indexStart++; + } + indexStart++; + } + + // Parse the data + data.magneticFieldTimestamp = TimestampTimer::getTimestamp(); + data.magneticFieldX = + strtod(recvString.data() + indexStart + 1, &nextNumber); + data.magneticFieldY = strtod(nextNumber + 1, &nextNumber); + data.magneticFieldZ = strtod(nextNumber + 1, NULL); + + return data; +} + +AccelerometerData VNCommonSerial::sampleAccelerometer() +{ + unsigned int indexStart = 0; + char *nextNumber; + AccelerometerData data; + + // Look for the ninth ',' in the string + // I can avoid the string control because it has already been done in + // sampleImpl + for (int i = 0; i < 9; i++) + { + while (indexStart < recvStringLength && recvString[indexStart] != ',') + { + indexStart++; + } + indexStart++; + } + + // Parse the data + data.accelerationTimestamp = TimestampTimer::getTimestamp(); + data.accelerationX = + strtod(recvString.data() + indexStart + 1, &nextNumber); + data.accelerationY = strtod(nextNumber + 1, &nextNumber); + data.accelerationZ = strtod(nextNumber + 1, NULL); + + return data; +} + +GyroscopeData VNCommonSerial::sampleGyroscope() +{ + unsigned int indexStart = 0; + char *nextNumber; + GyroscopeData data; + + // Look for the twelfth ',' in the string + // I can avoid the string control because it has already been done in + // sampleImpl + for (int i = 0; i < 12; i++) + { + while (indexStart < recvStringLength && recvString[indexStart] != ',') + { + indexStart++; + } + indexStart++; + } + + // Parse the data + data.angularSpeedTimestamp = TimestampTimer::getTimestamp(); + data.angularSpeedX = + strtod(recvString.data() + indexStart + 1, &nextNumber); + data.angularSpeedY = strtod(nextNumber + 1, &nextNumber); + data.angularSpeedZ = strtod(nextNumber + 1, NULL); + + return data; +} + +uint8_t VNCommonSerial::checkErrorVN(const char *message) +{ + if (strncmp(message, "$VNERR,", 7) == 0) + { + // Extract the error code + int errorCode = atoi(&message[7]); + return errorCode; // Error detected + } + + return 0; // No error detected +} + +bool VNCommonSerial::closeAndReset() +{ + // Send the reset command to the VN300 + if (!sendStringCommand("VNRST")) + { + LOG_WARN(logger, "Impossible to reset the VN300"); + return false; + } + + isInit = false; + + return true; +} + +bool VNCommonSerial::sendStringCommand(std::string command) +{ + if (crc == CRCOptions::CRC_ENABLE_8) + { + char checksum[4]; // 2 hex + \n + \0 + // I convert the calculated checksum in hex using itoa + itoa(calculateChecksum8((uint8_t *)command.c_str(), command.length()), + checksum, 16); + checksum[2] = '\n'; + checksum[3] = '\0'; + // I concatenate + command = fmt::format("{}{}{}{}", "$", command, "*", checksum); + } + else if (crc == CRCOptions::CRC_ENABLE_16) + { + char checksum[6]; // 4 hex + \n + \0 + // I convert the calculated checksum in hex using itoa + itoa(calculateChecksum16((uint8_t *)command.c_str(), command.length()), + checksum, 16); + checksum[4] = '\n'; + checksum[5] = '\0'; + // I concatenate + command = fmt::format("{}{}{}{}", "$", command, "*", checksum); + } + else + { + // No checksum, i add only 'XX' at the end and not 'XXXX' because + // in cas of CRC_NO the enabled crc is 8 bit + command = fmt::format("{}{}{}", "$", command, "*XX\n"); + } + + // I send the final command + usart.writeString(command.c_str()); + + /** + * Wait enough to let the message reach the sensor. The wait time is + * expressed in milliseconds. The baudrate is expressed in bps. + * As a safety measure 10ms are added to the wait time. + * + * Thus the command size is multiplied by 1000 to obtain milliseconds and by + * 8 because the baudrate is expressed in bit per second. + */ + float waitTime = command.size() * 8000; + waitTime /= baudRate; + waitTime = ceil(waitTime); + waitTime += 10; + miosix::Thread::sleep(waitTime); + + return true; +} + +bool VNCommonSerial::recvStringCommand(char *command, int maxLength) +{ + int i = 0; + // Read the buffer + if (!usart.readBlocking(command, maxLength, maxTimeout)) + { + return false; + } + + // Iterate until i reach the end or i find \n then i substitute it with a \0 + while (i < maxLength && command[i] != '\n') + { + i++; + } + + // Terminate the string + command[i] = '\0'; + + // Assing the length + recvStringLength = i - 1; + + return true; +} + +} // namespace Boardcore diff --git a/src/shared/sensors/Vectornav/VNCommonSerial.h b/src/shared/sensors/Vectornav/VNCommonSerial.h new file mode 100644 index 0000000000000000000000000000000000000000..08c64c3d50238b225928915d5034901942d44e7d --- /dev/null +++ b/src/shared/sensors/Vectornav/VNCommonSerial.h @@ -0,0 +1,271 @@ +/* Copyright (c) 2024 Skyward Experimental Rocketry + * Author: Matteo Pignataro, Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include <diagnostic/PrintLogger.h> +#include <drivers/usart/USART.h> +#include <sensors/SensorData.h> + +namespace Boardcore +{ + +class VNCommonSerial +{ +public: + enum class CRCOptions : uint8_t + { + CRC_NO = 0x00, + CRC_ENABLE_8 = 0x08, + CRC_ENABLE_16 = 0x10 + }; + + /** + * @brief Constructor. + * + * @param usart Serial bus used for the sensor. + * @param BaudRate Selectd baudrate. + * @param sensorName The name of the sensor (VN100/VN300/...). + * @param crc Checksum option. + * @param timeout The maximum time that will be waited when reading from the + * sensor. + */ + VNCommonSerial(USART &usart, int baudrate, const char *sensorName, + CRCOptions crc, const std::chrono::milliseconds timeout); + + ~VNCommonSerial(); + + /** + * @brief Method to reset the sensor to default values and to close + * the connection. Used if you need to close and re initialize the sensor. + * + * @return True if operation succeeded. + */ + bool closeAndReset(); + +protected: + /** + * @brief Calculate the 8bit checksum on the given array. + * + * @param command Command on which compute the crc. + * @param length Array length. + * + * @return The 8 bit checksum. + */ + uint8_t calculateChecksum8(const uint8_t *message, int length); + + /** + * @brief Calculate the 16bit array on the given array. + * + * @param command Command on which compute the crc. + * @param length Array length. + * + * @return The 16 bit CRC16-CCITT error check. + */ + uint16_t calculateChecksum16(const uint8_t *message, int length); + + /** + * @brief Method to verify the crc validity of a command. + * + * @param command The char array which contains the command. + * @param maxLength Maximum length for the command array. + * + * @return True if operation succeeded. + */ + bool verifyChecksum(char *command, int maxLength); + + /** + * @brief Disables the async messages that the sensor is default configured + * to send at 40Hz on startup. + * + * @param waitResponse If true wait for a serial response. + * + * @return True if operation succeeded. + */ + bool disableAsyncMessages(bool waitResponse); + + /** + * @brief Sets the user selected crc method. + * + * @param waitResponse If true wait for a serial response. + * + * @return True if operation succeeded. + */ + bool setCrc(bool waitResponse = true); + + /** + * @brief Configures the default serial communication. + */ + void configDefaultSerialPort(); + + /** + * @brief Configures the user defined serial communication. + * + * @return True if operation succeeded. + */ + bool configUserSerialPort(); + + /** + * @brief Verify the model number of the sensor. + * + * @param expectedModelNumber The expected model number. + * + * @return True if the model number received from the sensor corresponds + * with the expected one. + */ + bool verifyModelNumber(const char *expectedModelNumber); + + /** + * @brief Utility function used to retrieve the binary output from the + * sensor. + * + * @param binaryData The variable that will hold the data. + * @param sampleCommand The command to be sent to sample data. + * @return True if operation successful, false otherwise. + */ + template <typename T> + bool getBinaryOutput(T &binaryData, const char *const sampleCommand); + + /** + * @brief Utility function used to extract quaternion data from the + * receiving string. + */ + QuaternionData sampleQuaternion(); + + /** + * @brief Utility function used to extract magnetometer data from the + * receiving string. + */ + MagnetometerData sampleMagnetometer(); + + /** + * @brief Utility function used to extract accelerometer data from the + * receiving string. + */ + AccelerometerData sampleAccelerometer(); + + /** + * @brief Utility function used to extract gyroscope data from the receiving + * string. + */ + GyroscopeData sampleGyroscope(); + + /** + * @brief Check if the message received from the sensor contains an error. + * + * @param message The message to be checked. + * + * @return Returns 0 if no error was found, else returns the actual error + * code. + */ + uint8_t checkErrorVN(const char *message); + + /** + * @brief Sends the command to the sensor with the correct checksum added + * so '*' symbol is not needed at the end of the string as well as the '$' + * at the beginning of the command. This function takes into account the + * time needed for the command to reach the sensor. DO NOT USE THIS FUNCTION + * IF LOW EXECUTION TIME IS NEEDED (for example when sending the sample + * command). + * + * @param command Command to send. + * + * @return True if operation succeeded. + */ + bool sendStringCommand(std::string command); + + /** + * @brief Receives a command from the sensor but + * swaps the first \n with a \0 to close the message. + * + * @param command The char array which will be filled with the command. + * @param maxLength Maximum length for the command array. + * + * @return True if operation succeeded. + */ + bool recvStringCommand(char *command, int maxLength); + + /** + * @brief Serial interface that is needed to communicate + * with the sensor via ASCII codes. + */ + USART &usart; + int baudRate; + + CRCOptions crc; + + PrintLogger logger; + + /** + * @brief Default baudrate value for the usart communication. + */ + static const int DEFAULT_BAUDRATE = 115200; + + /** + * @brief Maximum size of the receiving string. + */ + static const uint8_t recvStringMaxDimension = 200; + + /** + * @brief Buffer used to store the string received from the sensor. + */ + std::array<char, recvStringMaxDimension> recvString; + + /** + * @brief Actual strlen() of the recvString. + */ + uint8_t recvStringLength = 0; + + bool isInit = false; + +private: + /** + * @brief The maximum time that will be waited during + * a read from the serial channel. + */ + const std::chrono::milliseconds maxTimeout; +}; + +template <typename T> +bool VNCommonSerial::getBinaryOutput(T &binaryData, + const char *const sampleCommand) +{ + uint8_t initByte = 0; + + // Remove any junk + usart.clearQueue(); + + usart.writeString(sampleCommand); + + // Check the read of the 0xFA byte to find the start of the message + if (usart.readBlocking(&initByte, 1, maxTimeout) && initByte == 0xFA) + { + if (usart.readBlocking(&binaryData, sizeof(T), maxTimeout)) + { + return true; + } + } + + return false; +} + +} // namespace Boardcore diff --git a/src/tests/sensors/test-vn100-serial.cpp b/src/tests/sensors/test-vn100-serial.cpp index c4aa36e8728d16ae0033b1ea56b26519a6943fef..9d11d0f125ad99680e0adb1b6713cb6ba3e96e8c 100644 --- a/src/tests/sensors/test-vn100-serial.cpp +++ b/src/tests/sensors/test-vn100-serial.cpp @@ -22,7 +22,7 @@ #include <drivers/timer/TimestampTimer.h> #include <inttypes.h> -#include <sensors/VN100/VN100Serial.h> +#include <sensors/Vectornav/VN100/VN100Serial.h> using namespace miosix; using namespace Boardcore; diff --git a/src/tests/sensors/test-vn100-spi.cpp b/src/tests/sensors/test-vn100-spi.cpp index dd0a7e091cb5b55e3d05da3ad47da15b6c92fca9..3750babb55488a881a7bfbfbd18fad2abe719983 100644 --- a/src/tests/sensors/test-vn100-spi.cpp +++ b/src/tests/sensors/test-vn100-spi.cpp @@ -21,7 +21,7 @@ */ #include <drivers/spi/SPIDriver.h> -#include <sensors/VN100/VN100Spi.h> +#include <sensors/Vectornav/VN100/VN100Spi.h> using namespace miosix; using namespace Boardcore; diff --git a/src/tests/sensors/test-vn300.cpp b/src/tests/sensors/test-vn300.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9d5cbaaffb9ffd257d6cf0f425cded50708e891 --- /dev/null +++ b/src/tests/sensors/test-vn300.cpp @@ -0,0 +1,90 @@ +/* Copyright (c) 2023 Skyward Experimental Rocketry + * Author: Lorenzo Cucchi, Fabrizio Monti + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <sensors/Vectornav/VN300/VN300.h> + +using namespace miosix; +using namespace Boardcore; + +int main() +{ + VN300Data sample; + + GpioPin u6tx1(GPIOA_BASE, 2); + GpioPin u6rx1(GPIOA_BASE, 3); + + u6rx1.alternateFunction(7); + u6rx1.mode(Mode::ALTERNATE); + u6tx1.alternateFunction(7); + u6tx1.mode(Mode::ALTERNATE); + + const int baud = 921600; + USART usart(USART2, baud); + VN300 sensor(usart, baud, VN300Defs::SampleOptions::FULL, + VN300::CRCOptions::CRC_ENABLE_8, + std::chrono::milliseconds(10)); + + // Let the sensor start up + Thread::sleep(1000); + + printf("Initializing sensor\n"); + if (!sensor.init()) + { + printf("Error initializing the sensor!\n"); + return 0; + } + + printf("Running self-test\n"); + if (!sensor.selfTest()) + { + printf("Unable to execute self-test\n"); + return 0; + } + + // Sample and print 10 samples + for (int i = 0; i < 100; i++) + { + sensor.sample(); + sample = sensor.getLastSample(); + + printf("sample %d:\n", i + 1); + printf("acc: %f, %f, %f\n", sample.accelerationX, sample.accelerationY, + sample.accelerationZ); + printf("gyr: %f, %f, %f\n", sample.angularSpeedX, sample.angularSpeedY, + sample.angularSpeedZ); + printf("magn: %f, %f, %f\n", sample.magneticFieldX, + sample.magneticFieldY, sample.magneticFieldZ); + printf("attitude: %f, %f, %f\n", sample.yaw, sample.pitch, sample.roll); + printf("quaternion: %f, %f, %f, %f\n", sample.quaternionW, + sample.quaternionX, sample.quaternionY, sample.quaternionZ); + printf("latitude: %f\n", sample.latitude); + printf("longitude: %f\n", sample.longitude); + printf("gps-fix: %u\n\n", sample.gpsFix); + + Thread::sleep(500); + } + + sensor.closeAndReset(); + printf("Sensor communication closed!\n"); + + return 0; +}