diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index a41cd2089e3c3d93d8ae7ebeae9a6546e2645f57..67b3b9ff613f4759e09a39a6f2d99f273dc2df33 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -51,8 +51,7 @@ "${workspaceFolder}/src/tests" ], "limitSymbolsToIncludedHeaders": true - }, - "compileCommands": "${workspaceFolder}/build/compile_commands.json" + } }, { "name": "stm32f429zi_stm32f4discovery", @@ -115,7 +114,7 @@ "defines": [ "DEBUG", "_ARCH_CORTEXM4_STM32F4", - "_BOARD_STM32F429ZI_SKYWARD_DEATH_STACK_X", + "_BOARD_STM32F429ZI_SKYWARD_DEATHST_X", "_MIOSIX_BOARDNAME=stm32f429zi_skyward_death_stack_x", "HSE_VALUE=8000000", "SYSCLK_FREQ_168MHz=168000000", diff --git a/.vscode/settings.json b/.vscode/settings.json index 43e514f2603ebb9bb2b90fdab6c1c80067e2f17d..e1a98af224ab9e9c71af4a5ba32200cd6d082039 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -126,6 +126,7 @@ "Baro", "boardcore", "Boardcorev", + "boudrate", "Corigliano", "CORTEXM", "cpitch", @@ -149,6 +150,7 @@ "fiprintf", "Gatttr", "getdetahstate", + "GNSS", "Gpio", "GPIOA", "GPIOB", @@ -183,6 +185,7 @@ "NDTR", "NGPS", "NMAG", + "NMEA", "NMEKF", "nord", "NVIC", @@ -215,6 +218,9 @@ "testsuite", "TSCPP", "Ublox", + "UBXACK", + "UBXGPS", + "UBXNAV", "Unsync", "upcounter", "USART", diff --git a/CMakeLists.txt b/CMakeLists.txt index 53273bb8481f7b3ac5c2385fd4505fd29a0f90fe..26363322d4e7e677b86a8fc4fdb4e8264736d062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,12 +349,11 @@ sbs_target(test-mpu9250 stm32f429zi_parafoil) add_executable(test-ms5803 src/tests/sensors/test-ms5803.cpp) sbs_target(test-ms5803 stm32f429zi_skyward_death_stack_x) -add_executable(test-ubloxgps-serial src/tests/sensors/test-ubloxgps.cpp) -sbs_target(test-ubloxgps-serial stm32f429zi_skyward_death_stack_x) +add_executable(test-ubxgps-serial src/tests/sensors/test-ubxgps-serial.cpp) +sbs_target(test-ubxgps-serial stm32f429zi_skyward_death_stack_x) -add_executable(test-ubloxgps-spi src/tests/sensors/test-ubloxgps.cpp) -target_compile_definitions(test-ubloxgps-spi PRIVATE USE_SPI) -sbs_target(test-ubloxgps-spi stm32f407vg_stm32f4discovery) +add_executable(test-ubxgps-spi src/tests/sensors/test-ubxgps-spi.cpp) +sbs_target(test-ubxgps-spi stm32f429zi_skyward_death_stack_x) add_executable(test-vn100 src/tests/sensors/test-vn100.cpp) sbs_target(test-vn100 stm32f407vg_stm32f4discovery) diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake index 2fdea04d4a7b2628845b1bcfc62952433eee4e2b..a38c686066fed81df16df3fd247bf487af8f56e9 100644 --- a/cmake/boardcore.cmake +++ b/cmake/boardcore.cmake @@ -83,7 +83,8 @@ foreach(OPT_BOARD ${BOARDS}) ${SBS_BASE}/src/shared/sensors/MS5803/MS5803.cpp ${SBS_BASE}/src/shared/sensors/SensorManager.cpp ${SBS_BASE}/src/shared/sensors/SensorSampler.cpp - ${SBS_BASE}/src/shared/sensors/UbloxGPS/UbloxGPS.cpp + ${SBS_BASE}/src/shared/sensors/UBXGPS/UBXGPSSerial.cpp + ${SBS_BASE}/src/shared/sensors/UBXGPS/UBXGPSSpi.cpp ${SBS_BASE}/src/shared/sensors/VN100/VN100.cpp # Calibration diff --git a/src/shared/drivers/spi/SPI.h b/src/shared/drivers/spi/SPI.h index 07761041ff0f5079edfa690bc301a76e62068dbb..b1456fae0ff9e4965a856a121dad12b09024f3f6 100644 --- a/src/shared/drivers/spi/SPI.h +++ b/src/shared/drivers/spi/SPI.h @@ -485,10 +485,10 @@ inline uint16_t SPI::transfer(uint16_t data) inline void SPI::transfer(uint8_t *data, size_t nBytes) { - // Cleaer the RX buffer + // Clear the RX buffer (void)spi->DR; - // Write the first data item to transmit + // Write the first data item to transmitted spi->DR = data[0]; for (size_t i = 1; i < nBytes; i++) @@ -508,26 +508,30 @@ inline void SPI::transfer(uint8_t *data, size_t nBytes) data[i - 1] = static_cast<uint8_t>(spi->DR); } - // Wait until data is received - // while ((spi->SR & SPI_SR_RXNE) == 0) - // ; - while (spi->SR & SPI_SR_BSY) + // Wait until the last data item is received + while ((spi->SR & SPI_SR_RXNE) == 0) ; // Read the last received data item data[nBytes - 1] = static_cast<uint8_t>(spi->DR); + + // Wait until TXE=1 and then wait until BSY=0 before concluding + while ((spi->SR & SPI_SR_TXE) == 0) + ; + while (spi->SR & SPI_SR_BSY) + ; } inline void SPI::transfer(uint16_t *data, size_t nBytes) { - // Cleaer the RX buffer + // Clear the RX buffer (void)spi->DR; // Set 16 bit frame format set16BitFrameFormat(); - // Write the first data item to transmit - spi->DR = data[0]; + // Write the first data item to transmitted + spi->DR = static_cast<uint16_t>(data[0]); for (size_t i = 1; i < nBytes / 2; i++) { @@ -546,15 +550,19 @@ inline void SPI::transfer(uint16_t *data, size_t nBytes) data[i - 1] = static_cast<uint16_t>(spi->DR); } - // Wait until data is received - // while ((spi->SR & SPI_SR_RXNE) == 0) - // ; - while (spi->SR & SPI_SR_BSY) + // Wait until the last data item is received + while ((spi->SR & SPI_SR_RXNE) == 0) ; // Read the last received data item data[nBytes / 2 - 1] = static_cast<uint16_t>(spi->DR); + // Wait until TXE=1 and then wait until BSY=0 before concluding + while ((spi->SR & SPI_SR_TXE) == 0) + ; + while (spi->SR & SPI_SR_BSY) + ; + // Go back to 8 bit frame format set8BitFrameFormat(); } diff --git a/src/shared/logger/LogTypes.h b/src/shared/logger/LogTypes.h index 4df3ac83cf0921490e029d276f9e26510322d316..741e0113bd37b467a30d2f72857caf59253db7eb 100644 --- a/src/shared/logger/LogTypes.h +++ b/src/shared/logger/LogTypes.h @@ -46,7 +46,7 @@ #include <sensors/MPU9250/MPU9250Data.h> #include <sensors/MS5803/MS5803Data.h> #include <sensors/SensorData.h> -#include <sensors/UbloxGPS/UbloxGPSData.h> +#include <sensors/UBXGPS/UBXGPSData.h> #include <sensors/VN100/VN100Data.h> #include <sensors/analog/BatteryVoltageSensorData.h> #include <sensors/analog/CurrentSensorData.h> @@ -97,7 +97,7 @@ void registerTypes(Deserializer& ds) ds.registerType<MPU9250Data>(); ds.registerType<MS5803Data>(); ds.registerType<TemperatureData>(); - ds.registerType<UbloxGPSData>(); + ds.registerType<UBXGPSData>(); ds.registerType<VN100Data>(); ds.registerType<BatteryVoltageSensorData>(); ds.registerType<CurrentSensorData>(); diff --git a/src/shared/sensors/SensorData.h b/src/shared/sensors/SensorData.h index d40024e73622d384aacdd9bddf89b3a76307e217..2a2a2ecb08270a9b4e107871e0480b4bb7b42f79 100644 --- a/src/shared/sensors/SensorData.h +++ b/src/shared/sensors/SensorData.h @@ -134,16 +134,17 @@ struct MagnetometerData struct GPSData { uint64_t gpsTimestamp; - float latitude; /**< [deg] */ - float longitude; /**< [deg] */ - float height; /**< [m] */ - float velocityNorth; /**< [m/s] */ - float velocityEast; /**< [m/s] */ - float velocityDown; /**< [m/s] */ - float speed; /**< [m/s] */ - float track; /**< [deg] */ - uint8_t satellites; /**< [1] */ - bool fix; + float latitude; // [deg] + float longitude; // [deg] + float height; // [m] + float velocityNorth; // [m/s] + float velocityEast; // [m/s] + float velocityDown; // [m/s] + float speed; // [m/s] + float track; // [deg] + float positionDOP; // [?] + uint8_t satellites; // [1] + uint8_t fix; // 0 = no fix }; /** diff --git a/src/shared/sensors/UBXGPS/UBXFrame.h b/src/shared/sensors/UBXGPS/UBXFrame.h new file mode 100644 index 0000000000000000000000000000000000000000..46437171199cdad96bf4477b06d8629969f4dfda --- /dev/null +++ b/src/shared/sensors/UBXGPS/UBXFrame.h @@ -0,0 +1,309 @@ +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Authors: Damiano Amatruda, Alberto Nidasio + * + * 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 <stdint.h> + +#include <algorithm> +#include <cstring> + +namespace Boardcore +{ + +/** + * @brief UBX messages enumeration. + */ +enum class UBXMessage : uint16_t +{ + UBX_NAV_PVT = 0x0701, // Navigation position velocity time solution + UBX_ACK_NAK = 0x0005, // Message acknowledged + UBX_ACK_ACK = 0x0105, // Message not acknowledged + UBX_CFG_PRT = 0x0006, // Port configuration + UBX_CFG_MSG = 0x0106, // Set message rate + UBX_CFG_RST = 0x0406, // Reset receiver + UBX_CFG_RATE = 0x0806, // Navigation/measurement rate settings + UBX_CFG_NAV5 = 0x2406, // Navigation engine settings + UBX_CFG_VALSET = 0x8a06, // TODO: Update to newer command +}; + +/** + * @brief Generic UBX frame. + */ +struct UBXFrame +{ + static constexpr uint16_t MAX_PAYLOAD_LENGTH = 92; + static constexpr uint16_t MAX_FRAME_LENGTH = MAX_PAYLOAD_LENGTH + 8; + static constexpr uint8_t PREAMBLE[] = {0xb5, 0x62}; + static constexpr uint8_t WAIT = 0xff; + + uint8_t preamble[2]; + uint16_t message; + uint16_t payloadLength; + uint8_t payload[MAX_PAYLOAD_LENGTH]; + uint8_t checksum[2]; + + UBXFrame() = default; + + /** + * @brief Construct a new UBXFrame with the specified message and given + * payload. + */ + UBXFrame(UBXMessage message, const uint8_t* payload, + uint16_t payloadLength); + + /** + * @brief Return the total frame length. + */ + uint16_t getLength() const; + + /** + * @brief Return the stored payload length. + */ + uint16_t getRealPayloadLength() const; + + UBXMessage getMessage() const; + + /** + * @brief Tells whether the current frame is valid or not. Checks the + * preamble and the checksum. + */ + bool isValid() const; + + /** + * @brief Writes the current message into the given array. + * + * @param frame Must be an array of the proper length. + */ + void writePacked(uint8_t* frame) const; + + /** + * @brief Reads a raw frame. + * + * Note that the frame bytes are assumed to start with the preamble. + * + * @param frame An array of length at least MAX_FRAME_LENGTH. + */ + void readPacked(const uint8_t* frame); + + /** + * @brief Computes the frame checksum. + * + * @param checksum Array of 2 elements where to store the checksum + */ + void calcChecksum(uint8_t* checksum) const; +}; + +/** + * @brief UBX frames UBX-ACK-ACK and UBX-ACK-NAK. + */ +struct UBXAckFrame : public UBXFrame +{ + /** + * @brief Payload of UBX frames UBX-ACK-ACK and UBX-ACK-NAK. + */ + struct __attribute__((packed)) Payload + { + uint16_t ackMessage; + }; + + Payload& getPayload() const; + + UBXMessage getAckMessage() const; + + /** + * @brief Tells whether the frame is an ack. + */ + bool isAck() const; + + /** + * @brief Tells whether the frame is a nak. + */ + bool isNack() const; + + /** + * @brief Tells whether the frame is an ack frame. + */ + bool isValid() const; +}; + +/** + * @brief UBX frame UBX-NAV-PVT. + */ +struct UBXPvtFrame : public UBXFrame +{ +public: + /** + * @brief Payload of UBX frame UBX-NAV-PVT. + */ + struct __attribute__((packed)) Payload + { + uint32_t iTOW; // GPS time of week of the navigation epoch [ms] + uint16_t year; // Year (UTC) [y] + uint8_t month; // Month, range 1..12 (UTC) [month] + uint8_t day; // Day of month, range 1..31 (UTC) [d] + uint8_t hour; // Hour of day, range 0..23 (UTC) [h] + uint8_t min; // Minute of hour, range 0..59 (UTC) [min] + uint8_t sec; // Seconds of minute, range 0..60 (UTC) [s] + uint8_t valid; // Validity flags + uint32_t tAcc; // Time accuracy estimate (UTC) [ns] + int32_t nano; // Fraction of second, range -1e9 .. 1e9 (UTC) [ns] + uint8_t fixType; // GNSS fix Type + uint8_t flags; // Fix status flags + uint8_t flags2; // Additional flags + uint8_t numSV; // Number of satellites used in Nav Solution + int32_t lon; // Longitude {1e-7} [deg] + int32_t lat; // Latitude {1e-7} [deg] + int32_t height; // Height above ellipsoid [mm] + int32_t hMSL; // Height above mean sea level [mm] + uint32_t hAcc; // Horizontal accuracy estimate [mm] + uint32_t vAcc; // Vertical accuracy estimate [mm] + int32_t velN; // NED north velocity [mm/s] + int32_t velE; // NED east velocity [mm/s] + int32_t velD; // NED down velocity [mm/s] + int32_t gSpeed; // Ground Speed (2-D) [mm/s] + int32_t headMot; // Heading of motion (2-D) {1e-5} [deg] + uint32_t sAcc; // Speed accuracy estimate [mm/s] + uint32_t headAcc; // Heading accuracy estimate (both motion and + // vehicle) {1e-5} [deg] + uint16_t pDOP; // Position DOP {0.01} + uint16_t flags3; // Additional flags + uint8_t reserved0[4]; // Reserved + int32_t headVeh; // Heading of vehicle (2-D) {1e-5} [deg] + int16_t magDec; // Magnetic declination {1e-2} [deg] + uint16_t magAcc; // Magnetic declination accuracy {1e-2} [deg] + }; + + Payload& getPayload() const; + + /** + * @brief Tells whether the frame is an ack frame. + */ + bool isValid() const; +}; + +inline UBXFrame::UBXFrame(UBXMessage message, const uint8_t* payload, + uint16_t payloadLength) + : message(static_cast<uint16_t>(message)), payloadLength(payloadLength) +{ + memcpy(preamble, PREAMBLE, 2); + if (payload != nullptr) + memcpy(this->payload, payload, getRealPayloadLength()); + calcChecksum(checksum); +} + +inline uint16_t UBXFrame::getLength() const { return payloadLength + 8; } + +inline uint16_t UBXFrame::getRealPayloadLength() const +{ + return std::min(payloadLength, MAX_PAYLOAD_LENGTH); +} + +inline UBXMessage UBXFrame::getMessage() const +{ + return static_cast<UBXMessage>(message); +} + +inline bool UBXFrame::isValid() const +{ + if (memcmp(preamble, PREAMBLE, 2) != 0) + return false; + + if (payloadLength > MAX_PAYLOAD_LENGTH) + return false; + + uint8_t validChecksum[2]; + calcChecksum(validChecksum); + return memcmp(checksum, validChecksum, 2) == 0; +} + +inline void UBXFrame::writePacked(uint8_t* frame) const +{ + memcpy(frame, preamble, 2); + memcpy(&frame[2], &message, 2); + memcpy(&frame[4], &payloadLength, 2); + memcpy(&frame[6], payload, getRealPayloadLength()); + memcpy(&frame[6 + payloadLength], checksum, 2); +} + +inline void UBXFrame::readPacked(const uint8_t* frame) +{ + memcpy(preamble, frame, 2); + memcpy(&message, &frame[2], 2); + memcpy(&payloadLength, &frame[4], 2); + memcpy(payload, &frame[6], getRealPayloadLength()); + memcpy(checksum, &frame[6 + payloadLength], 2); +} + +inline void UBXFrame::calcChecksum(uint8_t* checksum) const +{ + uint8_t data[getRealPayloadLength() + 4]; + memcpy(data, &message, 2); + memcpy(&data[2], &payloadLength, 2); + memcpy(&data[4], payload, getRealPayloadLength()); + + checksum[0] = 0; + checksum[1] = 0; + + for (size_t i = 0; i < sizeof(data); ++i) + { + checksum[0] += data[i]; + checksum[1] += checksum[0]; + } +} + +inline UBXAckFrame::Payload& UBXAckFrame::getPayload() const +{ + return (Payload&)payload; +} + +inline UBXMessage UBXAckFrame::getAckMessage() const +{ + return static_cast<UBXMessage>(getPayload().ackMessage); +} + +inline bool UBXAckFrame::isAck() const +{ + return getMessage() == UBXMessage::UBX_ACK_ACK; +} + +inline bool UBXAckFrame::isNack() const +{ + return getMessage() == UBXMessage::UBX_ACK_NAK; +} + +inline bool UBXAckFrame::isValid() const +{ + return UBXFrame::isValid() && (isAck() || isNack()); +} + +inline UBXPvtFrame::Payload& UBXPvtFrame::getPayload() const +{ + return (Payload&)payload; +} + +inline bool UBXPvtFrame::isValid() const +{ + return UBXFrame::isValid() && getMessage() == UBXMessage::UBX_NAV_PVT; +} + +} // namespace Boardcore diff --git a/src/shared/sensors/UbloxGPS/UbloxGPSData.h b/src/shared/sensors/UBXGPS/UBXGPSData.h similarity index 79% rename from src/shared/sensors/UbloxGPS/UbloxGPSData.h rename to src/shared/sensors/UBXGPS/UBXGPSData.h index 7f5570000b78b915a956b96e754d784e08f7611f..ed734d258bd1c0dd79206b8d392562b7009a22bf 100644 --- a/src/shared/sensors/UbloxGPS/UbloxGPSData.h +++ b/src/shared/sensors/UBXGPS/UBXGPSData.h @@ -1,5 +1,5 @@ -/* Copyright (c) 2020 Skyward Experimental Rocketry - * Author: Davide Bonomini +/* Copyright (c) 2020-2022 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Damiano Amatruda * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,20 +27,21 @@ namespace Boardcore { -struct UbloxGPSData : public GPSData +struct UBXGPSData : public GPSData { static std::string header() { - return "gps_timestamp,latitude,longitude,height,velocity_north," - "velocity_east,velocity_down,speed,track,num_satellites,fix\n"; + return "gpsTimestamp,latitude,longitude,height,velocityNorth," + "velocityEast,velocityDown,speed,track,positionDOP,satellites," + "fix\n"; } void print(std::ostream &os) const { os << gpsTimestamp << "," << latitude << "," << longitude << "," << height << "," << velocityNorth << "," << velocityEast << "," - << velocityDown << "," << speed << "," << track << "," - << (int)satellites << "," << (int)fix << "\n"; + << velocityDown << "," << speed << "," << track << "," << positionDOP + << "," << (int)satellites << "," << (int)fix << "\n"; } }; diff --git a/src/shared/sensors/UBXGPS/UBXGPSSerial.cpp b/src/shared/sensors/UBXGPS/UBXGPSSerial.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c63ac353610543a18299cbfedf5739986b268ab --- /dev/null +++ b/src/shared/sensors/UBXGPS/UBXGPSSerial.cpp @@ -0,0 +1,438 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio + * + * 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 "UBXGPSSerial.h" + +#include <diagnostic/StackLogger.h> +#include <drivers/serial.h> +#include <drivers/timer/TimestampTimer.h> +#include <fcntl.h> +#include <filesystem/file_access.h> + +using namespace miosix; + +namespace Boardcore +{ + +UBXGPSSerial::UBXGPSSerial(int baudrate, uint8_t sampleRate, int serialPortNum, + const char* serialPortName, int defaultBaudrate) + : baudrate(baudrate), sampleRate(sampleRate), + serialPortNumber(serialPortNum), serialPortName(serialPortName), + defaultBaudrate(defaultBaudrate) +{ + // Prepare the gps file path with the specified name + strcpy(gpsFilePath, "/dev/"); + strcat(gpsFilePath, serialPortName); +} + +bool UBXGPSSerial::init() +{ + LOG_DEBUG(logger, "Changing device baudrate..."); + + if (!setSerialCommunication()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Error while setting serial communication"); + return false; + } + + LOG_DEBUG(logger, "Resetting the device..."); + + if (!reset()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not reset the device"); + return false; + } + + LOG_DEBUG(logger, "Setting the UBX protocol..."); + + if (!setUBXProtocol()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the UBX protocol"); + return false; + } + + LOG_DEBUG(logger, "Setting the dynamic model..."); + + if (!setDynamicModelToAirborne4g()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the dynamic model"); + return false; + } + + LOG_DEBUG(logger, "Setting the sample rate..."); + + if (!setSampleRate()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the sample rate"); + return false; + } + + LOG_DEBUG(logger, "Setting the PVT message rate..."); + + if (!setPVTMessageRate()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the PVT message rate"); + return false; + } + + return true; +} + +bool UBXGPSSerial::selfTest() { return true; } + +UBXGPSData UBXGPSSerial::sampleImpl() +{ + Lock<FastMutex> l(mutex); + return threadSample; +} + +bool UBXGPSSerial::reset() +{ + uint8_t payload[] = { + 0x00, 0x00, // Hot start + 0x00, // Hardware reset + 0x00 // Reserved + }; + + UBXFrame frame{UBXMessage::UBX_CFG_RST, payload, sizeof(payload)}; + + // The reset message will not be followed by an acknowledgment + if (!writeUBXFrame(frame)) + return false; + + // Do not interact with the module while it is resetting + miosix::Thread::sleep(RESET_SLEEP_TIME); + + return true; +} + +bool UBXGPSSerial::setBaudrate() +{ + uint8_t payload[] = { + 0x00, // Version + 0xff, // All layers + 0x00, 0x00, // Reserved + 0x01, 0x00, 0x52, 0x40, // Configuration item key ID + 0xff, 0xff, 0xff, 0xff, // Value + }; + + // Prepare baudrate + payload[8] = baudrate; + payload[9] = baudrate >> 8; + payload[10] = baudrate >> 16; + payload[11] = baudrate >> 24; + + UBXFrame frame{UBXMessage::UBX_CFG_VALSET, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSerial::setSerialCommunication() +{ + intrusive_ref_ptr<DevFs> devFs = FilesystemManager::instance().getDevFs(); + + // Change the baudrate only if it is different than the default + if (baudrate != defaultBaudrate) + { + // Close the gps file if already opened + devFs->remove(serialPortName); + + // Open the serial port device with the default boudrate + if (!devFs->addDevice(serialPortName, + intrusive_ref_ptr<Device>(new STM32Serial( + serialPortNumber, defaultBaudrate)))) + { + LOG_ERR(logger, + "[gps] Failed to open serial port {0} with baudrate {1} as " + "file {2}", + serialPortNumber, defaultBaudrate, serialPortName); + return false; + } + + // Open the gps file + if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) + { + LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); + return false; + } + + // Change boudrate + if (!setBaudrate()) + { + return false; + }; + + // Close the gps file + if (close(gpsFile) < 0) + { + LOG_ERR(logger, "Failed to close gps file {}", gpsFilePath); + return false; + } + + // Close the serial port + if (!devFs->remove(serialPortName)) + { + LOG_ERR(logger, "Failed to close serial port {} as file {}", + serialPortNumber, serialPortName); + return false; + } + } + + // Reopen the serial port with the configured boudrate + if (!devFs->addDevice(serialPortName, + intrusive_ref_ptr<Device>( + new STM32Serial(serialPortNumber, baudrate)))) + { + LOG_ERR(logger, + "Failed to open serial port {} with baudrate {} as file {}\n", + serialPortNumber, defaultBaudrate, serialPortName); + return false; + } + + // Reopen the gps file + if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) + { + LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); + return false; + } + + return true; +} + +bool UBXGPSSerial::setUBXProtocol() +{ + uint8_t payload[] = { + 0x04, // SPI port + 0x00, // reserved0 + 0x00, 0x00, // txReady + 0x00, 0x00, 0x00, 0x00, // spiMode = 0, ffCnt = 0 (mechanism off) + 0x00, 0x00, 0x00, 0x00, // reserved1 + 0x01, 0x00, // inProtoMask = UBX + 0x01, 0x00, // outProtoMask = UBX + 0x00, 0x00, // flags + 0x00, 0x00 // reserved2 + }; + + UBXFrame frame{UBXMessage::UBX_CFG_PRT, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSerial::setDynamicModelToAirborne4g() +{ + uint8_t payload[] = { + 0x01, 0x00, // Parameters bitmask, apply dynamic model configuration + 0x08, // Dynamic model = airbone 4g + 0x00, // Fix mode + 0x00, 0x00, 0x00, 0x00, // Fixed altitude for 2D mode + 0x00, 0x00, 0x00, 0x00, // Fixed altitude variance for 2D mode + 0x00, // Minimun elevation for a GNSS satellite to be used + 0x00, // Reserved + 0x00, 0x00, // Position DOP mask to use + 0x00, 0x00, // Time DOP mask to use + 0x00, 0x00, // Position accuracy mask + 0x00, 0x00, // Time accuracy mask + 0x00, // Static hold threshold + 0x00, // DGNSS timeout + 0x00, // C/NO threshold number SVs + 0x00, // C/NO threshold + 0x00, 0x00, // Reserved + 0x00, 0x00, // Static hold distance threshold + 0x00, // UTC standard to be used + 0x00, 0x00, 0x00, 0x00, 0x00 // Reserved + }; + + UBXFrame frame{UBXMessage::UBX_CFG_NAV5, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSerial::setSampleRate() +{ + uint8_t payload[] = { + 0x00, 0x00, // Measurement rate + 0x01, 0x00, // One navigation solution per measurement + 0x01, 0x01 // GPS time + }; + + uint16_t sampleRateMs = 1000 / sampleRate; + payload[0] = sampleRateMs; + payload[1] = sampleRateMs >> 8; + + UBXFrame frame{UBXMessage::UBX_CFG_RATE, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSerial::setPVTMessageRate() +{ + uint8_t payload[] = { + 0x01, 0x07, // PVT message + 0x01 // Rate = 1 navigation solution update + }; + + UBXFrame frame{UBXMessage::UBX_CFG_MSG, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSerial::readUBXFrame(UBXFrame& frame) +{ + // Search UBX frame preamble byte by byte + size_t i = 0; + while (i < 2) + { + uint8_t c; + if (read(gpsFile, &c, 1) <= 0) // No more data available + return false; + + if (c == UBXFrame::PREAMBLE[i]) + { + frame.preamble[i++] = c; + } + else if (c == UBXFrame::PREAMBLE[0]) + { + i = 0; + frame.preamble[i++] = c; + } + else + { + i = 0; + LOG_DEBUG(logger, "Received unexpected byte: {:02x} {:#c}", c, c); + } + } + + if (read(gpsFile, &frame.message, 2) <= 0 || + read(gpsFile, &frame.payloadLength, 2) <= 0 || + read(gpsFile, frame.payload, frame.getRealPayloadLength()) <= 0 || + read(gpsFile, frame.checksum, 2) <= 0) + return false; + + if (!frame.isValid()) + { + LOG_ERR(logger, "Received invalid UBX frame"); + return false; + } + + return true; +} + +bool UBXGPSSerial::writeUBXFrame(const UBXFrame& frame) +{ + if (!frame.isValid()) + { + LOG_ERR(logger, "Trying to send an invalid UBX frame"); + return false; + } + + uint8_t packedFrame[frame.getLength()]; + frame.writePacked(packedFrame); + + if (write(gpsFile, packedFrame, frame.getLength()) < 0) + { + LOG_ERR(logger, "Failed to write ubx message"); + return false; + } + + return true; +} + +bool UBXGPSSerial::safeWriteUBXFrame(const UBXFrame& frame) +{ + for (unsigned int i = 0; i < MAX_TRIES; i++) + { + if (i > 0) + LOG_DEBUG(logger, "Retrying (attempt {:#d} of {:#d})...", i + 1, + MAX_TRIES); + + if (!writeUBXFrame(frame)) + return false; + + UBXAckFrame ack; + + if (!readUBXFrame(ack)) + continue; + + if (ack.isNack()) + { + if (ack.getAckMessage() == frame.getMessage()) + LOG_DEBUG(logger, "Received NAK"); + else + LOG_DEBUG(logger, "Received NAK for different UBX frame {:04x}", + static_cast<uint16_t>(ack.getPayload().ackMessage)); + continue; + } + + if (ack.isAck() && ack.getAckMessage() != frame.getMessage()) + { + LOG_DEBUG(logger, "Received ACK for different UBX frame {:04x}", + static_cast<uint16_t>(ack.getPayload().ackMessage)); + continue; + } + + return true; + } + + LOG_ERR(logger, "Gave up after {:#d} tries", MAX_TRIES); + return false; +} + +void UBXGPSSerial::run() +{ + while (!shouldStop()) + { + UBXPvtFrame pvt; + + // Try to read the message + if (!readUBXFrame(pvt)) + { + LOG_DEBUG(logger, "Unable to read a UBX message"); + continue; + } + + UBXPvtFrame::Payload& pvtP = pvt.getPayload(); + + threadSample.gpsTimestamp = + TimestampTimer::getInstance().getTimestamp(); + threadSample.latitude = (float)pvtP.lat / 1e7; + threadSample.longitude = (float)pvtP.lon / 1e7; + threadSample.height = (float)pvtP.height / 1e3; + threadSample.velocityNorth = (float)pvtP.velN / 1e3; + threadSample.velocityEast = (float)pvtP.velE / 1e3; + threadSample.velocityDown = (float)pvtP.velD / 1e3; + threadSample.speed = (float)pvtP.gSpeed / 1e3; + threadSample.track = (float)pvtP.headMot / 1e5; + threadSample.positionDOP = (float)pvtP.pDOP / 1e2; + threadSample.satellites = pvtP.numSV; + threadSample.fix = pvtP.fixType; + + StackLogger::getInstance().updateStack(THID_GPS); + } +} + +} // namespace Boardcore diff --git a/src/shared/sensors/UBXGPS/UBXGPSSerial.h b/src/shared/sensors/UBXGPS/UBXGPSSerial.h new file mode 100644 index 0000000000000000000000000000000000000000..7f8bcc646cac712ac3395a15c6ff15aad9d3503f --- /dev/null +++ b/src/shared/sensors/UBXGPS/UBXGPSSerial.h @@ -0,0 +1,179 @@ +/* Copyright (c) 2021 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio + * + * 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 <ActiveObject.h> +#include <diagnostic/PrintLogger.h> +#include <miosix.h> +#include <sensors/Sensor.h> + +#include "UBXFrame.h" +#include "UBXGPSData.h" + +namespace Boardcore +{ + +/** + * @brief Driver for Ublox GPSs + * + * This driver handles communication and setup with Ublox GPSs. It uses the + * binary UBX protocol to retrieve and parse navigation data quicker than using + * the string based NMEA. + * + * At initialization it configures the device with the specified baudrate, reset + * the configuration and sets up UBX messages and GNSS parameters. + * + * Communication with the device is performed through a file, the driver opens + * the serial port under the filepath /dev/<serialPortName>. + * There is no need for the file to be setted up beforhand. + * + * This driver was written for a NEO-M9N gps with the latest version of UBX. + */ +class UBXGPSSerial : public Sensor<UBXGPSData>, public ActiveObject +{ +public: + /** + * @brief Construct a new UBXGPSSerial object. + * + * @param baudrate Baudrate to communicate with the device (max: 921600, + * min: 4800 for NEO-M9N). + * @param sampleRate GPS sample rate (max: 25 for NEO-M9N). + * @param serialPortNumber Number of the serial port connected to the GPS. + * @param serialPortName Name of the file for the gps device. + * @param defaultBaudrate Startup baudrate (38400 for NEO-M9N). + */ + UBXGPSSerial(int baudrate = 921600, uint8_t sampleRate = 10, + int serialPortNumber = 2, const char *serialPortName = "gps", + int defaultBaudrate = 38400); + + /** + * @brief Sets up the serial port baudrate, disables the NMEA messages, + * configures GNSS options and enables UBX-PVT message. + * + * @return True if the operation succeeded. + */ + bool init() override; + + /** + * @brief Read a single message form the GPS, waits 2 sample cycle. + * + * @return True if a message was sampled. + */ + bool selfTest() override; + +private: + UBXGPSData sampleImpl() override; + + /** + * @brief Resets the device to its default configuration. + * + * @return True if the device reset succeeded. + */ + bool reset(); + + bool setBaudrate(); + + /** + * @brief Sets up the serial port with the correct baudrate. + * + * Opens the serial port with the default baudrate and changes it to the + * value specified in the constructor, then it reopens the serial port. If + * the device is already using the correct baudrate this won't have effect. + * However if the gps is using a different baudrate the diver won't be able + * to communicate. + */ + bool setSerialCommunication(); + + /** + * @brief Enables UBX and disables NMEA on the SPI port. + * + * @return True if the configuration received an acknowledgement. + */ + bool setUBXProtocol(); + + /** + * @brief Configures the dynamic model to airborn 4g. + * + * @return True if the configuration received an acknowledgement. + */ + bool setDynamicModelToAirborne4g(); + + /** + * @brief Configures the navigation solution sample rate. + * + * @return True if the configuration received an acknowledgement. + */ + bool setSampleRate(); + + /** + * @brief Configures the PVT message output rate. + * + * @return True if the configuration received an acknowledgement. + */ + bool setPVTMessageRate(); + + /** + * @brief Reads a UBX frame. + * + * @param frame The received frame. + * @return True if a valid frame was read. + */ + bool readUBXFrame(UBXFrame &frame); + + /** + * @brief Writes a UBX frame. + * + * @param frame The frame to write. + * @return True if the frame is valid. + */ + bool writeUBXFrame(const UBXFrame &frame); + + /** + * @brief Writes a UBX frame and waits for its acknowledgement. + * + * @param frame The frame to write. + * @return True if the frame is valid and acknowledged. + */ + bool safeWriteUBXFrame(const UBXFrame &frame); + + void run() override; + + const int baudrate; // [baud] + const uint8_t sampleRate; // [Hz] + const int serialPortNumber; + const char *serialPortName; + const int defaultBaudrate; // [baud] + + char gpsFilePath[16]; ///< Allows for a filename of up to 10 characters + int gpsFile = -1; + + mutable miosix::FastMutex mutex; + UBXGPSData threadSample{}; + + PrintLogger logger = Logging::getLogger("ubloxgps"); + + static constexpr unsigned int RESET_SLEEP_TIME = 5000; // [ms] + static constexpr unsigned int MAX_TRIES = 5; // [1] +}; + +} // namespace Boardcore diff --git a/src/shared/sensors/UBXGPS/UBXGPSSpi.cpp b/src/shared/sensors/UBXGPS/UBXGPSSpi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..832059373b8c67d9aaff5f75850f75ad5e0e05c7 --- /dev/null +++ b/src/shared/sensors/UBXGPS/UBXGPSSpi.cpp @@ -0,0 +1,358 @@ +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio, Damiano Amatruda + * + * 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 "UBXGPSSpi.h" + +#include <drivers/timer/TimestampTimer.h> +#include <interfaces/endianness.h> + +namespace Boardcore +{ + +constexpr uint16_t UBXFrame::MAX_PAYLOAD_LENGTH; +constexpr uint16_t UBXFrame::MAX_FRAME_LENGTH; +constexpr uint8_t UBXFrame::PREAMBLE[]; +constexpr uint8_t UBXFrame::WAIT; + +constexpr float UBXGPSSpi::MS_TO_TICK; + +constexpr unsigned int UBXGPSSpi::RESET_SLEEP_TIME; +constexpr unsigned int UBXGPSSpi::READ_TIMEOUT; +constexpr unsigned int UBXGPSSpi::MAX_TRIES; + +UBXGPSSpi::UBXGPSSpi(SPIBusInterface& spiBus, miosix::GpioPin spiCs, + SPIBusConfig spiConfig, uint8_t sampleRate) + : spiSlave(spiBus, spiCs, spiConfig), sampleRate(sampleRate) +{ +} + +SPIBusConfig UBXGPSSpi::getDefaultSPIConfig() +{ + /* From data sheet of series NEO-M8: + * "Minimum initialization time" (setup time): 10 us + * "Minimum deselect time" (hold time): 1 ms */ + return SPIBusConfig{SPI::ClockDivider::DIV_32, SPI::Mode::MODE_0, + SPI::BitOrder::MSB_FIRST, 10, 1000}; +} + +uint8_t UBXGPSSpi::getSampleRate() { return sampleRate; } + +bool UBXGPSSpi::init() +{ + LOG_DEBUG(logger, "Resetting the device..."); + + if (!reset()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not reset the device"); + return false; + } + + LOG_DEBUG(logger, "Setting the UBX protocol..."); + + if (!setUBXProtocol()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the UBX protocol"); + return false; + } + + LOG_DEBUG(logger, "Setting the dynamic model..."); + + if (!setDynamicModelToAirborne4g()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the dynamic model"); + return false; + } + + LOG_DEBUG(logger, "Setting the sample rate..."); + + if (!setSampleRate()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the sample rate"); + return false; + } + + LOG_DEBUG(logger, "Setting the PVT message rate..."); + + if (!setPVTMessageRate()) + { + lastError = SensorErrors::INIT_FAIL; + LOG_ERR(logger, "Could not set the PVT message rate"); + return false; + } + + return true; +} + +bool UBXGPSSpi::selfTest() { return true; } + +UBXGPSData UBXGPSSpi::sampleImpl() +{ + UBXPvtFrame pvt; + + if (!readUBXFrame(pvt)) + return lastSample; + + UBXPvtFrame::Payload& pvtP = pvt.getPayload(); + + UBXGPSData sample; + sample.gpsTimestamp = TimestampTimer::getInstance().getTimestamp(); + sample.latitude = (float)pvtP.lat / 1e7; + sample.longitude = (float)pvtP.lon / 1e7; + sample.height = (float)pvtP.height / 1e3; + sample.velocityNorth = (float)pvtP.velN / 1e3; + sample.velocityEast = (float)pvtP.velE / 1e3; + sample.velocityDown = (float)pvtP.velD / 1e3; + sample.speed = (float)pvtP.gSpeed / 1e3; + sample.track = (float)pvtP.headMot / 1e5; + sample.positionDOP = (float)pvtP.pDOP / 1e2; + sample.satellites = pvtP.numSV; + sample.fix = pvtP.fixType; + + return sample; +} + +bool UBXGPSSpi::reset() +{ + uint8_t payload[] = { + 0x00, 0x00, // Hot start + 0x00, // Hardware reset + 0x00 // Reserved + }; + + UBXFrame frame{UBXMessage::UBX_CFG_RST, payload, sizeof(payload)}; + + // The reset message will not be followed by an acknowledgment + if (!writeUBXFrame(frame)) + return false; + + // Do not interact with the module while it is resetting + miosix::Thread::sleep(RESET_SLEEP_TIME); + + return true; +} + +bool UBXGPSSpi::setUBXProtocol() +{ + uint8_t payload[] = { + 0x04, // SPI port + 0x00, // reserved0 + 0x00, 0x00, // txReady + 0x00, 0x00, 0x00, 0x00, // spiMode = 0, ffCnt = 0 (mechanism off) + 0x00, 0x00, 0x00, 0x00, // reserved1 + 0x01, 0x00, // inProtoMask = UBX + 0x01, 0x00, // outProtoMask = UBX + 0x00, 0x00, // flags + 0x00, 0x00 // reserved2 + }; + + UBXFrame frame{UBXMessage::UBX_CFG_PRT, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSpi::setDynamicModelToAirborne4g() +{ + uint8_t payload[] = { + 0x01, 0x00, // Parameters bitmask, apply dynamic model configuration + 0x08, // Dynamic model = airbone 4g + 0x00, // Fix mode + 0x00, 0x00, 0x00, 0x00, // Fixed altitude for 2D mode + 0x00, 0x00, 0x00, 0x00, // Fixed altitude variance for 2D mode + 0x00, // Minimun elevation for a GNSS satellite to be used + 0x00, // Reserved + 0x00, 0x00, // Position DOP mask to use + 0x00, 0x00, // Time DOP mask to use + 0x00, 0x00, // Position accuracy mask + 0x00, 0x00, // Time accuracy mask + 0x00, // Static hold threshold + 0x00, // DGNSS timeout + 0x00, // C/NO threshold number SVs + 0x00, // C/NO threshold + 0x00, 0x00, // Reserved + 0x00, 0x00, // Static hold distance threshold + 0x00, // UTC standard to be used + 0x00, 0x00, 0x00, 0x00, 0x00 // Reserved + }; + + UBXFrame frame{UBXMessage::UBX_CFG_NAV5, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSpi::setSampleRate() +{ + uint8_t payload[] = { + 0x00, 0x00, // Measurement rate + 0x01, 0x00, // One navigation solution per measurement + 0x01, 0x01 // GPS time + }; + + uint16_t sampleRateMs = 1000 / sampleRate; + payload[0] = sampleRateMs; + payload[1] = sampleRateMs >> 8; + + UBXFrame frame{UBXMessage::UBX_CFG_RATE, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSpi::setPVTMessageRate() +{ + uint8_t payload[] = { + 0x01, 0x07, // PVT message + 0x01 // Rate = 1 navigation solution update + }; + + UBXFrame frame{UBXMessage::UBX_CFG_MSG, payload, sizeof(payload)}; + + return safeWriteUBXFrame(frame); +} + +bool UBXGPSSpi::readUBXFrame(UBXFrame& frame) +{ + long long start = miosix::getTick(); + long long end = start + READ_TIMEOUT * MS_TO_TICK; + + { + SPITransaction spi{spiSlave}; + + // Search UBX frame preamble byte by byte + size_t i = 0; + bool waiting = false; + while (i < 2) + { + if (miosix::getTick() >= end) + { + LOG_ERR(logger, "Timeout for read expired"); + return false; + } + + uint8_t c = spi.read(); + + if (c == UBXFrame::PREAMBLE[i]) + { + waiting = false; + frame.preamble[i++] = c; + } + else if (c == UBXFrame::PREAMBLE[0]) + { + i = 0; + waiting = false; + frame.preamble[i++] = c; + } + else if (c == UBXFrame::WAIT) + { + i = 0; + if (!waiting) + { + waiting = true; + // LOG_DEBUG(logger, "Device is waiting..."); + } + } + else + { + i = 0; + waiting = false; + LOG_DEBUG(logger, "Received unexpected byte: {:02x} {:#c}", c, + c); + } + } + + frame.message = swapBytes16(spi.read16()); + frame.payloadLength = swapBytes16(spi.read16()); + spi.read(frame.payload, frame.getRealPayloadLength()); + spi.read(frame.checksum, 2); + } + + if (!frame.isValid()) + { + LOG_ERR(logger, "Received invalid UBX frame"); + return false; + } + + return true; +} + +bool UBXGPSSpi::writeUBXFrame(const UBXFrame& frame) +{ + if (!frame.isValid()) + { + LOG_ERR(logger, "Trying to send an invalid UBX frame"); + return false; + } + + uint8_t packedFrame[frame.getLength()]; + frame.writePacked(packedFrame); + + { + SPITransaction spi{spiSlave}; + spi.write(packedFrame, frame.getLength()); + } + + return true; +} + +bool UBXGPSSpi::safeWriteUBXFrame(const UBXFrame& frame) +{ + for (unsigned int i = 0; i < MAX_TRIES; i++) + { + if (i > 0) + LOG_DEBUG(logger, "Retrying (attempt {:#d} of {:#d})...", i + 1, + MAX_TRIES); + + if (!writeUBXFrame(frame)) + return false; + + UBXAckFrame ack; + + if (!readUBXFrame(ack)) + continue; + + if (ack.isNack()) + { + if (ack.getAckMessage() == frame.getMessage()) + LOG_DEBUG(logger, "Received NAK"); + else + LOG_DEBUG(logger, "Received NAK for different UBX frame {:04x}", + static_cast<uint16_t>(ack.getPayload().ackMessage)); + continue; + } + + if (ack.isAck() && ack.getAckMessage() != frame.getMessage()) + { + LOG_DEBUG(logger, "Received ACK for different UBX frame {:04x}", + static_cast<uint16_t>(ack.getPayload().ackMessage)); + continue; + } + + return true; + } + + LOG_ERR(logger, "Gave up after {:#d} tries", MAX_TRIES); + return false; +} + +} // namespace Boardcore diff --git a/src/shared/sensors/UBXGPS/UBXGPSSpi.h b/src/shared/sensors/UBXGPS/UBXGPSSpi.h new file mode 100644 index 0000000000000000000000000000000000000000..e20bc3bfcec2c8b36e69093291be5af048d80ce6 --- /dev/null +++ b/src/shared/sensors/UBXGPS/UBXGPSSpi.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio, Damiano Amatruda + * + * 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/spi/SPIDriver.h> +#include <sensors/Sensor.h> + +#include "UBXFrame.h" +#include "UBXGPSData.h" + +namespace Boardcore +{ + +/** + * @brief Sensor for UBlox GPS. + * + * This sensor handles communication and setup with UBlox GPSs. It uses the + * binary UBX protocol to retrieve and parse navigation data faster than using + * the string-based NMEA. + * + * At initialization it resets the device, sets the configuration and sets up + * UBX messages and GNSS parameters. + * + * Communication with the device is performed through SPI. + * + * This sensor is compatible with series NEO-M8 and NEO-M9. + */ +class UBXGPSSpi : public Sensor<UBXGPSData> +{ +public: + /** + * @brief Constructor. + * + * @param spiBus The SPI bus. + * @param spiCs The CS pin to lower when we need to sample. + * @param spiConfig The SPI configuration. + * @param sampleRate The GPS sample rate [kHz]. + */ + UBXGPSSpi(SPIBusInterface& spiBus, miosix::GpioPin spiCs, + SPIBusConfig spiConfig = getDefaultSPIConfig(), + uint8_t sampleRate = 5); + + /** + * @brief Constructs the default config for the SPI bus. + * + * @return The default SPIBusConfig object. + */ + static SPIBusConfig getDefaultSPIConfig(); + + uint8_t getSampleRate(); + + bool init() override; + + bool selfTest() override; + +private: + UBXGPSData sampleImpl() override; + + /** + * @brief Resets the device to its default configuration. + * + * @return True if the device reset succeeded. + */ + bool reset(); + + /** + * @brief Enables UBX and disables NMEA on the SPI port. + * + * @return True if the configuration received an acknowledgement. + */ + bool setUBXProtocol(); + + /** + * @brief Configures the dynamic model to airborn 4g. + * + * @return True if the configuration received an acknowledgement. + */ + bool setDynamicModelToAirborne4g(); + + /** + * @brief Configures the navigation solution sample rate. + * + * @return True if the configuration received an acknowledgement. + */ + bool setSampleRate(); + + /** + * @brief Configures the PVT message output rate. + * + * @return True if the configuration received an acknowledgement. + */ + bool setPVTMessageRate(); + + /** + * @brief Reads a UBX frame. + * + * @param frame The received frame. + * @return True if a valid frame was read. + */ + bool readUBXFrame(UBXFrame& frame); + + /** + * @brief Writes a UBX frame. + * + * @param frame The frame to write. + * @return True if the frame is valid. + */ + bool writeUBXFrame(const UBXFrame& frame); + + /** + * @brief Writes a UBX frame and waits for its acknowledgement. + * + * @param frame The frame to write. + * @return True if the frame is valid and acknowledged. + */ + bool safeWriteUBXFrame(const UBXFrame& frame); + + SPISlave spiSlave; + uint8_t sampleRate; + + PrintLogger logger = Logging::getLogger("ubxgps"); + + static constexpr float MS_TO_TICK = miosix::TICK_FREQ / 1000.f; + + static constexpr unsigned int RESET_SLEEP_TIME = 5000; // [ms] + static constexpr unsigned int READ_TIMEOUT = 5000; // [ms] + static constexpr unsigned int MAX_TRIES = 5; // [1] +}; + +} // namespace Boardcore diff --git a/src/shared/sensors/UbloxGPS/UbloxGPS.cpp b/src/shared/sensors/UbloxGPS/UbloxGPS.cpp deleted file mode 100644 index dff24046dfefeca63f88c4499f1d0b2bf4c9f9ce..0000000000000000000000000000000000000000 --- a/src/shared/sensors/UbloxGPS/UbloxGPS.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* Copyright (c) 2021 Skyward Experimental Rocketry - * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio - * - * 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 "UbloxGPS.h" - -#include <diagnostic/StackLogger.h> -#include <drivers/serial.h> -#include <drivers/timer/TimestampTimer.h> -#include <fcntl.h> -#include <filesystem/file_access.h> - -using namespace miosix; - -namespace Boardcore -{ - -UbloxGPS::UbloxGPS(int baudrate_, uint8_t sampleRate_, int serialPortNum_, - const char* serialPortName_, int defaultBaudrate_) - : baudrate(baudrate_), sampleRate(sampleRate_), - serialPortNumber(serialPortNum_), serialPortName(serialPortName_), - defaultBaudrate(defaultBaudrate_) -{ - // Prepare the gps file path with the specified name - strcpy(gpsFilePath, "/dev/"); - strcat(gpsFilePath, serialPortName); -} - -bool UbloxGPS::init() -{ - // Change the baud rate from the default value - if (!serialCommuinicationSetup()) - { - return false; - } - - Thread::sleep(10); - - // Reset configuration to default - // TODO: maybe move this on serial communication setup - if (!resetConfiguration()) - { - return false; - } - - // Disable NMEA messages - if (!disableNMEAMessages()) - { - return false; - } - - Thread::sleep(100); - - // Set GNSS configuration - if (!setGNSSConfiguration()) - { - return false; - } - - Thread::sleep(100); - - // Enable UBX messages - if (!enableUBXMessages()) - { - return false; - } - - Thread::sleep(100); - - // Set rate - if (!setRate()) - { - return false; - } - - return true; -} - -bool UbloxGPS::selfTest() { return true; } - -UbloxGPSData UbloxGPS::sampleImpl() -{ - Lock<FastMutex> l(mutex); - return threadSample; -} - -void UbloxGPS::run() -{ - /** - * UBX message structure: - * - 2B: Preamble - * - 1B: Message class - * - 1B: Message id - * - 2B: Payload length - * - lB: Payload - * - 2B: Checksum - */ - uint8_t message[6 + UBX_MAX_PAYLOAD_LENGTH + 2]; - uint16_t payloadLength; - - while (!shouldStop()) - { - StackLogger::getInstance().updateStack(THID_GPS); - - // Try to read the message - if (!readUBXMessage(message, payloadLength)) - { - LOG_DEBUG(logger, "Unable to read a UBX message"); - continue; - } - - // Parse the message - if (!parseUBXMessage(message)) - { - LOG_DEBUG(logger, - "UBX message not recognized (class:0x{02x}, id: 0x{02x})", - message[2], message[3]); - } - } -} - -void UbloxGPS::ubxChecksum(uint8_t* msg, int len) -{ - uint8_t ck_a = 0, ck_b = 0; - - // The length must be valid, at least 8 bytes (preamble, msg, length, - // checksum) - if (len <= 8) - { - return; - } - - // Checksum calculation from byte 2 to end of payload - for (int i = 2; i < len - 2; i++) - { - ck_a = ck_a + msg[i]; - ck_b = ck_b + ck_a; - } - - msg[len - 2] = ck_a; - msg[len - 1] = ck_b; -} - -bool UbloxGPS::writeUBXMessage(uint8_t* message, int length) -{ - // Compute the checksum - ubxChecksum(message, length); - - // Write configuration - if (write(gpsFile, message, length) < 0) - { - LOG_ERR(logger, - "Failed to write ubx message (class:0x{02x}, id: 0x{02x})", - message[2], message[3]); - return false; - } - - return true; -} - -bool UbloxGPS::serialCommuinicationSetup() -{ - intrusive_ref_ptr<DevFs> devFs = FilesystemManager::instance().getDevFs(); - - // Change the baudrate only if it is different than the default - if (baudrate != defaultBaudrate) - { - // Close the gps file if already opened - devFs->remove(serialPortName); - - // Open the serial port device with the default boudrate - if (!devFs->addDevice(serialPortName, - intrusive_ref_ptr<Device>(new STM32Serial( - serialPortNumber, defaultBaudrate)))) - { - LOG_ERR(logger, - "[gps] Faild to open serial port {0} with baudrate {1} as " - "file {2}", - serialPortNumber, defaultBaudrate, serialPortName); - return false; - } - - // Open the gps file - if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) - { - LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); - return false; - } - - // Change boudrate - if (!setBaudrate()) - { - return false; - }; - - // Close the gps file - if (close(gpsFile) < 0) - { - LOG_ERR(logger, "Failed to close gps file {}", gpsFilePath); - return false; - } - - // Close the serial port - if (!devFs->remove(serialPortName)) - { - LOG_ERR(logger, "Failed to close serial port {} as file {}", - serialPortNumber, serialPortName); - return false; - } - } - - // Reopen the serial port with the configured boudrate - if (!devFs->addDevice(serialPortName, - intrusive_ref_ptr<Device>( - new STM32Serial(serialPortNumber, baudrate)))) - { - LOG_ERR(logger, - "Faild to open serial port {} with baudrate {} as file {}\n", - serialPortNumber, defaultBaudrate, serialPortName); - return false; - } - - // Reopen the gps file - if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0) - { - LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath); - return false; - } - - return true; -} - -bool UbloxGPS::resetConfiguration() -{ - uint8_t ubx_cfg_prt[RESET_CONFIG_MSG_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x04, // Message UBX-CFG-RST - 0x04, 0x00, // Length - 0x00, 0x00, // navBbrMask (Hot start) - 0x00, // Hardware reset immediately - 0x00, // Reserved - 0xff, 0xff // Checksum - }; - - return writeUBXMessage(ubx_cfg_prt, RESET_CONFIG_MSG_LEN); -} - -bool UbloxGPS::setBaudrate() -{ - uint8_t ubx_cfg_prt[SET_BAUDRATE_MSG_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x8a, // Message UBX-CFG-VALSET - 0x0c, 0x00, // Length - 0x00, // Version - 0xff, // All layers - 0x00, 0x00, // Reserved - 0x01, 0x00, 0x52, 0x40, // Configuration item key ID - 0xff, 0xff, 0xff, 0xff, // Value - 0xff, 0xff // Checksum - }; - - // Prepare boud rate - ubx_cfg_prt[14] = baudrate; - ubx_cfg_prt[15] = baudrate >> 8; - ubx_cfg_prt[16] = baudrate >> 16; - ubx_cfg_prt[17] = baudrate >> 24; - - return writeUBXMessage(ubx_cfg_prt, SET_BAUDRATE_MSG_LEN); -} - -bool UbloxGPS::disableNMEAMessages() -{ - uint8_t ubx_cfg_valset[DISABLE_NMEA_MESSAGES_MSG_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x8a, // Message UBX-CFG-VALSET - 0x22, 0x00, // Length - 0x00, // Version - 0xff, // All layers - 0x00, 0x00, // Reserved - 0xbb, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GGA_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_GGA_UART1 value - 0xca, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GLL_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_GLL_UART1 value - 0xc0, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GSA_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_GSA_UART1 value - 0xc5, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_GSV_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_GSV_UART1 value - 0xac, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_RMC_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_RMC_UART1 value - 0xb1, 0x00, 0x91, 0x20, // CFG-MSGOUT-NMEA_ID_VTG_UART1 key ID - 0x00, // CFG-MSGOUT-NMEA_ID_VTG_UART1 value - 0xff, 0xff // Checksum - }; - - return writeUBXMessage(ubx_cfg_valset, DISABLE_NMEA_MESSAGES_MSG_LEN); -} - -bool UbloxGPS::setGNSSConfiguration() -{ - uint8_t ubx_cfg_valset[SET_GNSS_CONF_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x8a, // Message UBX-CFG-VALSET - 0x09, 0x00, // Length - 0x00, // Version - 0x07, // All layers - 0x00, 0x00, // Reserved - 0x21, 0x00, 0x11, 0x20, // CFG-NAVSPG-DYNMODEL key ID - 0x08, // CFG-NAVSPG-DYNMODEL value - 0xff, 0xff // Checksum - }; - - return writeUBXMessage(ubx_cfg_valset, SET_GNSS_CONF_LEN); -} - -bool UbloxGPS::enableUBXMessages() -{ - uint8_t ubx_cfg_valset[ENABLE_UBX_MESSAGES_MSG_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x8a, // Message UBX-CFG-VALSET - 0x09, 0x00, // Length - 0x00, // Version - 0xff, // All layers - 0x00, 0x00, // Reserved - 0x07, 0x00, 0x91, 0x20, // CFG-MSGOUT-UBX_NAV_PVT_UART1 key ID - 0x01, // CFG-MSGOUT-UBX_NAV_PVT_UART1 value - 0xff, 0xff // Checksum - }; - - return writeUBXMessage(ubx_cfg_valset, ENABLE_UBX_MESSAGES_MSG_LEN); -} - -bool UbloxGPS::setRate() -{ - uint16_t rate = 1000 / sampleRate; - LOG_DEBUG(logger, "Rate: {}", rate); - - uint8_t ubx_cfg_valset[SET_RATE_MSG_LEN] = { - 0Xb5, 0x62, // Preamble - 0x06, 0x8a, // Message UBX-CFG-VALSET - 0x0a, 0x00, // Length - 0x00, // Version - 0x07, // All layers - 0x00, 0x00, // Reserved - 0x01, 0x00, 0x21, 0x30, // CFG-RATE-MEAS key ID - 0xff, 0xff, // CFG-RATE-MEAS value - 0xff, 0xff // Checksum - }; - - // Prepare rate - ubx_cfg_valset[14] = rate; - ubx_cfg_valset[15] = rate >> 8; - - return writeUBXMessage(ubx_cfg_valset, SET_RATE_MSG_LEN); -} - -bool UbloxGPS::readUBXMessage(uint8_t* message, uint16_t& payloadLength) -{ - // Read preamble - do - { - // Read util the first byte of the preamble - do - { - if (read(gpsFile, &message[0], 1) <= 0) // No more data available - { - return false; - } - } while (message[0] != PREAMBLE[0]); - - // Read the next byte - if (read(gpsFile, &message[1], 1) <= 0) // No more data available - { - return false; - } - } while (message[1] != PREAMBLE[1]); // Continue - - // Read message class and ID - if (read(gpsFile, &message[2], 1) <= 0) - { - return false; - } - if (read(gpsFile, &message[3], 1) <= 0) - { - return false; - } - - // Read length - if (read(gpsFile, &message[4], 2) <= 0) - { - return false; - } - payloadLength = message[4] | (message[5] << 8); - if (payloadLength > UBX_MAX_PAYLOAD_LENGTH) - { - return false; - } - - // Read paylaod and checksum - for (auto i = 0; i < payloadLength + 2; i++) - { - if (read(gpsFile, &message[6 + i], 1) <= 0) - { - return false; - } - } - - // Verify the checksum - uint8_t msgChecksum1 = message[6 + payloadLength]; - uint8_t msgChecksum2 = message[6 + payloadLength + 1]; - ubxChecksum(message, 6 + payloadLength + 2); - if (msgChecksum1 != message[6 + payloadLength] || - msgChecksum2 != message[6 + payloadLength + 1]) - { - LOG_ERR(logger, "Message checksum verification failed"); - return false; - } - - return true; -} - -bool UbloxGPS::parseUBXMessage(uint8_t* message) -{ - switch (message[2]) // Message class - { - case 0x01: // UBX-NAV - return parseUBXNAVMessage(message); - case 0x05: // UBX-ACK - return parseUBXACKMessage(message); - } - return false; -} - -bool UbloxGPS::parseUBXNAVMessage(uint8_t* message) -{ - switch (message[3]) // Message id - { - case 0x07: // UBX-NAV-PVT - // Lock the threadSample variable - Lock<FastMutex> l(mutex); - - // Latitude - int32_t rawLatitude = message[6 + 28] | message[6 + 29] << 8 | - message[6 + 30] << 16 | message[6 + 31] << 24; - threadSample.latitude = (float)rawLatitude / 1e7; - - // Longitude - int32_t rawLongitude = message[6 + 24] | message[6 + 25] << 8 | - message[6 + 26] << 16 | - message[6 + 27] << 24; - threadSample.longitude = (float)rawLongitude / 1e7; - - // Height - int32_t rawHeight = message[6 + 32] | message[6 + 33] << 8 | - message[6 + 34] << 16 | message[6 + 35] << 24; - threadSample.height = (float)rawHeight / 1e3; - - // Velocity north - int32_t rawVelocityNorth = message[6 + 48] | message[6 + 49] << 8 | - message[6 + 50] << 16 | - message[6 + 51] << 24; - threadSample.velocityNorth = (float)rawVelocityNorth / 1e3; - - // Velocity east - int32_t rawVelocityEast = message[6 + 52] | message[6 + 53] << 8 | - message[6 + 54] << 16 | - message[6 + 55] << 24; - threadSample.velocityEast = (float)rawVelocityEast / 1e3; - - // Velocity down - int32_t rawVelocityDown = message[6 + 56] | message[6 + 57] << 8 | - message[6 + 58] << 16 | - message[6 + 59] << 24; - threadSample.velocityDown = (float)rawVelocityDown / 1e3; - - // Speed - int32_t rawSpeed = message[6 + 60] | message[6 + 61] << 8 | - message[6 + 62] << 16 | message[6 + 63] << 24; - threadSample.speed = (float)rawSpeed / 1e3; - - // Track (heading of motion) - int32_t rawTrack = message[6 + 64] | message[6 + 65] << 8 | - message[6 + 66] << 16 | message[6 + 67] << 24; - threadSample.track = (float)rawTrack / 1e5; - - // Number of satellite - threadSample.satellites = (uint8_t)message[6 + 23]; - - // Fix (every type of fix accepted) - threadSample.fix = message[6 + 20] != 0; - - // Timestamp - threadSample.gpsTimestamp = - TimestampTimer::getInstance().getTimestamp(); - - return true; - } - - return false; -} - -bool UbloxGPS::parseUBXACKMessage(uint8_t* message) -{ - switch (message[3]) // Message id - { - case 0x00: // UBX-ACK-NAC - LOG_DEBUG(logger, - "Received NAC for message (class:0x{02x}, id: 0x{02x})", - message[6], message[7]); - return true; - case 0x01: // UBX-ACK-ACK - LOG_DEBUG(logger, - "Received ACK for message (class:0x{02x}, id: 0x{02x})", - message[6], message[7]); - return true; - } - return false; -} - -} // namespace Boardcore diff --git a/src/shared/sensors/UbloxGPS/UbloxGPS.h b/src/shared/sensors/UbloxGPS/UbloxGPS.h deleted file mode 100644 index 9495bc6f5fe792774f0bb9a59c32f35c6e97e5b8..0000000000000000000000000000000000000000 --- a/src/shared/sensors/UbloxGPS/UbloxGPS.h +++ /dev/null @@ -1,151 +0,0 @@ -/* Copyright (c) 2021 Skyward Experimental Rocketry - * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio - * - * 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 <ActiveObject.h> -#include <diagnostic/PrintLogger.h> -#include <miosix.h> -#include <sensors/Sensor.h> - -#include "UbloxGPSData.h" - -namespace Boardcore -{ - -/** - * @brief Driver for Ublox GPSs - * - * This driver handles communication and setup with Ublox GPSs. It uses the - * binary UBX protocol to retrieve and parse navigation data quicker than using - * the string based NMEA. - * - * At initialization it configures the device with the specified baudrate, reset - * the configuration and sets up UBX messages and GNSS parameters. - * - * Communication with the device is performed through a file, the driver opens - * the serial port under the filepath /dev/<serialPortName>. - * There is no need for the file to be setted up beforhand. - * - * This driver was written for a NEO-M9N gps with the latest version of UBX. - */ -class UbloxGPS : public Sensor<UbloxGPSData>, public ActiveObject -{ -public: - /** - * @brief Construct a new UbloxGPS object - * - * @param boudrate_ Baudrate to communicate with the device (max: 921600, - * min: 4800 for NEO-M9N) - * @param sampleRate_ GPS sample rate (max: 25 for NEO-M9N) - * @param serialPortName_ Name of the file for the gps device - * @param defaultBaudrate_ Startup baudrate (38400 for NEO-M9N) - */ - UbloxGPS(int boudrate_ = 921600, uint8_t sampleRate_ = 10, - int serialPortNumber_ = 2, const char *serialPortName_ = "gps", - int defaultBaudrate_ = 38400); - - /** - * @brief Sets up the serial port baudrate, disables the NMEA messages, - * configures GNSS options and enables UBX-PVT message - * - * @return True if the operation succeeded - */ - bool init() override; - - /** - * @brief Read a single message form the GPS, waits 2 sample cycle - * - * @return True if a message was sampled - */ - bool selfTest() override; - -private: - UbloxGPSData sampleImpl() override; - - void run() override; - - void ubxChecksum(uint8_t *msg, int len); - - /** - * @brief Compute the checksum and write the message to the device - */ - bool writeUBXMessage(uint8_t *message, int length); - - /** - * @brief Sets up the serial port with the correct baudrate - * - * Opens the serial port with the defaul baudrate and changes it to - * the value specified in the constructor, then it reopens the serial port. - * If the device is already using the correct baudrate this won't have - * effect. However if the gps is using a different baudrate the diver won't - * be able to communicate. - */ - bool serialCommuinicationSetup(); - - bool resetConfiguration(); - - bool setBaudrate(); - - bool disableNMEAMessages(); - - bool setGNSSConfiguration(); - - bool enableUBXMessages(); - - bool setRate(); - - bool readUBXMessage(uint8_t *message, uint16_t &payloadLength); - - bool parseUBXMessage(uint8_t *message); - - bool parseUBXNAVMessage(uint8_t *message); - - bool parseUBXACKMessage(uint8_t *message); - - const int baudrate; // [baud] - const uint8_t sampleRate; // [Hz] - const int serialPortNumber; - const char *serialPortName; - const int defaultBaudrate; // [baud] - - char gpsFilePath[16]; ///< Allows for a filename of up to 10 characters - int gpsFile = -1; - - mutable miosix::FastMutex mutex; - UbloxGPSData threadSample{}; - - static constexpr int SET_BAUDRATE_MSG_LEN = 20; - static constexpr int RESET_CONFIG_MSG_LEN = 12; - static constexpr int DISABLE_NMEA_MESSAGES_MSG_LEN = 42; - static constexpr int SET_GNSS_CONF_LEN = 17; - static constexpr int ENABLE_UBX_MESSAGES_MSG_LEN = 17; - static constexpr int SET_RATE_MSG_LEN = 18; - - static constexpr uint8_t PREAMBLE[] = {0xb5, 0x62}; - - static constexpr int UBX_MAX_PAYLOAD_LENGTH = 92; - - PrintLogger logger = Logging::getLogger("ubloxgps"); -}; - -} // namespace Boardcore diff --git a/src/tests/algorithms/NAS/test-nas-parafoil.cpp b/src/tests/algorithms/NAS/test-nas-parafoil.cpp index 57fd167c839ec3bdef2d24c69417ddb7f118aab1..06f5bccae190975ac036c7a61806baed7af9845c 100644 --- a/src/tests/algorithms/NAS/test-nas-parafoil.cpp +++ b/src/tests/algorithms/NAS/test-nas-parafoil.cpp @@ -26,7 +26,7 @@ #include <miosix.h> #include <sensors/MPU9250/MPU9250.h> #include <sensors/SensorManager.h> -#include <sensors/UbloxGPS/UbloxGPS.h> +#include <sensors/UBXGPS/UBXGPSSerial.h> #include <utils/AeroUtils/AeroUtils.h> #include <utils/SkyQuaternion/SkyQuaternion.h> @@ -48,8 +48,8 @@ Vector2f startPos = Vector2f(45.501141, 9.156281); NAS* nas; SPIBus spi1(SPI1); -MPU9250* imu = nullptr; -UbloxGPS* gps = nullptr; +MPU9250* imu = nullptr; +UBXGPSSerial* gps = nullptr; int main() { @@ -110,7 +110,7 @@ void init() imu = new MPU9250(spi1, sensors::mpu9250::cs::getPin()); imu->init(); - gps = new UbloxGPS(38400, 10, 2, "gps", 38400); + gps = new UBXGPSSerial(38400, 10, 2, "gps", 38400); gps->init(); gps->start(); diff --git a/src/tests/algorithms/NAS/test-nas-stack.cpp b/src/tests/algorithms/NAS/test-nas-stack.cpp index e72a1c9830b63682cffff7968cbc27106b85a61f..31980dd9195f45c444cba38d8a0bd1e927f22268 100644 --- a/src/tests/algorithms/NAS/test-nas-stack.cpp +++ b/src/tests/algorithms/NAS/test-nas-stack.cpp @@ -27,7 +27,7 @@ #include <sensors/BMX160/BMX160.h> #include <sensors/MS5803/MS5803.h> #include <sensors/SensorManager.h> -#include <sensors/UbloxGPS/UbloxGPS.h> +#include <sensors/UBXGPS/UBXGPSSerial.h> #include <utils/AeroUtils/AeroUtils.h> #include <utils/Constants.h> #include <utils/SkyQuaternion/SkyQuaternion.h> @@ -50,9 +50,9 @@ Vector2f startPos = Vector2f(45.501141, 9.156281); NAS* nas; SPIBus spi1(SPI1); -BMX160* imu = nullptr; -UbloxGPS* gps = nullptr; -MS5803* baro = nullptr; +BMX160* imu = nullptr; +UBXGPSSerial* gps = nullptr; +MS5803* baro = nullptr; int main() { @@ -134,7 +134,7 @@ void init() new BMX160(spi1, sensors::bmx160::cs::getPin(), bmx_config, spiConfig); imu->init(); - gps = new UbloxGPS(921600, 10, 2, "gps", 38400); + gps = new UBXGPSSerial(921600, 10, 2, "gps", 38400); gps->init(); gps->start(); diff --git a/src/tests/catch/xbee/test-xbee-parser.cpp b/src/tests/catch/xbee/test-xbee-parser.cpp index 924d96851e8970f3729ef57d13f65de28ae78be1..26867ab7acd2037a74f063e06089dd0d52c61e2f 100644 --- a/src/tests/catch/xbee/test-xbee-parser.cpp +++ b/src/tests/catch/xbee/test-xbee-parser.cpp @@ -71,9 +71,7 @@ void printu64(uint64_t v) uint8_t* p = reinterpret_cast<uint8_t*>(&v); for (int i = 0; i < 8; i++) - { printf("%02X ", p[i]); - } printf("\n"); } diff --git a/src/tests/sensors/test-ubloxgps.cpp b/src/tests/sensors/test-ubxgps-serial.cpp similarity index 95% rename from src/tests/sensors/test-ubloxgps.cpp rename to src/tests/sensors/test-ubxgps-serial.cpp index 24b6cc092a73e531765fbbe4897a29133a827014..5518137419f3e03ea55e72011efdab7dbae65111 100644 --- a/src/tests/sensors/test-ubloxgps.cpp +++ b/src/tests/sensors/test-ubxgps-serial.cpp @@ -22,7 +22,7 @@ #include <drivers/timer/TimestampTimer.h> #include <miosix.h> -#include <sensors/UbloxGPS/UbloxGPS.h> +#include <sensors/UBXGPS/UBXGPSSerial.h> #include <utils/Debug.h> #include <cstdio> @@ -39,8 +39,8 @@ int main() printf("Welcome to the ublox test\n"); // Keep GPS baud rate at default for easier testing - UbloxGPS gps(921600, RATE, 2, "gps", 38400); - UbloxGPSData dataGPS; + UBXGPSSerial gps(921600, RATE, 2, "gps", 38400); + UBXGPSData dataGPS; printf("Gps allocated\n"); // Init the gps diff --git a/src/tests/sensors/test-ubxgps-spi.cpp b/src/tests/sensors/test-ubxgps-spi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3fcecde01e161e98c8e01a1aa2021d8e4e4775dc --- /dev/null +++ b/src/tests/sensors/test-ubxgps-spi.cpp @@ -0,0 +1,82 @@ +/* Copyright (c) 2022 Skyward Experimental Rocketry + * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio, Damiano Amatruda + * + * 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/UBXGPS/UBXGPSSpi.h> +#include <utils/Debug.h> + +using namespace miosix; +using namespace Boardcore; + +int main() +{ +#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X + SPIBus spiBus(SPI2); + GpioPin spiCs(GPIOG_BASE, 3); + GpioPin spiSck(GPIOB_BASE, 13); + GpioPin spiMiso(GPIOB_BASE, 14); + GpioPin spiMosi(GPIOB_BASE, 15); +#else + SPIBus spiBus(SPI1); + GpioPin spiCs(GPIOA_BASE, 3); + GpioPin spiSck(GPIOA_BASE, 5); + GpioPin spiMiso(GPIOA_BASE, 6); + GpioPin spiMosi(GPIOA_BASE, 7); +#endif + + spiCs.mode(Mode::OUTPUT); + spiCs.high(); + spiSck.mode(Mode::ALTERNATE); + spiSck.alternateFunction(5); + spiMiso.mode(Mode::ALTERNATE); + spiMiso.alternateFunction(5); + spiMosi.mode(Mode::ALTERNATE); + spiMosi.alternateFunction(5); + + UBXGPSSpi gps{spiBus, spiCs}; + + TRACE("Initializing UBXGPSSpi...\n"); + + while (!gps.init()) + { + TRACE("Init failed! (code: %d)\n", gps.getLastError()); + + TRACE("Retrying in 10 seconds...\n"); + miosix::Thread::sleep(10000); + } + + while (true) + { + gps.sample(); + GPSData sample __attribute__((unused)) = gps.getLastSample(); + + TRACE( + "timestamp: %4.3f, lat: %f, lon: %f, height: %4.1f, velN: %3.2f, " + "velE: %3.2f, velD: %3.2f, speed: %3.2f, track %3.1f, pDOP: %f, " + "nsat: %2d, fix: %2d\n", + (float)sample.gpsTimestamp / 1000000, sample.latitude, + sample.longitude, sample.height, sample.velocityNorth, + sample.velocityEast, sample.velocityDown, sample.speed, + sample.track, sample.positionDOP, sample.satellites, sample.fix); + + Thread::sleep(1000 / gps.getSampleRate()); + } +}