A driver for the MAX8U GPS by uBlox. Provides core functionality. Communicates through I2C.

MAX8U Driver

This driver supports a wide range of functions provided by the MAX8U chip. Most of the essential features are supported. It lays a framework to add more commands easily, with only a little extra work.

Driver was originally made for the USC Rocket Propulsion Lab by Adhyyan Sekhsaria and Jamie Smith

Features

Currently supports:

  • Getting coordinates in latitude, longitude, accuracy...
  • Velocity measurements in each axis
  • Individual satellite information
  • Time of last message
  • Antenna Power Status
  • GPS Fix Quality
  • Save configuration in memory

Documentation

Full documentation is available here

Usage

Code Structure:

In factory settings, the module is not configured to send any messages. For the GPS to give updates, we need to configure it by sending messages using setMessageEnabled() which internally calls sendMessage() and waitForAck().

calling configure() once will enable a few useful messages, and save this configuration in the MAX8U non-volatile memory.

update() to be called periodically by the user. Processes and reads all the messages currently stored in the GPS. Calls processMessage() on every message. processMessage(): processes the message currently in the buffer by calling the respective functions. Each message is read, and its relevant information is then stored in the public variables.

By default, the GPS sends messages in NMEA format (but we reconfigure it to UBX format), so readNextMessage() has to determine which format the message is.

define the macro MAX8U_DEBUG to enable printing out to message information to the debug port. If not defined, will only print error messages to the debug port.

Example

Outputting Coordinates, Velocity and Time from GPS

#include "MAX8U.h"

int main(){
    MAX8U gps(&pc, PIN_I2C_SDA, PIN_I2C_SCL, p25);
    bool booted = gps.begin();

    if(!booted){
        //handle error
    }
    booted = gps.configure();
    if(!booted){
        //handle error
    }

    while(true){
        bool newMessageRcvd = gps.update();
        pc.printf(">Position: %.06f %c, %.06f %c, Height %.02f m\r\n",
                  std::abs(gps.latitude), gps.latitude > 0 ? 'N' : 'S',
                  std::abs(gps.longitude), gps.longitude > 0 ? 'E' : 'W',
                  gps.height);

        // velocity
        pc.printf(">Velocity: %.02f m/s N, %.02f m/s E, %.02f m/s Down\r\n",
                  gps.northVel * .02f,
                  gps.eastVel * .02f,
                  gps.downVel * .02f);

        // accuracy
        pc.printf(">Fix: Quality: %" PRIu8 ", Num Satellites: %" PRIu8 ", Position Accuracy: %.02f m\r\n",
                static_cast<uint8_t>(gps.fixQuality), gps.numSatellites, gps.posAccuracy);

        // time
        pc.printf(">Time: %" PRIu8 "/%" PRIu8"/%" PRIu16" %" PRIu8":%" PRIu8 ":%" PRIu8 "\r\n",
                gps.month,
                gps.day,
                gps.year,
                gps.hour,
                gps.minute,
                gps.second);
    }
}

MAX8U.cpp

Committer:
adhyyan
Date:
2020-01-07
Revision:
3:b51460af3259
Parent:
0:7f603f221713

File content as of revision 3:b51460af3259:

//MAX 8U GPS Driver

//Author: Adhyyan Sekhsaria, Jamie Smith

//Written with the help of the following data sheets:
//https://www.u-blox.com/sites/default/files/MAX-8-M8-FW3_HardwareIntegrationManual_%28UBX-15030059%29.pdf
//https://www.u-blox.com/en/docs/UBX-13003221

#include "MAX8U.h"

#include <cinttypes>
#include <algorithm>

#define MAX8U_DEBUG 1

const char* GNSSNames[] = {"GPS", "SBAS", "Galileo", "BeiDou", "IMES", "QZSS", "GLONASS"};

const char *MAX8U::SatelliteInfo::getGNSSName()
{
    return GNSSNames[static_cast<uint8_t>(gnss)];
}


