Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GPS Drivers #4

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f1f7d0c
add WIP gps drivers
EricPedley Feb 13, 2024
946f8d2
rename folder to be more specific
EricPedley Feb 13, 2024
8d2b2d0
switch to pvt
EricPedley Feb 13, 2024
6150e71
remove unnecessary files
EricPedley Feb 13, 2024
81692b4
fix errors with incomplete removal of old code
EricPedley Feb 13, 2024
9a58e82
move enums into class and add more specific poll results
EricPedley Feb 14, 2024
228d388
add verbose comments and more specific enum types
EricPedley Feb 14, 2024
5832e52
add line to not mess up enum formatting
EricPedley Feb 14, 2024
6a962f8
ran clang-format
EricPedley Feb 14, 2024
8ec2a07
refactor conditional logic in ubxPacket
EricPedley Feb 18, 2024
c10d3fb
rename files to .cc
EricPedley Feb 18, 2024
d32e5c0
change functions to PascalCase
EricPedley Feb 18, 2024
050647a
put enum definition before reference
EricPedley Feb 18, 2024
7e7e005
replace i2c header include
EricPedley Feb 18, 2024
df9ed0e
rename gps class to include full info and pull reset pin in constructor
EricPedley Feb 18, 2024
5a561fe
move ubxHelpers to private methods of gps class
EricPedley Feb 18, 2024
cb0b67f
add formatting to comments
EricPedley Feb 18, 2024
130b5b9
refactor i2c timeout to a macro
EricPedley Feb 18, 2024
e178026
refactor: move private helper to end of file
EricPedley Feb 21, 2024
5739f12
move pin reset to Init method
EricPedley Feb 21, 2024
6388ca8
fix comments
EricPedley Feb 21, 2024
106088d
add newline
EricPedley Feb 21, 2024
3a5fe53
decrease column limit
EricPedley Feb 21, 2024
1bd56a7
make private variables prefixed with underscore
EricPedley Feb 21, 2024
d0c32bd
make reset pin and port private members
EricPedley Feb 21, 2024
a747683
replace hard-coded i2c header with macros
EricPedley Feb 21, 2024
4015ee3
succumb to function squashing
EricPedley Feb 21, 2024
38d1f2a
add ecef payload back to messages
EricPedley Feb 22, 2024
04d50ac
un-hardcode what message the GPS is sending.
EricPedley Feb 22, 2024
4e2f6f0
fix syntax errors from not building earlier
EricPedley Feb 23, 2024
e91c121
add extra parens
EricPedley Feb 23, 2024
df910cc
add ecef conversion helper
EricPedley Feb 23, 2024
03be05a
un-const messages
EricPedley Feb 23, 2024
143f797
start working on coordinate conversion
EricPedley Feb 23, 2024
080f3cb
add datesheet links to message structs
EricPedley Feb 23, 2024
0df9418
convert coords to ECEF
EricPedley Feb 25, 2024
459a1cc
fix whitespace problem
EricPedley Feb 25, 2024
35dbd3f
fix math includes
EricPedley Mar 7, 2024
27f57f0
add include guard on coorhelpers header
EricPedley Mar 25, 2024
9e486a0
pascal case function names
EricPedley Mar 25, 2024
5f5f7f3
fix macros
EricPedley Mar 25, 2024
6b27138
rename and dont ifndef packet reader size
EricPedley Mar 25, 2024
e049ea2
shorten datasheet link
EricPedley Mar 25, 2024
4a8d227
add example of ecef conversion function
EricPedley Mar 25, 2024
345ab6e
use only 1 accuracy and fix duplicate structs
EricPedley Mar 25, 2024
f44baaf
fix header order
EricPedley Mar 25, 2024
a3d3b0d
update example
EricPedley Apr 12, 2024
b9ef6ea
check ubx send status and actually return payload
EricPedley Apr 12, 2024
12e6126
swap packet reader conditionals (fixed dumbass bug)
EricPedley Apr 12, 2024
398572b
rename files and add missing namespace to payload conversion
EricPedley Apr 12, 2024
25b6347
make payload conversion pass-by-reference
EricPedley Apr 12, 2024
093a5a5
make conversion function private and just return simpler struct
EricPedley Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ BasedOnStyle: Google
AccessModifierOffset: -2
ColumnLimit: 200
IndentWidth: 4
AllowShortEnumsOnASingleLine: false
DanielHeEGG marked this conversation as resolved.
Show resolved Hide resolved
39 changes: 39 additions & 0 deletions gps_ubxm8_i2c/coordHelpers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "coordHelpers.h"

#include <cmath>
#define M_PI 3.14159265358979323

// Constants for WGS84 ellipsoid
const double WGS84_A = 6378137.0; // semi-major axis in meters
const double WGS84_INV_FLATTENING = 298.257223563; // inverse flattening

