Improved GPS Settings app and GPS service (#222)
- Fixes and improvements to `GpsSettings` app, `GpsDevice` and `GpsService` - Implemented location/GPS statusbar icon - Added app icon - Added support for other GPS models (based on Meshtastic code)
This commit is contained in:
committed by
GitHub
parent
ad2cad3bf1
commit
5055fa7822
@@ -1,12 +1,48 @@
|
||||
#include "Tactility/hal/gps/GpsDevice.h"
|
||||
#include <minmea.h>
|
||||
#include "Tactility/hal/gps/GpsInit.h"
|
||||
#include "Tactility/hal/gps/Probe.h"
|
||||
#include <cstring>
|
||||
#include <minmea.h>
|
||||
|
||||
#define TAG "gps"
|
||||
#define GPS_UART_BUFFER_SIZE 256
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
const char* toString(GpsModel model) {
|
||||
using enum GpsModel;
|
||||
switch (model) {
|
||||
case AG3335:
|
||||
return TT_STRINGIFY(AG3335);
|
||||
case AG3352:
|
||||
return TT_STRINGIFY(AG3352);
|
||||
case ATGM336H:
|
||||
return TT_STRINGIFY(ATGM336H);
|
||||
case LS20031:
|
||||
return TT_STRINGIFY(LS20031);
|
||||
case MTK:
|
||||
return TT_STRINGIFY(MTK);
|
||||
case MTK_L76B:
|
||||
return TT_STRINGIFY(MTK_L76B);
|
||||
case MTK_PA1616S:
|
||||
return TT_STRINGIFY(MTK_PA1616S);
|
||||
case UBLOX6:
|
||||
return TT_STRINGIFY(UBLOX6);
|
||||
case UBLOX7:
|
||||
return TT_STRINGIFY(UBLOX7);
|
||||
case UBLOX8:
|
||||
return TT_STRINGIFY(UBLOX8);
|
||||
case UBLOX9:
|
||||
return TT_STRINGIFY(UBLOX9);
|
||||
case UBLOX10:
|
||||
return TT_STRINGIFY(UBLOX10);
|
||||
case UC6580:
|
||||
return TT_STRINGIFY(UC6580);
|
||||
default:
|
||||
return TT_STRINGIFY(Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GpsDevice::threadMainStatic(void* parameter) {
|
||||
auto* gps_device = (GpsDevice*)parameter;
|
||||
return gps_device->threadMain();
|
||||
@@ -20,49 +56,66 @@ int32_t GpsDevice::threadMain() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!configuration.initFunction(configuration.uartPort)) {
|
||||
TT_LOG_E(TAG, "Failed to init");
|
||||
|
||||
GpsModel model = configuration.model;
|
||||
if (model == GpsModel::Unknown) {
|
||||
model = probe(configuration.uartPort);
|
||||
if (model == GpsModel::Unknown) {
|
||||
TT_LOG_E(TAG, "Probe failed");
|
||||
setState(State::Error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
this->model = model;
|
||||
mutex.unlock();
|
||||
|
||||
if (!init(configuration.uartPort, model)) {
|
||||
TT_LOG_E(TAG, "Init failed");
|
||||
setState(State::Error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
setState(State::On);
|
||||
|
||||
// Reference: https://gpsd.gitlab.io/gpsd/NMEA.html
|
||||
while (!isThreadInterrupted()) {
|
||||
if (uart::readUntil(configuration.uartPort, (uint8_t*)buffer, GPS_UART_BUFFER_SIZE, '\n', 100 / portTICK_PERIOD_MS) > 0) {
|
||||
TT_LOG_D(TAG, "RX: %s", buffer);
|
||||
size_t bytes_read = uart::readUntil(configuration.uartPort, (uint8_t*)buffer, GPS_UART_BUFFER_SIZE, '\n', 100 / portTICK_PERIOD_MS);
|
||||
if (bytes_read > 0U) {
|
||||
|
||||
// Thread might've been interrupted in the meanwhile
|
||||
if (isThreadInterrupted()) {
|
||||
break;
|
||||
}
|
||||
|
||||
TT_LOG_D(TAG, "%s", buffer);
|
||||
|
||||
switch (minmea_sentence_id((char*)buffer, false)) {
|
||||
case MINMEA_SENTENCE_RMC:
|
||||
minmea_sentence_rmc frame;
|
||||
if (minmea_parse_rmc(&frame, (char*)buffer)) {
|
||||
for (auto& subscription : locationSubscriptions) {
|
||||
(*subscription.onData)(getId(), frame);
|
||||
minmea_sentence_rmc rmc_frame;
|
||||
if (minmea_parse_rmc(&rmc_frame, (char*)buffer)) {
|
||||
mutex.lock();
|
||||
for (auto& subscription : rmcSubscriptions) {
|
||||
(*subscription.onData)(getId(), rmc_frame);
|
||||
}
|
||||
TT_LOG_D(TAG, "RX RMC %f lat, %f lon, %f m/s", minmea_tocoord(&frame.latitude), minmea_tocoord(&frame.longitude), minmea_tofloat(&frame.speed));
|
||||
mutex.unlock();
|
||||
TT_LOG_D(TAG, "RMC %f lat, %f lon, %f m/s", minmea_tocoord(&rmc_frame.latitude), minmea_tocoord(&rmc_frame.longitude), minmea_tofloat(&rmc_frame.speed));
|
||||
} else {
|
||||
TT_LOG_W(TAG, "RX RMC parse error: %s", buffer);
|
||||
TT_LOG_W(TAG, "RMC parse error: %s", buffer);
|
||||
}
|
||||
break;
|
||||
case MINMEA_SENTENCE_GGA:
|
||||
minmea_sentence_gga gga_frame;
|
||||
if (minmea_parse_gga(&gga_frame, (char*)buffer)) {
|
||||
TT_LOG_D(TAG, "RX GGA %f lat, %f lon", minmea_tocoord(&gga_frame.latitude), minmea_tocoord(&gga_frame.longitude));
|
||||
} else {
|
||||
TT_LOG_W(TAG, "RX GGA parse error: %s", buffer);
|
||||
}
|
||||
break;
|
||||
case MINMEA_SENTENCE_GSV:
|
||||
minmea_sentence_gsv gsv_frame;
|
||||
if (minmea_parse_gsv(&gsv_frame, (char*)buffer)) {
|
||||
for (auto& sat : gsv_frame.sats) {
|
||||
if (sat.nr != 0 && sat.elevation != 0 && sat.snr != 0) {
|
||||
for (auto& subscription : satelliteSubscriptions) {
|
||||
(*subscription.onData)(getId(), sat);
|
||||
}
|
||||
}
|
||||
TT_LOG_D(TAG, "Satellite: id %d, elevation %d, azimuth %d, snr %d", sat.nr, sat.elevation, sat.azimuth, sat.snr);
|
||||
mutex.lock();
|
||||
for (auto& subscription : ggaSubscriptions) {
|
||||
(*subscription.onData)(getId(), gga_frame);
|
||||
}
|
||||
TT_LOG_D(TAG, "RX GGA %f lat, %f lon", minmea_tocoord(&gga_frame.latitude), minmea_tocoord(&gga_frame.longitude));
|
||||
mutex.unlock();
|
||||
TT_LOG_D(TAG, "GGA %f lat, %f lon", minmea_tocoord(&gga_frame.latitude), minmea_tocoord(&gga_frame.longitude));
|
||||
} else {
|
||||
TT_LOG_W(TAG, "RX GGA parse error: %s", buffer);
|
||||
TT_LOG_W(TAG, "GGA parse error: %s", buffer);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -95,6 +148,9 @@ bool GpsDevice::start() {
|
||||
|
||||
threadInterrupted = false;
|
||||
|
||||
TT_LOG_I(TAG, "Starting thread");
|
||||
setState(State::PendingOn);
|
||||
|
||||
thread = std::make_unique<Thread>(
|
||||
"gps",
|
||||
4096,
|
||||
@@ -104,12 +160,15 @@ bool GpsDevice::start() {
|
||||
thread->setPriority(tt::Thread::Priority::High);
|
||||
thread->start();
|
||||
|
||||
TT_LOG_I(TAG, "Starting finished");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpsDevice::stop() {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock(portMAX_DELAY);
|
||||
lock.lock();
|
||||
|
||||
setState(State::PendingOff);
|
||||
|
||||
if (thread != nullptr) {
|
||||
threadInterrupted = true;
|
||||
@@ -130,23 +189,38 @@ bool GpsDevice::stop() {
|
||||
if (uart::isStarted(configuration.uartPort)) {
|
||||
if (!uart::stop(configuration.uartPort)) {
|
||||
TT_LOG_E(TAG, "UART %d failed to stop", configuration.uartPort);
|
||||
setState(State::Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
setState(State::Off);
|
||||
|
||||
bool GpsDevice::isStarted() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return thread != nullptr && thread->getState() != Thread::State::Stopped;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpsDevice::isThreadInterrupted() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock(portMAX_DELAY);
|
||||
lock.lock();
|
||||
return threadInterrupted;
|
||||
}
|
||||
|
||||
GpsModel GpsDevice::getModel() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return model; // Make copy because of thread safety
|
||||
}
|
||||
|
||||
GpsDevice::State GpsDevice::getState() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return state; // Make copy because of thread safety
|
||||
}
|
||||
|
||||
void GpsDevice::setState(State newState) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
state = newState;
|
||||
}
|
||||
|
||||
} // namespace tt::hal::gps
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/hal/uart/Uart.h>
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
|
||||
#define TAG "gps"
|
||||
#define GPS_UART_BUFFER_SIZE 256
|
||||
|
||||
using namespace tt;
|
||||
using namespace tt::hal;
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
static int ackGps(uart_port_t port, uint8_t* buffer, uint16_t size, uint8_t requestedClass, uint8_t requestedId) {
|
||||
uint16_t ubxFrameCounter = 0;
|
||||
uint32_t startTime = kernel::getTicks();
|
||||
uint16_t needRead;
|
||||
|
||||
while (kernel::getTicks() - startTime < 800) {
|
||||
while (uart::available(port)) {
|
||||
uint8_t c;
|
||||
uart::readByte(port, &c);
|
||||
switch (ubxFrameCounter) {
|
||||
case 0:
|
||||
if (c == 0xB5) {
|
||||
ubxFrameCounter++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0x62) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (c == requestedClass) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (c == requestedId) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
needRead = c;
|
||||
ubxFrameCounter++;
|
||||
break;
|
||||
case 5:
|
||||
needRead |= (c << 8);
|
||||
ubxFrameCounter++;
|
||||
break;
|
||||
case 6:
|
||||
if (needRead >= size) {
|
||||
ubxFrameCounter = 0;
|
||||
break;
|
||||
}
|
||||
if (uart::readBytes(port, buffer, needRead) != needRead) {
|
||||
ubxFrameCounter = 0;
|
||||
} else {
|
||||
return needRead;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool configureGps(uart_port_t port, uint8_t* buffer, size_t bufferSize) {
|
||||
// According to the reference implementation, L76K GPS uses 9600 baudrate, but the default in the developer device was 38400
|
||||
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/GPSShield/GPSShield.ino
|
||||
bool result = false;
|
||||
uint32_t startTimeout;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!uart::writeString(port, "$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n")) {
|
||||
TT_LOG_E(TAG, "Failed to write init string");
|
||||
}
|
||||
|
||||
kernel::delayMillis(5U);
|
||||
// Get version information
|
||||
startTimeout = kernel::getMillis() + 1000;
|
||||
|
||||
TT_LOG_I(TAG, "Manual flushing of input");
|
||||
#ifdef ESP_PLATFORM
|
||||
esp_rom_printf("Waiting...");
|
||||
#endif
|
||||
while (uart::available(port)) {
|
||||
#ifdef ESP_PLATFORM
|
||||
esp_rom_printf(".");
|
||||
#endif
|
||||
uart::readUntil(port, buffer, bufferSize, '\n');
|
||||
if (kernel::getMillis() > startTimeout) {
|
||||
TT_LOG_E(TAG, "NMEA timeout");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#ifdef ESP_PLATFORM
|
||||
esp_rom_printf("\n");
|
||||
#endif
|
||||
uart::flushInput(port);
|
||||
kernel::delayMillis(200);
|
||||
|
||||
if (!uart::writeString(port, "$PCAS06,0*1B\r\n")) {
|
||||
TT_LOG_E(TAG, "Failed to write PCAS06");
|
||||
}
|
||||
|
||||
startTimeout = kernel::getMillis() + 500;
|
||||
while (!uart::available(port)) {
|
||||
if (kernel::getMillis() > startTimeout) {
|
||||
TT_LOG_E(TAG, "L76K timeout");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto ver = uart::readStringUntil(port, '\n');
|
||||
if (ver.starts_with("$GPTXT,01,01,02")) {
|
||||
TT_LOG_I(TAG, "L76K GNSS init success");
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
kernel::delayMillis(500);
|
||||
}
|
||||
// Initialize the L76K Chip, use GPS + GLONASS
|
||||
uart::writeString(port, "$PCAS04,5*1C\r\n");
|
||||
kernel::delayMillis(250);
|
||||
uart::writeString(port, "$PCAS03,1,1,1,1,1,1,1,1,1,1,,,0,0*26\r\n");
|
||||
kernel::delayMillis(250);
|
||||
|
||||
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
|
||||
uart::writeString(port, "$PCAS11,3*1E\r\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool recoverGps(uart_port_t port, uint8_t* buffer, size_t bufferSize) {
|
||||
uint8_t cfg_clear1[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1C, 0xA2};
|
||||
uint8_t cfg_clear2[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1B, 0xA1};
|
||||
uint8_t cfg_clear3[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x1D, 0xB3};
|
||||
|
||||
if (!uart::writeBytes(port, cfg_clear1, sizeof(cfg_clear1), 10)) {
|
||||
return false;
|
||||
TT_LOG_E(TAG, "Failed to send ack 1");
|
||||
}
|
||||
|
||||
if (ackGps(port, buffer, bufferSize, 0x05, 0x01)) {
|
||||
TT_LOG_I(TAG, "Ack 1");
|
||||
} else {
|
||||
TT_LOG_W(TAG, "Ack 1 failed");
|
||||
}
|
||||
|
||||
if (!uart::writeBytes(port, cfg_clear2, sizeof(cfg_clear2))) {
|
||||
return false;
|
||||
TT_LOG_E(TAG, "Failed to send ack 2");
|
||||
}
|
||||
|
||||
if (ackGps(port, buffer, bufferSize, 0x05, 0x01)) {
|
||||
TT_LOG_I(TAG, "Ack 2");
|
||||
} else {
|
||||
TT_LOG_W(TAG, "Ack 2 failed");
|
||||
}
|
||||
|
||||
if (!uart::writeBytes(port, cfg_clear3, sizeof(cfg_clear3))) {
|
||||
TT_LOG_E(TAG, "Failed to send ack 3");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ackGps(port, buffer, bufferSize, 0x05, 0x01)) {
|
||||
TT_LOG_I(TAG, "Ack 3");
|
||||
} else {
|
||||
TT_LOG_W(TAG, "Ack 3 failed");
|
||||
}
|
||||
|
||||
// UBX-CFG-RATE, Size 8, 'Navigation/measurement rate settings'
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x0E, 0x30};
|
||||
uart::writeBytes(port, cfg_rate, sizeof(cfg_rate));
|
||||
if (ackGps(port, buffer, bufferSize, 0x06, 0x08)) {
|
||||
TT_LOG_I(TAG, "Ack completed");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initGpsDefault(uart_port_t port) {
|
||||
uint8_t buffer[GPS_UART_BUFFER_SIZE];
|
||||
if (!configureGps(port, buffer, GPS_UART_BUFFER_SIZE)) {
|
||||
if (!recoverGps(port, buffer, GPS_UART_BUFFER_SIZE)) {
|
||||
uint32_t initial_baud_rate = std::max(uart::getBaudRate(port), (uint32_t)9600U);
|
||||
uart::setBaudRate(port, 9600U);
|
||||
if (!recoverGps(port, buffer, GPS_UART_BUFFER_SIZE)) {
|
||||
TT_LOG_E(TAG, "Recovery repeatedly failed");
|
||||
return false;
|
||||
} else {
|
||||
TT_LOG_I(TAG, "Recovery 2 complete");
|
||||
}
|
||||
uart::setBaudRate(port, initial_baud_rate);
|
||||
} else {
|
||||
TT_LOG_I(TAG, "Recovery 1 complete");
|
||||
}
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Init complete");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace tt::hal::gps
|
||||
@@ -1,8 +1,140 @@
|
||||
#include "Tactility/hal/gps/Cas.h"
|
||||
#include "Tactility/hal/gps/GpsDevice.h"
|
||||
#include "Tactility/hal/gps/Ublox.h"
|
||||
#include "Tactility/service/Service.h"
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "gps"
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
bool initMtk(uart_port_t port);
|
||||
bool initMtkL76b(uart_port_t port);
|
||||
bool initMtkPa1616s(uart_port_t port);
|
||||
bool initAtgm336h(uart_port_t port);
|
||||
bool initUc6580(uart_port_t port);
|
||||
bool initAg33xx(uart_port_t port);
|
||||
|
||||
// region CAS
|
||||
|
||||
// Calculate the checksum for a CAS packet
|
||||
static void CASChecksum(uint8_t *message, size_t length)
|
||||
{
|
||||
uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID
|
||||
cksum += ((uint32_t)message[4]) << 16; // Class
|
||||
cksum += message[2]; // Payload Len
|
||||
|
||||
// Iterate over the payload as a series of uint32_t's and
|
||||
// accumulate the cksum
|
||||
for (size_t i = 0; i < (length - 10) / 4; i++) {
|
||||
uint32_t pl = 0;
|
||||
memcpy(&pl, (message + 6) + (i * sizeof(uint32_t)), sizeof(uint32_t)); // avoid pointer dereference
|
||||
cksum += pl;
|
||||
}
|
||||
|
||||
// Place the checksum values in the message
|
||||
message[length - 4] = (cksum & 0xFF);
|
||||
message[length - 3] = (cksum & (0xFF << 8)) >> 8;
|
||||
message[length - 2] = (cksum & (0xFF << 16)) >> 16;
|
||||
message[length - 1] = (cksum & (0xFF << 24)) >> 24;
|
||||
}
|
||||
|
||||
// Function to create a CAS packet for editing in memory
|
||||
static uint8_t makeCASPacket(uint8_t* buffer, uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
|
||||
{
|
||||
// General CAS structure
|
||||
// | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum |
|
||||
// Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 |
|
||||
// Pos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ... | 6 + payload_len ... |
|
||||
// |------|------|-------------|------|------|------|--------------|---------------------------|
|
||||
// | 0xBA | 0xCE | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
|
||||
// Construct the CAS packet
|
||||
buffer[0] = 0xBA; // header 1 (0xBA)
|
||||
buffer[1] = 0xCE; // header 2 (0xCE)
|
||||
buffer[2] = payload_size; // length 1
|
||||
buffer[3] = 0; // length 2
|
||||
buffer[4] = class_id; // class
|
||||
buffer[5] = msg_id; // id
|
||||
|
||||
buffer[6 + payload_size] = 0x00; // Checksum
|
||||
buffer[7 + payload_size] = 0x00;
|
||||
buffer[8 + payload_size] = 0x00;
|
||||
buffer[9 + payload_size] = 0x00;
|
||||
|
||||
for (int i = 0; i < payload_size; i++) {
|
||||
buffer[6 + i] = msg[i];
|
||||
}
|
||||
CASChecksum(buffer, (payload_size + 10));
|
||||
|
||||
return (payload_size + 10);
|
||||
}
|
||||
|
||||
GpsResponse getACKCas(uart_port_t port, uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
|
||||
{
|
||||
uint32_t startTime = kernel::getMillis();
|
||||
uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0};
|
||||
uint8_t bufferPos = 0;
|
||||
|
||||
// CAS-ACK-(N)ACK structure
|
||||
// | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) |
|
||||
// | | | | | | Cls | Msg | Reserved | |
|
||||
// |------|------|-------------|------|------|------|------|-------------|---------------------------|
|
||||
// ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
// ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
|
||||
|
||||
while (kernel::getTicks() - startTime < waitMillis) {
|
||||
if (uart::available(port)) {
|
||||
uart::readByte(port, &buffer[bufferPos++]);
|
||||
|
||||
// keep looking at the first two bytes of buffer until
|
||||
// we have found the CAS frame header (0xBA, 0xCE), if not
|
||||
// keep reading bytes until we find a frame header or we run
|
||||
// out of time.
|
||||
if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) {
|
||||
buffer[0] = buffer[1];
|
||||
buffer[1] = 0;
|
||||
bufferPos = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// we have read all the bytes required for the Ack/Nack (14-bytes)
|
||||
// and we must have found a frame to get this far
|
||||
if (bufferPos == sizeof(buffer) - 1) {
|
||||
uint8_t msg_cls = buffer[4]; // message class should be 0x05
|
||||
uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01
|
||||
uint8_t payload_cls = buffer[6]; // payload class id
|
||||
uint8_t payload_msg = buffer[7]; // payload message id
|
||||
|
||||
// Check for an ACK-ACK for the specified class and message id
|
||||
if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_INFO("Got ACK for class %02X message %02X in %dms", class_id, msg_id, millis() - startTime);
|
||||
#endif
|
||||
return GpsResponse::Ok;
|
||||
}
|
||||
|
||||
// Check for an ACK-NACK for the specified class and message id
|
||||
if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_WARN("Got NACK for class %02X message %02X in %dms", class_id, msg_id, millis() - startTime);
|
||||
#endif
|
||||
return GpsResponse::NotAck;
|
||||
}
|
||||
|
||||
// This isn't the frame we are looking for, clear the buffer
|
||||
// and try again until we run out of time.
|
||||
memset(buffer, 0x0, sizeof(buffer));
|
||||
bufferPos = 0;
|
||||
}
|
||||
}
|
||||
return GpsResponse::None;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
|
||||
/** Initialize the HAL with the provided configuration */
|
||||
bool init(const std::vector<GpsDevice::Configuration>& configurations) {
|
||||
for (auto& configuration : configurations) {
|
||||
auto device = std::make_shared<GpsDevice>(configuration);
|
||||
@@ -12,4 +144,157 @@ bool init(const std::vector<GpsDevice::Configuration>& configurations) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool init(uart_port_t port, GpsModel type) {
|
||||
switch (type) {
|
||||
case GpsModel::Unknown:
|
||||
tt_crash();
|
||||
case GpsModel::AG3335:
|
||||
case GpsModel::AG3352:
|
||||
return initAg33xx(port);
|
||||
case GpsModel::ATGM336H:
|
||||
return initAtgm336h(port);
|
||||
case GpsModel::LS20031:
|
||||
break;
|
||||
case GpsModel::MTK:
|
||||
return initMtk(port);
|
||||
case GpsModel::MTK_L76B:
|
||||
return initMtkL76b(port);
|
||||
case GpsModel::MTK_PA1616S:
|
||||
return initMtkPa1616s(port);
|
||||
case GpsModel::UBLOX6:
|
||||
case GpsModel::UBLOX7:
|
||||
case GpsModel::UBLOX8:
|
||||
case GpsModel::UBLOX9:
|
||||
case GpsModel::UBLOX10:
|
||||
return ublox::init(port, type);
|
||||
case GpsModel::UC6580:
|
||||
return initUc6580(port);
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Init not implemented %d", static_cast<int>(type));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool initAg33xx(uart_port_t port) {
|
||||
uart::writeString(port, "$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
|
||||
|
||||
// Configure NMEA (sentences will output once per fix)
|
||||
uart::writeString(port, "$PAIR062,0,1*3F\r\n"); // GGA ON
|
||||
uart::writeString(port, "$PAIR062,1,0*3F\r\n"); // GLL OFF
|
||||
uart::writeString(port, "$PAIR062,2,0*3C\r\n"); // GSA OFF
|
||||
uart::writeString(port, "$PAIR062,3,0*3D\r\n"); // GSV OFF
|
||||
uart::writeString(port, "$PAIR062,4,1*3B\r\n"); // RMC ON
|
||||
uart::writeString(port, "$PAIR062,5,0*3B\r\n"); // VTG OFF
|
||||
uart::writeString(port, "$PAIR062,6,0*38\r\n"); // ZDA ON
|
||||
|
||||
kernel::delayMillis(250);
|
||||
uart::writeString(port, "$PAIR513*3D\r\n"); // save configuration
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initUc6580(uart_port_t port) {
|
||||
// The Unicore UC6580 can use a lot of sat systems, enable it to
|
||||
// use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS + QZSS
|
||||
// This will reset the receiver, so wait a bit afterwards
|
||||
// The paranoid will wait for the OK*04 confirmation response after each command.
|
||||
uart::writeString(port, "$CFGSYS,h35155\r\n");
|
||||
kernel::delayMillis(750);
|
||||
// Must be done after the CFGSYS command
|
||||
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
|
||||
uart::writeString(port, "$CFGMSG,0,3,0\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Turn off GSA messages, TinyGPS++ doesn't use this message.
|
||||
uart::writeString(port, "$CFGMSG,0,2,0\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care.
|
||||
uart::writeString(port, "$CFGMSG,6,0,0\r\n");
|
||||
kernel::delayMillis(250);
|
||||
uart::writeString(port, "$CFGMSG,6,1,0\r\n");
|
||||
kernel::delayMillis(250);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initAtgm336h(uart_port_t port) {
|
||||
uint8_t buffer[256];
|
||||
|
||||
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
|
||||
int msglen = makeCASPacket(buffer, 0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
uart::writeBytes(port, buffer, msglen);
|
||||
if (getACKCas(port, 0x06, 0x07, 250) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "ATGM336H: Could not set Config");
|
||||
}
|
||||
|
||||
// Set the update frequence to 1Hz
|
||||
msglen = makeCASPacket(buffer, 0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
uart::writeBytes(port, buffer, msglen);
|
||||
if (getACKCas(port, 0x06, 0x04, 250) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "ATGM336H: Could not set Update Frequency");
|
||||
}
|
||||
|
||||
// Set the NEMA output messages
|
||||
// Ask for only RMC and GGA
|
||||
uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
|
||||
for (unsigned int i = 0; i < sizeof(fields); i++) {
|
||||
// Construct a CAS-CFG-MSG packet
|
||||
uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
|
||||
msglen = makeCASPacket(buffer, 0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
|
||||
uart::writeBytes(port, buffer, msglen);
|
||||
if (getACKCas(port, 0x06, 0x01, 250) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "ATGM336H: Could not enable NMEA MSG: %d", fields[i]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initMtkPa1616s(uart_port_t port) {
|
||||
// PA1616S is used in some GPS breakout boards from Adafruit
|
||||
// PA1616S does not have GLONASS capability. PA1616D does, but is not implemented here.
|
||||
uart::writeString(port, "$PMTK353,1,0,0,0,0*2A\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
kernel::delayMillis(1000);
|
||||
// Only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
uart::writeString(port, "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Enable SBAS / WAAS
|
||||
uart::writeString(port, "$PMTK301,2*2E\r\n");
|
||||
kernel::delayMillis(250);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initMtkL76b(uart_port_t port) {
|
||||
// Waveshare Pico-GPS hat uses the L76B with 9600 baud
|
||||
// Initialize the L76B Chip, use GPS + GLONASS
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29
|
||||
uart::writeString(port, "$PMTK353,1,1,0,0,0*2B\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
kernel::delayMillis(1000);
|
||||
// only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
|
||||
uart::writeString(port, "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Enable SBAS
|
||||
uart::writeString(port, "$PMTK301,2*2E\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Enable PPS for 2D/3D fix only
|
||||
uart::writeString(port, "$PMTK285,3,100*3F\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
|
||||
uart::writeString(port, "$PMTK886,1*29\r\n");
|
||||
kernel::delayMillis(250);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initMtk(uart_port_t port) {
|
||||
// Initialize the L76K Chip, use GPS + GLONASS + BEIDOU
|
||||
uart::writeString(port, "$PCAS04,7*1E\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// only ask for RMC and GGA
|
||||
uart::writeString(port, "$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n");
|
||||
kernel::delayMillis(250);
|
||||
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
|
||||
uart::writeString(port, "$PCAS11,3*1E\r\n");
|
||||
kernel::delayMillis(250);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace tt::hal::gps
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "Tactility/hal/gps/GpsDevice.h"
|
||||
#include "Tactility/hal/gps/Ublox.h"
|
||||
#include <Tactility/Log.h>
|
||||
#include <Tactility/hal/uart/Uart.h>
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "gps"
|
||||
#define GPS_UART_BUFFER_SIZE 256
|
||||
|
||||
using namespace tt;
|
||||
using namespace tt::hal;
|
||||
|
||||
namespace tt::hal::gps {
|
||||
|
||||
/**
|
||||
* From: https://github.com/meshtastic/firmware/blob/3b0232de1b6282eacfbff6e50b68fca7e67b8511/src/meshUtils.cpp#L40
|
||||
*/
|
||||
char* strnstr(const char* s, const char* find, size_t slen) {
|
||||
char c;
|
||||
if ((c = *find++) != '\0') {
|
||||
char sc;
|
||||
size_t len;
|
||||
|
||||
len = strlen(find);
|
||||
do {
|
||||
do {
|
||||
if (slen-- < 1 || (sc = *s++) == '\0')
|
||||
return (nullptr);
|
||||
} while (sc != c);
|
||||
if (len > slen)
|
||||
return (nullptr);
|
||||
} while (strncmp(s, find, len) != 0);
|
||||
s--;
|
||||
}
|
||||
return ((char*)s);
|
||||
}
|
||||
|
||||
/**
|
||||
* From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp
|
||||
*/
|
||||
GpsResponse getAck(uart_port_t port, const char* message, uint32_t waitMillis) {
|
||||
uint8_t buffer[768] = {0};
|
||||
uint8_t b;
|
||||
int bytesRead = 0;
|
||||
uint32_t startTimeout = kernel::getMillis() + waitMillis;
|
||||
#ifdef GPS_DEBUG
|
||||
std::string debugmsg = "";
|
||||
#endif
|
||||
while (kernel::getMillis() < startTimeout) {
|
||||
if (uart::available(port)) {
|
||||
uart::readByte(port, &b);
|
||||
|
||||
#ifdef GPS_DEBUG
|
||||
debugmsg += vformat("%c", (b >= 32 && b <= 126) ? b : '.');
|
||||
#endif
|
||||
buffer[bytesRead] = b;
|
||||
bytesRead++;
|
||||
if ((bytesRead == 767) || (b == '\r')) {
|
||||
if (strnstr((char*)buffer, message, bytesRead) != nullptr) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("Found: %s", message); // Log the found message
|
||||
#endif
|
||||
return GpsResponse::Ok;
|
||||
} else {
|
||||
bytesRead = 0;
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(debugmsg.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return GpsResponse::None;
|
||||
}
|
||||
|
||||
/**
|
||||
* From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp
|
||||
*/
|
||||
#define PROBE_SIMPLE(PORT, CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
|
||||
do { \
|
||||
TT_LOG_I(TAG, "Probing for %s (%s)", CHIP, TOWRITE); \
|
||||
uart::flushInput(PORT); \
|
||||
uart::writeString(PORT, TOWRITE "\r\n", TIMEOUT); \
|
||||
if (getAck(PORT, RESPONSE, TIMEOUT) == GpsResponse::Ok) { \
|
||||
TT_LOG_I(TAG, "Probe detected %s %s", CHIP, #DRIVER); \
|
||||
return DRIVER; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* From: https://github.com/meshtastic/firmware/blob/f81d3b045dd1b7e3ca7870af3da915ff4399ea98/src/gps/GPS.cpp
|
||||
*/
|
||||
GpsModel probe(uart_port_t port) {
|
||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||
uart::writeString(port, "$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||
kernel::delayMillis(20);
|
||||
|
||||
// Close NMEA sequences on Ublox
|
||||
uart::writeString(port, "$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
|
||||
uart::writeString(port, "$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
|
||||
uart::writeString(port, "$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
|
||||
kernel::delayMillis(20);
|
||||
|
||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
|
||||
PROBE_SIMPLE(port, "UC6580", "$PDTINFO", "UC6580", GpsModel::UC6580, 500);
|
||||
PROBE_SIMPLE(port, "UM600", "$PDTINFO", "UM600", GpsModel::UC6580, 500);
|
||||
PROBE_SIMPLE(port, "ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GpsModel::ATGM336H, 500);
|
||||
|
||||
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
|
||||
based on AT6558 */
|
||||
PROBE_SIMPLE(port, "ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GpsModel::ATGM336H, 500);
|
||||
|
||||
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
|
||||
uart::writeString(port, "$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
|
||||
uart::writeString(port, "$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
|
||||
uart::writeString(port, "$PAIR513*3D\r\n"); // save configuration
|
||||
PROBE_SIMPLE(port, "AG3335", "$PAIR021*39", "$PAIR021,AG3335", GpsModel::AG3335, 500);
|
||||
PROBE_SIMPLE(port, "AG3352", "$PAIR021*39", "$PAIR021,AG3352", GpsModel::AG3352, 500);
|
||||
PROBE_SIMPLE(port, "LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GpsModel::AG3352, 500);
|
||||
|
||||
PROBE_SIMPLE(port, "L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GpsModel::MTK, 500);
|
||||
|
||||
// Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
|
||||
uart::writeString(port, "$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
|
||||
kernel::delayMillis(20);
|
||||
|
||||
PROBE_SIMPLE(port, "L76B", "$PMTK605*31", "Quectel-L76B", GpsModel::MTK_L76B, 500);
|
||||
PROBE_SIMPLE(port, "PA1616S", "$PMTK605*31", "1616S", GpsModel::MTK_PA1616S, 500);
|
||||
|
||||
auto ublox_result = ublox::probe(port);
|
||||
if (ublox_result != GpsModel::Unknown) {
|
||||
return ublox_result;
|
||||
} else {
|
||||
TT_LOG_W(TAG, "No GNSS Module (baudrate %lu)", uart::getBaudRate(port));
|
||||
return GpsModel::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tt::hal::gps
|
||||
@@ -0,0 +1,479 @@
|
||||
#include "Tactility/hal/gps/Ublox.h"
|
||||
#include "Tactility/hal/gps/UbloxMessages.h"
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "ublox"
|
||||
|
||||
namespace tt::hal::gps::ublox {
|
||||
|
||||
bool initUblox6(uart_port_t port);
|
||||
bool initUblox789(uart_port_t port, GpsModel model);
|
||||
bool initUblox10(uart_port_t port);
|
||||
|
||||
#define SEND_UBX_PACKET(PORT, BUFFER, TYPE, ID, DATA, ERRMSG, TIMEOUT) \
|
||||
do { \
|
||||
auto msglen = makePacket(TYPE, ID, DATA, sizeof(DATA), BUFFER); \
|
||||
uart::writeBytes(PORT, BUFFER, sizeof(BUFFER)); \
|
||||
if (getAck(PORT, TYPE, ID, TIMEOUT) != GpsResponse::Ok) { \
|
||||
TT_LOG_I(TAG, "Sending packet failed: %s", #ERRMSG); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void checksum(uint8_t* message, size_t length) {
|
||||
uint8_t CK_A = 0, CK_B = 0;
|
||||
|
||||
// Calculate the checksum, starting from the CLASS field (which is message[2])
|
||||
for (size_t i = 2; i < length - 2; i++) {
|
||||
CK_A = (CK_A + message[i]) & 0xFF;
|
||||
CK_B = (CK_B + CK_A) & 0xFF;
|
||||
}
|
||||
|
||||
// Place the calculated checksum values in the message
|
||||
message[length - 2] = CK_A;
|
||||
message[length - 1] = CK_B;
|
||||
}
|
||||
|
||||
uint8_t makePacket(uint8_t classId, uint8_t messageId, const uint8_t* payload, uint8_t payloadSize, uint8_t* bufferOut) {
|
||||
// Construct the UBX packet
|
||||
bufferOut[0] = 0xB5U; // header
|
||||
bufferOut[1] = 0x62U; // header
|
||||
bufferOut[2] = classId; // class
|
||||
bufferOut[3] = messageId; // id
|
||||
bufferOut[4] = payloadSize; // length
|
||||
bufferOut[5] = 0x00U;
|
||||
|
||||
bufferOut[6 + payloadSize] = 0x00U; // CK_A
|
||||
bufferOut[7 + payloadSize] = 0x00U; // CK_B
|
||||
|
||||
for (int i = 0; i < payloadSize; i++) {
|
||||
bufferOut[6 + i] = payload[i];
|
||||
}
|
||||
checksum(bufferOut, (payloadSize + 8U));
|
||||
return (payloadSize + 8U);
|
||||
}
|
||||
|
||||
GpsResponse getAck(uart_port_t port, uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) {
|
||||
uint8_t b;
|
||||
uint8_t ack = 0;
|
||||
const uint8_t ackP[2] = {class_id, msg_id};
|
||||
uint8_t buf[10] = {0xB5, 0x62, 0x05, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint32_t startTime = kernel::getMillis();
|
||||
const char frame_errors[] = "More than 100 frame errors";
|
||||
int sCounter = 0;
|
||||
#ifdef GPS_DEBUG
|
||||
std::string debugmsg = "";
|
||||
#endif
|
||||
|
||||
for (int j = 2; j < 6; j++) {
|
||||
buf[8] += buf[j];
|
||||
buf[9] += buf[8];
|
||||
}
|
||||
|
||||
for (int j = 0; j < 2; j++) {
|
||||
buf[6 + j] = ackP[j];
|
||||
buf[8] += buf[6 + j];
|
||||
buf[9] += buf[8];
|
||||
}
|
||||
|
||||
while (kernel::getTicks() - startTime < waitMillis) {
|
||||
if (ack > 9) {
|
||||
#ifdef GPS_DEBUG
|
||||
TT_LOG_I(TAG, "Got ACK for class %02X message %02X in %lums", class_id, msg_id, kernel::getMillis() - startTime);
|
||||
#endif
|
||||
return GpsResponse::Ok; // ACK received
|
||||
}
|
||||
if (uart::available(port)) {
|
||||
uart::readByte(port, &b);
|
||||
if (b == frame_errors[sCounter]) {
|
||||
sCounter++;
|
||||
if (sCounter == 26) {
|
||||
#ifdef GPS_DEBUG
|
||||
|
||||
TT_LOG_I(TAG, "%s", debugmsg.c_str());
|
||||
#endif
|
||||
return GpsResponse::FrameErrors;
|
||||
}
|
||||
} else {
|
||||
sCounter = 0;
|
||||
}
|
||||
#ifdef GPS_DEBUG
|
||||
debugmsg += std::format("%02X", b);
|
||||
#endif
|
||||
if (b == buf[ack]) {
|
||||
ack++;
|
||||
} else {
|
||||
if (ack == 3 && b == 0x00) { // UBX-ACK-NAK message
|
||||
#ifdef GPS_DEBUG
|
||||
TT_LOG_I(TAG, "%s", debugmsg.c_str());
|
||||
#endif
|
||||
TT_LOG_W(TAG, "Got NAK for class %02X message %02X", class_id, msg_id);
|
||||
return GpsResponse::NotAck; // NAK received
|
||||
}
|
||||
ack = 0; // Reset the acknowledgement counter
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef GPS_DEBUG
|
||||
TT_LOG_I(TAG, "%s", debugmsg.c_str());
|
||||
TT_LOG_W(TAG, "No response for class %02X message %02X", class_id, msg_id);
|
||||
#endif
|
||||
return GpsResponse::None; // No response received within timeout
|
||||
}
|
||||
|
||||
static int getAck(uart_port_t port, uint8_t* buffer, uint16_t size, uint8_t requestedClass, uint8_t requestedId, TickType_t timeout) {
|
||||
uint16_t ubxFrameCounter = 0;
|
||||
uint32_t startTime = kernel::getTicks();
|
||||
uint16_t needRead = 0;
|
||||
|
||||
while (kernel::getTicks() - startTime < timeout) {
|
||||
while (uart::available(port)) {
|
||||
uint8_t c;
|
||||
uart::readByte(port, &c);
|
||||
|
||||
switch (ubxFrameCounter) {
|
||||
case 0:
|
||||
if (c == 0xB5) {
|
||||
ubxFrameCounter++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0x62) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (c == requestedClass) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (c == requestedId) {
|
||||
ubxFrameCounter++;
|
||||
} else {
|
||||
ubxFrameCounter = 0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
needRead = c;
|
||||
ubxFrameCounter++;
|
||||
break;
|
||||
case 5: {
|
||||
// Payload length msb
|
||||
needRead |= (c << 8);
|
||||
ubxFrameCounter++;
|
||||
// Check for buffer overflow
|
||||
if (needRead >= size) {
|
||||
ubxFrameCounter = 0;
|
||||
break;
|
||||
}
|
||||
auto read_bytes = uart::readBytes(port, buffer, needRead, 250 / portTICK_PERIOD_MS);
|
||||
if (read_bytes != needRead) {
|
||||
ubxFrameCounter = 0;
|
||||
} else {
|
||||
// return payload length
|
||||
#ifdef GPS_DEBUG
|
||||
TT_LOG_I(TAG, "Got ACK for class %02X message %02X in %lums", requestedClass, requestedId, kernel::getMillis() - startTime);
|
||||
#endif
|
||||
return needRead;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DETECTED_MESSAGE "%s detected, using %s Module"
|
||||
|
||||
static struct uBloxGnssModelInfo {
|
||||
char swVersion[30];
|
||||
char hwVersion[10];
|
||||
uint8_t extensionNo;
|
||||
char extension[10][30];
|
||||
uint8_t protocol_version;
|
||||
} ublox_info;
|
||||
|
||||
GpsModel probe(uart_port_t port) {
|
||||
TT_LOG_I(TAG, "Probing for U-blox");
|
||||
|
||||
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
|
||||
checksum(cfg_rate, sizeof(cfg_rate));
|
||||
uart::flushInput(port);
|
||||
uart::writeBytes(port, cfg_rate, sizeof(cfg_rate));
|
||||
// Check that the returned response class and message ID are correct
|
||||
GpsResponse response = getAck(port, 0x06, 0x08, 750);
|
||||
if (response == GpsResponse::None) {
|
||||
TT_LOG_W(TAG, "No GNSS Module (baudrate %lu)", uart::getBaudRate(port));
|
||||
return GpsModel::Unknown;
|
||||
} else if (response == GpsResponse::FrameErrors) {
|
||||
TT_LOG_W(TAG, "UBlox Frame Errors (baudrate %lu)", uart::getBaudRate(port));
|
||||
}
|
||||
|
||||
uint8_t buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
uint8_t _message_MONVER[8] = {
|
||||
0xB5, 0x62, // Sync message for UBX protocol
|
||||
0x0A, 0x04, // Message class and ID (UBX-MON-VER)
|
||||
0x00, 0x00, // Length of payload (we're asking for an answer, so no payload)
|
||||
0x00, 0x00 // Checksum
|
||||
};
|
||||
// Get Ublox gnss module hardware and software info
|
||||
checksum(_message_MONVER, sizeof(_message_MONVER));
|
||||
uart::flushInput(port);
|
||||
uart::writeBytes(port, _message_MONVER, sizeof(_message_MONVER));
|
||||
|
||||
uint16_t ack_response_len = getAck(port, buffer, sizeof(buffer), 0x0A, 0x04, 1200);
|
||||
if (ack_response_len) {
|
||||
uint16_t position = 0;
|
||||
for (char& i: ublox_info.swVersion) {
|
||||
i = buffer[position];
|
||||
position++;
|
||||
}
|
||||
for (char& i: ublox_info.hwVersion) {
|
||||
i = buffer[position];
|
||||
position++;
|
||||
}
|
||||
|
||||
while (ack_response_len >= position + 30) {
|
||||
for (int i = 0; i < 30; i++) {
|
||||
ublox_info.extension[ublox_info.extensionNo][i] = buffer[position];
|
||||
position++;
|
||||
}
|
||||
ublox_info.extensionNo++;
|
||||
if (ublox_info.extensionNo > 9)
|
||||
break;
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Module Info : ");
|
||||
TT_LOG_I(TAG, "Soft version: %s", ublox_info.swVersion);
|
||||
TT_LOG_I(TAG, "Hard version: %s", ublox_info.hwVersion);
|
||||
TT_LOG_I(TAG, "Extensions:%d", ublox_info.extensionNo);
|
||||
for (int i = 0; i < ublox_info.extensionNo; i++) {
|
||||
TT_LOG_I(TAG, " %s", ublox_info.extension[i]);
|
||||
}
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
// tips: extensionNo field is 0 on some 6M GNSS modules
|
||||
for (int i = 0; i < ublox_info.extensionNo; ++i) {
|
||||
if (!strncmp(ublox_info.extension[i], "MOD=", 4)) {
|
||||
strncpy((char*)buffer, &(ublox_info.extension[i][4]), sizeof(buffer));
|
||||
} else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) {
|
||||
char* ptr = nullptr;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
strncpy((char*)buffer, &(ublox_info.extension[i][8]), sizeof(buffer));
|
||||
TT_LOG_I(TAG, "Protocol Version:%s", (char*)buffer);
|
||||
if (strlen((char*)buffer)) {
|
||||
ublox_info.protocol_version = strtoul((char*)buffer, &ptr, 10);
|
||||
TT_LOG_I(TAG, "ProtVer=%d", ublox_info.protocol_version);
|
||||
} else {
|
||||
ublox_info.protocol_version = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) {
|
||||
TT_LOG_I(TAG, DETECTED_MESSAGE, "U-blox 6", "6");
|
||||
return GpsModel::UBLOX6;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) {
|
||||
TT_LOG_I(TAG, DETECTED_MESSAGE, "U-blox 7", "7");
|
||||
return GpsModel::UBLOX7;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) {
|
||||
TT_LOG_I(TAG, DETECTED_MESSAGE, "U-blox 8", "8");
|
||||
return GpsModel::UBLOX8;
|
||||
} else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) {
|
||||
TT_LOG_I(TAG, DETECTED_MESSAGE, "U-blox 9", "9");
|
||||
return GpsModel::UBLOX9;
|
||||
} else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) {
|
||||
TT_LOG_I(TAG, DETECTED_MESSAGE, "U-blox 10", "10");
|
||||
return GpsModel::UBLOX10;
|
||||
}
|
||||
}
|
||||
|
||||
return GpsModel::Unknown;
|
||||
}
|
||||
|
||||
bool init(uart_port_t port, GpsModel model) {
|
||||
TT_LOG_I(TAG, "U-blox init");
|
||||
switch (model) {
|
||||
case GpsModel::UBLOX6:
|
||||
return initUblox6(port);
|
||||
case GpsModel::UBLOX7:
|
||||
case GpsModel::UBLOX8:
|
||||
case GpsModel::UBLOX9:
|
||||
return initUblox789(port, model);
|
||||
case GpsModel::UBLOX10:
|
||||
return initUblox10(port);
|
||||
default:
|
||||
TT_LOG_E(TAG, "Unknown or unsupported U-blox model");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool initUblox10(uart_port_t port) {
|
||||
uint8_t buffer[256];
|
||||
kernel::delayMillis(1000);
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300);
|
||||
kernel::delayMillis(750);
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300);
|
||||
kernel::delayMillis(750);
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300);
|
||||
kernel::delayMillis(750);
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300);
|
||||
kernel::delayMillis(750);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersave for M10 GPS RAM", 300);
|
||||
kernel::delayMillis(750);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersave for M10 GPS BBR", 300);
|
||||
kernel::delayMillis(750);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable jam detection M10 GPS RAM", 300);
|
||||
kernel::delayMillis(750);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable jam detection M10 GPS BBR", 300);
|
||||
kernel::delayMillis(750);
|
||||
// Here is where the init commands should go to do further M10 initialization.
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300);
|
||||
kernel::delayMillis(750); // will cause a receiver restart so wait a bit
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300);
|
||||
kernel::delayMillis(750); // will cause a receiver restart so wait a bit
|
||||
|
||||
// Done with initialization
|
||||
|
||||
// Enable wanted NMEA messages in BBR layer so they will survive a periodic sleep
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300);
|
||||
kernel::delayMillis(750);
|
||||
// Enable wanted NMEA messages in RAM layer
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500);
|
||||
kernel::delayMillis(750);
|
||||
|
||||
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
auto packet_size = makePacket(0x06, 0x09, _message_SAVE_10, sizeof(_message_SAVE_10), buffer);
|
||||
uart::writeBytes(port, buffer, packet_size);
|
||||
if (getAck(port, 0x06, 0x09, 2000) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "Unable to save GNSS module config");
|
||||
} else {
|
||||
TT_LOG_I(TAG, "GNSS module configuration saved!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initUblox789(uart_port_t port, GpsModel model) {
|
||||
uint8_t buffer[256];
|
||||
if (model == GpsModel::UBLOX7) {
|
||||
TT_LOG_D(TAG, "Set GPS+SBAS");
|
||||
auto msglen = makePacket(0x06, 0x3e, _message_GNSS_7, sizeof(_message_GNSS_7), buffer);
|
||||
uart::writeBytes(port, buffer, msglen);
|
||||
} else { // 8,9
|
||||
auto msglen = makePacket(0x06, 0x3e, _message_GNSS_8, sizeof(_message_GNSS_8), buffer);
|
||||
uart::writeBytes(port, buffer, msglen);
|
||||
}
|
||||
|
||||
if (getAck(port, 0x06, 0x3e, 800) == GpsResponse::NotAck) {
|
||||
// It's not critical if the module doesn't acknowledge this configuration.
|
||||
TT_LOG_D(TAG, "reconfigure GNSS - defaults maintained. Is this module GPS-only?");
|
||||
} else {
|
||||
if (model == GpsModel::UBLOX7) {
|
||||
TT_LOG_I(TAG, "GPS+SBAS configured");
|
||||
} else { // 8,9
|
||||
TT_LOG_I(TAG, "GPS+SBAS+GLONASS+Galileo configured");
|
||||
}
|
||||
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// commands for the M8 it tends to be more. 1 sec should be enough
|
||||
kernel::delayMillis(1000);
|
||||
}
|
||||
|
||||
uart::flushInput(port);
|
||||
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
|
||||
if (model == GpsModel::UBLOX8) { // 8
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x39, _message_JAM_8, "enable interference resistance", 500);
|
||||
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x23, _message_NAVX5_8, "configure NAVX5_8 settings", 500);
|
||||
} else { // 6,7,9
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
}
|
||||
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
|
||||
if (ublox_info.protocol_version >= 18) {
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
|
||||
// For M8 we want to enable NMEA version 4.10 so we can see the additional satellites.
|
||||
if (model == GpsModel::UBLOX8) {
|
||||
uart::flushInput(port);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
|
||||
}
|
||||
} else {
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x11, _message_CFG_RXM_PSM, "enable powersave mode for GPS", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
}
|
||||
|
||||
auto packet_size = makePacket(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer);
|
||||
uart::writeBytes(port, buffer, packet_size);
|
||||
if (getAck(port, 0x06, 0x09, 2000) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "Unable to save GNSS module config");
|
||||
} else {
|
||||
TT_LOG_I(TAG, "GNSS module configuration saved!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initUblox6(uart_port_t port) {
|
||||
uint8_t buffer[256];
|
||||
|
||||
uart::flushInput(port);
|
||||
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
|
||||
uart::flushInput(port);
|
||||
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x11, _message_CFG_RXM_ECO, "enable powersave ECO mode for Neo-6", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
SEND_UBX_PACKET(port, buffer, 0x06, 0x01, _message_AID, "disable UBX-AID", 500);
|
||||
|
||||
auto packet_size = makePacket(0x06, 0x09, _message_SAVE, sizeof(_message_SAVE), buffer);
|
||||
uart::writeBytes(port, buffer, packet_size);
|
||||
if (getAck(port, 0x06, 0x09, 2000) != GpsResponse::Ok) {
|
||||
TT_LOG_W(TAG, "Unable to save GNSS module config");
|
||||
} else {
|
||||
TT_LOG_I(TAG, "GNSS module config saved!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace tt::hal::gps::ublox
|
||||
Reference in New Issue
Block a user