MAX8U::MAX8U(Serial *debugPort, PinName user_SDApin, PinName user_SCLpin, PinName user_RSTPin,
             uint8_t i2cAddress, int i2cPortSpeed) :
    _debugPort(debugPort),
    _i2cPort(user_SDApin, user_SCLpin),
    _i2cAddress(i2cAddress),
    _rst(user_RSTPin, 1)

{

    //Get user settings
    _i2cPortSpeed = i2cPortSpeed;

    #define MAX8U_MAX_SPEED 4000000 // 400 kHz
    if(_i2cPortSpeed > MAX8U_MAX_SPEED)
    {
        _i2cPortSpeed = MAX8U_MAX_SPEED; 
    }
    _i2cPort.frequency(_i2cPortSpeed);
}


bool MAX8U::begin(){
    //Configure the MAX8U for I2C communication

    _rst = 0; // Reset BNO080
    wait(.002f); // Min length not specified in datasheet?
    _rst = 1; // Bring out of reset

    //TODO: wait for the GPS to boot. Couldnt find any pin in the data sheet which would be useful
    wait(0.1f); //for now, just wait for some time

    if(checkVersion())
    {
#ifdef MAX8U_DEBUG
        _debugPort->printf("MAX8U booted up");
#endif
        return true;
    }
    else
    {
        _debugPort->printf("MAX8U not detected!");
        return false;
    }
}

bool MAX8U::update()
{
    bool gotAnyMessages = false;

    while(readNextMessage())
    {
        processMessage();

        gotAnyMessages = true;
    }


    return gotAnyMessages;
}

bool MAX8U::configure()
{
    // switch to UBX mode
    if(!configureCommSettings())
    {
        return false;
    }

    // enable NAV messages
    // latitude, longitude, and height
    if(!setMessageEnabled(UBX_CLASS_NAV, UBX_NAV_POSLLH, true))
    {
        return false;
    }

    if(!setMessageEnabled(UBX_CLASS_NAV, UBX_NAV_SOL, true))
    {
        return false;
    }

    if(!setMessageEnabled(UBX_CLASS_NAV, UBX_NAV_TIMEUTC, true))
    {
        return false;
    }


    if(!setMessageEnabled(UBX_CLASS_NAV, UBX_NAV_VELNED, true))
    {
        return false;
    }

    return saveSettings();

}

bool MAX8U::setMessageEnabled(uint8_t messageClass, uint8_t messageID, bool enabled)
{
    uint8_t data[3];

    data[0] = messageClass; // byte 0: class
    data[1] = messageID; // byte 1: ID
    data[2] = static_cast<uint8_t>(enabled ? 1 : 0); // byte 2: rate

    if(!sendCommand(UBX_CLASS_CFG, UBX_CFG_MSG, data, sizeof(data)))
    {
        return false;
    }

    return waitForACK(UBX_CLASS_CFG, UBX_CFG_MSG);
}

MAX8U::AntennaPowerStatus MAX8U::antennaPowerStatus() {
    sendCommand(UBX_CLASS_MON, UBX_MON_HW, nullptr, 0);

    if(!waitForMessage(UBX_CLASS_MON, UBX_MON_HW))
    {
        return MAX8U::AntennaPowerStatus::NO_MESSAGE_RCVD;
    }

    //else print out whether its on or off
    MAX8U::AntennaPowerStatus powerStatus = AntennaPowerStatus (static_cast<uint8_t >(buffer[UBX_DATA_OFFSET + 21]));
    return powerStatus;
}

ssize_t MAX8U::readSatelliteInfo(MAX8U::SatelliteInfo *satelliteInfos, size_t infoLen)
{
    sendCommand(UBX_CLASS_NAV, UBX_NAV_SAT, nullptr, 0);

    if(!waitForMessage(UBX_CLASS_NAV, UBX_NAV_SAT))
    {
        return -1;
    }

    uint8_t satellitesReturned = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 5]);

    for(size_t i = 0; i < std::min(static_cast<size_t>(satellitesReturned), infoLen); i++)
    {
        // detect a buffer overrun in the case where more satellites were returned than could
        // fit in the buffer
        size_t flagOffset = UBX_DATA_OFFSET + 16 + 12*i;
        if(flagOffset >= bufferMaxLen)
        {
            _debugPort->printf("Error: NAV-SAT message truncated by receive buffer size!\r\n");

            // keep the part that was valid
            return i;
        }

        satelliteInfos[i].gnss = static_cast<GNSSID>(static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 8 + 12 * i]));
        satelliteInfos[i].satelliteID = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 9 + 12*i]);
        satelliteInfos[i].signalStrength = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 10 + 12*i]);

        uint32_t flag = readUnalignedValue<uint32_t>(buffer, flagOffset);

        satelliteInfos[i].signalQuality = (flag & 0x0007);
        satelliteInfos[i].svUsed = (flag & (1<<3));

        //satelliteInfos[i].elevation = static_cast<int8_t >(buffer[UBX_DATA_OFFSET])