// Convert degrees to radians
double Deg2Rad(double deg) { return deg * M_PI / 180.0; }

// Convert radians to degrees
double Rad2Deg(double rad) { return rad * 180.0 / M_PI; }

// Convert GPS coordinates (latitude, longitude, altitude) to ECEF coordinates
void Gps2Ecef(double lat, double lon, double alt, double& x, double& y, double& z) {
double cos_lat = cos(Deg2Rad(lat));
double sin_lat = sin(Deg2Rad(lat));
double cos_lon = cos(Deg2Rad(lon));
double sin_lon = sin(Deg2Rad(lon));

double N = WGS84_A / sqrt(1 - pow((1 - 1 / WGS84_INV_FLATTENING) * sin_lat, 2));

x = (N + alt) * cos_lat * cos_lon;
y = (N + alt) * cos_lat * sin_lon;
z = (N * (1 - 1 / WGS84_INV_FLATTENING) + alt) * sin_lat;
}

// Convert NED (North-East-Down) velocity to ECEF velocity
void Ned2EcefVel(double lat, double lon, double alt, double vn, double ve, double vd, double& vx, double& vy, double& vz) {
double x, y, z;
Gps2Ecef(lat, lon, alt, x, y, z);

// Convert NED velocity to ECEF velocity
vx = -ve * sin(Deg2Rad(lon)) - vn * sin(Deg2Rad(lat)) * cos(Deg2Rad(lon)) + vd * cos(Deg2Rad(lat)) * cos(Deg2Rad(lon));
vy = ve * cos(Deg2Rad(lon)) - vn * sin(Deg2Rad(lat)) * sin(Deg2Rad(lon)) + vd * cos(Deg2Rad(lat)) * sin(Deg2Rad(lon));
vz = vn * cos(Deg2Rad(lat)) + vd * sin(Deg2Rad(lat));
}
11 changes: 11 additions & 0 deletions gps_ubxm8_i2c/coordHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once
// Convert degrees to radians
EricPedley marked this conversation as resolved.
Show resolved Hide resolved
double Deg2Rad(double deg);

// Convert radians to degrees
double Rad2Deg(double rad);
// Convert GPS coordinates (latitude, longitude, altitude) to ECEF coordinates
void Gps2Ecef(double lat, double lon, double alt, double& x, double& y, double& z);

// Convert NED (North-East-Down) velocity to ECEF velocity
void Ned2EcefVel(double lat, double lon, double alt, double vn, double ve, double vd, double& vx, double& vy, double& vz);
186 changes: 186 additions & 0 deletions gps_ubxm8_i2c/gps.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#include "gps.h"

#include <algorithm>
#include <cmath>

#include "coordHelpers.h"
#include "ubxMessages.h"
#include "ubxPacket.h"

#define I2C_BUFFER_SIZE 1024
#define GPS_I2C_TIMEOUT 100

/**
* @param ubxMessage one of the messages from `ubxMessages.h` to send.
*/
GpsUbxM8I2c::GpsUbxM8I2c(GPIO_TypeDef* gpioResetPort, uint16_t gpioResetPin, I2C_HandleTypeDef* i2c, uint8_t* ubxMessage) {
_packetReader = UBXPacketReader();
_state = GpsUbxM8I2c::State::REQUEST_NOT_SENT;
_gpioResetPin = gpioResetPin;
_gpioResetPort = gpioResetPort;
_i2c = i2c;
_ubxMessage = ubxMessage;
}

void GpsUbxM8I2c::Init() { HAL_GPIO_WritePin(_gpioResetPort, _gpioResetPin, GPIO_PIN_SET); }

const GpsUbxM8I2c::State GpsUbxM8I2c::GetState() { return _state; }

