diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..cc6d283 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ +Thank you for opening an issue on an Adafruit Arduino library repository. To +improve the speed of resolution please review the following guidelines and +common troubleshooting steps below before creating the issue: + +- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use + the forums at http://forums.adafruit.com to ask questions and troubleshoot why + something isn't working as expected. In many cases the problem is a common issue + that you will more quickly receive help from the forum community. GitHub issues + are meant for known defects in the code. If you don't know if there is a defect + in the code then start with troubleshooting on the forum first. + +- **If following a tutorial or guide be sure you didn't miss a step.** Carefully + check all of the steps and commands to run have been followed. Consult the + forum if you're unsure or have questions about steps in a guide/tutorial. + +- **For Arduino projects check these very common issues to ensure they don't apply**: + + - For uploading sketches or communicating with the board make sure you're using + a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes + very hard to tell the difference between a data and charge cable! Try using the + cable with other devices or swapping to another cable to confirm it is not + the problem. + + - **Be sure you are supplying adequate power to the board.** Check the specs of + your board and plug in an external power supply. In many cases just + plugging a board into your computer is not enough to power it and other + peripherals. + + - **Double check all soldering joints and connections.** Flakey connections + cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. + + - **Ensure you are using an official Arduino or Adafruit board.** We can't + guarantee a clone board will have the same functionality and work as expected + with this code and don't support them. + +If you're sure this issue is a defect in the code and checked the steps above +please fill in the following fields to provide enough troubleshooting information. +You may delete the guideline and text above to just leave the following details: + +- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** + +- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO + VERSION HERE** + +- List the steps to reproduce the problem below (if possible attach a sketch or + copy the sketch code in too): **LIST REPRO STEPS BELOW** \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a110620 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +Thank you for creating a pull request to contribute to Adafruit's GitHub code! +Before you open the request please review the following guidelines and tips to +help it be more easily integrated: + +- **Describe the scope of your change--i.e. what the change does and what parts + of the code were modified.** This will help us understand any risks of integrating + the code. + +- **Describe any known limitations with your change.** For example if the change + doesn't apply to a supported platform of the library please mention it. + +- **Please run any tests or examples that can exercise your modified code.** We + strive to not break users of the code and running tests/examples helps with this + process. + +Thank you again for contributing! We will try to test and integrate the change +as soon as we can, but be aware we have many GitHub repositories to manage and +can't immediately respond to every request. There is no need to bump or check in +on a pull request (it will clutter the discussion of the request). + +Also don't be worried if the request is closed or not integrated--sometimes the +priorities of Adafruit's GitHub code (education, ease of use) might not match the +priorities of the pull request. Don't fret, the open source community thrives on +forks and GitHub makes it easy to keep your changes in a forked repo. + +After reviewing the guidelines above you can delete this text from the pull request. \ No newline at end of file diff --git a/.github/workflows/githubci.yml b/.github/workflows/githubci.yml new file mode 100644 index 0000000..6061569 --- /dev/null +++ b/.github/workflows/githubci.yml @@ -0,0 +1,32 @@ +name: Arduino Library CI + +on: [pull_request, push, repository_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + with: + repository: adafruit/ci-arduino + path: ci + + - name: Install the prerequisites + run: bash ci/actions_install.sh + + - name: Check for correct code formatting with clang-format + run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . + + - name: Check for correct documentation with doxygen + env: + GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} + PRETTYNAME : "Adafruit Bus Ublox Library" + run: bash ci/doxy_gen_and_deploy.sh + + - name: Test the code on supported platforms + run: python3 ci/build_platform.py main_platforms \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1daef4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Our handy .gitignore for automation ease +Doxyfile* +doxygen_sqlite3.db +html \ No newline at end of file diff --git a/Adafruit_UBX.cpp b/Adafruit_UBX.cpp index c0b7b6b..c1a4b80 100644 --- a/Adafruit_UBX.cpp +++ b/Adafruit_UBX.cpp @@ -1,38 +1,48 @@ /*! * @file Adafruit_UBX.cpp - * + * * @mainpage Arduino library for UBX protocol from u-blox GPS/RTK modules - * + * * @section intro_sec Introduction - * - * This is a library for parsing UBX protocol messages from u-blox GPS/RTK modules. - * It works with any Stream-based interface including UART and DDC (I2C). - * - * Designed specifically to work with u-blox GPS/RTK modules + * + * This is a library for parsing UBX protocol messages from u-blox GPS/RTK + * modules. It works with any Stream-based interface including UART and DDC + * (I2C). + * + * Designed specifically to work with u-blox GPS/RTK modules * like NEO-M8P, ZED-F9P, etc. - * + * * @section author Author - * - * Written by Your Name for Project Name. - * + * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * * @section license License - * + * * MIT license, all text above must be included in any redistribution */ #include "Adafruit_UBX.h" - - /*! * @brief Constructor - * @param stream Reference to a Stream object (Serial, Adafruit_UBloxDDC, etc.) + * @param stream Reference to a Stream object (Serial, Adafruit_UBloxDDC, + * etc.) */ Adafruit_UBX::Adafruit_UBX(Stream &stream) { _stream = &stream; onUBXMessage = NULL; } +/*! + * @brief Destructor + */ +Adafruit_UBX::~Adafruit_UBX() { + if (_stream) + delete _stream; + if (onUBXMessage) + onUBXMessage = NULL; +} + /*! * @brief Initializes the UBX parser * @return Always returns true (initialization is trivial) @@ -42,9 +52,6 @@ bool Adafruit_UBX::begin() { return true; } - - - /*! * @brief Configure the GPS module to output only UBX protocol (disables NMEA) * @param portID Port identifier (UBX_PORT_DDC, UBX_PORT_UART1, etc.) @@ -52,58 +59,60 @@ bool Adafruit_UBX::begin() { * @param timeout_ms Maximum time to wait for acknowledgment in milliseconds * @return UBXSendStatus indicating success, failure, or timeout */ -UBXSendStatus Adafruit_UBX::setUBXOnly(UBXPortId portID, bool checkAck, uint16_t timeout_ms) { +UBXSendStatus Adafruit_UBX::setUBXOnly(UBXPortId portID, bool checkAck, + uint16_t timeout_ms) { UBX_CFG_PRT_t cfgPrt; - + // Zero out the structure memset(&cfgPrt, 0, sizeof(cfgPrt)); - + // Set the port ID cfgPrt.fields.portID = portID; - + // Configure the port appropriately switch (portID) { - case UBX_PORT_DDC: // I2C/DDC port - // Set the I2C address to 0x42 (the default) - // For DDC, the mode field contains the I2C address in bits 7:1 - cfgPrt.fields.mode = 0x42 << 1; // 0x84 - - // Set protocol masks to UBX only - cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; - cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; - break; - - case UBX_PORT_UART1: // Fall through - case UBX_PORT_UART2: // UART ports - // Keep current baud rate (baudRate = 0 keeps current setting) - // Set 8N1 mode for binary protocol - cfgPrt.fields.mode = UBX_UART_MODE_8N1; - // Set protocol masks to UBX only - cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; - cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; - break; - - case UBX_PORT_USB: // USB port - // Set protocol masks to UBX only - cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; - cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; - break; - - case UBX_PORT_SPI: // SPI port - // Set protocol masks to UBX only - cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; - break; - - default: - if (verbose_debug > 0) { - Serial.println(F("UBX: Invalid port ID")); - } - return UBX_SEND_FAIL; // Invalid port ID + case UBX_PORT_DDC: // I2C/DDC port + // Set the I2C address to 0x42 (the default) + // For DDC, the mode field contains the I2C address in bits 7:1 + cfgPrt.fields.mode = 0x42 << 1; // 0x84 + + // Set protocol masks to UBX only + cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; + cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; + break; + + case UBX_PORT_UART1: // Fall through + case UBX_PORT_UART2: // UART ports + // Keep current baud rate (baudRate = 0 keeps current setting) + // Set 8N1 mode for binary protocol + cfgPrt.fields.mode = UBX_UART_MODE_8N1; + // Set protocol masks to UBX only + cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; + cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; + break; + + case UBX_PORT_USB: // USB port + // Set protocol masks to UBX only + cfgPrt.fields.inProtoMask = UBX_PROTOCOL_UBX; + cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; + break; + + case UBX_PORT_SPI: // SPI port + // Set protocol masks to UBX only + cfgPrt.fields.outProtoMask = UBX_PROTOCOL_UBX; + break; + + default: + if (verbose_debug > 0) { + Serial.println(F("UBX: Invalid port ID")); + } + return UBX_SEND_FAIL; // Invalid port ID } - + // Send the message and wait for acknowledgment if requested if (checkAck) { - return sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_PRT, cfgPrt.raw, sizeof(cfgPrt), timeout_ms); + return sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_PRT, cfgPrt.raw, + sizeof(cfgPrt), timeout_ms); } else { if (sendMessage(UBX_CLASS_CFG, UBX_CFG_PRT, cfgPrt.raw, sizeof(cfgPrt))) { return UBX_SEND_SUCCESS; @@ -113,12 +122,10 @@ UBXSendStatus Adafruit_UBX::setUBXOnly(UBXPortId portID, bool checkAck, uint16_t } } - - - /*! * @brief Sets the callback function for UBX messages - * @param callback Function pointer to call when a complete UBX message is received + * @param callback Function pointer to call when a complete UBX message is + * received */ void Adafruit_UBX::setMessageCallback(UBXMessageCallback callback) { onUBXMessage = callback; @@ -140,10 +147,11 @@ void Adafruit_UBX::resetParser() { * @param checksumA Reference to store the first checksum byte * @param checksumB Reference to store the second checksum byte */ -void Adafruit_UBX::calculateChecksum(uint8_t *buffer, uint16_t len, uint8_t &checksumA, uint8_t &checksumB) { +void Adafruit_UBX::calculateChecksum(uint8_t *buffer, uint16_t len, + uint8_t &checksumA, uint8_t &checksumB) { checksumA = 0; checksumB = 0; - + for (uint16_t i = 0; i < len; i++) { checksumA += buffer[i]; checksumB += checksumA; @@ -156,150 +164,158 @@ void Adafruit_UBX::calculateChecksum(uint8_t *buffer, uint16_t len, uint8_t &che */ bool Adafruit_UBX::checkMessages() { bool messageReceived = false; - + // Process all available bytes while (_stream->available()) { uint8_t incomingByte = _stream->read(); - + // State machine for UBX protocol parsing switch (_parserState) { - case WAIT_SYNC_1: - if (incomingByte == UBX_SYNC_CHAR_1) { - _parserState = WAIT_SYNC_2; - _buffer[0] = incomingByte; // Store for checksum calculation - } - break; - - case WAIT_SYNC_2: - if (incomingByte == UBX_SYNC_CHAR_2) { - _parserState = GET_CLASS; - _buffer[1] = incomingByte; // Store for checksum calculation - } else { - resetParser(); // Invalid sync char, reset - } - break; - - case GET_CLASS: - _msgClass = incomingByte; - _buffer[2] = incomingByte; // Store for checksum calculation - _parserState = GET_ID; - break; - - case GET_ID: - _msgId = incomingByte; - _buffer[3] = incomingByte; // Store for checksum calculation - _parserState = GET_LENGTH_1; - break; - - case GET_LENGTH_1: - _payloadLength = incomingByte; - _buffer[4] = incomingByte; // Store for checksum calculation - _parserState = GET_LENGTH_2; - break; - - case GET_LENGTH_2: - _payloadLength |= (incomingByte << 8); - _buffer[5] = incomingByte; // Store for checksum calculation - - if (_payloadLength > MAX_PAYLOAD_SIZE) { - resetParser(); // Payload too large, reset - } else { - _payloadCounter = 0; - _parserState = GET_PAYLOAD; - } - break; - - case GET_PAYLOAD: - if (_payloadCounter < _payloadLength) { - _buffer[6 + _payloadCounter] = incomingByte; - _payloadCounter++; - - if (_payloadCounter == _payloadLength) { - _parserState = GET_CHECKSUM_A; - } - } - break; - - case GET_CHECKSUM_A: - // Calculate expected checksum - calculateChecksum(_buffer + 2, _payloadLength + 4, _checksumA, _checksumB); - - if (incomingByte == _checksumA) { - _parserState = GET_CHECKSUM_B; // Checksum A matches - } else { - resetParser(); // Invalid checksum, reset - } - break; - - case GET_CHECKSUM_B: - if (incomingByte == _checksumB) { - // We have a valid message! - if (onUBXMessage != NULL) { - onUBXMessage(_msgClass, _msgId, _payloadLength, _buffer + 6); // Call the callback with the message - } - messageReceived = true; + case WAIT_SYNC_1: + if (incomingByte == UBX_SYNC_CHAR_1) { + _parserState = WAIT_SYNC_2; + _buffer[0] = incomingByte; // Store for checksum calculation + } + break; - _lastMsgClass = _msgClass; - _lastMsgId = _msgId; - _lastPayloadLength = _payloadLength; - - // Store a small copy of the payload if it's within size limits - if (_payloadLength <= sizeof(_lastPayload)) { - memcpy(_lastPayload, _buffer + 6, _payloadLength); - } + case WAIT_SYNC_2: + if (incomingByte == UBX_SYNC_CHAR_2) { + _parserState = GET_CLASS; + _buffer[1] = incomingByte; // Store for checksum calculation + } else { + resetParser(); // Invalid sync char, reset + } + break; - if (verbose_debug > 0) { - Serial.print("UBX RX: "); - - // Print header (sync chars, class, id, length) - Serial.print("HDR[B5 62 "); - if (_msgClass < 0x10) Serial.print("0"); - Serial.print(_msgClass, HEX); - Serial.print(" "); - if (_msgId < 0x10) Serial.print("0"); - Serial.print(_msgId, HEX); - Serial.print(" "); - - uint8_t lenLSB = _payloadLength & 0xFF; - uint8_t lenMSB = (_payloadLength >> 8) & 0xFF; - if (lenLSB < 0x10) Serial.print("0"); - Serial.print(lenLSB, HEX); - Serial.print(" "); - if (lenMSB < 0x10) Serial.print("0"); - Serial.print(lenMSB, HEX); - Serial.print("] "); - - // Print payload if verbose debug is enabled - if (verbose_debug > 1 && _payloadLength > 0) { - Serial.print("PL["); - for (uint16_t i = 0; i < _payloadLength; i++) { - if (_buffer[6 + i] < 0x10) Serial.print("0"); - Serial.print(_buffer[6 + i], HEX); - Serial.print(" "); - } - Serial.print("] "); + case GET_CLASS: + _msgClass = incomingByte; + _buffer[2] = incomingByte; // Store for checksum calculation + _parserState = GET_ID; + break; + + case GET_ID: + _msgId = incomingByte; + _buffer[3] = incomingByte; // Store for checksum calculation + _parserState = GET_LENGTH_1; + break; + + case GET_LENGTH_1: + _payloadLength = incomingByte; + _buffer[4] = incomingByte; // Store for checksum calculation + _parserState = GET_LENGTH_2; + break; + + case GET_LENGTH_2: + _payloadLength |= (incomingByte << 8); + _buffer[5] = incomingByte; // Store for checksum calculation + + if (_payloadLength > MAX_PAYLOAD_SIZE) { + resetParser(); // Payload too large, reset + } else { + _payloadCounter = 0; + _parserState = GET_PAYLOAD; + } + break; + + case GET_PAYLOAD: + if (_payloadCounter < _payloadLength) { + _buffer[6 + _payloadCounter] = incomingByte; + _payloadCounter++; + + if (_payloadCounter == _payloadLength) { + _parserState = GET_CHECKSUM_A; + } + } + break; + + case GET_CHECKSUM_A: + // Calculate expected checksum + calculateChecksum(_buffer + 2, _payloadLength + 4, _checksumA, + _checksumB); + + if (incomingByte == _checksumA) { + _parserState = GET_CHECKSUM_B; // Checksum A matches + } else { + resetParser(); // Invalid checksum, reset + } + break; + + case GET_CHECKSUM_B: + if (incomingByte == _checksumB) { + // We have a valid message! + if (onUBXMessage != NULL) { + onUBXMessage(_msgClass, _msgId, _payloadLength, + _buffer + 6); // Call the callback with the message + } + messageReceived = true; + + _lastMsgClass = _msgClass; + _lastMsgId = _msgId; + _lastPayloadLength = _payloadLength; + + // Store a small copy of the payload if it's within size limits + if (_payloadLength <= sizeof(_lastPayload)) { + memcpy(_lastPayload, _buffer + 6, _payloadLength); + } + + if (verbose_debug > 0) { + Serial.print("UBX RX: "); + + // Print header (sync chars, class, id, length) + Serial.print("HDR[B5 62 "); + if (_msgClass < 0x10) + Serial.print("0"); + Serial.print(_msgClass, HEX); + Serial.print(" "); + if (_msgId < 0x10) + Serial.print("0"); + Serial.print(_msgId, HEX); + Serial.print(" "); + + uint8_t lenLSB = _payloadLength & 0xFF; + uint8_t lenMSB = (_payloadLength >> 8) & 0xFF; + if (lenLSB < 0x10) + Serial.print("0"); + Serial.print(lenLSB, HEX); + Serial.print(" "); + if (lenMSB < 0x10) + Serial.print("0"); + Serial.print(lenMSB, HEX); + Serial.print("] "); + + // Print payload if verbose debug is enabled + if (verbose_debug > 1 && _payloadLength > 0) { + Serial.print("PL["); + for (uint16_t i = 0; i < _payloadLength; i++) { + if (_buffer[6 + i] < 0x10) + Serial.print("0"); + Serial.print(_buffer[6 + i], HEX); + Serial.print(" "); } - - // Print checksum - Serial.print("CS["); - if (_checksumA < 0x10) Serial.print("0"); - Serial.print(_checksumA, HEX); - Serial.print(" "); - if (_checksumB < 0x10) Serial.print("0"); - Serial.print(_checksumB, HEX); - Serial.println("]"); + Serial.print("] "); } + + // Print checksum + Serial.print("CS["); + if (_checksumA < 0x10) + Serial.print("0"); + Serial.print(_checksumA, HEX); + Serial.print(" "); + if (_checksumB < 0x10) + Serial.print("0"); + Serial.print(_checksumB, HEX); + Serial.println("]"); } - - resetParser(); // Reset for next message - break; + } + + resetParser(); // Reset for next message + break; } } - + return messageReceived; } - /*! * @brief Send a UBX message and wait for acknowledgment * @param msgClass Message class @@ -309,7 +325,10 @@ bool Adafruit_UBX::checkMessages() { * @param timeout_ms Maximum time to wait for acknowledgment * @return UBXSendStatus indicating success, failure, or timeout */ -UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, uint8_t *payload, uint16_t length, uint16_t timeout_ms = 500) { +UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, + uint8_t *payload, + uint16_t length, + uint16_t timeout_ms = 500) { // First send the message if (!sendMessage(msgClass, msgId, payload, length)) { if (verbose_debug > 0) { @@ -317,9 +336,9 @@ UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, } return UBX_SEND_FAIL; } - + uint32_t startTime = millis(); - + // Check for messages until timeout while ((millis() - startTime) < timeout_ms) { // Process incoming bytes @@ -332,10 +351,12 @@ UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, if (_lastPayload[0] == msgClass && _lastPayload[1] == msgId) { if (verbose_debug > 0) { Serial.print(F("UBX ACK: SUCCESS for message class 0x")); - if (msgClass < 0x10) Serial.print("0"); + if (msgClass < 0x10) + Serial.print("0"); Serial.print(msgClass, HEX); Serial.print(" ID 0x"); - if (msgId < 0x10) Serial.print("0"); + if (msgId < 0x10) + Serial.print("0"); Serial.println(msgId, HEX); } return UBX_SEND_SUCCESS; @@ -345,10 +366,12 @@ UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, if (_lastPayload[0] == msgClass && _lastPayload[1] == msgId) { if (verbose_debug > 0) { Serial.print(F("UBX ACK: NAK for message class 0x")); - if (msgClass < 0x10) Serial.print("0"); + if (msgClass < 0x10) + Serial.print("0"); Serial.print(msgClass, HEX); Serial.print(" ID 0x"); - if (msgId < 0x10) Serial.print("0"); + if (msgId < 0x10) + Serial.print("0"); Serial.println(msgId, HEX); } return UBX_SEND_NAK; @@ -356,96 +379,105 @@ UBXSendStatus Adafruit_UBX::sendMessageWithAck(uint8_t msgClass, uint8_t msgId, } } } - + // Short delay delay(1); } - + if (verbose_debug > 0) { - Serial.print(F("UBX ACK: TIMEOUT waiting for ACK/NAK for message class 0x")); - if (msgClass < 0x10) Serial.print("0"); + Serial.print( + F("UBX ACK: TIMEOUT waiting for ACK/NAK for message class 0x")); + if (msgClass < 0x10) + Serial.print("0"); Serial.print(msgClass, HEX); Serial.print(" ID 0x"); - if (msgId < 0x10) Serial.print("0"); + if (msgId < 0x10) + Serial.print("0"); Serial.println(msgId, HEX); } - + return UBX_SEND_TIMEOUT; } - /*! * @brief Send a UBX message to the GPS module * @param msgClass Message class * @param msgId Message ID - * @param payload Pointer to the payload data (can be NULL for zero-length payload) + * @param payload Pointer to the payload data (can be NULL for zero-length + * payload) * @param length Length of payload * @return True if message was sent successfully */ -bool Adafruit_UBX::sendMessage(uint8_t msgClass, uint8_t msgId, uint8_t *payload, uint16_t length) { - // Buffer for message (2 sync chars + class + id + 2 length bytes + payload + 2 checksum bytes) +bool Adafruit_UBX::sendMessage(uint8_t msgClass, uint8_t msgId, + uint8_t *payload, uint16_t length) { + // Buffer for message (2 sync chars + class + id + 2 length bytes + payload + + // 2 checksum bytes) uint8_t msgBuffer[length + 8]; - + // Sync characters msgBuffer[0] = UBX_SYNC_CHAR_1; msgBuffer[1] = UBX_SYNC_CHAR_2; - + // Message class and ID msgBuffer[2] = msgClass; msgBuffer[3] = msgId; - + // Length (little endian) msgBuffer[4] = length & 0xFF; msgBuffer[5] = (length >> 8) & 0xFF; - + // Payload if (payload != NULL && length > 0) { memcpy(&msgBuffer[6], payload, length); } - + // Calculate checksum uint8_t checksumA, checksumB; calculateChecksum(&msgBuffer[2], length + 4, checksumA, checksumB); - + msgBuffer[6 + length] = checksumA; msgBuffer[7 + length] = checksumB; - + // Debug output if (verbose_debug > 0) { Serial.print("UBX TX: "); - + // Print header (sync chars, class, id, length) Serial.print("HDR["); for (int i = 0; i < 6; i++) { - if (msgBuffer[i] < 0x10) Serial.print("0"); + if (msgBuffer[i] < 0x10) + Serial.print("0"); Serial.print(msgBuffer[i], HEX); Serial.print(" "); } Serial.print("] "); - + // Print payload if verbose debug is enabled if (verbose_debug > 1 && length > 0) { Serial.print("PL["); for (uint16_t i = 0; i < length; i++) { - if (msgBuffer[6 + i] < 0x10) Serial.print("0"); + if (msgBuffer[6 + i] < 0x10) + Serial.print("0"); Serial.print(msgBuffer[6 + i], HEX); Serial.print(" "); } Serial.print("] "); } - + // Print checksum Serial.print("CS["); - if (checksumA < 0x10) Serial.print("0"); + if (checksumA < 0x10) + Serial.print("0"); Serial.print(checksumA, HEX); Serial.print(" "); - if (checksumB < 0x10) Serial.print("0"); + if (checksumB < 0x10) + Serial.print("0"); Serial.print(checksumB, HEX); Serial.println("]"); } - + // Send the message size_t written = _stream->write(msgBuffer, length + 8); - + return (written == length + 8); } diff --git a/Adafruit_UBX.h b/Adafruit_UBX.h index 6691e12..f68f3af 100644 --- a/Adafruit_UBX.h +++ b/Adafruit_UBX.h @@ -1,98 +1,110 @@ /*! * @file Adafruit_UBX.h - * + * * Arduino library for parsing UBX protocol from u-blox GPS/RTK modules. - * + * * This library can use any Stream object as input (UART, DDC, or other). - * + * * Adafruit invests time and resources providing this open source code, * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Written by Your Name for Project Name. - * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * * MIT license, all text here must be included in any redistribution. */ #ifndef ADAFRUIT_UBX_H #define ADAFRUIT_UBX_H +#include "Adafruit_uBlox_Ubx_Messages.h" +#include "Adafruit_uBlox_typedef.h" #include #include -#include "Adafruit_uBlox_typedef.h" // UBX protocol constants -#define UBX_SYNC_CHAR_1 0xB5 // First UBX protocol sync char (µ) -#define UBX_SYNC_CHAR_2 0x62 // Second UBX protocol sync char (b) +#define UBX_SYNC_CHAR_1 0xB5 ///< First UBX protocol sync char (�) +#define UBX_SYNC_CHAR_2 0x62 ///< Second UBX protocol sync char (b) // UBX ACK Message IDs -#define UBX_ACK_NAK 0x00 // Message Not Acknowledged -#define UBX_ACK_ACK 0x01 // Message Acknowledged - -// Callback function type for UBX messages - defined at global scope so other classes can use it -typedef void (*UBXMessageCallback)(uint8_t msgClass, uint8_t msgId, uint16_t payloadLen, uint8_t *payload); +#define UBX_ACK_NAK 0x00 ///< Message Not Acknowledged +#define UBX_ACK_ACK 0x01 ///< Message Acknowledged +/*! + * @brief Callback function type for UBX messages - defined at global scope so + * other classes can use it + * @param msgClass Message class + * @param msgId Message ID + * @param payloadLen Length of payload data + * @param payload Pointer to payload data + */ +typedef void (*UBXMessageCallback)(uint8_t msgClass, uint8_t msgId, + uint16_t payloadLen, uint8_t *payload); /*! * @brief Class for parsing UBX protocol messages from u-blox GPS/RTK modules */ class Adafruit_UBX { - public: - // Constructor - Adafruit_UBX(Stream &stream); - uint8_t verbose_debug = 0; // 0=off, 1=basic, 2=verbose +public: + Adafruit_UBX(Stream &stream); + ~Adafruit_UBX(); + uint8_t verbose_debug = 0; ///< 0=off, 1=basic, 2=verbose + // Basic methods + bool begin(); + bool checkMessages(); // Message parsing + bool sendMessage(uint8_t msgClass, uint8_t msgId, uint8_t *payload, + uint16_t length); // Send a UBX message + UBXSendStatus sendMessageWithAck(uint8_t msgClass, uint8_t msgId, + uint8_t *payload, uint16_t length, + uint16_t timeout_ms = 500); - // Basic methods - bool begin(); - - bool checkMessages(); // Message parsing - bool sendMessage(uint8_t msgClass, uint8_t msgId, uint8_t *payload, uint16_t length); // Send a UBX message - UBXSendStatus sendMessageWithAck(uint8_t msgClass, uint8_t msgId, uint8_t *payload, uint16_t length, uint16_t timeout_ms = 500); + // Configure port to use UBX protocol only (disable NMEA) + UBXSendStatus setUBXOnly(UBXPortId portID, bool checkAck = true, + uint16_t timeout_ms = 500); - // Configure port to use UBX protocol only (disable NMEA) - UBXSendStatus setUBXOnly(UBXPortId portID, bool checkAck = true, uint16_t timeout_ms = 500); + void setMessageCallback(UBXMessageCallback callback); // Set callback function + UBXMessageCallback onUBXMessage; ///< Callback for message received - void setMessageCallback(UBXMessageCallback callback); // Set callback function - UBXMessageCallback onUBXMessage; // Callback for message received +private: + Stream *_stream; // Stream interface for reading data - private: - Stream *_stream; // Stream interface for reading data - - // Buffer for reading messages - static const uint16_t MAX_PAYLOAD_SIZE = 64; // Maximum UBX payload size - uint8_t _buffer[MAX_PAYLOAD_SIZE + 8]; // Buffer for message (header, payload, checksum) - - // Parser state machine - enum ParserState { - WAIT_SYNC_1, // Waiting for first sync char (0xB5) - WAIT_SYNC_2, // Waiting for second sync char (0x62) - GET_CLASS, // Reading message class - GET_ID, // Reading message ID - GET_LENGTH_1, // Reading length LSB - GET_LENGTH_2, // Reading length MSB - GET_PAYLOAD, // Reading payload - GET_CHECKSUM_A, // Reading checksum A - GET_CHECKSUM_B // Reading checksum B - }; - - ParserState _parserState = WAIT_SYNC_1; // Current state of the parser - uint8_t _msgClass; // Message class of current message - uint8_t _msgId; // Message ID of current message - uint16_t _payloadLength; // Length of current message payload - uint16_t _payloadCounter; // Counter for payload bytes received - uint8_t _checksumA; // Running checksum A - uint8_t _checksumB; // Running checksum B - - // Calculate checksum for a block of data - void calculateChecksum(uint8_t *buffer, uint16_t len, uint8_t &checksumA, uint8_t &checksumB); - - // Reset parser state - void resetParser(); + // Buffer for reading messages + static const uint16_t MAX_PAYLOAD_SIZE = 64; // Maximum UBX payload size + uint8_t _buffer[MAX_PAYLOAD_SIZE + + 8]; // Buffer for message (header, payload, checksum) - // Add to private section of Adafruit_UBX.h - uint8_t _lastMsgClass; // Class of last message - uint8_t _lastMsgId; // ID of last message - uint16_t _lastPayloadLength; // Length of last message payload - uint8_t _lastPayload[8]; // Buffer for small payloads (like ACK messages) + // Parser state machine + enum ParserState { + WAIT_SYNC_1, // Waiting for first sync char (0xB5) + WAIT_SYNC_2, // Waiting for second sync char (0x62) + GET_CLASS, // Reading message class + GET_ID, // Reading message ID + GET_LENGTH_1, // Reading length LSB + GET_LENGTH_2, // Reading length MSB + GET_PAYLOAD, // Reading payload + GET_CHECKSUM_A, // Reading checksum A + GET_CHECKSUM_B // Reading checksum B + }; + + ParserState _parserState = WAIT_SYNC_1; // Current state of the parser + uint8_t _msgClass; // Message class of current message + uint8_t _msgId; // Message ID of current message + uint16_t _payloadLength; // Length of current message payload + uint16_t _payloadCounter; // Counter for payload bytes received + uint8_t _checksumA; // Running checksum A + uint8_t _checksumB; // Running checksum B + + // Calculate checksum for a block of data + void calculateChecksum(uint8_t *buffer, uint16_t len, uint8_t &checksumA, + uint8_t &checksumB); + + // Reset parser state + void resetParser(); + + // Add to private section of Adafruit_UBX.h + uint8_t _lastMsgClass; // Class of last message + uint8_t _lastMsgId; // ID of last message + uint16_t _lastPayloadLength; // Length of last message payload + uint8_t _lastPayload[8]; // Buffer for small payloads (like ACK messages) }; #endif // ADAFRUIT_UBX_H diff --git a/Adafruit_UBloxDDC.cpp b/Adafruit_UBloxDDC.cpp index b4c8e95..a7b5d09 100644 --- a/Adafruit_UBloxDDC.cpp +++ b/Adafruit_UBloxDDC.cpp @@ -1,26 +1,24 @@ /*! * @file Adafruit_UBloxDDC.cpp - * - * @mainpage Arduino library for u-blox GPS/RTK modules over DDC (I2C) - * - * @section intro_sec Introduction - * + * + * @section ddc_intro_sec Introduction + * * This is a library for the u-blox GPS/RTK modules using I2C interface (DDC) - * - * Designed specifically to work with u-blox GPS/RTK modules + * + * Designed specifically to work with u-blox GPS/RTK modules * like NEO-M8P, ZED-F9P, etc. - * - * @section dependencies Dependencies - * + * + * @section ddc_dependencies Dependencies + * * This library depends on: * Adafruit_BusIO - * - * @section author Author - * - * Written by Your Name for Project Name. - * - * @section license License - * + * + * @section ddc_author Author + * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * + * @section ddc_license License + * * MIT license, all text above must be included in any redistribution */ @@ -50,9 +48,7 @@ Adafruit_UBloxDDC::~Adafruit_UBloxDDC() { * @brief Initializes the GPS module and I2C interface * @return True if GPS module responds, false on any failure */ -bool Adafruit_UBloxDDC::begin() { - return _i2cDevice->begin(); -} +bool Adafruit_UBloxDDC::begin() { return _i2cDevice->begin(); } /*! * @brief Gets the number of bytes available for reading @@ -60,17 +56,18 @@ bool Adafruit_UBloxDDC::begin() { */ int Adafruit_UBloxDDC::available() { uint8_t buffer[2]; - + // Create a register for reading bytes available - Adafruit_BusIO_Register bytesAvailableReg = Adafruit_BusIO_Register(_i2cDevice, REG_BYTES_AVAILABLE_MSB, 2); - + Adafruit_BusIO_Register bytesAvailableReg = + Adafruit_BusIO_Register(_i2cDevice, REG_BYTES_AVAILABLE_MSB, 2); + if (!bytesAvailableReg.read(buffer, 2)) { return 0; } - + uint16_t bytesAvailable = (uint16_t)buffer[0] << 8; bytesAvailable |= buffer[1]; - + return bytesAvailable; } @@ -84,16 +81,17 @@ int Adafruit_UBloxDDC::read() { _hasPeeked = false; return _lastByte; } - + uint8_t value; - + // Create a register for the data stream - Adafruit_BusIO_Register dataStreamReg = Adafruit_BusIO_Register(_i2cDevice, REG_DATA_STREAM, 1); - + Adafruit_BusIO_Register dataStreamReg = + Adafruit_BusIO_Register(_i2cDevice, REG_DATA_STREAM, 1); + if (!dataStreamReg.read(&value, 1)) { return -1; } - + return value; } @@ -106,13 +104,13 @@ int Adafruit_UBloxDDC::peek() { if (_hasPeeked) { return _lastByte; } - + // Otherwise, read a byte and store it _lastByte = read(); if (_lastByte != -1) { _hasPeeked = true; } - + return _lastByte; } @@ -139,7 +137,7 @@ size_t Adafruit_UBloxDDC::write(const uint8_t *buffer, size_t size) { // Single-byte writes aren't supported return 0; } - + // Use Adafruit_BusIO to handle the I2C transaction if (_i2cDevice->write(buffer, size)) { return size; @@ -153,42 +151,43 @@ size_t Adafruit_UBloxDDC::write(const uint8_t *buffer, size_t size) { * @param length Maximum number of bytes to read * @return Number of bytes actually read, which may be less than requested */ -uint16_t Adafruit_UBloxDDC::readBytes(uint8_t* buffer, uint16_t length) { +uint16_t Adafruit_UBloxDDC::readBytes(uint8_t *buffer, uint16_t length) { if (buffer == nullptr || length == 0) { return 0; } - + uint16_t bytesRead = 0; uint16_t bytesAvailable = available(); - + // Don't try to read more bytes than are available length = min(length, bytesAvailable); - + // Handle any peeked byte first if (_hasPeeked && length > 0) { buffer[0] = _lastByte; _hasPeeked = false; bytesRead = 1; } - + if (bytesRead >= length) { return bytesRead; } - + // Create a register for the data stream - Adafruit_BusIO_Register dataStreamReg = Adafruit_BusIO_Register(_i2cDevice, REG_DATA_STREAM, 1); - + Adafruit_BusIO_Register dataStreamReg = + Adafruit_BusIO_Register(_i2cDevice, REG_DATA_STREAM, 1); + while (bytesRead < length) { // Calculate chunk size (I2C has a limit on bytes per transfer) - uint16_t chunkSize = min(length - bytesRead, (uint16_t)32); - + uint16_t chunkSize = min((uint16_t)(length - bytesRead), (uint16_t)32); + if (!dataStreamReg.read(&buffer[bytesRead], chunkSize)) { break; } - + bytesRead += chunkSize; } - + return bytesRead; } @@ -198,13 +197,13 @@ uint16_t Adafruit_UBloxDDC::readBytes(uint8_t* buffer, uint16_t length) { * @param maxLength Maximum length of buffer * @return Number of bytes read into the buffer */ -uint16_t Adafruit_UBloxDDC::readMessage(uint8_t* buffer, uint16_t maxLength) { +uint16_t Adafruit_UBloxDDC::readMessage(uint8_t *buffer, uint16_t maxLength) { uint16_t bytesAvailable = available(); - + if (bytesAvailable == 0) { return 0; } - + // Limit to buffer size uint16_t bytesToRead = min(bytesAvailable, maxLength); return readBytes(buffer, bytesToRead); @@ -215,8 +214,7 @@ uint16_t Adafruit_UBloxDDC::readMessage(uint8_t* buffer, uint16_t maxLength) { * @param messageLength Pointer to variable to store message length * @return Pointer to internal buffer containing the message */ -uint8_t* Adafruit_UBloxDDC::readMessage(uint16_t* messageLength) { +uint8_t *Adafruit_UBloxDDC::readMessage(uint16_t *messageLength) { *messageLength = readMessage(_buffer, MAX_BUFFER_SIZE); return _buffer; } - diff --git a/Adafruit_UBloxDDC.h b/Adafruit_UBloxDDC.h index b5138db..eb9f10b 100644 --- a/Adafruit_UBloxDDC.h +++ b/Adafruit_UBloxDDC.h @@ -1,68 +1,72 @@ /*! * @file Adafruit_UBloxDDC.h - * + * * Arduino library for interfacing with u-blox GPS/RTK modules over I2C (DDC). - * + * * This library implements the Stream interface, allowing it to be used with * existing GPS parsing libraries like TinyGPS++. - * + * * Adafruit invests time and resources providing this open source code, * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Written by Your Name for Project Name. - * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * * MIT license, all text here must be included in any redistribution. */ #ifndef ADAFRUIT_UBLOXDDC_H #define ADAFRUIT_UBLOXDDC_H -#include -#include #include #include +#include +#include /*! * @brief Arduino library for interfacing with u-blox GPS/RTK modules over I2C */ class Adafruit_UBloxDDC : public Stream { - private: - Adafruit_I2CDevice *_i2cDevice; ///< Underlying I2C device - - // Register addresses - static const uint8_t REG_DATA_STREAM = 0xFF; ///< Register for reading data stream - static const uint8_t REG_BYTES_AVAILABLE_MSB = 0xFD; ///< MSB of bytes available - static const uint8_t REG_BYTES_AVAILABLE_LSB = 0xFE; ///< LSB of bytes available - - // Buffer for reading messages - static const uint16_t MAX_BUFFER_SIZE = 128; ///< Maximum buffer size for messages - uint8_t _buffer[MAX_BUFFER_SIZE]; ///< Internal buffer for messages - - // Last byte read for peek() implementation - int _lastByte = -1; ///< Last byte read by peek() - bool _hasPeeked = false; ///< Indicates if we have a peeked byte waiting +private: + Adafruit_I2CDevice *_i2cDevice; ///< Underlying I2C device - public: - // Constructor & destructor - Adafruit_UBloxDDC(uint8_t address = 0x42, TwoWire *wire = &Wire); - ~Adafruit_UBloxDDC(); - - // Basic methods - bool begin(); - - // Stream interface implementation - virtual int available() override; - virtual int read() override; - virtual int peek() override; - // Stream interface implementation - virtual size_t write(uint8_t) override; - virtual size_t write(const uint8_t *buffer, size_t size) override; - - // Additional methods - uint16_t readBytes(uint8_t* buffer, uint16_t length); - uint16_t readMessage(uint8_t* buffer, uint16_t maxLength); - uint8_t* readMessage(uint16_t* messageLength); + // Register addresses + static const uint8_t REG_DATA_STREAM = + 0xFF; ///< Register for reading data stream + static const uint8_t REG_BYTES_AVAILABLE_MSB = + 0xFD; ///< MSB of bytes available + static const uint8_t REG_BYTES_AVAILABLE_LSB = + 0xFE; ///< LSB of bytes available + + // Buffer for reading messages + static const uint16_t MAX_BUFFER_SIZE = + 128; ///< Maximum buffer size for messages + uint8_t _buffer[MAX_BUFFER_SIZE]; ///< Internal buffer for messages + + // Last byte read for peek() implementation + int _lastByte = -1; ///< Last byte read by peek() + bool _hasPeeked = false; ///< Indicates if we have a peeked byte waiting + +public: + // Constructor & destructor + Adafruit_UBloxDDC(uint8_t address = 0x42, TwoWire *wire = &Wire); + ~Adafruit_UBloxDDC(); + + // Basic methods + bool begin(); + + // Stream interface implementation + virtual int available() override; + virtual int read() override; + virtual int peek() override; + // Stream interface implementation + virtual size_t write(uint8_t) override; + virtual size_t write(const uint8_t *buffer, size_t size) override; + + // Additional methods + uint16_t readBytes(uint8_t *buffer, uint16_t length); + uint16_t readMessage(uint8_t *buffer, uint16_t maxLength); + uint8_t *readMessage(uint16_t *messageLength); }; #endif // ADAFRUIT_UBLOXDDC_H diff --git a/Adafruit_uBlox_Ubx_Messages.h b/Adafruit_uBlox_Ubx_Messages.h new file mode 100644 index 0000000..cc62170 --- /dev/null +++ b/Adafruit_uBlox_Ubx_Messages.h @@ -0,0 +1,58 @@ +/*! + * @file Adafruit_uBlox_Ubx_Messages.h + * + * Pre-built UBX message payloads for common tasks + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * Written by Brent Rubell for Adafruit Industries. + * + * MIT license, all text here must be included in any redistribution. + */ + +#ifndef ADAFRUIT_UBLOX_UBX_MESSAGES +#define ADAFRUIT_UBLOX_UBX_MESSAGES + +#include + +// NMEA Message Configuration Commands (CFG-MSG) +// Format: {msgClass, msgID, rate_I2C, rate_UART1, rate_UART2, rate_USB, +// rate_SPI, reserved} + +// Enable specific NMEA message sentences to output on the DDC interface +static uint8_t UBX_CFG_MSG_NMEA_GGA_ENABLE[] = {0xF0, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GLL_ENABLE[] = {0xF0, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GSA_ENABLE[] = {0xF0, 0x02, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GSV_ENABLE[] = {0xF0, 0x03, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_RMC_ENABLE[] = {0xF0, 0x04, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_VTG_ENABLE[] = {0xF0, 0x05, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00}; +// Disable specific NMEA message sentences from outputting on the DDC interface +static uint8_t UBX_CFG_MSG_NMEA_GGA_DISABLE[] = {0xF0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GLL_DISABLE[] = {0xF0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GSA_DISABLE[] = {0xF0, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_GSV_DISABLE[] = {0xF0, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_RMC_DISABLE[] = {0xF0, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +static uint8_t UBX_CFG_MSG_NMEA_VTG_DISABLE[] = {0xF0, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + +// Navigation Rate Configuration Commands (CFG-RATE) +// Format: {measRate_ms (2 bytes), navRate, timeRef} +static uint8_t UBX_CFG_RATE_1HZ[] = {0xE8, 0x03, 0x01, 0x00, 0x01, 0x00}; +static uint8_t UBX_CFG_RATE_2HZ[] = {0xF4, 0x01, 0x01, 0x00, 0x01, 0x00}; +static uint8_t UBX_CFG_RATE_5HZ[] = {0xC8, 0x00, 0x01, 0x00, 0x01, 0x00}; +static uint8_t UBX_CFG_RATE_10HZ[] = {0x64, 0x00, 0x01, 0x00, 0x01, 0x00}; + +#endif // ADAFRUIT_UBLOX_UBX_MESSAGES \ No newline at end of file diff --git a/Adafruit_uBlox_typedef.h b/Adafruit_uBlox_typedef.h index f8e44b2..43352eb 100644 --- a/Adafruit_uBlox_typedef.h +++ b/Adafruit_uBlox_typedef.h @@ -1,14 +1,14 @@ /*! * @file Adafruit_uBlox_typedef.h - * + * * Type definitions for u-blox GPS/RTK module messages - * + * * Adafruit invests time and resources providing this open source code, * please support Adafruit and open-source hardware by purchasing * products from Adafruit! * - * Written by Your Name for Project Name. - * + * Written by Limor Fried/Ladyada for Adafruit Industries. + * * MIT license, all text here must be included in any redistribution. */ @@ -17,46 +17,46 @@ #include -// UBX Message Classes +/** UBX protocol message class identifiers. */ typedef enum { - UBX_CLASS_NAV = 0x01, // Navigation Results - UBX_CLASS_RXM = 0x02, // Receiver Manager Messages - UBX_CLASS_INF = 0x04, // Information Messages - UBX_CLASS_ACK = 0x05, // Acknowledgements - UBX_CLASS_CFG = 0x06, // Configuration - UBX_CLASS_UPD = 0x09, // Firmware Update - UBX_CLASS_MON = 0x0A, // Monitoring - UBX_CLASS_AID = 0x0B, // AssistNow Aiding - UBX_CLASS_TIM = 0x0D, // Timing - UBX_CLASS_ESF = 0x10, // External Sensor Fusion - UBX_CLASS_MGA = 0x13, // Multiple GNSS Assistance - UBX_CLASS_LOG = 0x21, // Logging - UBX_CLASS_SEC = 0x27, // Security - UBX_CLASS_HNR = 0x28, // High Rate Navigation - UBX_CLASS_NMEA = 0xF0 // NMEA Standard Messages + UBX_CLASS_NAV = 0x01, // Navigation Results + UBX_CLASS_RXM = 0x02, // Receiver Manager Messages + UBX_CLASS_INF = 0x04, // Information Messages + UBX_CLASS_ACK = 0x05, // Acknowledgements + UBX_CLASS_CFG = 0x06, // Configuration + UBX_CLASS_UPD = 0x09, // Firmware Update + UBX_CLASS_MON = 0x0A, // Monitoring + UBX_CLASS_AID = 0x0B, // AssistNow Aiding + UBX_CLASS_TIM = 0x0D, // Timing + UBX_CLASS_ESF = 0x10, // External Sensor Fusion + UBX_CLASS_MGA = 0x13, // Multiple GNSS Assistance + UBX_CLASS_LOG = 0x21, // Logging + UBX_CLASS_SEC = 0x27, // Security + UBX_CLASS_HNR = 0x28, // High Rate Navigation + UBX_CLASS_NMEA = 0xF0 // NMEA Standard Messages } UBXMessageClass; -// UBX CFG Message IDs +/** UBX CFG Message IDs. */ typedef enum { - UBX_CFG_PRT = 0x00, // Port Configuration - UBX_CFG_MSG = 0x01, // Message Configuration - UBX_CFG_RST = 0x04, // Reset Receiver - UBX_CFG_RATE = 0x08, // Navigation/Measurement Rate Settings - UBX_CFG_CFG = 0x09, // Clear, Save, and Load Configurations - UBX_CFG_NAVX5 = 0x23, // Navigation Engine Settings - UBX_CFG_GNSS = 0x3E, // GNSS Configuration - UBX_CFG_PMS = 0x86 // Power Mode Setup + UBX_CFG_PRT = 0x00, // Port Configuration + UBX_CFG_MSG = 0x01, // Message Configuration + UBX_CFG_RST = 0x04, // Reset Receiver + UBX_CFG_RATE = 0x08, // Navigation/Measurement Rate Settings + UBX_CFG_CFG = 0x09, // Clear, Save, and Load Configurations + UBX_CFG_NAVX5 = 0x23, // Navigation Engine Settings + UBX_CFG_GNSS = 0x3E, // GNSS Configuration + UBX_CFG_PMS = 0x86 // Power Mode Setup } UBXCfgMessageId; -// Return values for functions that wait for acknowledgment +/** Return values for functions that wait for acknowledgment. */ typedef enum { - UBX_SEND_SUCCESS = 0, // Message was acknowledged (ACK) - UBX_SEND_NAK, // Message was not acknowledged (NAK) - UBX_SEND_FAIL, // Failed to send the message - UBX_SEND_TIMEOUT // Timed out waiting for ACK/NAK + UBX_SEND_SUCCESS = 0, // Message was acknowledged (ACK) + UBX_SEND_NAK, // Message was not acknowledged (NAK) + UBX_SEND_FAIL, // Failed to send the message + UBX_SEND_TIMEOUT // Timed out waiting for ACK/NAK } UBXSendStatus; -// Port ID enum for different interfaces +/** Port ID enum for different interfaces. */ typedef enum { UBX_PORT_DDC = 0, // I2C / DDC port UBX_PORT_UART1 = 1, // UART1 port @@ -65,7 +65,7 @@ typedef enum { UBX_PORT_SPI = 4 // SPI port } UBXPortId; -// UART mode flags (Charlen, Parity & Stop bit settings) +/** UART mode flags (Charlen, Parity & Stop bit settings). */ typedef enum { UBX_UART_MODE_8N1 = 0x000, // 8-bit, no parity, 1 stop bit UBX_UART_MODE_8E1 = 0x100, // 8-bit, even parity, 1 stop bit @@ -82,26 +82,27 @@ typedef enum { } UBXUARTMode; // Protocol flags for inProtoMask and outProtoMask -#define UBX_PROTOCOL_UBX 0x0001 // UBX protocol -#define UBX_PROTOCOL_NMEA 0x0002 // NMEA protocol -#define UBX_PROTOCOL_RTCM 0x0004 // RTCM2 protocol (only for inProtoMask) -#define UBX_PROTOCOL_RTCM3 0x0020 // RTCM3 protocol +#define UBX_PROTOCOL_UBX 0x0001 ///< UBX protocol +#define UBX_PROTOCOL_NMEA 0x0002 ///< NMEA protocol +#define UBX_PROTOCOL_RTCM 0x0004 ///< RTCM2 protocol (only for inProtoMask) +#define UBX_PROTOCOL_RTCM3 0x0020 ///< RTCM3 protocol -// CFG-PRT (Port Configuration) Message -// Total size: 20 bytes +/** UBX CFG-PRT (Port Configuration) message structure. 20 bytes total. + */ typedef union { struct { - uint8_t portID; // Port identifier (0=DDC/I2C, 1=UART1, 2=UART2, 3=USB, 4=SPI) - uint8_t reserved1; // Reserved - uint16_t txReady; // TX ready PIN configuration - uint32_t mode; // UART mode (bit field) or Reserved for non-UART ports - uint32_t baudRate; // Baudrate in bits/second (UART only) - uint16_t inProtoMask; // Input protocol mask - uint16_t outProtoMask; // Output protocol mask - uint16_t flags; // Flags bit field - uint16_t reserved2; // Reserved - } fields; - uint8_t raw[20]; + uint8_t + portID; ///< Port identifier (0=DDC/I2C, 1=UART1, 2=UART2, 3=USB, 4=SPI) + uint8_t reserved1; ///< Reserved + uint16_t txReady; ///< TX ready PIN configuration + uint32_t mode; ///< UART mode (bit field) or Reserved for non-UART ports + uint32_t baudRate; ///< Baudrate in bits/second (UART only) + uint16_t inProtoMask; ///< Input protocol mask + uint16_t outProtoMask; ///< Output protocol mask + uint16_t flags; ///< Flags bit field + uint16_t reserved2; ///< Reserved + } fields; ///< Fields for CFG-PRT message + uint8_t raw[20]; ///< Raw byte array for CFG-PRT message } UBX_CFG_PRT_t; #endif // ADAFRUIT_UBLOX_TYPEDEF_H diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..860e3e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Adafruit Industries + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ca8732 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Adafruit uBlox Library [![Build Status](https://github.com/adafruit/Adafruit_uBlox/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_uBlox/actions) + +This is a driver library to abstract away the details of communicating with u-blox GPS and RTK modules. It provides a simple interface for sending and receiving data over a generic Stream interface. + +This library provides two main classes: +- `Adafruit_UBX`: Interfaces with u-blox GPS/RTK modules using any stream object as an input (UART, DDC, or other) and parses UBX protocol messages. +- `Adafruit_UBloxDDC`: Interface for communicating with u-blox modules over DDC (I2C). + +For parsing NMEA sentences, we provide an example for using this library with the [Adafruit_GPS library](https://github.com/adafruit/Adafruit_GPS). + +Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +MIT license, all text above must be included in any redistribution \ No newline at end of file diff --git a/examples/ublox_dcc.ino b/examples/ublox_dcc.ino new file mode 100644 index 0000000..9ff22a7 --- /dev/null +++ b/examples/ublox_dcc.ino @@ -0,0 +1,63 @@ +/*! + * @file ublox_ddc_example.ino + * + * Example sketch demonstrating the use of the Adafruit_UBloxDDC library + * with u-blox GPS/RTK modules over I2C (DDC) interface. + * + * This example simply streams all raw bytes from the GPS module to the + * Serial port so you can see the NMEA sentences in their original format. + * + * Written by Ladyada for Adafruit Industries. + * + * MIT license, all text above must be included in any redistribution + */ + +#include "Adafruit_UBloxDDC.h" + +// Create Adafruit_UBloxDDC object with default I2C address (0x42) +Adafruit_UBloxDDC gps; + +// Buffer for reading chunks of data +const size_t BUFFER_SIZE = 64; +uint8_t buffer[BUFFER_SIZE]; + +void setup() { + // Initialize serial port for debugging + Serial.begin(115200); + while (!Serial) { + ; // Wait for serial port to connect + } + + Serial.println("Adafruit UBlox DDC Raw NMEA Stream Example"); + + // Initialize GPS module + if (gps.begin()) { + Serial.println("GPS module found!"); + } else { + Serial.println("Failed to connect to GPS module!"); + while (1); // Don't proceed if we couldn't connect to the module + } + + Serial.println("Streaming raw data from GPS module..."); + Serial.println("------------------------------------"); +} + +void loop() { + // Check how many bytes are available + int bytesAvailable = gps.available(); + + if (bytesAvailable > 0) { + // Read up to BUFFER_SIZE bytes at a time + size_t bytesToRead = min(bytesAvailable, (int)BUFFER_SIZE); + size_t bytesRead = gps.readBytes(buffer, bytesToRead); + + // Stream the bytes directly to Serial + // This will show raw NMEA sentences + for (size_t i = 0; i < bytesRead; i++) { + Serial.write(buffer[i]); + } + } + + // Short delay to prevent overwhelming the serial monitor + delay(10); +} diff --git a/examples/ublox_ddc_parse_nmea.ino b/examples/ublox_ddc_parse_nmea.ino new file mode 100644 index 0000000..0cf5458 --- /dev/null +++ b/examples/ublox_ddc_parse_nmea.ino @@ -0,0 +1,192 @@ +/*! + * @file ublox_ddc_parse_nmea.ino + * + * Example sketch demonstrating parsing NMEA sentences from a u-blox GPS/RTK module + * using the Adafruit_GPS and Adafruit_UBX libraries. + * + * Written by Brent Rubell for Adafruit Industries. + * + * MIT license, all text above must be included in any redistribution + */ + +#include +#include +#include + +// Adafruit_UBloxDDC object with default I2C address (0x42) +Adafruit_UBloxDDC gps; +// Create Adafruit_UBX parser using the DDC object as input stream +Adafruit_UBX ubx(gps); +// Create Adafruit_GPS object for use as a NMEA parser only +Adafruit_GPS NMEA; + +// Buffer to store NMEA sentences from the module (may be partial sentences) +const size_t SZ_NMEA_BUFFER = 128; +uint8_t buffer[SZ_NMEA_BUFFER]; +uint32_t timer = millis(); + +/*! + * @brief Sets the NMEA output to only RMC and GGA sentences and waits for an + * acknowledgment. All other common NMEA sentences are disabled to increase + * loop() performance by decreasing the amount of messages output by the UBlox + * module. + * @return Status indicating success, failure, or timeout + */ +UBXSendStatus SetNMEAOutputRMCGGAOnly() { + UBXSendStatus status; + // Disable all common NMEA messages + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_GLL_DISABLE, + sizeof(UBX_CFG_MSG_NMEA_GLL_DISABLE)); + if (status != UBX_SEND_SUCCESS) + return status; + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_GSA_DISABLE, + sizeof(UBX_CFG_MSG_NMEA_GSA_DISABLE)); + if (status != UBX_SEND_SUCCESS) + return status; + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_GSV_DISABLE, + sizeof(UBX_CFG_MSG_NMEA_GSV_DISABLE)); + if (status != UBX_SEND_SUCCESS) + return status; + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_VTG_DISABLE, + sizeof(UBX_CFG_MSG_NMEA_VTG_DISABLE)); + if (status != UBX_SEND_SUCCESS) + return status; + // Enable only GGA and RMC messages + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_GGA_ENABLE, + sizeof(UBX_CFG_MSG_NMEA_GGA_ENABLE)); + if (status != UBX_SEND_SUCCESS) + return status; + status = ubx.sendMessageWithAck(UBX_CLASS_CFG, UBX_CFG_MSG, + UBX_CFG_MSG_NMEA_RMC_ENABLE, + sizeof(UBX_CFG_MSG_NMEA_RMC_ENABLE)); + return status; +} + +void setup() { + // Initialize serial port for debugging + Serial.begin(115200); + while (!Serial) { + ; // Wait for serial port to connect + } + + // Initialize GPS module + if (!gps.begin()) { + Serial.println("Failed to initialize GPS module!"); + while (1); // Halt if initialization fails + } + Serial.println("GPS module initialized successfully!"); + + // Initialize the u-blox parser + if (!ubx.begin()) { + Serial.println("Failed to initialize u-blox parser!"); + while (1); // Halt if initialization fails + } + Serial.println("u-blox parser initialized successfully!"); + ubx.verbose_debug = 3; // Set debug level to verbose + + // Set NMEA output on DDC to RMC and GGA sentences only + UBXSendStatus status = SetNMEAOutputRMCGGAOnly(); + if (status != UBX_SEND_SUCCESS) { + Serial.print("Failed to configure NMEA output [status code: "); + Serial.print(status); + Serial.println("]"); + while (1); // Halt if setting NMEA output fails + } +} + +void loop() { + static char nmeaSentenceBuf[SZ_NMEA_BUFFER]; + static int nmeaSentenceIdx = 0; + + // Check how many bytes are available + int bytesAvailable = gps.available(); + + if (bytesAvailable > 0) { + // Read up to SZ_NMEA_BUFFER bytes at a time + size_t bytesToRead = min(bytesAvailable, (int)SZ_NMEA_BUFFER); + size_t bytesRead = gps.readBytes(buffer, bytesToRead); + + // Build NMEA sentences and parse when complete + for (size_t i = 0; i < bytesRead; i++) { + char c = buffer[i]; + + // Add to buffer if space available + if (nmeaSentenceIdx < SZ_NMEA_BUFFER - 1) { + nmeaSentenceBuf[nmeaSentenceIdx++] = c; + } + + // Check for end of NMEA sentence + if (c == '\n') { + nmeaSentenceBuf[nmeaSentenceIdx] = '\0'; + + // Parse the NMEA sentence + if (NMEA.parse(nmeaSentenceBuf)) { + // Successfully parsed sentence! + Serial.println(nmeaSentenceBuf); + // approximately every 2 seconds or so, print out the current stats + if (millis() - timer > 2000) { + timer = millis(); // reset the timer + Serial.print("\nTime: "); + if (NMEA.hour < 10) { + Serial.print('0'); + } + Serial.print(NMEA.hour, DEC); + Serial.print(':'); + if (NMEA.minute < 10) { + Serial.print('0'); + } + Serial.print(NMEA.minute, DEC); + Serial.print(':'); + if (NMEA.seconds < 10) { + Serial.print('0'); + } + Serial.print(NMEA.seconds, DEC); + Serial.print('.'); + if (NMEA.milliseconds < 10) { + Serial.print("00"); + } else if (NMEA.milliseconds > 9 && NMEA.milliseconds < 100) { + Serial.print("0"); + } + Serial.println(NMEA.milliseconds); + Serial.print("Date: "); + Serial.print(NMEA.day, DEC); + Serial.print('/'); + Serial.print(NMEA.month, DEC); + Serial.print("/20"); + Serial.println(NMEA.year, DEC); + Serial.print("Fix: "); + Serial.print((int)NMEA.fix); + Serial.print(" quality: "); + Serial.println((int)NMEA.fixquality); + if (NMEA.fix) { + Serial.print("Location: "); + Serial.print(NMEA.latitude, 4); + Serial.print(NMEA.lat); + Serial.print(", "); + Serial.print(NMEA.longitude, 4); + Serial.println(NMEA.lon); + Serial.print("Speed (knots): "); + Serial.println(NMEA.speed); + Serial.print("Angle: "); + Serial.println(NMEA.angle); + Serial.print("Altitude: "); + Serial.println(NMEA.altitude); + Serial.print("Satellites: "); + Serial.println((int)NMEA.satellites); + } + } + } else { + Serial.println("Failed to parse sentence."); + } + nmeaSentenceIdx = 0; // Reset index for next sentence + } + } + } + // Short delay to prevent overwhelming the module + delay(10); +} \ No newline at end of file diff --git a/examples/ublox_ubxtest.ino b/examples/ublox_ubxtest.ino new file mode 100644 index 0000000..fd87a1d --- /dev/null +++ b/examples/ublox_ubxtest.ino @@ -0,0 +1,151 @@ +/*! + * @file ubx_mode_test.ino + * + * Test sketch to verify switching to UBX protocol mode + * This example uses I2C (DDC) to communicate with a u-blox module. + * + * Written by Ladyada for Adafruit Industries. + * + * MIT license, all text above must be included in any redistribution + */ + +#include "Adafruit_UBloxDDC.h" +#include "Adafruit_UBX.h" +#include "Adafruit_uBlox_typedef.h" + +// Create Adafruit_UBloxDDC object with default I2C address (0x42) +Adafruit_UBloxDDC ddc; + +// Create Adafruit_UBX parser using the DDC object as input stream +Adafruit_UBX ubx(ddc); + +// Variables to track message statistics +uint32_t messageCount = 0; +uint32_t lastMsgTime = 0; +bool ubxModeConfirmed = false; + +// Callback function for UBX messages +void ubxMessageCallback(uint8_t msgClass, uint8_t msgId, uint16_t payloadLen, uint8_t *payload) { + messageCount++; + lastMsgTime = millis(); + + // First successful UBX message confirms we're in UBX mode + if (!ubxModeConfirmed) { + ubxModeConfirmed = true; + Serial.println("SUCCESS: UBX mode confirmed! Receiving UBX messages."); + } + + // Print message details + Serial.print("UBX Message: Class 0x"); + if (msgClass < 0x10) Serial.print("0"); + Serial.print(msgClass, HEX); + + Serial.print(", ID 0x"); + if (msgId < 0x10) Serial.print("0"); + Serial.print(msgId, HEX); + + Serial.print(", Length: "); + Serial.print(payloadLen); + Serial.println(" bytes"); +} + +void setup() { + // Initialize serial port for debugging + Serial.begin(115200); + while (!Serial) { + ; // Wait for serial port to connect + } + + Serial.println("\n\n------------- UBX Mode Test -------------"); + Serial.println("This test will try to switch a u-blox module to UBX protocol"); + + // Initialize GPS module + Serial.print("Connecting to u-blox module via I2C..."); + if (ddc.begin()) { + Serial.println(" SUCCESS!"); + } else { + Serial.println(" FAILED!"); + Serial.println("Check connections and try again."); + while (1); // Don't proceed if we couldn't connect to the module + } + + // Show current data format (should be NMEA) + Serial.println("\nCurrent data format (should be NMEA sentences):"); + Serial.println("------------------------------------------------"); + + // Show some raw data + unsigned long startTime = millis(); + while (millis() - startTime < 3000) { + if (ddc.available()) { + int c = ddc.read(); + if (c != -1) { + Serial.write(c); + } + } + } + + Serial.println("\n------------------------------------------------"); + + // Initialize UBX parser and set debug mode + ubx.begin(); + ubx.verbose_debug = 2; // Enable basic debug output + + // Set the callback function for UBX messages + ubx.setMessageCallback(ubxMessageCallback); + + // Now switch to UBX mode + Serial.println("\nSwitching to UBX-only mode on DDC port..."); + + UBXSendStatus status = ubx.setUBXOnly(UBX_PORT_DDC, true, 1000); + + switch (status) { + case UBX_SEND_SUCCESS: + Serial.println("SUCCESS: UBX configuration command acknowledged!"); + break; + case UBX_SEND_NAK: + Serial.println("ERROR: UBX configuration command was rejected by the module."); + break; + case UBX_SEND_FAIL: + Serial.println("ERROR: Failed to send UBX configuration command."); + break; + case UBX_SEND_TIMEOUT: + Serial.println("ERROR: Timeout waiting for acknowledgment."); + break; + } + + if (status != UBX_SEND_SUCCESS) { + Serial.println("Continuing anyway to see if we receive any UBX messages..."); + } + + Serial.println("\nWaiting for UBX messages (timeout: 5 seconds)..."); + lastMsgTime = millis(); +} + +void loop() { + // Check for UBX messages + ubx.checkMessages(); + + // Display statistics every second + static uint32_t lastStatsTime = 0; + if (millis() - lastStatsTime >= 1000) { + lastStatsTime = millis(); + Serial.print("Status: Messages received: "); + Serial.print(messageCount); + + // Check for timeout + if (!ubxModeConfirmed && (millis() - lastMsgTime > 5000)) { + Serial.println("\n\nTIMEOUT: No UBX messages received within 5 seconds."); + Serial.println("Possible issues:"); + Serial.println("1. Module doesn't support UBX protocol on this port"); + Serial.println("2. Configuration command wasn't received properly"); + Serial.println("3. Module requires different configuration"); + Serial.println("\nTry using verbose_debug = 2 for more details."); + while(1); // Stop execution + } + + Serial.println(); + } + + // Short delay to prevent overwhelming the processor + delay(10); +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..b4e9802 --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=Adafruit uBlox +version=1.0.0 +author=Adafruit +maintainer=Adafruit +sentence=Library for interfacing with u-blox GPS/RTK modules. +paragraph=Library for interfacing with u-blox GPS/RTK modules. +category=Communication +url=https://github.com/adafruit/Adafruit_uBlox/ +architectures=* +depends=Adafruit BusIO, Adafruit GPS Library \ No newline at end of file