#if MAX8U_DEBUG
        _debugPort->printf("NAV_SAT Strength for %s %" PRIu8 ":: %" PRIu8 " dBHz; Quality: %" PRIu8 "\r\n",
                           satelliteInfos[i].getGNSSName(), satelliteInfos[i].satelliteID,
                           satelliteInfos[i].signalStrength, satelliteInfos[i].signalQuality);
#endif
    }

    return satellitesReturned;
}


void MAX8U::processMessage()
{
    if(buffer[UBX_BYTE_CLASS] == UBX_CLASS_NAV)
    {
        switch(buffer[UBX_BYTE_ID])
        {
            case UBX_NAV_POSLLH:
                processNAV_POSLLH();
                break;
            case UBX_NAV_SOL:
                processNAV_SOL();
                break;
            case UBX_NAV_TIMEUTC:
                processNAV_TIMEUTC();
                break;
            case UBX_NAV_VELNED:
                processNAV_VELNED();
                break;
        }

    }
}
void MAX8U::processNAV_VELNED() {
    northVel = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 4);
    eastVel = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 8);
    downVel = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 12);
    speed3D = readUnalignedValue<uint32_t>(buffer, UBX_DATA_OFFSET + 16);

#if MAX8U_DEBUG
    _debugPort->printf("Got NAV_VELNED message.  North Vel=%" PRIi32 ", East Vel=%" PRIi32 ", Down Vel=%" PRIi32 ", 3D Speed=%" PRIu32 "\r\n",
            northVel, eastVel, downVel, speed3D);
#endif
}


void MAX8U::processNAV_POSLLH()
{
    // read latitude and longitude
    int32_t longitudeInt = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 4);
    longitude = longitudeInt * 1e-7;

    int32_t latitudeInt = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 8);
    latitude = latitudeInt * 1e-7;

    height = readUnalignedValue<int32_t>(buffer, UBX_DATA_OFFSET + 12)/(1000.0f); //height above ellipsoid

#if MAX8U_DEBUG
    _debugPort->printf("Got NAV_POSLLH message.  Longitude=%.06f deg, Latitude=%.06f deg, Height=%.02f m\r\n", longitude, latitude, height);
#endif
}


void MAX8U::processNAV_SOL()
{
    fixQuality = static_cast<GPSFix>(buffer[UBX_DATA_OFFSET + 10]);

    posAccuracy = readUnalignedValue<uint32_t>(buffer, UBX_DATA_OFFSET + 24) / 100.0f;

    numSatellites = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 47]);

#if MAX8U_DEBUG
    _debugPort->printf("Got NAV_SOL message.  Fix quality=%" PRIu8 ", Pos accuracy=%.02f m, Num satellites=%" PRIu8 "\r\n",
                       static_cast<uint8_t>(fixQuality), posAccuracy, numSatellites);
#endif
}

void MAX8U::processNAV_TIMEUTC()
{
    year = readUnalignedValue<uint16_t>(buffer, UBX_DATA_OFFSET + 12);

    month = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 14]);

    day = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 15]);

    hour = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 16]);

    minute = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 17]);

    second = static_cast<uint8_t>(buffer[UBX_DATA_OFFSET + 18]);

#if MAX8U_DEBUG
    _debugPort->printf("Got NAV_TIMEUTC message.  year=%" PRIu16 ", month =%" PRIu8 ", day=%" PRIu8
            ", hour = %" PRIu8 ", min = %" PRIu8 ", sec = %" PRIu8 "\r\n",
                       year, month, day, hour, minute, second);