/**
* @brief Sends a request for position data if none are currently pending, checks data available,
* and returns a status indicator.
*
* @return a GpsUbxM8I2c::PollResult object. See the definition of that enum for details.
*/
const GpsUbxM8I2c::PollResult GpsUbxM8I2c::PollUpdate() {
if (_state == GpsUbxM8I2c::State::REQUEST_NOT_SENT) {
sendUBX(_ubxMessage);
EricPedley marked this conversation as resolved.
Show resolved Hide resolved
_state = GpsUbxM8I2c::State::POLLING_RESPONSE;
}

if (_state == GpsUbxM8I2c::State::POLLING_RESPONSE) {
uint8_t lenBytes[2];
uint16_t dataLen = 0;
uint8_t buffer[I2C_BUFFER_SIZE];

HAL_StatusTypeDef dataLenReadStatus = HAL_I2C_Mem_Read(_i2c, 0x42 << 1, 0xFD, 1, lenBytes, 2, GPS_I2C_TIMEOUT);
if (dataLenReadStatus != HAL_OK) {
return GpsUbxM8I2c::PollResult::DATA_LEN_POLL_FAILED;
}

dataLen = lenBytes[0] << 8 | lenBytes[1];
if (dataLen == 0) {
return GpsUbxM8I2c::PollResult::NO_DATA;
}

if (dataLen > I2C_BUFFER_SIZE) {
dataLen = I2C_BUFFER_SIZE;
}
HAL_StatusTypeDef rcvStatus = HAL_I2C_Master_Receive(_i2c, 0x42 << 1, buffer, dataLen, GPS_I2C_TIMEOUT);
if (rcvStatus != HAL_OK) {
return GpsUbxM8I2c::PollResult::DATA_RECEIVE_I2C_FAILED;
}

if (_packetReader.isInProgress()) {
for (uint16_t i = 0; i < dataLen; i++) {
UBXPacketUpdateResult res = _packetReader.update(buffer[i]);
if (res == UBXPacketUpdateResult::CHECKSUM_FAILED) {
_packetReader.reset();
return GpsUbxM8I2c::PollResult::DATA_RECEIVE_CHECKSUM_FAILED;
}
if (_packetReader.isComplete()) {
_state = GpsUbxM8I2c::State::RESPONSE_READY;
return GpsUbxM8I2c::PollResult::POLL_JUST_FINISHED;
}
}
return GpsUbxM8I2c::PollResult::RECEIVE_IN_PROGRESS;
} else {
for (uint16_t i = 0; i < dataLen; i++) {
if (buffer[i] == 0xB5 && buffer[i + 1] == 0x62) {
i += 2; // skip the header
while (i < dataLen && !_packetReader.isComplete()) {
UBXPacketUpdateResult res = _packetReader.update(buffer[i]);
if (res != UBXPacketUpdateResult::UPDATE_OK) {
_packetReader.reset();
return GpsUbxM8I2c::PollResult::DATA_RECEIVE_CHECKSUM_FAILED;
}
i++;
}
}
}

if (!_packetReader.isInProgress()) {
return GpsUbxM8I2c::PollResult::NO_UBX_DATA;
}

if (_packetReader.isComplete()) {
_state = GpsUbxM8I2c::State::RESPONSE_READY;
return GpsUbxM8I2c::PollResult::POLL_JUST_FINISHED;
}
return GpsUbxM8I2c::PollResult::RECEIVE_IN_PROGRESS;
}
}
return GpsUbxM8I2c::PollResult::POLL_ALREADY_FINISHED;
}

/**
* @brief Returns GPS position solution info. This will only give valid data
* if it's called after `pollUpdate` returns a POLL_JUST_FINISHED response,
* which will put the GPS's state in RESPONSE_READY mode. If you call this before that,
* the data in the struct is undefined.
*
* After calling this and using the info returned, getting the next
* packet requires you to call `reset` and `pollUpdate` again.
*
* @retval pointer to payload. Can be casted to a struct in ubxMessages.
*/
const void* GpsUbxM8I2c::GetSolution() { return _packetReader.getPayload(); }

/**
* @brief Puts the GPS state back to its initial value so that `pollUpdate` knows
* it needs to send a new data request.
*/
void GpsUbxM8I2c::Reset() {
_state = GpsUbxM8I2c::State::REQUEST_NOT_SENT;
_packetReader.reset();
}

