diff --git a/chip_test_example/gps.hpp b/chip_test_example/gps.hpp new file mode 100644 index 0000000..6042067 --- /dev/null +++ b/chip_test_example/gps.hpp @@ -0,0 +1,158 @@ +// gps.hpp — u-blox NMEA GPS reader over UART +// No external libs; uses POSIX termios + read(). +// +// Hardware pins (THE TRUTH): +// GPIO14/TXD0 → UBLOX_TX line (/dev/serial0) +// GPIO15/RXD0 → UBLOX_RX line +// Default baud: 9600 +// +// Enable UART: sudo raspi-config → Interfaces → Serial Port +// "login shell over serial" = No, "serial port hardware" = Yes +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +namespace gps { + +struct Fix { + bool valid = false; + double lat = 0.0; // degrees, positive=N + double lon = 0.0; // degrees, positive=E + float speed_knots = 0.0f; + char time[12]{}; // HHMMSS.ss (raw NMEA) + char date[7]{}; // DDMMYY +}; + +class Reader { +public: + bool begin(const char *dev = "/dev/serial0", int baud = 9600); + void end(); + + // Read one NMEA sentence into buf (null-terminated). Returns false on error. + bool readLine(char *buf, size_t cap); + + // Parse $GPRMC or $GNRMC from a sentence. Returns false if sentence is + // not RMC or position is invalid. + static bool parseRmc(const char *sentence, Fix &fix); + +private: + int fd_ = -1; +}; + +inline bool Reader::begin(const char *dev, int baud) +{ + fd_ = ::open(dev, O_RDWR | O_NOCTTY | O_NDELAY); + if (fd_ < 0) return false; + ::fcntl(fd_, F_SETFL, 0); // blocking + + termios t{}; + if (tcgetattr(fd_, &t) < 0) return false; + + speed_t spd; + switch (baud) { + case 4800: spd = B4800; break; + case 9600: spd = B9600; break; + case 19200: spd = B19200; break; + case 38400: spd = B38400; break; + case 57600: spd = B57600; break; + default: spd = B9600; + } + cfsetispeed(&t, spd); + cfsetospeed(&t, spd); + cfmakeraw(&t); + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + tcsetattr(fd_, TCSANOW, &t); + return true; +} + +inline void Reader::end() +{ + if (fd_ >= 0) { ::close(fd_); fd_ = -1; } +} + +inline bool Reader::readLine(char *buf, size_t cap) +{ + size_t n = 0; + char c; + // Wait for '$' (start of NMEA sentence) + do { + if (::read(fd_, &c, 1) != 1) return false; + } while (c != '$'); + buf[n++] = '$'; + + while (n < cap - 1) { + if (::read(fd_, &c, 1) != 1) return false; + buf[n++] = c; + if (c == '\n') break; + } + buf[n] = '\0'; + return true; +} + +// Parse $GPRMC or $GNRMC +// Format: $GNRMC,HHMMSS.ss,A,DDMM.MMMM,N,DDDMM.MMMM,E,spd,crs,DDMMYY,... +inline bool Reader::parseRmc(const char *sentence, Fix &fix) +{ + if (!sentence) return false; + + // Check sentence type + if (std::strncmp(sentence, "$GPRMC", 6) != 0 && + std::strncmp(sentence, "$GNRMC", 6) != 0) + return false; + + // Make a mutable copy + char buf[128]; + std::strncpy(buf, sentence, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + // Tokenize on commas + const char *fields[13]{}; + int nf = 0; + char *p = buf; + while (*p && nf < 13) { + fields[nf++] = p; + char *next = std::strchr(p, ','); + if (!next) break; + *next = '\0'; + p = next + 1; + } + if (nf < 10) return false; + + // field[2] = status ('A'=valid, 'V'=void) + if (fields[2][0] != 'A') { fix.valid = false; return false; } + + // field[1] = time + std::strncpy(fix.time, fields[1], sizeof(fix.time) - 1); + + // field[3] = lat DDMM.MMMM, field[4] = N/S + double raw_lat = std::atof(fields[3]); + int lat_deg = (int)(raw_lat / 100.0); + double lat_min = raw_lat - lat_deg * 100.0; + fix.lat = lat_deg + lat_min / 60.0; + if (fields[4][0] == 'S') fix.lat = -fix.lat; + + // field[5] = lon DDDMM.MMMM, field[6] = E/W + double raw_lon = std::atof(fields[5]); + int lon_deg = (int)(raw_lon / 100.0); + double lon_min = raw_lon - lon_deg * 100.0; + fix.lon = lon_deg + lon_min / 60.0; + if (fields[6][0] == 'W') fix.lon = -fix.lon; + + fix.speed_knots = (float)std::atof(fields[7]); + + // field[9] = date DDMMYY + std::strncpy(fix.date, fields[9], sizeof(fix.date) - 1); + + fix.valid = true; + return true; +} + +} // namespace gps diff --git a/chip_test_example/gps_test.cpp b/chip_test_example/gps_test.cpp new file mode 100644 index 0000000..199817a --- /dev/null +++ b/chip_test_example/gps_test.cpp @@ -0,0 +1,39 @@ +#include + +#include "gps.hpp" + +int main() +{ + gps::Reader reader; + + if (!reader.begin()) { + std::fprintf(stderr, "gps open failed\n" + " check /dev/serial0 (UART)\n" + " enable serial: sudo raspi-config -> Interfaces -> Serial Port\n" + " login shell = No, hardware = Yes\n"); + return 1; + } + std::fprintf(stderr, "gps reading /dev/serial0 at 9600...\n"); + + char line[128]; + gps::Fix fix; + for (;;) { + if (!reader.readLine(line, sizeof(line))) { + std::fprintf(stderr, "uart read error\n"); + break; + } + if (!gps::Reader::parseRmc(line, fix)) { + // Not an RMC sentence or no fix — print raw line for debug + // std::fprintf(stderr, "raw: %s", line); + continue; + } + if (fix.valid) { + std::fprintf(stderr, "fix lat=%+.6f lon=%+.6f spd=%.1f kn " + "time=%s date=%s\n", + fix.lat, fix.lon, fix.speed_knots, + fix.time, fix.date); + } else { + std::fprintf(stderr, "no fix\n"); + } + } +}