#endif

}

bool MAX8U::waitForMessage(uint8_t messageClass, uint8_t messageID, float timeout)
{
    Timer timeoutTimer;
    timeoutTimer.start();

    while(timeoutTimer.read() <= timeout)
    {
        bool newMessageReceived = readNextMessage();

        //We dont use TX Ready pin here, because it isn't configured when the GPS is first set up.

        if(!newMessageReceived){
            wait(0.001f);
            continue;
        }

        if(messageClass == buffer[UBX_BYTE_CLASS] && messageID == buffer[UBX_BYTE_ID])
        {
            // found correct packet!
            return true;
        }
        else
        {
            // other data message, send to proper channels
            processMessage();
        }

    }

    _debugPort->printf("Timeout waiting for message 0x%02" PRIx8 " 0x%02" PRIx8 ".\r\n", messageClass, messageID);
    return false;
}

bool MAX8U::waitForACK(uint8_t sentMessageClass, uint8_t sentMessageID, float timeout)
{
    // NOTE: we assume that we wait for an ACK before sending another message, so
    // there will never be two ACKs in play at once

    if(!waitForMessage(UBX_CLASS_ACK, UBX_ACK_ACK))
    {
        _debugPort->printf("Timeout waiting for ACK for message 0x%02" PRIx8 " 0x%02" PRIx8 "\r\n",
            sentMessageClass, sentMessageID);
        return false;
    }

    // check the byte IDs
    if(buffer[UBX_DATA_OFFSET] != sentMessageClass || buffer[UBX_DATA_OFFSET+1] != sentMessageID)
    {
        _debugPort->printf("Ack rcvd for wrong message\r\n");
        return false;
    }


    return true;
}

bool MAX8U::configureCommSettings(){
    //Configure DDC(I2C) by writing

    //Configures the MAX8 to output in UBX format instead of NMEA format.

    /*
    UBX-CFG-PRT Payload
    1 PortId  = 0
    1 reserved1
    2 txReady 
    4 mode - 7 address and 0 for write
    4 reserved - all 0s
    2 inProtoMask - keep 0th bit on, rest off
    2 outProtoMask - keep 0th bit on, rest off
    2 flags - all 0
    2 reserved - all 0
    */

    uint16_t dataLen = 20;
    uint8_t data[20];
    data[0]  = 0; //Port Id
    data[1]  = 0; //Reserved

    // disable TX ready
    data[2]  = 0;
    data[3]  = 0;

    data[4]  = (_i2cAddress<<1);
    data[5]  = 0;
    data[6]  = 0;
    data[7]  = 0;

    data[8]  = 0;
    data[9]  = 0;
    data[10] = 0;
    data[11] = 0;

    data[12] = 0x01; //enabling UBX mode for input
    data[13] = 0;

    data[14] = 0x01; //enabling UBX mode for output
    data[15] = 0;

    data[16] = 0;
    data[17] = 0;
    data[18] = 0;
    data[19] = 0;


    if(!sendCommand(UBX_CLASS_CFG, UBX_CFG_PRT, data, dataLen))
    {
        return false;
    }

    return waitForACK(UBX_CLASS_CFG, UBX_CFG_PRT);
}