UCIRP_GPS_PAYLOAD ConvertPayloadToECEF(UBX_NAV_PVT_PAYLOAD pvtPayload) {
UCIRP_GPS_PAYLOAD payload;
payload.gpsFix = pvtPayload.fixType;
payload.positionAcc = std::max((double)pvtPayload.hAcc / 1000, (double)pvtPayload.vAcc / 1000);
payload.speedAcc = (double)pvtPayload.sAcc / 1000;

double lat = (double)pvtPayload.lat / 1e7;
double lon = (double)pvtPayload.lon / 1e7;
double alt = (double)pvtPayload.height / 1000;

Gps2Ecef(lat, lon, alt, payload.ecefX, payload.ecefY, payload.ecefZ);

// convert velocity to ECEF about the lat and lon
double velN = (double)pvtPayload.velN / 1000;
double velE = (double)pvtPayload.velE / 1000;
double velD = (double)pvtPayload.velD / 1000;

Ned2EcefVel(lat, lon, alt, velN, velE, velD, payload.ecefVX, payload.ecefVY, payload.ecefVZ);
EricPedley marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @brief Sends a UBX protocol message over i2c
*
* @param message Array containing
* {messageClass, messageID, payloadLenHighBits, payloadLenLowBits},
* and if the payload length >0, all the payload bytes. Note: you don't need to
* include the UBX start sequence in this or the checksums.
*
* @param len Length of `message`
* @param i2c The HAL i2c handle to use for transmission.
*/
bool GpsUbxM8I2c::sendUBX(uint8_t* message) {
static uint8_t magicBytes[2] = {0xB5, 0x62};
uint16_t len = 4 + ((uint16_t)message[2] << 8) + message[3];

uint8_t CK_A{0}, CK_B{0};

HAL_StatusTypeDef status;
status = HAL_I2C_Master_Transmit(_i2c, 0x42 << 1, magicBytes, 2, GPS_I2C_TIMEOUT);
if (status != HAL_OK) {
return false;
}

for (uint16_t i = 0; i < len; i++) {
CK_A = CK_A + message[i];
CK_B = CK_B + CK_A;
}

uint8_t CK[2] = {CK_A, CK_B};
status = HAL_I2C_Master_Transmit(_i2c, 0x42 << 1, message, len, GPS_I2C_TIMEOUT);
if (status != HAL_OK) {
return false;
}
status = HAL_I2C_Master_Transmit(_i2c, 0x42 << 1, CK, 2, GPS_I2C_TIMEOUT);
if (status != HAL_OK) {
return false;
}
return true;
}
95 changes: 95 additions & 0 deletions gps_ubxm8_i2c/gps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#pragma once

// if this is shitting itself, just locally replace it with whatever
// header on your machine that can get you `I2C_HandleTypeDef`
#if defined(STM32F1)
#include "stm32f1xx_hal.h"
#elif defined(STM32F4xx)
#include "stm32f4xx_hal.h"
#endif

#include "ubxMessages.h"
#include "ubxPacket.h"

/**
* Class for interfacing over i2c with any M8 GPS.
* Before this class is used, the GPS reset pin needs to be pulled high.
*
* This class keeps its own finite state machine for abstracting away polling data availability and retrying requests.
* An example usage is as follows:
* ```
int main(void)
{
HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

MX_I2C1_Init();

GPS gps(GPS_RST_GPIO_Port, GPS_RST_Pin, &hi2c1, PVT_MESSAGE);
gps.Init();
HAL_Delay(1000);

int lastITOW = 0;
while(1) {
volatile GPS::PollResult res = gps.PollUpdate();
auto state = gps.GetState();
if(state == GPS::State::RESPONSE_READY) {
UBX_NAV_PVT_PAYLOAD sol = *(UBX_NAV_PVT_PAYLOAD*)gps.GetSolution();
volatile int diff = sol.iTOW - lastITOW;
lastITOW = sol.iTOW;
if((GPSFixType)sol.fixType == GPSFixType::FIX_3D) {
HAL_Delay(1000);
}
gps.Reset();
} else {
HAL_Delay(100);
}
}
}
```
*
* After a packet is received, you _need_ to call the reset method.
EricPedley marked this conversation as resolved.
Show resolved Hide resolved
* If you don't, `pollUpdate` will just do nothing and return a `POLL_ALREADY_FINISHED` response.
*
* The datasheet is here:
* https://content.u-blox.com/sites/default/files/products/documents/u-blox8-M8_ReceiverDescrProtSpec_UBX-13003221.pdf
* The most relevant parts are "UBX Protocol" (section 32) and "DDC Port" (section 15).
*
*/
class GpsUbxM8I2c {
public:
enum class State {
REQUEST_NOT_SENT,
POLLING_RESPONSE,
RESPONSE_READY
};
enum class PollResult {
POLL_JUST_FINISHED, // the ubx data has been received on this invocation, and is now ready to be retrieved
POLL_ALREADY_FINISHED, // the ubx data was already ready after a previous PollUpdate call
RECEIVE_IN_PROGRESS, // we found the UBX start bytes in the incoming stream but haven't read the entire message
NO_DATA, // no data from GPS available
NO_UBX_DATA, // data read successfully but no UBX start bytes found
DATA_LEN_POLL_FAILED, // there was a failure in the I2C mem read for getting the quantity of data available
DATA_RECEIVE_I2C_FAILED, // there was a failure in the I2C receive to read the available data from the GPS
DATA_RECEIVE_CHECKSUM_FAILED // we received UBX data but the checksum didn't match the message.
};
GpsUbxM8I2c(GPIO_TypeDef* gpioResetPort, uint16_t gpioResetPin, I2C_HandleTypeDef* i2c, uint8_t* ubxMessage);
void Init();
const State GetState();
const PollResult PollUpdate();
const void* GetSolution();
void Reset();
UCIRP_GPS_PAYLOAD ConvertPayloadToECEF(UBX_NAV_PVT_PAYLOAD pvtPayload);

private:
UBXPacketReader _packetReader;
State _state;
GPIO_TypeDef* _gpioResetPort;
uint16_t _gpioResetPin;
I2C_HandleTypeDef* _i2c;
uint8_t* _ubxMessage;
bool sendUBX(uint8_t* message);
};
Loading
Loading