//send the header then the data to the Max8 on the default address 0x42
//Returns false if sensor does not ACK
bool MAX8U::sendCommand(uint8_t messageClass, uint8_t messageID, uint8_t *data, uint16_t dataLen)
{

    //using UBX protocol

    // start the transaction and contact the IMU
    _i2cPort.start();

    // to indicate an i2c read, shift the 7 bit address up 1 bit and keep bit 0 as a 0
    int writeResult = _i2cPort.write(_i2cAddress << 1);

    if(writeResult != 1)
    {
        _debugPort->printf("MAX8U I2C write failed!\r\n");
        _i2cPort.stop();
        return false;
    }
    
    #if MAX8U_DEBUG
        _debugPort->printf("MAX8U I2C write acked!\r\n");
    #endif

    //compute checksum on header and data. Refer to datasheet
    uint8_t chka = 0, chkb = 0;

    // add header portions to checksum;
    chka += messageClass;
    chkb += chka;

    chka += messageID;
    chkb += chka;

    chka += dataLen & 0xFF;
    chkb += chka;

    chka += dataLen >> 8;
    chkb += chka;

    // add data to checksum
    for(int i = 0; i < dataLen; i++){
        chka = chka + data[i];
        chkb = chkb + chka;
    }

    //send the sync chars
    _i2cPort.write(UBX_SYNC_CHAR_1);
    _i2cPort.write(UBX_SYNC_CHAR_2);

    // send the header
    _i2cPort.write(messageClass);
    _i2cPort.write(messageID);
    _i2cPort.write(dataLen & 0xFF);
    _i2cPort.write(dataLen >> 8);


    for(uint8_t i = 0; i < dataLen; i++){
        _i2cPort.write(data[i]);
    }

    _i2cPort.write(chka);
    _i2cPort.write(chkb);

    _i2cPort.stop();

#if  MAX8U_DEBUG
    _debugPort->printf("Sending: %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8 " %02" PRIx8,
        UBX_SYNC_CHAR_1, UBX_SYNC_CHAR_2, messageClass, messageID, dataLen & 0xFF, dataLen >> 8);

    for(uint16_t i = 0; i < dataLen; i++){
        _debugPort->printf(" %02" PRIx8, data[i]);
    }
    _debugPort->printf(" %02" PRIx8 " %02" PRIx8 "\r\n", chka, chkb);
#endif

    return true;
}

bool MAX8U::saveSettings()
{
    const size_t dataLen = 12;
    uint8_t data[dataLen];

    // don't clear any settings
    memset(data, 0, 4);

    // save all settings
    data[4] = 0b00011111;
    data[5] = 0b00011111;
    data[6] = 0;
    data[7] = 0;

    memset(data + 8, 0, 4);

    if(!sendCommand(UBX_CLASS_CFG, UBX_CFG_CFG, data, dataLen))
    {
        return false;
    }

    return waitForACK(UBX_CLASS_CFG, UBX_CFG_CFG);

}

bool MAX8U::checkVersion()
{
    sendCommand(UBX_CLASS_MON, UBX_MON_VER, nullptr, 0);

    if(!waitForMessage(UBX_CLASS_MON, UBX_MON_VER))
    {
        return false;
    }

#if MAX8U_DEBUG
    _debugPort->printf("MAXU8 Software Version: %s\r\n", buffer + UBX_DATA_OFFSET);
    _debugPort->printf("MAXU8 Software Version: %s\r\n", buffer + UBX_DATA_OFFSET + 30);

    // print additional data
    size_t numAdditionalLines = (currMessageLength - UBX_HEADER_FOOTER_LENGTH - 40) / 30;

    for(size_t line = 0; line < numAdditionalLines; line++)
    {
        _debugPort->printf("Extra Info: %s\r\n", buffer + UBX_DATA_OFFSET + 40 + 30*line);
    }

    #endif

    return true;
}


void MAX8U::readGNSSConfig() {
    sendCommand(UBX_CLASS_CFG, UBX_CFG_GNSS, nullptr, 0);

    if(!waitForMessage(UBX_CLASS_CFG, UBX_CFG_GNSS)){
        _debugPort->printf("NO MESSAGE RCVD for GNSS CFG message");
        return;
    }

    uint8_t numTrkChHw = static_cast<uint8_t >(buffer[UBX_DATA_OFFSET + 1]);
    uint8_t usedTracks = static_cast<uint8_t >(buffer[UBX_DATA_OFFSET + 2]);
    uint8_t blocks = static_cast<uint8_t >(buffer[UBX_DATA_OFFSET + 3]);
    _debugPort->printf("CHANNELS: %" PRIx8 " , USED: %" PRIx8 " , LEN: %" PRIx8 "\r\n", numTrkChHw, usedTracks, blocks);
    for(int i = 0; i < blocks; i++){
        uint8_t gnssID = static_cast<uint8_t >(buffer[UBX_DATA_OFFSET + 4 + 8*i]);
        uint32_t flag = static_cast<uint32_t >(buffer[UBX_DATA_OFFSET + 8 + 8*i]);
        bool enabled = flag&1;
        _debugPort->printf("GNSS ID: %" PRIx8 ", NAME: %s, ENABLED: %d \r\n", gnssID, GNSSNames[gnssID], enabled);
    }

}

bool MAX8U::readNextMessage(){
    //keep reading the 0xff register until the buffer is ove

    int msgLen = readLen();
    if(msgLen == -1){
        _debugPort->printf("Didn't rcv ack from MAX8 when reading length\r\n");
        return false;
    }

    if(msgLen == 0)
    {
        //nothing to do
        return false;
    }

    _i2cPort.start();
    int readResult = _i2cPort.write((_i2cAddress << 1) | 0x01);

    if(readResult != 1){
        _debugPort->printf("Didn't receive ack from MAX8\r\n");
        return false;
    }

    // whether this is an NMEA or UBX sentence
    bool isLastByte = false;
    currMessageLength = 0;
    int ubxMsgLen = 100000; // large value to stop loop exit condition, will read real value later
    for(int i = 0; i < msgLen; i++) // for loop in case there's a data error and we don't detect the last byte
    {
        uint8_t incoming = static_cast<uint8_t >(_i2cPort.read(!isLastByte));
        if(i == 5 && !isNMEASentence){
            //read length and change that to msg length
            ubxMsgLen = (static_cast<uint16_t>(buffer[i] << 8) | buffer[i-1]) + 8;
            //non-payload body of a ubx message is 8

        }

        if(i == 0)
        {
            if(incoming == NMEA_MESSAGE_START_CHAR)
            {
                // NMEA sentences start with a dollars sign
                isNMEASentence = true;
            }
            else if(incoming == UBX_MESSAGE_START_CHAR)
            {
                // UBX sentences start with a 0xB5
                isNMEASentence = false;
            }
            else if(incoming == 0xFF)
            {
                _debugPort->printf("Message Length > 0, although buffer is empty\r\n");
            }else{
                _debugPort->printf("Unknown first character \r\n");
            }

        }

        if(i < bufferMaxLen){
            buffer[i] = incoming;
            ++currMessageLength;
        }

        if(isLastByte)
        {
            break;
        }

        // post-receive actions

        // if it's an NMEA sentence, there is a CRLF at the end
        // if it's an UBX  sentence, there is a length passed before the payload
        if((isNMEASentence && incoming == '\r') || (!isNMEASentence && i == ubxMsgLen-2))
        {
            isLastByte = true;
        }
    }

    // add null terminator
    buffer[currMessageLength] = 0;

    _i2cPort.stop();


#if  MAX8U_DEBUG
    _debugPort->printf("Read stream of MAX8U: ");
    for(uint16_t i = 0; i < currMessageLength; i++){
        _debugPort->printf("%02" PRIx8, buffer[i]);
    }
    _debugPort->printf(";\r\n");
#endif

    if(!isNMEASentence)
    {
        uint8_t chka = 0, chkb = 0;
        for(uint16_t i = 2; i < currMessageLength-2; i++)
        {
            chka += buffer[i];
            chkb += chka;
        }

        if((chka != buffer[currMessageLength-2]) || (chkb != buffer[currMessageLength-1]))
        {
            _debugPort->printf("Checksums for UBX message don't match!\r\n");
            return false;
        }
    }

    return true;

}


int32_t MAX8U::readLen(){

    _i2cPort.start();
    int i2cStatus = _i2cPort.write((_i2cAddress << 1) | 0x00);
    if(i2cStatus != 1)
        return -1;
    _i2cPort.write(0xFD);


    _i2cPort.start();
    i2cStatus = _i2cPort.write((_i2cAddress << 1) | 0x01);
    if(i2cStatus != 1)
        return -1;

    uint8_t highByte = static_cast<uint8_t>(_i2cPort.read(true));
    uint8_t lowByte = static_cast<uint8_t>(_i2cPort.read(false));

    _i2cPort.stop();

    return (static_cast<uint16_t>(highByte << 8) | lowByte);
}